Files
urldb/web/pages/index.vue

479 lines
17 KiB
Vue
Raw Normal View History

2025-07-11 00:49:41 +08:00
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
2025-07-10 13:58:28 +08:00
<div class="max-w-7xl mx-auto">
<!-- 头部 -->
2025-07-11 00:49:41 +08:00
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center relative">
2025-07-10 13:58:28 +08:00
<h1 class="text-2xl sm:text-3xl font-bold mb-4">
2025-07-11 00:49:41 +08:00
<a href="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">网盘资源管理系统</a>
2025-07-10 13:58:28 +08:00
</h1>
2025-07-10 21:14:17 +08:00
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4 right-4 top-0 absolute">
<NuxtLink
v-if="!userStore.isAuthenticated"
to="/login"
class="w-full sm:w-auto px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
2025-07-10 13:58:28 +08:00
>
2025-07-10 21:14:17 +08:00
<i class="fas fa-sign-in-alt"></i> 登录
</NuxtLink>
2025-07-10 13:58:28 +08:00
<NuxtLink
2025-07-10 21:14:17 +08:00
v-if="userStore.isAuthenticated"
2025-07-10 13:58:28 +08:00
to="/admin"
2025-07-10 21:14:17 +08:00
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
2025-07-10 13:58:28 +08:00
>
2025-07-10 21:14:17 +08:00
<i class="fas fa-user-shield"></i> 管理后台
2025-07-10 13:58:28 +08:00
</NuxtLink>
</nav>
2025-07-10 01:27:35 +08:00
</div>
2025-07-10 13:58:28 +08:00
<!-- 搜索区域 -->
<div class="w-full max-w-3xl mx-auto mb-4 sm:mb-8 px-2 sm:px-0">
<div class="relative">
<input
v-model="searchQuery"
@keyup="debounceSearch"
type="text"
2025-07-11 00:49:41 +08:00
class="w-full px-4 py-3 rounded-full border-2 border-gray-300 dark:border-gray-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500 transition-all"
2025-07-10 13:58:28 +08:00
placeholder="输入文件名或链接进行搜索..."
/>
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
<i class="fas fa-search text-gray-400"></i>
2025-07-10 01:27:35 +08:00
</div>
</div>
2025-07-10 13:58:28 +08:00
<!-- 平台类型筛选 -->
<div class="mt-3 flex flex-wrap gap-2" id="platformFilters">
<button
2025-07-11 00:49:41 +08:00
class="px-2 py-1 text-xs rounded-full bg-slate-800 dark:bg-gray-700 text-white dark:text-gray-100 active-filter"
2025-07-10 13:58:28 +08:00
@click="filterByPlatform('')"
>
全部
</button>
<button
v-for="platform in platforms"
:key="platform.id"
2025-07-11 00:49:41 +08:00
class="px-2 py-1 text-xs rounded-full bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-100 hover:bg-gray-300 dark:hover:bg-gray-700 transition-colors"
2025-07-10 13:58:28 +08:00
@click="filterByPlatform(platform.id)"
>
{{ getPlatformIcon(platform.name) }} {{ platform.name }}
</button>
2025-07-10 01:27:35 +08:00
</div>
2025-07-10 13:58:28 +08:00
<!-- 统计信息 -->
2025-07-11 00:49:41 +08:00
<div class="flex justify-between mt-3 text-sm text-gray-600 dark:text-gray-300 px-2">
2025-07-10 01:27:35 +08:00
<div class="flex items-center">
2025-07-10 13:58:28 +08:00
<i class="fas fa-calendar-day text-pink-600 mr-1"></i>
今日更新: <span class="font-medium text-pink-600 ml-1 count-up" :data-target="todayUpdates">0</span>
2025-07-10 01:27:35 +08:00
</div>
<div class="flex items-center">
2025-07-10 13:58:28 +08:00
<i class="fas fa-database text-blue-600 mr-1"></i>
总资源数: <span class="font-medium text-blue-600 ml-1 count-up" :data-target="stats?.total_resources || 0">0</span>
2025-07-10 01:27:35 +08:00
</div>
</div>
</div>
<!-- 资源列表 -->
2025-07-11 00:49:41 +08:00
<div class="overflow-x-auto bg-white dark:bg-gray-800 rounded-lg shadow">
2025-07-10 13:58:28 +08:00
<table class="w-full">
<thead>
2025-07-11 00:49:41 +08:00
<tr class="bg-slate-800 dark:bg-gray-700 text-white dark:text-gray-100">
2025-07-10 13:58:28 +08:00
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm">
<div class="flex items-center">
<i class="fas fa-cloud mr-1 text-gray-300"></i> 文件名
</div>
</th>
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm hidden sm:table-cell">链接</th>
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm">更新时间</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr v-if="loading" class="text-center py-8">
2025-07-11 00:49:41 +08:00
<td colspan="3" class="text-gray-500 dark:text-gray-400">
2025-07-10 13:58:28 +08:00
<i class="fas fa-spinner fa-spin mr-2"></i>加载中...
</td>
</tr>
<tr v-else-if="resources.length === 0" class="text-center py-8">
2025-07-11 00:49:41 +08:00
<td colspan="3" class="text-gray-500 dark:text-gray-400">暂无数据</td>
2025-07-10 13:58:28 +08:00
</tr>
<tr
v-for="resource in (resources as unknown as ExtendedResource[])"
:key="resource.id"
2025-07-11 00:49:41 +08:00
:class="isUpdatedToday(resource.updated_at) ? 'hover:bg-pink-50 dark:hover:bg-pink-900 bg-pink-50/30 dark:bg-pink-900/30' : 'hover:bg-gray-50 dark:hover:bg-gray-800'"
2025-07-10 13:58:28 +08:00
>
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm">
<div class="flex items-start">
<span class="mr-2 flex-shrink-0">{{ getPlatformIcon(getPlatformName(resource.pan_id || 0)) }}</span>
<span class="break-words">{{ resource.title }}</span>
</div>
<div class="sm:hidden mt-1">
<button
class="text-blue-600 hover:text-blue-800 text-xs flex items-center gap-1 show-link-btn"
@click="toggleLink(resource)"
>
<i class="fas fa-eye"></i> 显示链接
</button>
<a
v-if="resource.showLink"
:href="resource.url"
target="_blank"
2025-07-11 00:49:41 +08:00
class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline text-xs break-all"
2025-07-10 13:58:28 +08:00
>
{{ resource.url }}
</a>
</div>
</td>
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm hidden sm:table-cell">
<button
class="text-blue-600 hover:text-blue-800 flex items-center gap-1 show-link-btn"
@click="toggleLink(resource)"
>
<i class="fas fa-eye"></i> 显示链接
</button>
<a
v-if="resource.showLink"
:href="resource.url"
target="_blank"
2025-07-11 00:49:41 +08:00
class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline"
2025-07-10 13:58:28 +08:00
>
{{ resource.url }}
</a>
</td>
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-gray-500" :title="resource.updated_at">
{{ formatRelativeTime(resource.updated_at) }}
</td>
</tr>
</tbody>
</table>
2025-07-10 01:27:35 +08:00
</div>
2025-07-10 13:58:28 +08:00
<!-- 分页 -->
<div v-if="totalPages > 1" class="flex flex-wrap justify-center gap-1 sm:gap-2 my-4 sm:my-8 px-2">
<button
v-if="currentPage > 1"
@click="goToPage(currentPage - 1)"
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
>
<i class="fas fa-chevron-left mr-1"></i> 上一页
</button>
<button
@click="goToPage(1)"
:class="currentPage === 1 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
>
1
</button>
<button
v-if="totalPages > 1"
@click="goToPage(2)"
:class="currentPage === 2 ? 'bg-slate-800 text-white' : 'bg-white text-gray-700 hover:bg-gray-50'"
class="px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
>
2
</button>
<span v-if="currentPage > 2" class="px-2 py-1 sm:px-3 sm:py-2 text-gray-500 text-sm">...</span>
<button
v-if="currentPage !== 1 && currentPage !== 2 && currentPage > 2"
class="bg-slate-800 text-white px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm"
>
{{ currentPage }}
</button>
<button
v-if="currentPage < totalPages"
@click="goToPage(currentPage + 1)"
class="bg-white text-gray-700 hover:bg-gray-50 px-2 py-1 sm:px-4 sm:py-2 rounded border transition-colors text-sm flex items-center"
>
下一页 <i class="fas fa-chevron-right ml-1"></i>
2025-07-10 01:27:35 +08:00
</button>
</div>
</div>
<!-- 添加资源模态框 -->
2025-07-10 21:14:17 +08:00
<!-- <ResourceModal
2025-07-10 01:27:35 +08:00
v-if="showAddResourceModal"
:resource="editingResource"
@close="closeModal"
@save="handleSaveResource"
2025-07-10 21:14:17 +08:00
/> -->
2025-07-10 13:58:28 +08:00
<!-- 页脚 -->
<footer class="mt-8 py-6 border-t border-gray-200">
<div class="max-w-7xl mx-auto text-center text-gray-600 text-sm">
<p class="mb-2">本站内容由网络爬虫自动抓取本站不储存复制传播任何文件仅作个人公益学习请在获取后24小内删除!!!</p>
<p>© 2025 网盘资源管理系统 By 小七</p>
</div>
</footer>
2025-07-10 01:27:35 +08:00
</div>
</template>
<script setup lang="ts">
const store = useResourceStore()
2025-07-10 21:14:17 +08:00
const userStore = useUserStore()
2025-07-10 01:27:35 +08:00
const { resources, categories, stats, loading } = storeToRefs(store)
const searchQuery = ref('')
2025-07-10 13:58:28 +08:00
const selectedPlatform = ref('')
2025-07-10 21:14:17 +08:00
// const showAddResourceModal = ref(false)
2025-07-10 01:27:35 +08:00
const editingResource = ref(null)
2025-07-10 13:58:28 +08:00
const currentPage = ref(1)
const totalPages = ref(1)
interface Platform {
id: number
name: string
key?: number
ck?: string
is_valid: boolean
space: number
left_space: number
remark: string
created_at: string
updated_at: string
}
interface ExtendedResource {
id: number
title: string
description: string
url: string
pan_id?: number
quark_url?: string
file_size?: string
category_id?: number
category_name?: string
view_count: number
is_valid?: boolean
is_public?: boolean
created_at: string
updated_at: string
showLink?: boolean
}
const platforms = ref<Platform[]>([])
const todayUpdates = ref(0)
// 防抖搜索
let searchTimeout: NodeJS.Timeout
const debounceSearch = () => {
clearTimeout(searchTimeout)
searchTimeout = setTimeout(() => {
handleSearch()
}, 500)
}
2025-07-10 01:27:35 +08:00
// 获取数据
onMounted(async () => {
2025-07-10 21:14:17 +08:00
// 初始化用户状态
userStore.initAuth()
2025-07-10 01:27:35 +08:00
await Promise.all([
store.fetchResources(),
store.fetchCategories(),
store.fetchStats(),
2025-07-10 13:58:28 +08:00
fetchPlatforms(),
2025-07-10 01:27:35 +08:00
])
2025-07-10 13:58:28 +08:00
animateCounters()
2025-07-10 01:27:35 +08:00
})
2025-07-10 13:58:28 +08:00
// 获取平台列表
const fetchPlatforms = async () => {
try {
const { usePanApi } = await import('~/composables/useApi')
const panApi = usePanApi()
const response = await panApi.getPans() as any
platforms.value = response.pans || []
} catch (error) {
console.error('获取平台列表失败:', error)
}
}
2025-07-10 01:27:35 +08:00
// 搜索处理
const handleSearch = () => {
2025-07-10 13:58:28 +08:00
const platformId = selectedPlatform.value ? parseInt(selectedPlatform.value) : undefined
store.searchResources(searchQuery.value, platformId)
2025-07-10 01:27:35 +08:00
}
2025-07-10 13:58:28 +08:00
// 按平台筛选
const filterByPlatform = (platformId: string | number) => {
selectedPlatform.value = platformId.toString()
currentPage.value = 1
handleSearch()
}
// 获取平台图标
const getPlatformIcon = (platformName: string) => {
const icons: Record<string, string> = {
'百度网盘': '<i class="fas fa-cloud text-blue-500"></i>',
'阿里云盘': '<i class="fas fa-cloud text-orange-500"></i>',
'夸克网盘': '<i class="fas fa-atom text-purple-500"></i>',
'天翼云盘': '<i class="fas fa-cloud text-cyan-500"></i>',
'迅雷云盘': '<i class="fas fa-bolt text-yellow-500"></i>',
'微云': '<i class="fas fa-cloud text-green-500"></i>',
'蓝奏云': '<i class="fas fa-cloud text-blue-400"></i>',
'123云盘': '<i class="fas fa-cloud text-red-500"></i>',
'腾讯微云': '<i class="fas fa-cloud text-green-500"></i>',
'OneDrive': '<i class="fab fa-microsoft text-blue-600"></i>',
'Google云盘': '<i class="fab fa-google-drive text-green-600"></i>',
'Dropbox': '<i class="fab fa-dropbox text-blue-500"></i>',
'城通网盘': '<i class="fas fa-folder text-yellow-600"></i>',
'115网盘': '<i class="fas fa-cloud-upload-alt text-green-600"></i>',
'磁力链接': '<i class="fas fa-magnet text-red-600"></i>',
'UC网盘': '<i class="fas fa-cloud-download-alt text-purple-600"></i>',
'天翼云': '<i class="fas fa-cloud text-cyan-500"></i>',
'unknown': '<i class="fas fa-question-circle text-gray-400"></i>',
'其他': '<i class="fas fa-cloud text-gray-500"></i>'
}
return icons[platformName] || icons['unknown']
2025-07-10 01:27:35 +08:00
}
2025-07-10 13:58:28 +08:00
// 获取平台名称
const getPlatformName = (platformId: number) => {
const platform = platforms.value.find((p: Platform) => p.id === platformId)
return platform?.name || 'unknown'
}
// 切换链接显示
const toggleLink = (resource: any) => {
resource.showLink = !resource.showLink
}
// 格式化相对时间
const formatRelativeTime = (dateString: string) => {
const date = new Date(dateString)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffSec = Math.floor(diffMs / 1000)
const diffMin = Math.floor(diffSec / 60)
const diffHour = Math.floor(diffMin / 60)
const diffDay = Math.floor(diffHour / 24)
const diffWeek = Math.floor(diffDay / 7)
const diffMonth = Math.floor(diffDay / 30)
const diffYear = Math.floor(diffDay / 365)
const isToday = date.toDateString() === now.toDateString()
if (isToday) {
if (diffMin < 1) {
return '<span class="text-pink-600 font-medium flex items-center"><i class="fas fa-circle-dot text-xs mr-1 animate-pulse"></i>刚刚更新</span>'
} else if (diffHour < 1) {
return `<span class="text-pink-600 font-medium flex items-center"><i class="fas fa-circle-dot text-xs mr-1 animate-pulse"></i>${diffMin}分钟前</span>`
} else {
return `<span class="text-pink-600 font-medium flex items-center"><i class="fas fa-circle-dot text-xs mr-1 animate-pulse"></i>${diffHour}小时前</span>`
}
} else if (diffDay < 1) {
return `${diffHour}小时前`
} else if (diffDay < 7) {
return `${diffDay}天前`
} else if (diffWeek < 4) {
return `${diffWeek}周前`
} else if (diffMonth < 12) {
return `${diffMonth}个月前`
} else {
return `${diffYear}年前`
}
}
// 检查是否为今天更新
const isUpdatedToday = (dateString: string) => {
const date = new Date(dateString)
const now = new Date()
return date.toDateString() === now.toDateString()
}
// 数字动画效果
const animateCounters = () => {
const counters = document.querySelectorAll('.count-up')
const speed = 200
counters.forEach((counter: Element) => {
const target = parseInt(counter.getAttribute('data-target') || '0')
const increment = Math.ceil(target / speed)
let count = 0
const updateCount = () => {
if (count < target) {
count += increment
if (count > target) count = target
counter.textContent = count.toString()
setTimeout(updateCount, 1)
} else {
counter.textContent = target.toString()
}
}
updateCount()
})
}
// 页面跳转
const goToPage = (page: number) => {
currentPage.value = page
handleSearch()
window.scrollTo({
top: 0,
behavior: 'smooth'
})
2025-07-10 01:27:35 +08:00
}
// 编辑资源
2025-07-10 21:14:17 +08:00
// const editResource = (resource: any) => {
// editingResource.value = resource
// showAddResourceModal.value = true
// }
2025-07-10 01:27:35 +08:00
// 删除资源
const deleteResource = async (id: number) => {
if (confirm('确定要删除这个资源吗?')) {
try {
await store.deleteResource(id)
} catch (error) {
console.error('删除失败:', error)
}
}
}
// 关闭模态框
2025-07-10 21:14:17 +08:00
// const closeModal = () => {
// showAddResourceModal.value = false
// editingResource.value = null
// }
2025-07-10 01:27:35 +08:00
// 保存资源
const handleSaveResource = async (resourceData: any) => {
try {
if (editingResource.value) {
await store.updateResource(editingResource.value.id, resourceData)
} else {
await store.createResource(resourceData)
}
closeModal()
} catch (error) {
console.error('保存失败:', error)
}
}
</script>
<style scoped>
2025-07-10 13:58:28 +08:00
.active-filter {
@apply bg-slate-800 text-white;
}
.count-up {
transition: all 0.3s ease;
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .5;
}
2025-07-10 01:27:35 +08:00
}
</style>