+
侧边栏 Logo
-
+
开启水印
导航设置
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
-
+
+
+@/utils/color
diff --git a/src/layout/index.vue b/src/layout/index.vue
index 2d527ec..52dfd7f 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -3,14 +3,16 @@
+
+
@@ -53,17 +51,11 @@ const appStore = useAppStore();
const settingsStore = useSettingsStore();
const permissionStore = usePermissionStore();
-const fixedHeader = computed(() => settingsStore.fixedHeader);
-const showTagsView = computed(() => settingsStore.tagsView);
-const layout = computed(() => settingsStore.layout);
-
-const activeTopMenuPath = computed(() => {
- return appStore.activeTopMenuPath;
-});
-// 混合模式左侧菜单
-const mixLeftMenus = computed(() => {
- return permissionStore.mixLeftMenus;
-});
+const fixedHeader = computed(() => settingsStore.fixedHeader); // 是否固定header
+const showTagsView = computed(() => settingsStore.tagsView); // 是否显示tagsView
+const layout = computed(() => settingsStore.layout); // 布局模式 left top mix
+const activeTopMenuPath = computed(() => appStore.activeTopMenuPath); // 顶部菜单激活path
+const mixLeftMenus = computed(() => permissionStore.mixLeftMenus); // 混合布局左侧菜单
watch(
() => activeTopMenuPath.value,
@@ -209,7 +201,7 @@ function toggleSidebar() {
height: 100%;
padding-top: $navbar-height;
- .sidebar-container__left {
+ .mix-container__left {
position: relative;
width: $sidebar-width;
height: 100%;
@@ -284,7 +276,7 @@ function toggleSidebar() {
}
.mix-container {
- .sidebar-container__left {
+ .mix-container__left {
width: $sidebar-width-collapsed;
}
}
diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts
index 2d12d76..5e54d7c 100644
--- a/src/store/modules/settings.ts
+++ b/src/store/modules/settings.ts
@@ -35,8 +35,6 @@ export const useSettingsStore = defineStore("setting", () => {
tagsView,
sidebarLogo,
layout,
- themeColor,
- theme,
watermarkEnabled,
};
@@ -50,21 +48,41 @@ export const useSettingsStore = defineStore("setting", () => {
const setting = settingsMap[key];
if (setting) {
setting.value = value;
- // Special handling for theme changes
- if (key === "theme") {
- document.documentElement.classList.toggle("dark", value === "dark");
- }
}
}
+ /**
+ * 切换主题
+ */
+ function changeTheme(val: string) {
+ theme.value = val;
+ }
+
+ /**
+ * 切换主题颜色
+ */
+ function changeThemeColor(val: string) {
+ themeColor.value = val;
+ }
+
+ /**
+ * 切换布局
+ */
+ function changeLayout(val: string) {
+ layout.value = val;
+ }
+
return {
tagsView,
fixedHeader,
sidebarLogo,
layout,
themeColor,
- changeSetting,
theme,
watermarkEnabled,
+ changeSetting,
+ changeTheme,
+ changeThemeColor,
+ changeLayout,
};
});
diff --git a/src/utils/color.ts b/src/utils/color.ts
new file mode 100644
index 0000000..2503584
--- /dev/null
+++ b/src/utils/color.ts
@@ -0,0 +1,287 @@
+/**
+ * 颜色生成
+ */
+type RGB = {
+ r: number;
+ g: number;
+ b: number;
+};
+type HSL = {
+ h: number;
+ s: number;
+ l: number;
+};
+type HEX =
+ | "0"
+ | "1"
+ | "2"
+ | "3"
+ | "4"
+ | "5"
+ | "6"
+ | "7"
+ | "8"
+ | "9"
+ | "A"
+ | "B"
+ | "C"
+ | "D"
+ | "E"
+ | "F";
+
+const RGBUnit = 255;
+const HEX_MAP: Record
= {
+ 0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+ 4: 4,
+ 5: 5,
+ 6: 6,
+ 7: 7,
+ 8: 8,
+ 9: 9,
+ A: 10,
+ B: 11,
+ C: 12,
+ D: 13,
+ E: 14,
+ F: 15,
+};
+const rgbWhite = {
+ r: 255,
+ g: 255,
+ b: 255,
+};
+const rgbBlack = {
+ r: 0,
+ g: 0,
+ b: 0,
+};
+
+/**
+ * RGB颜色转HSL颜色值
+ * @param r 红色值
+ * @param g 绿色值
+ * @param b 蓝色值
+ * @returns { h: [0, 360]; s: [0, 1]; l: [0, 1] }
+ */
+function rgbToHsl(rgb: RGB): HSL {
+ let { r, g, b } = rgb;
+ const hsl = {
+ h: 0,
+ s: 0,
+ l: 0,
+ };
+
+ // 计算rgb基数 ∈ [0, 1]
+ r /= RGBUnit;
+ g /= RGBUnit;
+ b /= RGBUnit;
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+
+ // 计算h
+ if (max === min) {
+ hsl.h = 0;
+ } else if (max === r) {
+ hsl.h = 60 * ((g - b) / (max - min)) + (g >= b ? 0 : 360);
+ } else if (max === g) {
+ hsl.h = 60 * ((b - r) / (max - min)) + 120;
+ } else {
+ hsl.h = 60 * ((r - g) / (max - min)) + 240;
+ }
+ hsl.h = hsl.h > 360 ? hsl.h - 360 : hsl.h;
+
+ // 计算l
+ hsl.l = (max + min) / 2;
+
+ // 计算s
+ if (hsl.l === 0 || max === min) {
+ // 灰/白/黑
+ hsl.s = 0;
+ } else if (hsl.l > 0 && hsl.l <= 0.5) {
+ hsl.s = (max - min) / (max + min);
+ } else {
+ hsl.s = (max - min) / (2 - (max + min));
+ }
+
+ return hsl;
+}
+
+/**
+ * hsl -> rgb
+ * @param h [0, 360]
+ * @param s [0, 1]
+ * @param l [0, 1]
+ * @returns RGB
+ */
+function hslToRgb(hsl: HSL): RGB {
+ const { h, s, l } = hsl;
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ const hUnit = h / 360; // 色相转换为 [0, 1]
+
+ const Cr = fillCircleVal(hUnit + 1 / 3);
+ const Cg = fillCircleVal(hUnit);
+ const Cb = fillCircleVal(hUnit - 1 / 3);
+
+ // 保持 [0, 1] 环状取值
+ function fillCircleVal(val: number): number {
+ return val < 0 ? val + 1 : val > 1 ? val - 1 : val;
+ }
+
+ function computedRgb(val: number): number {
+ let colorVal: number;
+ if (val < 1 / 6) {
+ colorVal = p + (q - p) * 6 * val;
+ } else if (val >= 1 / 6 && val < 1 / 2) {
+ colorVal = q;
+ } else if (val >= 1 / 2 && val < 2 / 3) {
+ colorVal = p + (q - p) * 6 * (2 / 3 - val);
+ } else {
+ colorVal = p;
+ }
+ return colorVal * 255;
+ }
+
+ return {
+ r: Number(computedRgb(Cr).toFixed(0)),
+ g: Number(computedRgb(Cg).toFixed(0)),
+ b: Number(computedRgb(Cb).toFixed(0)),
+ };
+}
+
+/**
+ * 16进制颜色转换RGB
+ * @param color #rrggbb
+ * @returns RGB
+ */
+function hexToRGB(hex: string): RGB {
+ hex = hex.toUpperCase();
+ const hexRegExp = /^#([0-9A-F]{6})$/;
+ if (!hexRegExp.test(hex)) {
+ throw new Error("请传入合法的16进制颜色值,eg: #FF0000");
+ }
+
+ const hexValArr = (hexRegExp.exec(hex)?.[1] || "000000").split(
+ ""
+ ) as Array;
+
+ return {
+ r: HEX_MAP[hexValArr[0]] * 16 + HEX_MAP[hexValArr[1]],
+ g: HEX_MAP[hexValArr[2]] * 16 + HEX_MAP[hexValArr[3]],
+ b: HEX_MAP[hexValArr[4]] * 16 + HEX_MAP[hexValArr[5]],
+ };
+}
+
+/**
+ * rgb 转 16进制
+ * @param rgb RGB
+ * @returns #HEX{6}
+ */
+function rgbToHex(rgb: RGB): string {
+ const HEX_MAP_REVERSE: Record = {};
+ for (const key in HEX_MAP) {
+ HEX_MAP_REVERSE[HEX_MAP[key as HEX]] = key as HEX;
+ }
+ function getRemainderAndQuotient(val: number): string {
+ val = Math.round(val);
+ return `${HEX_MAP_REVERSE[Math.floor(val / 16)]}${
+ HEX_MAP_REVERSE[val % 16]
+ }`;
+ }
+
+ return `#${getRemainderAndQuotient(rgb.r)}${getRemainderAndQuotient(
+ rgb.g
+ )}${getRemainderAndQuotient(rgb.b)}`;
+}
+
+// hsl 转 16进制
+function hslToHex(hsl: HSL): string {
+ return rgbToHex(hslToRgb(hsl));
+}
+
+// 16进制 转 hsl
+function hexToHsl(hex: string): HSL {
+ return rgbToHsl(hexToRGB(hex));
+}
+
+// 生成混合色(混黑 + 混白)
+function genMixColor(base: string | RGB | HSL): {
+ DEFAULT: string;
+ dark: {
+ 1: string;
+ 2: string;
+ 3: string;
+ 4: string;
+ 5: string;
+ 6: string;
+ 7: string;
+ 8: string;
+ 9: string;
+ };
+ light: {
+ 1: string;
+ 2: string;
+ 3: string;
+ 4: string;
+ 5: string;
+ 6: string;
+ 7: string;
+ 8: string;
+ 9: string;
+ };
+} {
+ // 基准色统一转换为RGB
+ if (typeof base === "string") {
+ base = hexToRGB(base);
+ } else if ("h" in base) {
+ base = hslToRgb(base);
+ }
+
+ // 混合色
+ function mix(color: RGB, mixColor: RGB, weight: number): RGB {
+ return {
+ r: color.r * (1 - weight) + mixColor.r * weight,
+ g: color.g * (1 - weight) + mixColor.g * weight,
+ b: color.b * (1 - weight) + mixColor.b * weight,
+ };
+ }
+
+ return {
+ DEFAULT: rgbToHex(base),
+ dark: {
+ 1: rgbToHex(mix(base, rgbBlack, 0.1)),
+ 2: rgbToHex(mix(base, rgbBlack, 0.2)),
+ 3: rgbToHex(mix(base, rgbBlack, 0.3)),
+ 4: rgbToHex(mix(base, rgbBlack, 0.4)),
+ 5: rgbToHex(mix(base, rgbBlack, 0.5)),
+ 6: rgbToHex(mix(base, rgbBlack, 0.6)),
+ 7: rgbToHex(mix(base, rgbBlack, 0.7)),
+ 8: rgbToHex(mix(base, rgbBlack, 0.78)),
+ 9: rgbToHex(mix(base, rgbBlack, 0.85)),
+ },
+ light: {
+ 1: rgbToHex(mix(base, rgbWhite, 0.1)),
+ 2: rgbToHex(mix(base, rgbWhite, 0.2)),
+ 3: rgbToHex(mix(base, rgbWhite, 0.3)),
+ 4: rgbToHex(mix(base, rgbWhite, 0.4)),
+ 5: rgbToHex(mix(base, rgbWhite, 0.5)),
+ 6: rgbToHex(mix(base, rgbWhite, 0.6)),
+ 7: rgbToHex(mix(base, rgbWhite, 0.7)),
+ 8: rgbToHex(mix(base, rgbWhite, 0.78)),
+ 9: rgbToHex(mix(base, rgbWhite, 0.85)),
+ },
+ };
+}
+
+export {
+ genMixColor,
+ rgbToHsl,
+ rgbToHex,
+ hslToRgb,
+ hslToHex,
+ hexToRGB,
+ hexToHsl,
+};
diff --git a/src/utils/theme.ts b/src/utils/theme.ts
deleted file mode 100644
index edb88c6..0000000
--- a/src/utils/theme.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * hex颜色转rgb颜色
- * @param str 颜色值字符串
- * @return 返回处理后的颜色值
- */
-export function hexToRgb(str: any) {
- let hexs: any = "";
- const reg = /^\#?[0-9A-Fa-f]{6}$/;
- if (!reg.test(str)) return ElMessage.warning("错误的hex");
- str = str.replace("#", "");
- hexs = str.match(/../g);
- for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
- return hexs;
-}
-
-/**
- * rgb颜色转Hex颜色
- * @param r 代表红色
- * @param g 代表绿色
- * @param b 代表蓝色
- * @return 返回处理后的颜色值
- */
-export function rgbToHex(r: any, g: any, b: any) {
- const reg = /^\d{1,3}$/;
- if (!reg.test(r) || !reg.test(g) || !reg.test(b))
- return ElMessage.warning("错误的rgb颜色值");
- const hexs = [r.toString(16), g.toString(16), b.toString(16)];
- for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
- return `#${hexs.join("")}`;
-}
-
-/**
- * 加深颜色值
- * @param color 颜色值字符串
- * @param level 加深的程度,限0-1之间
- * @return 返回处理后的颜色值
- */
-export function getDarkColor(color: string, level: number) {
- const reg = /^\#?[0-9A-Fa-f]{6}$/;
- if (!reg.test(color)) return ElMessage.warning("错误的hex颜色值");
- const rgb = hexToRgb(color);
- for (let i = 0; i < 3; i++)
- rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level));
- return rgbToHex(rgb[0], rgb[1], rgb[2]);
-}
-
-/**
- * 变浅颜色值
- * @param color 颜色值字符串
- * @param level 加深的程度,限0-1之间
- * @return 返回处理后的颜色值
- */
-export function getLightColor(color: string, level: number) {
- const reg = /^\#?[0-9A-Fa-f]{6}$/;
- if (!reg.test(color)) return ElMessage.warning("错误的hex颜色值");
- const rgb = hexToRgb(color);
- for (let i = 0; i < 3; i++)
- rgb[i] = Math.round(255 * level + rgb[i] * (1 - level));
- return rgbToHex(rgb[0], rgb[1], rgb[2]);
-}
-
-/**
- * 根据dark加工颜色值
- * @param color 颜色值字符串
- * @param level 加深的程度,限0-1之间
- * @return 返回处理后的颜色值
- */
-export function getThemeColor(dark: boolean, color: string, level: number) {
- return dark ? getDarkColor(color, level) : getLightColor(color, level);
-}