feat: 🎉新增 页面切换动画 功能
- 实现了在项目配置中可配置页面切换动画 - 编写 页面切换动画 功能单元测试
This commit is contained in:
@@ -42,6 +42,7 @@ export const STORAGE_KEYS = {
|
|||||||
SHOW_TAGS_VIEW: `${APP_PREFIX}:ui:show_tags_view`,
|
SHOW_TAGS_VIEW: `${APP_PREFIX}:ui:show_tags_view`,
|
||||||
SHOW_APP_LOGO: `${APP_PREFIX}:ui:show_app_logo`,
|
SHOW_APP_LOGO: `${APP_PREFIX}:ui:show_app_logo`,
|
||||||
SHOW_WATERMARK: `${APP_PREFIX}:ui:show_watermark`,
|
SHOW_WATERMARK: `${APP_PREFIX}:ui:show_watermark`,
|
||||||
|
PAGE_SWITCHING_ANIMATION: `${APP_PREFIX}:ui:page_switching_animation`,
|
||||||
ENABLE_AI_ASSISTANT: `${APP_PREFIX}:ui:enable_ai_assistant`,
|
ENABLE_AI_ASSISTANT: `${APP_PREFIX}:ui:enable_ai_assistant`,
|
||||||
LAYOUT: `${APP_PREFIX}:ui:layout`,
|
LAYOUT: `${APP_PREFIX}:ui:layout`,
|
||||||
SIDEBAR_COLOR_SCHEME: `${APP_PREFIX}:ui:sidebar_color_scheme`,
|
SIDEBAR_COLOR_SCHEME: `${APP_PREFIX}:ui:sidebar_color_scheme`,
|
||||||
|
|||||||
@@ -121,3 +121,31 @@ export const enum DeviceEnum {
|
|||||||
*/
|
*/
|
||||||
MOBILE = "mobile",
|
MOBILE = "mobile",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面切换动画枚举
|
||||||
|
*/
|
||||||
|
export const enum PageSwitchingAnimationEnum {
|
||||||
|
/**
|
||||||
|
* 无动画
|
||||||
|
*/
|
||||||
|
NONE = "none",
|
||||||
|
/**
|
||||||
|
* 淡入淡出
|
||||||
|
*/
|
||||||
|
FADE = "fade",
|
||||||
|
/**
|
||||||
|
* 平滑切换
|
||||||
|
*/
|
||||||
|
FADE_SLIDE = "fade-slide",
|
||||||
|
/**
|
||||||
|
* 缩放切换
|
||||||
|
*/
|
||||||
|
FADE_SCALE = "fade-scale",
|
||||||
|
}
|
||||||
|
export const PageSwitchingAnimationOptions: Record<string, OptionItem> = {
|
||||||
|
none: { value: "none", label: "无动画" },
|
||||||
|
fade: { value: "fade", label: "淡入淡出" },
|
||||||
|
"fade-slide": { value: "fade-slide", label: "平滑切换" },
|
||||||
|
"fade-scale": { value: "fade-scale", label: "缩放切换" },
|
||||||
|
};
|
||||||
|
|||||||
@@ -72,6 +72,11 @@
|
|||||||
"showAppLogo": "Show App Logo",
|
"showAppLogo": "Show App Logo",
|
||||||
"sidebarColorScheme": "Sidebar Color Scheme",
|
"sidebarColorScheme": "Sidebar Color Scheme",
|
||||||
"showWatermark": "Show Watermark",
|
"showWatermark": "Show Watermark",
|
||||||
|
"pageSwitchingAnimation": "Page Switching Animation",
|
||||||
|
"none": "None",
|
||||||
|
"fade": "Fade",
|
||||||
|
"fade-slide": "Fade Slide",
|
||||||
|
"fade-scale": "Fade Scale",
|
||||||
"classicBlue": "Classic Blue",
|
"classicBlue": "Classic Blue",
|
||||||
"minimalWhite": "Minimal White",
|
"minimalWhite": "Minimal White",
|
||||||
"copyConfig": "Copy Config",
|
"copyConfig": "Copy Config",
|
||||||
|
|||||||
@@ -75,6 +75,11 @@
|
|||||||
"showTagsView": "显示页签",
|
"showTagsView": "显示页签",
|
||||||
"showAppLogo": "显示Logo",
|
"showAppLogo": "显示Logo",
|
||||||
"showWatermark": "显示水印",
|
"showWatermark": "显示水印",
|
||||||
|
"pageSwitchingAnimation": "页面切换动画",
|
||||||
|
"none": "无动画",
|
||||||
|
"fade": "淡入淡出",
|
||||||
|
"fade-slide": "平滑切换",
|
||||||
|
"fade-scale": "缩放切换",
|
||||||
"classicBlue": "经典蓝",
|
"classicBlue": "经典蓝",
|
||||||
"minimalWhite": "极简白",
|
"minimalWhite": "极简白",
|
||||||
"copyConfig": "复制配置",
|
"copyConfig": "复制配置",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<section class="app-main" :style="{ height: appMainHeight }">
|
<section class="app-main" :style="{ height: appMainHeight }">
|
||||||
<router-view>
|
<router-view>
|
||||||
<template #default="{ Component, route }">
|
<template #default="{ Component, route }">
|
||||||
<transition enter-active-class="animate__animated animate__fadeIn" mode="out-in">
|
<transition :name="transitionName" mode="out-in">
|
||||||
<keep-alive :include="cachedViews">
|
<keep-alive :include="cachedViews">
|
||||||
<component :is="currentComponent(Component, route)" :key="route.fullPath" />
|
<component :is="currentComponent(Component, route)" :key="route.fullPath" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
@@ -25,6 +25,8 @@ import Error404 from "@/views/error/404.vue";
|
|||||||
|
|
||||||
const { cachedViews } = toRefs(useTagsViewStore());
|
const { cachedViews } = toRefs(useTagsViewStore());
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
// 当前组件
|
// 当前组件
|
||||||
const wrapperMap = new Map<string, Component>();
|
const wrapperMap = new Map<string, Component>();
|
||||||
const currentComponent = (component: Component, route: RouteLocationNormalized) => {
|
const currentComponent = (component: Component, route: RouteLocationNormalized) => {
|
||||||
@@ -60,12 +62,17 @@ const currentComponent = (component: Component, route: RouteLocationNormalized)
|
|||||||
};
|
};
|
||||||
|
|
||||||
const appMainHeight = computed(() => {
|
const appMainHeight = computed(() => {
|
||||||
if (useSettingsStore().showTagsView) {
|
if (settingsStore.showTagsView) {
|
||||||
return `calc(100vh - ${variables["navbar-height"]} - ${variables["tags-view-height"]})`;
|
return `calc(100vh - ${variables["navbar-height"]} - ${variables["tags-view-height"]})`;
|
||||||
} else {
|
} else {
|
||||||
return `calc(100vh - ${variables["navbar-height"]})`;
|
return `calc(100vh - ${variables["navbar-height"]})`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 页面切换动画名称
|
||||||
|
const transitionName = computed(() => {
|
||||||
|
return settingsStore.pageSwitchingAnimation ?? "";
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -74,18 +81,42 @@ const appMainHeight = computed(() => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: var(--el-bg-color-page);
|
background-color: var(--el-bg-color-page);
|
||||||
|
|
||||||
/* 布局切换动画优化 */
|
/* fade */
|
||||||
&.animate__animated {
|
.fade-enter-active,
|
||||||
animation-duration: 0.4s;
|
.fade-leave-active {
|
||||||
animation-fill-mode: forwards;
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.animate__fadeOut {
|
/* fade-slide */
|
||||||
animation-timing-function: ease-in;
|
.fade-slide-leave-active,
|
||||||
|
.fade-slide-enter-active {
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.fade-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
.fade-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.animate__fadeIn {
|
/* fade-scale */
|
||||||
animation-timing-function: ease-out;
|
.fade-scale-leave-active,
|
||||||
|
.fade-scale-enter-active {
|
||||||
|
transition: all 0.28s;
|
||||||
|
}
|
||||||
|
.fade-scale-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
.fade-scale-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -49,6 +49,18 @@
|
|||||||
<el-switch v-model="settingsStore.showWatermark" />
|
<el-switch v-model="settingsStore.showWatermark" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="config-item flex-x-between">
|
||||||
|
<span class="text-xs">{{ t("settings.pageSwitchingAnimation") }}</span>
|
||||||
|
<el-select v-model="settingsStore.pageSwitchingAnimation" style="width: 150px">
|
||||||
|
<el-option
|
||||||
|
v-for="(item, key) in pageSwitchingAnimationOptions"
|
||||||
|
:key
|
||||||
|
:label="t(`settings.${item.value}`)"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="config-item flex-x-between">
|
<div class="config-item flex-x-between">
|
||||||
<span class="text-xs">灰色模式</span>
|
<span class="text-xs">灰色模式</span>
|
||||||
<el-switch v-model="settingsStore.grayMode" />
|
<el-switch v-model="settingsStore.grayMode" />
|
||||||
@@ -159,10 +171,13 @@
|
|||||||
import { DocumentCopy, RefreshLeft, Check } from "@element-plus/icons-vue";
|
import { DocumentCopy, RefreshLeft, Check } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
import { LayoutMode, SidebarColor, ThemeMode } from "@/enums";
|
import { LayoutMode, PageSwitchingAnimationOptions, SidebarColor, ThemeMode } from "@/enums";
|
||||||
import { useSettingsStore } from "@/store";
|
import { useSettingsStore } from "@/store";
|
||||||
import { themeColorPresets, appConfig } from "@/settings";
|
import { themeColorPresets, appConfig } from "@/settings";
|
||||||
|
|
||||||
|
// 页面切换动画选项
|
||||||
|
const pageSwitchingAnimationOptions: Record<string, OptionItem> = PageSwitchingAnimationOptions;
|
||||||
|
|
||||||
// 按钮图标
|
// 按钮图标
|
||||||
const copyIcon = markRaw(DocumentCopy);
|
const copyIcon = markRaw(DocumentCopy);
|
||||||
const resetIcon = markRaw(RefreshLeft);
|
const resetIcon = markRaw(RefreshLeft);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const defaults = {
|
|||||||
showTagsView: true,
|
showTagsView: true,
|
||||||
showAppLogo: true,
|
showAppLogo: true,
|
||||||
showWatermark: false,
|
showWatermark: false,
|
||||||
|
pageSwitchingAnimation: "fade-slide",
|
||||||
showSettings: true,
|
showSettings: true,
|
||||||
watermarkContent: pkg.name,
|
watermarkContent: pkg.name,
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export const useSettingsStore = defineStore("setting", () => {
|
|||||||
const showTagsView = useStorage(STORAGE_KEYS.SHOW_TAGS_VIEW, defaults.showTagsView);
|
const showTagsView = useStorage(STORAGE_KEYS.SHOW_TAGS_VIEW, defaults.showTagsView);
|
||||||
const showAppLogo = useStorage(STORAGE_KEYS.SHOW_APP_LOGO, defaults.showAppLogo);
|
const showAppLogo = useStorage(STORAGE_KEYS.SHOW_APP_LOGO, defaults.showAppLogo);
|
||||||
const showWatermark = useStorage(STORAGE_KEYS.SHOW_WATERMARK, defaults.showWatermark);
|
const showWatermark = useStorage(STORAGE_KEYS.SHOW_WATERMARK, defaults.showWatermark);
|
||||||
|
const pageSwitchingAnimation = useStorage(
|
||||||
|
STORAGE_KEYS.PAGE_SWITCHING_ANIMATION,
|
||||||
|
defaults.pageSwitchingAnimation
|
||||||
|
);
|
||||||
|
|
||||||
// 布局
|
// 布局
|
||||||
const layout = useStorage<LayoutMode>(STORAGE_KEYS.LAYOUT, defaults.layout as LayoutMode);
|
const layout = useStorage<LayoutMode>(STORAGE_KEYS.LAYOUT, defaults.layout as LayoutMode);
|
||||||
@@ -66,6 +70,7 @@ export const useSettingsStore = defineStore("setting", () => {
|
|||||||
showTagsView.value = defaults.showTagsView;
|
showTagsView.value = defaults.showTagsView;
|
||||||
showAppLogo.value = defaults.showAppLogo;
|
showAppLogo.value = defaults.showAppLogo;
|
||||||
showWatermark.value = defaults.showWatermark;
|
showWatermark.value = defaults.showWatermark;
|
||||||
|
pageSwitchingAnimation.value = defaults.pageSwitchingAnimation;
|
||||||
userEnableAi.value = false;
|
userEnableAi.value = false;
|
||||||
grayMode.value = false;
|
grayMode.value = false;
|
||||||
colorWeak.value = false;
|
colorWeak.value = false;
|
||||||
@@ -80,6 +85,7 @@ export const useSettingsStore = defineStore("setting", () => {
|
|||||||
showTagsView,
|
showTagsView,
|
||||||
showAppLogo,
|
showAppLogo,
|
||||||
showWatermark,
|
showWatermark,
|
||||||
|
pageSwitchingAnimation,
|
||||||
enableAiAssistant,
|
enableAiAssistant,
|
||||||
userEnableAi,
|
userEnableAi,
|
||||||
grayMode,
|
grayMode,
|
||||||
|
|||||||
@@ -160,6 +160,38 @@ describe("useSettingsStore", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("页面切换动画", () => {
|
||||||
|
it("应该修改页面切换动画", () => {
|
||||||
|
const store = useSettingsStore();
|
||||||
|
|
||||||
|
store.pageSwitchingAnimation = "fade";
|
||||||
|
expect(store.pageSwitchingAnimation).toBe("fade");
|
||||||
|
|
||||||
|
store.pageSwitchingAnimation = "fade-slide";
|
||||||
|
expect(store.pageSwitchingAnimation).toBe("fade-slide");
|
||||||
|
|
||||||
|
store.pageSwitchingAnimation = "fade-scale";
|
||||||
|
expect(store.pageSwitchingAnimation).toBe("fade-scale");
|
||||||
|
|
||||||
|
store.pageSwitchingAnimation = "none";
|
||||||
|
expect(store.pageSwitchingAnimation).toBe("none");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("应该持久化页面切换动画设置", () => {
|
||||||
|
const store = useSettingsStore();
|
||||||
|
store.pageSwitchingAnimation = "fade-scale";
|
||||||
|
|
||||||
|
const newStore = useSettingsStore();
|
||||||
|
expect(newStore.pageSwitchingAnimation).toBe("fade-scale");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("应该使用默认的页面切换动画", () => {
|
||||||
|
const store = useSettingsStore();
|
||||||
|
// 默认值应该是 "fade-slide"
|
||||||
|
expect(store.pageSwitchingAnimation).toBe("fade-slide");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("重置设置", () => {
|
describe("重置设置", () => {
|
||||||
it("应该重置所有设置为默认值", () => {
|
it("应该重置所有设置为默认值", () => {
|
||||||
const store = useSettingsStore();
|
const store = useSettingsStore();
|
||||||
@@ -168,6 +200,7 @@ describe("useSettingsStore", () => {
|
|||||||
store.showTagsView = false;
|
store.showTagsView = false;
|
||||||
store.showAppLogo = false;
|
store.showAppLogo = false;
|
||||||
store.showWatermark = false;
|
store.showWatermark = false;
|
||||||
|
store.pageSwitchingAnimation = "fade-slide";
|
||||||
store.userEnableAi = true;
|
store.userEnableAi = true;
|
||||||
store.grayMode = true;
|
store.grayMode = true;
|
||||||
store.colorWeak = true;
|
store.colorWeak = true;
|
||||||
@@ -181,6 +214,7 @@ describe("useSettingsStore", () => {
|
|||||||
expect(store.userEnableAi).toBe(false);
|
expect(store.userEnableAi).toBe(false);
|
||||||
expect(store.grayMode).toBe(false);
|
expect(store.grayMode).toBe(false);
|
||||||
expect(store.colorWeak).toBe(false);
|
expect(store.colorWeak).toBe(false);
|
||||||
|
expect(store.pageSwitchingAnimation).toBe("fade-slide");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user