Files
urldb/web/pages/admin/ready-resources.vue

408 lines
10 KiB
Vue
Raw Normal View History

2025-07-10 13:58:28 +08:00
<template>
2025-09-14 10:26:58 +08:00
<AdminPageLayout>
<!-- 页面头部 - 标题和按钮 -->
<template #page-header>
2025-08-07 18:47:26 +08:00
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">待处理资源</h1>
<p class="text-gray-600 dark:text-gray-400">管理待处理的资源</p>
</div>
<div class="flex space-x-3">
2025-09-14 10:26:58 +08:00
<n-button @click="navigateTo('/admin/failed-resources')" type="tertiary">
2025-08-07 18:47:26 +08:00
<template #icon>
<i class="fas fa-exclamation-triangle"></i>
</template>
错误资源
</n-button>
<n-button @click="refreshData">
<template #icon>
<i class="fas fa-refresh"></i>
</template>
刷新
</n-button>
2025-09-14 10:26:58 +08:00
<n-button
2025-08-07 18:47:26 +08:00
@click="toggleAutoProcess"
:disabled="updatingConfig"
:type="systemConfig?.auto_process_ready_resources ? 'error' : 'success'"
2025-07-10 13:58:28 +08:00
>
2025-08-07 18:47:26 +08:00
<template #icon>
<i v-if="updatingConfig" class="fas fa-spinner fa-spin"></i>
<i v-else :class="systemConfig?.auto_process_ready_resources ? 'fas fa-pause' : 'fas fa-play'"></i>
</template>
2025-09-14 10:26:58 +08:00
{{ systemConfig?.auto_process_ready_resources ? '关闭自动处理' : '开启自动处理' }}
2025-08-03 10:50:25 +08:00
</n-button>
2025-07-10 13:58:28 +08:00
</div>
2025-09-14 10:26:58 +08:00
</template>
2025-07-10 13:58:28 +08:00
2025-09-14 10:26:58 +08:00
<!-- 内容区header - 资源列表头部 -->
<template #content-header>
<div class="flex items-center justify-between">
<span class="text-lg font-semibold">待处理资源列表</span>
<div class="flex items-center space-x-4">
<span class="text-sm text-gray-500"> {{ totalCount }} 个待处理资源</span>
<n-button
@click="clearAll"
type="error"
size="small"
>
<template #icon>
<i class="fas fa-trash"></i>
</template>
清空全部
</n-button>
2025-07-10 13:58:28 +08:00
</div>
2025-09-14 10:26:58 +08:00
</div>
</template>
2025-08-07 18:47:26 +08:00
2025-09-14 10:26:58 +08:00
<!-- 内容区content - 资源列表 -->
<template #content>
<!-- 加载状态 -->
2025-08-07 18:47:26 +08:00
<div v-if="loading" class="flex items-center justify-center py-8">
<n-spin size="large" />
2025-07-10 13:58:28 +08:00
</div>
2025-09-14 10:26:58 +08:00
<!-- 空状态 -->
2025-08-07 18:47:26 +08:00
<div v-else-if="readyResources.length === 0" class="text-center py-8">
<i class="fas fa-inbox text-4xl text-gray-400 mb-4"></i>
<p class="text-gray-500">暂无待处理资源</p>
<p class="text-sm text-gray-400 mt-2">你可以点击上方"添加资源"按钮快速导入资源</p>
<div class="mt-4">
<n-button type="primary" @click="navigateTo('/admin/add-resource')">
<template #icon>
<i class="fas fa-plus"></i>
</template>
添加资源
</n-button>
2025-07-11 00:49:41 +08:00
</div>
</div>
2025-09-14 10:26:58 +08:00
<!-- 数据表格 -->
2025-08-07 18:47:26 +08:00
<div v-else>
<n-data-table
:columns="columns"
:data="readyResources"
:pagination="pagination"
:bordered="false"
:single-line="false"
:loading="loading"
@update:page="handlePageChange"
/>
2025-07-10 13:58:28 +08:00
</div>
2025-09-14 10:26:58 +08:00
</template>
</AdminPageLayout>
2025-07-10 13:58:28 +08:00
</template>
<script setup lang="ts">
2025-07-20 21:56:00 +08:00
// 设置页面布局
definePageMeta({
2025-08-07 18:47:26 +08:00
layout: 'admin'
2025-07-20 21:56:00 +08:00
})
2025-07-10 13:58:28 +08:00
interface ReadyResource {
id: number
title?: string
url: string
create_time: string
ip?: string
}
2025-08-05 11:45:18 +08:00
const notification = useNotification()
2025-07-10 13:58:28 +08:00
const readyResources = ref<ReadyResource[]>([])
const loading = ref(false)
2025-07-11 00:49:41 +08:00
// 分页相关状态
const currentPage = ref(1)
const pageSize = ref(100)
const totalCount = ref(0)
const totalPages = ref(0)
2025-07-10 13:58:28 +08:00
// 获取待处理资源API
2025-07-21 21:24:50 +08:00
import { useReadyResourceApi, useSystemConfigApi } from '~/composables/useApi'
import { useSystemConfigStore } from '~/stores/systemConfig'
2025-08-07 18:47:26 +08:00
import { h } from 'vue'
2025-07-10 13:58:28 +08:00
const readyResourceApi = useReadyResourceApi()
2025-07-12 13:57:39 +08:00
const systemConfigApi = useSystemConfigApi()
const systemConfigStore = useSystemConfigStore()
2025-07-12 13:57:39 +08:00
// 获取系统配置
const systemConfig = ref<any>(null)
2025-08-07 18:47:26 +08:00
const updatingConfig = ref(false)
2025-08-03 10:50:25 +08:00
const dialog = useDialog()
2025-08-07 18:47:26 +08:00
// 表格列定义
const columns = [
{
title: 'ID',
key: 'id',
width: 80,
render: (row: ReadyResource) => {
return h('span', { class: 'font-medium' }, row.id)
}
},
{
title: '标题',
key: 'title',
render: (row: ReadyResource) => {
if (row.title) {
return h('span', { title: row.title }, escapeHtml(row.title))
} else {
return h('span', { class: 'text-gray-400 italic' }, '未设置')
}
}
},
{
title: 'URL',
key: 'url',
render: (row: ReadyResource) => {
return h('a', {
href: checkUrlSafety(row.url),
target: '_blank',
rel: 'noopener noreferrer',
class: 'text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline break-all',
title: row.url
}, escapeHtml(row.url))
}
},
{
title: '创建时间',
key: 'create_time',
width: 180,
render: (row: ReadyResource) => {
return h('span', { class: 'text-gray-500' }, formatTime(row.create_time))
}
},
{
title: 'IP地址',
key: 'ip',
width: 120,
render: (row: ReadyResource) => {
return h('span', { class: 'text-gray-500' }, escapeHtml(row.ip || '-'))
}
},
{
title: '操作',
key: 'actions',
width: 80,
render: (row: ReadyResource) => {
return h('div', [
h('button', {
class: 'text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 transition-colors',
onClick: () => deleteResource(row.id),
title: '删除此资源'
}, [
h('i', { class: 'fas fa-trash' })
])
])
}
}
]
// 分页配置
const pagination = computed(() => ({
page: currentPage.value,
pageSize: pageSize.value,
itemCount: totalCount.value,
showSizePicker: true,
pageSizes: [20, 50, 100],
onChange: (page: number) => {
currentPage.value = page
fetchData()
},
onUpdatePageSize: (size: number) => {
pageSize.value = size
currentPage.value = 1
fetchData()
}
}))
2025-07-12 13:57:39 +08:00
const fetchSystemConfig = async () => {
try {
const response = await systemConfigApi.getSystemConfig()
systemConfig.value = response
systemConfigStore.setConfig(response)
2025-08-08 01:28:25 +08:00
console.log('ready-resources页面系统配置:', response)
2025-07-12 13:57:39 +08:00
} catch (error) {
console.error('获取系统配置失败:', error)
}
}
2025-07-10 13:58:28 +08:00
// 获取数据
const fetchData = async () => {
loading.value = true
try {
2025-07-11 00:49:41 +08:00
const response = await readyResourceApi.getReadyResources({
page: currentPage.value,
page_size: pageSize.value
}) as any
2025-07-12 13:57:39 +08:00
if (response && response.data) {
readyResources.value = response.data
totalCount.value = response.total || 0
totalPages.value = Math.ceil((response.total || 0) / pageSize.value)
} else if (Array.isArray(response)) {
readyResources.value = response
totalCount.value = response.length
totalPages.value = 1
} else {
readyResources.value = []
totalCount.value = 0
totalPages.value = 1
}
2025-07-10 13:58:28 +08:00
} catch (error) {
console.error('获取待处理资源失败:', error)
2025-07-12 13:57:39 +08:00
readyResources.value = []
totalCount.value = 0
totalPages.value = 1
2025-07-10 13:58:28 +08:00
} finally {
loading.value = false
}
}
2025-08-07 18:47:26 +08:00
// 处理分页变化
const handlePageChange = (page: number) => {
currentPage.value = page
fetchData()
2025-07-11 00:49:41 +08:00
}
2025-07-10 13:58:28 +08:00
// 刷新数据
const refreshData = () => {
fetchData()
}
2025-07-12 13:57:39 +08:00
// 刷新配置
const refreshConfig = () => {
fetchSystemConfig()
}
2025-07-10 13:58:28 +08:00
// 删除资源
const deleteResource = async (id: number) => {
2025-08-03 10:50:25 +08:00
dialog.warning({
title: '警告',
content: '确定要删除这个待处理资源吗?',
positiveText: '确定',
negativeText: '取消',
draggable: true,
onPositiveClick: async () => {
try {
await readyResourceApi.deleteReadyResource(id)
if (readyResources.value.length === 1 && currentPage.value > 1) {
currentPage.value--
}
fetchData()
2025-08-07 18:47:26 +08:00
notification.success({
content: '删除成功',
duration: 3000
})
2025-08-03 10:50:25 +08:00
} catch (error) {
console.error('删除失败:', error)
2025-08-05 11:45:18 +08:00
notification.error({
content: '删除失败',
duration: 3000
})
2025-08-03 10:50:25 +08:00
}
2025-07-11 00:49:41 +08:00
}
2025-08-03 10:50:25 +08:00
})
2025-07-10 13:58:28 +08:00
}
// 清空全部
const clearAll = async () => {
2025-08-03 10:50:25 +08:00
dialog.warning({
title: '警告',
content: '确定要清空所有待处理资源吗?此操作不可恢复!',
positiveText: '确定',
negativeText: '取消',
draggable: true,
onPositiveClick: async () => {
try {
const response = await readyResourceApi.clearReadyResources() as any
console.log('清空成功:', response)
2025-08-07 18:47:26 +08:00
currentPage.value = 1
2025-08-03 10:50:25 +08:00
fetchData()
2025-08-05 11:45:18 +08:00
notification.success({
content: `成功清空 ${response.data.deleted_count} 个资源`,
duration: 3000
})
2025-08-03 10:50:25 +08:00
} catch (error) {
console.error('清空失败:', error)
2025-08-05 11:45:18 +08:00
notification.error({
content: '清空失败',
duration: 3000
})
2025-08-03 10:50:25 +08:00
}
}
})
2025-07-10 13:58:28 +08:00
}
// 格式化时间
const formatTime = (timeString: string) => {
const date = new Date(timeString)
return date.toLocaleString('zh-CN')
}
2025-07-11 00:49:41 +08:00
// 转义HTML防止XSS
const escapeHtml = (text: string) => {
if (!text) return text
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}
// 验证URL安全性
const checkUrlSafety = (url: string) => {
if (!url) return '#'
try {
const urlObj = new URL(url)
if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') {
return '#'
}
return url
} catch {
return '#'
}
}
2025-07-12 13:57:39 +08:00
// 切换自动处理配置
const toggleAutoProcess = async () => {
if (updatingConfig.value) {
return
}
updatingConfig.value = true
try {
const newValue = !systemConfig.value?.auto_process_ready_resources
console.log('切换自动处理配置:', newValue)
2025-07-12 13:57:39 +08:00
const response = await systemConfigApi.toggleAutoProcess(newValue)
console.log('切换响应:', response)
2025-07-12 13:57:39 +08:00
systemConfig.value = response
systemConfigStore.setConfig(response)
2025-07-12 13:57:39 +08:00
2025-08-05 11:45:18 +08:00
notification.success({
content: `自动处理配置已${newValue ? '开启' : '关闭'}`,
duration: 3000
})
2025-07-12 13:57:39 +08:00
} catch (error: any) {
2025-08-05 11:45:18 +08:00
notification.error({
content: `切换自动处理配置失败`,
duration: 3000
})
2025-07-12 13:57:39 +08:00
} finally {
updatingConfig.value = false
}
}
2025-07-10 13:58:28 +08:00
// 页面加载时获取数据
2025-07-11 02:30:57 +08:00
onMounted(async () => {
try {
await fetchData()
2025-07-12 13:57:39 +08:00
await fetchSystemConfig()
2025-07-11 02:30:57 +08:00
} catch (error) {
console.error('页面初始化失败:', error)
}
2025-07-10 13:58:28 +08:00
})
2025-07-12 13:57:39 +08:00
2025-08-10 13:52:41 +08:00
2025-07-10 13:58:28 +08:00
</script>
<style scoped>
2025-08-07 18:47:26 +08:00
/* 自定义样式 */
2025-07-10 13:58:28 +08:00
</style>