Commit bb3b8f817de15d336968354515649f7142cd5683

Authored by nebv
1 parent e83cb06b

feat: the production environment can be dynamically configured

.env.production
... ... @@ -9,3 +9,7 @@ VITE_GLOB_API_URL=/api
9 9  
10 10 # Interface prefix
11 11 VITE_GLOB_API_URL_PREFIX=
  12 +
  13 +
  14 +# TODO use Cdn
  15 +VITE_USE_CDN = true
... ...
README.md
... ... @@ -218,6 +218,7 @@ yarn clean:lib # 删除node_modules,兼容window系统
218 218 - [x] 图表库
219 219 - [x] 数字动画
220 220 - [x] 首屏加载等待动画
  221 +- [x] 抽取生产环境配置文件
221 222  
222 223 ## 正在开发的功能
223 224  
... ... @@ -228,7 +229,7 @@ yarn clean:lib # 删除node_modules,兼容window系统
228 229 - [ ] 主题配置
229 230 - [ ] 黑暗主题
230 231 - [ ] 打包 Gzip
231   -- [ ] 抽取生产环境配置文件
  232 +- [ ] 打包 CDN
232 233 - [ ] 系统性能优化
233 234  
234 235 更多组件/功能/建议/bug/欢迎提交 pr 或者 issue
... ...
build/config/vite/cdn.ts 0 → 100644
  1 +const css = ['//cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.css'];
  2 +
  3 +// TODO use esm?
  4 +const js = [
  5 + '//cdn.bootcdn.net/ajax/libs/vue/3.0.0/vue.global.prod.js',
  6 + '//cdn.bootcdn.net/ajax/libs/vue-router/4.0.0-beta.13/vue-router.global.min.js',
  7 + '//cdn.bootcdn.net/ajax/libs/vuex/4.0.0-beta.4/vuex.global.prod.js',
  8 + '//cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js',
  9 + '//cdn.bootcdn.net/ajax/libs/qs/6.9.4/qs.min.js',
  10 + '//cdn.bootcdn.net/ajax/libs/nprogress/0.2.0/nprogress.min.js',
  11 + // '//cdn.bootcdn.net/ajax/libs/lodash.js/4.17.15/lodash.min.js',
  12 + // '//cdn.bootcdn.net/ajax/libs/crypto-js/3.3.0/crypto-js.min.js',
  13 + // '//cdn.bootcdn.net/ajax/libs/vue-i18n/8.18.1/vue-i18n.min.js',
  14 +];
  15 +
  16 +export const externals = ['vue', 'vuex', 'vue-router', 'axios', 'qs', 'nprogress'];
  17 +
  18 +export const cdnConf = {
  19 + css,
  20 + js,
  21 +};
... ...
build/config/vite/env.ts deleted 100644 → 0
1   -import moment from 'moment';
2   -// @ts-ignore
3   -import pkg from '../../../package.json';
4   -export function setupBasicEnv() {
5   - // version
6   - process.env.VITE_VERSION = (pkg as any).version;
7   - // build time
8   - process.env.VITE_APP_BUILD_TIME = moment().format('YYYY-MM-DD HH:mm:ss');
9   - process.env.VITE_BUILD_SHORT_TIME = moment().format('MMDDHHmmss');
10   -}
build/constant.ts 0 → 100644
  1 +export const GLOB_CONFIG_FILE_NAME = '_app.config.js';
... ...
build/getShortName.ts 0 → 100644
  1 +export const getShortName = (env: any) => {
  2 + return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
  3 + .toUpperCase()
  4 + .replace(/\s/g, '');
  5 +};
... ...
build/jsc.js 0 → 100644
  1 +// js调用cli 兼容调用ts
  2 +
  3 +const { sh } = require('tasksfile');
  4 +const { argv } = require('yargs');
  5 +
  6 +let command = ``;
  7 +
  8 +Object.keys(argv).forEach((key) => {
  9 + if (!/^\$/.test(key) && key !== '_') {
  10 + // @ts-ignore
  11 + if (argv[key]) {
  12 + command += `--${key} `;
  13 + }
  14 + }
  15 +});
  16 +
  17 +// 执行任务名称
  18 +let taskList = argv._;
  19 +
  20 +let NODE_ENV = process.env.NODE_ENV || 'development';
  21 +
  22 +if (taskList.includes('build') || taskList.includes('report') || taskList.includes('preview')) {
  23 + NODE_ENV = 'production';
  24 +}
  25 +
  26 +if (taskList && Array.isArray(taskList) && taskList.length) {
  27 + sh(
  28 + `cross-env NODE_ENV=${NODE_ENV} ts-node --project ./build/tsconfig.json ./build/script/cli.ts ${taskList.join(
  29 + ' '
  30 + )} ${command}`,
  31 + {
  32 + async: true,
  33 + nopipe: true,
  34 + }
  35 + );
  36 +}
... ...
build/script/build.ts 0 → 100644
  1 +// #!/usr/bin/env node
  2 +
  3 +import { sh } from 'tasksfile';
  4 +import { argv } from 'yargs';
  5 +import { runBuildConfig } from './buildConf';
  6 +import { runUpdateHtml } from './updateHtml';
  7 +import { errorConsole, successConsole } from '../utils';
  8 +
  9 +export const runBuild = async () => {
  10 + try {
  11 + const argvList = argv._;
  12 + let cmd = `cross-env NODE_ENV=production vite build`;
  13 + await sh(cmd, {
  14 + async: true,
  15 + nopipe: true,
  16 + });
  17 +
  18 + // Generate configuration file
  19 + if (!argvList.includes('no-conf')) {
  20 + await runBuildConfig();
  21 + }
  22 + await runUpdateHtml();
  23 + successConsole('Vite Build successfully!');
  24 + } catch (error) {
  25 + errorConsole('Vite Build Error\n' + error);
  26 + process.exit(1);
  27 + }
  28 +};
... ...
build/script/buildConf.ts 0 → 100644
  1 +import { GLOB_CONFIG_FILE_NAME } from '../constant';
  2 +import fs, { writeFileSync } from 'fs-extra';
  3 +
  4 +import viteConfig from '../../vite.config';
  5 +import { errorConsole, successConsole, getCwdPath, getEnvConfig } from '../utils';
  6 +
  7 +const getShortName = (env: any) => {
  8 + return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
  9 + .toUpperCase()
  10 + .replace(/\s/g, '');
  11 +};
  12 +
  13 +function createConfig(
  14 + {
  15 + configName,
  16 + config,
  17 + configFileName = GLOB_CONFIG_FILE_NAME,
  18 + }: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} }
  19 +) {
  20 + try {
  21 + const windowConf = `window.${configName}`;
  22 + const outDir = viteConfig.outDir || 'dist';
  23 + const configStr = `${windowConf}=${JSON.stringify(config)};
  24 +
  25 + Object.freeze(${windowConf});
  26 + Object.defineProperty(window, "${configName}", {
  27 + configurable: false,
  28 + writable: false,
  29 + });
  30 + `;
  31 + fs.mkdirp(getCwdPath(outDir));
  32 + writeFileSync(getCwdPath(`${outDir}/${configFileName}`), configStr);
  33 +
  34 + successConsole('The configuration file is build successfully!');
  35 + } catch (error) {
  36 + errorConsole('Configuration file configuration file failed to package\n' + error);
  37 + }
  38 +}
  39 +
  40 +export function runBuildConfig() {
  41 + const config = getEnvConfig();
  42 + const configFileName = getShortName(config);
  43 + createConfig({ config, configName: configFileName });
  44 +}
... ...
build/script/changelog.ts
1 1 // #!/usr/bin/env node
2 2  
3 3 import { sh } from 'tasksfile';
4   -import chalk from 'chalk';
  4 +import { errorConsole, successConsole } from '../utils';
5 5  
6   -const createChangeLog = async () => {
  6 +export const runChangeLog = async () => {
7 7 try {
8 8 let cmd = `conventional-changelog -p custom-config -i CHANGELOG.md -s -r 0 `;
9   - // if (shell.which('git')) {
10   - // cmd += '&& git add CHANGELOG.md';
11   - // }
12 9 await sh(cmd, {
13 10 async: true,
14 11 nopipe: true,
... ... @@ -18,21 +15,10 @@ const createChangeLog = async () => {
18 15 async: true,
19 16 nopipe: true,
20 17 });
21   - console.log(
22   - chalk.blue.bold('**************** ') +
23   - chalk.green.bold('CHANGE_LOG generated successfully!') +
24   - chalk.blue.bold(' ****************')
25   - );
  18 + successConsole('CHANGE_LOG.md generated successfully!');
26 19 } catch (error) {
27   - console.log(
28   - chalk.blue.red('**************** ') +
29   - chalk.green.red('CHANGE_LOG generated error\n' + error) +
30   - chalk.blue.red(' ****************')
31   - );
  20 + errorConsole('CHANGE_LOG.md generated error\n' + error);
  21 +
32 22 process.exit(1);
33 23 }
34 24 };
35   -createChangeLog();
36   -module.exports = {
37   - createChangeLog,
38   -};
... ...
build/script/cli.ts 0 → 100644
  1 +#!/usr/bin/env node
  2 +
  3 +import chalk from 'chalk';
  4 +import { argv } from 'yargs';
  5 +
  6 +import { runChangeLog } from './changelog';
  7 +import { runPostInstall } from './postinstall';
  8 +import { runPreview } from './preview';
  9 +import { runPreserve } from './preserve';
  10 +import { runBuild } from './build';
  11 +
  12 +const task = (argv._ || [])[0];
  13 +
  14 +console.log('Run Task: ' + chalk.cyan(task));
  15 +
  16 +switch (task) {
  17 + // change log
  18 + case 'log':
  19 + runChangeLog();
  20 + break;
  21 +
  22 + case 'build':
  23 + runBuild();
  24 + break;
  25 +
  26 + case 'preserve':
  27 + runPreserve();
  28 + break;
  29 +
  30 + case 'postinstall':
  31 + runPostInstall();
  32 + break;
  33 +
  34 + case 'preview':
  35 + runPreview();
  36 + break;
  37 +
  38 + // TODO
  39 + case 'gzip':
  40 + break;
  41 + default:
  42 + break;
  43 +}
  44 +
  45 +export default {};
... ...
build/script/hm.ts 0 → 100644
  1 +// 百度统计代码 用于站点部署
  2 +// 只在build:site开启
  3 +export const hmScript = `<script>
  4 +var _hmt = _hmt || [];
  5 +(function() {
  6 + var hm = document.createElement("script");
  7 + hm.src = "https://hm.baidu.com/hm.js?384d6046e02f6ac4ea075357bd0e9b43";
  8 + var s = document.getElementsByTagName("script")[0];
  9 + s.parentNode.insertBefore(hm, s);
  10 +})();
  11 +</script>
  12 +`;
... ...
build/script/postinstall.ts
... ... @@ -2,9 +2,14 @@ import { exec, which } from &#39;shelljs&#39;;
2 2  
3 3 function ignoreCaseGit() {
4 4 try {
5   - if (which('git')) {
  5 + if (which('git').code === 0) {
6 6 exec('git config core.ignorecase false ');
7 7 }
8   - } catch (error) {}
  8 + } catch (error) {
  9 + console.log(error);
  10 + }
  11 +}
  12 +
  13 +export function runPostInstall() {
  14 + ignoreCaseGit();
9 15 }
10   -ignoreCaseGit();
... ...
build/script/preserve.ts
1   -// 是否需要更新依赖,防止package.json更新了依赖,其他人获取代码后没有install
  1 +// Do you need to update the dependencies to prevent package.json from updating the dependencies, and no install after others get the code
2 2  
3 3 import path from 'path';
4 4 import fs from 'fs-extra';
5 5 import { isEqual } from 'lodash';
6   -import chalk from 'chalk';
7 6 import { sh } from 'tasksfile';
  7 +import { successConsole, errorConsole } from '../utils';
8 8  
9 9 const resolve = (dir: string) => {
10 10 return path.resolve(process.cwd(), dir);
11 11 };
12 12  
  13 +const reg = /[\u4E00-\u9FA5\uF900-\uFA2D]/;
  14 +
13 15 let NEED_INSTALL = false;
14 16  
15   -fs.mkdirp(resolve('build/.cache'));
16   -function checkPkgUpdate() {
17   - const pkg = require('../../package.json');
18   - const { dependencies, devDependencies } = pkg;
19   - const depsFile = resolve('build/.cache/deps.json');
20   - if (!fs.pathExistsSync(depsFile)) {
21   - NEED_INSTALL = true;
22   - return;
  17 +export async function runPreserve() {
  18 + const cwdPath = process.cwd();
  19 + if (reg.test(cwdPath)) {
  20 + errorConsole(
  21 + 'Do not include Chinese, Japanese or Korean in the full path of the project directory, please modify the directory name and run again!'
  22 + );
  23 + errorConsole('项目目录全路径请勿包含中文、日文、韩文,请修改目录名后再次重新运行!');
  24 + process.exit(1);
23 25 }
24   - const depsJson = require('../.cache/deps.json');
25 26  
26   - if (!isEqual(depsJson, { dependencies, devDependencies })) {
27   - NEED_INSTALL = true;
28   - }
29   -}
30   -checkPkgUpdate();
  27 + fs.mkdirp(resolve('build/.cache'));
  28 + function checkPkgUpdate() {
  29 + const pkg = require('../../package.json');
  30 + const { dependencies, devDependencies } = pkg;
  31 + const depsFile = resolve('build/.cache/deps.json');
  32 + if (!fs.pathExistsSync(depsFile)) {
  33 + NEED_INSTALL = true;
  34 + return;
  35 + }
  36 + const depsJson = require('../.cache/deps.json');
31 37  
32   -(async () => {
  38 + if (!isEqual(depsJson, { dependencies, devDependencies })) {
  39 + NEED_INSTALL = true;
  40 + }
  41 + }
  42 + checkPkgUpdate();
33 43 if (NEED_INSTALL) {
34   - console.log(
35   - chalk.blue.bold('**************** ') +
36   - chalk.red.bold('检测到依赖变化,正在安装依赖(Tip: 项目首次运行也会执行)!') +
37   - chalk.blue.bold(' ****************')
  44 + // no error
  45 + successConsole(
  46 + '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)!'
38 47 );
39 48 try {
40   - // 从代码执行貌似不会自动读取.npmrc 所以手动加上源地址
41   - // await run('yarn install --registry=https://registry.npm.taobao.org ', {
42   - await sh('yarn install ', {
  49 + await sh('npm run bootstrap ', {
43 50 async: true,
44 51 nopipe: true,
45 52 });
46   - console.log(
47   - chalk.blue.bold('**************** ') +
48   - chalk.green.bold('依赖安装成功,正在运行!') +
49   - chalk.blue.bold(' ****************')
50   - );
  53 +
  54 + successConsole('Dependency installation is successful, start running the project!');
51 55  
52 56 const pkg = require('../../package.json');
53 57 const { dependencies, devDependencies } = pkg;
... ... @@ -64,4 +68,4 @@ checkPkgUpdate();
64 68 }
65 69 } catch (error) {}
66 70 }
67   -})();
  71 +}
... ...
build/script/preview.ts
... ... @@ -11,7 +11,7 @@ import { getIPAddress } from &#39;../utils&#39;;
11 11 const BUILD = 1;
12 12 const NO_BUILD = 2;
13 13  
14   -// 启动服务器
  14 +// start server
15 15 const startApp = () => {
16 16 const port = 9680;
17 17 portfinder.basePort = port;
... ... @@ -23,7 +23,6 @@ const startApp = () =&gt; {
23 23 if (err) {
24 24 throw err;
25 25 } else {
26   - // const publicPath = process.env.BASE_URL;
27 26 app.listen(port, function () {
28 27 const empty = ' ';
29 28 const common = `The preview program is already running:
... ... @@ -36,7 +35,7 @@ const startApp = () =&gt; {
36 35 });
37 36 };
38 37  
39   -const preview = async () => {
  38 +export const runPreview = async () => {
40 39 const prompt = inquirer.prompt({
41 40 type: 'list',
42 41 message: 'Please select a preview method',
... ... @@ -61,7 +60,3 @@ const preview = async () =&gt; {
61 60 }
62 61 startApp();
63 62 };
64   -
65   -(() => {
66   - preview();
67   -})();
... ...
build/script/updateHtml.ts 0 → 100644
  1 +import { readFileSync, writeFileSync, existsSync } from 'fs-extra';
  2 +import viteConfig, { htmlConfig } from '../../vite.config';
  3 +import { getCwdPath, successConsole, errorConsole } from '../utils';
  4 +import { GLOB_CONFIG_FILE_NAME } from '../constant';
  5 +import { hmScript } from './hm';
  6 +const pkg = require('../../package.json');
  7 +
  8 +const { title, addHm, cdnConf, useCdn } = htmlConfig;
  9 +
  10 +function injectTitle(html: string, htmlTitle: string) {
  11 + if (/<\/title>/.test(html)) {
  12 + return html.replace(/<\/title>/, `${htmlTitle}</title>`);
  13 + }
  14 + return html;
  15 +}
  16 +
  17 +function injectConfigScript(html: string) {
  18 + const tag = `\t\t<script src='${viteConfig.base || './'}${GLOB_CONFIG_FILE_NAME}?v=${
  19 + pkg.version
  20 + }-${new Date().getTime()}'></script>`;
  21 +
  22 + if (/<\/head>/.test(html)) {
  23 + return html.replace(/<\/head>/, `${tag}\n\t\t</head>`);
  24 + }
  25 + return html;
  26 +}
  27 +
  28 +function injectHmScript(html: string) {
  29 + if (/<head>/.test(html)) {
  30 + return html.replace(/<head>/, `<head>\n${hmScript}`);
  31 + }
  32 + return html;
  33 +}
  34 +
  35 +function injectCdnCss(html: string) {
  36 + if (!cdnConf) return html;
  37 + const { css } = cdnConf;
  38 + if (!css || css.length === 0) return html;
  39 +
  40 + let cdnCssTag = '';
  41 + for (const cssLink of css) {
  42 + cdnCssTag += `<link rel="stylesheet" href="${cssLink}">`;
  43 + }
  44 + if (/<\/head>/.test(html)) {
  45 + return html.replace(/<\/head>/, `${cdnCssTag}\n\t\t</head>`);
  46 + }
  47 + return html;
  48 +}
  49 +
  50 +function injectCdnjs(html: string) {
  51 + if (!cdnConf) return html;
  52 + const { js } = cdnConf;
  53 + if (!js || js.length === 0) return html;
  54 +
  55 + let cdnJsTag = '';
  56 + for (const src of js) {
  57 + // TODO
  58 + // <script type="importmap">
  59 + // { "imports": {
  60 + // "vue": "https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.esm-browser.js",
  61 + // "vue-router": "https://cdnjs.cloudflare.com/ajax/libs/vue-router/4.0.0-alpha.13/vue-router.esm.js",
  62 + // "vuex": "https://cdnjs.cloudflare.com/ajax/libs/vuex/4.0.0-beta.2/vuex.esm-browser.js"
  63 + // } }
  64 + // </script>
  65 + cdnJsTag += `\t<script type="text/javascript" src="${src}"></script>\n`;
  66 + }
  67 + if (/<\/body>/.test(html)) {
  68 + return html.replace(/<\/body>/, `${cdnJsTag}\n</body>`);
  69 + }
  70 + return html;
  71 +}
  72 +
  73 +export async function runUpdateHtml() {
  74 + const outDir = viteConfig.outDir || 'dist';
  75 + const indexPath = getCwdPath(outDir, 'index.html');
  76 + if (!existsSync(`${indexPath}`)) {
  77 + return;
  78 + }
  79 + try {
  80 + let processedHtml = '';
  81 + const rawHtml = readFileSync(indexPath, 'utf-8');
  82 + processedHtml = rawHtml;
  83 + processedHtml = injectTitle(processedHtml, title);
  84 + processedHtml = injectConfigScript(processedHtml);
  85 + if (addHm) {
  86 + processedHtml = injectHmScript(processedHtml);
  87 + }
  88 + if (useCdn) {
  89 + processedHtml = injectCdnCss(processedHtml);
  90 + processedHtml = injectCdnjs(processedHtml);
  91 + }
  92 +
  93 + writeFileSync(indexPath, processedHtml);
  94 + successConsole('Update Html Successfully!');
  95 + } catch (error) {
  96 + errorConsole('Update Html Error\n' + error);
  97 + }
  98 +}
... ...
build/utils.ts
1 1 import fs from 'fs';
  2 +import path from 'path';
2 3 import { networkInterfaces } from 'os';
3 4 import dotenv from 'dotenv';
  5 +import chalk from 'chalk';
  6 +
4 7 export const isFunction = (arg: unknown): arg is (...args: any[]) => any =>
5 8 typeof arg === 'function';
6 9 export const isRegExp = (arg: unknown): arg is RegExp =>
... ... @@ -72,6 +75,8 @@ export interface ViteEnv {
72 75 VITE_USE_MOCK: boolean;
73 76 VITE_PUBLIC_PATH: string;
74 77 VITE_PROXY: [string, string][];
  78 + VITE_GLOB_APP_TITLE: string;
  79 + VITE_USE_CDN: boolean;
75 80 }
76 81  
77 82 export function loadEnv(): ViteEnv {
... ... @@ -100,3 +105,49 @@ export function loadEnv(): ViteEnv {
100 105 }
101 106 return ret;
102 107 }
  108 +
  109 +export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) {
  110 + let envConfig = {};
  111 + confFiles.forEach((item) => {
  112 + try {
  113 + const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
  114 +
  115 + envConfig = { ...envConfig, ...env };
  116 + } catch (error) {}
  117 + });
  118 + Object.keys(envConfig).forEach((key) => {
  119 + const reg = new RegExp(`^(${match})`);
  120 + if (!reg.test(key)) {
  121 + Reflect.deleteProperty(envConfig, key);
  122 + }
  123 + });
  124 + return envConfig;
  125 +}
  126 +
  127 +export function successConsole(message: any) {
  128 + console.log(
  129 + chalk.blue.bold('**************** ') +
  130 + chalk.green.bold('✨ ' + message) +
  131 + chalk.blue.bold(' ****************')
  132 + );
  133 +}
  134 +
  135 +export function errorConsole(message: any) {
  136 + console.log(
  137 + chalk.blue.bold('**************** ') +
  138 + chalk.red.bold('✨ ' + message) +
  139 + chalk.blue.bold(' ****************')
  140 + );
  141 +}
  142 +
  143 +export function warnConsole(message: any) {
  144 + console.log(
  145 + chalk.blue.bold('**************** ') +
  146 + chalk.yellow.bold('✨ ' + message) +
  147 + chalk.blue.bold(' ****************')
  148 + );
  149 +}
  150 +
  151 +export function getCwdPath(...dir: string[]) {
  152 + return path.resolve(process.cwd(), ...dir);
  153 +}
... ...
getEnvConfig.ts deleted 100644 → 0
1   -import type { GlobEnvConfig } from './src/types/config';
2   -
3   -export const getGlobEnvConfig = (): GlobEnvConfig => {
4   - const env = import.meta.env;
5   - return (env as unknown) as GlobEnvConfig;
6   -};
index.html
... ... @@ -2,9 +2,14 @@
2 2 <html lang="en">
3 3 <head>
4 4 <meta charset="UTF-8" />
  5 + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
  6 + <meta name="renderer" content="webkit" />
  7 + <meta
  8 + name="viewport"
  9 + content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
  10 + />
  11 + <title></title>
5 12 <link rel="icon" href="/favicon.ico" />
6   - <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7   - <title>Vue Vben admin 2.0</title>
8 13 </head>
9 14 <body>
10 15 <div id="app">
... ... @@ -43,7 +48,7 @@
43 48 .app-loading {
44 49 width: 100%;
45 50 height: 100%;
46   - background: rgba(255, 255, 255, 0, 3);
  51 + background: rgba(255, 255, 255, 0, 1);
47 52 }
48 53  
49 54 .app-loading .app-loading-wrap {
... ...
package.json
... ... @@ -3,21 +3,22 @@
3 3 "version": "2.0.0-beta.6",
4 4 "scripts": {
5 5 "bootstrap": "yarn install",
6   - "serve": "ts-node --project ./build/tsconfig.json ./build/script/preserve && cross-env NODE_ENV=development vite",
7   - "build": "cross-env NODE_ENV=production vite build ",
8   - "report": "cross-env REPORT=true yarn build ",
9   - "build:no-cache": "yarn clean:cache && yarn build",
10   - "preview": "ts-node --project ./build/tsconfig.json ./build/script/preview",
11   - "log": "ts-node --project ./build/tsconfig.json ./build/script/changelog",
12   - "gen:gz": "ts-node --project build/tsconfig.build.json ./build/gzip/index.ts ",
13   - "clean:cache": "npx rimraf node_modules/.cache/ && npx rimraf node_modules/.vite_opt_cache",
  6 + "serve": "node ./build/jsc.js preserve && cross-env NODE_ENV=development vite",
  7 + "build": "node ./build/jsc.js build",
  8 + "build:site": "cross-env SITE=true npm run build ",
  9 + "build:no-cache": "yarn clean:cache && npm run build",
  10 + "report": "cross-env REPORT=true npm run build ",
  11 + "preview": "node ./build/jsc.js preview",
  12 + "log": "node ./build/jsc.js log",
  13 + "gen:gz": "node ./build/jsc.js gzip",
  14 + "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite_opt_cache",
14 15 "clean:lib": "npx rimraf node_modules",
15 16 "ls-lint": "npx ls-lint",
16 17 "lint:eslint": "eslint --fix --ext \"src/**/*.{vue,less,css,scss}\"",
17 18 "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
18   - "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
19   - "reinstall": "npx rimraf node_modules && npx rimraf yarn.lock && npx rimraf package.lock.json && yarn run bootstrap",
20   - "postinstall": "ts-node --project ./build/tsconfig.json ./build/script/postinstall"
  19 + "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
  20 + "reinstall": "rimraf node_modules && rimraf yarn.lock && rimraf package.lock.json && npm run bootstrap",
  21 + "postinstall": "node ./build/jsc.js postinstall"
21 22 },
22 23 "dependencies": {
23 24 "@iconify/iconify": "^2.0.0-rc.1",
... ... @@ -53,6 +54,7 @@
53 54 "@types/qrcode": "^1.3.5",
54 55 "@types/rollup-plugin-visualizer": "^2.6.0",
55 56 "@types/shelljs": "^0.8.8",
  57 + "@types/yargs": "^15.0.8",
56 58 "@types/zxcvbn": "^4.4.0",
57 59 "@typescript-eslint/eslint-plugin": "^4.4.0",
58 60 "@typescript-eslint/parser": "^4.4.0",
... ... @@ -90,7 +92,8 @@
90 92 "vite": "^1.0.0-rc.4",
91 93 "vite-plugin-mock": "^1.0.2",
92 94 "vite-plugin-purge-icons": "^0.4.1",
93   - "vue-eslint-parser": "^7.1.0"
  95 + "vue-eslint-parser": "^7.1.0",
  96 + "yargs": "^16.0.3"
94 97 },
95 98 "repository": {
96 99 "type": "git",
... ...
src/components/Form/src/BasicForm.vue
... ... @@ -283,7 +283,19 @@
283 283 const element = values[key];
284 284 if (fields.includes(key) && element !== undefined && element !== null) {
285 285 // 时间
286   - (formModel as any)[key] = itemIsDateType(key) ? moment(element) : element;
  286 + if (itemIsDateType(key)) {
  287 + if (Array.isArray(element)) {
  288 + const arr: any[] = [];
  289 + for (const ele of element) {
  290 + arr.push(moment(ele));
  291 + }
  292 + (formModel as any)[key] = arr;
  293 + } else {
  294 + (formModel as any)[key] = moment(element);
  295 + }
  296 + } else {
  297 + (formModel as any)[key] = element;
  298 + }
287 299 if (formEl) {
288 300 formEl.validateFields([key]);
289 301 }
... ...
src/components/Menu/src/BasicMenu.tsx
... ... @@ -209,7 +209,7 @@ export default defineComponent({
209 209 : {};
210 210 return (
211 211 <Menu
212   - forceSubMenuRender={props.isAppMenu}
  212 + // forceSubMenuRender={props.isAppMenu}
213 213 selectedKeys={selectedKeys}
214 214 defaultSelectedKeys={defaultSelectedKeys}
215 215 mode={mode}
... ...
src/components/Table/src/hooks/useTableScroll.ts
... ... @@ -77,7 +77,7 @@ export function useTableScroll(refProps: ComputedRef&lt;BasicTableProps&gt;, tableElRe
77 77 if (el) {
78 78 headerHeight = (el as HTMLElement).offsetHeight;
79 79 }
80   - tableHeightRef.value =
  80 + const tHeight =
81 81 bottomIncludeBody -
82 82 (resizeHeightOffset || 0) -
83 83 paddingHeight -
... ... @@ -86,8 +86,7 @@ export function useTableScroll(refProps: ComputedRef&lt;BasicTableProps&gt;, tableElRe
86 86 footerHeight -
87 87 headerHeight;
88 88 useTimeout(() => {
89   - tableHeightRef.value =
90   - tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
  89 + tableHeightRef.value = tHeight > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
91 90 cb && cb();
92 91 }, 0);
93 92 }
... ...
src/hooks/core/useSetting.ts
1   -import type { ProjectConfig, GlobConfig, SettingWrap } from '/@/types/config';
  1 +import type { ProjectConfig, GlobConfig, SettingWrap, GlobEnvConfig } from '/@/types/config';
2 2  
3 3 import getProjectSetting from '/@/settings/projectSetting';
4 4  
5   -import { getGlobEnvConfig } from '../../../getEnvConfig';
  5 +import { getGlobEnvConfig, isDevMode } from '/@/utils/env';
  6 +import { getShortName } from '../../../build/getShortName';
  7 +
  8 +const ENV_NAME = getShortName(import.meta.env);
  9 +const ENV = ((isDevMode()
  10 + ? getGlobEnvConfig()
  11 + : window[ENV_NAME as any]) as unknown) as GlobEnvConfig;
6 12 const {
  13 + VITE_GLOB_APP_TITLE,
7 14 VITE_GLOB_API_URL,
8 15 VITE_GLOB_APP_SHORT_NAME,
9   - VITE_GLOB_APP_TITLE,
10 16 VITE_GLOB_API_URL_PREFIX,
11   -} = getGlobEnvConfig();
  17 +} = ENV;
12 18  
13 19 export const useSetting = (): SettingWrap => {
14 20 // Take global configuration
... ... @@ -19,7 +25,9 @@ export const useSetting = (): SettingWrap =&gt; {
19 25 urlPrefix: VITE_GLOB_API_URL_PREFIX,
20 26 };
21 27 const projectSetting: Readonly<ProjectConfig> = getProjectSetting;
22   -
  28 + console.log('======================');
  29 + console.log(glob);
  30 + console.log('======================');
23 31 return {
24 32 globSetting: glob as Readonly<GlobConfig>,
25 33 projectSetting,
... ...
src/hooks/web/useECharts.ts
... ... @@ -59,19 +59,17 @@ export function useECharts(
59 59  
60 60 function resize() {
61 61 const chartInstance = unref(chartInstanceRef);
62   - if (!chartInstance) {
63   - return;
64   - }
  62 + if (!chartInstance) return;
65 63 chartInstance.resize();
66 64 }
  65 +
67 66 tryOnUnmounted(() => {
68 67 const chartInstance = unref(chartInstanceRef);
69   - if (!chartInstance) {
70   - return;
71   - }
  68 + if (!chartInstance) return;
72 69 chartInstance.dispose();
73 70 chartInstanceRef.value = null;
74 71 });
  72 +
75 73 return {
76 74 setOptions,
77 75 echarts,
... ...
src/utils/env.ts
  1 +import type { GlobEnvConfig } from '/@/types/config';
  2 +
  3 +export const getGlobEnvConfig = (): GlobEnvConfig => {
  4 + const env = import.meta.env;
  5 + return (env as unknown) as GlobEnvConfig;
  6 +};
  7 +
1 8 /**
2 9 * @description: 开发模式
3 10 */
... ...
src/utils/helper/envHelper.ts
1 1 import { isDevMode, getEnv } from '/@/utils/env';
2 2 import { useSetting } from '/@/hooks/core/useSetting';
3   -
  3 +import moment from 'moment';
4 4 import pkg from '../../../package.json';
5 5 const { globSetting } = useSetting();
6 6  
7 7 // Generate cache key according to version
8 8 export const getStorageShortName = () => {
  9 + const shortTime = moment().format('MMDDHHmmss');
9 10 return `${globSetting.shortName}__${getEnv()}${
10   - isDevMode() ? `__${(pkg as any).version}` : '__' + process.env.VITE_BUILD_SHORT_TIME
  11 + `__${pkg.version}` + (isDevMode() ? '' : `__${shortTime}`)
11 12 }__`.toUpperCase();
12 13 };
... ...
vite.config.ts
... ... @@ -4,14 +4,26 @@ import type { UserConfig, Plugin as VitePlugin } from &#39;vite&#39;;
4 4  
5 5 import visualizer from 'rollup-plugin-visualizer';
6 6 import { modifyVars } from './build/config/glob/lessModifyVars';
7   -import { setupBasicEnv } from './build/config/vite/env';
  7 +import {
  8 + // externals,
  9 + cdnConf,
  10 +} from './build/config/vite/cdn';
  11 +
8 12 import { createProxy } from './build/config/vite/proxy';
9 13 import { createMockServer } from 'vite-plugin-mock';
10 14 import PurgeIcons from 'vite-plugin-purge-icons';
  15 +
11 16 import { isDevFn, isReportMode, isProdFn, loadEnv } from './build/utils';
  17 +const pkg = require('./package.json');
12 18  
13   -setupBasicEnv();
14   -const { VITE_USE_MOCK, VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY } = loadEnv();
  19 +const {
  20 + VITE_USE_MOCK,
  21 + VITE_PORT,
  22 + VITE_PUBLIC_PATH,
  23 + VITE_PROXY,
  24 + VITE_GLOB_APP_TITLE,
  25 + // VITE_USE_CDN,
  26 +} = loadEnv();
15 27  
16 28 function pathResolve(dir: string) {
17 29 return resolve(__dirname, '.', dir);
... ... @@ -95,9 +107,9 @@ const viteConfig: UserConfig = {
95 107 alias: {
96 108 '/@/': pathResolve('src'),
97 109 },
98   - // define: {
99   - // __ENV__: 'value',
100   - // },
  110 + define: {
  111 + __VERSION__: pkg.version,
  112 + },
101 113 // css预处理
102 114 cssPreprocessOptions: {
103 115 less: {
... ... @@ -122,8 +134,35 @@ const viteConfig: UserConfig = {
122 134 plugins: [PurgeIcons(), ...vitePlugins],
123 135 rollupOutputOptions: {},
124 136 rollupInputOptions: {
  137 + // TODO
  138 + // external: VITE_USE_CDN ? externals : [],
125 139 plugins: rollupPlugins,
126 140 },
127 141 };
128 142  
  143 +// 用于打包部署站点使用。实际项目可以删除
  144 +const isSite = process.env.SITE === 'true';
  145 +// 扩展配置, 往打包后的html注入内容
  146 +// 只针对生产环境
  147 +// TODO 目前只是简单手动注入实现,后续vite应该会提供配置项
  148 +export const htmlConfig: {
  149 + title: string;
  150 + addHm?: boolean;
  151 + cdnConf?: {
  152 + css?: string[];
  153 + js?: string[];
  154 + };
  155 + useCdn: boolean;
  156 +} = {
  157 + // html title
  158 + title: VITE_GLOB_APP_TITLE,
  159 + // 百度统计,不需要可以删除
  160 + addHm: isSite,
  161 + // 使用cdn打包
  162 + // TODO Cdn esm使用方式需要只能支持google,暂时关闭,后续查询更好的方式
  163 + useCdn: false,
  164 + // useCdn: VITE_USE_CDN,
  165 + // cdn列表
  166 + cdnConf,
  167 +};
129 168 export default viteConfig;
... ...
yarn.lock
... ... @@ -801,6 +801,18 @@
801 801 resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
802 802 integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
803 803  
  804 +"@types/yargs-parser@*":
  805 + version "15.0.0"
  806 + resolved "https://registry.npm.taobao.org/@types/yargs-parser/download/@types/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
  807 + integrity sha1-yz+fdBhp4gzOMw/765JxWQSDiC0=
  808 +
  809 +"@types/yargs@^15.0.8":
  810 + version "15.0.8"
  811 + 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"
  812 + integrity sha1-dkSQTK10J+twQzHqm/HuVJm4LiM=
  813 + dependencies:
  814 + "@types/yargs-parser" "*"
  815 +
804 816 "@types/zrender@*":
805 817 version "4.0.0"
806 818 resolved "https://registry.npmjs.org/@types/zrender/-/zrender-4.0.0.tgz#a6806f12ec4eccaaebd9b0d816f049aca6188fbd"
... ... @@ -1634,6 +1646,15 @@ cliui@^6.0.0:
1634 1646 strip-ansi "^6.0.0"
1635 1647 wrap-ansi "^6.2.0"
1636 1648  
  1649 +cliui@^7.0.0:
  1650 + version "7.0.1"
  1651 + resolved "https://registry.npm.taobao.org/cliui/download/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3"
  1652 + integrity sha1-pMtnqtRc2D2NBRKPyfTY+7iH5rM=
  1653 + dependencies:
  1654 + string-width "^4.2.0"
  1655 + strip-ansi "^6.0.0"
  1656 + wrap-ansi "^7.0.0"
  1657 +
1637 1658 clone-regexp@^2.1.0:
1638 1659 version "2.2.0"
1639 1660 resolved "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f"
... ... @@ -2406,7 +2427,7 @@ esbuild@^0.7.1:
2406 2427 resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.14.tgz#9de555e75669187c2315317fbf489b229b1a4cbb"
2407 2428 integrity sha512-w2CEVeRcUhCGYMHnNNwb8q+9w42scL7RcNzJm85gZVzNBE3AF0sLq5YP/IdaTBJIFBphIKG3bGbwRH+zsgH/ig==
2408 2429  
2409   -escalade@^3.1.0:
  2430 +escalade@^3.0.2, escalade@^3.1.0:
2410 2431 version "3.1.0"
2411 2432 resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e"
2412 2433 integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==
... ... @@ -2912,7 +2933,7 @@ gensync@^1.0.0-beta.1:
2912 2933 resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
2913 2934 integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
2914 2935  
2915   -get-caller-file@^2.0.1:
  2936 +get-caller-file@^2.0.1, get-caller-file@^2.0.5:
2916 2937 version "2.0.5"
2917 2938 resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
2918 2939 integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
... ... @@ -7004,6 +7025,15 @@ wrap-ansi@^6.2.0:
7004 7025 string-width "^4.1.0"
7005 7026 strip-ansi "^6.0.0"
7006 7027  
  7028 +wrap-ansi@^7.0.0:
  7029 + version "7.0.0"
  7030 + 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"
  7031 + integrity sha1-Z+FFz/UQpqaYS98RUpEdadLrnkM=
  7032 + dependencies:
  7033 + ansi-styles "^4.0.0"
  7034 + string-width "^4.1.0"
  7035 + strip-ansi "^6.0.0"
  7036 +
7007 7037 wrappy@1:
7008 7038 version "1.0.2"
7009 7039 resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
... ... @@ -7041,6 +7071,11 @@ y18n@^4.0.0:
7041 7071 resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
7042 7072 integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
7043 7073  
  7074 +y18n@^5.0.1:
  7075 + version "5.0.2"
  7076 + 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"
  7077 + integrity sha1-SCGN9donMbRAMRXDmhr3Cchz+Ck=
  7078 +
7044 7079 yallist@^3.0.2:
7045 7080 version "3.1.1"
7046 7081 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:
7067 7102 camelcase "^5.0.0"
7068 7103 decamelize "^1.2.0"
7069 7104  
  7105 +yargs-parser@^20.0.0:
  7106 + version "20.2.1"
  7107 + 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"
  7108 + integrity sha1-KPN3PFRs3Ypp3a5oEWtIpdoyjnc=
  7109 +
7070 7110 yargs@^13.2.4:
7071 7111 version "13.3.2"
7072 7112 resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
... ... @@ -7100,6 +7140,19 @@ yargs@^15.0.0, yargs@^15.1.0:
7100 7140 y18n "^4.0.0"
7101 7141 yargs-parser "^18.1.2"
7102 7142  
  7143 +yargs@^16.0.3:
  7144 + version "16.0.3"
  7145 + 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"
  7146 + integrity sha1-epGbnkPJD4DUoUKol5XoU5mn5Uw=
  7147 + dependencies:
  7148 + cliui "^7.0.0"
  7149 + escalade "^3.0.2"
  7150 + get-caller-file "^2.0.5"
  7151 + require-directory "^2.1.1"
  7152 + string-width "^4.2.0"
  7153 + y18n "^5.0.1"
  7154 + yargs-parser "^20.0.0"
  7155 +
7103 7156 ylru@^1.2.0:
7104 7157 version "1.2.1"
7105 7158 resolved "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f"
... ...