diff --git a/docker-compose.yml b/docker-compose.yml index c6b2c5e..5b4c48c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,8 +40,8 @@ services: frontend: image: ctwj/urldb-frontend:1.2.3 environment: + NODE_ENV: production NUXT_PUBLIC_API_SERVER: http://backend:8080/api - NUXT_PUBLIC_API_CLIENT: /api depends_on: - backend networks: diff --git a/web/components/Admin/TransferredList.vue b/web/components/Admin/TransferredList.vue index 33dd44f..39a3222 100644 --- a/web/components/Admin/TransferredList.vue +++ b/web/components/Admin/TransferredList.vue @@ -178,10 +178,19 @@ const fetchTransferredResources = async () => { console.log('结果结构:', Object.keys(result || {})) if (result && result.data) { - console.log('使用 resources 格式,数量:', result.data.length) - resources.value = result.data - total.value = result.total || 0 - pagination.itemCount = result.total || 0 + // 处理嵌套的data结构:{data: {data: [...], total: ...}} + if (result.data.data && Array.isArray(result.data.data)) { + console.log('使用嵌套data格式,数量:', result.data.data.length) + resources.value = result.data.data + total.value = result.data.total || 0 + pagination.itemCount = result.data.total || 0 + } else { + // 处理直接的data结构:{data: [...], total: ...} + console.log('使用直接data格式,数量:', result.data.length) + resources.value = result.data + total.value = result.total || 0 + pagination.itemCount = result.total || 0 + } } else if (Array.isArray(result)) { console.log('使用数组格式,数量:', result.length) resources.value = result diff --git a/web/components/Admin/UntransferredList.vue b/web/components/Admin/UntransferredList.vue index 5df7c80..e666adf 100644 --- a/web/components/Admin/UntransferredList.vue +++ b/web/components/Admin/UntransferredList.vue @@ -382,8 +382,15 @@ const fetchUntransferredResources = async () => { console.log('未转存资源结果:', result) if (result && result.data) { - resources.value = result.data - total.value = result.total || 0 + // 处理嵌套的data结构:{data: {data: [...], total: ...}} + if (result.data.data && Array.isArray(result.data.data)) { + resources.value = result.data.data + total.value = result.data.total || 0 + } else { + // 处理直接的data结构:{data: [...], total: ...} + resources.value = result.data + total.value = result.total || 0 + } } else if (Array.isArray(result)) { resources.value = result total.value = result.length diff --git a/web/layouts/default.vue b/web/layouts/default.vue index 158fda6..fa53529 100644 --- a/web/layouts/default.vue +++ b/web/layouts/default.vue @@ -33,14 +33,26 @@ import { ref, onMounted } from 'vue' const theme = lightTheme const isDark = ref(false) +// 使用 useCookie 来确保服务端和客户端状态一致 +const themeCookie = useCookie('theme', { default: () => 'light' }) + +// 初始化主题状态 +isDark.value = themeCookie.value === 'dark' + const toggleDarkMode = () => { isDark.value = !isDark.value - if (isDark.value) { - document.documentElement.classList.add('dark') - localStorage.setItem('theme', 'dark') - } else { - document.documentElement.classList.remove('dark') - localStorage.setItem('theme', 'light') + const newTheme = isDark.value ? 'dark' : 'light' + + // 更新 cookie + themeCookie.value = newTheme + + // 更新 DOM 类 + if (process.client) { + if (isDark.value) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } } } @@ -94,14 +106,13 @@ const fetchStatsCode = async () => { onMounted(async () => { - // 初始化主题 - if (localStorage.getItem('theme') === 'dark') { + // 初始化主题 - 使用 cookie 而不是 localStorage + if (themeCookie.value === 'dark') { isDark.value = true document.documentElement.classList.add('dark') } // 获取三方统计代码并直接加载 await fetchStatsCode() - }) \ No newline at end of file diff --git a/web/nuxt.config.ts b/web/nuxt.config.ts index d2692cd..2572e1e 100644 --- a/web/nuxt.config.ts +++ b/web/nuxt.config.ts @@ -67,10 +67,10 @@ export default defineNuxtConfig({ }, runtimeConfig: { public: { - // 开发环境:直接访问后端,生产环境:通过 Nginx 反代 - apiBase: process.env.NODE_ENV === 'production' ? '/api' : '/api', - // 服务端:开发环境直接访问,生产环境容器内访问 - apiServer: process.env.NODE_ENV === 'production' ? 'http://backend:8080/api' : '/api' + // 客户端API地址:开发环境通过代理,生产环境通过Nginx + apiBase: '/api', + // 服务端API地址:通过环境变量配置,支持不同部署方式 + apiServer: process.env.NUXT_PUBLIC_API_SERVER || (process.env.NODE_ENV === 'production' ? 'http://backend:8080/api' : '/api') } }, build: { diff --git a/web/pages/admin/resources.vue b/web/pages/admin/resources.vue index e8ac5a2..6d5d5a0 100644 --- a/web/pages/admin/resources.vue +++ b/web/pages/admin/resources.vue @@ -452,8 +452,15 @@ const fetchData = async () => { console.log('返回的资源数量:', response?.data?.length || 0) if (response && response.data) { - resources.value = response.data - total.value = response.total || 0 + // 处理嵌套的data结构:{data: {data: [...], total: ...}} + if (response.data.data && Array.isArray(response.data.data)) { + resources.value = response.data.data + total.value = response.data.total || 0 + } else { + // 处理直接的data结构:{data: [...], total: ...} + resources.value = response.data + total.value = response.total || 0 + } // 清空选择(因为数据已更新) selectedResources.value = [] } else { diff --git a/web/pages/admin/untransferred-resources.vue b/web/pages/admin/untransferred-resources.vue index 3e1d99e..40bf6ca 100644 --- a/web/pages/admin/untransferred-resources.vue +++ b/web/pages/admin/untransferred-resources.vue @@ -344,8 +344,22 @@ const fetchResources = async () => { } const response = await resourceApi.getResources(params) - resources.value = response.resources || [] - total.value = response.total || 0 + // 处理嵌套的data结构:{data: {data: [...], total: ...}} + if (response && response.data && response.data.data && Array.isArray(response.data.data)) { + resources.value = response.data.data + total.value = response.data.total || 0 + } else if (response && response.data && Array.isArray(response.data)) { + // 处理直接的data结构:{data: [...], total: ...} + resources.value = response.data + total.value = response.total || 0 + } else if (response && response.resources) { + // 兼容旧格式 + resources.value = response.resources + total.value = response.total || 0 + } else { + resources.value = [] + total.value = 0 + } } catch (error) { console.error('获取未转存资源失败:', error) notification.error({ diff --git a/web/pages/index.vue b/web/pages/index.vue index 5b50e2b..1066b18 100644 --- a/web/pages/index.vue +++ b/web/pages/index.vue @@ -47,21 +47,23 @@ API文档 - - - 登录 - - - - + + + + 登录 + + + + + @@ -251,9 +253,11 @@ const router = useRouter() // 响应式数据 const showLinkModal = ref(false) const selectedResource = ref(null) -const authInitialized = ref(true) // 在app.vue中已经初始化,这里直接设为true const pageLoading = ref(false) +// 使用 ClientOnly 包装器来处理认证状态 +const authInitialized = ref(false) + // 用户状态管理 const userStore = useUserStore() @@ -324,10 +328,25 @@ watch(systemConfigError, (error) => { }) // 从 SSR 数据中获取值 -const safeResources = computed(() => (resourcesData.value as any)?.data || []) +const safeResources = computed(() => { + const data = resourcesData.value as any + // 处理嵌套的data结构:{data: {data: [...], total: ...}} + if (data?.data?.data && Array.isArray(data.data.data)) { + return data.data.data + } + // 处理直接的data结构:{data: [...], total: ...} + if (data?.data && Array.isArray(data.data)) { + return data.data + } + // 处理直接的数组结构 + if (Array.isArray(data)) { + return data + } + return [] +}) const safeStats = computed(() => (statsData.value as any) || { total_resources: 0, total_categories: 0, total_tags: 0, total_views: 0, today_resources: 0 }) const platforms = computed(() => (platformsData.value as any) || []) -const systemConfig = computed(() => (systemConfigData.value as any).data || { site_title: '老九网盘资源数据库' }) +const systemConfig = computed(() => (systemConfigData.value as any)?.data || { site_title: '老九网盘资源数据库' }) const safeLoading = computed(() => pending.value) @@ -335,6 +354,25 @@ const safeLoading = computed(() => pending.value) const searchQuery = ref(route.query.search as string || '') const selectedPlatform = computed(() => route.query.platform as string || '') +// 记录搜索统计的函数 +const recordSearchStats = (keyword: string) => { + if (!keyword || keyword.trim().length === 0) { + // console.log('搜索关键词为空,跳过统计记录') + return + } + + const trimmedKeyword = keyword.trim() + // console.log('记录搜索统计:', trimmedKeyword) + + // 延迟执行,确保页面完全加载 + setTimeout(() => { + const searchStatsApi = useSearchStatsApi() + searchStatsApi.recordSearch({ keyword: trimmedKeyword }).catch(err => { + console.error('记录搜索统计失败:', err) + }) + }, 0) +} + const handleSearch = () => { const params = new URLSearchParams() if (searchQuery.value) params.set('search', searchQuery.value) @@ -344,20 +382,17 @@ const handleSearch = () => { // 初始化认证状态 onMounted(() => { + // 初始化认证状态 + authInitialized.value = true + animateCounters() - // 页面挂载完成时,如果有搜索关键词,请求 record 接口 + // 页面挂载完成时,如果有搜索关键词,记录搜索统计 if (process.client && route.query.search) { const searchKeyword = route.query.search as string - if (searchKeyword.trim()) { - // 延迟执行,确保页面完全加载 - setTimeout(() => { - const searchStatsApi = useSearchStatsApi() - searchStatsApi.recordSearch({ keyword: searchKeyword }).catch(err => { - console.error('记录搜索统计失败:', err) - }) - }, 0) - } + recordSearchStats(searchKeyword) + } else { + console.log('无搜索参数,跳过统计记录') } }) diff --git a/web/stores/resource.ts b/web/stores/resource.ts index b931b17..fff3147 100644 --- a/web/stores/resource.ts +++ b/web/stores/resource.ts @@ -77,15 +77,21 @@ export const useResourceStore = defineStore('resource', { return } - if (!data.resources) { - console.error('fetchResources - 缺少resources字段:', data) + // 处理嵌套的data结构:{data: {data: [...], total: ...}} + if (data.data && Array.isArray(data.data)) { + this.resources = data.data + this.currentPage = data.page || 1 + this.totalPages = Math.ceil((data.total || 0) / (data.page_size || 100)) + } else if (data.resources && Array.isArray(data.resources)) { + // 兼容旧格式 + this.resources = data.resources + this.currentPage = data.page || 1 + this.totalPages = Math.ceil((data.total || 0) / (data.page_size || 100)) + } else { + console.error('fetchResources - 数据格式不正确:', data) this.resources = [] return } - - this.resources = data.resources || [] - this.currentPage = data.page || 1 - this.totalPages = Math.ceil((data.total || 0) / (data.page_size || 100)) console.log('fetchResources - 设置成功:', { resourcesCount: this.resources.length, currentPage: this.currentPage, diff --git a/web/stores/user.ts b/web/stores/user.ts index 301e591..acad846 100644 --- a/web/stores/user.ts +++ b/web/stores/user.ts @@ -49,23 +49,35 @@ export const useUserStore = defineStore('user', { initAuth() { // 只在客户端执行 if (process.client && 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() + try { + 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 - 没有找到有效的登录信息') + // 确保状态一致 + this.token = null + this.user = null + this.isAuthenticated = false } - } else { - console.log('initAuth - 没有找到有效的登录信息') + } catch (error) { + console.error('初始化认证状态失败:', error) + // 确保状态一致 + this.token = null + this.user = null + this.isAuthenticated = false } } },