Эх сурвалжийг харах

feat: ep theme (#156)

* feat: ep-theme

* perf: ep-theme

Co-authored-by: lrl <742798240@qq.com>
啝裳 3 жил өмнө
parent
commit
955b76f30a

+ 2 - 0
package.json

@@ -40,6 +40,7 @@
     "animate.css": "^4.1.1",
     "axios": "^0.21.1",
     "cropperjs": "^1.5.11",
+    "css-color-function": "^1.3.3",
     "dayjs": "^1.10.7",
     "echarts": "^5.2.1",
     "element-plus": "1.2.0-beta.6",
@@ -56,6 +57,7 @@
     "remixicon": "^2.5.0",
     "resize-observer-polyfill": "^1.5.1",
     "responsive-storage": "^1.0.11",
+    "rgb-hex": "^4.0.0",
     "v-contextmenu": "3.0.0",
     "vue": "^3.2.24",
     "vue-i18n": "^9.2.0-beta.3",

+ 58 - 2
pnpm-lock.yaml

@@ -33,6 +33,7 @@ specifiers:
   babel-plugin-transform-remove-console: 6.9.4
   cropperjs: ^1.5.11
   cross-env: 7.0.3
+  css-color-function: ^1.3.3
   dayjs: ^1.10.7
   echarts: ^5.2.1
   element-plus: 1.2.0-beta.6
@@ -58,6 +59,7 @@ specifiers:
   remixicon: ^2.5.0
   resize-observer-polyfill: ^1.5.1
   responsive-storage: ^1.0.11
+  rgb-hex: ^4.0.0
   rimraf: 3.0.2
   sass: ^1.45.0
   sass-loader: ^12.3.0
@@ -98,6 +100,7 @@ dependencies:
   animate.css: 4.1.1
   axios: 0.21.4
   cropperjs: 1.5.12
+  css-color-function: 1.3.3
   dayjs: 1.10.7
   echarts: 5.2.2
   element-plus: 1.2.0-beta.6_vue@3.2.24
@@ -114,6 +117,7 @@ dependencies:
   remixicon: 2.5.0
   resize-observer-polyfill: 1.5.1
   responsive-storage: 1.0.11_vue@3.2.24
+  rgb-hex: 4.0.0
   v-contextmenu: 3.0.0_vue@3.2.24
   vue: 3.2.24
   vue-i18n: 9.2.0-beta.17_vue@3.2.24
@@ -1953,6 +1957,10 @@ packages:
       }
     dev: true
 
+  /balanced-match/0.1.0:
+    resolution: { integrity: sha1-tQS9BYabOSWd0MXvw12EMXbczEo= }
+    dev: false
+
   /balanced-match/1.0.2:
     resolution:
       {
@@ -2277,6 +2285,11 @@ packages:
       is-regexp: 2.1.0
     dev: true
 
+  /clone/1.0.4:
+    resolution: { integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4= }
+    engines: { node: ">=0.8" }
+    dev: false
+
   /clone/2.1.2:
     resolution: { integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= }
     engines: { node: ">=0.8" }
@@ -2308,7 +2321,12 @@ packages:
       {
         integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
       }
-    dev: true
+
+  /color-string/0.3.0:
+    resolution: { integrity: sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE= }
+    dependencies:
+      color-name: 1.1.4
+    dev: false
 
   /color-string/1.9.0:
     resolution:
@@ -2320,6 +2338,14 @@ packages:
       simple-swizzle: 0.2.2
     dev: true
 
+  /color/0.11.4:
+    resolution: { integrity: sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q= }
+    dependencies:
+      clone: 1.0.4
+      color-convert: 1.9.3
+      color-string: 0.3.0
+    dev: false
+
   /color/4.1.0:
     resolution:
       {
@@ -2527,6 +2553,15 @@ packages:
       which: 2.0.2
     dev: true
 
+  /css-color-function/1.3.3:
+    resolution: { integrity: sha1-jtJMLAIFBzM5+voAS8jBQfzLKC4= }
+    dependencies:
+      balanced-match: 0.1.0
+      color: 0.11.4
+      debug: 3.2.7
+      rgb: 0.1.0
+    dev: false
+
   /css-declaration-sorter/6.1.3_postcss@8.3.11:
     resolution:
       {
@@ -2726,6 +2761,15 @@ packages:
       ms: 2.0.0
     dev: true
 
+  /debug/3.2.7:
+    resolution:
+      {
+        integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+      }
+    dependencies:
+      ms: 2.1.2
+    dev: false
+
   /debug/4.3.2:
     resolution:
       {
@@ -4986,7 +5030,6 @@ packages:
       {
         integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
       }
-    dev: true
 
   /multimatch/4.0.0:
     resolution:
@@ -6281,6 +6324,19 @@ packages:
     engines: { iojs: ">=1.0.0", node: ">=0.10.0" }
     dev: true
 
+  /rgb-hex/4.0.0:
+    resolution:
+      {
+        integrity: sha512-Eg2ev5CiMBnQ9Gpflmqbwbso0CCdISqtVIow7OpYSLN1ULUv2jTB9YieS1DSSn/17AD7KkPWDPzSFzI4GSuu/Q==
+      }
+    engines: { node: ">=12" }
+    dev: false
+
+  /rgb/0.1.0:
+    resolution: { integrity: sha1-vieykej+/+rBvZlylyG/pA/AN7U= }
+    hasBin: true
+    dev: false
+
   /rimraf/3.0.2:
     resolution:
       {

+ 23 - 0
src/layout/components/setting/index.vue

@@ -9,6 +9,8 @@ import {
   useCssModule,
   getCurrentInstance
 } from "vue";
+import rgbHex from "rgb-hex";
+import { find } from "lodash-es";
 import panel from "../panel/index.vue";
 import { getConfig } from "/@/config";
 import { useRouter } from "vue-router";
@@ -19,8 +21,10 @@ import { debounce } from "/@/utils/debounce";
 import darkIcon from "/@/assets/svg/dark.svg";
 import { themeColorsType } from "../../types";
 import { useAppStoreHook } from "/@/store/modules/app";
+import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
 import { storageLocal, storageSession } from "/@/utils/storage";
 import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
+import { createNewStyle, writeNewStyle } from "/@/utils/theme";
 import { toggleTheme } from "@zougt/vite-plugin-theme-preprocessor/dist/browser-utils";
 
 const router = useRouter();
@@ -77,6 +81,8 @@ const markValue = ref(storageLocal.getItem("showModel") || "smart");
 
 const logoVal = ref(storageLocal.getItem("logoVal") || "1");
 
+const epThemeColor = ref(useEpThemeStoreHook().getEpThemeColor);
+
 const settings = reactive({
   greyVal: instance.sets.grey,
   weakVal: instance.sets.weak,
@@ -146,6 +152,8 @@ nextTick(() => {
   settings.weakVal &&
     document.querySelector("html")?.setAttribute("class", "html-weakness");
   settings.tabsVal && tagsChange();
+
+  writeNewStyle(createNewStyle(epThemeColor.value));
 });
 
 // 清空缓存并返回登录页
@@ -167,6 +175,7 @@ function onReset() {
     }
   ]);
   useMultiTagsStoreHook().multiTagsCacheChange(getConfig().MultiTagsCache);
+  useEpThemeStoreHook().setEpThemeColor("#409EFF");
   router.push("/login");
 }
 
@@ -236,8 +245,22 @@ function setLayoutThemeColor(theme: string) {
     scopeName: `layout-theme-${theme}`
   });
   instance.layout = { layout: useAppStoreHook().layout, theme };
+
+  if (theme === "default" || theme === "light") {
+    setEpThemeColor("#409EFF");
+  } else {
+    const colors = find(themeColors.value, { themeColor: theme });
+    const color = "#" + rgbHex(colors.rgb);
+    setEpThemeColor(color);
+  }
 }
 
+// 设置ep主题色
+const setEpThemeColor = (color: string) => {
+  writeNewStyle(createNewStyle(color));
+  useEpThemeStoreHook().setEpThemeColor(color);
+};
+
 let dataTheme = ref<boolean>(false);
 
 // 日间、夜间主题切换

+ 3 - 1
src/plugins/element-plus/index.ts

@@ -44,6 +44,7 @@ import {
   ElCollapse,
   ElCollapseItem,
   ElTreeV2,
+  ElColorPicker,
   // 指令
   ElLoading,
   ElInfiniteScroll
@@ -96,7 +97,8 @@ const components = [
   ElEmpty,
   ElCollapse,
   ElCollapseItem,
-  ElTreeV2
+  ElTreeV2,
+  ElColorPicker
 ];
 
 // https://element-plus.org/zh-CN/component/icon.html

+ 25 - 0
src/store/modules/epTheme.ts

@@ -0,0 +1,25 @@
+import { store } from "/@/store";
+import { defineStore } from "pinia";
+import { storageLocal } from "/@/utils/storage";
+
+export const useEpThemeStore = defineStore({
+  id: "pure-epTheme",
+  state: () => ({
+    epThemeColor: storageLocal.getItem("epThemeColor") || "#409EFF"
+  }),
+  getters: {
+    getEpThemeColor() {
+      return this.epThemeColor;
+    }
+  },
+  actions: {
+    setEpThemeColor(newColor) {
+      this.epThemeColor = newColor;
+      storageLocal.setItem("epThemeColor", newColor);
+    }
+  }
+});
+
+export function useEpThemeStoreHook() {
+  return useEpThemeStore(store);
+}

+ 71 - 0
src/utils/theme/index.ts

@@ -0,0 +1,71 @@
+import rgbHex from "rgb-hex";
+import color from "css-color-function";
+import epCss from "element-plus/dist/index.css";
+
+const formula = {
+  "shade-1": "color(primary shade(10%))",
+  "light-1": "color(primary tint(10%))",
+  "light-2": "color(primary tint(20%))",
+  "light-3": "color(primary tint(30%))",
+  "light-4": "color(primary tint(40%))",
+  "light-5": "color(primary tint(50%))",
+  "light-6": "color(primary tint(60%))",
+  "light-7": "color(primary tint(70%))",
+  "light-8": "color(primary tint(80%))",
+  "light-9": "color(primary tint(90%))"
+};
+
+export const writeNewStyle = newStyle => {
+  const style = window.document.createElement("style");
+  style.innerText = newStyle;
+  window.document.head.appendChild(style);
+};
+
+export const createNewStyle = primaryStyle => {
+  const colors = createColors(primaryStyle);
+  let cssText = getOriginStyle();
+  Object.keys(colors).forEach(key => {
+    cssText = cssText.replace(
+      new RegExp("(:|\\s+)" + key, "g"),
+      "$1" + colors[key]
+    );
+  });
+  return cssText;
+};
+
+export const createColors = primary => {
+  if (!primary) return;
+  const colors = {
+    primary
+  };
+  Object.keys(formula).forEach(key => {
+    const value = formula[key].replace(/primary/, primary);
+    colors[key] = "#" + rgbHex(color.convert(value));
+  });
+  return colors;
+};
+
+export const getOriginStyle = () => {
+  return getStyleTemplate(epCss);
+};
+
+const getStyleTemplate = data => {
+  const colorMap = {
+    "#3a8ee6": "shade-1",
+    "#409eff": "primary",
+    "#53a8ff": "light-1",
+    "#66b1ff": "light-2",
+    "#79bbff": "light-3",
+    "#8cc5ff": "light-4",
+    "#a0cfff": "light-5",
+    "#b3d8ff": "light-6",
+    "#c6e2ff": "light-7",
+    "#d9ecff": "light-8",
+    "#ecf5ff": "light-9"
+  };
+  Object.keys(colorMap).forEach(key => {
+    const value = colorMap[key];
+    data = data.replace(new RegExp(key, "ig"), value);
+  });
+  return data;
+};