2
0

初始化项目

This commit is contained in:
caiyuchao
2024-11-14 11:06:38 +08:00
parent 988b9e6799
commit 4ffac789e1
320 changed files with 34244 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue';
import type { Ref } from 'vue';
import { defineStore } from 'pinia';
import { useEventListener, usePreferredColorScheme } from '@vueuse/core';
import { SetupStoreId } from '@/enum';
import { localStg } from '@/utils/storage';
import { addThemeVarsToHtml, createThemeToken, getAntdTheme, initThemeSettings, toggleCssDarkMode } from './shared';
/** Theme store */
export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
const scope = effectScope();
const osTheme = usePreferredColorScheme();
/** Theme settings */
const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings());
/** Reset store */
function resetStore() {
const themeStore = useThemeStore();
themeStore.$reset();
}
/** Theme colors */
const themeColors = computed(() => {
const { themeColor, otherColor, isInfoFollowPrimary } = settings.value;
const colors: App.Theme.ThemeColor = {
primary: themeColor,
...otherColor,
info: isInfoFollowPrimary ? themeColor : otherColor.info
};
return colors;
});
/** Dark mode */
const darkMode = computed(() => {
if (settings.value.themeScheme === 'auto') {
return osTheme.value === 'dark';
}
return settings.value.themeScheme === 'dark';
});
/** Antd theme */
const antdTheme = computed(() => getAntdTheme(themeColors.value, darkMode.value));
/**
* Settings json
*
* It is for copy settings
*/
const settingsJson = computed(() => JSON.stringify(settings.value));
/**
* Set theme scheme
*
* @param themeScheme
*/
function setThemeScheme(themeScheme: UnionKey.ThemeScheme) {
settings.value.themeScheme = themeScheme;
}
/** Toggle theme scheme */
function toggleThemeScheme() {
const themeSchemes: UnionKey.ThemeScheme[] = ['light', 'dark', 'auto'];
const index = themeSchemes.findIndex(item => item === settings.value.themeScheme);
const nextIndex = index === themeSchemes.length - 1 ? 0 : index + 1;
const nextThemeScheme = themeSchemes[nextIndex];
setThemeScheme(nextThemeScheme);
}
/**
* Set theme layout
*
* @param mode Theme layout mode
*/
function setThemeLayout(mode: UnionKey.ThemeLayoutMode) {
settings.value.layout.mode = mode;
}
/**
* Update theme colors
*
* @param key Theme color key
* @param color Theme color
*/
function updateThemeColors(key: App.Theme.ThemeColorKey, color: string) {
if (key === 'primary') {
settings.value.themeColor = color;
} else {
settings.value.otherColor[key] = color;
}
}
/** Setup theme vars to html */
function setupThemeVarsToHtml() {
const { themeTokens, darkThemeTokens } = createThemeToken(themeColors.value);
addThemeVarsToHtml(themeTokens, darkThemeTokens);
}
/** Cache theme settings */
function cacheThemeSettings() {
const isProd = import.meta.env.PROD;
if (!isProd) return;
localStg.set('themeSettings', settings.value);
}
// cache theme settings when page is closed or refreshed
useEventListener(window, 'beforeunload', () => {
cacheThemeSettings();
});
// watch store
scope.run(() => {
// watch dark mode
watch(
darkMode,
val => {
toggleCssDarkMode(val);
},
{ immediate: true }
);
// themeColors change, update css vars and storage theme color
watch(
themeColors,
val => {
setupThemeVarsToHtml();
localStg.set('themeColor', val.primary);
},
{ immediate: true }
);
});
/** On scope dispose */
onScopeDispose(() => {
scope.stop();
});
return {
...toRefs(settings.value),
resetStore,
settingsJson,
darkMode,
themeColors,
antdTheme,
toggleThemeScheme,
setThemeScheme,
updateThemeColors,
setThemeLayout
};
});

View File

@@ -0,0 +1,217 @@
import { theme as antdTheme } from 'ant-design-vue';
import type { ConfigProviderProps } from 'ant-design-vue';
import { getColorPalette } from '@sa/color-palette';
import { getRgbOfColor } from '@sa/utils';
import { overrideThemeSettings, themeSettings } from '@/theme/settings';
import { themeVars } from '@/theme/vars';
import { localStg } from '@/utils/storage';
const DARK_CLASS = 'dark';
/** Init theme settings */
export function initThemeSettings() {
const isProd = import.meta.env.PROD;
// if it is development mode, the theme settings will not be cached, by update `themeSettings` in `src/theme/settings.ts` to update theme settings
if (!isProd) return themeSettings;
// if it is production mode, the theme settings will be cached in localStorage
// if want to update theme settings when publish new version, please update `overrideThemeSettings` in `src/theme/settings.ts`
const settings = localStg.get('themeSettings') || themeSettings;
const isOverride = localStg.get('overrideThemeFlag') === BUILD_TIME;
if (!isOverride) {
Object.assign(settings, overrideThemeSettings);
localStg.set('overrideThemeFlag', BUILD_TIME);
}
return settings;
}
/**
* Create theme token
*
* @param colors Theme colors
*/
export function createThemeToken(colors: App.Theme.ThemeColor) {
const paletteColors = createThemePaletteColors(colors);
const themeTokens: App.Theme.ThemeToken = {
colors: {
...paletteColors,
nprogress: paletteColors.primary,
container: 'rgb(255, 255, 255)',
layout: 'rgb(247, 250, 252)',
inverted: 'rgb(0, 20, 40)',
base_text: 'rgb(31, 31, 31)'
},
boxShadow: {
header: '0 1px 2px rgb(0, 21, 41, 0.08)',
sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
tab: '0 1px 2px rgb(0, 21, 41, 0.08)'
}
};
const darkThemeTokens: App.Theme.ThemeToken = {
colors: {
...themeTokens.colors,
container: 'rgb(28, 28, 28)',
layout: 'rgb(18, 18, 18)',
base_text: 'rgb(224, 224, 224)'
},
boxShadow: {
...themeTokens.boxShadow
}
};
return {
themeTokens,
darkThemeTokens
};
}
/**
* Create theme palette colors
*
* @param colors Theme colors
*/
function createThemePaletteColors(colors: App.Theme.ThemeColor) {
const colorKeys = Object.keys(colors) as App.Theme.ThemeColorKey[];
const colorPaletteVar = {} as App.Theme.ThemePaletteColor;
colorKeys.forEach(key => {
const { palettes, main } = getColorPalette(colors[key], key);
colorPaletteVar[key] = main.hexcode;
palettes.forEach(item => {
colorPaletteVar[`${key}-${item.number}`] = item.hexcode;
});
});
return colorPaletteVar;
}
/**
* Get css var by tokens
*
* @param tokens Theme base tokens
*/
function getCssVarByTokens(tokens: App.Theme.BaseToken) {
const styles: string[] = [];
function removeVarPrefix(value: string) {
return value.replace('var(', '').replace(')', '');
}
function removeRgbPrefix(value: string) {
return value.replace('rgb(', '').replace(')', '');
}
for (const [key, tokenValues] of Object.entries(themeVars)) {
for (const [tokenKey, tokenValue] of Object.entries(tokenValues)) {
let cssVarsKey = removeVarPrefix(tokenValue);
let cssValue = tokens[key][tokenKey];
if (key === 'colors') {
cssVarsKey = removeRgbPrefix(cssVarsKey);
const { r, g, b } = getRgbOfColor(cssValue);
cssValue = `${r} ${g} ${b}`;
}
styles.push(`${cssVarsKey}: ${cssValue}`);
}
}
const styleStr = styles.join(';');
return styleStr;
}
/**
* Add theme vars to html
*
* @param tokens
*/
export function addThemeVarsToHtml(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) {
const cssVarStr = getCssVarByTokens(tokens);
const darkCssVarStr = getCssVarByTokens(darkTokens);
const css = `
html {
${cssVarStr}
}
`;
const darkCss = `
html.${DARK_CLASS} {
${darkCssVarStr}
}
`;
const styleId = 'theme-vars';
const style = document.querySelector(`#${styleId}`) || document.createElement('style');
style.id = styleId;
style.textContent = css + darkCss;
document.head.appendChild(style);
}
/**
* Toggle css dark mode
*
* @param darkMode Is dark mode
*/
export function toggleCssDarkMode(darkMode = false) {
function addDarkClass() {
document.documentElement.classList.add(DARK_CLASS);
}
function removeDarkClass() {
document.documentElement.classList.remove(DARK_CLASS);
}
if (darkMode) {
addDarkClass();
} else {
removeDarkClass();
}
}
/**
* Get antd theme
*
* @param colors Theme colors
* @param darkMode Is dark mode
*/
export function getAntdTheme(colors: App.Theme.ThemeColor, darkMode: boolean) {
const { defaultAlgorithm, darkAlgorithm } = antdTheme;
const { primary, info, success, warning, error } = colors;
const theme: ConfigProviderProps['theme'] = {
token: {
colorPrimary: primary,
colorInfo: info,
colorSuccess: success,
colorWarning: warning,
colorError: error
},
algorithm: [darkMode ? darkAlgorithm : defaultAlgorithm],
components: {
Button: {
controlHeightSM: 28
},
Menu: {
colorSubItemBg: 'transparent'
}
}
};
return theme;
}