mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
Optimize site resource dialog layout
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Site } from '@/api/types'
|
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { TorrentInfo, SiteCategory } from '@/api/types'
|
import type { Site, TorrentInfo, SiteCategory } from '@/api/types'
|
||||||
import { formatFileSize } from '@core/utils/formatters'
|
import { formatFileSize } from '@core/utils/formatters'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
import AddDownloadDialog from '../dialog/AddDownloadDialog.vue'
|
import AddDownloadDialog from '../dialog/AddDownloadDialog.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
|
|
||||||
|
// 响应式断点
|
||||||
|
const display = useDisplay()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -23,84 +26,122 @@ const selectCategory = ref<number[]>([])
|
|||||||
// 全部分类
|
// 全部分类
|
||||||
const siteCategoryList = ref<SiteCategory[]>()
|
const siteCategoryList = ref<SiteCategory[]>()
|
||||||
|
|
||||||
// 分类选项
|
|
||||||
const categoryOptions = computed(() => {
|
|
||||||
return siteCategoryList.value?.map(item => {
|
|
||||||
return { title: item.desc, value: item.id }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 注册事件
|
// 注册事件
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
const resourceDataList = ref<TorrentInfo[]>([])
|
const resourceDataList = ref<TorrentInfo[]>([])
|
||||||
|
|
||||||
// 搜索
|
|
||||||
const resourceSearch = ref('')
|
|
||||||
|
|
||||||
// 总条数
|
|
||||||
const resourceTotalItems = ref(0)
|
|
||||||
|
|
||||||
// 每页条数
|
// 每页条数
|
||||||
const resourceItemsPerPage = ref(25)
|
const resourceItemsPerPage = ref(25)
|
||||||
|
|
||||||
|
// 当前页
|
||||||
|
const resourcePage = ref(1)
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const resourceLoading = ref(false)
|
const resourceLoading = ref(false)
|
||||||
|
|
||||||
// 种子元数据
|
// 种子元数据
|
||||||
const torrent = ref<TorrentInfo>()
|
const torrent = ref<TorrentInfo>()
|
||||||
|
|
||||||
|
// 添加下载对话框
|
||||||
|
const addDownloadDialog = ref(false)
|
||||||
|
|
||||||
|
// 移动端每页条数
|
||||||
|
const mobileItemsPerPage = 8
|
||||||
|
|
||||||
|
// 分类选项
|
||||||
|
const categoryOptions = computed(() => {
|
||||||
|
return siteCategoryList.value?.map(item => {
|
||||||
|
return { title: item.desc, value: item.id }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 总条数
|
||||||
|
const resourceTotalItems = computed(() => resourceDataList.value.length)
|
||||||
|
|
||||||
// 资源浏览表头
|
// 资源浏览表头
|
||||||
const resourceHeaders = [
|
const resourceHeaders = computed(() => [
|
||||||
{ title: t('dialog.siteResource.titleColumn'), key: 'title', sortable: false },
|
{ title: t('dialog.siteResource.titleColumn'), key: 'title', sortable: false },
|
||||||
{ title: t('dialog.siteResource.timeColumn'), key: 'pubdate', sortable: true },
|
{ title: t('dialog.siteResource.timeColumn'), key: 'pubdate', sortable: true },
|
||||||
{ title: t('dialog.siteResource.sizeColumn'), key: 'size', sortable: true },
|
{ title: t('dialog.siteResource.sizeColumn'), key: 'size', sortable: true },
|
||||||
{ title: t('dialog.siteResource.seedersColumn'), key: 'seeders', sortable: true },
|
{ title: t('dialog.siteResource.seedersColumn'), key: 'seeders', sortable: true },
|
||||||
{ title: t('dialog.siteResource.peersColumn'), key: 'peers', sortable: true },
|
{ title: t('dialog.siteResource.peersColumn'), key: 'peers', sortable: true },
|
||||||
{ title: '', key: 'actions', sortable: false },
|
{ title: '', key: 'actions', sortable: false },
|
||||||
]
|
])
|
||||||
|
|
||||||
|
// 输入框标签
|
||||||
|
const keywordFieldLabel = computed(() => {
|
||||||
|
return keyword.value ? '' : t('dialog.siteResource.searchKeyword')
|
||||||
|
})
|
||||||
|
|
||||||
|
const categoryFieldLabel = computed(() => {
|
||||||
|
return selectCategory.value.length > 0 ? '' : t('dialog.siteResource.resourceCategory')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 结果统计文案
|
||||||
|
const resultSummaryText = computed(() => {
|
||||||
|
if (locale.value.startsWith('zh')) {
|
||||||
|
return `共 ${resourceTotalItems.value} 条结果`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${resourceTotalItems.value} results`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 移动端分页总页数
|
||||||
|
const mobilePageCount = computed(() => {
|
||||||
|
return Math.max(1, Math.ceil(resourceTotalItems.value / mobileItemsPerPage))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 移动端分页数据
|
||||||
|
const mobileResourceList = computed(() => {
|
||||||
|
const start = (resourcePage.value - 1) * mobileItemsPerPage
|
||||||
|
|
||||||
|
return resourceDataList.value.slice(start, start + mobileItemsPerPage)
|
||||||
|
})
|
||||||
|
|
||||||
// 打开种子详情页面
|
// 打开种子详情页面
|
||||||
function openTorrentDetail(page_url: string) {
|
function openTorrentDetail(page_url: string) {
|
||||||
|
if (!page_url) return
|
||||||
window.open(page_url, '_blank')
|
window.open(page_url, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载种子文件
|
// 下载种子文件
|
||||||
async function downloadTorrentFile(enclosure: string) {
|
async function downloadTorrentFile(enclosure: string) {
|
||||||
|
if (!enclosure) return
|
||||||
window.open(enclosure, '_blank')
|
window.open(enclosure, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 促销Chip类
|
// 促销Chip类
|
||||||
function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) {
|
function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) {
|
||||||
if (downloadVolume === 0) return 'text-white bg-lime-500'
|
if (downloadVolume === 0) return 'text-white bg-lime-500'
|
||||||
else if (downloadVolume < 1) return 'text-white bg-green-500'
|
if (downloadVolume < 1) return 'text-white bg-green-500'
|
||||||
else if (uploadVolume !== 1) return 'text-white bg-sky-500'
|
if (uploadVolume !== 1) return 'text-white bg-sky-500'
|
||||||
else return 'text-white bg-gray-500'
|
|
||||||
|
return 'text-white bg-gray-500'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加下载
|
// 添加下载
|
||||||
async function addDownload(_torrent: any) {
|
async function addDownload(_torrent: TorrentInfo) {
|
||||||
torrent.value = _torrent
|
torrent.value = _torrent
|
||||||
addDownloadDialog.value = true
|
addDownloadDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加下载对话框
|
|
||||||
const addDownloadDialog = ref(false)
|
|
||||||
|
|
||||||
// 添加下载成功
|
// 添加下载成功
|
||||||
function addDownloadSuccess(url: string) {
|
function addDownloadSuccess(_url: string) {
|
||||||
addDownloadDialog.value = false
|
addDownloadDialog.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加下载失败
|
// 添加下载失败
|
||||||
function addDownloadError(error: string) {
|
function addDownloadError(_error: string) {
|
||||||
addDownloadDialog.value = false
|
addDownloadDialog.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用API,查询站点资源
|
// 调用API,查询站点资源
|
||||||
async function getResourceList() {
|
async function getResourceList() {
|
||||||
resourceLoading.value = true
|
resourceLoading.value = true
|
||||||
|
resourcePage.value = 1
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resourceDataList.value = await api.get(`site/resource/${props.site?.id}`, {
|
resourceDataList.value = await api.get(`site/resource/${props.site?.id}`, {
|
||||||
params: {
|
params: {
|
||||||
@@ -111,6 +152,7 @@ async function getResourceList() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceLoading.value = false
|
resourceLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,16 +165,31 @@ async function getSiteCategoryList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 装载时查询站点图标
|
watch([resourceItemsPerPage, resourceTotalItems, () => display.mdAndUp.value], () => {
|
||||||
|
if (display.mdAndUp.value) {
|
||||||
|
const maxPage = Math.max(1, Math.ceil(resourceTotalItems.value / resourceItemsPerPage.value))
|
||||||
|
if (resourcePage.value > maxPage) {
|
||||||
|
resourcePage.value = maxPage
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourcePage.value > mobilePageCount.value) {
|
||||||
|
resourcePage.value = mobilePageCount.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 装载时查询站点分类和资源
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getSiteCategoryList()
|
getSiteCategoryList()
|
||||||
getResourceList()
|
getResourceList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog scrollable fullscreen :scrim="false" transition="dialog-bottom-transition">
|
<VDialog scrollable :fullscreen="display.smAndDown.value" max-width="92rem" transition="dialog-bottom-transition">
|
||||||
<VCard>
|
<VCard class="site-resource-dialog">
|
||||||
<!-- Toolbar -->
|
|
||||||
<div>
|
<div>
|
||||||
<VToolbar color="primary" density="comfortable">
|
<VToolbar color="primary" density="comfortable">
|
||||||
<VToolbarTitle>{{ t('dialog.siteResource.browseTitle', { name: props.site?.name }) }}</VToolbarTitle>
|
<VToolbarTitle>{{ t('dialog.siteResource.browseTitle', { name: props.site?.name }) }}</VToolbarTitle>
|
||||||
@@ -144,45 +201,79 @@ onMounted(() => {
|
|||||||
</VToolbarItems>
|
</VToolbarItems>
|
||||||
</VToolbar>
|
</VToolbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3">
|
|
||||||
<VRow>
|
<div class="pa-3 pb-2">
|
||||||
<VCol cols="6" md="5">
|
<VSheet class="site-resource-filter-panel" rounded="lg" border>
|
||||||
<VTextField
|
<div class="site-resource-filter-panel__inner">
|
||||||
v-model="keyword"
|
<VRow class="site-resource-filter-row">
|
||||||
size="small"
|
<VCol cols="12" md="4">
|
||||||
density="compact"
|
<VTextField
|
||||||
:label="t('dialog.siteResource.searchKeyword')"
|
v-model="keyword"
|
||||||
clearable
|
class="site-resource-filter-input"
|
||||||
prepend-inner-icon="mdi-magnify"
|
size="small"
|
||||||
/>
|
density="compact"
|
||||||
</VCol>
|
variant="solo-filled"
|
||||||
<VCol cols="6" md="5">
|
flat
|
||||||
<VSelect
|
:label="keywordFieldLabel"
|
||||||
v-model="selectCategory"
|
clearable
|
||||||
:items="categoryOptions"
|
prepend-inner-icon="mdi-magnify"
|
||||||
size="small"
|
hide-details
|
||||||
density="compact"
|
@keyup.enter="getResourceList"
|
||||||
chips
|
/>
|
||||||
:label="t('dialog.siteResource.resourceCategory')"
|
</VCol>
|
||||||
multiple
|
<VCol cols="12" md="5">
|
||||||
clearable
|
<VSelect
|
||||||
prepend-inner-icon="mdi-folder"
|
v-model="selectCategory"
|
||||||
/>
|
:items="categoryOptions"
|
||||||
</VCol>
|
class="site-resource-filter-input"
|
||||||
<VCol cols="12" md="2" class="text-center">
|
size="small"
|
||||||
<VBtn variant="tonal" block prepend-icon="mdi-magnify" @click="getResourceList">
|
density="compact"
|
||||||
{{ t('dialog.siteResource.search') }}
|
variant="solo-filled"
|
||||||
</VBtn>
|
flat
|
||||||
</VCol>
|
chips
|
||||||
</VRow>
|
:label="categoryFieldLabel"
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
prepend-inner-icon="mdi-folder"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="3" class="d-flex align-center">
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
variant="flat"
|
||||||
|
block
|
||||||
|
size="default"
|
||||||
|
rounded="lg"
|
||||||
|
prepend-icon="mdi-magnify"
|
||||||
|
class="site-resource-search-btn"
|
||||||
|
@click="getResourceList"
|
||||||
|
>
|
||||||
|
{{ t('dialog.siteResource.search') }}
|
||||||
|
</VBtn>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
|
||||||
|
<div v-if="resourceTotalItems > 0" class="d-flex justify-space-between align-center flex-wrap gap-2 mt-3">
|
||||||
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
|
{{ resultSummaryText }}
|
||||||
|
</div>
|
||||||
|
<VChip size="small" color="primary" variant="tonal" class="site-resource-result-chip">
|
||||||
|
{{ resourceTotalItems }}
|
||||||
|
</VChip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VSheet>
|
||||||
</div>
|
</div>
|
||||||
<VCardText class="px-0 py-0 my-0">
|
|
||||||
|
<VCardText class="site-resource-content px-0 py-0 my-0">
|
||||||
<VDataTable
|
<VDataTable
|
||||||
|
v-if="display.mdAndUp.value"
|
||||||
|
v-model:page="resourcePage"
|
||||||
v-model:items-per-page="resourceItemsPerPage"
|
v-model:items-per-page="resourceItemsPerPage"
|
||||||
:headers="resourceHeaders"
|
:headers="resourceHeaders"
|
||||||
:items="resourceDataList"
|
:items="resourceDataList"
|
||||||
:items-length="resourceTotalItems"
|
:items-length="resourceTotalItems"
|
||||||
:search="resourceSearch"
|
|
||||||
:loading="resourceLoading"
|
:loading="resourceLoading"
|
||||||
density="compact"
|
density="compact"
|
||||||
item-value="title"
|
item-value="title"
|
||||||
@@ -191,60 +282,69 @@ onMounted(() => {
|
|||||||
hover
|
hover
|
||||||
:items-per-page-text="t('dialog.siteResource.itemsPerPage')"
|
:items-per-page-text="t('dialog.siteResource.itemsPerPage')"
|
||||||
:loading-text="t('dialog.siteResource.loading')"
|
:loading-text="t('dialog.siteResource.loading')"
|
||||||
class="h-full"
|
:items-per-page-options="[10, 25, 50, 100]"
|
||||||
|
height="100%"
|
||||||
|
class="h-full site-resource-table"
|
||||||
>
|
>
|
||||||
<template #item.title="{ item }">
|
<template #item.title="{ item }">
|
||||||
<a href="javascript:void(0)" @click.stop="addDownload(item)">
|
<button type="button" class="site-resource-title-btn text-start" @click.stop="addDownload(item)">
|
||||||
<div class="text-high-emphasis pt-1">
|
<div class="text-high-emphasis pt-1 font-weight-medium">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm my-1">
|
<div v-if="item.description" class="text-sm my-1 text-medium-emphasis">
|
||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
</div>
|
</div>
|
||||||
<VChip v-if="item.hit_and_run" variant="elevated" size="small" class="me-1 mb-1 text-white bg-black">
|
<div class="mt-2">
|
||||||
H&R
|
<VChip v-if="item.hit_and_run" variant="elevated" size="small" class="me-1 mb-1 text-white bg-black">
|
||||||
</VChip>
|
H&R
|
||||||
<VChip v-if="item.freedate_diff" variant="elevated" color="secondary" size="small" class="me-1 mb-1">
|
</VChip>
|
||||||
{{ item.freedate_diff }}
|
<VChip v-if="item.freedate_diff" variant="elevated" color="secondary" size="small" class="me-1 mb-1">
|
||||||
</VChip>
|
{{ item.freedate_diff }}
|
||||||
<VChip
|
</VChip>
|
||||||
v-for="(label, index) in item.labels"
|
<VChip
|
||||||
:key="index"
|
v-for="(label, index) in item.labels"
|
||||||
variant="elevated"
|
:key="index"
|
||||||
size="small"
|
variant="elevated"
|
||||||
color="primary"
|
size="small"
|
||||||
class="me-1 mb-1"
|
color="primary"
|
||||||
>
|
class="me-1 mb-1"
|
||||||
{{ label }}
|
>
|
||||||
</VChip>
|
{{ label }}
|
||||||
<VChip
|
</VChip>
|
||||||
v-if="item.downloadvolumefactor !== 1 || item.uploadvolumefactor !== 1"
|
<VChip
|
||||||
:class="getVolumeFactorClass(item.downloadvolumefactor, item.uploadvolumefactor)"
|
v-if="item.downloadvolumefactor !== 1 || item.uploadvolumefactor !== 1"
|
||||||
variant="elevated"
|
:class="getVolumeFactorClass(item.downloadvolumefactor, item.uploadvolumefactor)"
|
||||||
size="small"
|
variant="elevated"
|
||||||
class="me-1 mb-1"
|
size="small"
|
||||||
>
|
class="me-1 mb-1"
|
||||||
{{ item.volume_factor }}
|
>
|
||||||
</VChip>
|
{{ item.volume_factor }}
|
||||||
</a>
|
</VChip>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item.pubdate="{ item }">
|
<template #item.pubdate="{ item }">
|
||||||
<div>{{ item.date_elapsed }}</div>
|
<div>{{ item.date_elapsed }}</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm text-medium-emphasis">
|
||||||
{{ item.pubdate }}
|
{{ item.pubdate }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item.size="{ item }">
|
<template #item.size="{ item }">
|
||||||
<div class="text-nowrap whitespace-nowrap">
|
<div class="text-nowrap whitespace-nowrap">
|
||||||
{{ formatFileSize(item.size) }}
|
{{ formatFileSize(item.size) }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item.seeders="{ item }">
|
<template #item.seeders="{ item }">
|
||||||
<div>{{ item.seeders }}</div>
|
<div>{{ item.seeders }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item.peers="{ item }">
|
<template #item.peers="{ item }">
|
||||||
<div>{{ item.peers }}</div>
|
<div>{{ item.peers }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item.actions="{ item }">
|
<template #item.actions="{ item }">
|
||||||
<div class="me-n3">
|
<div class="me-n3">
|
||||||
<IconBtn>
|
<IconBtn>
|
||||||
@@ -268,11 +368,129 @@ onMounted(() => {
|
|||||||
</IconBtn>
|
</IconBtn>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #no-data>{{ t('dialog.siteResource.noData') }}</template>
|
<template #no-data>{{ t('dialog.siteResource.noData') }}</template>
|
||||||
</VDataTable>
|
</VDataTable>
|
||||||
|
|
||||||
|
<div v-else class="site-resource-mobile">
|
||||||
|
<div v-if="resourceLoading" class="px-4 py-6">
|
||||||
|
<VProgressLinear color="primary" indeterminate rounded />
|
||||||
|
<div class="text-center text-body-2 text-medium-emphasis mt-3">
|
||||||
|
{{ t('dialog.siteResource.loading') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="mobileResourceList.length > 0" class="px-3 pb-4">
|
||||||
|
<VCard
|
||||||
|
v-for="(item, index) in mobileResourceList"
|
||||||
|
:key="item.page_url || item.enclosure || `${item.title}-${index}`"
|
||||||
|
class="site-resource-card mb-3"
|
||||||
|
rounded="lg"
|
||||||
|
variant="tonal"
|
||||||
|
>
|
||||||
|
<VCardText class="pa-4">
|
||||||
|
<button type="button" class="site-resource-title-btn text-start" @click="addDownload(item)">
|
||||||
|
<div class="text-body-1 font-weight-medium text-high-emphasis">
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.description"
|
||||||
|
class="site-resource-card__description mt-2 text-body-2 text-medium-emphasis"
|
||||||
|
>
|
||||||
|
{{ item.description }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<VChip v-if="item.hit_and_run" variant="elevated" size="small" class="me-1 mb-1 text-white bg-black">
|
||||||
|
H&R
|
||||||
|
</VChip>
|
||||||
|
<VChip v-if="item.freedate_diff" variant="elevated" color="secondary" size="small" class="me-1 mb-1">
|
||||||
|
{{ item.freedate_diff }}
|
||||||
|
</VChip>
|
||||||
|
<VChip
|
||||||
|
v-for="(label, chipIndex) in item.labels"
|
||||||
|
:key="chipIndex"
|
||||||
|
variant="elevated"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
class="me-1 mb-1"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</VChip>
|
||||||
|
<VChip
|
||||||
|
v-if="item.downloadvolumefactor !== 1 || item.uploadvolumefactor !== 1"
|
||||||
|
:class="getVolumeFactorClass(item.downloadvolumefactor, item.uploadvolumefactor)"
|
||||||
|
variant="elevated"
|
||||||
|
size="small"
|
||||||
|
class="me-1 mb-1"
|
||||||
|
>
|
||||||
|
{{ item.volume_factor }}
|
||||||
|
</VChip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="site-resource-card__meta mt-4">
|
||||||
|
<div class="site-resource-card__meta-item">
|
||||||
|
<div class="text-caption text-medium-emphasis">{{ t('dialog.siteResource.timeColumn') }}</div>
|
||||||
|
<div class="text-body-2 font-weight-medium">{{ item.date_elapsed || item.pubdate || '-' }}</div>
|
||||||
|
<div v-if="item.pubdate" class="text-caption text-medium-emphasis mt-1">{{ item.pubdate }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-resource-card__meta-item">
|
||||||
|
<div class="text-caption text-medium-emphasis">{{ t('dialog.siteResource.sizeColumn') }}</div>
|
||||||
|
<div class="text-body-2 font-weight-medium">{{ formatFileSize(item.size) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-resource-card__meta-item">
|
||||||
|
<div class="text-caption text-medium-emphasis">{{ t('dialog.siteResource.seedersColumn') }}</div>
|
||||||
|
<div class="text-body-2 font-weight-medium">{{ item.seeders }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-resource-card__meta-item">
|
||||||
|
<div class="text-caption text-medium-emphasis">{{ t('dialog.siteResource.peersColumn') }}</div>
|
||||||
|
<div class="text-body-2 font-weight-medium">{{ item.peers }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="site-resource-card__actions mt-4">
|
||||||
|
<VBtn color="primary" variant="flat" block prepend-icon="mdi-download" @click="addDownload(item)">
|
||||||
|
{{ t('actionStep.addDownload') }}
|
||||||
|
</VBtn>
|
||||||
|
<div class="site-resource-card__secondary-actions mt-2">
|
||||||
|
<VBtn
|
||||||
|
variant="tonal"
|
||||||
|
prepend-icon="mdi-open-in-new"
|
||||||
|
@click="openTorrentDetail(item.page_url || '')"
|
||||||
|
>
|
||||||
|
{{ t('common.viewDetails') }}
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
v-if="item.enclosure?.startsWith('http')"
|
||||||
|
variant="tonal"
|
||||||
|
prepend-icon="mdi-tray-arrow-down"
|
||||||
|
@click="downloadTorrentFile(item.enclosure)"
|
||||||
|
>
|
||||||
|
{{ t('dialog.siteResource.downloadTorrent') }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
|
||||||
|
<VPagination
|
||||||
|
v-if="mobilePageCount > 1"
|
||||||
|
v-model="resourcePage"
|
||||||
|
:length="mobilePageCount"
|
||||||
|
:total-visible="0"
|
||||||
|
rounded="circle"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="px-4 py-10 text-center text-medium-emphasis">
|
||||||
|
{{ t('dialog.siteResource.noData') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<!-- 添加下载对话框 -->
|
|
||||||
<AddDownloadDialog
|
<AddDownloadDialog
|
||||||
v-if="addDownloadDialog"
|
v-if="addDownloadDialog"
|
||||||
v-model="addDownloadDialog"
|
v-model="addDownloadDialog"
|
||||||
@@ -285,7 +503,153 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.site-resource-dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-filter-row {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-filter-panel {
|
||||||
|
border-color: rgba(var(--v-border-color), calc(var(--v-border-opacity) * 0.9));
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, rgba(var(--v-theme-primary), 0.06), transparent 40%),
|
||||||
|
linear-gradient(180deg, rgba(var(--v-theme-surface), 0.98), rgba(var(--v-theme-surface), 0.93));
|
||||||
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-filter-panel__inner {
|
||||||
|
padding: 0.75rem 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-filter-input :deep(.v-field) {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
background: rgba(var(--v-theme-surface), 0.92);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(var(--v-border-color), calc(var(--v-border-opacity) * 0.8));
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-filter-input :deep(.v-field__prepend-inner) {
|
||||||
|
color: rgba(var(--v-theme-primary), 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-search-btn {
|
||||||
|
box-shadow: 0 8px 18px rgba(var(--v-theme-primary), 0.18);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
min-block-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-result-chip {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-title-btn {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
inline-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-content {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-block-size: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-table {
|
||||||
|
block-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-table :deep(.v-data-table) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
block-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-table :deep(.v-data-table__wrapper) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-block-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-table :deep(.v-table__wrapper) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-block-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-table :deep(.v-data-table-footer) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.v-table th {
|
.v-table th {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.site-resource-card {
|
||||||
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(var(--v-theme-surface), 0.98), rgba(var(--v-theme-surface), 0.92)),
|
||||||
|
linear-gradient(135deg, rgba(var(--v-theme-primary), 0.05), rgba(var(--v-theme-secondary), 0.03));
|
||||||
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 6%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-card__description {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-card__meta {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.55rem;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-card__meta-item {
|
||||||
|
border: 1px solid rgba(var(--v-border-color), calc(var(--v-border-opacity) * 0.7));
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
background: rgba(var(--v-theme-surface), 0.78);
|
||||||
|
min-block-size: 0;
|
||||||
|
padding-block: 0.55rem;
|
||||||
|
padding-inline: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-card__meta-item :deep(.text-caption) {
|
||||||
|
font-size: 0.72rem !important;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-card__meta-item :deep(.text-body-2) {
|
||||||
|
font-size: 0.82rem !important;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-card__secondary-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-card__secondary-actions :deep(.v-btn) {
|
||||||
|
flex: 1 1 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= 960px) {
|
||||||
|
.site-resource-dialog {
|
||||||
|
block-size: min(88vh, 960px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 959px) {
|
||||||
|
.site-resource-dialog {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-resource-filter-panel__inner {
|
||||||
|
padding: 0.7rem 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user