zq940222 пре 5 месеци
родитељ
комит
43a40ce876

+ 79 - 0
build/config/themeConfig.ts

@@ -0,0 +1,79 @@
+import { generate } from '@ant-design/colors';
+
+export const primaryColor = '#1890FF';
+
+export const darkMode = 'light';
+
+type Fn = (...arg: any) => any;
+
+type GenerateTheme = 'default' | 'dark';
+
+export interface GenerateColorsParams {
+  mixLighten: Fn;
+  mixDarken: Fn;
+  tinycolor: any;
+  color?: string;
+}
+
+export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
+  return generate(color, {
+    theme,
+  });
+}
+
+export function getThemeColors(color?: string) {
+  const tc = color || primaryColor;
+  const lightColors = generateAntColors(tc);
+  const primary = lightColors[5];
+  const modeColors = generateAntColors(primary, 'dark');
+
+  return [...lightColors, ...modeColors];
+}
+
+export function generateColors({
+  color = primaryColor,
+  mixLighten,
+  mixDarken,
+  tinycolor,
+}: GenerateColorsParams) {
+  const arr = new Array(19).fill(0);
+  const lightens = arr.map((_t, i) => {
+    return mixLighten(color, i / 5);
+  });
+
+  const darkens = arr.map((_t, i) => {
+    return mixDarken(color, i / 5);
+  });
+
+  const alphaColors = arr.map((_t, i) => {
+    return tinycolor(color)
+      .setAlpha(i / 20)
+      .toRgbString();
+  });
+
+  const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.'));
+
+  const tinycolorLightens = arr
+    .map((_t, i) => {
+      return tinycolor(color)
+        .lighten(i * 5)
+        .toHexString();
+    })
+    .filter((item) => item !== '#ffffff');
+
+  const tinycolorDarkens = arr
+    .map((_t, i) => {
+      return tinycolor(color)
+        .darken(i * 5)
+        .toHexString();
+    })
+    .filter((item) => item !== '#000000');
+  return [
+    ...lightens,
+    ...darkens,
+    ...alphaColors,
+    ...shortAlphaColors,
+    ...tinycolorDarkens,
+    ...tinycolorLightens,
+  ].filter((item) => !item.includes('-'));
+}

+ 6 - 0
build/constant.ts

@@ -0,0 +1,6 @@
+/**
+ * The name of the configuration file entered in the production environment
+ */
+export const GLOB_CONFIG_FILE_NAME = '_app.config.js';
+
+export const OUTPUT_DIR = 'dist';

+ 49 - 0
build/generate/generateModifyVars.ts

@@ -0,0 +1,49 @@
+import { primaryColor } from '../config/themeConfig';
+// import { getThemeVariables } from 'ant-design-vue/dist/theme';
+import { resolve } from 'path';
+import { generate } from '@ant-design/colors';
+import { theme } from 'ant-design-vue/lib';
+import convertLegacyToken from 'ant-design-vue/lib/theme/convertLegacyToken';
+const { defaultAlgorithm, defaultSeed } = theme;
+
+function generateAntColors(color: string, theme: 'default' | 'dark' = 'default') {
+  return generate(color, {
+    theme,
+  });
+}
+
+/**
+ * less global variable
+ */
+export function generateModifyVars() {
+  const palettes = generateAntColors(primaryColor);
+  const primary = palettes[5];
+
+  const primaryColorObj: Record<string, string> = {};
+
+  for (let index = 0; index < 10; index++) {
+    primaryColorObj[`primary-${index + 1}`] = palettes[index];
+  }
+
+  const mapToken = defaultAlgorithm(defaultSeed);
+  const v3Token = convertLegacyToken(mapToken);
+  return {
+    ...v3Token,
+    // ...modifyVars,
+    // Used for global import to avoid the need to import each style file separately
+    // reference:  Avoid repeated references
+    hack: `true; @import (reference) "${resolve('src/design/config.less')}";`,
+    'primary-color': primary,
+    ...primaryColorObj,
+    'info-color': primary,
+    'processing-color': primary,
+    'success-color': '#55D187', //  Success color
+    'error-color': '#ED6F6F', //  False color
+    'warning-color': '#EFBD47', //   Warning color
+    //'border-color-base': '#EEEEEE',
+    'font-size-base': '14px', //  Main font size
+    'border-radius-base': '2px', //  Component/float fillet
+    'link-color': primary, //   Link color
+    'app-content-background': '#fafafa', //   Link color
+  };
+}

+ 68 - 0
build/generate/icon/index.ts

@@ -0,0 +1,68 @@
+import path from 'path';
+import fs from 'fs-extra';
+import inquirer from 'inquirer';
+import colors from 'picocolors';
+import pkg from '../../../package.json';
+
+async function generateIcon() {
+  const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json');
+
+  const raw = await fs.readJSON(path.join(dir, 'collections.json'));
+
+  const collections = Object.entries(raw).map(([id, v]) => ({
+    ...(v as any),
+    id,
+  }));
+
+  const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }));
+
+  inquirer
+    .prompt([
+      {
+        type: 'list',
+        name: 'useType',
+        choices: [
+          { key: 'local', value: 'local', name: 'Local' },
+          { key: 'onLine', value: 'onLine', name: 'OnLine' },
+        ],
+        message: 'How to use icons?',
+      },
+      {
+        type: 'list',
+        name: 'iconSet',
+        choices: choices,
+        message: 'Select the icon set that needs to be generated?',
+      },
+      {
+        type: 'input',
+        name: 'output',
+        message: 'Select the icon set that needs to be generated?',
+        default: 'src/components/Icon/data',
+      },
+    ])
+    .then(async (answers) => {
+      const { iconSet, output, useType } = answers;
+      const outputDir = path.resolve(process.cwd(), output);
+      fs.ensureDir(outputDir);
+      const genCollections = collections.filter((item) => [iconSet].includes(item.id));
+      const prefixSet: string[] = [];
+      for (const info of genCollections) {
+        const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`));
+        if (data) {
+          const { prefix } = data;
+          const isLocal = useType === 'local';
+          const icons = Object.keys(data.icons).map((item) => `${isLocal ? prefix + ':' : ''}${item}`);
+
+          await fs.writeFileSync(
+            path.join(output, `icons.data.ts`),
+            `export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`
+          );
+          prefixSet.push(prefix);
+        }
+      }
+      fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite'));
+      console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`);
+    });
+}
+
+generateIcon();

+ 7 - 0
build/getConfigFileName.ts

@@ -0,0 +1,7 @@
+/**
+ * Get the configuration file variable name
+ * @param env
+ */
+export const getConfigFileName = (env: Record<string, any>) => {
+  return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
+};

+ 47 - 0
build/script/buildConf.ts

@@ -0,0 +1,47 @@
+/**
+ * 生成外部配置文件,用于生产发布后配置,无需重新打包
+ */
+import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
+import fs, { writeFileSync } from 'fs-extra';
+import colors from 'picocolors';
+
+import { getEnvConfig, getRootPath } from '../utils';
+import { getConfigFileName } from '../getConfigFileName';
+
+import pkg from '../../package.json';
+
+interface CreateConfigParams {
+  configName: string;
+  config: any;
+  configFileName?: string;
+}
+
+function createConfig(params: CreateConfigParams) {
+  const { configName, config, configFileName } = params;
+  try {
+    const windowConf = `window.${configName}`;
+    // Ensure that the variable will not be modified
+    let configStr = `${windowConf}=${JSON.stringify(config)};`;
+    configStr += `
+      Object.freeze(${windowConf});
+      Object.defineProperty(window, "${configName}", {
+        configurable: false,
+        writable: false,
+      });
+    `.replace(/\s/g, '');
+
+    fs.mkdirp(getRootPath(OUTPUT_DIR));
+    writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
+
+    console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
+    console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
+  } catch (error) {
+    console.log(colors.red('configuration file configuration file failed to package:\n' + error));
+  }
+}
+
+export function runBuildConfig() {
+  const config = getEnvConfig();
+  const configFileName = getConfigFileName(config);
+  createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
+}

+ 23 - 0
build/script/postBuild.ts

@@ -0,0 +1,23 @@
+// #!/usr/bin/env node
+
+import { runBuildConfig } from './buildConf';
+import colors from 'picocolors';
+
+import pkg from '../../package.json';
+
+export const runBuild = async () => {
+  try {
+    const argvList = process.argv.splice(2);
+
+    // Generate configuration file
+    if (!argvList.includes('disabled-config')) {
+      runBuildConfig();
+    }
+
+    console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
+  } catch (error) {
+    console.log(colors.red('vite build error:\n' + error));
+    process.exit(1);
+  }
+};
+runBuild();

+ 94 - 0
build/utils.ts

@@ -0,0 +1,94 @@
+import fs from 'fs';
+import path from 'path';
+import dotenv from 'dotenv';
+
+export function isDevFn(mode: string): boolean {
+  return mode === 'development';
+}
+
+export function isProdFn(mode: string): boolean {
+  return mode === 'production';
+}
+
+/**
+ * Whether to generate package preview
+ */
+export function isReportMode(): boolean {
+  return process.env.REPORT === 'true';
+}
+
+// Read all environment variable configuration files to process.env
+export function wrapperEnv(envConf: Recordable): ViteEnv {
+  const ret: any = {};
+
+  for (const envName of Object.keys(envConf)) {
+    let realName = envConf[envName].replace(/\\n/g, '\n');
+    realName = realName === 'true' ? true : realName === 'false' ? false : realName;
+
+    if (envName === 'VITE_PORT') {
+      realName = Number(realName);
+    }
+    if (envName === 'VITE_PROXY' && realName) {
+      try {
+        realName = JSON.parse(realName.replace(/'/g, '"'));
+      } catch (error) {
+        realName = '';
+      }
+    }
+    ret[envName] = realName;
+    if (typeof realName === 'string') {
+      process.env[envName] = realName;
+    } else if (typeof realName === 'object') {
+      process.env[envName] = JSON.stringify(realName);
+    }
+  }
+  return ret;
+}
+
+/**
+ * 获取当前环境下生效的配置文件名
+ */
+function getConfFiles() {
+  const script = process.env.npm_lifecycle_script;
+  // update-begin--author:liaozhiyang---date:20240326---for:【QQYUN-8690】修正获取当前环境下的文件名
+  const reg = new RegExp('NODE_ENV=([a-z_\\d]+)');
+  // update-end--author:liaozhiyang---date:20240326---for:【QQYUN-8690】修正获取当前环境下的文件名
+  const result = reg.exec(script as string) as any;
+  if (result) {
+    const mode = result[1] as string;
+    return ['.env', `.env.${mode}`];
+  }
+  return ['.env', '.env.production'];
+}
+
+/**
+ * Get the environment variables starting with the specified prefix
+ * @param match prefix
+ * @param confFiles ext
+ */
+export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
+  let envConfig = {};
+  confFiles.forEach((item) => {
+    try {
+      const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
+      envConfig = { ...envConfig, ...env };
+    } catch (e) {
+      console.error(`Error in parsing ${item}`, e);
+    }
+  });
+  const reg = new RegExp(`^(${match})`);
+  Object.keys(envConfig).forEach((key) => {
+    if (!reg.test(key)) {
+      Reflect.deleteProperty(envConfig, key);
+    }
+  });
+  return envConfig;
+}
+
+/**
+ * Get user root directory
+ * @param dir file path
+ */
+export function getRootPath(...dir: string[]) {
+  return path.resolve(process.cwd(), ...dir);
+}

+ 36 - 0
build/vite/plugin/compress.ts

@@ -0,0 +1,36 @@
+/**
+ * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
+ * https://github.com/anncwb/vite-plugin-compression
+ */
+import type { PluginOption } from 'vite';
+import compressPlugin from 'vite-plugin-compression';
+
+export function configCompressPlugin(compress: 'gzip' | 'brotli' | 'none', deleteOriginFile = false): PluginOption | PluginOption[] {
+  const compressList = compress.split(',');
+
+  const plugins: PluginOption[] = [];
+
+  if (compressList.includes('gzip')) {
+    plugins.push(
+      compressPlugin({
+        verbose: true,
+        disable: false,
+        threshold: 10240,
+        algorithm: 'gzip',
+        ext: '.gz',
+        deleteOriginFile,
+      })
+    );
+  }
+
+  if (compressList.includes('brotli')) {
+    plugins.push(
+      compressPlugin({
+        ext: '.br',
+        algorithm: 'brotliCompress',
+        deleteOriginFile,
+      })
+    );
+  }
+  return plugins;
+}

+ 40 - 0
build/vite/plugin/html.ts

@@ -0,0 +1,40 @@
+/**
+ * Plugin to minimize and use ejs template syntax in index.html.
+ * https://github.com/anncwb/vite-plugin-html
+ */
+import type { PluginOption } from 'vite';
+import { createHtmlPlugin } from 'vite-plugin-html';
+import pkg from '../../../package.json';
+import { GLOB_CONFIG_FILE_NAME } from '../../constant';
+
+export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
+  const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
+
+  const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
+
+  const getAppConfigSrc = () => {
+    return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
+  };
+
+  const htmlPlugin: PluginOption[] = createHtmlPlugin({
+    minify: isBuild,
+    inject: {
+      // 修改模板html的标题
+      data: {
+        title: VITE_GLOB_APP_TITLE,
+      },
+      // 将app.config.js文件注入到模板html中
+      tags: isBuild
+        ? [
+            {
+              tag: 'script',
+              attrs: {
+                src: getAppConfigSrc(),
+              },
+            },
+          ]
+        : [],
+    },
+  });
+  return htmlPlugin;
+}

+ 35 - 0
build/vite/plugin/imagemin.ts

@@ -0,0 +1,35 @@
+// 【图片压缩插件】
+// Image resource files used to compress the output of the production environment
+// https://github.com/anncwb/vite-plugin-imagemin
+import viteImagemin from 'vite-plugin-imagemin';
+
+export function configImageminPlugin() {
+  const plugin = viteImagemin({
+    gifsicle: {
+      optimizationLevel: 7,
+      interlaced: false,
+    },
+    optipng: {
+      optimizationLevel: 7,
+    },
+    mozjpeg: {
+      quality: 20,
+    },
+    pngquant: {
+      quality: [0.8, 0.9],
+      speed: 4,
+    },
+    svgo: {
+      plugins: [
+        {
+          name: 'removeViewBox',
+        },
+        {
+          name: 'removeEmptyAttrs',
+          active: false,
+        },
+      ],
+    },
+  });
+  return plugin;
+}

+ 70 - 0
build/vite/plugin/index.ts

@@ -0,0 +1,70 @@
+import { PluginOption } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import vueJsx from '@vitejs/plugin-vue-jsx';
+import purgeIcons from 'vite-plugin-purge-icons';
+import UnoCSS from 'unocss/vite';
+import { presetTypography, presetUno } from 'unocss';
+
+// 本地调试https配置方法
+import VitePluginCertificate from 'vite-plugin-mkcert';
+//[issues/555]开发环境,vscode断点调试,文件或行数对不上
+import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus';
+import { configHtmlPlugin } from './html';
+import { configMockPlugin } from './mock';
+import { configCompressPlugin } from './compress';
+import { configVisualizerConfig } from './visualizer';
+import { configThemePlugin } from './theme';
+import { configSvgIconsPlugin } from './svgSprite';
+// //预编译加载插件(不支持vite3作废)
+// import OptimizationPersist from 'vite-plugin-optimize-persist';
+// import PkgConfig from 'vite-plugin-package-config';
+
+export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
+  const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
+
+  const vitePlugins: (PluginOption | PluginOption[])[] = [
+    // have to
+    vue(),
+    // have to
+    vueJsx(),
+    // support name
+    vueSetupExtend(),
+    // @ts-ignore
+    VitePluginCertificate({
+      source: 'coding',
+    }),
+  ];
+
+  vitePlugins.push(UnoCSS({ presets: [presetUno(), presetTypography()] }));
+
+  // vite-plugin-html
+  vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
+
+  // vite-plugin-svg-icons
+  vitePlugins.push(configSvgIconsPlugin(isBuild));
+
+  // vite-plugin-mock
+  VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
+
+  // vite-plugin-purge-icons
+  vitePlugins.push(purgeIcons());
+
+  // rollup-plugin-visualizer
+  vitePlugins.push(configVisualizerConfig());
+
+  // vite-plugin-theme
+  vitePlugins.push(configThemePlugin(isBuild));
+
+  // The following plugins only work in the production environment
+  if (isBuild) {
+    
+    // rollup-plugin-gzip
+    vitePlugins.push(configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE));
+
+  }
+
+  // //vite-plugin-theme【预编译加载插件,解决vite首次打开界面加载慢问题】
+  // vitePlugins.push(PkgConfig());
+  // vitePlugins.push(OptimizationPersist());
+  return vitePlugins;
+}

+ 19 - 0
build/vite/plugin/mock.ts

@@ -0,0 +1,19 @@
+/**
+ * Mock plugin for development and production.
+ * https://github.com/anncwb/vite-plugin-mock
+ */
+import { viteMockServe } from 'vite-plugin-mock';
+
+export function configMockPlugin(isBuild: boolean) {
+  return viteMockServe({
+    ignore: /^\_/,
+    mockPath: 'mock',
+    localEnabled: !isBuild,
+    prodEnabled: isBuild,
+    injectCode: `
+      import { setupProdMockServer } from '../mock/_createProductionServer';
+
+      setupProdMockServer();
+      `,
+  });
+}

+ 82 - 0
build/vite/plugin/styleImport.ts

@@ -0,0 +1,82 @@
+/**
+ * 【样式按需加载插件 ——主要处理antd的样式】
+ *  Introduces component library styles on demand.
+ * https://github.com/anncwb/vite-plugin-style-import
+ */
+import { createStyleImportPlugin } from 'vite-plugin-style-import';
+
+export function configStyleImportPlugin(_isBuild: boolean) {
+  if (!_isBuild) {
+    return [];
+  }
+  const styleImportPlugin = createStyleImportPlugin({
+    libs: [
+      {
+        libraryName: 'ant-design-vue',
+        esModule: true,
+        resolveStyle: (name) => {
+          // 这里是无需额外引入样式文件的“子组件”列表
+          const ignoreList = [
+            'anchor-link',
+            'sub-menu',
+            'menu-item',
+            'menu-divider',
+            'menu-item-group',
+            'breadcrumb-item',
+            'breadcrumb-separator',
+            'form-item',
+            'step',
+            'select-option',
+            'select-opt-group',
+            'card-grid',
+            'card-meta',
+            'collapse-panel',
+            'descriptions-item',
+            'list-item',
+            'list-item-meta',
+            'table-column',
+            'table-column-group',
+            'tab-pane',
+            'tab-content',
+            'timeline-item',
+            'tree-node',
+            'skeleton-input',
+            'skeleton-avatar',
+            'skeleton-title',
+            'skeleton-paragraph',
+            'skeleton-image',
+            'skeleton-button',
+          ];
+          // 这里是需要额外引入样式的子组件列表
+          // 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
+          const replaceList = {
+            'typography-text': 'typography',
+            'typography-title': 'typography',
+            'typography-paragraph': 'typography',
+            'typography-link': 'typography',
+            'dropdown-button': 'dropdown',
+            'input-password': 'input',
+            'input-search': 'input',
+            'input-group': 'input',
+            'radio-group': 'radio',
+            'checkbox-group': 'checkbox',
+            'layout-sider': 'layout',
+            'layout-content': 'layout',
+            'layout-footer': 'layout',
+            'layout-header': 'layout',
+            'month-picker': 'date-picker',
+            'range-picker': 'date-picker',
+            'image-preview-group': 'image',
+          };
+
+          return ignoreList.includes(name)
+            ? ''
+            : replaceList.hasOwnProperty(name)
+            ? `ant-design-vue/es/${replaceList[name]}/style/index`
+            : `ant-design-vue/es/${name}/style/index`;
+        },
+      },
+    ],
+  });
+  return styleImportPlugin;
+}

+ 17 - 0
build/vite/plugin/svgSprite.ts

@@ -0,0 +1,17 @@
+/**
+ *  Vite Plugin for fast creating SVG sprites.
+ * https://github.com/anncwb/vite-plugin-svg-icons
+ */
+
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
+import path from 'path';
+
+export function configSvgIconsPlugin(isBuild: boolean) {
+  const svgIconsPlugin = createSvgIconsPlugin({
+    iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
+    svgoOptions: isBuild,
+    // default
+    symbolId: 'icon-[dir]-[name]',
+  });
+  return svgIconsPlugin;
+}

+ 100 - 0
build/vite/plugin/theme.ts

@@ -0,0 +1,100 @@
+/**
+ * Vite plugin for website theme color switching
+ * https://github.com/anncwb/vite-plugin-theme
+ */
+import type { PluginOption } from 'vite';
+import path from 'path';
+import { viteThemePlugin, antdDarkThemePlugin, mixLighten, mixDarken, tinycolor } from '@rys-fe/vite-plugin-theme';
+import { getThemeColors, generateColors } from '../../config/themeConfig';
+import { generateModifyVars } from '../../generate/generateModifyVars';
+
+export function configThemePlugin(isBuild: boolean): PluginOption[] {
+  const colors = generateColors({
+    mixDarken,
+    mixLighten,
+    tinycolor,
+  });
+
+  // update-begin-修复编译后主题色切换不生效黑屏的问题-----------------------
+  // https://github.com/vbenjs/vue-vben-admin/issues/1445
+  // 抽取出viteThemePlugin插件,下方会根据不同环境设置enforce
+  const vite_theme_plugin = viteThemePlugin({
+    resolveSelector: (s) => {
+      s = s.trim();
+      switch (s) {
+        case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
+          return '.ant-steps-item-icon > .ant-steps-icon';
+        case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
+        case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
+        case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
+          return s;
+        case '.ant-steps-item-icon > .ant-steps-icon':
+          return s;
+        case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
+          return s;
+        default:
+          if (s.indexOf('.ant-btn') >= -1) {
+            // 按钮被重新定制过,需要过滤掉class防止覆盖
+            return s;
+          }
+      }
+      return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
+    },
+    colorVariables: [...getThemeColors(), ...colors],
+  });
+  vite_theme_plugin.forEach(function (item) {
+    //对vite:theme插件特殊配置
+    if ('vite:theme' === item.name) {
+      // 打包时去除enforce: "post",vite 2.6.x适配,否则生成app-theme-style为空,因为async transform(code, id) {的code没有正确获取
+      if (isBuild) {
+        delete item.enforce;
+      }
+    }
+  });
+  // update-end-修复编译后主题色切换不生效黑屏的问题-----------------------
+
+  const plugin = [
+    vite_theme_plugin,
+    antdDarkThemePlugin({
+      preloadFiles: [
+        // path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/reset.css'),
+        //path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.dark.less'),
+        path.resolve(process.cwd(), 'src/design/index.less'),
+      ],
+      filter: (id) => (isBuild ? !id.endsWith('antd.less') : true),
+      // extractCss: false,
+      darkModifyVars: {
+        ...generateModifyVars(true),
+        'text-color': '#c9d1d9',
+        'primary-1': 'rgb(255 255 255 / 8%)',
+        'text-color-base': '#c9d1d9',
+        'component-background': '#151515',
+        'heading-color': 'rgb(255 255 255 / 65%)',
+        // black: '#0e1117',
+        // #8b949e
+        'text-color-secondary': '#8b949e',
+        'border-color-base': '#303030',
+        'header-light-bottom-border-color': '#303030',
+        // 'border-color-split': '#30363d',
+        'item-active-bg': '#111b26',
+        'app-content-background': '#1e1e1e',
+        'tree-node-selected-bg': '#11263c',
+
+        'alert-success-border-color': '#274916',
+        'alert-success-bg-color': '#162312',
+        'alert-success-icon-color': '#49aa19',
+        'alert-info-border-color': '#153450',
+        'alert-info-bg-color': '#111b26',
+        'alert-info-icon-color': '#177ddc',
+        'alert-warning-border-color': '#594214',
+        'alert-warning-bg-color': '#2b2111',
+        'alert-warning-icon-color': '#d89614',
+        'alert-error-border-color': '#58181c',
+        'alert-error-bg-color': '#2a1215',
+        'alert-error-icon-color': '#a61d24',
+      },
+    }),
+  ];
+
+  return plugin as unknown as PluginOption[];
+}

+ 17 - 0
build/vite/plugin/visualizer.ts

@@ -0,0 +1,17 @@
+/**
+ * Package file volume analysis
+ */
+import visualizer from 'rollup-plugin-visualizer';
+import { isReportMode } from '../../utils';
+
+export function configVisualizerConfig() {
+  if (isReportMode()) {
+    return visualizer({
+      filename: './node_modules/.cache/visualizer/stats.html',
+      open: true,
+      gzipSize: true,
+      brotliSize: true,
+    }) as Plugin;
+  }
+  return [];
+}

+ 34 - 0
build/vite/proxy.ts

@@ -0,0 +1,34 @@
+/**
+ * Used to parse the .env.development proxy configuration
+ */
+import type { ProxyOptions } from 'vite';
+
+type ProxyItem = [string, string];
+
+type ProxyList = ProxyItem[];
+
+type ProxyTargetList = Record<string, ProxyOptions>;
+
+const httpsRE = /^https:\/\//;
+
+/**
+ * Generate proxy
+ * @param list
+ */
+export function createProxy(list: ProxyList = []) {
+  const ret: ProxyTargetList = {};
+  for (const [prefix, target] of list) {
+    const isHttps = httpsRE.test(target);
+
+    // https://github.com/http-party/node-http-proxy#options
+    ret[prefix] = {
+      target: target,
+      changeOrigin: true,
+      ws: true,
+      rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
+      // https is require secure=false
+      ...(isHttps ? { secure: false } : {}),
+    };
+  }
+  return ret;
+}