From 1fc6460ae0b77ea413a8b6f5bfc3ed0a84613b16 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 15 Aug 2025 00:01:18 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dvditor=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E7=AB=AF=E8=B4=B4=E9=A1=B6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend_nuxt/utils/vditor.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend_nuxt/utils/vditor.js b/frontend_nuxt/utils/vditor.js index c38aed7b1..11adebc5d 100644 --- a/frontend_nuxt/utils/vditor.js +++ b/frontend_nuxt/utils/vditor.js @@ -2,6 +2,7 @@ import Vditor from 'vditor' import { getToken, authState } from './auth' import { searchUsers, fetchFollowings, fetchAdmins } from './user' import { tiebaEmoji } from './tiebaEmoji' +import { useIsMobile } from './screen' export function getEditorTheme() { return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'classic' @@ -33,7 +34,8 @@ export function createVditor(editorId, options = {}) { return searchUsers(value) } - const isMobile = window.innerWidth <= 768 + const isMobile = useIsMobile() + const toolbar = isMobile ? ['emoji', 'upload'] : [ @@ -164,7 +166,7 @@ export function createVditor(editorId, options = {}) { // } // } // }, - toolbarConfig: { pin: true }, + toolbarConfig: { pin: isMobile ? false : true }, cache: { enable: false }, input, after, From 2cf89e480293cdc93a2a41356f79f3b603254b22 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 15 Aug 2025 00:12:06 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20ssr=20=E6=B0=B4=E5=90=88=E9=87=87?= =?UTF-8?q?=E7=94=A8useAsyncData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/openisle/config/SecurityConfig.java | 4 +- frontend_nuxt/components/MenuComponent.vue | 54 +++++++------ frontend_nuxt/pages/posts/[id]/index.vue | 81 ++++++++++--------- 3 files changed, 73 insertions(+), 66 deletions(-) diff --git a/backend/src/main/java/com/openisle/config/SecurityConfig.java b/backend/src/main/java/com/openisle/config/SecurityConfig.java index 7701d3df7..083a95984 100644 --- a/backend/src/main/java/com/openisle/config/SecurityConfig.java +++ b/backend/src/main/java/com/openisle/config/SecurityConfig.java @@ -81,8 +81,8 @@ public class SecurityConfig { "http://localhost", "http://30.211.97.238:3000", "http://30.211.97.238", - "http://192.168.7.70", - "http://192.168.7.70:8080", + "http://192.168.7.98", + "http://192.168.7.98:3000", websiteUrl, websiteUrl.replace("://www.", "://") )); diff --git a/frontend_nuxt/components/MenuComponent.vue b/frontend_nuxt/components/MenuComponent.vue index bae242696..04fa3a801 100644 --- a/frontend_nuxt/components/MenuComponent.vue +++ b/frontend_nuxt/components/MenuComponent.vue @@ -128,41 +128,46 @@ import { ref, computed, watch, onMounted } from 'vue' import { themeState, cycleTheme, ThemeMode } from '~/utils/theme' import { authState } from '~/utils/auth' import { fetchUnreadCount, notificationState } from '~/utils/notification' + const config = useRuntimeConfig() const API_BASE_URL = config.public.apiBaseUrl const props = defineProps({ - visible: { - type: Boolean, - default: true, - }, + visible: { type: Boolean, default: true }, }) - const emit = defineEmits(['item-click']) const categoryOpen = ref(true) const tagOpen = ref(true) -const isLoadingCategory = ref(false) -const isLoadingTag = ref(false) -const categoryData = ref([]) -const tagData = ref([]) -const fetchCategoryData = async () => { - isLoadingCategory.value = true - const res = await fetch(`${API_BASE_URL}/api/categories`) - const data = await res.json() - categoryData.value = data - isLoadingCategory.value = false -} +/** ✅ 用 useAsyncData 替换原生 fetch,避免 SSR+CSR 二次请求 */ +const { + data: categoryData, + pending: isLoadingCategory, + error: categoryError, +} = await useAsyncData( + // 稳定 key:避免 hydration 期误判 + 'menu:categories', + () => $fetch(`${API_BASE_URL}/api/categories`), + { + server: true, // SSR 预取 + default: () => [], // 初始默认值,减少空判断 + // 5 分钟内复用缓存,避免路由往返重复请求 + staleTime: 5 * 60 * 1000, + }, +) -const fetchTagData = async () => { - isLoadingTag.value = true - const res = await fetch(`${API_BASE_URL}/api/tags?limit=10`) - const data = await res.json() - tagData.value = data - isLoadingTag.value = false -} +const { + data: tagData, + pending: isLoadingTag, + error: tagError, +} = await useAsyncData('menu:tags', () => $fetch(`${API_BASE_URL}/api/tags?limit=10`), { + server: true, + default: () => [], + staleTime: 5 * 60 * 1000, +}) +/** 其余逻辑保持不变 */ const iconClass = computed(() => { switch (themeState.mode) { case ThemeMode.DARK: @@ -188,6 +193,7 @@ const updateCount = async () => { onMounted(async () => { await updateCount() + // 登录态变化时再拉一次未读数;与 useAsyncData 无关 watch(() => authState.loggedIn, updateCount) }) @@ -211,8 +217,6 @@ const gotoTag = (t) => { navigateTo({ path: '/', query: { tags: value } }, { replace: true }) handleItemClick() } - -await Promise.all([fetchCategoryData(), fetchTagData()])