update: 首页优化

This commit is contained in:
ctwj
2025-08-19 01:11:09 +08:00
parent aa7d6ea2fe
commit cbf673126e
10 changed files with 175 additions and 74 deletions

View File

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

View File

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

View File

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

View File

@@ -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()
})
</script>

View File

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

View File

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

View File

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

View File

@@ -47,21 +47,23 @@
<i class="fas fa-book text-xs"></i> API文档
</n-button>
</NuxtLink>
<NuxtLink v-if="authInitialized && !userStore.isAuthenticated" to="/login" class="sm:flex">
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
<i class="fas fa-sign-in-alt text-xs"></i> 登录
</n-button>
</NuxtLink>
<NuxtLink v-if="authInitialized && userStore.isAuthenticated && userStore.user?.role === 'admin'" to="/admin" class="hidden sm:flex">
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
<i class="fas fa-user-shield text-xs"></i> 管理后台
</n-button>
</NuxtLink>
<NuxtLink v-if="authInitialized && userStore.isAuthenticated && userStore.user?.role !== 'admin'" to="/user" class="hidden sm:flex">
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
<i class="fas fa-user text-xs"></i> 用户中心
</n-button>
</NuxtLink>
<ClientOnly>
<NuxtLink v-if="authInitialized && !userStore.isAuthenticated" to="/login" class="sm:flex">
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
<i class="fas fa-sign-in-alt text-xs"></i> 登录
</n-button>
</NuxtLink>
<NuxtLink v-if="authInitialized && userStore.isAuthenticated && userStore.user?.role === 'admin'" to="/admin" class="hidden sm:flex">
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
<i class="fas fa-user-shield text-xs"></i> 管理后台
</n-button>
</NuxtLink>
<NuxtLink v-if="authInitialized && userStore.isAuthenticated && userStore.user?.role !== 'admin'" to="/user" class="hidden sm:flex">
<n-button size="tiny" type="tertiary" round ghost class="!px-2 !py-1 !text-xs !text-white dark:!text-white !border-white/30 hover:!border-white">
<i class="fas fa-user text-xs"></i> 用户中心
</n-button>
</NuxtLink>
</ClientOnly>
</nav>
</div>
@@ -251,9 +253,11 @@ const router = useRouter()
// 响应式数据
const showLinkModal = ref(false)
const selectedResource = ref<any>(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('无搜索参数,跳过统计记录')
}
})

View File

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

View File

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