mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: ui
This commit is contained in:
@@ -392,8 +392,9 @@ func (h *TaskHandler) DeleteTask(c *gin.Context) {
|
||||
// CreateExpansionTask 创建扩容任务
|
||||
func (h *TaskHandler) CreateExpansionTask(c *gin.Context) {
|
||||
var req struct {
|
||||
PanAccountID uint `json:"pan_account_id" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
PanAccountID uint `json:"pan_account_id" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
DataSource map[string]interface{} `json:"dataSource"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -420,10 +421,14 @@ func (h *TaskHandler) CreateExpansionTask(c *gin.Context) {
|
||||
accountName = fmt.Sprintf("账号%d", cks.ID)
|
||||
}
|
||||
|
||||
// 构建任务配置(存储账号ID)
|
||||
// 构建任务配置(存储账号ID和数据源)
|
||||
taskConfig := map[string]interface{}{
|
||||
"pan_account_id": req.PanAccountID,
|
||||
}
|
||||
// 如果有数据源配置,添加到taskConfig中
|
||||
if req.DataSource != nil && len(req.DataSource) > 0 {
|
||||
taskConfig["data_source"] = req.DataSource
|
||||
}
|
||||
configJSON, _ := json.Marshal(taskConfig)
|
||||
|
||||
// 创建任务标题,包含账号名称
|
||||
@@ -451,6 +456,10 @@ func (h *TaskHandler) CreateExpansionTask(c *gin.Context) {
|
||||
expansionInput := task.ExpansionInput{
|
||||
PanAccountID: req.PanAccountID,
|
||||
}
|
||||
// 如果有数据源配置,添加到输入数据中
|
||||
if req.DataSource != nil && len(req.DataSource) > 0 {
|
||||
expansionInput.DataSource = req.DataSource
|
||||
}
|
||||
|
||||
inputJSON, _ := json.Marshal(expansionInput)
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ func (ep *ExpansionProcessor) GetTaskType() string {
|
||||
|
||||
// ExpansionInput 扩容任务输入数据结构
|
||||
type ExpansionInput struct {
|
||||
PanAccountID uint `json:"pan_account_id"`
|
||||
PanAccountID uint `json:"pan_account_id"`
|
||||
DataSource map[string]interface{} `json:"data_source,omitempty"`
|
||||
}
|
||||
|
||||
// ExpansionOutput 扩容任务输出数据结构
|
||||
@@ -93,8 +94,8 @@ func (ep *ExpansionProcessor) Process(ctx context.Context, taskID uint, item *en
|
||||
return err
|
||||
}
|
||||
|
||||
// 执行扩容操作(这里留空,直接返回成功)
|
||||
if err := ep.performExpansion(ctx, input.PanAccountID); err != nil {
|
||||
// 执行扩容操作(传入数据源)
|
||||
if err := ep.performExpansion(ctx, input.PanAccountID, input.DataSource); err != nil {
|
||||
output := ExpansionOutput{
|
||||
Success: false,
|
||||
Message: "扩容失败",
|
||||
@@ -177,11 +178,11 @@ func (ep *ExpansionProcessor) checkAccountType(panAccountID uint) error {
|
||||
}
|
||||
|
||||
// performExpansion 执行扩容操作
|
||||
func (ep *ExpansionProcessor) performExpansion(ctx context.Context, panAccountID uint) error {
|
||||
func (ep *ExpansionProcessor) performExpansion(ctx context.Context, panAccountID uint, dataSource map[string]interface{}) error {
|
||||
// 扩容逻辑暂时留空,直接返回成功
|
||||
// TODO: 实现具体的扩容逻辑
|
||||
|
||||
utils.Info("执行扩容操作,账号ID: %d", panAccountID)
|
||||
utils.Info("执行扩容操作,账号ID: %d, 数据源: %v", panAccountID, dataSource)
|
||||
|
||||
// 模拟扩容操作延迟
|
||||
// time.Sleep(2 * time.Second)
|
||||
|
||||
@@ -1,112 +1,110 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-6 h-full">
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<n-card title="批量转存资源列表">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- 左侧:资源输入 -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
资源内容 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<n-input
|
||||
v-model:value="resourceText"
|
||||
type="textarea"
|
||||
placeholder="请输入资源内容,格式:标题和URL为一组..."
|
||||
:autosize="{ minRows: 10, maxRows: 15 }"
|
||||
show-count
|
||||
:maxlength="100000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:配置选项 -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
默认分类
|
||||
</label>
|
||||
<CategorySelector
|
||||
v-model="selectedCategory"
|
||||
placeholder="选择分类"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
标签
|
||||
</label>
|
||||
<TagSelector
|
||||
v-model="selectedTags"
|
||||
placeholder="选择标签"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
网盘账号 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<n-select
|
||||
v-model:value="selectedAccounts"
|
||||
:options="accountOptions"
|
||||
placeholder="选择网盘账号"
|
||||
multiple
|
||||
filterable
|
||||
:loading="accountsLoading"
|
||||
@update:value="handleAccountChange"
|
||||
>
|
||||
<template #option="{ option: accountOption }">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-sm">{{ accountOption.label }}</span>
|
||||
<n-tag v-if="accountOption.is_valid" type="success" size="small">有效</n-tag>
|
||||
<n-tag v-else type="error" size="small">无效</n-tag>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ formatSpace(accountOption.left_space) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-select>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
请选择要使用的网盘账号,系统将使用选中的账号进行转存操作
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="space-y-3 pt-4">
|
||||
<n-button
|
||||
type="primary"
|
||||
block
|
||||
size="large"
|
||||
:loading="processing"
|
||||
:disabled="!resourceText.trim() || !selectedAccounts.length || processing"
|
||||
@click="handleBatchTransfer"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-upload"></i>
|
||||
</template>
|
||||
开始批量转存
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
block
|
||||
@click="clearInput"
|
||||
:disabled="processing"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
清空内容
|
||||
</n-button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- 左侧:资源输入 -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
资源内容 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<n-input
|
||||
v-model:value="resourceText"
|
||||
type="textarea"
|
||||
placeholder="请输入资源内容,格式:标题和URL为一组..."
|
||||
:autosize="{ minRows: 10, maxRows: 15 }"
|
||||
show-count
|
||||
:maxlength="100000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 右侧:配置选项 -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
默认分类
|
||||
</label>
|
||||
<CategorySelector
|
||||
v-model="selectedCategory"
|
||||
placeholder="选择分类"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
标签
|
||||
</label>
|
||||
<TagSelector
|
||||
v-model="selectedTags"
|
||||
placeholder="选择标签"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
网盘账号 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<n-select
|
||||
v-model:value="selectedAccounts"
|
||||
:options="accountOptions"
|
||||
placeholder="选择网盘账号"
|
||||
multiple
|
||||
filterable
|
||||
:loading="accountsLoading"
|
||||
@update:value="handleAccountChange"
|
||||
>
|
||||
<template #option="scope">
|
||||
<div class="flex items-center justify-between w-full" v-if="scope && scope.option">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-sm">{{ scope.option.label || '未知账号' }}</span>
|
||||
<n-tag v-if="scope.option.is_valid" type="success" size="small">有效</n-tag>
|
||||
<n-tag v-else type="error" size="small">无效</n-tag>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ formatSpace(scope.option.left_space || 0) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-select>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
请选择要使用的网盘账号,系统将使用选中的账号进行转存操作
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="space-y-3 pt-4">
|
||||
<n-button
|
||||
type="primary"
|
||||
block
|
||||
size="large"
|
||||
:loading="processing"
|
||||
:disabled="!resourceText.trim() || !selectedAccounts.length || processing"
|
||||
@click="handleBatchTransfer"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-upload"></i>
|
||||
</template>
|
||||
开始批量转存
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
block
|
||||
@click="clearInput"
|
||||
:disabled="processing"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
清空内容
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 处理结果 -->
|
||||
<n-card v-if="results.length > 0" title="转存结果">
|
||||
@@ -202,15 +200,15 @@ const invalidUrls = computed(() => {
|
||||
})
|
||||
|
||||
const successCount = computed(() => {
|
||||
return results.value.filter((r: any) => r.status === 'success').length
|
||||
return results.value ? results.value.filter((r: any) => r && r.status === 'success').length : 0
|
||||
})
|
||||
|
||||
const failedCount = computed(() => {
|
||||
return results.value.filter((r: any) => r.status === 'failed').length
|
||||
return results.value ? results.value.filter((r: any) => r && r.status === 'failed').length : 0
|
||||
})
|
||||
|
||||
const processingCount = computed(() => {
|
||||
return results.value.filter((r: any) => r.status === 'processing').length
|
||||
return results.value ? results.value.filter((r: any) => r && r.status === 'processing').length : 0
|
||||
})
|
||||
|
||||
// 结果表格列
|
||||
@@ -243,9 +241,10 @@ const resultColumns = [
|
||||
pending: { color: 'warning', text: '等待中', icon: 'fas fa-clock' }
|
||||
}
|
||||
const status = statusMap[row.status as keyof typeof statusMap] || statusMap.failed
|
||||
return h('n-tag', { type: status.color }, {
|
||||
icon: () => h('i', { class: status.icon }),
|
||||
default: () => status.text
|
||||
const safeStatus = status || statusMap.failed
|
||||
return h('n-tag', { type: safeStatus.color }, {
|
||||
icon: () => h('i', { class: safeStatus.icon }),
|
||||
default: () => safeStatus.text
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -264,7 +263,7 @@ const resultColumns = [
|
||||
tooltip: true
|
||||
},
|
||||
render: (row: any) => {
|
||||
if (row.saveUrl) {
|
||||
if (row && row.saveUrl) {
|
||||
return h('a', {
|
||||
href: row.saveUrl,
|
||||
target: '_blank',
|
||||
@@ -354,7 +353,11 @@ const handleBatchTransfer = async () => {
|
||||
// 第三步:创建任务
|
||||
const taskResponse = await taskApi.createBatchTransferTask(taskData) as any
|
||||
console.log('任务创建响应:', taskResponse)
|
||||
|
||||
|
||||
if (!taskResponse || !taskResponse.task_id) {
|
||||
throw new Error('创建任务失败:响应数据无效')
|
||||
}
|
||||
|
||||
currentTaskId.value = taskResponse.task_id
|
||||
|
||||
// 第四步:启动任务
|
||||
@@ -523,14 +526,17 @@ const getAccountOptions = async () => {
|
||||
const response = await cksApi.getCks() as any
|
||||
const accounts = Array.isArray(response) ? response : []
|
||||
|
||||
accountOptions.value = accounts.map((account: any) => ({
|
||||
label: `${account.username || '未知用户'} (${account.pan?.name || '未知平台'})`,
|
||||
value: account.id,
|
||||
is_valid: account.is_valid,
|
||||
left_space: account.left_space,
|
||||
username: account.username,
|
||||
pan_name: account.pan?.name || '未知平台'
|
||||
}))
|
||||
accountOptions.value = accounts.map((account: any) => {
|
||||
if (!account) return null
|
||||
return {
|
||||
label: `${account.username || '未知用户'} (${account.pan?.name || '未知平台'})`,
|
||||
value: account.id,
|
||||
is_valid: account.is_valid || false,
|
||||
left_space: account.left_space || 0,
|
||||
username: account.username || '未知用户',
|
||||
pan_name: account.pan?.name || '未知平台'
|
||||
}
|
||||
}).filter(option => option !== null) as any[]
|
||||
} catch (error) {
|
||||
console.error('获取网盘账号选项失败:', error)
|
||||
message.error('获取网盘账号失败')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col gap-2 h-full">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div class="flex-0 grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索已转存资源..."
|
||||
@@ -34,28 +34,111 @@
|
||||
</div>
|
||||
|
||||
<!-- 调试信息 -->
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
<div class="flex-0 text-sm text-gray-500">
|
||||
数据数量: {{ resources.length }}, 总数: {{ total }}, 加载状态: {{ loading }}
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="resources"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:remote="true"
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
:row-key="(row: any) => row.id"
|
||||
virtual-scroll
|
||||
max-height="500"
|
||||
/>
|
||||
<!-- 资源列表 -->
|
||||
<div class="flex-1 h-1">
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="resources.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>
|
||||
</div>
|
||||
|
||||
<div v-else class="h-full">
|
||||
<!-- 虚拟列表 -->
|
||||
<n-virtual-list
|
||||
:items="resources"
|
||||
:item-size="120"
|
||||
class="h-full"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="p-4 border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
<div class="flex items-start space-x-4">
|
||||
<!-- 资源信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center space-x-2 mb-2">
|
||||
<!-- ID -->
|
||||
<span class="text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 px-2 py-1 rounded">
|
||||
ID: {{ item.id }}
|
||||
</span>
|
||||
|
||||
<!-- 标题 -->
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white line-clamp-1 flex-1">
|
||||
{{ item.title || '未命名资源' }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm mt-3">
|
||||
<!-- 分类 -->
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-folder mr-1 text-gray-400"></i>
|
||||
<span class="text-gray-600 dark:text-gray-400">分类:</span>
|
||||
<span class="ml-2">{{ item.category_name || '未分类' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 转存时间 -->
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-calendar mr-1 text-gray-400"></i>
|
||||
<span class="text-gray-600 dark:text-gray-400">转存时间:</span>
|
||||
<span class="ml-2">{{ formatDate(item.updated_at) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 浏览数 -->
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-eye mr-1 text-gray-400"></i>
|
||||
<span class="text-gray-600 dark:text-gray-400">浏览数:</span>
|
||||
<span class="ml-2">{{ item.view_count || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 转存链接 -->
|
||||
<div class="mt-3">
|
||||
<div class="flex items-start space-x-2">
|
||||
<span class="text-xs text-gray-400">转存链接:</span>
|
||||
<a
|
||||
v-if="item.save_url"
|
||||
:href="item.save_url"
|
||||
target="_blank"
|
||||
class="text-xs text-green-500 hover:text-green-700 break-all"
|
||||
>
|
||||
{{ item.save_url.length > 60 ? item.save_url.substring(0, 60) + '...' : item.save_url }}
|
||||
</a>
|
||||
<span v-else class="text-xs text-gray-500">暂无转存链接</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="flex-0">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="total"
|
||||
:page-sizes="[10000, 20000, 50000, 100000]"
|
||||
show-size-picker
|
||||
show-quick-jumper
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, h } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useResourceApi, usePanApi } from '~/composables/useApi'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
@@ -81,78 +164,17 @@ const panApi = usePanApi()
|
||||
// 获取平台数据
|
||||
const { data: platformsData } = await useAsyncData('transferredPlatforms', () => panApi.getPans())
|
||||
|
||||
// 平台选项
|
||||
const platformOptions = computed(() => {
|
||||
const data = platformsData.value as any
|
||||
const platforms = data?.data || data || []
|
||||
return platforms.map((platform: any) => ({
|
||||
label: platform.remark || platform.name,
|
||||
value: platform.id
|
||||
}))
|
||||
})
|
||||
|
||||
// 获取平台名称
|
||||
const getPlatformName = (platformId: number) => {
|
||||
const platform = (platformsData.value as any)?.data?.find((plat: any) => plat.id === platformId)
|
||||
return platform?.remark || platform?.name || '未知平台'
|
||||
}
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10000,
|
||||
itemCount: 0,
|
||||
pageSizes: [10000, 20000, 50000, 100000],
|
||||
showSizePicker: true,
|
||||
showQuickJumper: true,
|
||||
prefix: ({ itemCount }: any) => `共 ${itemCount} 条`
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns: any[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 60,
|
||||
fixed: 'left' as const
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
width: 200,
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
key: 'category_name',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '转存链接',
|
||||
key: 'save_url',
|
||||
width: 200,
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
},
|
||||
render: (row: any) => {
|
||||
return h('a', {
|
||||
href: row.save_url,
|
||||
target: '_blank',
|
||||
class: 'text-green-500 hover:text-green-700'
|
||||
}, row.save_url.length > 30 ? row.save_url.substring(0, 30) + '...' : row.save_url)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '转存时间',
|
||||
key: 'updated_at',
|
||||
width: 130,
|
||||
render: (row: any) => {
|
||||
return new Date(row.updated_at).toLocaleDateString()
|
||||
}
|
||||
}
|
||||
]
|
||||
// 格式化日期
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return '未知时间'
|
||||
return new Date(dateString).toLocaleDateString()
|
||||
}
|
||||
|
||||
// 获取已转存资源
|
||||
const fetchTransferredResources = async () => {
|
||||
@@ -183,24 +205,20 @@ const fetchTransferredResources = async () => {
|
||||
console.log('使用嵌套data格式,数量:', result.data.data.length)
|
||||
resources.value = result.data.data
|
||||
total.value = result.data.total || 0
|
||||
pagination.itemCount = result.data.total || 0
|
||||
} else {
|
||||
// 处理直接的data结构:{data: [...], total: ...}
|
||||
console.log('使用直接data格式,数量:', result.data.length)
|
||||
resources.value = result.data
|
||||
total.value = result.total || 0
|
||||
pagination.itemCount = result.total || 0
|
||||
}
|
||||
} else if (Array.isArray(result)) {
|
||||
console.log('使用数组格式,数量:', result.length)
|
||||
resources.value = result
|
||||
total.value = result.length
|
||||
pagination.itemCount = result.length
|
||||
} else {
|
||||
console.log('未知格式,设置空数组')
|
||||
resources.value = []
|
||||
total.value = 0
|
||||
pagination.itemCount = 0
|
||||
}
|
||||
|
||||
console.log('最终 resources.value:', resources.value)
|
||||
@@ -223,22 +241,18 @@ const fetchTransferredResources = async () => {
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
pagination.page = 1
|
||||
fetchTransferredResources()
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page
|
||||
pagination.page = page
|
||||
fetchTransferredResources()
|
||||
}
|
||||
|
||||
const handlePageSizeChange = (size: number) => {
|
||||
pageSize.value = size
|
||||
pagination.pageSize = size
|
||||
currentPage.value = 1
|
||||
pagination.page = 1
|
||||
fetchTransferredResources()
|
||||
}
|
||||
|
||||
@@ -246,4 +260,13 @@ const handlePageSizeChange = (size: number) => {
|
||||
onMounted(() => {
|
||||
fetchTransferredResources()
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-1 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="h-full flex flex-col gap-2">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
<div class="flex-0 grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索未转存资源..."
|
||||
@@ -41,47 +41,45 @@
|
||||
</div>
|
||||
|
||||
<!-- 批量操作 -->
|
||||
<n-card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="toggleSelectAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
/>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">全选</span>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">
|
||||
共 {{ total }} 个资源,已选择 {{ selectedResources.length }} 个
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<n-button
|
||||
type="primary"
|
||||
:disabled="selectedResources.length === 0"
|
||||
:loading="batchTransferring"
|
||||
@click="handleBatchTransfer"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</template>
|
||||
批量转存 ({{ selectedResources.length }})
|
||||
</n-button>
|
||||
|
||||
<n-button @click="refreshData">
|
||||
<template #icon>
|
||||
<i class="fas fa-refresh"></i>
|
||||
</template>
|
||||
刷新
|
||||
</n-button>
|
||||
<div class="flex-0 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="toggleSelectAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
/>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">全选</span>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">
|
||||
共 {{ total }} 个资源,已选择 {{ selectedResources.length }} 个
|
||||
</span>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<n-button
|
||||
type="primary"
|
||||
:disabled="selectedResources.length === 0"
|
||||
:loading="batchTransferring"
|
||||
@click="handleBatchTransfer"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</template>
|
||||
批量转存 ({{ selectedResources.length }})
|
||||
</n-button>
|
||||
|
||||
<n-button @click="refreshData">
|
||||
<template #icon>
|
||||
<i class="fas fa-refresh"></i>
|
||||
</template>
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 资源列表 -->
|
||||
<n-card>
|
||||
<div class="flex-1 h-1">
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
@@ -91,13 +89,12 @@
|
||||
<p class="text-gray-500">暂无未转存的夸克资源</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div v-else class="h-full">
|
||||
<!-- 虚拟列表 -->
|
||||
<n-virtual-list
|
||||
:items="resources"
|
||||
:item-size="120"
|
||||
style="max-height: 500px"
|
||||
container-style="height: 500px;"
|
||||
class="h-full"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="p-4 border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
@@ -167,22 +164,23 @@
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="total"
|
||||
:page-sizes="[10000, 20000, 50000, 100000]"
|
||||
show-size-picker
|
||||
show-quick-jumper
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
</div>
|
||||
<div class="flex-0">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="total"
|
||||
:page-sizes="[10000, 20000, 50000, 100000]"
|
||||
show-size-picker
|
||||
show-quick-jumper
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网盘账号选择模态框 -->
|
||||
<n-modal v-model:show="showAccountSelectionModal" preset="card" title="选择网盘账号" style="width: 600px">
|
||||
|
||||
80
web/components/AdminPageLayout.vue
Normal file
80
web/components/AdminPageLayout.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="h-full flex flex-col gap-3">
|
||||
<!-- 顶部标题和按钮区域 -->
|
||||
<div class="flex-0 w-full flex">
|
||||
<div v-if="isSubPage" class="flex-0 mr-4 flex items-center">
|
||||
<n-button @click="goBack" type="text" size="small">
|
||||
<template #icon>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
<!-- 页面头部内容 -->
|
||||
<div class="flex-1 w-1 flex items-center justify-between">
|
||||
<slot name="page-header"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知提示区域 -->
|
||||
<div v-if="hasNoticeSection" class="flex-shrink-0">
|
||||
<slot name="notice-section"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 过滤栏区域 -->
|
||||
<div v-if="hasFilterBar" class="flex-shrink-0">
|
||||
<slot name="filter-bar"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 内容区 - 自适应剩余高度 -->
|
||||
<div class="flex-1 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
|
||||
<!-- 内容区header -->
|
||||
<div v-if="hasContentHeader" class="px-6 py-4 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-700 whitespace-nowrap">
|
||||
<slot name="content-header"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 内容区content - 自适应剩余高度 -->
|
||||
<div class="flex-1 h-1 content-slot overflow-hidden">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 内容区footer -->
|
||||
<div v-if="hasContentFooter" class="flex-shrink-0 bg-gray-50 dark:bg-gray-700 border-t border-gray-200 dark:border-gray-700">
|
||||
<slot name="content-footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
const $slots = useSlots()
|
||||
const hasNoticeSection = computed(() => $slots['notice-section'] !== undefined)
|
||||
const hasFilterBar = computed(() => $slots['filter-bar'] !== undefined)
|
||||
const hasContentHeader = computed(() => $slots['content-header'] !== undefined)
|
||||
const hasContentFooter = computed(() => $slots['content-footer'] !== undefined)
|
||||
|
||||
const goBack = () => {
|
||||
try {
|
||||
router.back()
|
||||
} catch (error) {
|
||||
navigateTo('/admin')
|
||||
}
|
||||
}
|
||||
|
||||
defineProps({
|
||||
minHeight: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
isSubPage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.content-slot) {
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<div class="min-h-screen max-h-screen overflow-hidden bg-gray-50 dark:bg-gray-900">
|
||||
<!-- 设置通用title -->
|
||||
<Head>
|
||||
<title>管理后台 - 老九网盘资源数据库</title>
|
||||
@@ -129,9 +129,9 @@
|
||||
</header>
|
||||
|
||||
<!-- 侧边栏和主内容区域 -->
|
||||
<div class="flex">
|
||||
<div class="flex main-content">
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="w-64 bg-white dark:bg-gray-800 shadow-sm border-r border-gray-200 dark:border-gray-700 min-h-screen">
|
||||
<aside class="w-64 bg-white dark:bg-gray-800 shadow-sm border-r border-gray-200 dark:border-gray-700 h-full overflow-y-auto">
|
||||
<nav class="mt-8">
|
||||
<div class="px-4 space-y-6">
|
||||
<!-- 仪表盘 -->
|
||||
@@ -273,7 +273,7 @@
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<main class="flex-1 p-8">
|
||||
<main class="flex-1 p-4 h-full overflow-y-auto">
|
||||
<ClientOnly>
|
||||
<n-message-provider>
|
||||
<n-notification-provider>
|
||||
@@ -304,9 +304,7 @@ const systemConfigStore = useSystemConfigStore()
|
||||
// 任务状态管理
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
// 初始化系统配置(管理员页面使用管理员API)
|
||||
// 在setup阶段初始化,确保数据可用
|
||||
await systemConfigStore.initConfig(false, true)
|
||||
systemConfigStore.initConfig(false, true).catch(console.error)
|
||||
|
||||
// 版本信息
|
||||
const versionInfo = ref({
|
||||
@@ -328,10 +326,18 @@ const fetchVersionInfo = async () => {
|
||||
// 初始化版本信息和任务状态管理
|
||||
onMounted(() => {
|
||||
fetchVersionInfo()
|
||||
|
||||
|
||||
// 启动任务状态自动更新
|
||||
taskStore.startAutoUpdate()
|
||||
console.log('Admin layout: 任务状态自动更新已启动')
|
||||
|
||||
// 确保在客户端配置被正确载入(防止SSR水合问题)
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await systemConfigStore.initConfig(true, true) // 强制刷新,防止SSR水合问题
|
||||
} catch (error) {
|
||||
console.error('Admin layout: onMounted 配置刷新失败', error)
|
||||
}
|
||||
}, 100) // 延迟100ms,确保组件渲染完成
|
||||
})
|
||||
|
||||
// 组件销毁时清理任务状态管理
|
||||
@@ -344,9 +350,6 @@ onBeforeUnmount(() => {
|
||||
// 系统配置
|
||||
const systemConfig = computed(() => {
|
||||
const config = systemConfigStore.config || {}
|
||||
console.log('顶部导航系统配置:', config)
|
||||
console.log('自动处理状态:', config.auto_process_ready_resources)
|
||||
console.log('自动转存状态:', config.auto_transfer_enabled)
|
||||
return config
|
||||
})
|
||||
|
||||
@@ -605,4 +608,7 @@ const navigateToTasks = () => {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-weight: 900;
|
||||
}
|
||||
.main-content {
|
||||
height: calc(100vh - 85px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,76 +1,176 @@
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">账号扩容管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理账号扩容任务和状态</p>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<n-alert type="info" :show-icon="false">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fas fa-info-circle text-blue-500"></i>
|
||||
<span class="text-sm">
|
||||
<strong>20T扩容说明:</strong>建议 <span @click="showImageModal = true" style="color:red" class="cursor-pointer">蜂小推</span> quark 账号扩容。<span @click="drawActive = true" style="color:blue" class="cursor-pointer">【什么推荐蜂小推】</span><br>
|
||||
1. 20T扩容 只支持新号,等到蜂小推首次 6T 奖励 到账后进行扩容<br>
|
||||
2. 账号需要处于关闭状态, 开启状态可能会被用于,自动转存等任务,存咋影响<br>
|
||||
3. <strong><n-text style='font-size:16px' type="error">扩容完成后,并不直接获得容量</n-text>,账号将存储大量热门资源,<n-text style='font-size:16px' type="error">需要手动推广</n-text></strong><br>
|
||||
4. 注意 推广获得20T容量,删除所有资源, 热门资源比较敏感,不建议,长期推广,仅用于扩容
|
||||
</span>
|
||||
<AdminPageLayout :is-sub-page="true">
|
||||
<!-- 页面头部 - 标题 -->
|
||||
<template #page-header>
|
||||
<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>
|
||||
</n-alert>
|
||||
</template>
|
||||
|
||||
<n-drawer v-model:show="drawActive" :width="502" closable placement="right">
|
||||
<n-drawer-content title="扩容说明">
|
||||
<div class="space-y-6 p-4">
|
||||
<div class="mb-4">
|
||||
<p class="text-gray-700 text-large dark:text-gray-300 leading-relaxed">
|
||||
扩容是网盘公司提供给推广用户的<n-text type="success">特权</n-text>!需要先注册推广平台并<n-text type="success">达标</n-text>即可获得权益。
|
||||
</p>
|
||||
</div>
|
||||
<!-- 通知提示区域 - 扩容说明Alert -->
|
||||
<template #notice-section>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fas fa-info-circle text-blue-500"></i>
|
||||
<span class="text-sm">
|
||||
<strong>20T扩容说明:</strong>建议 <span @click="showImageModal = true" style="color:red" class="cursor-pointer">蜂小推</span> quark 账号扩容。<span @click="drawActive = true" style="color:blue" class="cursor-pointer">【什么推荐蜂小推】</span><br>
|
||||
1. 20T扩容 只支持新号,等到蜂小推首次 6T 奖励 到账后进行扩容<br>
|
||||
2. 账号需要处于关闭状态, 开启状态可能会被用于,自动转存等任务,存咋影响<br>
|
||||
3. <strong><n-text style='font-size:16px' type="error">扩容完成后,并不直接获得容量</n-text>,账号将存储大量热门资源,<n-text style='font-size:16px' type="error">需要手动推广</n-text></strong><br>
|
||||
4. 注意 推广获得20T容量,删除所有资源, 热门资源比较敏感,不建议,长期推广,仅用于扩容
|
||||
</span>
|
||||
</div>
|
||||
</n-alert>
|
||||
</template>
|
||||
|
||||
<n-collapse arrow-placement="right">
|
||||
<n-collapse-item title="达标要求" name="0">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-list-check text-blue-500 mr-2"></i>
|
||||
达标要求(以蜂小推为例)
|
||||
</h3>
|
||||
<span>首次账号累计7天转存 > 10 或 拉新 > 5</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="注意事项" name="1">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-exclamation-triangle text-orange-500 mr-2"></i>
|
||||
注意事项
|
||||
</h3>
|
||||
<span>每个人的转存,只有当日第一次转存,且通过手机转存,才算有效转存。</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="扩容原理" name="2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-question-circle text-purple-500 mr-2"></i>
|
||||
扩容原理
|
||||
</h3>
|
||||
<span>大量转存热播资源,这样才能尽可能快的达标。</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="为什么推荐蜂小推" name="3">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-thumbs-up text-green-500 mr-2"></i>
|
||||
为什么推荐蜂小推
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
登记后,第二天,即会发送 <strong class="text-blue-600">6T 空间</strong>,满足大量存储资源的前提条件。
|
||||
</p>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="蜂小推怎么注册" name="3">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
请扫描下方二维码进行注册。
|
||||
</p>
|
||||
<div class="mt-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg text-center">
|
||||
<n-qr-code :value="qrCode" />
|
||||
<!-- 内容区header - 账号列表标题 -->
|
||||
<template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">支持扩容的账号列表</span>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
共 {{ expansionAccounts.length }} 个账号
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区content - 账号列表详情 -->
|
||||
<template #content>
|
||||
<div class="flex-1 h-full overflow-hidden">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<n-spin size="large">
|
||||
<template #description>
|
||||
<span class="text-gray-500">加载中...</span>
|
||||
</template>
|
||||
</n-spin>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="expansionAccounts.length === 0" class="flex flex-col items-center justify-center py-12">
|
||||
<n-empty description="暂无可扩容的账号,请先添加有效的 quark 账号">
|
||||
<template #icon>
|
||||
<i class="fas fa-user-circle text-4xl text-gray-400"></i>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
|
||||
<!-- 账号列表内容的虚拟滚动列表 -->
|
||||
<div v-else class="h-full">
|
||||
<n-virtual-list class="h-full" :items="expansionAccounts" :item-size="80">
|
||||
<template #default="{ item }">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 左侧信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 平台图标 -->
|
||||
<span v-html="getPlatformIcon(item.service_type === 'quark' ? '夸克网盘' : '其他')" class="text-lg"></span>
|
||||
|
||||
<!-- 账号信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 line-clamp-1">
|
||||
{{ item.name }}
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ item.service_type === 'quark' ? '夸克网盘' : '其他平台' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 扩容状态 -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-tag v-if="item.expanded" type="success" size="small">
|
||||
已扩容
|
||||
</n-tag>
|
||||
<n-tag v-else type="warning" size="small">
|
||||
可扩容
|
||||
</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建时间 -->
|
||||
<div class="mt-2">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
创建时间: {{ formatDate(item.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作按钮 -->
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<n-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="item.expanded"
|
||||
:loading="expandingAccountId === item.id"
|
||||
@click="handleExpansion(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-expand"></i>
|
||||
</template>
|
||||
{{ item.expanded ? '已扩容' : '扩容' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
<!-- 抽屉和模态框 - 移动到AdminPageLayout外部 -->
|
||||
<n-drawer v-model:show="drawActive" :width="502" closable placement="right">
|
||||
<n-drawer-content title="扩容说明">
|
||||
<div class="space-y-6 p-4">
|
||||
<div class="mb-4">
|
||||
<p class="text-gray-700 text-large dark:text-gray-300 leading-relaxed">
|
||||
扩容是网盘公司提供给推广用户的<n-text type="success">特权</n-text>!需要先注册推广平台并<n-text type="success">达标</n-text>即可获得权益。
|
||||
</p>
|
||||
</div>
|
||||
</n-drawer-content>
|
||||
|
||||
<n-collapse arrow-placement="right">
|
||||
<n-collapse-item title="达标要求" name="0">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-list-check text-blue-500 mr-2"></i>
|
||||
达标要求(以蜂小推为例)
|
||||
</h3>
|
||||
<span>首次账号累计7天转存 > 10 或 拉新 > 5</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="注意事项" name="1">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-exclamation-triangle text-orange-500 mr-2"></i>
|
||||
注意事项
|
||||
</h3>
|
||||
<span>每个人的转存,只有当日第一次转存,且通过手机转存,才算有效转存。</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="扩容原理" name="2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-question-circle text-purple-500 mr-2"></i>
|
||||
扩容原理
|
||||
</h3>
|
||||
<span>大量转存热播资源,这样才能尽可能快的达标。</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="为什么推荐蜂小推" name="3">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-thumbs-up text-green-500 mr-2"></i>
|
||||
为什么推荐蜂小推
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
登记后,第二天,即会发送 <strong class="text-blue-600">6T 空间</strong>,满足大量存储资源的前提条件。
|
||||
</p>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="蜂小推怎么注册" name="3">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
请扫描下方二维码进行注册。
|
||||
</p>
|
||||
<div class="mt-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg text-center">
|
||||
<n-qr-code :value="qrCode" />
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</div>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
|
||||
<!-- 图片模态框 -->
|
||||
@@ -115,110 +215,6 @@
|
||||
</div>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
|
||||
<!-- 账号列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">支持扩容的账号列表</span>
|
||||
<div class="text-sm text-gray-500">
|
||||
共 {{ expansionAccounts.length }} 个账号
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<n-spin size="large">
|
||||
<template #description>
|
||||
<span class="text-gray-500">加载中...</span>
|
||||
</template>
|
||||
</n-spin>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="expansionAccounts.length === 0" class="flex flex-col items-center justify-center py-12">
|
||||
<n-empty description="暂无可扩容的账号,请先添加有效的 quark 账号">
|
||||
<template #icon>
|
||||
<i class="fas fa-user-circle text-4xl text-gray-400"></i>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
|
||||
<!-- 账号列表 -->
|
||||
<div v-else>
|
||||
<n-virtual-list :items="expansionAccounts" :item-size="80" style="max-height: 500px">
|
||||
<template #default="{ item }">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 左侧信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 平台图标 -->
|
||||
<span v-html="getPlatformIcon(item.service_type === 'quark' ? '夸克网盘' : '其他')" class="text-lg"></span>
|
||||
|
||||
<!-- 账号信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 line-clamp-1">
|
||||
{{ item.name }}
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ item.service_type === 'quark' ? '夸克网盘' : '其他平台' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 扩容状态 -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-tag v-if="item.expanded" type="success" size="small">
|
||||
已扩容
|
||||
</n-tag>
|
||||
<n-tag v-else type="warning" size="small">
|
||||
可扩容
|
||||
</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建时间 -->
|
||||
<div class="mt-2">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
创建时间: {{ formatDate(item.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作按钮 -->
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<n-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="item.expanded"
|
||||
:loading="expandingAccountId === item.id"
|
||||
@click="handleExpansion(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-expand"></i>
|
||||
</template>
|
||||
{{ item.expanded ? '已扩容' : '扩容' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 扩容任务列表 -->
|
||||
<n-card v-if="expansionTasks.length > 0" title="扩容任务列表">
|
||||
<n-data-table
|
||||
:columns="taskColumns"
|
||||
:data="expansionTasks"
|
||||
:pagination="false"
|
||||
max-height="400"
|
||||
size="small"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -233,7 +229,6 @@ import { useNotification, useDialog } from 'naive-ui'
|
||||
|
||||
// 响应式数据
|
||||
const expansionAccounts = ref([])
|
||||
const expansionTasks = ref([])
|
||||
const loading = ref(true)
|
||||
const expandingAccountId = ref(null)
|
||||
const drawActive = ref(false) // 侧边栏激活
|
||||
@@ -248,62 +243,6 @@ const pendingAccount = ref<any>(null) // 待处理的账号
|
||||
const taskApi = useTaskApi()
|
||||
const notification = useNotification()
|
||||
|
||||
// 表格列配置
|
||||
const taskColumns = [
|
||||
{
|
||||
title: '任务ID',
|
||||
key: 'id',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (row: any) => {
|
||||
const statusMap = {
|
||||
pending: { color: 'warning', text: '等待中', icon: 'fas fa-clock' },
|
||||
running: { color: 'info', text: '运行中', icon: 'fas fa-spinner fa-spin' },
|
||||
completed: { color: 'success', text: '已完成', icon: 'fas fa-check' },
|
||||
failed: { color: 'error', text: '失败', icon: 'fas fa-times' }
|
||||
}
|
||||
const status = statusMap[row.status as keyof typeof statusMap] || statusMap.failed
|
||||
return h('n-tag', { type: status.color }, {
|
||||
icon: () => h('i', { class: status.icon }),
|
||||
default: () => status.text
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'created_at',
|
||||
width: 150,
|
||||
render: (row: any) => formatDate(row.created_at)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 150,
|
||||
render: (row: any) => h('div', { class: 'flex space-x-2' }, [
|
||||
h('n-button', {
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
onClick: () => viewTaskDetails(row.id)
|
||||
}, '详情'),
|
||||
row.status === 'running' ? h('n-button', {
|
||||
size: 'small',
|
||||
type: 'warning',
|
||||
onClick: () => stopTask(row.id)
|
||||
}, '停止') : null
|
||||
].filter(Boolean))
|
||||
}
|
||||
]
|
||||
|
||||
// 获取支持扩容的账号列表
|
||||
const fetchExpansionAccounts = async () => {
|
||||
@@ -323,15 +262,6 @@ const fetchExpansionAccounts = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取扩容任务列表
|
||||
const fetchExpansionTasks = async () => {
|
||||
try {
|
||||
const response = await taskApi.getTasks({ taskType: 'expansion' })
|
||||
expansionTasks.value = response.tasks || []
|
||||
} catch (error) {
|
||||
console.error('获取扩容任务列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理扩容操作
|
||||
const handleExpansion = async (account) => {
|
||||
@@ -366,11 +296,12 @@ const confirmDataSourceSelection = async () => {
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
navigateTo('/admin/tasks')
|
||||
// 刷新数据
|
||||
await Promise.all([
|
||||
fetchExpansionAccounts(),
|
||||
fetchExpansionTasks()
|
||||
])
|
||||
// await Promise.all([
|
||||
// fetchExpansionAccounts(),
|
||||
// fetchExpansionTasks()
|
||||
// ])
|
||||
} catch (error) {
|
||||
console.error('创建扩容任务失败:', error)
|
||||
notification.error({
|
||||
@@ -384,52 +315,7 @@ const confirmDataSourceSelection = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 查看任务详情
|
||||
const viewTaskDetails = async (taskId) => {
|
||||
try {
|
||||
const status = await taskApi.getTaskStatus(taskId)
|
||||
console.log('任务详情:', status)
|
||||
|
||||
// 这里可以展示任务详情的模态框
|
||||
notification.info({
|
||||
title: '任务详情',
|
||||
content: `任务状态: ${status.status}, 总项目数: ${status.total_items}`,
|
||||
duration: 5000
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取任务详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 停止任务
|
||||
const stopTask = async (taskId) => {
|
||||
const dialog = useDialog()
|
||||
|
||||
dialog.warning({
|
||||
title: '确认停止',
|
||||
content: '确定要停止这个扩容任务吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await taskApi.stopTask(taskId)
|
||||
notification.success({
|
||||
title: '成功',
|
||||
content: '任务已停止',
|
||||
duration: 3000
|
||||
})
|
||||
await fetchExpansionTasks()
|
||||
} catch (error) {
|
||||
console.error('停止任务失败:', error)
|
||||
notification.error({
|
||||
title: '失败',
|
||||
content: '停止任务失败',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取平台图标
|
||||
const getPlatformIcon = (platformName) => {
|
||||
@@ -455,10 +341,7 @@ const formatDate = (dateString) => {
|
||||
|
||||
// 页面加载
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
fetchExpansionAccounts(),
|
||||
fetchExpansionTasks()
|
||||
])
|
||||
await fetchExpansionAccounts()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -469,4 +352,4 @@ onMounted(async () => {
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和按钮 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">账号管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理平台账号信息</p>
|
||||
@@ -26,41 +26,43 @@
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<n-card>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<n-input v-model:value="searchQuery" placeholder="搜索账号..." clearable class="flex-1">
|
||||
<template #prefix>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
</n-input>
|
||||
<!-- 过滤栏 - 搜索和筛选 -->
|
||||
<template #filter-bar>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<n-input v-model:value="searchQuery" placeholder="搜索账号..." clearable class="flex-1">
|
||||
<template #prefix>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
</n-input>
|
||||
|
||||
<n-select v-model:value="platform" placeholder="选择平台" :options="platformOptions" clearable
|
||||
@update:value="onPlatformChange" class="w-full md:w-48" />
|
||||
<n-select v-model:value="platform" placeholder="选择平台" :options="platformOptions" clearable
|
||||
@update:value="onPlatformChange" class="w-full md:w-48" />
|
||||
|
||||
<n-button type="primary" @click="handleSearch" class="w-full md:w-auto md:min-w-[100px]">
|
||||
<template #icon>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 账号列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">账号列表</span>
|
||||
<div class="text-sm text-gray-500">
|
||||
共 {{ filteredCksList.length }} 个账号
|
||||
</div>
|
||||
<n-button type="primary" @click="handleSearch" class="w-full md:w-auto md:min-w-[100px]">
|
||||
<template #icon>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<!-- 内容区header - 账号列表头部 -->
|
||||
<template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold text-gray-900 dark:text-white">账号列表</span>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
共 {{ filteredCksList.length }} 个账号
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区content - 账号列表表格 -->
|
||||
<template #content>
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<n-spin size="large">
|
||||
<template #description>
|
||||
@@ -69,7 +71,6 @@
|
||||
</n-spin>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="filteredCksList.length === 0" class="flex flex-col items-center justify-center py-12">
|
||||
<n-empty description="暂无账号">
|
||||
<template #icon>
|
||||
@@ -86,9 +87,9 @@
|
||||
</n-empty>
|
||||
</div>
|
||||
|
||||
<!-- 账号列表 -->
|
||||
<div v-else>
|
||||
<n-virtual-list :items="filteredCksList" :item-size="100" style="max-height: 500px">
|
||||
<!-- 账号列表和分页 -->
|
||||
<div v-else class="flex flex-col flex-1 h-full overflow-hidden">
|
||||
<n-virtual-list :items="filteredCksList" :item-size="100" class="h-full">
|
||||
<template #default="{ item }">
|
||||
<div
|
||||
class="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||
@@ -123,13 +124,13 @@
|
||||
<n-tag :type="item.is_valid ? 'success' : 'error'" size="small">
|
||||
{{ item.is_valid ? '有效' : '无效' }}
|
||||
</n-tag>
|
||||
<span class="text-xs text-gray-500">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
总空间: {{ formatFileSize(item.space) }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
已使用: {{ formatFileSize(Math.max(0, item.used_space || (item.space - item.left_space))) }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
剩余: {{ formatFileSize(Math.max(0, item.left_space)) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -171,15 +172,19 @@
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="flex justify-center">
|
||||
<n-pagination v-model:page="currentPage" v-model:page-size="itemsPerPage" :item-count="filteredCksList.length"
|
||||
:page-sizes="[10, 20, 50, 100]" show-size-picker @update:page="goToPage"
|
||||
@update:page-size="(size) => { itemsPerPage = size; currentPage = 1; }" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 内容区footer - 分页组件 -->
|
||||
<template #content-footer>
|
||||
<div class="p-4">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination v-model:page="currentPage" v-model:page-size="itemsPerPage" :item-count="filteredCksList.length"
|
||||
:page-sizes="[10, 20, 50, 100]" show-size-picker @update:page="goToPage"
|
||||
@update:page-size="(size) => { itemsPerPage = size; currentPage = 1; }" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
|
||||
<!-- 创建/编辑账号模态框 -->
|
||||
<n-modal :show="showCreateModal || showEditModal" preset="card" title="账号管理" style="width: 500px"
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题 -->
|
||||
<template #page-header>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<n-card>
|
||||
<!-- 顶部Tabs -->
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
animated
|
||||
class="mb-6"
|
||||
>
|
||||
<n-tab-pane name="qq" tab="QQ机器人">
|
||||
<div class="space-y-8">
|
||||
<!-- 内容区 - 配置表单 -->
|
||||
<template #content>
|
||||
<div class="config-content h-full">
|
||||
<!-- 顶部Tabs -->
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
animated
|
||||
class="mb-6"
|
||||
>
|
||||
<n-tab-pane name="qq" tab="QQ机器人">
|
||||
<div class="tab-content-container">
|
||||
<div class="space-y-8">
|
||||
<!-- 步骤1:Astrobot 安装指南 -->
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
@@ -87,10 +89,8 @@
|
||||
• 自动格式化搜索结果<br>
|
||||
• 支持超时时间配置<br><br>
|
||||
<strong>安装步骤:</strong><br>
|
||||
1. 下载插件文件<br>
|
||||
2. 将插件放入 Astrobot 的 plugins 目录<br>
|
||||
3. 重启 Astrobot<br>
|
||||
4. 在配置文件中添加插件配置
|
||||
1. Astrbot 插件市场, 搜索 urldb 安装<br>
|
||||
2. 根据下面的配置信息配置插件
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,38 +157,47 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="wechat" tab="微信公众号">
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">微信公众号机器人功能正在开发中,敬请期待</p>
|
||||
<div class="tab-content-container">
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">微信公众号机器人功能正在开发中,敬请期待</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="telegram" tab="Telegram机器人">
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">Telegram机器人功能正在开发中,敬请期待</p>
|
||||
<div class="tab-content-container">
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">Telegram机器人功能正在开发中,敬请期待</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="wechat_open" tab="微信开放平台">
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">微信开放平台机器人功能正在开发中,敬请期待</p>
|
||||
<div class="tab-content-container">
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">微信开放平台机器人功能正在开发中,敬请期待</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
|
||||
|
||||
// 设置页面布局
|
||||
@@ -278,5 +287,21 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
</style>
|
||||
/* 机器人管理页面样式 */
|
||||
|
||||
.config-content {
|
||||
padding: 8px;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
.dark .config-content {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
|
||||
/* tab内容容器 - 个别内容滚动 */
|
||||
.tab-content-container {
|
||||
height: calc(100vh - 240px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,32 +1,22 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和按钮 -->
|
||||
<template #page-header>
|
||||
<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">
|
||||
<n-button type="primary" @click="showAddModal = true">
|
||||
<template #icon>
|
||||
<i class="fas fa-plus"></i>
|
||||
</template>
|
||||
添加分类
|
||||
</n-button>
|
||||
<n-button @click="refreshData">
|
||||
<template #icon>
|
||||
<i class="fas fa-refresh"></i>
|
||||
</template>
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<n-alert title="分类用于对资源进行分类管理,可以关联多个标签" type="info" />
|
||||
<!-- 提示信息区域 -->
|
||||
<template #notice-section>
|
||||
<n-alert title="分类用于对资源进行分类管理,可以关联多个标签" type="info" />
|
||||
</template>
|
||||
|
||||
<!-- 搜索和操作 -->
|
||||
<n-card>
|
||||
<!-- 过滤栏 - 搜索和操作 -->
|
||||
<template #filter-bar>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex gap-2">
|
||||
<n-button @click="showAddModal = true" type="success">
|
||||
@@ -38,9 +28,9 @@
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="relative">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
@input="debounceSearch"
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
@input="debounceSearch"
|
||||
type="text"
|
||||
placeholder="搜索分类名称..."
|
||||
clearable
|
||||
@@ -58,21 +48,24 @@
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<!-- 分类列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">分类列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个分类</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内容区header - 分类列表标题 -->
|
||||
<!-- <template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">分类列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个分类</span>
|
||||
</div>
|
||||
</template> -->
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<!-- 内容区 - 分类数据 -->
|
||||
<template #content>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex h-full items-center justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="categories.length === 0" class="text-center py-8">
|
||||
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mb-4 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 48 48">
|
||||
<circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
|
||||
@@ -88,20 +81,38 @@
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="categories"
|
||||
:pagination="pagination"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:loading="loading"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</n-card>
|
||||
<!-- 数据表格 -->
|
||||
<div v-else class="flex flex-col h-full min-h-[600px]">
|
||||
<!-- 数据表格容器,自适应填充剩余高度 -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="categories"
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:loading="loading"
|
||||
:scroll-x="800"
|
||||
class="h-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑分类模态框 -->
|
||||
<!-- 分页组件外部显示 -->
|
||||
<div class="mt-4 flex justify-center border-t pt-4">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
show-size-picker
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="(size) => { pageSize = size; currentPage = 1; fetchData() }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
<!-- 添加/编辑分类模态框 -->
|
||||
<n-modal v-model:show="showAddModal" preset="card" :title="editingCategory ? '编辑分类' : '添加分类'" style="width: 500px">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
@@ -147,7 +158,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -288,23 +298,7 @@ const columns = [
|
||||
}
|
||||
]
|
||||
|
||||
// 分页配置
|
||||
const pagination = computed(() => ({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
itemCount: total.value,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
onChange: (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchData()
|
||||
},
|
||||
onUpdatePageSize: (size: number) => {
|
||||
pageSize.value = size
|
||||
currentPage.value = 1
|
||||
fetchData()
|
||||
}
|
||||
}))
|
||||
// 分页配置已经移到模板中外部显示
|
||||
|
||||
// 获取数据
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">数据推送</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-2">数据推送管理</p>
|
||||
</div>
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题 -->
|
||||
<template #page-header>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<!-- 内容区 -->
|
||||
<template #content>
|
||||
<div class="text-center py-12">
|
||||
<div class="text-gray-400 dark:text-gray-500 mb-4">
|
||||
<i class="fas fa-upload text-4xl"></i>
|
||||
@@ -13,11 +17,13 @@
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能开发中</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">数据推送功能正在开发中,敬请期待...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
|
||||
// 数据推送管理页面
|
||||
definePageMeta({
|
||||
layout: 'admin',
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<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>
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题 -->
|
||||
<template #page-header>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<n-card>
|
||||
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||
<!-- 手动批量转存 -->
|
||||
<n-tab-pane name="manual" tab="手动批量转存">
|
||||
<AdminManualBatchTransfer />
|
||||
</n-tab-pane>
|
||||
<!-- 内容区 - 配置表单 -->
|
||||
<template #content>
|
||||
<div class="config-content h-full">
|
||||
<!-- 顶部Tabs -->
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
animated
|
||||
class="mb-6"
|
||||
>
|
||||
<!-- 手动批量转存 -->
|
||||
<n-tab-pane name="manual" tab="手动批量转存">
|
||||
<div class="tab-content-container">
|
||||
<AdminManualBatchTransfer />
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 已转存列表 -->
|
||||
<n-tab-pane name="transferred" tab="已转存列表">
|
||||
<AdminTransferredList ref="transferredListRef" />
|
||||
</n-tab-pane>
|
||||
<!-- 已转存列表 -->
|
||||
<n-tab-pane name="transferred" tab="已转存列表">
|
||||
<div class="tab-content-container">
|
||||
<AdminTransferredList ref="transferredListRef" />
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 未转存列表 -->
|
||||
<n-tab-pane name="untransferred" tab="未转存列表">
|
||||
<AdminUntransferredList ref="untransferredListRef" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
<!-- 未转存列表 -->
|
||||
<n-tab-pane name="untransferred" tab="未转存列表">
|
||||
<div class="tab-content-container">
|
||||
<AdminUntransferredList ref="untransferredListRef" />
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
|
||||
// 页面配置
|
||||
definePageMeta({
|
||||
@@ -43,4 +60,24 @@ const activeTab = ref('manual')
|
||||
// 组件引用
|
||||
const transferredListRef = ref(null)
|
||||
const untransferredListRef = ref(null)
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 数据转存管理页面样式 */
|
||||
|
||||
.config-content {
|
||||
padding: 8px;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
.dark .config-content {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
|
||||
/* tab内容容器 - 个别内容滚动 */
|
||||
.tab-content-container {
|
||||
height: calc(100vh - 240px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和保存按钮 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">开发配置</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理API和开发相关配置</p>
|
||||
@@ -12,11 +12,11 @@
|
||||
</template>
|
||||
保存配置
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<n-card>
|
||||
<div class="space-y-6">
|
||||
<!-- 内容区 - 配置表单 -->
|
||||
<template #content>
|
||||
<div class="config-content h-full">
|
||||
<!-- API Token -->
|
||||
<div>
|
||||
<n-form-item label="公开API访问令牌" path="api_token">
|
||||
@@ -71,12 +71,13 @@
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
|
||||
// 设置页面布局
|
||||
definePageMeta({
|
||||
@@ -332,4 +333,13 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
</style>
|
||||
|
||||
.config-content {
|
||||
padding: 1rem;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
.dark .config-content {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
</style>
|
||||
@@ -1,36 +1,36 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout :is-sub-page="true">
|
||||
<!-- 页面头部 - 标题和按钮 -->
|
||||
<template #page-header>
|
||||
<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">
|
||||
<n-button
|
||||
@click="retryAllFailed"
|
||||
:disabled="selectedResources.length === 0 || isProcessing"
|
||||
:type="selectedResources.length > 0 && !isProcessing ? 'success' : 'default'"
|
||||
:loading="isProcessing"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="isProcessing" class="fas fa-spinner fa-spin"></i>
|
||||
<i v-else class="fas fa-redo"></i>
|
||||
</template>
|
||||
{{ isProcessing ? '处理中...' : `重新放入待处理池 (${selectedResources.length})` }}
|
||||
</n-button>
|
||||
<n-button
|
||||
@click="clearAllErrors"
|
||||
:disabled="selectedResources.length === 0 || isProcessing"
|
||||
:type="selectedResources.length > 0 && !isProcessing ? 'warning' : 'default'"
|
||||
:loading="isProcessing"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="isProcessing" class="fas fa-spinner fa-spin"></i>
|
||||
<i v-else class="fas fa-trash"></i>
|
||||
</template>
|
||||
{{ isProcessing ? '处理中...' : `删除失败资源 (${selectedResources.length})` }}
|
||||
</n-button>
|
||||
<n-button
|
||||
@click="retryAllFailed"
|
||||
:disabled="selectedResources.length === 0 || isProcessing"
|
||||
:type="selectedResources.length > 0 && !isProcessing ? 'success' : 'default'"
|
||||
:loading="isProcessing"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="isProcessing" class="fas fa-spinner fa-spin"></i>
|
||||
<i v-else class="fas fa-redo"></i>
|
||||
</template>
|
||||
{{ isProcessing ? '处理中...' : `重新放入待处理池 (${selectedResources.length})` }}
|
||||
</n-button>
|
||||
<n-button
|
||||
@click="clearAllErrors"
|
||||
:disabled="selectedResources.length === 0 || isProcessing"
|
||||
:type="selectedResources.length > 0 && !isProcessing ? 'warning' : 'default'"
|
||||
:loading="isProcessing"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="isProcessing" class="fas fa-spinner fa-spin"></i>
|
||||
<i v-else class="fas fa-trash"></i>
|
||||
</template>
|
||||
{{ isProcessing ? '处理中...' : `删除失败资源 (${selectedResources.length})` }}
|
||||
</n-button>
|
||||
<n-button @click="refreshData" type="info">
|
||||
<template #icon>
|
||||
<i class="fas fa-refresh"></i>
|
||||
@@ -38,189 +38,194 @@
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<n-card>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
||||
<n-select
|
||||
v-model:value="errorFilter"
|
||||
placeholder="选择状态"
|
||||
:options="statusOptions"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<n-button type="primary" @click="handleSearch" class="w-full md:w-auto md:min-w-[100px]">
|
||||
<template #icon>
|
||||
<i class="fas fa-search"></i>
|
||||
<!-- 过滤栏 - 搜索和筛选 -->
|
||||
<template #filter-bar>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<n-select
|
||||
v-model:value="errorFilter"
|
||||
placeholder="选择状态"
|
||||
:options="statusOptions"
|
||||
clearable
|
||||
/>
|
||||
<n-button type="primary" @click="handleSearch" class="w-full md:w-auto md:min-w-[100px]">
|
||||
<template #icon>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区header - 失败资源列表头部 -->
|
||||
<template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-lg font-semibold">失败资源列表</span>
|
||||
<n-checkbox
|
||||
:checked="isAllSelected"
|
||||
:indeterminate="isIndeterminate"
|
||||
@update:checked="toggleSelectAll"
|
||||
>
|
||||
全选
|
||||
</n-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500">
|
||||
<span>共 {{ totalCount }} 个资源,已选择 {{ selectedResources.length }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区content - 失败资源列表 -->
|
||||
<template #content>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<n-spin size="large">
|
||||
<template #description>
|
||||
<span class="text-gray-500">加载中...</span>
|
||||
</template>
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</div>
|
||||
|
||||
<!-- 失败资源列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-lg font-semibold">失败资源列表</span>
|
||||
<n-checkbox
|
||||
:checked="isAllSelected"
|
||||
:indeterminate="isIndeterminate"
|
||||
@update:checked="toggleSelectAll"
|
||||
>
|
||||
全选
|
||||
</n-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500">
|
||||
<span class="text-sm text-gray-500">共 {{ totalCount }} 个资源,已选择 {{ selectedResources.length }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 虚拟列表 -->
|
||||
<div v-else-if="failedResources.length > 0">
|
||||
<n-virtual-list
|
||||
:items="failedResources"
|
||||
:item-size="120"
|
||||
:item-resizable="true"
|
||||
style="max-height: 600px"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 左侧信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 复选框 -->
|
||||
<n-checkbox
|
||||
:checked="selectedResources.includes(item.id)"
|
||||
@update:checked="(checked) => {
|
||||
if (checked) {
|
||||
selectedResources.push(item.id)
|
||||
} else {
|
||||
const index = selectedResources.indexOf(item.id)
|
||||
if (index > -1) {
|
||||
selectedResources.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<n-spin size="large">
|
||||
<template #description>
|
||||
<span class="text-gray-500">加载中...</span>
|
||||
</template>
|
||||
</n-spin>
|
||||
</div>
|
||||
<!-- ID -->
|
||||
<div class="w-16 text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
#{{ item.id }}
|
||||
</div>
|
||||
|
||||
<!-- 虚拟列表 -->
|
||||
<n-virtual-list
|
||||
v-if="!loading"
|
||||
:items="failedResources"
|
||||
:item-size="80"
|
||||
:item-resizable="true"
|
||||
style="max-height: 400px"
|
||||
container-style="height: 600px;"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 左侧信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 复选框 -->
|
||||
<n-checkbox
|
||||
:checked="selectedResources.includes(item.id)"
|
||||
@update:checked="(checked) => {
|
||||
if (checked) {
|
||||
selectedResources.push(item.id)
|
||||
} else {
|
||||
const index = selectedResources.indexOf(item.id)
|
||||
if (index > -1) {
|
||||
selectedResources.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}"
|
||||
/>
|
||||
|
||||
<!-- ID -->
|
||||
<div class="w-16 text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
#{{ item.id }}
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 line-clamp-1" :title="item.title || '未设置'">
|
||||
{{ item.title || '未设置' }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<div class="mt-2 flex items-center space-x-2">
|
||||
<!-- 标题 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 line-clamp-1" :title="item.title || '未设置'">
|
||||
{{ item.title || '未设置' }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<div class="mt-2 flex items-center space-x-2">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 line-clamp-1 mt-1" :title="item.url">
|
||||
<a
|
||||
:href="checkUrlSafety(item.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"
|
||||
>
|
||||
{{ item.url }}
|
||||
</a>
|
||||
</p>
|
||||
<n-tag type="error" size="small" :title="item.error_msg">
|
||||
{{ truncateError(item.error_msg) }}
|
||||
</n-tag>
|
||||
</div>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<div class="flex items-center space-x-4 mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>创建时间: {{ formatTime(item.create_time) }}</span>
|
||||
<span>IP: {{ item.ip || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作按钮 -->
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<n-button
|
||||
size="small"
|
||||
type="success"
|
||||
@click="retryResource(item.id)"
|
||||
title="重试此资源"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-redo"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button
|
||||
size="small"
|
||||
type="warning"
|
||||
@click="clearError(item.id)"
|
||||
title="清除错误信息"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-broom"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button
|
||||
size="small"
|
||||
type="error"
|
||||
@click="deleteResource(item.id)"
|
||||
title="删除此资源"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
<a
|
||||
:href="checkUrlSafety(item.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"
|
||||
>
|
||||
{{ item.url }}
|
||||
</a>
|
||||
</p>
|
||||
<n-tag type="error" size="small" :title="item.error_msg">
|
||||
{{ truncateError(item.error_msg) }}
|
||||
</n-tag>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!loading && failedResources.length === 0" class="flex flex-col items-center justify-center py-12">
|
||||
<n-empty description="暂无失败资源">
|
||||
<template #icon>
|
||||
<i class="fas fa-check-circle text-4xl text-green-500"></i>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-sm text-gray-500">所有资源处理成功</span>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
<!-- 底部信息 -->
|
||||
<div class="flex items-center space-x-4 mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>创建时间: {{ formatTime(item.create_time) }}</span>
|
||||
<span>IP: {{ item.ip || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="totalCount"
|
||||
:page-sizes="[100, 200, 500, 1000]"
|
||||
show-size-picker
|
||||
@update:page="fetchData"
|
||||
@update:page-size="(size) => { pageSize = size; currentPage = 1; fetchData() }"
|
||||
/>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
<!-- 右侧操作按钮 -->
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<n-button
|
||||
size="small"
|
||||
type="success"
|
||||
@click="retryResource(item.id)"
|
||||
title="重试此资源"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-redo"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button
|
||||
size="small"
|
||||
type="warning"
|
||||
@click="clearError(item.id)"
|
||||
title="清除错误信息"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-broom"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button
|
||||
size="small"
|
||||
type="error"
|
||||
@click="deleteResource(item.id)"
|
||||
title="删除此资源"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="flex flex-col items-center justify-center py-12">
|
||||
<n-empty description="暂无失败资源">
|
||||
<template #icon>
|
||||
<i class="fas fa-check-circle text-4xl text-green-500"></i>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-sm text-gray-500">所有资源处理成功</span>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区footer - 分页组件 -->
|
||||
<template #content-footer>
|
||||
<div class="p-4">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="totalCount"
|
||||
:page-sizes="[100, 200, 500, 1000]"
|
||||
show-size-picker
|
||||
@update:page="fetchData"
|
||||
@update:page-size="(size) => { pageSize = size; currentPage = 1; fetchData() }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和保存按钮 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">功能配置</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理系统功能开关和参数设置</p>
|
||||
@@ -12,10 +12,11 @@
|
||||
</template>
|
||||
保存配置
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<n-card>
|
||||
<!-- 内容区 - 配置表单 -->
|
||||
<template #content>
|
||||
<div class="config-content h-full">
|
||||
<!-- 顶部Tabs -->
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
@@ -24,15 +25,15 @@
|
||||
class="mb-6"
|
||||
>
|
||||
<n-tab-pane name="resource" tab="资源处理">
|
||||
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="tab-content-container">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="space-y-8">
|
||||
<!-- 自动处理配置组 -->
|
||||
<div class="space-y-4">
|
||||
@@ -166,19 +167,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-form>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="transfer" tab="转存配置">
|
||||
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="tab-content-container">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- 自动转存 -->
|
||||
<div class="space-y-2">
|
||||
@@ -243,19 +245,20 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-form>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="drama" tab="热播剧">
|
||||
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="tab-content-container">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- 热播剧自动获取 -->
|
||||
<div class="space-y-2">
|
||||
@@ -266,17 +269,20 @@
|
||||
<n-switch v-model:value="configForm.hot_drama_auto_fetch" />
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-form>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useNotification } from 'naive-ui'
|
||||
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
|
||||
// 设置页面布局
|
||||
definePageMeta({
|
||||
@@ -537,4 +543,20 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
</style>
|
||||
|
||||
.config-content {
|
||||
padding: 8px;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
.dark .config-content {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
|
||||
/* tab内容容器 - 个别内容滚动 */
|
||||
.tab-content-container {
|
||||
height: calc(100vh - 240px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和按钮 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">文件管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理系统中的上传文件</p>
|
||||
@@ -20,13 +20,15 @@
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<n-alert title="支持图片格式文件,最大文件大小5MB" type="info" />
|
||||
<!-- 提示信息区域 -->
|
||||
<template #notice-section>
|
||||
<n-alert title="支持图片格式文件,最大文件大小5MB" type="info" />
|
||||
</template>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<n-card>
|
||||
<!-- 过滤栏 - 搜索功能 -->
|
||||
<template #filter-bar>
|
||||
<div class="flex gap-4">
|
||||
<n-input
|
||||
v-model:value="searchKeyword"
|
||||
@@ -39,7 +41,7 @@
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
</n-input>
|
||||
|
||||
|
||||
<n-button type="primary" @click="handleSearch" class="w-20">
|
||||
<template #icon>
|
||||
<i class="fas fa-search"></i>
|
||||
@@ -47,21 +49,24 @@
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">文件列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个文件</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内容区header - 文件列表标题 -->
|
||||
<!-- <template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">文件列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个文件</span>
|
||||
</div>
|
||||
</template> -->
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<!-- 内容区 - 文件列表 -->
|
||||
<template #content>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex h-full items-center justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="fileList.length === 0" class="text-center py-8">
|
||||
<i class="fas fa-file-upload text-4xl text-gray-400 mb-4"></i>
|
||||
<p class="text-gray-500">暂无文件数据</p>
|
||||
@@ -73,99 +78,106 @@
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- 图片预览区域 -->
|
||||
<div class="image-preview-container">
|
||||
<n-image-group>
|
||||
<div class="image-grid">
|
||||
<div
|
||||
v-for="file in fileList"
|
||||
:key="file.id"
|
||||
class="image-item"
|
||||
:class="{ 'is-image': isImageFile(file) }"
|
||||
>
|
||||
<!-- 图片文件显示预览 -->
|
||||
<div v-if="isImageFile(file)" class="file-item cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg p-3 transition-colors border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600">
|
||||
<div class="image-preview relative">
|
||||
<n-image
|
||||
:src="getImageUrl(file.access_url)"
|
||||
:alt="file.original_name"
|
||||
:lazy="false"
|
||||
object-fit="cover"
|
||||
class="preview-image rounded"
|
||||
@error="handleImageError"
|
||||
@load="handleImageLoad"
|
||||
/>
|
||||
<div class="delete-button">
|
||||
<n-button
|
||||
size="small"
|
||||
type="error"
|
||||
circle
|
||||
@click="confirmDelete(file)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-info mt-2">
|
||||
<div class="file-name text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{{ file.original_name }}
|
||||
</div>
|
||||
<div class="file-size text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ formatFileSize(file.file_size) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 非图片文件显示图标 -->
|
||||
<div v-else class="file-item cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg p-3 transition-colors border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600 relative">
|
||||
<div class="file-icon">
|
||||
<i :class="getFileIconClass(file.file_type)"></i>
|
||||
</div>
|
||||
<div class="file-info">
|
||||
<div class="file-name text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{{ file.original_name }}
|
||||
</div>
|
||||
<div class="file-size text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ formatFileSize(file.file_size) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="delete-button">
|
||||
<n-button
|
||||
size="small"
|
||||
type="error"
|
||||
circle
|
||||
@click="confirmDelete(file)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文件网格和分页容器 -->
|
||||
<div v-else class="flex flex-col h-full">
|
||||
<!-- 文件网格区域 - 自适应高度 -->
|
||||
<div class="flex-1 overflow-auto">
|
||||
<div class="file-list-container">
|
||||
<n-image-group>
|
||||
<div class="image-grid">
|
||||
<div
|
||||
v-for="file in fileList"
|
||||
:key="file.id"
|
||||
class="image-item"
|
||||
:class="{ 'is-image': isImageFile(file) }"
|
||||
>
|
||||
<!-- 图片文件显示预览 -->
|
||||
<div v-if="isImageFile(file)" class="file-item cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg p-3 transition-colors border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600">
|
||||
<div class="image-preview relative">
|
||||
<n-image
|
||||
:src="getImageUrl(file.access_url)"
|
||||
:alt="file.original_name"
|
||||
:lazy="false"
|
||||
object-fit="cover"
|
||||
class="preview-image rounded"
|
||||
@error="handleImageError"
|
||||
@load="handleImageLoad"
|
||||
/>
|
||||
<div class="delete-button">
|
||||
<n-button
|
||||
size="small"
|
||||
type="error"
|
||||
circle
|
||||
@click="confirmDelete(file)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-info mt-2">
|
||||
<div class="file-name text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{{ file.original_name }}
|
||||
</div>
|
||||
<div class="file-size text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ formatFileSize(file.file_size) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 非图片文件显示图标 -->
|
||||
<div v-else class="file-item cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg p-3 transition-colors border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600 relative">
|
||||
<div class="file-icon">
|
||||
<i :class="getFileIconClass(file.file_type)"></i>
|
||||
</div>
|
||||
<div class="file-info">
|
||||
<div class="file-name text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{{ file.original_name }}
|
||||
</div>
|
||||
<div class="file-size text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ formatFileSize(file.file_size) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="delete-button">
|
||||
<n-button
|
||||
size="small"
|
||||
type="error"
|
||||
circle
|
||||
@click="confirmDelete(file)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-image-group>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-count="Math.ceil(pagination.total / pagination.pageSize)"
|
||||
:page-sizes="pagination.pageSizes"
|
||||
show-size-picker
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
</n-image-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<!-- 上传模态框 -->
|
||||
<!-- 内容区footer - 分页组件 -->
|
||||
<template #content-footer>
|
||||
<div class="p-4">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:item-count="pagination.total"
|
||||
:page-sizes="[100, 200, 500, 1000]"
|
||||
show-size-picker
|
||||
@update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
<!-- 上传模态框 -->
|
||||
<n-modal v-model:show="showUploadModal" preset="card" title="上传文件" style="width: 800px" @update:show="handleModalClose">
|
||||
<FileUpload ref="fileUploadRef" :key="uploadModalKey" />
|
||||
<template #footer>
|
||||
@@ -191,7 +203,6 @@
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -479,12 +490,22 @@ onMounted(() => {
|
||||
<style scoped>
|
||||
/* 文件管理页面样式 */
|
||||
|
||||
.file-list-container {
|
||||
/* 容器样式,将替换原来的n-card背景 */
|
||||
padding: 1rem;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
/* 暗色主题支持 */
|
||||
.dark .file-list-container {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
max-height: 400px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -568,27 +589,26 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
/* 由于分页已移到外部,这里的样式不再需要 */
|
||||
/* 分页现在直接使用 AdminPageLayout 的 content-footer */
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.image-preview-container::-webkit-scrollbar {
|
||||
/* 滚动条样式 - 更新为新的容器类名 */
|
||||
.image-grid::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.image-preview-container::-webkit-scrollbar-track {
|
||||
.image-grid::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.image-preview-container::-webkit-scrollbar-thumb {
|
||||
.image-grid::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.image-preview-container::-webkit-scrollbar-thumb:hover {
|
||||
.image-grid::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和按钮 -->
|
||||
<template #page-header>
|
||||
<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">
|
||||
<n-button @click="navigateTo('/admin/failed-resources')" type="error">
|
||||
<n-button @click="navigateTo('/admin/failed-resources')" type="tertiary">
|
||||
<template #icon>
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</template>
|
||||
@@ -19,98 +19,48 @@
|
||||
</template>
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动处理配置状态 -->
|
||||
<n-card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fas fa-cog text-gray-600 dark:text-gray-400"></i>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">自动处理配置:</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div
|
||||
:class="[
|
||||
'w-3 h-3 rounded-full',
|
||||
systemConfig?.auto_process_ready_resources
|
||||
? 'bg-green-500 animate-pulse'
|
||||
: 'bg-red-500'
|
||||
]"
|
||||
></div>
|
||||
<span
|
||||
:class="[
|
||||
'text-sm font-medium',
|
||||
systemConfig?.auto_process_ready_resources
|
||||
? 'text-green-600 dark:text-green-400'
|
||||
: 'text-red-600 dark:text-red-400'
|
||||
]"
|
||||
>
|
||||
{{ systemConfig?.auto_process_ready_resources ? '已开启' : '已关闭' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
{{ systemConfig?.auto_process_ready_resources
|
||||
? '系统会自动处理待处理资源并入库'
|
||||
: '需要手动处理待处理资源'
|
||||
}}
|
||||
</div>
|
||||
<!-- <n-button
|
||||
@click="refreshConfig"
|
||||
:disabled="updatingConfig"
|
||||
size="small"
|
||||
type="tertiary"
|
||||
title="刷新配置"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</template>
|
||||
</n-button> -->
|
||||
<n-button
|
||||
<n-button
|
||||
@click="toggleAutoProcess"
|
||||
:disabled="updatingConfig"
|
||||
:type="systemConfig?.auto_process_ready_resources ? 'error' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
<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>
|
||||
{{ systemConfig?.auto_process_ready_resources ? '关闭' : '开启' }}
|
||||
{{ systemConfig?.auto_process_ready_resources ? '关闭自动处理' : '开启自动处理' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区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>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 资源列表 -->
|
||||
<n-card>
|
||||
<template #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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 内容区content - 资源列表 -->
|
||||
<template #content>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<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>
|
||||
@@ -125,6 +75,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div v-else>
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
@@ -136,8 +87,8 @@
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和按钮 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">资源管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理系统中的所有资源</p>
|
||||
@@ -26,93 +26,97 @@
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<n-card>
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索资源..."
|
||||
@keyup.enter="handleSearch"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
</n-input>
|
||||
|
||||
<n-select
|
||||
v-model:value="selectedCategory"
|
||||
placeholder="选择分类"
|
||||
:options="categoryOptions"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<n-select
|
||||
v-model:value="selectedPlatform"
|
||||
placeholder="选择平台"
|
||||
:options="platformOptions"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<n-button type="primary" @click="handleSearch" class="w-20">
|
||||
<template #icon>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<!-- 过滤栏 - 搜索和筛选 -->
|
||||
<template #filter-bar>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索资源..."
|
||||
@keyup.enter="handleSearch"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
</n-input>
|
||||
|
||||
<!-- 资源列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-lg font-semibold">资源列表</span>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="toggleSelectAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
/>
|
||||
<span class="text-sm text-gray-500">全选</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个资源,已选择 {{ selectedResources.length }} 个</span>
|
||||
<n-select
|
||||
v-model:value="selectedCategory"
|
||||
placeholder="选择分类"
|
||||
:options="categoryOptions"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<n-select
|
||||
v-model:value="selectedPlatform"
|
||||
placeholder="选择平台"
|
||||
:options="platformOptions"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<n-button type="primary" @click="handleSearch" class="w-20">
|
||||
<template #icon>
|
||||
<i class="fas fa-search"></i>
|
||||
</template>
|
||||
搜索
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<!-- 内容区header - 资源列表头部 -->
|
||||
<template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-lg font-semibold">资源列表</span>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="toggleSelectAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
/>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">全选</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">共 {{ total }} 个资源,已选择 {{ selectedResources.length }} 个</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区content - 资源列表 -->
|
||||
<template #content>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="resources.length === 0" class="text-center py-8">
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="resources.length === 0" class="flex flex-col items-center justify-center py-12">
|
||||
<i class="fas fa-inbox text-4xl text-gray-400 mb-4"></i>
|
||||
<p class="text-gray-500">暂无资源数据</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">暂无资源数据</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- 虚拟列表 -->
|
||||
<!-- 虚拟列表容器 -->
|
||||
<div v-else class="flex-1 h-full overflow-hidden">
|
||||
<n-virtual-list
|
||||
:items="resources"
|
||||
:item-size="100"
|
||||
style="max-height: 400px"
|
||||
container-style="height: 600px;"
|
||||
class="h-full"
|
||||
>
|
||||
<template #default="{ item: resource }">
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors mb-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-2 mb-2">
|
||||
<n-checkbox
|
||||
:value="resource.id"
|
||||
<n-checkbox
|
||||
:value="resource.id"
|
||||
:checked="selectedResources.includes(resource.id)"
|
||||
@update:checked="(checked) => toggleResourceSelection(resource.id, checked)"
|
||||
/>
|
||||
<span class="text-sm text-gray-500">{{ resource.id }}</span>
|
||||
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">{{ resource.id }}</span>
|
||||
|
||||
<span v-if="resource.pan_id" class="text-xs px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded flex-shrink-0">
|
||||
{{ getPlatformName(resource.pan_id) }}
|
||||
</span>
|
||||
@@ -122,14 +126,14 @@
|
||||
<span v-if="resource.category_id" class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded flex-shrink-0">
|
||||
{{ getCategoryName(resource.category_id) }}
|
||||
</span>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<p v-if="resource.description" class="text-gray-600 dark:text-gray-400 mb-2 line-clamp-2">
|
||||
{{ resource.description }}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center space-x-4 text-sm text-gray-500">
|
||||
|
||||
<div class="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span>
|
||||
<i class="fas fa-link mr-1"></i>
|
||||
{{ resource.url }}
|
||||
@@ -150,9 +154,9 @@
|
||||
|
||||
<div v-if="resource.tags && resource.tags.length > 0" class="mt-2">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-for="tag in resource.tags"
|
||||
:key="tag.id"
|
||||
<span
|
||||
v-for="tag in resource.tags"
|
||||
:key="tag.id"
|
||||
class="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded"
|
||||
>
|
||||
{{ tag.name }}
|
||||
@@ -160,7 +164,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<!-- <n-button size="small" type="primary" @click="editResource(resource)">
|
||||
<template #icon>
|
||||
@@ -179,9 +183,13 @@
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="mt-6">
|
||||
<!-- 内容区footer - 分页组件 -->
|
||||
<template #content-footer>
|
||||
<div class="p-4">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@@ -193,104 +201,107 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
|
||||
<!-- 批量操作模态框 -->
|
||||
<n-modal v-model:show="showBatchModal" preset="card" title="批量操作" style="width: 600px">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="font-medium">已选择 {{ selectedResources.length }} 个资源</span>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
{{ isAllSelected ? '已全选当前页面' : isIndeterminate ? '部分选中' : '未选择' }}
|
||||
</p>
|
||||
</div>
|
||||
<n-button size="small" @click="clearSelection">清空选择</n-button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<n-button type="error" @click="batchDelete" :disabled="selectedResources.length === 0">
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button type="warning" @click="batchUpdate" :disabled="selectedResources.length === 0">
|
||||
<template #icon>
|
||||
<i class="fas fa-edit"></i>
|
||||
</template>
|
||||
批量更新
|
||||
</n-button>
|
||||
<!-- 模态框 - 在AdminPageLayout外部 -->
|
||||
<!-- 批量操作模态框 -->
|
||||
<n-modal v-model:show="showBatchModal" preset="card" title="批量操作" style="width: 600px">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="font-medium">已选择 {{ selectedResources.length }} 个资源</span>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
{{ isAllSelected ? '已全选当前页面' : isIndeterminate ? '部分选中' : '未选择' }}
|
||||
</p>
|
||||
</div>
|
||||
<n-button size="small" @click="clearSelection">清空选择</n-button>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- 编辑资源模态框 -->
|
||||
<n-modal v-model:show="showEditModal" preset="card" title="编辑资源" style="width: 600px">
|
||||
<n-form
|
||||
ref="editFormRef"
|
||||
:model="editForm"
|
||||
:rules="editRules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input v-model:value="editForm.title" placeholder="请输入资源标题" />
|
||||
</n-form-item>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<n-button type="error" @click="batchDelete" :disabled="selectedResources.length === 0">
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button type="warning" @click="batchUpdate" :disabled="selectedResources.length === 0">
|
||||
<template #icon>
|
||||
<i class="fas fa-edit"></i>
|
||||
</template>
|
||||
批量更新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input
|
||||
v-model:value="editForm.description"
|
||||
type="textarea"
|
||||
placeholder="请输入资源描述"
|
||||
:rows="3"
|
||||
/>
|
||||
</n-form-item>
|
||||
<!-- 编辑资源模态框 -->
|
||||
<n-modal v-model:show="showEditModal" preset="card" title="编辑资源" style="width: 600px">
|
||||
<n-form
|
||||
ref="editFormRef"
|
||||
:model="editForm"
|
||||
:rules="editRules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input v-model:value="editForm.title" placeholder="请输入资源标题" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="URL" path="url">
|
||||
<n-input v-model:value="editForm.url" placeholder="请输入资源链接" />
|
||||
</n-form-item>
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input
|
||||
v-model:value="editForm.description"
|
||||
type="textarea"
|
||||
placeholder="请输入资源描述"
|
||||
:rows="3"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="分类" path="category_id">
|
||||
<n-select
|
||||
v-model:value="editForm.category_id"
|
||||
:options="categoryOptions"
|
||||
placeholder="请选择分类"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="URL" path="url">
|
||||
<n-input v-model:value="editForm.url" placeholder="请输入资源链接" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="平台" path="pan_id">
|
||||
<n-select
|
||||
v-model:value="editForm.pan_id"
|
||||
:options="platformOptions"
|
||||
placeholder="请选择平台"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="分类" path="category_id">
|
||||
<n-select
|
||||
v-model:value="editForm.category_id"
|
||||
:options="categoryOptions"
|
||||
placeholder="请选择分类"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="标签" path="tag_ids">
|
||||
<n-select
|
||||
v-model:value="editForm.tag_ids"
|
||||
:options="tagOptions"
|
||||
placeholder="请选择标签"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-form-item label="平台" path="pan_id">
|
||||
<n-select
|
||||
v-model:value="editForm.pan_id"
|
||||
:options="platformOptions"
|
||||
placeholder="请选择平台"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="标签" path="tag_ids">
|
||||
<n-select
|
||||
v-model:value="editForm.tag_ids"
|
||||
:options="tagOptions"
|
||||
placeholder="请选择标签"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<n-button @click="showEditModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="handleEditSubmit" :loading="editing">
|
||||
保存
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<n-button @click="showEditModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="handleEditSubmit" :loading="editing">
|
||||
保存
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">SEO管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-2">搜索引擎优化管理</p>
|
||||
</div>
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">SEO管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">搜索引擎优化管理</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Tab导航 -->
|
||||
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||
<n-tab-pane name="site-submit" tab="站点提交">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<!-- 内容区 -->
|
||||
<template #content>
|
||||
<div class="config-content h-full">
|
||||
<!-- Tab导航 -->
|
||||
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||
<n-tab-pane name="site-submit" tab="站点提交">
|
||||
<div class="tab-content-container">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">站点提交(待开发)</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">向各大搜索引擎提交站点信息</p>
|
||||
@@ -181,94 +188,101 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="link-building" tab="外链建设">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">外链建设(待开发)</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理和监控外部链接建设情况</p>
|
||||
</div>
|
||||
<n-tab-pane name="link-building" tab="外链建设">
|
||||
<div class="tab-content-container">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">外链建设(待开发)</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理和监控外部链接建设情况</p>
|
||||
</div>
|
||||
|
||||
<!-- 外链统计 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
|
||||
<i class="fas fa-link text-blue-600 dark:text-blue-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">总外链数</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.total }}</p>
|
||||
</div>
|
||||
<!-- 外链统计 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
|
||||
<i class="fas fa-link text-blue-600 dark:text-blue-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-green-100 dark:bg-green-900 rounded-lg">
|
||||
<i class="fas fa-check text-green-600 dark:text-green-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">有效外链</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.valid }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
|
||||
<i class="fas fa-clock text-yellow-600 dark:text-yellow-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">待审核</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.pending }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-red-50 dark:bg-red-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-red-100 dark:bg-red-900 rounded-lg">
|
||||
<i class="fas fa-times text-red-600 dark:text-red-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">失效外链</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.invalid }}</p>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">总外链数</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.total }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 外链列表 -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-lg font-medium text-gray-900 dark:text-white">外链列表</h4>
|
||||
<n-button type="primary" @click="addNewLink">
|
||||
<template #icon>
|
||||
<i class="fas fa-plus"></i>
|
||||
</template>
|
||||
添加外链
|
||||
</n-button>
|
||||
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-green-100 dark:bg-green-900 rounded-lg">
|
||||
<i class="fas fa-check text-green-600 dark:text-green-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">有效外链</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.valid }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-data-table
|
||||
:columns="linkColumns"
|
||||
:data="linkList"
|
||||
:pagination="linkPagination"
|
||||
:loading="linkLoading"
|
||||
:bordered="false"
|
||||
striped
|
||||
/>
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
|
||||
<i class="fas fa-clock text-yellow-600 dark:text-yellow-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">待审核</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.pending }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-red-50 dark:bg-red-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-red-100 dark:bg-red-900 rounded-lg">
|
||||
<i class="fas fa-times text-red-600 dark:text-red-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">失效外链</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ linkStats.invalid }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 外链列表 -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-lg font-medium text-gray-900 dark:text-white">外链列表</h4>
|
||||
<n-button type="primary" @click="addNewLink">
|
||||
<template #icon>
|
||||
<i class="fas fa-plus"></i>
|
||||
</template>
|
||||
添加外链
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<n-data-table
|
||||
:columns="linkColumns"
|
||||
:data="linkList"
|
||||
:pagination="linkPagination"
|
||||
:loading="linkLoading"
|
||||
:bordered="false"
|
||||
striped
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
|
||||
// SEO管理页面
|
||||
definePageMeta({
|
||||
layout: 'admin'
|
||||
@@ -493,4 +507,23 @@ const deleteLink = (row: any) => {
|
||||
onMounted(() => {
|
||||
loadLinkList()
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* SEO管理页面样式 */
|
||||
|
||||
.config-content {
|
||||
padding: 8px;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
.dark .config-content {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
|
||||
.tab-content-container {
|
||||
height: calc(100vh - 240px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和保存按钮 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">站点配置</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理网站基本信息和设置</p>
|
||||
@@ -12,172 +12,174 @@
|
||||
</template>
|
||||
保存配置
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<n-card>
|
||||
<!-- 顶部Tabs -->
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
animated
|
||||
class="mb-6"
|
||||
>
|
||||
<n-tab-pane name="basic" tab="基本信息">
|
||||
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- 网站标题 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">网站标题</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">网站的主要标识,显示在浏览器标签页和搜索结果中</span>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.site_title"
|
||||
placeholder="请输入网站标题"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 网站描述 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">网站描述</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">网站的简要介绍,用于SEO和社交媒体分享</span>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.site_description"
|
||||
placeholder="请输入网站描述"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 关键词 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">关键词</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">用于SEO优化,多个关键词用逗号分隔</span>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.keywords"
|
||||
placeholder="请输入关键词,用逗号分隔"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 网站Logo -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">网站Logo</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">选择网站Logo图片,建议使用正方形图片</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div v-if="configForm.site_logo" class="flex-shrink-0">
|
||||
<n-image
|
||||
:src="getImageUrl(configForm.site_logo)"
|
||||
alt="网站Logo"
|
||||
width="80"
|
||||
height="80"
|
||||
object-fit="cover"
|
||||
class="rounded-lg border"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<n-button type="primary" @click="openLogoSelector">
|
||||
<template #icon>
|
||||
<i class="fas fa-image"></i>
|
||||
</template>
|
||||
{{ configForm.site_logo ? '更换Logo' : '选择Logo' }}
|
||||
</n-button>
|
||||
<n-button v-if="configForm.site_logo" @click="clearLogo" class="ml-2">
|
||||
<template #icon>
|
||||
<i class="fas fa-times"></i>
|
||||
</template>
|
||||
清除
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版权信息 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">版权信息</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">网站底部的版权声明信息</span>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.copyright"
|
||||
placeholder="请输入版权信息"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
|
||||
|
||||
|
||||
<n-tab-pane name="security" tab="安全设置">
|
||||
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- 维护模式 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">维护模式</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">开启后网站将显示维护页面,暂停用户访问</span>
|
||||
</div>
|
||||
<n-switch v-model:value="configForm.maintenance_mode" />
|
||||
</div>
|
||||
|
||||
<!-- 违禁词 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 内容区 - 配置表单 -->
|
||||
<template #content>
|
||||
<div class="config-content h-full">
|
||||
<!-- 顶部Tabs -->
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
animated
|
||||
>
|
||||
<n-tab-pane name="basic" tab="基本信息">
|
||||
<div class="tab-content-container">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- 网站标题 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">违禁词</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">包含这些词汇的资源将被过滤,多个词汇用逗号分隔</span>
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">网站标题</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">网站的主要标识,显示在浏览器标签页和搜索结果中</span>
|
||||
</div>
|
||||
<a
|
||||
href="https://raw.githubusercontent.com/ctwj/urldb/refs/heads/main/db/forbidden.txt"
|
||||
target="_blank"
|
||||
class="text-xs text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 underline"
|
||||
>
|
||||
开源违禁词
|
||||
</a>
|
||||
<n-input
|
||||
v-model:value="configForm.site_title"
|
||||
placeholder="请输入网站标题"
|
||||
/>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.forbidden_words"
|
||||
placeholder="请输入违禁词,用逗号分隔"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 开启注册 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">开启注册</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">开启后用户才能注册新账号,关闭后注册页面将显示"当前系统已关闭注册功能"</span>
|
||||
<!-- 网站描述 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">网站描述</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">网站的简要介绍,用于SEO和社交媒体分享</span>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.site_description"
|
||||
placeholder="请输入网站描述"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 关键词 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">关键词</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">用于SEO优化,多个关键词用逗号分隔</span>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.keywords"
|
||||
placeholder="请输入关键词,用逗号分隔"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 网站Logo -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">网站Logo</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">选择网站Logo图片,建议使用正方形图片</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div v-if="configForm.site_logo" class="flex-shrink-0">
|
||||
<n-image
|
||||
:src="getImageUrl(configForm.site_logo)"
|
||||
alt="网站Logo"
|
||||
width="80"
|
||||
height="80"
|
||||
object-fit="cover"
|
||||
class="rounded-lg border"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<n-button type="primary" @click="openLogoSelector">
|
||||
<template #icon>
|
||||
<i class="fas fa-image"></i>
|
||||
</template>
|
||||
{{ configForm.site_logo ? '更换Logo' : '选择Logo' }}
|
||||
</n-button>
|
||||
<n-button v-if="configForm.site_logo" @click="clearLogo" class="ml-2">
|
||||
<template #icon>
|
||||
<i class="fas fa-times"></i>
|
||||
</template>
|
||||
清除
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版权信息 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">版权信息</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">网站底部的版权声明信息</span>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.copyright"
|
||||
placeholder="请输入版权信息"
|
||||
/>
|
||||
</div>
|
||||
<n-switch v-model:value="configForm.enable_register" />
|
||||
</div>
|
||||
</n-form>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="security" tab="安全设置">
|
||||
|
||||
<div class="tab-content-container">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="configForm"
|
||||
:rules="rules"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- 维护模式 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">维护模式</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">开启后网站将显示维护页面,暂停用户访问</span>
|
||||
</div>
|
||||
<n-switch v-model:value="configForm.maintenance_mode" />
|
||||
</div>
|
||||
|
||||
<!-- 违禁词 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">违禁词</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">包含这些词汇的资源将被过滤,多个词汇用逗号分隔</span>
|
||||
</div>
|
||||
<a
|
||||
href="https://raw.githubusercontent.com/ctwj/urldb/refs/heads/main/db/forbidden.txt"
|
||||
target="_blank"
|
||||
class="text-xs text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 underline"
|
||||
>
|
||||
开源违禁词
|
||||
</a>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="configForm.forbidden_words"
|
||||
placeholder="请输入违禁词,用逗号分隔"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 开启注册 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">开启注册</label>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">开启后用户才能注册新账号,关闭后注册页面将显示"当前系统已关闭注册功能"</span>
|
||||
</div>
|
||||
<n-switch v-model:value="configForm.enable_register" />
|
||||
</div>
|
||||
</div>
|
||||
</n-form>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
<!-- Logo选择模态框 -->
|
||||
<n-modal v-model:show="showLogoSelector" preset="card" title="选择Logo图片" style="width: 90vw; max-width: 1200px; max-height: 80vh;">
|
||||
<div class="space-y-4">
|
||||
@@ -271,7 +273,6 @@
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -584,7 +585,31 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
/* 站点配置页面样式 */
|
||||
|
||||
.config-content {
|
||||
padding: 8px;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
.dark .config-content {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
|
||||
/* 配置标签容器 - 支持滚动 */
|
||||
.config-tabs-container {
|
||||
height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* tab内容容器 - 个别内容滚动 */
|
||||
.tab-content-container {
|
||||
height: calc(100vh - 240px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.file-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
@@ -611,11 +636,9 @@ onMounted(() => {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,32 +1,21 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<template #page-header>
|
||||
<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">
|
||||
<n-button type="primary" @click="showAddModal = true">
|
||||
<template #icon>
|
||||
<i class="fas fa-plus"></i>
|
||||
</template>
|
||||
添加标签
|
||||
</n-button>
|
||||
<n-button @click="refreshData">
|
||||
<template #icon>
|
||||
<i class="fas fa-refresh"></i>
|
||||
</template>
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<n-alert title="提交的数据中,如果包含标签,数据添加成功,会自动添加标签" type="info" />
|
||||
<!-- 提示信息区域 -->
|
||||
<template #notice-section>
|
||||
<n-alert title="提交的数据中,如果包含标签,数据添加成功,会自动添加标签" type="info" />
|
||||
</template>
|
||||
|
||||
<!-- 搜索和操作 -->
|
||||
<n-card>
|
||||
<!-- 过滤栏 - 搜索和操作 -->
|
||||
<template #filter-bar>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex gap-2">
|
||||
<n-button @click="showAddModal = true" type="success">
|
||||
@@ -38,9 +27,9 @@
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="relative">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
@input="debounceSearch"
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
@input="debounceSearch"
|
||||
type="text"
|
||||
placeholder="搜索标签名称..."
|
||||
clearable
|
||||
@@ -58,21 +47,24 @@
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<!-- 标签列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">标签列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个标签</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内容区header - 标签列表标题 -->
|
||||
<!-- <template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">标签列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个标签</span>
|
||||
</div>
|
||||
</template> -->
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<!-- 内容区 - 标签数据 -->
|
||||
<template #content>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex h-full items-center justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="tags.length === 0" class="text-center py-8">
|
||||
<svg class="w-16 h-16 text-gray-300 dark:text-gray-600 mb-4 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 48 48">
|
||||
<circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
|
||||
@@ -87,21 +79,40 @@
|
||||
添加标签
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- 数据表格 - 自适应高度 -->
|
||||
<div v-else class="flex flex-col h-full overflow-auto">
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="tags"
|
||||
:pagination="pagination"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:loading="loading"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
:columns="columns"
|
||||
:data="tags"
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:loading="loading"
|
||||
:scroll-x="800"
|
||||
class="h-full"
|
||||
/>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<!-- 添加/编辑标签模态框 -->
|
||||
<!-- 内容区footer - 分页组件 -->
|
||||
<template #content-footer>
|
||||
<div class="p-4">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="total"
|
||||
:page-sizes="[100, 200, 500, 1000]"
|
||||
show-size-picker
|
||||
@update:page="fetchData"
|
||||
@update:page-size="(size) => { pageSize = size; currentPage = 1; fetchData() }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</AdminPageLayout>
|
||||
<!-- 添加/编辑标签模态框 -->
|
||||
<n-modal v-model:show="showAddModal" preset="card" :title="editingTag ? '编辑标签' : '添加标签'" style="width: 500px">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
@@ -146,7 +157,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -291,23 +301,7 @@ const columns = [
|
||||
}
|
||||
]
|
||||
|
||||
// 分页配置
|
||||
const pagination = computed(() => ({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
itemCount: total.value,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
onChange: (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchData()
|
||||
},
|
||||
onUpdatePageSize: (size: number) => {
|
||||
pageSize.value = size
|
||||
currentPage.value = 1
|
||||
fetchData()
|
||||
}
|
||||
}))
|
||||
// 分页配置已经被移到模板中处理
|
||||
|
||||
// 获取数据
|
||||
const fetchData = async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<AdminPageLayout>
|
||||
<!-- 页面头部 - 标题和操作按钮 -->
|
||||
<template #page-header>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">用户管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理系统中的用户账户</p>
|
||||
@@ -20,21 +20,25 @@
|
||||
刷新
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<n-alert title="用户管理功能,可以创建、编辑、删除用户,以及修改用户密码" type="info" />
|
||||
<!-- 通知区域 -->
|
||||
<template #notice-section>
|
||||
<n-alert title="用户管理功能,可以创建、编辑、删除用户,以及修改用户密码" type="info" />
|
||||
</template>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">用户列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个用户</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内容区header -->
|
||||
<template #content-header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">用户列表</span>
|
||||
<span class="text-sm text-gray-500">共 {{ total }} 个用户</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<!-- 内容区 - 用户列表 -->
|
||||
<template #content>
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<n-spin size="large" />
|
||||
</div>
|
||||
|
||||
@@ -53,20 +57,38 @@
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div v-else class="h-full">
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="users"
|
||||
:pagination="pagination"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:loading="loading"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<!-- 创建/编辑用户模态框 -->
|
||||
|
||||
<!-- 内容区footer - 分页组件 -->
|
||||
<template #content-footer>
|
||||
<div class="p-4">
|
||||
<div class="flex justify-center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:item-count="total"
|
||||
:page-sizes="[100, 200, 500, 1000]"
|
||||
show-size-picker
|
||||
@update:page="fetchData"
|
||||
@update:page-size="(size) => { pageSize = size; currentPage = 1; fetchData() }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
|
||||
<!-- 创建/编辑用户模态框 -->
|
||||
<n-modal v-model:show="showModal" preset="card" :title="showEditModal ? '编辑用户' : '创建用户'" style="width: 500px">
|
||||
<div v-if="showEditModal && editingUser?.username === 'admin'" class="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<p class="text-sm text-yellow-800">
|
||||
@@ -138,7 +160,7 @@
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- 修改密码模态框 -->
|
||||
<!-- 修改密码模态框 -->
|
||||
<n-modal v-model:show="showChangePasswordModal" preset="card" title="修改密码" style="width: 400px">
|
||||
<n-form
|
||||
ref="passwordFormRef"
|
||||
@@ -176,10 +198,11 @@
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
|
||||
// 设置页面布局
|
||||
definePageMeta({
|
||||
layout: 'admin'
|
||||
@@ -594,4 +617,13 @@ const showModal = computed({
|
||||
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
</style>
|
||||
|
||||
.config-content {
|
||||
padding: 1rem;
|
||||
background-color: var(--color-white, #ffffff);
|
||||
}
|
||||
|
||||
.dark .config-content {
|
||||
background-color: var(--color-dark-bg, #1f2937);
|
||||
}
|
||||
</style>
|
||||
@@ -65,7 +65,7 @@ export const useTaskStore = defineStore('task', () => {
|
||||
// 获取任务统计信息
|
||||
const fetchTaskStats = async () => {
|
||||
try {
|
||||
const response = await taskApi.getTasks({status: 'running'}) as any
|
||||
const response = await taskApi.getTasks() as any
|
||||
// console.log('原始任务API响应:', response)
|
||||
|
||||
// 处理API响应格式
|
||||
|
||||
Reference in New Issue
Block a user