update: 优化添加资源

This commit is contained in:
ctwj
2025-07-12 08:15:25 +08:00
parent b060200583
commit e787b64ce8
6 changed files with 391 additions and 12 deletions

14
web/components.d.ts vendored
View File

@@ -8,6 +8,20 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPagination: typeof import('naive-ui')['NPagination']
NTag: typeof import('naive-ui')['NTag']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@@ -0,0 +1,38 @@
<template>
<div class="space-y-4">
<div class="text-gray-700 dark:text-gray-300 text-sm">
<p>你可以通过API批量添加资源</p>
<pre class="bg-gray-100 dark:bg-gray-800 p-3 rounded text-xs overflow-x-auto mt-2">
POST /api/resources/batch
Content-Type: application/json
Body:
[
{ "title": "资源A", "url": "https://a.com", "file_type": "pan", ... },
{ "title": "资源B", "url": "https://b.com", ... }
]
</pre>
<p>参数说明<br/>
title: 标题<br/>
url: 资源链接<br/>
file_type: 类型pan/link/other<br/>
tags: 标签数组可选<br/>
description: 描述可选<br/>
... 其他字段参考文档
</p>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" @click="$emit('cancel')" class="btn-secondary">关闭</button>
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['cancel'])
</script>
<style scoped>
.btn-secondary {
@apply px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md transition-colors;
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">输入格式说明</label>
<div class="bg-gray-50 dark:bg-gray-800 p-3 rounded text-sm text-gray-600 dark:text-gray-300 mb-4">
<p class="mb-2"><strong>格式1</strong>标题和URL两行一组</p>
<pre class="bg-white dark:bg-gray-800 p-2 rounded border text-xs">
电影标题1
https://pan.baidu.com/s/123456
电影标题2
https://pan.baidu.com/s/789012</pre>
<p class="mt-2 mb-2"><strong>格式2</strong>只有URL系统自动判断</p>
<pre class="bg-white dark:bg-gray-800 p-2 rounded border text-xs">
https://pan.baidu.com/s/123456
https://pan.baidu.com/s/789012
https://pan.baidu.com/s/345678</pre>
</div>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">资源内容</label>
<textarea
v-model="batchInput"
rows="15"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-900 dark:text-gray-100"
placeholder="请输入资源内容,支持两种格式..."
></textarea>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" @click="$emit('cancel')" class="btn-secondary">取消</button>
<button type="button" @click="handleSubmit" class="btn-primary" :disabled="loading">
{{ loading ? '保存中...' : '批量添加' }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useReadyResourceApi } from '~/composables/useApi'
const emit = defineEmits(['success', 'error', 'cancel'])
const loading = ref(false)
const batchInput = ref('')
const readyResourceApi = useReadyResourceApi()
// 批量添加提交
const handleSubmit = async () => {
loading.value = true
try {
if (!batchInput.value.trim()) throw new Error('请输入资源内容')
const res: any = await readyResourceApi.createReadyResourcesFromText(batchInput.value)
emit('success', `成功添加 ${res.count || 0} 个资源,资源已进入待处理列表,处理完成后会自动入库`)
batchInput.value = ''
} catch (e: any) {
emit('error', e.message || '批量添加失败')
} finally {
loading.value = false
}
}
</script>
<style scoped>
.btn-primary {
@apply px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors disabled:opacity-50;
}
.btn-secondary {
@apply px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md transition-colors;
}
</style>

View File

@@ -0,0 +1,123 @@
<template>
<div class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">标题</label>
<input v-model="form.title" class="input-field dark:bg-gray-900 dark:text-gray-100 dark:border-gray-700" placeholder="输入标题" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">描述</label>
<textarea v-model="form.description" rows="3" class="input-field dark:bg-gray-900 dark:text-gray-100 dark:border-gray-700" placeholder="输入资源描述"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">类型</label>
<select v-model="form.file_type" class="input-field dark:bg-gray-900 dark:text-gray-100 dark:border-gray-700">
<option value="">选择类型</option>
<option value="pan">网盘</option>
<option value="link">直链</option>
<option value="other">其他</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">标签</label>
<div class="flex flex-wrap gap-2 mb-2">
<span v-for="tag in form.tags" :key="tag" class="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs flex items-center">
{{ tag }}
<button type="button" class="ml-1 text-xs" @click="removeTag(tag)">×</button>
</span>
</div>
<input v-model="newTag" @keyup.enter.prevent="addTag" class="input-field dark:bg-gray-900 dark:text-gray-100 dark:border-gray-700" placeholder="输入标签后回车添加" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">链接可多行每行一个链接</label>
<textarea v-model="form.url" rows="3" class="input-field dark:bg-gray-900 dark:text-gray-100 dark:border-gray-700" placeholder="https://a.com&#10;https://b.com"></textarea>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" @click="$emit('cancel')" class="btn-secondary">取消</button>
<button type="button" @click="handleSubmit" class="btn-primary" :disabled="loading">
{{ loading ? '保存中...' : '添加' }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useResourceStore } from '~/stores/resource'
const emit = defineEmits(['success', 'error', 'cancel'])
const store = useResourceStore()
const loading = ref(false)
const newTag = ref('')
// 单个添加表单
const form = ref({
title: '',
description: '',
url: '', // 多行
category_id: '',
tags: [] as string[],
file_path: '',
file_type: '',
file_size: 0,
is_public: true,
})
const addTag = () => {
const tag = newTag.value.trim()
if (tag && !form.value.tags.includes(tag)) {
form.value.tags.push(tag)
newTag.value = ''
}
}
const removeTag = (tag: string) => {
const index = form.value.tags.indexOf(tag)
if (index > -1) {
form.value.tags.splice(index, 1)
}
}
// 单个添加提交
const handleSubmit = async () => {
loading.value = true
try {
// 多行链接
const urls = form.value.url.split(/\r?\n/).map(l => l.trim()).filter(Boolean)
if (!urls.length) throw new Error('请输入至少一个链接')
for (const url of urls) {
await store.createResource({
...form.value,
url,
tags: [...form.value.tags],
})
}
emit('success', '资源已进入待处理列表,处理完成后会自动入库')
// 清空表单
form.value.title = ''
form.value.description = ''
form.value.url = ''
form.value.tags = []
form.value.file_type = ''
} catch (e: any) {
emit('error', e.message || '添加失败')
} finally {
loading.value = false
}
}
</script>
<style scoped>
.input-field {
@apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500;
}
.btn-primary {
@apply px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors disabled:opacity-50;
}
.btn-secondary {
@apply px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md transition-colors;
}
</style>

137
web/pages/add-resource.vue Normal file
View File

@@ -0,0 +1,137 @@
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100">
<!-- 页面头部 -->
<div class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div class="max-w-4xl mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<button
@click="$router.back()"
class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
>
<i class="fas fa-arrow-left text-xl"></i>
</button>
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">添加资源</h1>
</div>
<div class="flex items-center space-x-2">
<NuxtLink
to="/admin"
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md transition-colors text-sm"
>
<i class="fas fa-cog mr-1"></i>管理后台
</NuxtLink>
</div>
</div>
</div>
</div>
<!-- 主要内容 -->
<div class="max-w-4xl mx-auto px-4 py-8">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg">
<!-- Tab 切换 -->
<div class="border-b border-gray-200 dark:border-gray-700">
<div class="flex">
<button
v-for="tab in tabs"
:key="tab.value"
:class="[
'px-6 py-4 text-sm font-medium border-b-2 transition-colors',
mode === tab.value
? 'border-blue-500 text-blue-600 dark:text-blue-400'
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
]"
@click="mode = tab.value"
>
{{ tab.label }}
</button>
</div>
</div>
<!-- 内容区域 -->
<div class="p-6">
<!-- 批量添加 -->
<BatchAddResource
v-if="mode === 'batch'"
@success="handleSuccess"
@error="handleError"
@cancel="handleCancel"
/>
<!-- 单个添加 -->
<SingleAddResource
v-else-if="mode === 'single'"
@success="handleSuccess"
@error="handleError"
@cancel="handleCancel"
/>
<!-- API说明 -->
<ApiDocumentation
v-else
@cancel="handleCancel"
/>
</div>
</div>
</div>
<!-- 成功/失败提示 -->
<SuccessToast v-if="showSuccess" :message="successMsg" @close="showSuccess = false" />
<ErrorToast v-if="showError" :message="errorMsg" @close="showError = false" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import BatchAddResource from '~/components/BatchAddResource.vue'
import SingleAddResource from '~/components/SingleAddResource.vue'
import ApiDocumentation from '~/components/ApiDocumentation.vue'
import SuccessToast from '~/components/SuccessToast.vue'
import ErrorToast from '~/components/ErrorToast.vue'
const router = useRouter()
const showSuccess = ref(false)
const successMsg = ref('')
const showError = ref(false)
const errorMsg = ref('')
const tabs = [
{ label: '批量添加', value: 'batch' },
{ label: '单个添加', value: 'single' },
{ label: 'API说明', value: 'api' },
]
const mode = ref('batch')
// 检查用户权限
onMounted(() => {
const userStore = useUserStore()
if (!userStore.isAuthenticated) {
router.push('/login')
return
}
})
// 事件处理
const handleSuccess = (message: string) => {
successMsg.value = message
showSuccess.value = true
}
const handleError = (message: string) => {
errorMsg.value = message
showError.value = true
}
const handleCancel = () => {
router.back()
}
// 设置页面标题
useHead({
title: '添加资源 - 网盘资源管理系统'
})
</script>
<style scoped>
/* 自定义样式 */
</style>

View File

@@ -42,12 +42,12 @@
>
<i class="fas fa-home"></i> 返回首页
</NuxtLink>
<button
@click="showAddResourceModal = true"
<NuxtLink
to="/add-resource"
class="w-full sm:w-auto px-4 py-2 bg-green-600 hover:bg-green-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
>
<i class="fas fa-plus"></i> 添加资源
</button>
</NuxtLink>
</nav>
</div>
@@ -71,12 +71,12 @@
<i class="fas fa-chevron-right text-gray-400"></i>
</div>
</button>
<button @click="showAddResourceModal = true" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<NuxtLink to="/add-resource" class="w-full text-left p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors block">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">添加资源</span>
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">批量添加资源</span>
<i class="fas fa-plus text-gray-400"></i>
</div>
</button>
</NuxtLink>
</div>
</div>
@@ -269,16 +269,11 @@
</div>
</div>
</div>
<!-- 模态框组件 -->
<ResourceModal v-if="showAddResourceModal" @close="showAddResourceModal = false" />
</div>
</div>
</template>
<script setup>
import ResourceModal from '~/components/ResourceModal.vue'
definePageMeta({
middleware: 'auth'
})
@@ -292,7 +287,6 @@ const { $api } = useNuxtApp()
const user = ref(null)
const stats = ref(null)
const showAddResourceModal = ref(false)
const pageLoading = ref(true) // 添加页面加载状态
const systemConfig = ref(null) // 添加系统配置状态