mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 19:37:33 +08:00
update: 持久化系统配置
This commit is contained in:
@@ -78,6 +78,7 @@ func autoMigrate() error {
|
||||
&entity.ReadyResource{},
|
||||
&entity.User{},
|
||||
&entity.SearchStat{},
|
||||
&entity.SystemConfig{},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
60
db/converter/system_config_converter.go
Normal file
60
db/converter/system_config_converter.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"res_db/db/dto"
|
||||
"res_db/db/entity"
|
||||
)
|
||||
|
||||
// SystemConfigToResponse 将系统配置实体转换为响应DTO
|
||||
func SystemConfigToResponse(config *entity.SystemConfig) *dto.SystemConfigResponse {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &dto.SystemConfigResponse{
|
||||
ID: config.ID,
|
||||
CreatedAt: config.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: config.UpdatedAt.Format(time.RFC3339),
|
||||
|
||||
// SEO 配置
|
||||
SiteTitle: config.SiteTitle,
|
||||
SiteDescription: config.SiteDescription,
|
||||
Keywords: config.Keywords,
|
||||
Author: config.Author,
|
||||
Copyright: config.Copyright,
|
||||
|
||||
// 自动处理配置
|
||||
AutoProcessReadyResources: config.AutoProcessReadyResources,
|
||||
AutoProcessInterval: config.AutoProcessInterval,
|
||||
|
||||
// 其他配置
|
||||
PageSize: config.PageSize,
|
||||
MaintenanceMode: config.MaintenanceMode,
|
||||
}
|
||||
}
|
||||
|
||||
// RequestToSystemConfig 将请求DTO转换为系统配置实体
|
||||
func RequestToSystemConfig(req *dto.SystemConfigRequest) *entity.SystemConfig {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &entity.SystemConfig{
|
||||
// SEO 配置
|
||||
SiteTitle: req.SiteTitle,
|
||||
SiteDescription: req.SiteDescription,
|
||||
Keywords: req.Keywords,
|
||||
Author: req.Author,
|
||||
Copyright: req.Copyright,
|
||||
|
||||
// 自动处理配置
|
||||
AutoProcessReadyResources: req.AutoProcessReadyResources,
|
||||
AutoProcessInterval: req.AutoProcessInterval,
|
||||
|
||||
// 其他配置
|
||||
PageSize: req.PageSize,
|
||||
MaintenanceMode: req.MaintenanceMode,
|
||||
}
|
||||
}
|
||||
41
db/dto/system_config.go
Normal file
41
db/dto/system_config.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package dto
|
||||
|
||||
// SystemConfigRequest 系统配置请求
|
||||
type SystemConfigRequest struct {
|
||||
// SEO 配置
|
||||
SiteTitle string `json:"site_title" validate:"required"`
|
||||
SiteDescription string `json:"site_description"`
|
||||
Keywords string `json:"keywords"`
|
||||
Author string `json:"author"`
|
||||
Copyright string `json:"copyright"`
|
||||
|
||||
// 自动处理配置
|
||||
AutoProcessReadyResources bool `json:"auto_process_ready_resources"`
|
||||
AutoProcessInterval int `json:"auto_process_interval" validate:"min=1,max=1440"`
|
||||
|
||||
// 其他配置
|
||||
PageSize int `json:"page_size" validate:"min=10,max=500"`
|
||||
MaintenanceMode bool `json:"maintenance_mode"`
|
||||
}
|
||||
|
||||
// SystemConfigResponse 系统配置响应
|
||||
type SystemConfigResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
|
||||
// SEO 配置
|
||||
SiteTitle string `json:"site_title"`
|
||||
SiteDescription string `json:"site_description"`
|
||||
Keywords string `json:"keywords"`
|
||||
Author string `json:"author"`
|
||||
Copyright string `json:"copyright"`
|
||||
|
||||
// 自动处理配置
|
||||
AutoProcessReadyResources bool `json:"auto_process_ready_resources"`
|
||||
AutoProcessInterval int `json:"auto_process_interval"`
|
||||
|
||||
// 其他配置
|
||||
PageSize int `json:"page_size"`
|
||||
MaintenanceMode bool `json:"maintenance_mode"`
|
||||
}
|
||||
32
db/entity/system_config.go
Normal file
32
db/entity/system_config.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SystemConfig 系统配置实体
|
||||
type SystemConfig struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// SEO 配置
|
||||
SiteTitle string `json:"site_title" gorm:"size:200;not null;default:'网盘资源管理系统'"`
|
||||
SiteDescription string `json:"site_description" gorm:"size:500"`
|
||||
Keywords string `json:"keywords" gorm:"size:500"`
|
||||
Author string `json:"author" gorm:"size:100"`
|
||||
Copyright string `json:"copyright" gorm:"size:200"`
|
||||
|
||||
// 自动处理配置
|
||||
AutoProcessReadyResources bool `json:"auto_process_ready_resources" gorm:"default:false"`
|
||||
AutoProcessInterval int `json:"auto_process_interval" gorm:"default:30"` // 分钟
|
||||
|
||||
// 其他配置
|
||||
PageSize int `json:"page_size" gorm:"default:100"`
|
||||
MaintenanceMode bool `json:"maintenance_mode" gorm:"default:false"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (SystemConfig) TableName() string {
|
||||
return "system_configs"
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type RepositoryManager struct {
|
||||
ReadyResourceRepository ReadyResourceRepository
|
||||
UserRepository UserRepository
|
||||
SearchStatRepository SearchStatRepository
|
||||
SystemConfigRepository SystemConfigRepository
|
||||
}
|
||||
|
||||
// NewRepositoryManager 创建Repository管理器
|
||||
@@ -27,5 +28,6 @@ func NewRepositoryManager(db *gorm.DB) *RepositoryManager {
|
||||
ReadyResourceRepository: NewReadyResourceRepository(db),
|
||||
UserRepository: NewUserRepository(db),
|
||||
SearchStatRepository: NewSearchStatRepository(db),
|
||||
SystemConfigRepository: NewSystemConfigRepository(db),
|
||||
}
|
||||
}
|
||||
|
||||
80
db/repo/system_config_repository.go
Normal file
80
db/repo/system_config_repository.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"res_db/db/entity"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SystemConfigRepository 系统配置Repository接口
|
||||
type SystemConfigRepository interface {
|
||||
BaseRepository[entity.SystemConfig]
|
||||
FindFirst() (*entity.SystemConfig, error)
|
||||
GetOrCreateDefault() (*entity.SystemConfig, error)
|
||||
Upsert(config *entity.SystemConfig) error
|
||||
}
|
||||
|
||||
// SystemConfigRepositoryImpl 系统配置Repository实现
|
||||
type SystemConfigRepositoryImpl struct {
|
||||
BaseRepositoryImpl[entity.SystemConfig]
|
||||
}
|
||||
|
||||
// NewSystemConfigRepository 创建系统配置Repository
|
||||
func NewSystemConfigRepository(db *gorm.DB) SystemConfigRepository {
|
||||
return &SystemConfigRepositoryImpl{
|
||||
BaseRepositoryImpl: BaseRepositoryImpl[entity.SystemConfig]{db: db},
|
||||
}
|
||||
}
|
||||
|
||||
// FindFirst 获取第一个配置(通常只有一个配置)
|
||||
func (r *SystemConfigRepositoryImpl) FindFirst() (*entity.SystemConfig, error) {
|
||||
var config entity.SystemConfig
|
||||
err := r.db.First(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Upsert 创建或更新系统配置
|
||||
func (r *SystemConfigRepositoryImpl) Upsert(config *entity.SystemConfig) error {
|
||||
var existingConfig entity.SystemConfig
|
||||
err := r.db.First(&existingConfig).Error
|
||||
|
||||
if err != nil {
|
||||
// 如果不存在,则创建
|
||||
return r.db.Create(config).Error
|
||||
} else {
|
||||
// 如果存在,则更新
|
||||
config.ID = existingConfig.ID
|
||||
return r.db.Save(config).Error
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrCreateDefault 获取配置或创建默认配置
|
||||
func (r *SystemConfigRepositoryImpl) GetOrCreateDefault() (*entity.SystemConfig, error) {
|
||||
config, err := r.FindFirst()
|
||||
if err != nil {
|
||||
// 创建默认配置
|
||||
defaultConfig := &entity.SystemConfig{
|
||||
SiteTitle: "网盘资源管理系统",
|
||||
SiteDescription: "专业的网盘资源管理系统",
|
||||
Keywords: "网盘,资源管理,文件分享",
|
||||
Author: "系统管理员",
|
||||
Copyright: "© 2024 网盘资源管理系统",
|
||||
AutoProcessReadyResources: false,
|
||||
AutoProcessInterval: 30,
|
||||
PageSize: 100,
|
||||
MaintenanceMode: false,
|
||||
}
|
||||
|
||||
err = r.db.Create(defaultConfig).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return defaultConfig, nil
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
144
handlers/system_config_handler.go
Normal file
144
handlers/system_config_handler.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"res_db/db/converter"
|
||||
"res_db/db/dto"
|
||||
"res_db/db/repo"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SystemConfigHandler 系统配置处理器
|
||||
type SystemConfigHandler struct {
|
||||
systemConfigRepo repo.SystemConfigRepository
|
||||
}
|
||||
|
||||
// NewSystemConfigHandler 创建系统配置处理器
|
||||
func NewSystemConfigHandler(systemConfigRepo repo.SystemConfigRepository) *SystemConfigHandler {
|
||||
return &SystemConfigHandler{
|
||||
systemConfigRepo: systemConfigRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig 获取系统配置
|
||||
func (h *SystemConfigHandler) GetConfig(c *gin.Context) {
|
||||
config, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||
if err != nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "获取系统配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
configResponse := converter.SystemConfigToResponse(config)
|
||||
SuccessResponse(c, configResponse, "获取系统配置成功")
|
||||
}
|
||||
|
||||
// UpdateConfig 更新系统配置
|
||||
func (h *SystemConfigHandler) UpdateConfig(c *gin.Context) {
|
||||
var req dto.SystemConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
ErrorResponse(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证参数
|
||||
if req.SiteTitle == "" {
|
||||
ErrorResponse(c, http.StatusBadRequest, "网站标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.AutoProcessInterval < 1 || req.AutoProcessInterval > 1440 {
|
||||
ErrorResponse(c, http.StatusBadRequest, "自动处理间隔必须在1-1440分钟之间")
|
||||
return
|
||||
}
|
||||
|
||||
if req.PageSize < 10 || req.PageSize > 500 {
|
||||
ErrorResponse(c, http.StatusBadRequest, "每页显示数量必须在10-500之间")
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为实体
|
||||
config := converter.RequestToSystemConfig(&req)
|
||||
if config == nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "数据转换失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
err := h.systemConfigRepo.Upsert(config)
|
||||
if err != nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "保存系统配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回更新后的配置
|
||||
updatedConfig, err := h.systemConfigRepo.FindFirst()
|
||||
if err != nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "获取更新后的配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
configResponse := converter.SystemConfigToResponse(updatedConfig)
|
||||
SuccessResponse(c, configResponse, "系统配置保存成功")
|
||||
}
|
||||
|
||||
// GetSystemConfig 获取系统配置(使用全局repoManager)
|
||||
func GetSystemConfig(c *gin.Context) {
|
||||
config, err := repoManager.SystemConfigRepository.GetOrCreateDefault()
|
||||
if err != nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "获取系统配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
configResponse := converter.SystemConfigToResponse(config)
|
||||
SuccessResponse(c, configResponse, "获取系统配置成功")
|
||||
}
|
||||
|
||||
// UpdateSystemConfig 更新系统配置(使用全局repoManager)
|
||||
func UpdateSystemConfig(c *gin.Context) {
|
||||
var req dto.SystemConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
ErrorResponse(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证参数
|
||||
if req.SiteTitle == "" {
|
||||
ErrorResponse(c, http.StatusBadRequest, "网站标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.AutoProcessInterval < 1 || req.AutoProcessInterval > 1440 {
|
||||
ErrorResponse(c, http.StatusBadRequest, "自动处理间隔必须在1-1440分钟之间")
|
||||
return
|
||||
}
|
||||
|
||||
if req.PageSize < 10 || req.PageSize > 500 {
|
||||
ErrorResponse(c, http.StatusBadRequest, "每页显示数量必须在10-500之间")
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为实体
|
||||
config := converter.RequestToSystemConfig(&req)
|
||||
if config == nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "数据转换失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
err := repoManager.SystemConfigRepository.Upsert(config)
|
||||
if err != nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "保存系统配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回更新后的配置
|
||||
updatedConfig, err := repoManager.SystemConfigRepository.FindFirst()
|
||||
if err != nil {
|
||||
ErrorResponse(c, http.StatusInternalServerError, "获取更新后的配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
configResponse := converter.SystemConfigToResponse(updatedConfig)
|
||||
SuccessResponse(c, configResponse, "系统配置保存成功")
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func Login(c *gin.Context) {
|
||||
User: converter.ToUserResponse(user),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
SuccessResponse(c, response, "登录成功")
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
|
||||
4
main.go
4
main.go
@@ -111,6 +111,10 @@ func main() {
|
||||
api.GET("/search-stats/trend", handlers.GetSearchTrend)
|
||||
api.GET("/search-stats/keyword/:keyword/trend", handlers.GetKeywordTrend)
|
||||
api.POST("/search-stats/record", handlers.RecordSearch)
|
||||
|
||||
// 系统配置路由
|
||||
api.GET("/system/config", handlers.GetSystemConfig)
|
||||
api.POST("/system/config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.UpdateSystemConfig)
|
||||
}
|
||||
|
||||
// 静态文件服务
|
||||
|
||||
26
web/components/LoadingOverlay.vue
Normal file
26
web/components/LoadingOverlay.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div v-if="show" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ title }}</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">{{ message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
show: boolean
|
||||
title?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
title: '正在加载...',
|
||||
message: '请稍候'
|
||||
})
|
||||
</script>
|
||||
@@ -429,4 +429,35 @@ export const useStatsApi = () => {
|
||||
return {
|
||||
getStats,
|
||||
}
|
||||
}
|
||||
|
||||
// 系统配置相关API
|
||||
export const useSystemConfigApi = () => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const getAuthHeaders = () => {
|
||||
const userStore = useUserStore()
|
||||
return userStore.authHeaders
|
||||
}
|
||||
|
||||
const getSystemConfig = async () => {
|
||||
return await $fetch('/system/config', {
|
||||
baseURL: config.public.apiBase,
|
||||
// GET接口不需要认证头
|
||||
})
|
||||
}
|
||||
|
||||
const updateSystemConfig = async (data: any) => {
|
||||
return await $fetch('/system/config', {
|
||||
baseURL: config.public.apiBase,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
headers: getAuthHeaders() as Record<string, string>
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
getSystemConfig,
|
||||
updateSystemConfig,
|
||||
}
|
||||
}
|
||||
84
web/composables/useSeo.ts
Normal file
84
web/composables/useSeo.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
interface SystemConfig {
|
||||
id: number
|
||||
site_title: string
|
||||
site_description: string
|
||||
keywords: string
|
||||
author: string
|
||||
copyright: string
|
||||
auto_process_ready_resources: boolean
|
||||
auto_process_interval: number
|
||||
page_size: number
|
||||
maintenance_mode: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export const useSeo = () => {
|
||||
const systemConfig = ref<SystemConfig | null>(null)
|
||||
const { getSystemConfig } = useSystemConfigApi()
|
||||
|
||||
// 获取系统配置
|
||||
const fetchSystemConfig = async () => {
|
||||
try {
|
||||
const response = await getSystemConfig() as any
|
||||
console.log('系统配置响应:', response)
|
||||
if (response && response.success && response.data) {
|
||||
systemConfig.value = response.data
|
||||
} else if (response && response.data) {
|
||||
// 兼容非标准格式
|
||||
systemConfig.value = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取系统配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成页面标题
|
||||
const generateTitle = (pageTitle: string) => {
|
||||
if (systemConfig.value?.site_title) {
|
||||
return `${systemConfig.value.site_title} - ${pageTitle}`
|
||||
}
|
||||
return `${pageTitle} - 网盘资源管理系统`
|
||||
}
|
||||
|
||||
// 生成页面元数据
|
||||
const generateMeta = (customMeta?: Record<string, string>) => {
|
||||
const defaultMeta = {
|
||||
description: systemConfig.value?.site_description || '专业的网盘资源管理系统',
|
||||
keywords: systemConfig.value?.keywords || '网盘,资源管理,文件分享',
|
||||
author: systemConfig.value?.author || '系统管理员',
|
||||
copyright: systemConfig.value?.copyright || '© 2024 网盘资源管理系统'
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultMeta,
|
||||
...customMeta
|
||||
}
|
||||
}
|
||||
|
||||
// 设置页面SEO
|
||||
const setPageSeo = (pageTitle: string, customMeta?: Record<string, string>) => {
|
||||
const title = generateTitle(pageTitle)
|
||||
const meta = generateMeta(customMeta)
|
||||
|
||||
useHead({
|
||||
title,
|
||||
meta: [
|
||||
{ name: 'description', content: meta.description },
|
||||
{ name: 'keywords', content: meta.keywords },
|
||||
{ name: 'author', content: meta.author },
|
||||
{ name: 'copyright', content: meta.copyright }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
systemConfig,
|
||||
fetchSystemConfig,
|
||||
generateTitle,
|
||||
generateMeta,
|
||||
setPageSeo
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,26 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
||||
<!-- 全局加载状态 -->
|
||||
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">正在加载...</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">请稍候,正在初始化管理后台</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- 头部 -->
|
||||
<div class="bg-slate-800 text-white rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
||||
<NuxtLink to="/" class="text-white hover:text-gray-200 no-underline">网盘资源管理系统</NuxtLink>
|
||||
<NuxtLink to="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">
|
||||
{{ systemConfig?.site_title || '网盘资源管理系统' }}
|
||||
</NuxtLink>
|
||||
</h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="text-sm">
|
||||
@@ -268,6 +283,9 @@ definePageMeta({
|
||||
middleware: 'auth'
|
||||
})
|
||||
|
||||
// API
|
||||
const { getSystemConfig } = useSystemConfigApi()
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const { $api } = useNuxtApp()
|
||||
@@ -275,15 +293,59 @@ const { $api } = useNuxtApp()
|
||||
const user = ref(null)
|
||||
const stats = ref(null)
|
||||
const showAddResourceModal = ref(false)
|
||||
const pageLoading = ref(true) // 添加页面加载状态
|
||||
const systemConfig = ref(null) // 添加系统配置状态
|
||||
|
||||
// 页面元数据 - 移到变量声明之后
|
||||
useHead({
|
||||
title: () => systemConfig.value?.site_title ? `${systemConfig.value.site_title} - 管理后台` : '管理后台 - 网盘资源管理系统',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: () => systemConfig.value?.site_description || '网盘资源管理系统管理后台'
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
content: () => systemConfig.value?.keywords || '网盘,资源管理,管理后台'
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
content: () => systemConfig.value?.author || '系统管理员'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 获取系统配置
|
||||
const fetchSystemConfig = async () => {
|
||||
try {
|
||||
const response = await getSystemConfig()
|
||||
console.log('admin系统配置响应:', response)
|
||||
if (response && response.success && response.data) {
|
||||
systemConfig.value = response.data
|
||||
} else if (response && response.data) {
|
||||
// 兼容非标准格式
|
||||
systemConfig.value = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取系统配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查认证状态
|
||||
const checkAuth = () => {
|
||||
console.log('admin - checkAuth 开始')
|
||||
userStore.initAuth()
|
||||
|
||||
console.log('admin - isAuthenticated:', userStore.isAuthenticated)
|
||||
console.log('admin - user:', userStore.userInfo)
|
||||
|
||||
if (!userStore.isAuthenticated) {
|
||||
console.log('admin - 用户未认证,重定向到首页')
|
||||
router.push('/')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('admin - 用户已认证,继续')
|
||||
}
|
||||
|
||||
// 获取统计信息
|
||||
@@ -324,7 +386,7 @@ const goToBatchAdd = () => {
|
||||
}
|
||||
|
||||
const goToSystemSettings = () => {
|
||||
// 实现系统设置页面跳转
|
||||
router.push('/system-config')
|
||||
}
|
||||
|
||||
const goToUserManagement = () => {
|
||||
@@ -336,9 +398,19 @@ const goToHotKeywords = () => {
|
||||
}
|
||||
|
||||
// 页面加载时检查认证
|
||||
onMounted(() => {
|
||||
checkAuth()
|
||||
fetchStats()
|
||||
onMounted(async () => {
|
||||
try {
|
||||
checkAuth()
|
||||
await Promise.all([
|
||||
fetchStats(),
|
||||
fetchSystemConfig()
|
||||
])
|
||||
} catch (error) {
|
||||
console.error('admin页面初始化失败:', error)
|
||||
} finally {
|
||||
// 所有数据加载完成后,关闭加载状态
|
||||
pageLoading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
||||
<!-- 全局加载状态 -->
|
||||
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">正在加载...</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">请稍候,正在初始化系统</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- 头部 -->
|
||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center relative">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold mb-4">
|
||||
<a href="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">网盘资源管理系统</a>
|
||||
<a href="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">
|
||||
{{ systemConfig?.site_title || '网盘资源管理系统' }}
|
||||
</a>
|
||||
</h1>
|
||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4 right-4 top-0 absolute">
|
||||
<NuxtLink
|
||||
v-if="!userStore.isAuthenticated"
|
||||
v-if="authInitialized && !userStore.isAuthenticated"
|
||||
to="/login"
|
||||
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-sign-in-alt"></i> 登录
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="userStore.isAuthenticated"
|
||||
v-if="authInitialized && userStore.isAuthenticated"
|
||||
to="/admin"
|
||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
||||
>
|
||||
@@ -202,7 +217,7 @@
|
||||
<footer class="mt-8 py-6 border-t border-gray-200">
|
||||
<div class="max-w-7xl mx-auto text-center text-gray-600 text-sm">
|
||||
<p class="mb-2">本站内容由网络爬虫自动抓取。本站不储存、复制、传播任何文件,仅作个人公益学习,请在获取后24小内删除!!!</p>
|
||||
<p>© 2025 网盘资源管理系统 By 小七</p>
|
||||
<p>{{ systemConfig?.copyright || '© 2025 网盘资源管理系统 By 小七' }}</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -213,8 +228,77 @@ const store = useResourceStore()
|
||||
const userStore = useUserStore()
|
||||
const { resources, categories, stats, loading } = storeToRefs(store)
|
||||
|
||||
// 响应式数据
|
||||
const searchQuery = ref('')
|
||||
const selectedPlatform = ref('')
|
||||
const authInitialized = ref(false) // 添加认证状态初始化标志
|
||||
const pageLoading = ref(true) // 添加页面加载状态
|
||||
const systemConfig = ref<SystemConfig | null>(null) // 添加系统配置状态
|
||||
|
||||
// 动态SEO配置
|
||||
const seoConfig = computed(() => ({
|
||||
title: systemConfig.value?.site_title || '网盘资源管理系统',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: systemConfig.value?.site_description || '专业的网盘资源管理系统'
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
content: systemConfig.value?.keywords || '网盘,资源管理,文件分享'
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
content: systemConfig.value?.author || '系统管理员'
|
||||
},
|
||||
{
|
||||
name: 'copyright',
|
||||
content: systemConfig.value?.copyright || '© 2024 网盘资源管理系统'
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
// 页面元数据 - 使用watchEffect来避免组件卸载时的错误
|
||||
watchEffect(() => {
|
||||
if (systemConfig.value) {
|
||||
useHead({
|
||||
title: systemConfig.value.site_title || '网盘资源管理系统',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: systemConfig.value.site_description || '专业的网盘资源管理系统'
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
content: systemConfig.value.keywords || '网盘,资源管理,文件分享'
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
content: systemConfig.value.author || '系统管理员'
|
||||
},
|
||||
{
|
||||
name: 'copyright',
|
||||
content: systemConfig.value.copyright || '© 2024 网盘资源管理系统'
|
||||
}
|
||||
]
|
||||
})
|
||||
} else {
|
||||
// 默认SEO配置
|
||||
useHead({
|
||||
title: '网盘资源管理系统',
|
||||
meta: [
|
||||
{ name: 'description', content: '专业的网盘资源管理系统' },
|
||||
{ name: 'keywords', content: '网盘,资源管理,文件分享' },
|
||||
{ name: 'author', content: '系统管理员' },
|
||||
{ name: 'copyright', content: '© 2024 网盘资源管理系统' }
|
||||
]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// API
|
||||
const { getSystemConfig } = useSystemConfigApi()
|
||||
|
||||
// const showAddResourceModal = ref(false)
|
||||
const editingResource = ref(null)
|
||||
const currentPage = ref(1)
|
||||
@@ -250,6 +334,21 @@ interface ExtendedResource {
|
||||
showLink?: boolean
|
||||
}
|
||||
|
||||
interface SystemConfig {
|
||||
id: number
|
||||
site_title: string
|
||||
site_description: string
|
||||
keywords: string
|
||||
author: string
|
||||
copyright: string
|
||||
auto_process_ready_resources: boolean
|
||||
auto_process_interval: number
|
||||
page_size: number
|
||||
maintenance_mode: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
const platforms = ref<Platform[]>([])
|
||||
const todayUpdates = ref(0)
|
||||
|
||||
@@ -262,18 +361,50 @@ const debounceSearch = () => {
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 获取系统配置
|
||||
const fetchSystemConfig = async () => {
|
||||
try {
|
||||
const response = await getSystemConfig() as any
|
||||
console.log('首页系统配置响应:', response)
|
||||
if (response && response.success && response.data) {
|
||||
systemConfig.value = response.data
|
||||
} else if (response && response.data) {
|
||||
// 兼容非标准格式
|
||||
systemConfig.value = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取系统配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
onMounted(async () => {
|
||||
console.log('首页 - onMounted 开始')
|
||||
|
||||
// 初始化用户状态
|
||||
userStore.initAuth()
|
||||
authInitialized.value = true // 设置认证状态初始化完成
|
||||
|
||||
await Promise.all([
|
||||
store.fetchResources(),
|
||||
store.fetchCategories(),
|
||||
store.fetchStats(),
|
||||
fetchPlatforms(),
|
||||
])
|
||||
animateCounters()
|
||||
console.log('首页 - authInitialized:', authInitialized.value)
|
||||
console.log('首页 - isAuthenticated:', userStore.isAuthenticated)
|
||||
console.log('首页 - user:', userStore.userInfo)
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
store.fetchResources(),
|
||||
store.fetchCategories(),
|
||||
store.fetchStats(),
|
||||
fetchPlatforms(),
|
||||
fetchSystemConfig(), // 获取系统配置
|
||||
])
|
||||
animateCounters()
|
||||
} catch (error) {
|
||||
console.error('页面数据加载失败:', error)
|
||||
} finally {
|
||||
// 所有数据加载完成后,关闭加载状态
|
||||
pageLoading.value = false
|
||||
console.log('首页 - onMounted 完成')
|
||||
}
|
||||
})
|
||||
|
||||
// 获取平台列表
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
||||
<!-- 全局加载状态 -->
|
||||
<div v-if="pageLoading" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-xl">
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">正在加载...</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">请稍候,正在加载待处理资源</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- 头部 -->
|
||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold mb-4">
|
||||
<NuxtLink to="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">网盘资源管理系统</NuxtLink>
|
||||
</h1>
|
||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
||||
<NuxtLink
|
||||
to="/admin"
|
||||
@@ -13,13 +23,12 @@
|
||||
>
|
||||
<i class="fas fa-arrow-left"></i> 返回
|
||||
</NuxtLink>
|
||||
<button
|
||||
@click="showAddModal = true"
|
||||
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>
|
||||
</nav>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
||||
<NuxtLink to="/" class="text-white hover:text-gray-200 dark:hover:text-gray-300 no-underline">网盘资源管理系统</NuxtLink>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 批量添加模态框 -->
|
||||
@@ -72,7 +81,12 @@ https://pan.baidu.com/s/345678</pre>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-900">待处理资源管理</h2>
|
||||
<button
|
||||
@click="showAddModal = true"
|
||||
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>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="refreshData"
|
||||
@@ -237,6 +251,7 @@ const readyResources = ref<ReadyResource[]>([])
|
||||
const loading = ref(false)
|
||||
const showAddModal = ref(false)
|
||||
const resourceText = ref('')
|
||||
const pageLoading = ref(true) // 添加页面加载状态
|
||||
|
||||
// 分页相关状态
|
||||
const currentPage = ref(1)
|
||||
@@ -414,8 +429,15 @@ const checkUrlSafety = (url: string) => {
|
||||
}
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await fetchData()
|
||||
} catch (error) {
|
||||
console.error('页面初始化失败:', error)
|
||||
} finally {
|
||||
// 数据加载完成后,关闭加载状态
|
||||
pageLoading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
353
web/pages/system-config.vue
Normal file
353
web/pages/system-config.vue
Normal file
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100 p-3 sm:p-5">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- 头部 -->
|
||||
<div class="bg-slate-800 dark:bg-gray-800 text-white dark:text-gray-100 rounded-lg shadow-lg p-4 sm:p-8 mb-4 sm:mb-8 text-center flex items-center">
|
||||
<nav class="mt-4 flex flex-col sm:flex-row justify-center gap-2 sm:gap-4">
|
||||
<NuxtLink
|
||||
to="/admin"
|
||||
class="w-full sm:w-auto px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors text-center flex items-center justify-center gap-2"
|
||||
>
|
||||
<i class="fas fa-arrow-left"></i> 返回
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold">
|
||||
{{ systemConfig?.site_title ? `${systemConfig.site_title} - 系统配置` : '系统配置' }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
||||
<form @submit.prevent="saveConfig" class="space-y-6">
|
||||
<!-- SEO 配置 -->
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-search text-blue-600"></i>
|
||||
SEO 配置
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
<input
|
||||
v-model="config.siteTitle"
|
||||
type="text"
|
||||
required
|
||||
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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
网站描述
|
||||
</label>
|
||||
<input
|
||||
v-model="config.siteDescription"
|
||||
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 class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
关键词 (用逗号分隔)
|
||||
</label>
|
||||
<input
|
||||
v-model="config.keywords"
|
||||
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>
|
||||
<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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
版权信息
|
||||
</label>
|
||||
<input
|
||||
v-model="config.copyright"
|
||||
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="© 2024 网盘资源管理系统"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动处理配置 -->
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 pb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-cogs text-green-600"></i>
|
||||
自动处理配置
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 待处理资源自动处理 -->
|
||||
<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.autoProcessReadyResources"
|
||||
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-blue-300 dark:peer-focus:ring-blue-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-blue-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动处理间隔 -->
|
||||
<div v-if="config.autoProcessReadyResources" class="ml-6">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
自动处理间隔 (分钟)
|
||||
</label>
|
||||
<input
|
||||
v-model.number="config.autoProcessInterval"
|
||||
type="number"
|
||||
min="1"
|
||||
max="1440"
|
||||
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="30"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
建议设置 5-60 分钟,避免过于频繁的处理
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他配置 -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-info-circle text-purple-600"></i>
|
||||
其他配置
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div class="flex justify-end space-x-4 pt-6">
|
||||
<button
|
||||
type="button"
|
||||
@click="resetForm"
|
||||
class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
重置
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="loading"
|
||||
class="px-6 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<i v-if="loading" class="fas fa-spinner fa-spin mr-2"></i>
|
||||
{{ loading ? '保存中...' : '保存配置' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// API
|
||||
const { getSystemConfig, updateSystemConfig } = useSystemConfigApi()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const config = ref({
|
||||
// SEO 配置
|
||||
siteTitle: '网盘资源管理系统',
|
||||
siteDescription: '专业的网盘资源管理系统',
|
||||
keywords: '网盘,资源管理,文件分享',
|
||||
author: '系统管理员',
|
||||
copyright: '© 2024 网盘资源管理系统',
|
||||
|
||||
// 自动处理配置
|
||||
autoProcessReadyResources: false,
|
||||
autoProcessInterval: 30,
|
||||
|
||||
// 其他配置
|
||||
pageSize: 100,
|
||||
maintenanceMode: false
|
||||
})
|
||||
|
||||
// 系统配置状态(用于SEO)
|
||||
const systemConfig = ref(null)
|
||||
|
||||
// 页面元数据 - 移到变量声明之后
|
||||
useHead({
|
||||
title: () => systemConfig.value?.site_title ? `${systemConfig.value.site_title} - 系统配置` : '系统配置 - 网盘资源管理系统',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: () => systemConfig.value?.site_description || '系统配置管理页面'
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
content: () => systemConfig.value?.keywords || '系统配置,管理'
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
content: () => systemConfig.value?.author || '系统管理员'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 加载配置
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await getSystemConfig()
|
||||
console.log('系统配置响应:', response)
|
||||
if (response && response.success && response.data) {
|
||||
config.value = {
|
||||
siteTitle: response.data.site_title || '网盘资源管理系统',
|
||||
siteDescription: response.data.site_description || '专业的网盘资源管理系统',
|
||||
keywords: response.data.keywords || '网盘,资源管理,文件分享',
|
||||
author: response.data.author || '系统管理员',
|
||||
copyright: response.data.copyright || '© 2024 网盘资源管理系统',
|
||||
autoProcessReadyResources: response.data.auto_process_ready_resources || false,
|
||||
autoProcessInterval: response.data.auto_process_interval || 30,
|
||||
pageSize: response.data.page_size || 100,
|
||||
maintenanceMode: response.data.maintenance_mode || false
|
||||
}
|
||||
systemConfig.value = response.data // 更新系统配置状态
|
||||
} else if (response && response.data) {
|
||||
// 兼容非标准格式
|
||||
config.value = {
|
||||
siteTitle: response.data.site_title || '网盘资源管理系统',
|
||||
siteDescription: response.data.site_description || '专业的网盘资源管理系统',
|
||||
keywords: response.data.keywords || '网盘,资源管理,文件分享',
|
||||
author: response.data.author || '系统管理员',
|
||||
copyright: response.data.copyright || '© 2024 网盘资源管理系统',
|
||||
autoProcessReadyResources: response.data.auto_process_ready_resources || false,
|
||||
autoProcessInterval: response.data.auto_process_interval || 30,
|
||||
pageSize: response.data.page_size || 100,
|
||||
maintenanceMode: response.data.maintenance_mode || false
|
||||
}
|
||||
systemConfig.value = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error)
|
||||
// 显示错误提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
const requestData = {
|
||||
site_title: config.value.siteTitle,
|
||||
site_description: config.value.siteDescription,
|
||||
keywords: config.value.keywords,
|
||||
author: config.value.author,
|
||||
copyright: config.value.copyright,
|
||||
auto_process_ready_resources: config.value.autoProcessReadyResources,
|
||||
auto_process_interval: config.value.autoProcessInterval,
|
||||
page_size: config.value.pageSize,
|
||||
maintenance_mode: config.value.maintenanceMode
|
||||
}
|
||||
|
||||
const response = await updateSystemConfig(requestData)
|
||||
if (response.success) {
|
||||
alert('配置保存成功!')
|
||||
} else {
|
||||
alert('保存配置失败:' + (response.message || '未知错误'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
alert('保存配置失败,请重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
if (confirm('确定要重置所有配置吗?')) {
|
||||
loadConfig()
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取配置
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
</script>
|
||||
134
web/pages/test-auth.vue
Normal file
134
web/pages/test-auth.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 p-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-2xl font-bold mb-6">登录状态测试</h1>
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold mb-4">当前状态</h2>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between">
|
||||
<span>是否已认证:</span>
|
||||
<span :class="userStore.isAuthenticated ? 'text-green-600' : 'text-red-600'">
|
||||
{{ userStore.isAuthenticated ? '是' : '否' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>用户名:</span>
|
||||
<span>{{ userStore.userInfo?.username || '未登录' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>角色:</span>
|
||||
<span>{{ userStore.userInfo?.role || '未登录' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Token:</span>
|
||||
<span>{{ userStore.token ? '存在' : '不存在' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold mb-4">localStorage 状态</h2>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between">
|
||||
<span>Token:</span>
|
||||
<span>{{ localStorageToken ? '存在' : '不存在' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>用户信息:</span>
|
||||
<span>{{ localStorageUser ? '存在' : '不存在' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold mb-4">操作</h2>
|
||||
<div class="space-y-4">
|
||||
<button
|
||||
@click="initAuth"
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||
>
|
||||
重新初始化认证状态
|
||||
</button>
|
||||
<button
|
||||
@click="clearStorage"
|
||||
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 ml-2"
|
||||
>
|
||||
清除localStorage
|
||||
</button>
|
||||
<button
|
||||
@click="refreshPage"
|
||||
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 ml-2"
|
||||
>
|
||||
刷新页面
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h2 class="text-lg font-semibold mb-4">调试信息</h2>
|
||||
<pre class="bg-gray-100 dark:bg-gray-700 p-4 rounded text-sm overflow-auto">{{ debugInfo }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const userStore = useUserStore()
|
||||
|
||||
const localStorageToken = ref('')
|
||||
const localStorageUser = ref('')
|
||||
const debugInfo = ref('')
|
||||
|
||||
// 检查localStorage
|
||||
const checkLocalStorage = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorageToken.value = localStorage.getItem('token') ? '存在' : '不存在'
|
||||
localStorageUser.value = localStorage.getItem('user') ? '存在' : '不存在'
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化认证状态
|
||||
const initAuth = () => {
|
||||
userStore.initAuth()
|
||||
checkLocalStorage()
|
||||
updateDebugInfo()
|
||||
}
|
||||
|
||||
// 清除localStorage
|
||||
const clearStorage = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
checkLocalStorage()
|
||||
updateDebugInfo()
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新页面
|
||||
const refreshPage = () => {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
// 更新调试信息
|
||||
const updateDebugInfo = () => {
|
||||
debugInfo.value = JSON.stringify({
|
||||
store: {
|
||||
isAuthenticated: userStore.isAuthenticated,
|
||||
user: userStore.userInfo,
|
||||
token: userStore.token ? '存在' : '不存在'
|
||||
},
|
||||
localStorage: {
|
||||
token: localStorageToken.value,
|
||||
user: localStorageUser.value
|
||||
}
|
||||
}, null, 2)
|
||||
}
|
||||
|
||||
// 页面加载时检查状态
|
||||
onMounted(() => {
|
||||
userStore.initAuth()
|
||||
checkLocalStorage()
|
||||
updateDebugInfo()
|
||||
})
|
||||
</script>
|
||||
@@ -46,15 +46,21 @@ export const useUserStore = defineStore('user', {
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('token')
|
||||
const userStr = localStorage.getItem('user')
|
||||
console.log('initAuth - token:', token ? 'exists' : 'not found')
|
||||
console.log('initAuth - userStr:', userStr ? 'exists' : 'not found')
|
||||
|
||||
if (token && userStr) {
|
||||
try {
|
||||
this.token = token
|
||||
this.user = JSON.parse(userStr)
|
||||
this.isAuthenticated = true
|
||||
console.log('initAuth - 状态恢复成功:', this.user?.username)
|
||||
} catch (error) {
|
||||
console.error('解析用户信息失败:', error)
|
||||
this.logout()
|
||||
}
|
||||
} else {
|
||||
console.log('initAuth - 没有找到有效的登录信息')
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -66,19 +72,29 @@ export const useUserStore = defineStore('user', {
|
||||
const authApi = useAuthApi()
|
||||
const response = await authApi.login(credentials)
|
||||
|
||||
if (response.token) {
|
||||
this.token = response.token
|
||||
this.user = response.user
|
||||
this.isAuthenticated = true
|
||||
|
||||
// 保存到localStorage
|
||||
localStorage.setItem('token', response.token)
|
||||
localStorage.setItem('user', JSON.stringify(response.user))
|
||||
|
||||
return { success: true }
|
||||
} else {
|
||||
return { success: false, message: '登录失败,服务器未返回有效令牌' }
|
||||
console.log('login - 响应:', response)
|
||||
|
||||
// 处理标准化的响应格式
|
||||
if (response.success && response.data) {
|
||||
const { token, user } = response.data
|
||||
if (token && user) {
|
||||
this.token = token
|
||||
this.user = user
|
||||
this.isAuthenticated = true
|
||||
|
||||
// 保存到localStorage
|
||||
localStorage.setItem('token', token)
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
|
||||
console.log('login - 状态保存成功:', user.username)
|
||||
console.log('login - localStorage token:', localStorage.getItem('token') ? 'saved' : 'not saved')
|
||||
console.log('login - localStorage user:', localStorage.getItem('user') ? 'saved' : 'not saved')
|
||||
|
||||
return { success: true }
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false, message: '登录失败,服务器未返回有效数据' }
|
||||
} catch (error: any) {
|
||||
console.error('登录错误:', error)
|
||||
// 处理HTTP错误响应
|
||||
|
||||
Reference in New Issue
Block a user