mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 09:20:18 +08:00
refactor: standardize app property names and improve TypeScript definitions
- Updated property names in AppCard.vue, AppDetailModal.vue, AppGrid.vue, and other components to use camelCase for consistency. - Enhanced TypeScript definitions for props and emits in various components to improve type safety. - Refactored download status handling in processInstall.ts to align with updated App interface. - Improved error handling and type definitions in DownloadDetail.vue and related components. - Added optional properties and refined existing interfaces in typedefinition.ts for better clarity and usability.
This commit is contained in:
@@ -7,36 +7,36 @@
|
||||
:class="['h-full w-full object-cover transition-opacity duration-300', isLoaded ? 'opacity-100' : 'opacity-0']" />
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-1 overflow-hidden">
|
||||
<div class="truncate text-base font-semibold text-slate-900 dark:text-white">{{ app.Name || '' }}</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">{{ app.Pkgname || '' }} · {{ app.Version || '' }}</div>
|
||||
<div class="truncate text-base font-semibold text-slate-900 dark:text-white">{{ app.name || '' }}</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">{{ app.pkgname || '' }} · {{ app.version || '' }}</div>
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">{{ description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps, defineEmits, onMounted, onBeforeUnmount, ref, watch } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { APM_STORE_BASE_URL } from '../global/storeConfig';
|
||||
import type { App } from '../global/typedefinition';
|
||||
|
||||
const props = defineProps({
|
||||
app: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const props = defineProps<{
|
||||
app: App
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['open-detail']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'open-detail', app: App): void
|
||||
}>();
|
||||
|
||||
const iconImg = ref(null);
|
||||
const iconImg = ref<HTMLImageElement | null>(null);
|
||||
const isLoaded = ref(false);
|
||||
const loadedIcon = ref('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"%3E%3Crect fill="%23f0f0f0" width="100" height="100"/%3E%3C/svg%3E');
|
||||
|
||||
const iconPath = computed(() => {
|
||||
return `${APM_STORE_BASE_URL}/${window.apm_store.arch}/${props.app._category}/${props.app.Pkgname}/icon.png`;
|
||||
return `${APM_STORE_BASE_URL}/${window.apm_store.arch}/${props.app.category}/${props.app.pkgname}/icon.png`;
|
||||
});
|
||||
|
||||
const description = computed(() => {
|
||||
const more = props.app.More || '';
|
||||
const more = props.app.more || '';
|
||||
return more.substring(0, 80) + (more.length > 80 ? '...' : '');
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ const openDetail = () => {
|
||||
emit('open-detail', props.app);
|
||||
};
|
||||
|
||||
let observer = null;
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
// 创建 Intersection Observer
|
||||
@@ -57,13 +57,15 @@ onMounted(() => {
|
||||
img.onload = () => {
|
||||
loadedIcon.value = iconPath.value;
|
||||
isLoaded.value = true;
|
||||
observer.unobserve(entry.target);
|
||||
if (observer)
|
||||
observer.unobserve(entry.target);
|
||||
};
|
||||
img.onerror = () => {
|
||||
// 加载失败时使用默认图标
|
||||
loadedIcon.value = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"%3E%3Crect fill="%23e0e0e0" width="100" height="100"/%3E%3Ctext x="50" y="50" text-anchor="middle" dy=".3em" fill="%23999" font-size="14"%3ENo Icon%3C/text%3E%3C/svg%3E';
|
||||
isLoaded.value = true;
|
||||
observer.unobserve(entry.target);
|
||||
if (observer)
|
||||
observer.unobserve(entry.target);
|
||||
};
|
||||
img.src = iconPath.value;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white">{{ app?.Name || '' }}</p>
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white">{{ app?.name || '' }}</p>
|
||||
<!-- Close button for mobile layout could be considered here if needed, but for now sticking to desktop layout logic mainly -->
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">{{ app?.Pkgname || '' }} · {{ app?.Version || '' }}</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">{{ app?.pkgname || '' }} · {{ app?.version || '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 lg:ml-auto">
|
||||
@@ -58,106 +58,98 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid gap-4 sm:grid-cols-2">
|
||||
<div v-if="app?.Author" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<div v-if="app?.author" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<p class="text-xs uppercase tracking-wide text-slate-400">作者</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.Author }}</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.author }}</p>
|
||||
</div>
|
||||
<div v-if="app?.Contributor" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<div v-if="app?.contributor" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<p class="text-xs uppercase tracking-wide text-slate-400">贡献者</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.Contributor }}</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.contributor }}</p>
|
||||
</div>
|
||||
<div v-if="app?.Size" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<div v-if="app?.size" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<p class="text-xs uppercase tracking-wide text-slate-400">大小</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.Size }}</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.size }}</p>
|
||||
</div>
|
||||
<div v-if="app?.Update" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<div v-if="app?.update" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<p class="text-xs uppercase tracking-wide text-slate-400">更新时间</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.Update }}</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.update }}</p>
|
||||
</div>
|
||||
<div v-if="app?.Website" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<div v-if="app?.website" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<p class="text-xs uppercase tracking-wide text-slate-400">网站</p>
|
||||
<a :href="app.Website" target="_blank"
|
||||
class="text-sm font-medium text-brand hover:underline">{{ app.Website }}</a>
|
||||
<a :href="app.website" target="_blank"
|
||||
class="text-sm font-medium text-brand hover:underline">{{ app.website }}</a>
|
||||
</div>
|
||||
<div v-if="app?.Version" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<div v-if="app?.version" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<p class="text-xs uppercase tracking-wide text-slate-400">版本</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.Version }}</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.version }}</p>
|
||||
</div>
|
||||
<div v-if="app?.Tags" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<div v-if="app?.tags" class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60">
|
||||
<p class="text-xs uppercase tracking-wide text-slate-400">标签</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.Tags }}</p>
|
||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">{{ app.tags }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="app?.More && app.More.trim() !== ''" class="mt-6 space-y-3">
|
||||
<div v-if="app?.more && app.more.trim() !== ''" class="mt-6 space-y-3">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">应用详情</h3>
|
||||
<div
|
||||
class="max-h-60 space-y-2 overflow-y-auto rounded-2xl border border-slate-200/60 bg-slate-50/80 p-4 text-sm leading-relaxed text-slate-600 dark:border-slate-800/60 dark:bg-slate-900/60 dark:text-slate-300"
|
||||
v-html="app.More.replace(/\n/g, '<br>')"></div>
|
||||
v-html="app.more.replace(/\n/g, '<br>')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps, defineEmits, useAttrs, ref } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useAttrs } from 'vue';
|
||||
import { APM_STORE_BASE_URL } from '../global/storeConfig';
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
import type { App } from '../global/typedefinition';
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
app: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
screenshots: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
isinstalled: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
app: App | null;
|
||||
screenshots: string[];
|
||||
isinstalled: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['close', 'install', 'remove', 'open-preview']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'install'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'open-preview', index: number): void;
|
||||
}>();
|
||||
|
||||
|
||||
const installFeedback = ref(false);
|
||||
|
||||
const iconPath = computed(() => {
|
||||
return props.app ? `${APM_STORE_BASE_URL}/${window.apm_store.arch}/${props.app._category}/${props.app.Pkgname}/icon.png` : '';
|
||||
if (!props.app) return '';
|
||||
return `${APM_STORE_BASE_URL}/${window.apm_store.arch}/${props.app.category}/${props.app.pkgname}/icon.png`;
|
||||
});
|
||||
|
||||
const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const installFeedback = ref(false);
|
||||
|
||||
const handleInstall = () => {
|
||||
emit('install');
|
||||
installFeedback.value = true;
|
||||
emit('install');
|
||||
setTimeout(() => {
|
||||
installFeedback.value = false;
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
emit('remove');
|
||||
}
|
||||
|
||||
const openPreview = (index) => {
|
||||
const openPreview = (index: number) => {
|
||||
emit('open-preview', index);
|
||||
};
|
||||
|
||||
const hideImage = (event) => {
|
||||
if (event?.target) {
|
||||
event.target.style.display = 'none';
|
||||
}
|
||||
const hideImage = (e: Event) => {
|
||||
(e.target as HTMLElement).style.display = 'none';
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -14,20 +14,16 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import AppCard from './AppCard.vue';
|
||||
import type { App } from '@/global/typedefinition';
|
||||
|
||||
defineProps({
|
||||
apps: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
defineProps<{
|
||||
apps: App[];
|
||||
loading: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits(['open-detail']);
|
||||
defineEmits<{
|
||||
(e: 'open-detail', app: App): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -18,28 +18,26 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import TopActions from './TopActions.vue';
|
||||
|
||||
const props = defineProps({
|
||||
searchQuery: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
appsCount: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const props = defineProps<{
|
||||
searchQuery: string;
|
||||
appsCount: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update-search', 'update', 'list']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'update-search', query: string): void;
|
||||
(e: 'update'): void;
|
||||
(e: 'list'): void;
|
||||
}>();
|
||||
|
||||
const localSearchQuery = ref(props.searchQuery || '');
|
||||
const timeoutId = ref(null);
|
||||
const timeoutId = ref<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const debounceSearch = () => {
|
||||
clearTimeout(timeoutId.value);
|
||||
if (timeoutId.value) clearTimeout(timeoutId.value);
|
||||
timeoutId.value = setTimeout(() => {
|
||||
emit('update-search', localSearchQuery.value);
|
||||
}, 220);
|
||||
|
||||
@@ -33,37 +33,28 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import ThemeToggle from './ThemeToggle.vue';
|
||||
import amberLogo from '../assets/imgs/amber-pm-logo.png';
|
||||
|
||||
defineProps({
|
||||
categories: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
activeCategory: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
categoryCounts: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isDarkTheme: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
defineProps<{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
categories: Record<string, any>;
|
||||
activeCategory: string;
|
||||
categoryCounts: Record<string, number>;
|
||||
isDarkTheme: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['toggle-theme', 'select-category']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'toggle-theme'): void;
|
||||
(e: 'select-category', category: string): void;
|
||||
}>();
|
||||
|
||||
const toggleTheme = () => {
|
||||
emit('toggle-theme');
|
||||
};
|
||||
|
||||
const selectCategory = (category) => {
|
||||
const selectCategory = (category: string) => {
|
||||
emit('select-category', category);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -120,21 +120,23 @@
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import type { DownloadItem } from '../global/typedefinition';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
download: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
download: DownloadItem | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'pause', download: DownloadItem): void;
|
||||
(e: 'resume', download: DownloadItem): void;
|
||||
(e: 'cancel', download: DownloadItem): void;
|
||||
(e: 'retry', download: DownloadItem): void;
|
||||
(e: 'open-app', download: DownloadItem): void;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['close', 'pause', 'resume', 'cancel', 'retry', 'open-app']);
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
@@ -157,15 +159,19 @@ const cancel = () => {
|
||||
};
|
||||
|
||||
const retry = () => {
|
||||
emit('retry', props.download.id);
|
||||
if (props.download) {
|
||||
emit('retry', props.download);
|
||||
}
|
||||
};
|
||||
|
||||
const openApp = () => {
|
||||
emit('open-app', props.download);
|
||||
if (props.download) {
|
||||
emit('open-app', props.download);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': '等待中',
|
||||
'downloading': '下载中',
|
||||
'installing': '安装中',
|
||||
@@ -177,29 +183,29 @@ const getStatusText = (status) => {
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
const formatSize = (bytes) => {
|
||||
const formatSize = (bytes: number) => {
|
||||
if (!bytes) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + units[i];
|
||||
};
|
||||
|
||||
const formatSpeed = (bytesPerSecond) => {
|
||||
const formatSpeed = (bytesPerSecond: number) => {
|
||||
return formatSize(bytesPerSecond) + '/s';
|
||||
};
|
||||
|
||||
const formatTime = (seconds) => {
|
||||
const formatTime = (seconds: number) => {
|
||||
if (seconds < 60) return `${seconds}秒`;
|
||||
if (seconds < 3600) return `${Math.floor(seconds / 60)}分钟`;
|
||||
return `${Math.floor(seconds / 3600)}小时${Math.floor((seconds % 3600) / 60)}分钟`;
|
||||
};
|
||||
|
||||
const formatDate = (timestamp) => {
|
||||
const formatDate = (timestamp: number) => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString('zh-CN');
|
||||
};
|
||||
|
||||
const formatLogTime = (timestamp) => {
|
||||
const formatLogTime = (timestamp: number) => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString('zh-CN');
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<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.id)">
|
||||
title="重试" @click.stop="retryDownload(download)">
|
||||
<i class="fas fa-redo"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -70,24 +70,23 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import type { DownloadItem } from '../global/typedefinition';
|
||||
|
||||
const props = defineProps({
|
||||
downloads: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
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 emit = defineEmits([
|
||||
'pause',
|
||||
'resume',
|
||||
'cancel',
|
||||
'retry',
|
||||
'clear-completed',
|
||||
'show-detail'
|
||||
]);
|
||||
|
||||
const isExpanded = ref(false);
|
||||
|
||||
@@ -101,27 +100,27 @@ const toggleExpand = () => {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
};
|
||||
|
||||
const pauseDownload = (id) => {
|
||||
const pauseDownload = (id: string) => {
|
||||
// emit('pause', id);
|
||||
};
|
||||
|
||||
const resumeDownload = (id) => {
|
||||
const resumeDownload = (id: string) => {
|
||||
// emit('resume', id);
|
||||
};
|
||||
|
||||
const cancelDownload = (id) => {
|
||||
const cancelDownload = (id: string) => {
|
||||
// emit('cancel', id);
|
||||
};
|
||||
|
||||
const retryDownload = (id) => {
|
||||
emit('retry', id);
|
||||
const retryDownload = (download: DownloadItem) => {
|
||||
emit('retry', download);
|
||||
};
|
||||
|
||||
const clearCompleted = () => {
|
||||
emit('clear-completed');
|
||||
};
|
||||
|
||||
const showDownloadDetail = (download) => {
|
||||
const showDownloadDetail = (download: DownloadItem) => {
|
||||
emit('show-detail', download);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -73,28 +73,20 @@
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import type { App } from '@/global/typedefinition';
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
apps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
defineProps<{
|
||||
show: boolean;
|
||||
apps: App[];
|
||||
loading: boolean;
|
||||
error: string;
|
||||
}>();
|
||||
|
||||
defineEmits(['close', 'refresh', 'uninstall']);
|
||||
defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'refresh'): void;
|
||||
(e: 'uninstall', app: App): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -35,25 +35,20 @@
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
screenshots: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
currentScreenIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
screenshots: string[];
|
||||
currentScreenIndex: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['close', 'prev', 'next']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'prev'): void;
|
||||
(e: 'next'): void;
|
||||
}>();
|
||||
|
||||
const currentScreenshot = computed(() => {
|
||||
return props.screenshots[props.currentScreenIndex] || '';
|
||||
|
||||
@@ -12,17 +12,14 @@
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
isDark: boolean;
|
||||
}>();
|
||||
|
||||
defineProps({
|
||||
isDark: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['toggle']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'toggle'): void;
|
||||
}>();
|
||||
|
||||
const toggle = () => {
|
||||
emit('toggle');
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineEmits } from 'vue';
|
||||
|
||||
const emit = defineEmits(['update', 'list']);
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'update'): void;
|
||||
(e: 'list'): void;
|
||||
}>();
|
||||
|
||||
const handleUpdate = () => {
|
||||
emit('update');
|
||||
|
||||
@@ -56,30 +56,28 @@
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps, defineEmits, ref, watch, nextTick, onUnmounted } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, nextTick, onUnmounted } from 'vue';
|
||||
import type { App } from '@/global/typedefinition';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
app: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
app: App | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['close', 'success']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'success'): void;
|
||||
}>();
|
||||
|
||||
const uninstalling = ref(false);
|
||||
const completed = ref(false);
|
||||
const logs = ref([]);
|
||||
const logs = ref<string[]>([]);
|
||||
const error = ref('');
|
||||
const logEnd = ref(null);
|
||||
const logEnd = ref<HTMLElement | null>(null);
|
||||
|
||||
const appName = computed(() => props.app?.Name || props.app?.name || '未知应用');
|
||||
const appPkg = computed(() => props.app?.Pkgname || props.app?.pkgname || '');
|
||||
const appName = computed(() => props.app?.name || '未知应用');
|
||||
const appPkg = computed(() => props.app?.pkgname || '');
|
||||
|
||||
const handleClose = () => {
|
||||
if (uninstalling.value && !completed.value) return; // Prevent closing while uninstalling
|
||||
@@ -113,7 +111,8 @@ const confirmUninstall = () => {
|
||||
};
|
||||
|
||||
// Listeners
|
||||
const onProgress = (_event, chunk) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const onProgress = (_event: any, chunk: string) => {
|
||||
if (!uninstalling.value) return;
|
||||
// Split by newline but handle chunks correctly?
|
||||
// For simplicity, just appending lines if chunk contains newlines, or appending to last line?
|
||||
@@ -124,7 +123,8 @@ const onProgress = (_event, chunk) => {
|
||||
scrollToBottom();
|
||||
};
|
||||
|
||||
const onComplete = (_event, result) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const onComplete = (_event: any, result: { success: boolean; message: any }) => {
|
||||
if (!uninstalling.value) return; // Ignore if not current session
|
||||
|
||||
const msgObj = typeof result.message === 'string' ? JSON.parse(result.message) : result.message;
|
||||
|
||||
@@ -79,32 +79,23 @@
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import type { UpdateAppItem } from '@/global/typedefinition';
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
apps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
hasSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
defineProps<{
|
||||
show: boolean;
|
||||
apps: UpdateAppItem[];
|
||||
loading: boolean;
|
||||
error: string;
|
||||
hasSelected: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits(['close', 'refresh', 'toggle-all', 'upgrade-selected', 'upgrade-one']);
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'refresh'): void;
|
||||
(e: 'toggle-all'): void;
|
||||
(e: 'upgrade-selected'): void;
|
||||
(e: 'upgrade-one', app: UpdateAppItem): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user