mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
update: 优化添加资源
This commit is contained in:
14
web/components.d.ts
vendored
14
web/components.d.ts
vendored
@@ -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']
|
||||
}
|
||||
|
||||
38
web/components/ApiDocumentation.vue
Normal file
38
web/components/ApiDocumentation.vue
Normal 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>
|
||||
73
web/components/BatchAddResource.vue
Normal file
73
web/components/BatchAddResource.vue
Normal 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>
|
||||
123
web/components/SingleAddResource.vue
Normal file
123
web/components/SingleAddResource.vue
Normal 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 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
137
web/pages/add-resource.vue
Normal 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>
|
||||
@@ -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) // 添加系统配置状态
|
||||
|
||||
|
||||
Reference in New Issue
Block a user