Browse Source

feat: the production environment can be dynamically configured

nebv 4 years ago
parent
commit
bb3b8f817d

+ 4 - 0
.env.production

@@ -9,3 +9,7 @@ VITE_GLOB_API_URL=/api
 
 # Interface prefix
 VITE_GLOB_API_URL_PREFIX=
+
+
+# TODO use Cdn
+VITE_USE_CDN = true

+ 2 - 1
README.md

@@ -218,6 +218,7 @@ yarn clean:lib # 删除node_modules,兼容window系统
 - [x] 图表库
 - [x] 数字动画
 - [x] 首屏加载等待动画
+- [x] 抽取生产环境配置文件
 
 ## 正在开发的功能
 
@@ -228,7 +229,7 @@ yarn clean:lib # 删除node_modules,兼容window系统
 - [ ] 主题配置
 - [ ] 黑暗主题
 - [ ] 打包 Gzip
-- [ ] 抽取生产环境配置文件
+- [ ] 打包 CDN
 - [ ] 系统性能优化
 
 更多组件/功能/建议/bug/欢迎提交 pr 或者 issue

+ 21 - 0
build/config/vite/cdn.ts

@@ -0,0 +1,21 @@
+const css = ['//cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.css'];
+
+// TODO use esm?
+const js = [
+  '//cdn.bootcdn.net/ajax/libs/vue/3.0.0/vue.global.prod.js',
+  '//cdn.bootcdn.net/ajax/libs/vue-router/4.0.0-beta.13/vue-router.global.min.js',
+  '//cdn.bootcdn.net/ajax/libs/vuex/4.0.0-beta.4/vuex.global.prod.js',
+  '//cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js',
+  '//cdn.bootcdn.net/ajax/libs/qs/6.9.4/qs.min.js',
+  '//cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.js',
+  // '//cdn.bootcdn.net/ajax/libs/lodash.js/4.17.15/lodash.min.js',
+  // '//cdn.bootcdn.net/ajax/libs/crypto-js/3.3.0/crypto-js.min.js',
+  // '//cdn.bootcdn.net/ajax/libs/vue-i18n/8.18.1/vue-i18n.min.js',
+];
+
+export const externals = ['vue', 'vuex', 'vue-router', 'axios', 'qs', 'nprogress'];
+
+export const cdnConf = {
+  css,
+  js,
+};

+ 0 - 10
build/config/vite/env.ts

@@ -1,10 +0,0 @@
-import moment from 'moment';
-// @ts-ignore
-import pkg from '../../../package.json';
-export function setupBasicEnv() {
-  // version
-  process.env.VITE_VERSION = (pkg as any).version;
-  // build time
-  process.env.VITE_APP_BUILD_TIME = moment().format('YYYY-MM-DD HH:mm:ss');
-  process.env.VITE_BUILD_SHORT_TIME = moment().format('MMDDHHmmss');
-}

+ 1 - 0
build/constant.ts

@@ -0,0 +1 @@
+export const GLOB_CONFIG_FILE_NAME = '_app.config.js';

+ 5 - 0
build/getShortName.ts

@@ -0,0 +1,5 @@
+export const getShortName = (env: any) => {
+  return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
+    .toUpperCase()
+    .replace(/\s/g, '');
+};

+ 36 - 0
build/jsc.js

@@ -0,0 +1,36 @@
+// js调用cli 兼容调用ts
+
+const { sh } = require('tasksfile');
+const { argv } = require('yargs');
+
+let command = ``;
+
+Object.keys(argv).forEach((key) => {
+  if (!/^\$/.test(key) && key !== '_') {
+    // @ts-ignore
+    if (argv[key]) {
+      command += `--${key} `;
+    }
+  }
+});
+
+// 执行任务名称
+let taskList = argv._;
+
+let NODE_ENV = process.env.NODE_ENV || 'development';
+
+if (taskList.includes('build') || taskList.includes('report') || taskList.includes('preview')) {
+  NODE_ENV = 'production';
+}
+
+if (taskList && Array.isArray(taskList) && taskList.length) {
+  sh(
+    `cross-env NODE_ENV=${NODE_ENV} ts-node --project  ./build/tsconfig.json ./build/script/cli.ts ${taskList.join(
+      ' '
+    )} ${command}`,
+    {
+      async: true,
+      nopipe: true,
+    }
+  );
+}

+ 28 - 0
build/script/build.ts

@@ -0,0 +1,28 @@
+// #!/usr/bin/env node
+
+import { sh } from 'tasksfile';
+import { argv } from 'yargs';
+import { runBuildConfig } from './buildConf';
+import { runUpdateHtml } from './updateHtml';
+import { errorConsole, successConsole } from '../utils';
+
+export const runBuild = async () => {
+  try {
+    const argvList = argv._;
+    let cmd = `cross-env NODE_ENV=production vite build`;
+    await sh(cmd, {
+      async: true,
+      nopipe: true,
+    });
+
+    // Generate configuration file
+    if (!argvList.includes('no-conf')) {
+      await runBuildConfig();
+    }
+    await runUpdateHtml();
+    successConsole('Vite Build successfully!');
+  } catch (error) {
+    errorConsole('Vite Build Error\n' + error);
+    process.exit(1);
+  }
+};

+ 44 - 0
build/script/buildConf.ts

@@ -0,0 +1,44 @@
+import { GLOB_CONFIG_FILE_NAME } from '../constant';
+import fs, { writeFileSync } from 'fs-extra';
+
+import viteConfig from '../../vite.config';
+import { errorConsole, successConsole, getCwdPath, getEnvConfig } from '../utils';
+
+const getShortName = (env: any) => {
+  return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
+    .toUpperCase()
+    .replace(/\s/g, '');
+};
+
+function createConfig(
+  {
+    configName,
+    config,
+    configFileName = GLOB_CONFIG_FILE_NAME,
+  }: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} }
+) {
+  try {
+    const windowConf = `window.${configName}`;
+    const outDir = viteConfig.outDir || 'dist';
+    const configStr = `${windowConf}=${JSON.stringify(config)};
+
+      Object.freeze(${windowConf});
+      Object.defineProperty(window, "${configName}", {
+        configurable: false,
+        writable: false,
+      });
+    `;
+    fs.mkdirp(getCwdPath(outDir));
+    writeFileSync(getCwdPath(`${outDir}/${configFileName}`), configStr);
+
+    successConsole('The configuration file is build successfully!');
+  } catch (error) {
+    errorConsole('Configuration file configuration file failed to package\n' + error);
+  }
+}
+
+export function runBuildConfig() {
+  const config = getEnvConfig();
+  const configFileName = getShortName(config);
+  createConfig({ config, configName: configFileName });
+}

+ 5 - 19
build/script/changelog.ts

@@ -1,14 +1,11 @@
 // #!/usr/bin/env node
 
 import { sh } from 'tasksfile';
-import chalk from 'chalk';
+import { errorConsole, successConsole } from '../utils';
 
-const createChangeLog = async () => {
+export const runChangeLog = async () => {
   try {
     let cmd = `conventional-changelog -p custom-config -i CHANGELOG.md -s -r 0 `;
-    // if (shell.which('git')) {
-    //   cmd += '&& git add CHANGELOG.md';
-    // }
     await sh(cmd, {
       async: true,
       nopipe: true,
@@ -18,21 +15,10 @@ const createChangeLog = async () => {
       async: true,
       nopipe: true,
     });
-    console.log(
-      chalk.blue.bold('****************  ') +
-        chalk.green.bold('CHANGE_LOG generated successfully!') +
-        chalk.blue.bold('  ****************')
-    );
+    successConsole('CHANGE_LOG.md generated successfully!');
   } catch (error) {
-    console.log(
-      chalk.blue.red('****************  ') +
-        chalk.green.red('CHANGE_LOG generated error\n' + error) +
-        chalk.blue.red('  ****************')
-    );
+    errorConsole('CHANGE_LOG.md generated error\n' + error);
+
     process.exit(1);
   }
 };
-createChangeLog();
-module.exports = {
-  createChangeLog,
-};

+ 45 - 0
build/script/cli.ts

@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+
+import chalk from 'chalk';
+import { argv } from 'yargs';
+
+import { runChangeLog } from './changelog';
+import { runPostInstall } from './postinstall';
+import { runPreview } from './preview';
+import { runPreserve } from './preserve';
+import { runBuild } from './build';
+
+const task = (argv._ || [])[0];
+
+console.log('Run Task: ' + chalk.cyan(task));
+
+switch (task) {
+  // change log
+  case 'log':
+    runChangeLog();
+    break;
+
+  case 'build':
+    runBuild();
+    break;
+
+  case 'preserve':
+    runPreserve();
+    break;
+
+  case 'postinstall':
+    runPostInstall();
+    break;
+
+  case 'preview':
+    runPreview();
+    break;
+
+  // TODO
+  case 'gzip':
+    break;
+  default:
+    break;
+}
+
+export default {};

+ 12 - 0
build/script/hm.ts

@@ -0,0 +1,12 @@
+// 百度统计代码 用于站点部署
+// 只在build:site开启
+export const hmScript = `<script>
+var _hmt = _hmt || [];
+(function() {
+  var hm = document.createElement("script");
+  hm.src = "https://hm.baidu.com/hm.js?384d6046e02f6ac4ea075357bd0e9b43";
+  var s = document.getElementsByTagName("script")[0];
+  s.parentNode.insertBefore(hm, s);
+})();
+</script>
+`;

+ 8 - 3
build/script/postinstall.ts

@@ -2,9 +2,14 @@ import { exec, which } from 'shelljs';
 
 function ignoreCaseGit() {
   try {
-    if (which('git')) {
+    if (which('git').code === 0) {
       exec('git config core.ignorecase false ');
     }
-  } catch (error) {}
+  } catch (error) {
+    console.log(error);
+  }
+}
+
+export function runPostInstall() {
+  ignoreCaseGit();
 }
-ignoreCaseGit();

+ 34 - 30
build/script/preserve.ts

@@ -1,53 +1,57 @@
-// 是否需要更新依赖,防止package.json更新了依赖,其他人获取代码后没有install
+// Do you need to update the dependencies to prevent package.json from updating the dependencies, and no install after others get the code
 
 import path from 'path';
 import fs from 'fs-extra';
 import { isEqual } from 'lodash';
-import chalk from 'chalk';
 import { sh } from 'tasksfile';
+import { successConsole, errorConsole } from '../utils';
 
 const resolve = (dir: string) => {
   return path.resolve(process.cwd(), dir);
 };
 
+const reg = /[\u4E00-\u9FA5\uF900-\uFA2D]/;
+
 let NEED_INSTALL = false;
 
-fs.mkdirp(resolve('build/.cache'));
-function checkPkgUpdate() {
-  const pkg = require('../../package.json');
-  const { dependencies, devDependencies } = pkg;
-  const depsFile = resolve('build/.cache/deps.json');
-  if (!fs.pathExistsSync(depsFile)) {
-    NEED_INSTALL = true;
-    return;
+export async function runPreserve() {
+  const cwdPath = process.cwd();
+  if (reg.test(cwdPath)) {
+    errorConsole(
+      'Do not include Chinese, Japanese or Korean in the full path of the project directory, please modify the directory name and run again!'
+    );
+    errorConsole('项目目录全路径请勿包含中文、日文、韩文,请修改目录名后再次重新运行!');
+    process.exit(1);
   }
-  const depsJson = require('../.cache/deps.json');
 
-  if (!isEqual(depsJson, { dependencies, devDependencies })) {
-    NEED_INSTALL = true;
-  }
-}
-checkPkgUpdate();
+  fs.mkdirp(resolve('build/.cache'));
+  function checkPkgUpdate() {
+    const pkg = require('../../package.json');
+    const { dependencies, devDependencies } = pkg;
+    const depsFile = resolve('build/.cache/deps.json');
+    if (!fs.pathExistsSync(depsFile)) {
+      NEED_INSTALL = true;
+      return;
+    }
+    const depsJson = require('../.cache/deps.json');
 
-(async () => {
+    if (!isEqual(depsJson, { dependencies, devDependencies })) {
+      NEED_INSTALL = true;
+    }
+  }
+  checkPkgUpdate();
   if (NEED_INSTALL) {
-    console.log(
-      chalk.blue.bold('****************  ') +
-        chalk.red.bold('检测到依赖变化,正在安装依赖(Tip: 项目首次运行也会执行)!') +
-        chalk.blue.bold('  ****************')
+    // no error
+    successConsole(
+      'A dependency change is detected, and the dependency is being installed to ensure that the dependency is consistent! (Tip: The project will be executed for the first time)!'
     );
     try {
-      // 从代码执行貌似不会自动读取.npmrc 所以手动加上源地址
-      // await run('yarn install --registry=https://registry.npm.taobao.org ', {
-      await sh('yarn install ', {
+      await sh('npm run bootstrap ', {
         async: true,
         nopipe: true,
       });
-      console.log(
-        chalk.blue.bold('****************  ') +
-          chalk.green.bold('依赖安装成功,正在运行!') +
-          chalk.blue.bold('  ****************')
-      );
+
+      successConsole('Dependency installation is successful, start running the project!');
 
       const pkg = require('../../package.json');
       const { dependencies, devDependencies } = pkg;
@@ -64,4 +68,4 @@ checkPkgUpdate();
       }
     } catch (error) {}
   }
-})();
+}

+ 2 - 7
build/script/preview.ts

@@ -11,7 +11,7 @@ import { getIPAddress } from '../utils';
 const BUILD = 1;
 const NO_BUILD = 2;
 
-// 启动服务器
+// start server
 const startApp = () => {
   const port = 9680;
   portfinder.basePort = port;
@@ -23,7 +23,6 @@ const startApp = () => {
     if (err) {
       throw err;
     } else {
-      // const publicPath = process.env.BASE_URL;
       app.listen(port, function () {
         const empty = '    ';
         const common = `The preview program is already running:
@@ -36,7 +35,7 @@ const startApp = () => {
   });
 };
 
-const preview = async () => {
+export const runPreview = async () => {
   const prompt = inquirer.prompt({
     type: 'list',
     message: 'Please select a preview method',
@@ -61,7 +60,3 @@ const preview = async () => {
   }
   startApp();
 };
-
-(() => {
-  preview();
-})();

+ 98 - 0
build/script/updateHtml.ts

@@ -0,0 +1,98 @@
+import { readFileSync, writeFileSync, existsSync } from 'fs-extra';
+import viteConfig, { htmlConfig } from '../../vite.config';
+import { getCwdPath, successConsole, errorConsole } from '../utils';
+import { GLOB_CONFIG_FILE_NAME } from '../constant';
+import { hmScript } from './hm';
+const pkg = require('../../package.json');
+
+const { title, addHm, cdnConf, useCdn } = htmlConfig;
+
+function injectTitle(html: string, htmlTitle: string) {
+  if (/<\/title>/.test(html)) {
+    return html.replace(/<\/title>/, `${htmlTitle}</title>`);
+  }
+  return html;
+}
+
+function injectConfigScript(html: string) {
+  const tag = `\t\t<script  src='${viteConfig.base || './'}${GLOB_CONFIG_FILE_NAME}?v=${
+    pkg.version
+  }-${new Date().getTime()}'></script>`;
+
+  if (/<\/head>/.test(html)) {
+    return html.replace(/<\/head>/, `${tag}\n\t\t</head>`);
+  }
+  return html;
+}
+
+function injectHmScript(html: string) {
+  if (/<head>/.test(html)) {
+    return html.replace(/<head>/, `<head>\n${hmScript}`);
+  }
+  return html;
+}
+
+function injectCdnCss(html: string) {
+  if (!cdnConf) return html;
+  const { css } = cdnConf;
+  if (!css || css.length === 0) return html;
+
+  let cdnCssTag = '';
+  for (const cssLink of css) {
+    cdnCssTag += `<link rel="stylesheet" href="${cssLink}">`;
+  }
+  if (/<\/head>/.test(html)) {
+    return html.replace(/<\/head>/, `${cdnCssTag}\n\t\t</head>`);
+  }
+  return html;
+}
+
+function injectCdnjs(html: string) {
+  if (!cdnConf) return html;
+  const { js } = cdnConf;
+  if (!js || js.length === 0) return html;
+
+  let cdnJsTag = '';
+  for (const src of js) {
+    // TODO
+    // <script type="importmap">
+    // { "imports": {
+    //   "vue":        "https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.esm-browser.js",
+    //   "vue-router": "https://cdnjs.cloudflare.com/ajax/libs/vue-router/4.0.0-alpha.13/vue-router.esm.js",
+    //   "vuex":       "https://cdnjs.cloudflare.com/ajax/libs/vuex/4.0.0-beta.2/vuex.esm-browser.js"
+    // } }
+    // </script>
+    cdnJsTag += `\t<script type="text/javascript" src="${src}"></script>\n`;
+  }
+  if (/<\/body>/.test(html)) {
+    return html.replace(/<\/body>/, `${cdnJsTag}\n</body>`);
+  }
+  return html;
+}
+
+export async function runUpdateHtml() {
+  const outDir = viteConfig.outDir || 'dist';
+  const indexPath = getCwdPath(outDir, 'index.html');
+  if (!existsSync(`${indexPath}`)) {
+    return;
+  }
+  try {
+    let processedHtml = '';
+    const rawHtml = readFileSync(indexPath, 'utf-8');
+    processedHtml = rawHtml;
+    processedHtml = injectTitle(processedHtml, title);
+    processedHtml = injectConfigScript(processedHtml);
+    if (addHm) {
+      processedHtml = injectHmScript(processedHtml);
+    }
+    if (useCdn) {
+      processedHtml = injectCdnCss(processedHtml);
+      processedHtml = injectCdnjs(processedHtml);
+    }
+
+    writeFileSync(indexPath, processedHtml);
+    successConsole('Update Html Successfully!');
+  } catch (error) {
+    errorConsole('Update Html Error\n' + error);
+  }
+}

+ 51 - 0
build/utils.ts

@@ -1,6 +1,9 @@
 import fs from 'fs';
+import path from 'path';
 import { networkInterfaces } from 'os';
 import dotenv from 'dotenv';
+import chalk from 'chalk';
+
 export const isFunction = (arg: unknown): arg is (...args: any[]) => any =>
   typeof arg === 'function';
 export const isRegExp = (arg: unknown): arg is RegExp =>
@@ -72,6 +75,8 @@ export interface ViteEnv {
   VITE_USE_MOCK: boolean;
   VITE_PUBLIC_PATH: string;
   VITE_PROXY: [string, string][];
+  VITE_GLOB_APP_TITLE: string;
+  VITE_USE_CDN: boolean;
 }
 
 export function loadEnv(): ViteEnv {
@@ -100,3 +105,49 @@ export function loadEnv(): ViteEnv {
   }
   return ret;
 }
+
+export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) {
+  let envConfig = {};
+  confFiles.forEach((item) => {
+    try {
+      const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
+
+      envConfig = { ...envConfig, ...env };
+    } catch (error) {}
+  });
+  Object.keys(envConfig).forEach((key) => {
+    const reg = new RegExp(`^(${match})`);
+    if (!reg.test(key)) {
+      Reflect.deleteProperty(envConfig, key);
+    }
+  });
+  return envConfig;
+}
+
+export function successConsole(message: any) {
+  console.log(
+    chalk.blue.bold('****************  ') +
+      chalk.green.bold('✨ ' + message) +
+      chalk.blue.bold('  ****************')
+  );
+}
+
+export function errorConsole(message: any) {
+  console.log(
+    chalk.blue.bold('****************  ') +
+      chalk.red.bold('✨ ' + message) +
+      chalk.blue.bold('  ****************')
+  );
+}
+
+export function warnConsole(message: any) {
+  console.log(
+    chalk.blue.bold('****************  ') +
+      chalk.yellow.bold('✨ ' + message) +
+      chalk.blue.bold('  ****************')
+  );
+}
+
+export function getCwdPath(...dir: string[]) {
+  return path.resolve(process.cwd(), ...dir);
+}

+ 0 - 6
getEnvConfig.ts

@@ -1,6 +0,0 @@
-import type { GlobEnvConfig } from './src/types/config';
-
-export const getGlobEnvConfig = (): GlobEnvConfig => {
-  const env = import.meta.env;
-  return (env as unknown) as GlobEnvConfig;
-};

+ 8 - 3
index.html

@@ -2,9 +2,14 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta
+      name="viewport"
+      content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
+    />
+    <title></title>
     <link rel="icon" href="/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vue Vben admin 2.0</title>
   </head>
   <body>
     <div id="app">
@@ -43,7 +48,7 @@
         .app-loading {
           width: 100%;
           height: 100%;
-          background: rgba(255, 255, 255, 0, 3);
+          background: rgba(255, 255, 255, 0, 1);
         }
 
         .app-loading .app-loading-wrap {

+ 15 - 12
package.json

@@ -3,21 +3,22 @@
   "version": "2.0.0-beta.6",
   "scripts": {
     "bootstrap": "yarn install",
-    "serve": "ts-node --project ./build/tsconfig.json  ./build/script/preserve && cross-env NODE_ENV=development vite",
-    "build": "cross-env NODE_ENV=production vite build ",
-    "report": "cross-env REPORT=true yarn build ",
-    "build:no-cache": "yarn  clean:cache && yarn build",
-    "preview": "ts-node --project ./build/tsconfig.json  ./build/script/preview",
-    "log": "ts-node --project ./build/tsconfig.json  ./build/script/changelog",
-    "gen:gz": "ts-node --project build/tsconfig.build.json ./build/gzip/index.ts ",
-    "clean:cache": "npx rimraf node_modules/.cache/ && npx rimraf node_modules/.vite_opt_cache",
+    "serve": "node ./build/jsc.js preserve && cross-env NODE_ENV=development vite",
+    "build": "node ./build/jsc.js build",
+    "build:site": "cross-env SITE=true npm run build ",
+    "build:no-cache": "yarn  clean:cache && npm run build",
+    "report": "cross-env REPORT=true npm run build ",
+    "preview": "node ./build/jsc.js preview",
+    "log": "node ./build/jsc.js log",
+    "gen:gz": "node ./build/jsc.js gzip",
+    "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite_opt_cache",
     "clean:lib": "npx rimraf node_modules",
     "ls-lint": "npx ls-lint",
     "lint:eslint": "eslint --fix --ext \"src/**/*.{vue,less,css,scss}\"",
     "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
-    "lint:stylelint": "stylelint  --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
-    "reinstall": "npx rimraf node_modules && npx rimraf yarn.lock && npx rimraf package.lock.json && yarn run bootstrap",
-    "postinstall": "ts-node --project ./build/tsconfig.json  ./build/script/postinstall"
+    "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
+    "reinstall": "rimraf node_modules && rimraf yarn.lock && rimraf package.lock.json && npm run bootstrap",
+    "postinstall": "node ./build/jsc.js postinstall"
   },
   "dependencies": {
     "@iconify/iconify": "^2.0.0-rc.1",
@@ -53,6 +54,7 @@
     "@types/qrcode": "^1.3.5",
     "@types/rollup-plugin-visualizer": "^2.6.0",
     "@types/shelljs": "^0.8.8",
+    "@types/yargs": "^15.0.8",
     "@types/zxcvbn": "^4.4.0",
     "@typescript-eslint/eslint-plugin": "^4.4.0",
     "@typescript-eslint/parser": "^4.4.0",
@@ -90,7 +92,8 @@
     "vite": "^1.0.0-rc.4",
     "vite-plugin-mock": "^1.0.2",
     "vite-plugin-purge-icons": "^0.4.1",
-    "vue-eslint-parser": "^7.1.0"
+    "vue-eslint-parser": "^7.1.0",
+    "yargs": "^16.0.3"
   },
   "repository": {
     "type": "git",

+ 13 - 1
src/components/Form/src/BasicForm.vue

@@ -283,7 +283,19 @@
           const element = values[key];
           if (fields.includes(key) && element !== undefined && element !== null) {
             // 时间
-            (formModel as any)[key] = itemIsDateType(key) ? moment(element) : element;
+            if (itemIsDateType(key)) {
+              if (Array.isArray(element)) {
+                const arr: any[] = [];
+                for (const ele of element) {
+                  arr.push(moment(ele));
+                }
+                (formModel as any)[key] = arr;
+              } else {
+                (formModel as any)[key] = moment(element);
+              }
+            } else {
+              (formModel as any)[key] = element;
+            }
             if (formEl) {
               formEl.validateFields([key]);
             }

+ 1 - 1
src/components/Menu/src/BasicMenu.tsx

@@ -209,7 +209,7 @@ export default defineComponent({
         : {};
       return (
         <Menu
-          forceSubMenuRender={props.isAppMenu}
+          // forceSubMenuRender={props.isAppMenu}
           selectedKeys={selectedKeys}
           defaultSelectedKeys={defaultSelectedKeys}
           mode={mode}

+ 2 - 3
src/components/Table/src/hooks/useTableScroll.ts

@@ -77,7 +77,7 @@ export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRe
     if (el) {
       headerHeight = (el as HTMLElement).offsetHeight;
     }
-    tableHeightRef.value =
+    const tHeight =
       bottomIncludeBody -
       (resizeHeightOffset || 0) -
       paddingHeight -
@@ -86,8 +86,7 @@ export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRe
       footerHeight -
       headerHeight;
     useTimeout(() => {
-      tableHeightRef.value =
-        tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
+      tableHeightRef.value = tHeight > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
       cb && cb();
     }, 0);
   }

+ 13 - 5
src/hooks/core/useSetting.ts

@@ -1,14 +1,20 @@
-import type { ProjectConfig, GlobConfig, SettingWrap } from '/@/types/config';
+import type { ProjectConfig, GlobConfig, SettingWrap, GlobEnvConfig } from '/@/types/config';
 
 import getProjectSetting from '/@/settings/projectSetting';
 
-import { getGlobEnvConfig } from '../../../getEnvConfig';
+import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
+import { getShortName } from '../../../build/getShortName';
+
+const ENV_NAME = getShortName(import.meta.env);
+const ENV = ((isDevMode()
+  ? getGlobEnvConfig()
+  : window[ENV_NAME as any]) as unknown) as GlobEnvConfig;
 const {
+  VITE_GLOB_APP_TITLE,
   VITE_GLOB_API_URL,
   VITE_GLOB_APP_SHORT_NAME,
-  VITE_GLOB_APP_TITLE,
   VITE_GLOB_API_URL_PREFIX,
-} = getGlobEnvConfig();
+} = ENV;
 
 export const useSetting = (): SettingWrap => {
   // Take global configuration
@@ -19,7 +25,9 @@ export const useSetting = (): SettingWrap => {
     urlPrefix: VITE_GLOB_API_URL_PREFIX,
   };
   const projectSetting: Readonly<ProjectConfig> = getProjectSetting;
-
+  console.log('======================');
+  console.log(glob);
+  console.log('======================');
   return {
     globSetting: glob as Readonly<GlobConfig>,
     projectSetting,

+ 4 - 6
src/hooks/web/useECharts.ts

@@ -59,19 +59,17 @@ export function useECharts(
 
   function resize() {
     const chartInstance = unref(chartInstanceRef);
-    if (!chartInstance) {
-      return;
-    }
+    if (!chartInstance) return;
     chartInstance.resize();
   }
+
   tryOnUnmounted(() => {
     const chartInstance = unref(chartInstanceRef);
-    if (!chartInstance) {
-      return;
-    }
+    if (!chartInstance) return;
     chartInstance.dispose();
     chartInstanceRef.value = null;
   });
+
   return {
     setOptions,
     echarts,

+ 7 - 0
src/utils/env.ts

@@ -1,3 +1,10 @@
+import type { GlobEnvConfig } from '/@/types/config';
+
+export const getGlobEnvConfig = (): GlobEnvConfig => {
+  const env = import.meta.env;
+  return (env as unknown) as GlobEnvConfig;
+};
+
 /**
  * @description: 开发模式
  */

+ 3 - 2
src/utils/helper/envHelper.ts

@@ -1,12 +1,13 @@
 import { isDevMode, getEnv } from '/@/utils/env';
 import { useSetting } from '/@/hooks/core/useSetting';
-
+import moment from 'moment';
 import pkg from '../../../package.json';
 const { globSetting } = useSetting();
 
 // Generate cache key according to version
 export const getStorageShortName = () => {
+  const shortTime = moment().format('MMDDHHmmss');
   return `${globSetting.shortName}__${getEnv()}${
-    isDevMode() ? `__${(pkg as any).version}` : '__' + process.env.VITE_BUILD_SHORT_TIME
+    `__${pkg.version}` + (isDevMode() ? '' : `__${shortTime}`)
   }__`.toUpperCase();
 };

+ 45 - 6
vite.config.ts

@@ -4,14 +4,26 @@ import type { UserConfig, Plugin as VitePlugin } from 'vite';
 
 import visualizer from 'rollup-plugin-visualizer';
 import { modifyVars } from './build/config/glob/lessModifyVars';
-import { setupBasicEnv } from './build/config/vite/env';
+import {
+  // externals,
+  cdnConf,
+} from './build/config/vite/cdn';
+
 import { createProxy } from './build/config/vite/proxy';
 import { createMockServer } from 'vite-plugin-mock';
 import PurgeIcons from 'vite-plugin-purge-icons';
+
 import { isDevFn, isReportMode, isProdFn, loadEnv } from './build/utils';
+const pkg = require('./package.json');
 
-setupBasicEnv();
-const { VITE_USE_MOCK, VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY } = loadEnv();
+const {
+  VITE_USE_MOCK,
+  VITE_PORT,
+  VITE_PUBLIC_PATH,
+  VITE_PROXY,
+  VITE_GLOB_APP_TITLE,
+  // VITE_USE_CDN,
+} = loadEnv();
 
 function pathResolve(dir: string) {
   return resolve(__dirname, '.', dir);
@@ -95,9 +107,9 @@ const viteConfig: UserConfig = {
   alias: {
     '/@/': pathResolve('src'),
   },
-  // define: {
-  //   __ENV__: 'value',
-  // },
+  define: {
+    __VERSION__: pkg.version,
+  },
   // css预处理
   cssPreprocessOptions: {
     less: {
@@ -122,8 +134,35 @@ const viteConfig: UserConfig = {
   plugins: [PurgeIcons(), ...vitePlugins],
   rollupOutputOptions: {},
   rollupInputOptions: {
+    // TODO
+    // external: VITE_USE_CDN ? externals : [],
     plugins: rollupPlugins,
   },
 };
 
+// 用于打包部署站点使用。实际项目可以删除
+const isSite = process.env.SITE === 'true';
+// 扩展配置, 往打包后的html注入内容
+// 只针对生产环境
+// TODO 目前只是简单手动注入实现,后续vite应该会提供配置项
+export const htmlConfig: {
+  title: string;
+  addHm?: boolean;
+  cdnConf?: {
+    css?: string[];
+    js?: string[];
+  };
+  useCdn: boolean;
+} = {
+  // html title
+  title: VITE_GLOB_APP_TITLE,
+  // 百度统计,不需要可以删除
+  addHm: isSite,
+  // 使用cdn打包
+  // TODO Cdn esm使用方式需要只能支持google,暂时关闭,后续查询更好的方式
+  useCdn: false,
+  // useCdn: VITE_USE_CDN,
+  // cdn列表
+  cdnConf,
+};
 export default viteConfig;

+ 55 - 2
yarn.lock

@@ -801,6 +801,18 @@
   resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
   integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
 
+"@types/yargs-parser@*":
+  version "15.0.0"
+  resolved "https://registry.npm.taobao.org/@types/yargs-parser/download/@types/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
+  integrity sha1-yz+fdBhp4gzOMw/765JxWQSDiC0=
+
+"@types/yargs@^15.0.8":
+  version "15.0.8"
+  resolved "https://registry.npm.taobao.org/@types/yargs/download/@types/yargs-15.0.8.tgz?cache=0&sync_timestamp=1602182032636&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fyargs%2Fdownload%2F%40types%2Fyargs-15.0.8.tgz#7644904cad7427eb704331ea9bf1ee5499b82e23"
+  integrity sha1-dkSQTK10J+twQzHqm/HuVJm4LiM=
+  dependencies:
+    "@types/yargs-parser" "*"
+
 "@types/zrender@*":
   version "4.0.0"
   resolved "https://registry.npmjs.org/@types/zrender/-/zrender-4.0.0.tgz#a6806f12ec4eccaaebd9b0d816f049aca6188fbd"
@@ -1634,6 +1646,15 @@ cliui@^6.0.0:
     strip-ansi "^6.0.0"
     wrap-ansi "^6.2.0"
 
+cliui@^7.0.0:
+  version "7.0.1"
+  resolved "https://registry.npm.taobao.org/cliui/download/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3"
+  integrity sha1-pMtnqtRc2D2NBRKPyfTY+7iH5rM=
+  dependencies:
+    string-width "^4.2.0"
+    strip-ansi "^6.0.0"
+    wrap-ansi "^7.0.0"
+
 clone-regexp@^2.1.0:
   version "2.2.0"
   resolved "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f"
@@ -2406,7 +2427,7 @@ esbuild@^0.7.1:
   resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.14.tgz#9de555e75669187c2315317fbf489b229b1a4cbb"
   integrity sha512-w2CEVeRcUhCGYMHnNNwb8q+9w42scL7RcNzJm85gZVzNBE3AF0sLq5YP/IdaTBJIFBphIKG3bGbwRH+zsgH/ig==
 
-escalade@^3.1.0:
+escalade@^3.0.2, escalade@^3.1.0:
   version "3.1.0"
   resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e"
   integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==
@@ -2912,7 +2933,7 @@ gensync@^1.0.0-beta.1:
   resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
   integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
 
-get-caller-file@^2.0.1:
+get-caller-file@^2.0.1, get-caller-file@^2.0.5:
   version "2.0.5"
   resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
   integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -7004,6 +7025,15 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-7.0.0.tgz?cache=0&sync_timestamp=1587574502741&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwrap-ansi%2Fdownload%2Fwrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha1-Z+FFz/UQpqaYS98RUpEdadLrnkM=
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -7041,6 +7071,11 @@ y18n@^4.0.0:
   resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
   integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
 
+y18n@^5.0.1:
+  version "5.0.2"
+  resolved "https://registry.npm.taobao.org/y18n/download/y18n-5.0.2.tgz?cache=0&sync_timestamp=1601576683926&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fy18n%2Fdownload%2Fy18n-5.0.2.tgz#48218df5da2731b4403115c39a1af709c873f829"
+  integrity sha1-SCGN9donMbRAMRXDmhr3Cchz+Ck=
+
 yallist@^3.0.2:
   version "3.1.1"
   resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
@@ -7067,6 +7102,11 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
+yargs-parser@^20.0.0:
+  version "20.2.1"
+  resolved "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-20.2.1.tgz?cache=0&sync_timestamp=1601576684570&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs-parser%2Fdownload%2Fyargs-parser-20.2.1.tgz#28f3773c546cdd8a69ddae68116b48a5da328e77"
+  integrity sha1-KPN3PFRs3Ypp3a5oEWtIpdoyjnc=
+
 yargs@^13.2.4:
   version "13.3.2"
   resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
@@ -7100,6 +7140,19 @@ yargs@^15.0.0, yargs@^15.1.0:
     y18n "^4.0.0"
     yargs-parser "^18.1.2"
 
+yargs@^16.0.3:
+  version "16.0.3"
+  resolved "https://registry.npm.taobao.org/yargs/download/yargs-16.0.3.tgz?cache=0&sync_timestamp=1600660006050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c"
+  integrity sha1-epGbnkPJD4DUoUKol5XoU5mn5Uw=
+  dependencies:
+    cliui "^7.0.0"
+    escalade "^3.0.2"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.0"
+    y18n "^5.0.1"
+    yargs-parser "^20.0.0"
+
 ylru@^1.2.0:
   version "1.2.1"
   resolved "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f"