mirror of https://github.com/halo-dev/halo
fix: refactor the theme and post preview feature to solve the session expiration issue (#5518)
#### What type of PR is this? /area ui /kind bug /milestone 2.14.x #### What this PR does / why we need it: 重构文章预览和主题预览的页面加载方式,尝试解决在某些情况下打开预览导致登录 session 失效的问题。 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/4047 #### Special notes for your reviewer: 需要测试: 1. 主题预览中的各个功能是否正常。 2. 文章预览功能是否正常。 #### Does this PR introduce a user-facing change? ```release-note 修复在某些情况下,打开文章预览或主题预览之后导致登录失效的问题。 ```pull/5531/head
parent
fa26516ecc
commit
47d837952b
|
@ -433,10 +433,10 @@ async function handleUploadImage(file: File, options?: AxiosRequestConfig) {
|
|||
/>
|
||||
|
||||
<UrlPreviewModal
|
||||
v-if="isUpdateMode"
|
||||
v-model:visible="previewModal"
|
||||
v-if="previewModal"
|
||||
:title="formState.page.spec.title"
|
||||
:url="`/preview/singlepages/${formState.page.metadata.name}`"
|
||||
@close="previewModal = false"
|
||||
/>
|
||||
|
||||
<VPageHeader :title="$t('core.page.title')">
|
||||
|
|
|
@ -461,10 +461,10 @@ async function handleUploadImage(file: File, options?: AxiosRequestConfig) {
|
|||
/>
|
||||
|
||||
<UrlPreviewModal
|
||||
v-if="isUpdateMode"
|
||||
v-model:visible="previewModal"
|
||||
v-if="previewModal"
|
||||
:title="formState.post.spec.title"
|
||||
:url="`/preview/posts/${formState.post.metadata.name}`"
|
||||
@close="previewModal = false"
|
||||
/>
|
||||
|
||||
<VPageHeader :title="$t('core.post.title')">
|
||||
|
|
|
@ -229,9 +229,10 @@ const actions: Action[] = [
|
|||
</OverlayScrollbarsComponent>
|
||||
</VCard>
|
||||
<ThemePreviewModal
|
||||
v-model:visible="themePreviewVisible"
|
||||
v-if="themePreviewVisible"
|
||||
:title="
|
||||
$t('core.dashboard.widgets.presets.quicklink.actions.view_site.title')
|
||||
"
|
||||
@close="themePreviewVisible = false"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -106,8 +106,9 @@ const handleOpenPreview = (theme: Theme) => {
|
|||
</ul>
|
||||
</Transition>
|
||||
<ThemePreviewModal
|
||||
v-model:visible="previewVisible"
|
||||
v-if="previewVisible"
|
||||
:theme="selectedPreviewTheme"
|
||||
@close="previewVisible = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import ThemePreviewListItem from "./ThemePreviewListItem.vue";
|
||||
import { useSettingFormConvert } from "@console/composables/use-setting-form";
|
||||
import { useThemeStore } from "@console/stores/theme";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { apiClient, axiosInstance } from "@/utils/api-client";
|
||||
import type {
|
||||
ConfigMap,
|
||||
Setting,
|
||||
|
@ -22,28 +22,27 @@ import {
|
|||
IconTablet,
|
||||
IconRefreshLine,
|
||||
Toast,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, markRaw, ref, toRaw, watch } from "vue";
|
||||
import { computed, markRaw, ref, toRaw } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
theme?: Theme;
|
||||
}>(),
|
||||
{
|
||||
visible: false,
|
||||
title: undefined,
|
||||
theme: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:visible", visible: boolean): void;
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
|
@ -56,7 +55,6 @@ interface SettingTab {
|
|||
|
||||
const { activatedTheme } = storeToRefs(useThemeStore());
|
||||
|
||||
const previewFrame = ref<HTMLIFrameElement | null>(null);
|
||||
const themesVisible = ref(false);
|
||||
const switching = ref(false);
|
||||
const selectedTheme = ref<Theme>();
|
||||
|
@ -71,29 +69,11 @@ const { data: themes } = useQuery<Theme[]>({
|
|||
});
|
||||
return data.items;
|
||||
},
|
||||
enabled: computed(() => props.visible),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
selectedTheme.value = props.theme || activatedTheme?.value;
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
themesVisible.value = false;
|
||||
settingsVisible.value = false;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
selectedTheme.value = toRaw(props.theme) || toRaw(activatedTheme?.value);
|
||||
});
|
||||
|
||||
const handleOpenThemes = () => {
|
||||
settingsVisible.value = false;
|
||||
|
@ -122,6 +102,26 @@ const modalTitle = computed(() => {
|
|||
});
|
||||
});
|
||||
|
||||
const {
|
||||
data: previewHTML,
|
||||
isLoading,
|
||||
refetch: refetchPreviewHTML,
|
||||
} = useQuery({
|
||||
queryKey: ["site-preview", previewUrl],
|
||||
queryFn: async () => {
|
||||
const { data } = await axiosInstance.get(previewUrl.value, {
|
||||
headers: {
|
||||
Accept: "text/html",
|
||||
"Cache-Control": "no-cache",
|
||||
Pragma: "no-cache",
|
||||
Expires: "0",
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
enabled: computed(() => !!previewUrl.value),
|
||||
});
|
||||
|
||||
// theme settings
|
||||
const saving = ref(false);
|
||||
const settingTabs = ref<SettingTab[]>([] as SettingTab[]);
|
||||
|
@ -150,9 +150,7 @@ const { data: setting } = useQuery<Setting>({
|
|||
|
||||
activeSettingTab.value = settingTabs.value[0].id;
|
||||
},
|
||||
enabled: computed(
|
||||
() => props.visible && !!selectedTheme.value?.spec.settingName
|
||||
),
|
||||
enabled: computed(() => !!selectedTheme.value?.spec.settingName),
|
||||
});
|
||||
|
||||
const { data: configMap, refetch: handleFetchConfigMap } = useQuery<ConfigMap>({
|
||||
|
@ -195,7 +193,7 @@ const handleSaveConfigMap = async () => {
|
|||
|
||||
saving.value = false;
|
||||
|
||||
handleRefresh();
|
||||
refetchPreviewHTML();
|
||||
};
|
||||
|
||||
const handleOpenSettings = (theme?: Theme) => {
|
||||
|
@ -206,10 +204,6 @@ const handleOpenSettings = (theme?: Theme) => {
|
|||
settingsVisible.value = !settingsVisible.value;
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
previewFrame.value?.contentWindow?.location.reload();
|
||||
};
|
||||
|
||||
// mock devices
|
||||
const mockDevices = [
|
||||
{
|
||||
|
@ -241,11 +235,10 @@ const iframeClasses = computed(() => {
|
|||
<template>
|
||||
<VModal
|
||||
:body-class="['!p-0']"
|
||||
:visible="visible"
|
||||
fullscreen
|
||||
:title="modalTitle"
|
||||
:mount-to-body="true"
|
||||
@update:visible="onVisibleChange"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<template #center>
|
||||
<!-- TODO: Reactor VTabbar component to support icon prop -->
|
||||
|
@ -281,7 +274,7 @@ const iframeClasses = computed(() => {
|
|||
content: $t('core.common.buttons.refresh'),
|
||||
delay: 300,
|
||||
}"
|
||||
@click="handleRefresh"
|
||||
@click="refetchPreviewHTML()"
|
||||
>
|
||||
<IconRefreshLine />
|
||||
</span>
|
||||
|
@ -432,12 +425,12 @@ const iframeClasses = computed(() => {
|
|||
<div
|
||||
class="flex h-full flex-1 items-center justify-center transition-all duration-300"
|
||||
>
|
||||
<VLoading v-if="isLoading" />
|
||||
<iframe
|
||||
v-if="visible"
|
||||
ref="previewFrame"
|
||||
v-else
|
||||
class="border-none transition-all duration-500"
|
||||
:class="iframeClasses"
|
||||
:src="previewUrl"
|
||||
:srcdoc="previewHTML"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -269,6 +269,10 @@ onMounted(() => {
|
|||
</div>
|
||||
|
||||
<ThemeListModal v-model:visible="themesModal" @select="onSelectTheme" />
|
||||
<ThemePreviewModal v-model:visible="previewModal" :theme="selectedTheme" />
|
||||
<ThemePreviewModal
|
||||
v-if="previewModal"
|
||||
:theme="selectedTheme"
|
||||
@close="previewModal = false"
|
||||
/>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { axiosInstance } from "@/utils/api-client";
|
||||
import {
|
||||
VModal,
|
||||
IconLink,
|
||||
|
@ -6,34 +7,29 @@ import {
|
|||
IconComputer,
|
||||
IconTablet,
|
||||
IconPhone,
|
||||
VLoading,
|
||||
} from "@halo-dev/components";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { toRefs } from "vue";
|
||||
import { computed, markRaw, ref } from "vue";
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
url?: string;
|
||||
}>(),
|
||||
{
|
||||
visible: false,
|
||||
title: undefined,
|
||||
url: "",
|
||||
}
|
||||
);
|
||||
|
||||
const { url } = toRefs(props);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:visible", visible: boolean): void;
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
|
||||
const mockDevices = [
|
||||
{
|
||||
id: "desktop",
|
||||
|
@ -59,15 +55,30 @@ const iframeClasses = computed(() => {
|
|||
}
|
||||
return "w-96 h-[50rem] ring-2 rounded ring-gray-300";
|
||||
});
|
||||
|
||||
const { data: html, isLoading } = useQuery({
|
||||
queryKey: ["url-preview", url],
|
||||
queryFn: async () => {
|
||||
const { data } = await axiosInstance.get(url.value, {
|
||||
headers: {
|
||||
Accept: "text/html",
|
||||
"Cache-Control": "no-cache",
|
||||
Pragma: "no-cache",
|
||||
Expires: "0",
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
enabled: computed(() => !!url.value),
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
:body-class="['!p-0']"
|
||||
:visible="visible"
|
||||
fullscreen
|
||||
:title="title"
|
||||
:layer-closable="true"
|
||||
@update:visible="onVisibleChange"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<template #center>
|
||||
<!-- TODO: Reactor VTabbar component to support icon prop -->
|
||||
|
@ -86,11 +97,12 @@ const iframeClasses = computed(() => {
|
|||
</span>
|
||||
</template>
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<VLoading v-if="isLoading" />
|
||||
<iframe
|
||||
v-if="visible"
|
||||
v-else
|
||||
class="border-none transition-all duration-500"
|
||||
:class="iframeClasses"
|
||||
:src="url"
|
||||
:srcdoc="html"
|
||||
></iframe>
|
||||
</div>
|
||||
</VModal>
|
||||
|
|
|
@ -273,4 +273,4 @@ function setupApiClient(axios: AxiosInstance) {
|
|||
};
|
||||
}
|
||||
|
||||
export { apiClient };
|
||||
export { apiClient, axiosInstance };
|
||||
|
|
Loading…
Reference in New Issue