mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: 首页优化
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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('无搜索参数,跳过统计记录')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user