wangzhibo
2026-07-16 b57020623cf0c946706573bf102e548c3a544423
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { defineStore } from 'pinia';
import { computed, reactive, ref } from 'vue';
import { module, storage } from '/@/cool';
import { useBase } from '/$/base';
import { useDark } from '@vueuse/core';
import { mix } from '../utils';
import { assign } from 'lodash-es';
import { config } from '/@/config';
 
export const useTheme = defineStore('theme', () => {
    const { app } = useBase();
    const isDark = useDark();
    const { options } = module.get('theme');
 
    // 当前主题
    const theme = reactive<Theme>(
        storage.get('theme') ||
            assign(
                {
                    isGroup: config.app.menu.isGroup,
                    transition: config.app.router.transition
                },
                options
            )
    );
 
    // 主题列表
    const themes = ref(options.list);
 
    // 主题色
    const color = computed(() => theme.color);
 
    // 设置主题
    function setTheme({ color, name, isGroup, transition, dark }: Theme) {
        // 设置暗黑模式
        if (dark !== undefined) {
            isDark.value = dark;
        }
 
        // 主题配置
        const theme = storage.get('theme') || {};
 
        // 变量前缀
        const pre = '--el-color-primary';
 
        // 白色混合色
        const mixWhite = '#ffffff';
 
        // 黑色混合色
        const mixBlack = '#131313';
 
        // 元素
        const el = document.documentElement;
 
        // 主题
        if (name) {
            const item = options.list.find(e => e.name == name);
 
            if (item) {
                if (!color) {
                    color = item.color;
                }
                document.body?.setAttribute('class', `theme-${name}`);
            }
 
            theme.name = name;
        }
 
        // 设置主色
        if (color) {
            el.style.setProperty(pre, color);
 
            // 设置辅色
            for (let i = 1; i < 10; i += 1) {
                if (isDark.value) {
                    el.style.setProperty(`${pre}-light-${i}`, mix(color, mixBlack, i * 0.1));
                    el.style.setProperty(`${pre}-dark-${i}`, mix(color, mixWhite, i * 0.1));
                } else {
                    el.style.setProperty(`${pre}-light-${i}`, mix(color, mixWhite, i * 0.1));
                    el.style.setProperty(`${pre}-dark-${i}`, mix(color, mixBlack, i * 0.1));
                }
            }
 
            theme.color = color;
        }
 
        // 菜单分组显示
        if (isGroup !== undefined) {
            theme.isGroup = isGroup;
            app.set({
                menu: {
                    isGroup
                }
            });
        }
 
        // 转场动画
        if (transition !== undefined) {
            theme.transition = transition;
            app.set({
                router: {
                    transition
                }
            });
        }
 
        storage.set('theme', theme);
    }
 
    // 切换暗黑模式
    function changeDark(el: Element, isDark: boolean, cb: () => void) {
        // @ts-ignore
        const transition = document.startViewTransition(() => {
            cb();
        });
 
        transition.ready.then(() => {
            const rect = el.getBoundingClientRect();
            const x = rect.left + rect.width / 2;
            const y = rect.top + rect.height / 2;
            const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
            const clipPath = [
                `circle(0 at ${x}px ${y}px)`,
                `circle(${endRadius}px at ${x}px ${y}px)`
            ];
            document.documentElement.animate(
                {
                    clipPath: isDark ? clipPath.reverse() : clipPath
                },
                {
                    duration: 400,
                    pseudoElement: isDark
                        ? '::view-transition-old(root)'
                        : '::view-transition-new(root)'
                }
            );
        });
    }
 
    // 初始化
    setTheme(theme);
 
    return {
        isDark,
        color,
        theme,
        themes,
        setTheme,
        changeDark
    };
});