Prechádzať zdrojové kódy

feat: 同步master代码

kailong321200875 1 rok pred
rodič
commit
6a83d08309

+ 10 - 1
.env.base

@@ -1,5 +1,5 @@
 # 环境
-NODE_ENV=development
+VITE_NODE_ENV=development
 
 # 接口前缀
 VITE_API_BASE_PATH=
@@ -9,3 +9,12 @@ VITE_BASE_PATH=/
 
 # 标题
 VITE_APP_TITLE=ElementAdmin
+
+# 是否全量引入element-plus样式
+VITE_USE_ALL_ELEMENT_PLUS_STYLE=true
+
+# 是否开启mock
+VITE_USE_MOCK=true
+
+# 是否使用在线图标
+VITE_USE_ONLINE_ICON=true

+ 16 - 1
.env.dev

@@ -1,5 +1,5 @@
 # 环境
-NODE_ENV=production
+VITE_NODE_ENV=production
 
 # 接口前缀
 VITE_API_BASE_PATH=
@@ -21,3 +21,18 @@ VITE_OUT_DIR=dist-dev
 
 # 标题
 VITE_APP_TITLE=ElementAdmin
+
+# 是否包分析
+VITE_USE_BUNDLE_ANALYZER=false
+
+# 是否全量引入element-plus样式
+VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
+
+# 是否开启mock
+VITE_USE_MOCK=true
+
+# 是否切割css
+VITE_USE_CSS_SPLIT=true
+
+# 是否使用在线图标
+VITE_USE_ONLINE_ICON=true

+ 16 - 1
.env.gitee

@@ -1,5 +1,5 @@
 # 环境
-NODE_ENV=production
+VITE_NODE_ENV=production
 
 # 接口前缀
 VITE_API_BASE_PATH=
@@ -21,3 +21,18 @@ VITE_OUT_DIR=dist-pro
 
 # 标题
 VITE_APP_TITLE=ElementAdmin
+
+# 是否包分析
+VITE_USE_BUNDLE_ANALYZER=false
+
+# 是否全量引入element-plus样式
+VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
+
+# 是否开启mock
+VITE_USE_MOCK=true
+
+# 是否切割css
+VITE_USE_CSS_SPLIT=true
+
+# 是否使用在线图标
+VITE_USE_ONLINE_ICON=true

+ 16 - 1
.env.pro

@@ -1,5 +1,5 @@
 # 环境
-NODE_ENV=production
+VITE_NODE_ENV=production
 
 # 接口前缀
 VITE_API_BASE_PATH=
@@ -21,3 +21,18 @@ VITE_OUT_DIR=dist-pro
 
 # 标题
 VITE_APP_TITLE=ElementAdmin
+
+# 是否包分析
+VITE_USE_BUNDLE_ANALYZER=true
+
+# 是否全量引入element-plus样式
+VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
+
+# 是否开启mock
+VITE_USE_MOCK=true
+
+# 是否切割css
+VITE_USE_CSS_SPLIT=true
+
+# 是否使用在线图标
+VITE_USE_ONLINE_ICON=true

+ 14 - 2
.env.test

@@ -1,8 +1,8 @@
 # 环境
-NODE_ENV=production
+VITE_NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=test
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/dist-test/
@@ -21,3 +21,15 @@ VITE_OUT_DIR=dist-test
 
 # 标题
 VITE_APP_TITLE=ElementAdmin
+
+# 是否包分析
+VITE_USE_BUNDLE_ANALYZER=false
+
+# 是否全量引入element-plus样式
+VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
+
+# 是否开启mock
+VITE_USE_MOCK=true
+
+# 是否切割css
+VITE_USE_CSS_SPLIT=false

+ 1 - 0
.gitignore

@@ -6,3 +6,4 @@ dist-ssr
 /dist*
 *-lock.*
 pnpm-debug
+stats.html

+ 1 - 1
package.json

@@ -61,7 +61,6 @@
     "@commitlint/config-conventional": "^18.4.3",
     "@iconify/json": "^2.2.153",
     "@intlify/unplugin-vue-i18n": "^1.5.0",
-    "@purge-icons/generated": "^0.10.0",
     "@types/fs-extra": "^11.0.4",
     "@types/inquirer": "^9.0.7",
     "@types/lodash-es": "^4.17.12",
@@ -97,6 +96,7 @@
     "prettier": "^3.1.0",
     "rimraf": "^5.0.5",
     "rollup": "^4.6.1",
+    "rollup-plugin-visualizer": "^5.12.0",
     "stylelint": "^15.11.0",
     "stylelint-config-html": "^1.1.0",
     "stylelint-config-recommended": "^13.0.0",

+ 18 - 3
src/components/Icon/src/Icon.vue

@@ -25,6 +25,11 @@ const symbolId = computed(() => {
   return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
 })
 
+// 是否使用在线图标
+const isUseOnline = computed(() => {
+  return import.meta.env.VITE_USE_ONLINE_ICON === 'true'
+})
+
 const getIconifyStyle = computed(() => {
   const { color, size } = props
   return {
@@ -40,7 +45,10 @@ const getIconifyStyle = computed(() => {
       <use :xlink:href="symbolId" />
     </svg>
 
-    <Icon v-else :icon="icon" :style="getIconifyStyle" />
+    <template v-else>
+      <Icon v-if="isUseOnline" :icon="icon" :style="getIconifyStyle" />
+      <div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
+    </template>
   </ElIcon>
 </template>
 
@@ -49,11 +57,18 @@ const getIconifyStyle = computed(() => {
 
 .@{prefix-cls},
 .iconify {
-  &:hover {
-    :deep(svg) {
+  :deep(svg) {
+    &:hover {
       // stylelint-disable-next-line
       color: v-bind(hoverColor) !important;
     }
   }
 }
+
+.iconify {
+  &:hover {
+    // stylelint-disable-next-line
+    color: v-bind(hoverColor) !important;
+  }
+}
 </style>

+ 20 - 14
src/components/Setting/src/Setting.vue

@@ -1,12 +1,12 @@
 <script setup lang="ts">
 import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
-import { ref, unref, computed, watch } from 'vue'
+import { ref, unref, computed } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ThemeSwitch } from '@/components/ThemeSwitch'
 import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
 import { useCssVar } from '@vueuse/core'
 import { useAppStore } from '@/store/modules/app'
-import { trim, setCssVar } from '@/utils'
+import { trim, setCssVar, getCssVar } from '@/utils'
 import ColorRadioPicker from './components/ColorRadioPicker.vue'
 import InterfaceDisplay from './components/InterfaceDisplay.vue'
 import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
@@ -95,17 +95,17 @@ const setMenuTheme = (color: string) => {
 }
 
 // 监听layout变化,重置一些主题色
-watch(
-  () => layout.value,
-  (n) => {
-    if (n === 'top' && !appStore.getIsDark) {
-      headerTheme.value = '#fff'
-      setHeaderTheme('#fff')
-    } else {
-      setMenuTheme(unref(menuTheme))
-    }
-  }
-)
+// watch(
+//   () => layout.value,
+//   (n) => {
+//     if (n === 'top' && !appStore.getIsDark) {
+//       headerTheme.value = '#fff'
+//       setHeaderTheme('#fff')
+//     } else {
+//       setMenuTheme(unref(menuTheme))
+//     }
+//   }
+// )
 
 // 拷贝
 const copyConfig = async () => {
@@ -192,6 +192,12 @@ const clear = () => {
   storageClear()
   window.location.reload()
 }
+
+const themeChange = () => {
+  const color = getCssVar('--el-bg-color')
+  setMenuTheme(color)
+  setHeaderTheme(color)
+}
 </script>
 
 <template>
@@ -211,7 +217,7 @@ const clear = () => {
     <div class="text-center">
       <!-- 主题 -->
       <ElDivider>{{ t('setting.theme') }}</ElDivider>
-      <ThemeSwitch />
+      <ThemeSwitch @change="themeChange" />
 
       <!-- 布局 -->
       <ElDivider>{{ t('setting.layout') }}</ElDivider>

+ 3 - 0
src/components/ThemeSwitch/src/ThemeSwitch.vue

@@ -7,6 +7,8 @@ import { useDesign } from '@/hooks/web/useDesign'
 
 const { getPrefixCls } = useDesign()
 
+const emit = defineEmits(['change'])
+
 const prefixCls = getPrefixCls('theme-switch')
 
 const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
@@ -23,6 +25,7 @@ const blackColor = 'var(--el-color-black)'
 
 const themeChange = (val: boolean) => {
   appStore.setIsDark(val)
+  emit('change', val)
 }
 </script>
 

+ 2 - 2
src/hooks/web/usePageLoading.ts

@@ -1,13 +1,13 @@
 import { useAppStoreWithOut } from '@/store/modules/app'
 
-const appStore = useAppStoreWithOut()
-
 export const usePageLoading = () => {
   const loadStart = () => {
+    const appStore = useAppStoreWithOut()
     appStore.setPageLoading(true)
   }
 
   const loadDone = () => {
+    const appStore = useAppStoreWithOut()
     appStore.setPageLoading(false)
   }
 

+ 2 - 2
src/hooks/web/useTitle.ts

@@ -3,10 +3,10 @@ import { isString } from '@/utils/is'
 import { useAppStoreWithOut } from '@/store/modules/app'
 import { useI18n } from '@/hooks/web/useI18n'
 
-const appStore = useAppStoreWithOut()
-
 export const useTitle = (newTitle?: string) => {
   const { t } = useI18n()
+  const appStore = useAppStoreWithOut()
+
   const title = ref(
     newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
   )

+ 1 - 2
src/layout/components/ToolHeader.vue

@@ -41,8 +41,7 @@ export default defineComponent({
         id={`${variables.namespace}-tool-header`}
         class={[
           prefixCls,
-          'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between',
-          'dark:bg-[var(--el-bg-color)]'
+          'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
         ]}
       >
         {layout.value !== 'top' ? (

+ 7 - 1
src/plugins/elementPlus/index.ts

@@ -12,7 +12,13 @@ export const setupElementPlus = (app: App<Element>) => {
     app.use(plugin)
   })
 
+  // 为了开发环境启动更快,一次性引入所有样式
+  if (import.meta.env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'true') {
+    import('element-plus/dist/index.css')
+    return
+  }
+
   components.forEach((component) => {
-    app.component(component.name, component)
+    app.component(component.name!, component)
   })
 }

+ 0 - 2
src/plugins/svgIcon/index.ts

@@ -1,3 +1 @@
 import 'virtual:svg-icons-register'
-
-import '@purge-icons/generated'

+ 14 - 0
src/store/modules/app.ts

@@ -1,6 +1,7 @@
 import { defineStore } from 'pinia'
 import { store } from '../index'
 import { setCssVar, humpToUnderline } from '@/utils'
+import { mix } from '@/utils/color'
 import { ElMessage, ComponentSize } from 'element-plus'
 
 interface AppState {
@@ -239,6 +240,7 @@ export const useAppStore = defineStore('app', {
         document.documentElement.classList.add('light')
         document.documentElement.classList.remove('dark')
       }
+      this.setPrimaryLight()
     },
     setCurrentSize(currentSize: ComponentSize) {
       this.currentSize = currentSize
@@ -253,9 +255,21 @@ export const useAppStore = defineStore('app', {
       for (const key in this.theme) {
         setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
       }
+      this.setPrimaryLight()
     },
     setFooter(footer: boolean) {
       this.footer = footer
+    },
+    setPrimaryLight() {
+      if (this.theme.elColorPrimary) {
+        const elColorPrimary = this.theme.elColorPrimary
+        const color = this.isDark ? '#000000' : '#ffffff'
+        const lightList = [3, 5, 7, 8, 9]
+        lightList.forEach((v) => {
+          setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
+        })
+        setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
+      }
     }
   },
   persist: true

+ 19 - 0
src/utils/color.ts

@@ -151,3 +151,22 @@ const subtractLight = (color: string, amount: number) => {
   const c = cc < 0 ? 0 : cc
   return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
 }
+
+/**
+ * Mixes two colors.
+ *
+ * @param {string} color1 - The first color, should be a 6-digit hexadecimal color code starting with `#`.
+ * @param {string} color2 - The second color, should be a 6-digit hexadecimal color code starting with `#`.
+ * @param {number} [weight=0.5] - The weight of color1 in the mix, should be a number between 0 and 1, where 0 represents 100% of color2, and 1 represents 100% of color1.
+ * @returns {string} The mixed color, a 6-digit hexadecimal color code starting with `#`.
+ */
+export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
+  let color = '#'
+  for (let i = 0; i <= 2; i++) {
+    const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16)
+    const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16)
+    const c = Math.round(c1 * weight + c2 * (1 - weight))
+    color += c.toString(16).padStart(2, '0')
+  }
+  return color
+}

+ 4 - 0
src/utils/index.ts

@@ -47,6 +47,10 @@ export const setCssVar = (prop: string, val: any, dom = document.documentElement
   dom.style.setProperty(prop, val)
 }
 
+export const getCssVar = (prop: string, dom = document.documentElement) => {
+  return getComputedStyle(dom).getPropertyValue(prop)
+}
+
 /**
  * 查找数组对象的某个下标
  * @param {Array} ary 查找的数组

+ 32 - 3
uno.config.ts

@@ -1,9 +1,33 @@
-import { defineConfig, toEscapedSelector as e, presetUno } from 'unocss'
+import { defineConfig, toEscapedSelector as e, presetUno, presetIcons } from 'unocss'
 import transformerVariantGroup from '@unocss/transformer-variant-group'
 
+const createPresetIcons = () => {
+  // @ts-ignore
+  if (import.meta.env.VITE_USE_ONLINE_ICON === 'true') {
+    return [
+      presetIcons({
+        prefix: ''
+      })
+    ]
+  } else {
+    return []
+  }
+}
+
 export default defineConfig({
   // ...UnoCSS options
   rules: [
+    [
+      /^overflow-ellipsis$/,
+      ([], { rawSelector }) => {
+        const selector = e(rawSelector)
+        return `
+${selector} {
+  text-overflow: ellipsis;
+}
+`
+      }
+    ],
     [
       /^custom-hover$/,
       ([], { rawSelector }) => {
@@ -100,6 +124,11 @@ ${selector}:after {
       }
     ]
   ],
-  presets: [presetUno({ dark: 'class', attributify: false })],
-  transformers: [transformerVariantGroup()]
+  presets: [presetUno({ dark: 'class', attributify: false }), ...createPresetIcons()],
+  transformers: [transformerVariantGroup()],
+  content: {
+    pipeline: {
+      include: [/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html|ts)($|\?)/]
+    }
+  }
 })

+ 44 - 27
vite.config.ts

@@ -12,6 +12,7 @@ import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
 import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
 import UnoCSS from 'unocss/vite'
+import { visualizer } from 'rollup-plugin-visualizer'
 
 // https://vitejs.dev/config/
 const root = process.cwd()
@@ -39,19 +40,23 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       }),
       VueJsx(),
       progress(),
-      createStyleImportPlugin({
-        resolves: [ElementPlusResolve()],
-        libs: [{
-          libraryName: 'element-plus',
-          esModule: true,
-          resolveStyle: (name) => {
-            if (name === 'click-outside') {
-              return ''
-            }
-            return `element-plus/es/components/${name.replace(/^el-/, '')}/style/css`
-          }
-        }]
-      }),
+      env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'false'
+        ? createStyleImportPlugin({
+            resolves: [ElementPlusResolve()],
+            libs: [
+              {
+                libraryName: 'element-plus',
+                esModule: true,
+                resolveStyle: (name) => {
+                  if (name === 'click-outside') {
+                    return ''
+                  }
+                  return `element-plus/es/components/${name.replace(/^el-/, '')}/style/css`
+                }
+              }
+            ]
+          })
+        : undefined,
       EslintPlugin({
         cache: false,
         include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
@@ -67,17 +72,19 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         svgoOptions: true
       }),
       PurgeIcons(),
-      viteMockServe({
-        ignore: /^\_/,
-        mockPath: 'mock',
-        localEnabled: !isBuild,
-        prodEnabled: isBuild,
-        injectCode: `
+      env.VITE_USE_MOCK === 'true'
+        ? viteMockServe({
+            ignore: /^\_/,
+            mockPath: 'mock',
+            localEnabled: !isBuild,
+            prodEnabled: isBuild,
+            injectCode: `
           import { setupProdMockServer } from '../mock/_createProductionServer'
 
           setupProdMockServer()
           `
-      }),
+          })
+        : undefined,
       ViteEjsPlugin({
         title: env.VITE_APP_TITLE
       }),
@@ -106,17 +113,27 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         }
       ]
     },
+    esbuild: {
+      pure: env.VITE_DROP_CONSOLE === 'true' ? ['console.log'] : undefined,
+      drop: env.VITE_DROP_DEBUGGER === 'true' ? ['debugger'] : undefined
+    },
     build: {
-      minify: 'terser',
+      target: 'es2015',
       outDir: env.VITE_OUT_DIR || 'dist',
-      sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false,
+      sourcemap: env.VITE_SOURCEMAP === 'true',
       // brotliSize: false,
-      terserOptions: {
-        compress: {
-          drop_debugger: env.VITE_DROP_DEBUGGER === 'true',
-          drop_console: env.VITE_DROP_CONSOLE === 'true'
+      rollupOptions: {
+        plugins: env.VITE_USE_BUNDLE_ANALYZER === 'true' ? [visualizer()] : undefined,
+        // 拆包
+        output: {
+          manualChunks: {
+            'vue-chunks': ['vue', 'vue-router', 'pinia', 'vue-i18n'],
+            'element-plus': ['element-plus'],
+            'wang-editor': ['@wangeditor/editor', '@wangeditor/editor-for-vue']
+          }
         }
-      }
+      },
+      cssCodeSplit: !(env.VITE_USE_CSS_SPLIT === 'false')
     },
     server: {
       port: 4000,