mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-11 18:10:49 +08:00
201 lines
6.2 KiB
Vue
201 lines
6.2 KiB
Vue
<script setup lang="ts">
|
|
import type { Site } from '@/api/types'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
// 多语言支持
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps({
|
|
sites: {
|
|
type: Array as PropType<Site[]>,
|
|
required: true,
|
|
},
|
|
selected: Array as PropType<Number[]>,
|
|
})
|
|
|
|
// 定义事件
|
|
const emit = defineEmits(['close', 'search', 'reload'])
|
|
|
|
// 过滤词
|
|
const siteFilter = ref('')
|
|
|
|
// 已选择站点
|
|
const selectedSites = ref<any[]>(props.selected || [])
|
|
|
|
watch(
|
|
() => props.selected,
|
|
value => {
|
|
if (selectedSites.value.length == 0 && value) {
|
|
selectedSites.value = value
|
|
}
|
|
},
|
|
)
|
|
|
|
// 全选/全不选按钮文字
|
|
const checkAllText = computed(() => {
|
|
return selectedSites.value.length < props.sites?.length
|
|
? t('dialog.searchSite.selectAll')
|
|
: t('dialog.searchSite.deselectAll')
|
|
})
|
|
|
|
// 全选/全不选
|
|
const checkAllSitesorNot = () => {
|
|
if (selectedSites.value.length < props.sites?.length) {
|
|
selectedSites.value = props.sites?.map((item: Site) => item.id)
|
|
} else {
|
|
selectedSites.value = []
|
|
}
|
|
}
|
|
|
|
// 根据筛选条件过滤站点
|
|
const filteredSites = computed(() => {
|
|
if (!siteFilter.value) return props.sites
|
|
const filter = siteFilter.value.toLowerCase()
|
|
return props.sites?.filter((site: Site) => site.name.toLowerCase().includes(filter))
|
|
})
|
|
</script>
|
|
<template>
|
|
<!-- Site Selection Dialog -->
|
|
<DialogWrapper max-width="40rem" fullscreen-mobile>
|
|
<VCard class="site-dialog">
|
|
<VCardItem>
|
|
<template #prepend>
|
|
<VIcon icon="mdi-web-check" />
|
|
</template>
|
|
<VCardTitle>
|
|
{{ t('dialog.searchSite.selectSites') }}
|
|
</VCardTitle>
|
|
</VCardItem>
|
|
<VDialogCloseBtn @click="emit('close')" />
|
|
<VDivider />
|
|
<VCardText style="max-block-size: 420px" class="overflow-y-auto px-4 py-4">
|
|
<!-- 站点列表 -->
|
|
<div v-if="filteredSites.length > 0">
|
|
<!-- 选择操作 -->
|
|
<div class="d-flex align-center mb-4">
|
|
<VBtn
|
|
size="small"
|
|
:color="selectedSites.length < sites.length ? 'primary' : 'error'"
|
|
@click="checkAllSitesorNot"
|
|
class="me-2"
|
|
rounded="pill"
|
|
variant="flat"
|
|
>
|
|
<VIcon start size="small">
|
|
{{ selectedSites.length < sites.length ? 'mdi-check-all' : 'mdi-close-circle-outline' }}
|
|
</VIcon>
|
|
{{ checkAllText }}
|
|
</VBtn>
|
|
<div
|
|
class="text-body-2 font-weight-medium"
|
|
:class="selectedSites.length > 0 ? 'text-primary' : 'text-medium-emphasis'"
|
|
>
|
|
{{ t('dialog.searchSite.searchAllSites', { selected: selectedSites.length, total: sites.length }) }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 站点选择器 -->
|
|
<VRow dense>
|
|
<VCol v-for="site in filteredSites" :key="site.id" cols="6" sm="6" md="4">
|
|
<VHover v-slot="{ isHovering, props }">
|
|
<div
|
|
v-bind="props"
|
|
:class="[
|
|
'site-checkbox-wrapper pa-2 pa-sm-3 rounded-lg d-flex align-center',
|
|
{
|
|
'site-selected': selectedSites.includes(site.id),
|
|
'site-hover': isHovering && !selectedSites.includes(site.id),
|
|
},
|
|
]"
|
|
@click="
|
|
() => {
|
|
const index = selectedSites.indexOf(site.id)
|
|
if (index === -1) {
|
|
selectedSites.push(site.id)
|
|
} else {
|
|
selectedSites.splice(index, 1)
|
|
}
|
|
}
|
|
"
|
|
>
|
|
<VIcon
|
|
:icon="selectedSites.includes(site.id) ? 'mdi-check-circle' : 'mdi-checkbox-blank-circle-outline'"
|
|
:color="selectedSites.includes(site.id) ? 'primary' : 'medium-emphasis'"
|
|
class="me-2"
|
|
size="small"
|
|
/>
|
|
<span :class="['text-body-2 site-name', { 'font-weight-medium': selectedSites.includes(site.id) }]">
|
|
{{ site.name }}
|
|
</span>
|
|
</div>
|
|
</VHover>
|
|
</VCol>
|
|
</VRow>
|
|
</div>
|
|
<div v-else class="text-center py-8 empty-site-state">
|
|
<div class="search-icon-wrapper mb-4 mx-auto warning">
|
|
<VIcon icon="mdi-alert-circle-outline" size="large" color="warning" />
|
|
</div>
|
|
<div class="text-h6 font-weight-medium mb-2">{{ t('torrent.noMatchingResults') }}</div>
|
|
<div class="text-subtitle-1 text-medium-emphasis mb-4">
|
|
{{ siteFilter ? t('site.noFilterData') : t('site.sitesWillBeShownHere') }}
|
|
</div>
|
|
<VBtn
|
|
v-if="siteFilter"
|
|
color="primary"
|
|
variant="flat"
|
|
class="mt-3"
|
|
prepend-icon="mdi-refresh"
|
|
@click="siteFilter = ''"
|
|
>
|
|
{{ t('torrent.clearFilters') }}
|
|
</VBtn>
|
|
<VBtn v-else color="primary" variant="flat" class="mt-3" prepend-icon="mdi-refresh" @click="emit('reload')">
|
|
{{ t('common.loading') }}
|
|
</VBtn>
|
|
</div>
|
|
</VCardText>
|
|
|
|
<VCardActions class="pt-3">
|
|
<VSpacer />
|
|
<VBtn
|
|
color="primary"
|
|
:disabled="selectedSites.length === 0"
|
|
@click="emit('search', selectedSites)"
|
|
prepend-icon="mdi-magnify"
|
|
class="d-flex align-center justify-center px-5"
|
|
>
|
|
{{ t('common.search') }}
|
|
</VBtn>
|
|
</VCardActions>
|
|
</VCard>
|
|
</DialogWrapper>
|
|
</template>
|
|
<style scoped>
|
|
.site-checkbox-wrapper {
|
|
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
|
cursor: pointer;
|
|
transition: transform 0.2s ease, background-color 0.2s ease;
|
|
}
|
|
|
|
.site-checkbox-wrapper:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.site-name {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.site-selected {
|
|
border-color: rgba(var(--v-theme-primary), 0.2);
|
|
background-color: rgba(var(--v-theme-primary), 0.08);
|
|
color: rgb(var(--v-theme-primary));
|
|
}
|
|
|
|
.site-hover {
|
|
background-color: rgba(var(--v-theme-primary), 0.04);
|
|
}
|
|
</style>
|