mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 19:37:33 +08:00
update: 后台页面优化
This commit is contained in:
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
@@ -12,6 +12,7 @@ declare module 'vue' {
|
||||
NAlert: typeof import('naive-ui')['NAlert']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<div v-if="show" class="fixed top-4 right-4 z-50">
|
||||
<div class="bg-red-50 border border-red-200 rounded-lg p-4 shadow-lg max-w-sm">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-circle text-red-400"></i>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<h3 class="text-sm font-medium text-red-800">错误</h3>
|
||||
<div class="mt-1 text-sm text-red-700">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 flex-shrink-0">
|
||||
<button
|
||||
@click="close"
|
||||
class="inline-flex text-red-400 hover:text-red-600 focus:outline-none"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
interface Props {
|
||||
message: string
|
||||
duration?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
duration: 5000
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const show = ref(false)
|
||||
|
||||
const close = () => {
|
||||
show.value = false
|
||||
emit('close')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
show.value = true
|
||||
if (props.duration > 0) {
|
||||
setTimeout(() => {
|
||||
close()
|
||||
}, props.duration)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -114,10 +114,6 @@ Body:
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成功/失败提示 -->
|
||||
<SuccessToast v-if="showSuccess" :message="successMsg" @close="showSuccess = false" />
|
||||
<ErrorToast v-if="showError" :message="errorMsg" @close="showError = false" />
|
||||
</div>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
@@ -147,10 +143,9 @@ Body:
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useResourceStore } from '~/stores/resource'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import SuccessToast from './SuccessToast.vue'
|
||||
import ErrorToast from './ErrorToast.vue'
|
||||
import { useReadyResourceApi } from '~/composables/useApi'
|
||||
|
||||
const notification = useNotification()
|
||||
const store = useResourceStore()
|
||||
const { categories } = storeToRefs(store)
|
||||
|
||||
@@ -159,10 +154,6 @@ const emit = defineEmits(['close', 'save'])
|
||||
|
||||
const loading = ref(false)
|
||||
const newTag = ref('')
|
||||
const showSuccess = ref(false)
|
||||
const showError = ref(false)
|
||||
const successMsg = ref('')
|
||||
const errorMsg = ref('')
|
||||
|
||||
const tabs = [
|
||||
{ label: '批量添加', value: 'batch' },
|
||||
@@ -226,12 +217,14 @@ const handleBatchSubmit = async () => {
|
||||
try {
|
||||
if (!batchInput.value.trim()) throw new Error('请输入资源内容')
|
||||
const res: any = await readyResourceApi.createReadyResourcesFromText(batchInput.value)
|
||||
showSuccess.value = true
|
||||
successMsg.value = `成功添加 ${res.count || 0} 个资源,资源已进入待处理列表,处理完成后会自动入库`
|
||||
notification.success({
|
||||
content: `成功添加 ${res.count || 0} 个资源,资源已进入待处理列表,处理完成后会自动入库`
|
||||
})
|
||||
batchInput.value = ''
|
||||
} catch (e: any) {
|
||||
showError.value = true
|
||||
errorMsg.value = e.message || '批量添加失败'
|
||||
notification.error({
|
||||
content: e.message || '批量添加失败'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -251,8 +244,9 @@ const handleSingleSubmit = async () => {
|
||||
tags: [...form.value.tags],
|
||||
})
|
||||
}
|
||||
showSuccess.value = true
|
||||
successMsg.value = '资源已进入待处理列表,处理完成后会自动入库'
|
||||
notification.success({
|
||||
content: '资源已进入待处理列表,处理完成后会自动入库'
|
||||
})
|
||||
// 清空表单
|
||||
form.value.title = ''
|
||||
form.value.description = ''
|
||||
@@ -260,8 +254,9 @@ const handleSingleSubmit = async () => {
|
||||
form.value.tags = []
|
||||
form.value.file_type = ''
|
||||
} catch (e: any) {
|
||||
showError.value = true
|
||||
errorMsg.value = e.message || '添加失败'
|
||||
notification.error({
|
||||
content: e.message || '添加失败'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<div v-if="show" class="fixed top-4 right-4 z-50">
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4 shadow-lg max-w-sm">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-check-circle text-green-400"></i>
|
||||
</div>
|
||||
<div class="ml-3 flex-1">
|
||||
<h3 class="text-sm font-medium text-green-800">成功</h3>
|
||||
<div class="mt-1 text-sm text-green-700">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 flex-shrink-0">
|
||||
<button
|
||||
@click="close"
|
||||
class="inline-flex text-green-400 hover:text-green-600 focus:outline-none"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
interface Props {
|
||||
message: string
|
||||
duration?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const show = ref(false)
|
||||
|
||||
const close = () => {
|
||||
show.value = false
|
||||
emit('close')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
show.value = true
|
||||
if (props.duration > 0) {
|
||||
setTimeout(() => {
|
||||
close()
|
||||
}, props.duration)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -18,12 +18,15 @@
|
||||
<AdminHeader :title="pageTitle" />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="p-3 sm:p-5">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- 页面内容插槽 -->
|
||||
<slot />
|
||||
<ClientOnly>
|
||||
<n-notification-provider>
|
||||
<!-- 页面内容插槽 -->
|
||||
<slot />
|
||||
</n-notification-provider>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="forbidden-layout">
|
||||
<slot />
|
||||
<div class="single-layout">
|
||||
<n-notification-provider>
|
||||
<slot />
|
||||
</n-notification-provider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,7 +19,7 @@ body, html {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.forbidden-layout {
|
||||
.single-layout {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -43,10 +43,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成功/失败提示 -->
|
||||
<SuccessToast v-if="showSuccess" :message="successMsg" @close="showSuccess = false" />
|
||||
<ErrorToast v-if="showError" :message="errorMsg" @close="showError = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,21 +57,15 @@ 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' },
|
||||
]
|
||||
const mode = ref('batch')
|
||||
const notification = useNotification()
|
||||
|
||||
// 检查用户权限
|
||||
onMounted(() => {
|
||||
@@ -88,13 +78,15 @@ onMounted(() => {
|
||||
|
||||
// 事件处理
|
||||
const handleSuccess = (message: string) => {
|
||||
successMsg.value = message
|
||||
showSuccess.value = true
|
||||
notification.success({
|
||||
content: message
|
||||
})
|
||||
}
|
||||
|
||||
const handleError = (message: string) => {
|
||||
errorMsg.value = message
|
||||
showError.value = true
|
||||
notification.error({
|
||||
content: message
|
||||
})
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
<form @submit.prevent="saveConfig" class="space-y-6">
|
||||
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane name="SEO 配置" tab="SEO 配置">
|
||||
<n-tab-pane name="站点配置" tab="站点配置">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- 网站标题 -->
|
||||
<div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
网站标题 *
|
||||
</label>
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 网站描述 -->
|
||||
<div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
网站描述
|
||||
</label>
|
||||
@@ -62,21 +62,9 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 作者 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
作者
|
||||
</label>
|
||||
<input
|
||||
v-model="config.author"
|
||||
type="text"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="系统管理员"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 版权信息 -->
|
||||
<div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
版权信息
|
||||
</label>
|
||||
@@ -87,9 +75,46 @@
|
||||
placeholder="© 2024 老九网盘资源数据库"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- <div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
每页显示数量
|
||||
</label>
|
||||
<select
|
||||
v-model.number="config.pageSize"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value="20">20 条</option>
|
||||
<option value="50">50 条</option>
|
||||
<option value="100">100 条</option>
|
||||
<option value="200">200 条</option>
|
||||
</select>
|
||||
</div> -->
|
||||
|
||||
<!-- 系统维护模式 -->
|
||||
<div class="md:col-span-2 flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
||||
维护模式
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
开启后,普通用户无法访问系统
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
v-model="config.maintenanceMode"
|
||||
type="checkbox"
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-red-300 dark:peer-focus:ring-red-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-red-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="自动处理配置" tab="自动处理配置">
|
||||
<n-tab-pane name="功能配置" tab="功能配置">
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<!-- 待处理资源自动处理 -->
|
||||
@@ -221,47 +246,6 @@
|
||||
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="其他配置" tab="其他配置">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- 每页显示数量 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
每页显示数量
|
||||
</label>
|
||||
<select
|
||||
v-model.number="config.pageSize"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value="20">20 条</option>
|
||||
<option value="50">50 条</option>
|
||||
<option value="100">100 条</option>
|
||||
<option value="200">200 条</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 系统维护模式 -->
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
||||
维护模式
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
开启后,普通用户无法访问系统
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
v-model="config.maintenanceMode"
|
||||
type="checkbox"
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-red-300 dark:peer-focus:ring-red-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-red-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="API配置" tab="API配置">
|
||||
<div class="space-y-4">
|
||||
<!-- API Token -->
|
||||
@@ -300,7 +284,6 @@
|
||||
<p>• 资源搜索: GET /api/public/resources/search</p>
|
||||
<p>• 热门剧: GET /api/public/hot-dramas</p>
|
||||
<p>• 认证方式: 在请求头中添加 X-API-Token 或在查询参数中添加 api_token</p>
|
||||
<p>• Swagger文档: <a href="/swagger/index.html" target="_blank" class="underline">查看完整API文档</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -63,19 +63,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<ErrorToast
|
||||
v-if="showErrorToast"
|
||||
:message="errorToastMessage"
|
||||
@close="showErrorToast = false"
|
||||
/>
|
||||
|
||||
<!-- 成功提示 -->
|
||||
<SuccessToast
|
||||
v-if="showSuccessToast"
|
||||
:message="successToastMessage"
|
||||
@close="showSuccessToast = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -85,6 +72,7 @@ import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const notification = useNotification()
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
@@ -96,12 +84,6 @@ const errors = reactive({
|
||||
password: ''
|
||||
})
|
||||
|
||||
const showErrorToast = ref(false)
|
||||
const errorToastMessage = ref('')
|
||||
const showSuccessToast = ref(false)
|
||||
const successToastMessage = ref('')
|
||||
|
||||
|
||||
|
||||
const validateForm = () => {
|
||||
errors.username = ''
|
||||
@@ -129,6 +111,9 @@ const handleLogin = async () => {
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
notification.success({
|
||||
content: '登录成功'
|
||||
})
|
||||
await router.push('/admin')
|
||||
} else {
|
||||
// 根据错误类型提供更友好的提示
|
||||
@@ -144,11 +129,17 @@ const handleLogin = async () => {
|
||||
message = result.message
|
||||
}
|
||||
}
|
||||
errorToastMessage.value = message
|
||||
showErrorToast.value = true
|
||||
notification.error({
|
||||
content: message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
layout: 'single',
|
||||
ssr: false
|
||||
})
|
||||
|
||||
// 设置页面标题
|
||||
useHead({
|
||||
title: '管理员登录 - 老九网盘资源数据库'
|
||||
|
||||
@@ -86,20 +86,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<ErrorToast
|
||||
v-if="showErrorToast"
|
||||
:message="errorToastMessage"
|
||||
@close="showErrorToast = false"
|
||||
/>
|
||||
|
||||
<!-- 成功提示 -->
|
||||
<SuccessToast
|
||||
v-if="showSuccessToast"
|
||||
:message="successToastMessage"
|
||||
@close="showSuccessToast = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -109,7 +95,7 @@ import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const notification = useNotification()
|
||||
const form = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
@@ -124,11 +110,6 @@ const errors = reactive({
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const showErrorToast = ref(false)
|
||||
const errorToastMessage = ref('')
|
||||
const showSuccessToast = ref(false)
|
||||
const successToastMessage = ref('')
|
||||
|
||||
const validateForm = () => {
|
||||
errors.username = ''
|
||||
errors.email = ''
|
||||
@@ -184,8 +165,9 @@ const handleRegister = async () => {
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
successToastMessage.value = '注册成功!请登录'
|
||||
showSuccessToast.value = true
|
||||
notification.success({
|
||||
content: '注册成功!请登录'
|
||||
})
|
||||
setTimeout(() => {
|
||||
router.push('/login')
|
||||
}, 2000)
|
||||
@@ -203,11 +185,17 @@ const handleRegister = async () => {
|
||||
errorMessage = result.message
|
||||
}
|
||||
}
|
||||
errorToastMessage.value = errorMessage
|
||||
showErrorToast.value = true
|
||||
notification.error({
|
||||
content: errorMessage
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
layout: 'single',
|
||||
ssr: false
|
||||
})
|
||||
|
||||
// 设置页面标题
|
||||
useHead({
|
||||
title: '用户注册 - 老九网盘资源数据库'
|
||||
|
||||
Reference in New Issue
Block a user