update: ui

This commit is contained in:
ctwj
2025-09-14 10:26:58 +08:00
parent 9690a63646
commit d23a6b26e4
24 changed files with 1887 additions and 1713 deletions

View File

@@ -394,6 +394,7 @@ func (h *TaskHandler) CreateExpansionTask(c *gin.Context) {
var req struct { var req struct {
PanAccountID uint `json:"pan_account_id" binding:"required"` PanAccountID uint `json:"pan_account_id" binding:"required"`
Description string `json:"description"` Description string `json:"description"`
DataSource map[string]interface{} `json:"dataSource"`
} }
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
@@ -420,10 +421,14 @@ func (h *TaskHandler) CreateExpansionTask(c *gin.Context) {
accountName = fmt.Sprintf("账号%d", cks.ID) accountName = fmt.Sprintf("账号%d", cks.ID)
} }
// 构建任务配置存储账号ID // 构建任务配置存储账号ID和数据源
taskConfig := map[string]interface{}{ taskConfig := map[string]interface{}{
"pan_account_id": req.PanAccountID, "pan_account_id": req.PanAccountID,
} }
// 如果有数据源配置添加到taskConfig中
if req.DataSource != nil && len(req.DataSource) > 0 {
taskConfig["data_source"] = req.DataSource
}
configJSON, _ := json.Marshal(taskConfig) configJSON, _ := json.Marshal(taskConfig)
// 创建任务标题,包含账号名称 // 创建任务标题,包含账号名称
@@ -451,6 +456,10 @@ func (h *TaskHandler) CreateExpansionTask(c *gin.Context) {
expansionInput := task.ExpansionInput{ expansionInput := task.ExpansionInput{
PanAccountID: req.PanAccountID, PanAccountID: req.PanAccountID,
} }
// 如果有数据源配置,添加到输入数据中
if req.DataSource != nil && len(req.DataSource) > 0 {
expansionInput.DataSource = req.DataSource
}
inputJSON, _ := json.Marshal(expansionInput) inputJSON, _ := json.Marshal(expansionInput)

View File

@@ -30,6 +30,7 @@ func (ep *ExpansionProcessor) GetTaskType() string {
// ExpansionInput 扩容任务输入数据结构 // ExpansionInput 扩容任务输入数据结构
type ExpansionInput struct { type ExpansionInput struct {
PanAccountID uint `json:"pan_account_id"` PanAccountID uint `json:"pan_account_id"`
DataSource map[string]interface{} `json:"data_source,omitempty"`
} }
// ExpansionOutput 扩容任务输出数据结构 // ExpansionOutput 扩容任务输出数据结构
@@ -93,8 +94,8 @@ func (ep *ExpansionProcessor) Process(ctx context.Context, taskID uint, item *en
return err return err
} }
// 执行扩容操作(这里留空,直接返回成功 // 执行扩容操作(传入数据源
if err := ep.performExpansion(ctx, input.PanAccountID); err != nil { if err := ep.performExpansion(ctx, input.PanAccountID, input.DataSource); err != nil {
output := ExpansionOutput{ output := ExpansionOutput{
Success: false, Success: false,
Message: "扩容失败", Message: "扩容失败",
@@ -177,11 +178,11 @@ func (ep *ExpansionProcessor) checkAccountType(panAccountID uint) error {
} }
// performExpansion 执行扩容操作 // 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: 实现具体的扩容逻辑 // TODO: 实现具体的扩容逻辑
utils.Info("执行扩容操作账号ID: %d", panAccountID) utils.Info("执行扩容操作账号ID: %d, 数据源: %v", panAccountID, dataSource)
// 模拟扩容操作延迟 // 模拟扩容操作延迟
// time.Sleep(2 * time.Second) // time.Sleep(2 * time.Second)

View File

@@ -1,8 +1,7 @@
<template> <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="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 左侧资源输入 --> <!-- 左侧资源输入 -->
<div class="space-y-4"> <div class="space-y-4">
@@ -59,15 +58,15 @@
:loading="accountsLoading" :loading="accountsLoading"
@update:value="handleAccountChange" @update:value="handleAccountChange"
> >
<template #option="{ option: accountOption }"> <template #option="scope">
<div class="flex items-center justify-between w-full"> <div class="flex items-center justify-between w-full" v-if="scope && scope.option">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<span class="text-sm">{{ accountOption.label }}</span> <span class="text-sm">{{ scope.option.label || '未知账号' }}</span>
<n-tag v-if="accountOption.is_valid" type="success" size="small">有效</n-tag> <n-tag v-if="scope.option.is_valid" type="success" size="small">有效</n-tag>
<n-tag v-else type="error" size="small">无效</n-tag> <n-tag v-else type="error" size="small">无效</n-tag>
</div> </div>
<div class="text-xs text-gray-500"> <div class="text-xs text-gray-500">
{{ formatSpace(accountOption.left_space) }} {{ formatSpace(scope.option.left_space || 0) }}
</div> </div>
</div> </div>
</template> </template>
@@ -106,7 +105,6 @@
</div> </div>
</div> </div>
</div> </div>
</n-card>
<!-- 处理结果 --> <!-- 处理结果 -->
<n-card v-if="results.length > 0" title="转存结果"> <n-card v-if="results.length > 0" title="转存结果">
@@ -202,15 +200,15 @@ const invalidUrls = computed(() => {
}) })
const successCount = 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(() => { 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(() => { 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' } pending: { color: 'warning', text: '等待中', icon: 'fas fa-clock' }
} }
const status = statusMap[row.status as keyof typeof statusMap] || statusMap.failed const status = statusMap[row.status as keyof typeof statusMap] || statusMap.failed
return h('n-tag', { type: status.color }, { const safeStatus = status || statusMap.failed
icon: () => h('i', { class: status.icon }), return h('n-tag', { type: safeStatus.color }, {
default: () => status.text icon: () => h('i', { class: safeStatus.icon }),
default: () => safeStatus.text
}) })
} }
}, },
@@ -264,7 +263,7 @@ const resultColumns = [
tooltip: true tooltip: true
}, },
render: (row: any) => { render: (row: any) => {
if (row.saveUrl) { if (row && row.saveUrl) {
return h('a', { return h('a', {
href: row.saveUrl, href: row.saveUrl,
target: '_blank', target: '_blank',
@@ -355,6 +354,10 @@ const handleBatchTransfer = async () => {
const taskResponse = await taskApi.createBatchTransferTask(taskData) as any const taskResponse = await taskApi.createBatchTransferTask(taskData) as any
console.log('任务创建响应:', taskResponse) console.log('任务创建响应:', taskResponse)
if (!taskResponse || !taskResponse.task_id) {
throw new Error('创建任务失败:响应数据无效')
}
currentTaskId.value = taskResponse.task_id currentTaskId.value = taskResponse.task_id
// 第四步:启动任务 // 第四步:启动任务
@@ -523,14 +526,17 @@ const getAccountOptions = async () => {
const response = await cksApi.getCks() as any const response = await cksApi.getCks() as any
const accounts = Array.isArray(response) ? response : [] const accounts = Array.isArray(response) ? response : []
accountOptions.value = accounts.map((account: any) => ({ accountOptions.value = accounts.map((account: any) => {
if (!account) return null
return {
label: `${account.username || '未知用户'} (${account.pan?.name || '未知平台'})`, label: `${account.username || '未知用户'} (${account.pan?.name || '未知平台'})`,
value: account.id, value: account.id,
is_valid: account.is_valid, is_valid: account.is_valid || false,
left_space: account.left_space, left_space: account.left_space || 0,
username: account.username, username: account.username || '未知用户',
pan_name: account.pan?.name || '未知平台' pan_name: account.pan?.name || '未知平台'
})) }
}).filter(option => option !== null) as any[]
} catch (error) { } catch (error) {
console.error('获取网盘账号选项失败:', error) console.error('获取网盘账号选项失败:', error)
message.error('获取网盘账号失败') message.error('获取网盘账号失败')

View File

@@ -1,7 +1,7 @@
<template> <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 <n-input
v-model:value="searchQuery" v-model:value="searchQuery"
placeholder="搜索已转存资源..." placeholder="搜索已转存资源..."
@@ -34,28 +34,111 @@
</div> </div>
<!-- 调试信息 --> <!-- 调试信息 -->
<div class="text-sm text-gray-500 mb-2"> <div class="flex-0 text-sm text-gray-500">
数据数量: {{ resources.length }}, 总数: {{ total }}, 加载状态: {{ loading }} 数据数量: {{ resources.length }}, 总数: {{ total }}, 加载状态: {{ loading }}
</div> </div>
<!-- 数据表格 --> <!-- 资源列表 -->
<n-data-table <div class="flex-1 h-1">
:columns="columns" <div v-if="loading" class="flex items-center justify-center py-8">
:data="resources" <n-spin size="large" />
:loading="loading" </div>
:pagination="pagination"
:remote="true" <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="handlePageChange"
@update:page-size="handlePageSizeChange" @update:page-size="handlePageSizeChange"
:row-key="(row: any) => row.id"
virtual-scroll
max-height="500"
/> />
</div> </div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <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 { useResourceApi, usePanApi } from '~/composables/useApi'
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
@@ -81,78 +164,17 @@ const panApi = usePanApi()
// 获取平台数据 // 获取平台数据
const { data: platformsData } = await useAsyncData('transferredPlatforms', () => panApi.getPans()) 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 getPlatformName = (platformId: number) => {
const platform = (platformsData.value as any)?.data?.find((plat: any) => plat.id === platformId) const platform = (platformsData.value as any)?.data?.find((plat: any) => plat.id === platformId)
return platform?.remark || platform?.name || '未知平台' return platform?.remark || platform?.name || '未知平台'
} }
// 分页配置 // 格式化日期
const pagination = reactive({ const formatDate = (dateString: string) => {
page: 1, if (!dateString) return '未知时间'
pageSize: 10000, return new Date(dateString).toLocaleDateString()
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 fetchTransferredResources = async () => { const fetchTransferredResources = async () => {
@@ -183,24 +205,20 @@ const fetchTransferredResources = async () => {
console.log('使用嵌套data格式数量:', result.data.data.length) console.log('使用嵌套data格式数量:', result.data.data.length)
resources.value = result.data.data resources.value = result.data.data
total.value = result.data.total || 0 total.value = result.data.total || 0
pagination.itemCount = result.data.total || 0
} else { } else {
// 处理直接的data结构{data: [...], total: ...} // 处理直接的data结构{data: [...], total: ...}
console.log('使用直接data格式数量:', result.data.length) console.log('使用直接data格式数量:', result.data.length)
resources.value = result.data resources.value = result.data
total.value = result.total || 0 total.value = result.total || 0
pagination.itemCount = result.total || 0
} }
} else if (Array.isArray(result)) { } else if (Array.isArray(result)) {
console.log('使用数组格式,数量:', result.length) console.log('使用数组格式,数量:', result.length)
resources.value = result resources.value = result
total.value = result.length total.value = result.length
pagination.itemCount = result.length
} else { } else {
console.log('未知格式,设置空数组') console.log('未知格式,设置空数组')
resources.value = [] resources.value = []
total.value = 0 total.value = 0
pagination.itemCount = 0
} }
console.log('最终 resources.value:', resources.value) console.log('最终 resources.value:', resources.value)
@@ -223,22 +241,18 @@ const fetchTransferredResources = async () => {
// 搜索处理 // 搜索处理
const handleSearch = () => { const handleSearch = () => {
currentPage.value = 1 currentPage.value = 1
pagination.page = 1
fetchTransferredResources() fetchTransferredResources()
} }
// 分页处理 // 分页处理
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
currentPage.value = page currentPage.value = page
pagination.page = page
fetchTransferredResources() fetchTransferredResources()
} }
const handlePageSizeChange = (size: number) => { const handlePageSizeChange = (size: number) => {
pageSize.value = size pageSize.value = size
pagination.pageSize = size
currentPage.value = 1 currentPage.value = 1
pagination.page = 1
fetchTransferredResources() fetchTransferredResources()
} }
@@ -247,3 +261,12 @@ onMounted(() => {
fetchTransferredResources() fetchTransferredResources()
}) })
</script> </script>
<style scoped>
.line-clamp-1 {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>

View File

@@ -1,7 +1,7 @@
<template> <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 <n-input
v-model:value="searchQuery" v-model:value="searchQuery"
placeholder="搜索未转存资源..." placeholder="搜索未转存资源..."
@@ -41,8 +41,7 @@
</div> </div>
<!-- 批量操作 --> <!-- 批量操作 -->
<n-card> <div class="flex-0 flex items-center justify-between">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<n-checkbox <n-checkbox
@@ -78,10 +77,9 @@
</n-button> </n-button>
</div> </div>
</div> </div>
</n-card>
<!-- 资源列表 --> <!-- 资源列表 -->
<n-card> <div class="flex-1 h-1">
<div v-if="loading" class="flex items-center justify-center py-8"> <div v-if="loading" class="flex items-center justify-center py-8">
<n-spin size="large" /> <n-spin size="large" />
</div> </div>
@@ -91,13 +89,12 @@
<p class="text-gray-500">暂无未转存的夸克资源</p> <p class="text-gray-500">暂无未转存的夸克资源</p>
</div> </div>
<div v-else> <div v-else class="h-full">
<!-- 虚拟列表 --> <!-- 虚拟列表 -->
<n-virtual-list <n-virtual-list
:items="resources" :items="resources"
:item-size="120" :item-size="120"
style="max-height: 500px" class="h-full"
container-style="height: 500px;"
> >
<template #default="{ item }"> <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="p-4 border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800">
@@ -167,9 +164,11 @@
</div> </div>
</template> </template>
</n-virtual-list> </n-virtual-list>
</div>
<!-- 分页 --> </div>
<div class="mt-4 flex justify-center"> <div class="flex-0">
<div class="flex justify-center">
<n-pagination <n-pagination
v-model:page="currentPage" v-model:page="currentPage"
v-model:page-size="pageSize" v-model:page-size="pageSize"
@@ -182,7 +181,6 @@
/> />
</div> </div>
</div> </div>
</n-card>
<!-- 网盘账号选择模态框 --> <!-- 网盘账号选择模态框 -->
<n-modal v-model:show="showAccountSelectionModal" preset="card" title="选择网盘账号" style="width: 600px"> <n-modal v-model:show="showAccountSelectionModal" preset="card" title="选择网盘账号" style="width: 600px">

View 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>

View File

@@ -1,5 +1,5 @@
<template> <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 --> <!-- 设置通用title -->
<Head> <Head>
<title>管理后台 - 老九网盘资源数据库</title> <title>管理后台 - 老九网盘资源数据库</title>
@@ -129,9 +129,9 @@
</header> </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"> <nav class="mt-8">
<div class="px-4 space-y-6"> <div class="px-4 space-y-6">
<!-- 仪表盘 --> <!-- 仪表盘 -->
@@ -273,7 +273,7 @@
</aside> </aside>
<!-- 主内容区域 --> <!-- 主内容区域 -->
<main class="flex-1 p-8"> <main class="flex-1 p-4 h-full overflow-y-auto">
<ClientOnly> <ClientOnly>
<n-message-provider> <n-message-provider>
<n-notification-provider> <n-notification-provider>
@@ -304,9 +304,7 @@ const systemConfigStore = useSystemConfigStore()
// 任务状态管理 // 任务状态管理
const taskStore = useTaskStore() const taskStore = useTaskStore()
// 初始化系统配置管理员页面使用管理员API systemConfigStore.initConfig(false, true).catch(console.error)
// 在setup阶段初始化确保数据可用
await systemConfigStore.initConfig(false, true)
// 版本信息 // 版本信息
const versionInfo = ref({ const versionInfo = ref({
@@ -331,7 +329,15 @@ onMounted(() => {
// 启动任务状态自动更新 // 启动任务状态自动更新
taskStore.startAutoUpdate() 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 systemConfig = computed(() => {
const config = systemConfigStore.config || {} const config = systemConfigStore.config || {}
console.log('顶部导航系统配置:', config)
console.log('自动处理状态:', config.auto_process_ready_resources)
console.log('自动转存状态:', config.auto_transfer_enabled)
return config return config
}) })
@@ -605,4 +608,7 @@ const navigateToTasks = () => {
font-family: 'Font Awesome 6 Free'; font-family: 'Font Awesome 6 Free';
font-weight: 900; font-weight: 900;
} }
.main-content {
height: calc(100vh - 85px);
}
</style> </style>

View File

@@ -1,12 +1,15 @@
<template> <template>
<div class="max-w-7xl mx-auto space-y-6"> <AdminPageLayout :is-sub-page="true">
<!-- 页面标题 --> <!-- 页面头部 - 标题 -->
<div class="flex items-center justify-between"> <template #page-header>
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">账号扩容管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">账号扩容管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理账号扩容任务和状态</p> <p class="text-gray-600 dark:text-gray-400">管理账号扩容任务和状态</p>
</div> </div>
</template>
<!-- 提示信息 --> <!-- 通知提示区域 - 扩容说明Alert -->
<template #notice-section>
<n-alert type="info" :show-icon="false"> <n-alert type="info" :show-icon="false">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<i class="fas fa-info-circle text-blue-500"></i> <i class="fas fa-info-circle text-blue-500"></i>
@@ -19,7 +22,104 @@
</span> </span>
</div> </div>
</n-alert> </n-alert>
</template>
<!-- 内容区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>
</div>
</template>
</n-virtual-list>
</div>
</div>
</template>
</AdminPageLayout>
<!-- 抽屉和模态框 - 移动到AdminPageLayout外部 -->
<n-drawer v-model:show="drawActive" :width="502" closable placement="right"> <n-drawer v-model:show="drawActive" :width="502" closable placement="right">
<n-drawer-content title="扩容说明"> <n-drawer-content title="扩容说明">
<div class="space-y-6 p-4"> <div class="space-y-6 p-4">
@@ -115,110 +215,6 @@
</div> </div>
</n-card> </n-card>
</n-modal> </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> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -233,7 +229,6 @@ import { useNotification, useDialog } from 'naive-ui'
// 响应式数据 // 响应式数据
const expansionAccounts = ref([]) const expansionAccounts = ref([])
const expansionTasks = ref([])
const loading = ref(true) const loading = ref(true)
const expandingAccountId = ref(null) const expandingAccountId = ref(null)
const drawActive = ref(false) // 侧边栏激活 const drawActive = ref(false) // 侧边栏激活
@@ -248,62 +243,6 @@ const pendingAccount = ref<any>(null) // 待处理的账号
const taskApi = useTaskApi() const taskApi = useTaskApi()
const notification = useNotification() 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 () => { 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) => { const handleExpansion = async (account) => {
@@ -366,11 +296,12 @@ const confirmDataSourceSelection = async () => {
duration: 3000 duration: 3000
}) })
navigateTo('/admin/tasks')
// 刷新数据 // 刷新数据
await Promise.all([ // await Promise.all([
fetchExpansionAccounts(), // fetchExpansionAccounts(),
fetchExpansionTasks() // fetchExpansionTasks()
]) // ])
} catch (error) { } catch (error) {
console.error('创建扩容任务失败:', error) console.error('创建扩容任务失败:', error)
notification.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) => { const getPlatformIcon = (platformName) => {
@@ -455,10 +341,7 @@ const formatDate = (dateString) => {
// 页面加载 // 页面加载
onMounted(async () => { onMounted(async () => {
await Promise.all([ await fetchExpansionAccounts()
fetchExpansionAccounts(),
fetchExpansionTasks()
])
}) })
</script> </script>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">账号管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">账号管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理平台账号信息</p> <p class="text-gray-600 dark:text-gray-400">管理平台账号信息</p>
@@ -26,10 +26,11 @@
刷新 刷新
</n-button> </n-button>
</div> </div>
</div> </template>
<!-- 搜索和筛选 --> <!-- 过滤栏 - 搜索和筛选 -->
<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="flex flex-col md:flex-row gap-4"> <div class="flex flex-col md:flex-row gap-4">
<n-input v-model:value="searchQuery" placeholder="搜索账号..." clearable class="flex-1"> <n-input v-model:value="searchQuery" placeholder="搜索账号..." clearable class="flex-1">
<template #prefix> <template #prefix>
@@ -47,20 +48,21 @@
搜索 搜索
</n-button> </n-button>
</div> </div>
</n-card> </div>
</template>
<!-- 账号列表 --> <!-- 内容区header - 账号列表头部 -->
<n-card> <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-lg font-semibold">账号列表</span> <span class="text-lg font-semibold text-gray-900 dark:text-white">账号列表</span>
<div class="text-sm text-gray-500"> <div class="text-sm text-gray-500 dark:text-gray-400">
{{ filteredCksList.length }} 个账号 {{ filteredCksList.length }} 个账号
</div> </div>
</div> </div>
</template> </template>
<!-- 加载状态 --> <!-- 内容区content - 账号列表表格 -->
<template #content>
<div v-if="loading" class="flex items-center justify-center py-12"> <div v-if="loading" class="flex items-center justify-center py-12">
<n-spin size="large"> <n-spin size="large">
<template #description> <template #description>
@@ -69,7 +71,6 @@
</n-spin> </n-spin>
</div> </div>
<!-- 空状态 -->
<div v-else-if="filteredCksList.length === 0" class="flex flex-col items-center justify-center py-12"> <div v-else-if="filteredCksList.length === 0" class="flex flex-col items-center justify-center py-12">
<n-empty description="暂无账号"> <n-empty description="暂无账号">
<template #icon> <template #icon>
@@ -86,9 +87,9 @@
</n-empty> </n-empty>
</div> </div>
<!-- 账号列表 --> <!-- 账号列表和分页 -->
<div v-else> <div v-else class="flex flex-col flex-1 h-full overflow-hidden">
<n-virtual-list :items="filteredCksList" :item-size="100" style="max-height: 500px"> <n-virtual-list :items="filteredCksList" :item-size="100" class="h-full">
<template #default="{ item }"> <template #default="{ item }">
<div <div
class="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"> 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"> <n-tag :type="item.is_valid ? 'success' : 'error'" size="small">
{{ item.is_valid ? '有效' : '无效' }} {{ item.is_valid ? '有效' : '无效' }}
</n-tag> </n-tag>
<span class="text-xs text-gray-500"> <span class="text-xs text-gray-500 dark:text-gray-400">
总空间: {{ formatFileSize(item.space) }} 总空间: {{ formatFileSize(item.space) }}
</span> </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))) }} 已使用: {{ formatFileSize(Math.max(0, item.used_space || (item.space - item.left_space))) }}
</span> </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)) }} 剩余: {{ formatFileSize(Math.max(0, item.left_space)) }}
</span> </span>
</div> </div>
@@ -171,15 +172,19 @@
</template> </template>
</n-virtual-list> </n-virtual-list>
</div> </div>
</n-card> </template>
<!-- 分页 --> <!-- 内容区footer - 分页组件 -->
<template #content-footer>
<div class="p-4">
<div class="flex justify-center"> <div class="flex justify-center">
<n-pagination v-model:page="currentPage" v-model:page-size="itemsPerPage" :item-count="filteredCksList.length" <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" :page-sizes="[10, 20, 50, 100]" show-size-picker @update:page="goToPage"
@update:page-size="(size) => { itemsPerPage = size; currentPage = 1; }" /> @update:page-size="(size) => { itemsPerPage = size; currentPage = 1; }" />
</div> </div>
</div> </div>
</template>
</AdminPageLayout>
<!-- 创建/编辑账号模态框 --> <!-- 创建/编辑账号模态框 -->
<n-modal :show="showCreateModal || showEditModal" preset="card" title="账号管理" style="width: 500px" <n-modal :show="showCreateModal || showEditModal" preset="card" title="账号管理" style="width: 500px"

View File

@@ -1,15 +1,16 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">机器人管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">机器人管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理各平台的机器人配置和自动回复功能</p> <p class="text-gray-600 dark:text-gray-400">管理各平台的机器人配置和自动回复功能</p>
</div> </div>
</div> </template>
<!-- 配置表单 --> <!-- 内容区 - 配置表单 -->
<n-card> <template #content>
<div class="config-content h-full">
<!-- 顶部Tabs --> <!-- 顶部Tabs -->
<n-tabs <n-tabs
v-model:value="activeTab" v-model:value="activeTab"
@@ -18,6 +19,7 @@
class="mb-6" class="mb-6"
> >
<n-tab-pane name="qq" tab="QQ机器人"> <n-tab-pane name="qq" tab="QQ机器人">
<div class="tab-content-container">
<div class="space-y-8"> <div class="space-y-8">
<!-- 步骤1Astrobot 安装指南 --> <!-- 步骤1Astrobot 安装指南 -->
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-6"> <div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-6">
@@ -87,10 +89,8 @@
自动格式化搜索结果<br> 自动格式化搜索结果<br>
支持超时时间配置<br><br> 支持超时时间配置<br><br>
<strong>安装步骤</strong><br> <strong>安装步骤</strong><br>
1. 下载插件文件<br> 1. Astrbot 插件市场 搜索 urldb 安装<br>
2. 将插件放入 Astrobot plugins 目录<br> 2. 根据下面的配置信息配置插件
3. 重启 Astrobot<br>
4. 在配置文件中添加插件配置
</p> </p>
</div> </div>
</div> </div>
@@ -158,37 +158,46 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="wechat" tab="微信公众号"> <n-tab-pane name="wechat" tab="微信公众号">
<div class="tab-content-container">
<div class="text-center py-12"> <div class="text-center py-12">
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i> <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> <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> <p class="text-gray-500 dark:text-gray-400">微信公众号机器人功能正在开发中敬请期待</p>
</div> </div>
</div>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="telegram" tab="Telegram机器人"> <n-tab-pane name="telegram" tab="Telegram机器人">
<div class="tab-content-container">
<div class="text-center py-12"> <div class="text-center py-12">
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i> <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> <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> <p class="text-gray-500 dark:text-gray-400">Telegram机器人功能正在开发中敬请期待</p>
</div> </div>
</div>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="wechat_open" tab="微信开放平台"> <n-tab-pane name="wechat_open" tab="微信开放平台">
<div class="tab-content-container">
<div class="text-center py-12"> <div class="text-center py-12">
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i> <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> <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> <p class="text-gray-500 dark:text-gray-400">微信开放平台机器人功能正在开发中敬请期待</p>
</div> </div>
</div>
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs>
</n-card>
</div> </div>
</template>
</AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import AdminPageLayout from '~/components/AdminPageLayout.vue'
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection' import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
// 设置页面布局 // 设置页面布局
@@ -278,5 +287,21 @@ onMounted(() => {
</script> </script>
<style scoped> <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> </style>

View File

@@ -1,32 +1,22 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">分类管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">分类管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理系统中的资源分类</p> <p class="text-gray-600 dark:text-gray-400">管理系统中的资源分类</p>
</div> </div>
<div class="flex space-x-3"> <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> </div>
</template>
<!-- 提示信息 --> <!-- 提示信息区域 -->
<template #notice-section>
<n-alert title="分类用于对资源进行分类管理,可以关联多个标签" type="info" /> <n-alert title="分类用于对资源进行分类管理,可以关联多个标签" type="info" />
</template>
<!-- 搜索和操作 --> <!-- 过滤栏 - 搜索和操作 -->
<n-card> <template #filter-bar>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex gap-2"> <div class="flex gap-2">
<n-button @click="showAddModal = true" type="success"> <n-button @click="showAddModal = true" type="success">
@@ -58,21 +48,24 @@
</n-button> </n-button>
</div> </div>
</div> </div>
</n-card> </template>
<!-- 分类列表 --> <!-- 内容区header - 分类列表标题 -->
<n-card> <!-- <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-lg font-semibold">分类列表</span> <span class="text-lg font-semibold">分类列表</span>
<span class="text-sm text-gray-500"> {{ total }} 个分类</span> <span class="text-sm text-gray-500"> {{ total }} 个分类</span>
</div> </div>
</template> </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" /> <n-spin size="large" />
</div> </div>
<!-- 空状态 -->
<div v-else-if="categories.length === 0" class="text-center py-8"> <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"> <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" /> <circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
@@ -88,19 +81,37 @@
</n-button> </n-button>
</div> </div>
<div v-else> <!-- 数据表格 -->
<div v-else class="flex flex-col h-full min-h-[600px]">
<!-- 数据表格容器自适应填充剩余高度 -->
<div class="flex-1 overflow-hidden">
<n-data-table <n-data-table
:columns="columns" :columns="columns"
:data="categories" :data="categories"
:pagination="pagination" :pagination="false"
:bordered="false" :bordered="false"
:single-line="false" :single-line="false"
:loading="loading" :loading="loading"
@update:page="handlePageChange" :scroll-x="800"
class="h-full"
/> />
</div> </div>
</n-card>
<!-- 分页组件外部显示 -->
<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-modal v-model:show="showAddModal" preset="card" :title="editingCategory ? '编辑分类' : '添加分类'" style="width: 500px">
<n-form <n-form
@@ -147,7 +158,6 @@
</div> </div>
</template> </template>
</n-modal> </n-modal>
</div>
</template> </template>
<script setup lang="ts"> <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 () => { const fetchData = async () => {

View File

@@ -1,11 +1,15 @@
<template> <template>
<div class="p-6"> <AdminPageLayout>
<div class="mb-6"> <!-- 页面头部 - 标题 -->
<template #page-header>
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">数据推送</h1> <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> <p class="text-gray-600 dark:text-gray-400">数据推送管理</p>
</div> </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-center py-12">
<div class="text-gray-400 dark:text-gray-500 mb-4"> <div class="text-gray-400 dark:text-gray-500 mb-4">
<i class="fas fa-upload text-4xl"></i> <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> <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> <p class="text-gray-500 dark:text-gray-400">数据推送功能正在开发中敬请期待...</p>
</div> </div>
</div> </template>
</div> </AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import AdminPageLayout from '~/components/AdminPageLayout.vue'
// 数据推送管理页面 // 数据推送管理页面
definePageMeta({ definePageMeta({
layout: 'admin', layout: 'admin',

View File

@@ -1,35 +1,52 @@
<template> <template>
<div class="max-w-7xl mx-auto space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题 -->
<template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">数据转存管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">数据转存管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理资源转存任务和状态</p> <p class="text-gray-600 dark:text-gray-400">管理资源转存任务和状态</p>
</div> </div>
</template>
<!-- 主要内容 --> <!-- 内容 - 配置表单 -->
<n-card> <template #content>
<n-tabs v-model:value="activeTab" type="line" animated> <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="手动批量转存"> <n-tab-pane name="manual" tab="手动批量转存">
<div class="tab-content-container">
<AdminManualBatchTransfer /> <AdminManualBatchTransfer />
</div>
</n-tab-pane> </n-tab-pane>
<!-- 已转存列表 --> <!-- 已转存列表 -->
<n-tab-pane name="transferred" tab="已转存列表"> <n-tab-pane name="transferred" tab="已转存列表">
<div class="tab-content-container">
<AdminTransferredList ref="transferredListRef" /> <AdminTransferredList ref="transferredListRef" />
</div>
</n-tab-pane> </n-tab-pane>
<!-- 未转存列表 --> <!-- 未转存列表 -->
<n-tab-pane name="untransferred" tab="未转存列表"> <n-tab-pane name="untransferred" tab="未转存列表">
<div class="tab-content-container">
<AdminUntransferredList ref="untransferredListRef" /> <AdminUntransferredList ref="untransferredListRef" />
</div>
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs>
</n-card>
</div> </div>
</template>
</AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import AdminPageLayout from '~/components/AdminPageLayout.vue'
// 页面配置 // 页面配置
definePageMeta({ definePageMeta({
@@ -44,3 +61,23 @@ const activeTab = ref('manual')
const transferredListRef = ref(null) const transferredListRef = ref(null)
const untransferredListRef = 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>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和保存按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">开发配置</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">开发配置</h1>
<p class="text-gray-600 dark:text-gray-400">管理API和开发相关配置</p> <p class="text-gray-600 dark:text-gray-400">管理API和开发相关配置</p>
@@ -12,11 +12,11 @@
</template> </template>
保存配置 保存配置
</n-button> </n-button>
</div> </template>
<!-- 配置表单 --> <!-- 内容区 - 配置表单 -->
<n-card> <template #content>
<div class="space-y-6"> <div class="config-content h-full">
<!-- API Token --> <!-- API Token -->
<div> <div>
<n-form-item label="公开API访问令牌" path="api_token"> <n-form-item label="公开API访问令牌" path="api_token">
@@ -71,12 +71,13 @@
</n-button> </n-button>
</div> </div>
</div> </div>
</n-card> </template>
</div> </AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection' import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
import AdminPageLayout from '~/components/AdminPageLayout.vue'
// 设置页面布局 // 设置页面布局
definePageMeta({ definePageMeta({
@@ -332,4 +333,13 @@ onMounted(() => {
<style scoped> <style scoped>
/* 自定义样式 */ /* 自定义样式 */
.config-content {
padding: 1rem;
background-color: var(--color-white, #ffffff);
}
.dark .config-content {
background-color: var(--color-dark-bg, #1f2937);
}
</style> </style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout :is-sub-page="true">
<!-- 页面标题 --> <!-- 页面头部 - 标题和按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">失败资源列表</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">失败资源列表</h1>
<p class="text-gray-600 dark:text-gray-400">显示处理失败的资源包含错误信息</p> <p class="text-gray-600 dark:text-gray-400">显示处理失败的资源包含错误信息</p>
@@ -38,19 +38,18 @@
刷新 刷新
</n-button> </n-button>
</div> </div>
</div> </template>
<!-- 搜索和筛选 -->
<n-card>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- 过滤栏 - 搜索和筛选 -->
<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 <n-select
v-model:value="errorFilter" v-model:value="errorFilter"
placeholder="选择状态" placeholder="选择状态"
:options="statusOptions" :options="statusOptions"
clearable clearable
/> />
<n-button type="primary" @click="handleSearch" class="w-full md:w-auto md:min-w-[100px]"> <n-button type="primary" @click="handleSearch" class="w-full md:w-auto md:min-w-[100px]">
<template #icon> <template #icon>
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
@@ -58,11 +57,11 @@
搜索 搜索
</n-button> </n-button>
</div> </div>
</n-card> </div>
</template>
<!-- 失败资源列表 --> <!-- 内容区header - 失败资源列表头部 -->
<n-card> <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<span class="text-lg font-semibold">失败资源列表</span> <span class="text-lg font-semibold">失败资源列表</span>
@@ -76,11 +75,13 @@
</div> </div>
<div class="text-sm text-gray-500"> <div class="text-sm text-gray-500">
<span class="text-sm text-gray-500"> {{ totalCount }} 个资源已选择 {{ selectedResources.length }} </span> <span> {{ totalCount }} 个资源已选择 {{ selectedResources.length }} </span>
</div> </div>
</div> </div>
</template> </template>
<!-- 内容区content - 失败资源列表 -->
<template #content>
<!-- 加载状态 --> <!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center py-12"> <div v-if="loading" class="flex items-center justify-center py-12">
<n-spin size="large"> <n-spin size="large">
@@ -91,13 +92,12 @@
</div> </div>
<!-- 虚拟列表 --> <!-- 虚拟列表 -->
<div v-else-if="failedResources.length > 0">
<n-virtual-list <n-virtual-list
v-if="!loading"
:items="failedResources" :items="failedResources"
:item-size="80" :item-size="120"
:item-resizable="true" :item-resizable="true"
style="max-height: 400px" style="max-height: 600px"
container-style="height: 600px;"
> >
<template #default="{ item }"> <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="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
@@ -194,9 +194,10 @@
</div> </div>
</template> </template>
</n-virtual-list> </n-virtual-list>
</div>
<!-- 空状态 --> <!-- 空状态 -->
<div v-if="!loading && failedResources.length === 0" class="flex flex-col items-center justify-center py-12"> <div v-else class="flex flex-col items-center justify-center py-12">
<n-empty description="暂无失败资源"> <n-empty description="暂无失败资源">
<template #icon> <template #icon>
<i class="fas fa-check-circle text-4xl text-green-500"></i> <i class="fas fa-check-circle text-4xl text-green-500"></i>
@@ -206,9 +207,12 @@
</template> </template>
</n-empty> </n-empty>
</div> </div>
</template>
<!-- 分页 --> <!-- 内容区footer - 分页组件 -->
<div class="mt-6 flex justify-center"> <template #content-footer>
<div class="p-4">
<div class="flex justify-center">
<n-pagination <n-pagination
v-model:page="currentPage" v-model:page="currentPage"
v-model:page-size="pageSize" v-model:page-size="pageSize"
@@ -219,8 +223,9 @@
@update:page-size="(size) => { pageSize = size; currentPage = 1; fetchData() }" @update:page-size="(size) => { pageSize = size; currentPage = 1; fetchData() }"
/> />
</div> </div>
</n-card>
</div> </div>
</template>
</AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和保存按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">功能配置</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">功能配置</h1>
<p class="text-gray-600 dark:text-gray-400">管理系统功能开关和参数设置</p> <p class="text-gray-600 dark:text-gray-400">管理系统功能开关和参数设置</p>
@@ -12,10 +12,11 @@
</template> </template>
保存配置 保存配置
</n-button> </n-button>
</div> </template>
<!-- 配置表单 --> <!-- 内容区 - 配置表单 -->
<n-card> <template #content>
<div class="config-content h-full">
<!-- 顶部Tabs --> <!-- 顶部Tabs -->
<n-tabs <n-tabs
v-model:value="activeTab" v-model:value="activeTab"
@@ -24,7 +25,7 @@
class="mb-6" class="mb-6"
> >
<n-tab-pane name="resource" tab="资源处理"> <n-tab-pane name="resource" tab="资源处理">
<div class="tab-content-container">
<n-form <n-form
ref="formRef" ref="formRef"
:model="configForm" :model="configForm"
@@ -167,10 +168,11 @@
</div> </div>
</div> </div>
</n-form> </n-form>
</div>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="transfer" tab="转存配置"> <n-tab-pane name="transfer" tab="转存配置">
<div class="tab-content-container">
<n-form <n-form
ref="formRef" ref="formRef"
:model="configForm" :model="configForm"
@@ -244,10 +246,11 @@
</div> </div>
</div> </div>
</n-form> </n-form>
</div>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="drama" tab="热播剧"> <n-tab-pane name="drama" tab="热播剧">
<div class="tab-content-container">
<n-form <n-form
ref="formRef" ref="formRef"
:model="configForm" :model="configForm"
@@ -267,16 +270,19 @@
</div> </div>
</div> </div>
</n-form> </n-form>
</div>
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs>
</n-card>
</div> </div>
</template>
</AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useNotification } from 'naive-ui' import { useNotification } from 'naive-ui'
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection' import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
import AdminPageLayout from '~/components/AdminPageLayout.vue'
// 设置页面布局 // 设置页面布局
definePageMeta({ definePageMeta({
@@ -537,4 +543,20 @@ onMounted(() => {
<style scoped> <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> </style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">文件管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">文件管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理系统中的上传文件</p> <p class="text-gray-600 dark:text-gray-400">管理系统中的上传文件</p>
@@ -20,13 +20,15 @@
刷新 刷新
</n-button> </n-button>
</div> </div>
</div> </template>
<!-- 提示信息 --> <!-- 提示信息区域 -->
<template #notice-section>
<n-alert title="支持图片格式文件最大文件大小5MB" type="info" /> <n-alert title="支持图片格式文件最大文件大小5MB" type="info" />
</template>
<!-- 搜索和筛选 --> <!-- 过滤栏 - 搜索功能 -->
<n-card> <template #filter-bar>
<div class="flex gap-4"> <div class="flex gap-4">
<n-input <n-input
v-model:value="searchKeyword" v-model:value="searchKeyword"
@@ -47,21 +49,24 @@
搜索 搜索
</n-button> </n-button>
</div> </div>
</n-card> </template>
<!-- 文件列表 --> <!-- 内容区header - 文件列表标题 -->
<n-card> <!-- <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-lg font-semibold">文件列表</span> <span class="text-lg font-semibold">文件列表</span>
<span class="text-sm text-gray-500"> {{ total }} 个文件</span> <span class="text-sm text-gray-500"> {{ total }} 个文件</span>
</div> </div>
</template> </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" /> <n-spin size="large" />
</div> </div>
<!-- 空状态 -->
<div v-else-if="fileList.length === 0" class="text-center py-8"> <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> <i class="fas fa-file-upload text-4xl text-gray-400 mb-4"></i>
<p class="text-gray-500">暂无文件数据</p> <p class="text-gray-500">暂无文件数据</p>
@@ -73,9 +78,11 @@
</n-button> </n-button>
</div> </div>
<div v-else> <!-- 文件网格和分页容器 -->
<!-- 图片预览区域 --> <div v-else class="flex flex-col h-full">
<div class="image-preview-container"> <!-- 文件网格区域 - 自适应高度 -->
<div class="flex-1 overflow-auto">
<div class="file-list-container">
<n-image-group> <n-image-group>
<div class="image-grid"> <div class="image-grid">
<div <div
@@ -148,24 +155,29 @@
</div> </div>
</div> </div>
</n-image-group> </n-image-group>
</div>
</div>
</div>
</template>
<!-- 分页 --> <!-- 内容区footer - 分页组件 -->
<div class="pagination-wrapper"> <template #content-footer>
<div class="p-4">
<div class="flex justify-center">
<n-pagination <n-pagination
v-model:page="pagination.page" v-model:page="pagination.page"
v-model:page-size="pagination.pageSize" v-model:page-size="pagination.pageSize"
:page-count="Math.ceil(pagination.total / pagination.pageSize)" :item-count="pagination.total"
:page-sizes="pagination.pageSizes" :page-sizes="[100, 200, 500, 1000]"
show-size-picker show-size-picker
@update:page="handlePageChange" @update:page="handlePageChange"
@update:page-size="handlePageSizeChange" @update:page-size="handlePageSizeChange"
/> />
</div> </div>
</div> </div>
</div> </template>
</n-card> </AdminPageLayout>
<!-- 上传模态框 -->
<!-- 上传模态框 -->
<n-modal v-model:show="showUploadModal" preset="card" title="上传文件" style="width: 800px" @update:show="handleModalClose"> <n-modal v-model:show="showUploadModal" preset="card" title="上传文件" style="width: 800px" @update:show="handleModalClose">
<FileUpload ref="fileUploadRef" :key="uploadModalKey" /> <FileUpload ref="fileUploadRef" :key="uploadModalKey" />
<template #footer> <template #footer>
@@ -191,7 +203,6 @@
</n-space> </n-space>
</template> </template>
</n-modal> </n-modal>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -479,12 +490,22 @@ onMounted(() => {
<style scoped> <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 { .image-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem; gap: 1rem;
max-height: 400px; height: 100%;
overflow-y: auto; overflow-y: auto;
} }
@@ -568,27 +589,26 @@ onMounted(() => {
} }
.pagination-wrapper { .pagination-wrapper {
display: flex; /* 由于分页已移到外部,这里的样式不再需要 */
justify-content: center; /* 分页现在直接使用 AdminPageLayout 的 content-footer */
margin-top: 1rem;
} }
/* 滚动条样式 */ /* 滚动条样式 - 更新为新的容器类名 */
.image-preview-container::-webkit-scrollbar { .image-grid::-webkit-scrollbar {
width: 6px; width: 6px;
} }
.image-preview-container::-webkit-scrollbar-track { .image-grid::-webkit-scrollbar-track {
background: #f1f1f1; background: #f1f1f1;
border-radius: 3px; border-radius: 3px;
} }
.image-preview-container::-webkit-scrollbar-thumb { .image-grid::-webkit-scrollbar-thumb {
background: #c1c1c1; background: #c1c1c1;
border-radius: 3px; border-radius: 3px;
} }
.image-preview-container::-webkit-scrollbar-thumb:hover { .image-grid::-webkit-scrollbar-thumb:hover {
background: #a8a8a8; background: #a8a8a8;
} }
</style> </style>

View File

@@ -1,13 +1,13 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">待处理资源</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">待处理资源</h1>
<p class="text-gray-600 dark:text-gray-400">管理待处理的资源</p> <p class="text-gray-600 dark:text-gray-400">管理待处理的资源</p>
</div> </div>
<div class="flex space-x-3"> <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> <template #icon>
<i class="fas fa-exclamation-triangle"></i> <i class="fas fa-exclamation-triangle"></i>
</template> </template>
@@ -19,76 +19,22 @@
</template> </template>
刷新 刷新
</n-button> </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" @click="toggleAutoProcess"
:disabled="updatingConfig" :disabled="updatingConfig"
:type="systemConfig?.auto_process_ready_resources ? 'error' : 'success'" :type="systemConfig?.auto_process_ready_resources ? 'error' : 'success'"
size="small"
> >
<template #icon> <template #icon>
<i v-if="updatingConfig" class="fas fa-spinner fa-spin"></i> <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> <i v-else :class="systemConfig?.auto_process_ready_resources ? 'fas fa-pause' : 'fas fa-play'"></i>
</template> </template>
{{ systemConfig?.auto_process_ready_resources ? '关闭' : '开启' }} {{ systemConfig?.auto_process_ready_resources ? '关闭自动处理' : '开启自动处理' }}
</n-button> </n-button>
</div> </div>
</div> </template>
</n-card>
<!-- 资源列表 --> <!-- 内容区header - 资源列表头部 -->
<n-card> <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-lg font-semibold">待处理资源列表</span> <span class="text-lg font-semibold">待处理资源列表</span>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
@@ -107,10 +53,14 @@
</div> </div>
</template> </template>
<!-- 内容区content - 资源列表 -->
<template #content>
<!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center py-8"> <div v-if="loading" class="flex items-center justify-center py-8">
<n-spin size="large" /> <n-spin size="large" />
</div> </div>
<!-- 空状态 -->
<div v-else-if="readyResources.length === 0" class="text-center py-8"> <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> <i class="fas fa-inbox text-4xl text-gray-400 mb-4"></i>
<p class="text-gray-500">暂无待处理资源</p> <p class="text-gray-500">暂无待处理资源</p>
@@ -125,6 +75,7 @@
</div> </div>
</div> </div>
<!-- 数据表格 -->
<div v-else> <div v-else>
<n-data-table <n-data-table
:columns="columns" :columns="columns"
@@ -136,8 +87,8 @@
@update:page="handlePageChange" @update:page="handlePageChange"
/> />
</div> </div>
</n-card> </template>
</div> </AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">资源管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">资源管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理系统中的所有资源</p> <p class="text-gray-600 dark:text-gray-400">管理系统中的所有资源</p>
@@ -26,10 +26,11 @@
刷新 刷新
</n-button> </n-button>
</div> </div>
</div> </template>
<!-- 搜索和筛选 --> <!-- 过滤栏 - 搜索和筛选 -->
<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"> <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<n-input <n-input
v-model:value="searchQuery" v-model:value="searchQuery"
@@ -63,11 +64,11 @@
搜索 搜索
</n-button> </n-button>
</div> </div>
</n-card> </div>
</template>
<!-- 资源列表 --> <!-- 内容区header - 资源列表头部 -->
<n-card> <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<span class="text-lg font-semibold">资源列表</span> <span class="text-lg font-semibold">资源列表</span>
@@ -77,29 +78,32 @@
@update:checked="toggleSelectAll" @update:checked="toggleSelectAll"
:indeterminate="isIndeterminate" :indeterminate="isIndeterminate"
/> />
<span class="text-sm text-gray-500">全选</span> <span class="text-sm text-gray-500 dark:text-gray-400">全选</span>
</div> </div>
</div> </div>
<span class="text-sm text-gray-500"> {{ total }} 个资源已选择 {{ selectedResources.length }} </span> <span class="text-sm text-gray-500 dark:text-gray-400"> {{ total }} 个资源已选择 {{ selectedResources.length }} </span>
</div> </div>
</template> </template>
<div v-if="loading" class="flex items-center justify-center py-8"> <!-- 内容区content - 资源列表 -->
<template #content>
<!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center py-12">
<n-spin size="large" /> <n-spin size="large" />
</div> </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> <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>
<div v-else> <!-- 虚拟列表容器 -->
<!-- 虚拟列表 --> <div v-else class="flex-1 h-full overflow-hidden">
<n-virtual-list <n-virtual-list
:items="resources" :items="resources"
:item-size="100" :item-size="100"
style="max-height: 400px" class="h-full"
container-style="height: 600px;"
> >
<template #default="{ item: resource }"> <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="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">
@@ -111,7 +115,7 @@
:checked="selectedResources.includes(resource.id)" :checked="selectedResources.includes(resource.id)"
@update:checked="(checked) => toggleResourceSelection(resource.id, checked)" @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"> <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) }} {{ getPlatformName(resource.pan_id) }}
@@ -129,7 +133,7 @@
{{ resource.description }} {{ resource.description }}
</p> </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> <span>
<i class="fas fa-link mr-1"></i> <i class="fas fa-link mr-1"></i>
{{ resource.url }} {{ resource.url }}
@@ -179,9 +183,13 @@
</div> </div>
</template> </template>
</n-virtual-list> </n-virtual-list>
</div>
</template>
<!-- 分页 --> <!-- 内容区footer - 分页组件 -->
<div class="mt-6"> <template #content-footer>
<div class="p-4">
<div class="flex justify-center">
<n-pagination <n-pagination
v-model:page="currentPage" v-model:page="currentPage"
v-model:page-size="pageSize" v-model:page-size="pageSize"
@@ -193,8 +201,10 @@
/> />
</div> </div>
</div> </div>
</n-card> </template>
</AdminPageLayout>
<!-- 模态框 - 在AdminPageLayout外部 -->
<!-- 批量操作模态框 --> <!-- 批量操作模态框 -->
<n-modal v-model:show="showBatchModal" preset="card" title="批量操作" style="width: 600px"> <n-modal v-model:show="showBatchModal" preset="card" title="批量操作" style="width: 600px">
<div class="space-y-4"> <div class="space-y-4">
@@ -290,7 +300,8 @@
</div> </div>
</template> </template>
</n-modal> </n-modal>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,13 +1,20 @@
<template> <template>
<div class="p-6"> <AdminPageLayout>
<div class="mb-6"> <!-- 页面头部 - 标题 -->
<template #page-header>
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">SEO管理</h1> <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> <p class="text-gray-600 dark:text-gray-400">搜索引擎优化管理</p>
</div> </div>
</template>
<!-- 内容区 -->
<template #content>
<div class="config-content h-full">
<!-- Tab导航 --> <!-- Tab导航 -->
<n-tabs v-model:value="activeTab" type="line" animated> <n-tabs v-model:value="activeTab" type="line" animated>
<n-tab-pane name="site-submit" tab="站点提交"> <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="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="mb-6"> <div class="mb-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">站点提交待开发</h3> <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">站点提交待开发</h3>
@@ -181,9 +188,11 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="link-building" tab="外链建设"> <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="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="mb-6"> <div class="mb-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">外链建设待开发</h3> <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">外链建设待开发</h3>
@@ -263,12 +272,17 @@
/> />
</div> </div>
</div> </div>
</div>
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs>
</div> </div>
</template>
</AdminPageLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import AdminPageLayout from '~/components/AdminPageLayout.vue'
// SEO管理页面 // SEO管理页面
definePageMeta({ definePageMeta({
layout: 'admin' layout: 'admin'
@@ -494,3 +508,22 @@ onMounted(() => {
loadLinkList() 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>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和保存按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">站点配置</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">站点配置</h1>
<p class="text-gray-600 dark:text-gray-400">管理网站基本信息和设置</p> <p class="text-gray-600 dark:text-gray-400">管理网站基本信息和设置</p>
@@ -12,19 +12,19 @@
</template> </template>
保存配置 保存配置
</n-button> </n-button>
</div> </template>
<!-- 配置表单 --> <!-- 内容区 - 配置表单 -->
<n-card> <template #content>
<div class="config-content h-full">
<!-- 顶部Tabs --> <!-- 顶部Tabs -->
<n-tabs <n-tabs
v-model:value="activeTab" v-model:value="activeTab"
type="line" type="line"
animated animated
class="mb-6"
> >
<n-tab-pane name="basic" tab="基本信息"> <n-tab-pane name="basic" tab="基本信息">
<div class="tab-content-container">
<n-form <n-form
ref="formRef" ref="formRef"
:model="configForm" :model="configForm"
@@ -117,12 +117,12 @@
</div> </div>
</div> </div>
</n-form> </n-form>
</div>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="security" tab="安全设置"> <n-tab-pane name="security" tab="安全设置">
<div class="tab-content-container">
<n-form <n-form
ref="formRef" ref="formRef"
:model="configForm" :model="configForm"
@@ -174,10 +174,12 @@
</div> </div>
</div> </div>
</n-form> </n-form>
</div>
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs>
</n-card> </div>
</template>
</AdminPageLayout>
<!-- Logo选择模态框 --> <!-- Logo选择模态框 -->
<n-modal v-model:show="showLogoSelector" preset="card" title="选择Logo图片" style="width: 90vw; max-width: 1200px; max-height: 80vh;"> <n-modal v-model:show="showLogoSelector" preset="card" title="选择Logo图片" style="width: 90vw; max-width: 1200px; max-height: 80vh;">
<div class="space-y-4"> <div class="space-y-4">
@@ -271,7 +273,6 @@
</n-space> </n-space>
</template> </template>
</n-modal> </n-modal>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -584,7 +585,31 @@ onMounted(() => {
</script> </script>
<style scoped> <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 { .file-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
@@ -611,8 +636,6 @@ onMounted(() => {
border-radius: 4px; border-radius: 4px;
} }
.pagination-wrapper { .pagination-wrapper {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@@ -1,32 +1,21 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <template #page-header>
<div class="flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">标签管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">标签管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理系统中的资源标签</p> <p class="text-gray-600 dark:text-gray-400">管理系统中的资源标签</p>
</div> </div>
<div class="flex space-x-3"> <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> </div>
</template>
<!-- 提示信息 --> <!-- 提示信息区域 -->
<template #notice-section>
<n-alert title="提交的数据中,如果包含标签,数据添加成功,会自动添加标签" type="info" /> <n-alert title="提交的数据中,如果包含标签,数据添加成功,会自动添加标签" type="info" />
</template>
<!-- 搜索和操作 --> <!-- 过滤栏 - 搜索和操作 -->
<n-card> <template #filter-bar>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex gap-2"> <div class="flex gap-2">
<n-button @click="showAddModal = true" type="success"> <n-button @click="showAddModal = true" type="success">
@@ -58,21 +47,24 @@
</n-button> </n-button>
</div> </div>
</div> </div>
</n-card> </template>
<!-- 标签列表 --> <!-- 内容区header - 标签列表标题 -->
<n-card> <!-- <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-lg font-semibold">标签列表</span> <span class="text-lg font-semibold">标签列表</span>
<span class="text-sm text-gray-500"> {{ total }} 个标签</span> <span class="text-sm text-gray-500"> {{ total }} 个标签</span>
</div> </div>
</template> </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" /> <n-spin size="large" />
</div> </div>
<!-- 空状态 -->
<div v-else-if="tags.length === 0" class="text-center py-8"> <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"> <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" /> <circle cx="24" cy="24" r="20" stroke-width="3" stroke-dasharray="6 6" />
@@ -87,20 +79,39 @@
添加标签 添加标签
</n-button> </n-button>
</div> </div>
<!-- 数据表格 - 自适应高度 -->
<div v-else> <div v-else class="flex flex-col h-full overflow-auto">
<n-data-table <n-data-table
:columns="columns" :columns="columns"
:data="tags" :data="tags"
:pagination="pagination" :pagination="false"
:bordered="false" :bordered="false"
:single-line="false" :single-line="false"
:loading="loading" :loading="loading"
@update:page="handlePageChange" :scroll-x="800"
class="h-full"
/> />
</div> </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-modal v-model:show="showAddModal" preset="card" :title="editingTag ? '编辑标签' : '添加标签'" style="width: 500px">
<n-form <n-form
@@ -146,7 +157,6 @@
</div> </div>
</template> </template>
</n-modal> </n-modal>
</div>
</template> </template>
<script setup lang="ts"> <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 () => { const fetchData = async () => {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-6"> <AdminPageLayout>
<!-- 页面标题 --> <!-- 页面头部 - 标题和操作按钮 -->
<div class="flex items-center justify-between"> <template #page-header>
<div> <div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">用户管理</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-white">用户管理</h1>
<p class="text-gray-600 dark:text-gray-400">管理系统中的用户账户</p> <p class="text-gray-600 dark:text-gray-400">管理系统中的用户账户</p>
@@ -20,20 +20,24 @@
刷新 刷新
</n-button> </n-button>
</div> </div>
</div> </template>
<!-- 提示信息 --> <!-- 通知区域 -->
<template #notice-section>
<n-alert title="用户管理功能,可以创建、编辑、删除用户,以及修改用户密码" type="info" /> <n-alert title="用户管理功能,可以创建、编辑、删除用户,以及修改用户密码" type="info" />
</template>
<!-- 用户列表 --> <!-- 内容区header -->
<n-card> <template #content-header>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-lg font-semibold">用户列表</span> <span class="text-lg font-semibold">用户列表</span>
<span class="text-sm text-gray-500"> {{ total }} 个用户</span> <span class="text-sm text-gray-500"> {{ total }} 个用户</span>
</div> </div>
</template> </template>
<!-- 内容区 - 用户列表 -->
<template #content>
<div v-if="loading" class="flex items-center justify-center py-8"> <div v-if="loading" class="flex items-center justify-center py-8">
<n-spin size="large" /> <n-spin size="large" />
</div> </div>
@@ -53,18 +57,36 @@
</n-button> </n-button>
</div> </div>
<div v-else> <div v-else class="h-full">
<n-data-table <n-data-table
:columns="columns" :columns="columns"
:data="users" :data="users"
:pagination="pagination"
:bordered="false" :bordered="false"
:single-line="false" :single-line="false"
:loading="loading" :loading="loading"
@update:page="handlePageChange" @update:page="handlePageChange"
/> />
</div> </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"> <n-modal v-model:show="showModal" preset="card" :title="showEditModal ? '编辑用户' : '创建用户'" style="width: 500px">
@@ -176,10 +198,11 @@
</div> </div>
</template> </template>
</n-modal> </n-modal>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import AdminPageLayout from '~/components/AdminPageLayout.vue'
// 设置页面布局 // 设置页面布局
definePageMeta({ definePageMeta({
layout: 'admin' layout: 'admin'
@@ -594,4 +617,13 @@ const showModal = computed({
<style scoped> <style scoped>
/* 自定义样式 */ /* 自定义样式 */
.config-content {
padding: 1rem;
background-color: var(--color-white, #ffffff);
}
.dark .config-content {
background-color: var(--color-dark-bg, #1f2937);
}
</style> </style>

View File

@@ -65,7 +65,7 @@ export const useTaskStore = defineStore('task', () => {
// 获取任务统计信息 // 获取任务统计信息
const fetchTaskStats = async () => { const fetchTaskStats = async () => {
try { try {
const response = await taskApi.getTasks({status: 'running'}) as any const response = await taskApi.getTasks() as any
// console.log('原始任务API响应:', response) // console.log('原始任务API响应:', response)
// 处理API响应格式 // 处理API响应格式