update: 后台页面优化

This commit is contained in:
ctwj
2025-08-02 18:06:54 +08:00
parent 14130eac8b
commit 0700de36f5
11 changed files with 99 additions and 261 deletions

1
web/components.d.ts vendored
View File

@@ -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']

View File

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

View File

@@ -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
}

View File

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

View File

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

View File

@@ -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;

View File

@@ -1 +0,0 @@

View File

@@ -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 = () => {

View File

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

View File

@@ -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: '管理员登录 - 老九网盘资源数据库'

View File

@@ -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: '用户注册 - 老九网盘资源数据库'