提交 99ac309f 作者: vben

wip: support vite

上级 1d3007f0
......@@ -23,5 +23,5 @@ VITE_GLOB_API_URL_PREFIX=
# use pwa
VITE_USE_PWA = false
# TODO use Cdn
VITE_USE_CDN = true
# Is it compatible with older browsers
VITE_LEGACY = false
// #!/usr/bin/env node
import { sh } from 'tasksfile';
import { errorConsole, successConsole } from '../utils';
export const runChangeLog = async () => {
try {
let cmd = `conventional-changelog -p custom-config -i CHANGELOG.md -s -r 0 `;
await sh(cmd, {
async: true,
nopipe: true,
});
await sh('prettier --write **/CHANGELOG.md ', {
async: true,
nopipe: true,
});
successConsole('CHANGE_LOG.md generated successfully!');
} catch (error) {
errorConsole('CHANGE_LOG.md generated error\n' + error);
process.exit(1);
}
};
runChangeLog();
......@@ -3,7 +3,6 @@
import { argv } from 'yargs';
import { runBuildConfig } from './buildConf';
import { errorConsole, successConsole } from '../utils';
import { startGzipStyle } from '../vite/plugin/gzip/compress';
export const runBuild = async () => {
try {
......@@ -13,8 +12,6 @@ export const runBuild = async () => {
if (!argvList.includes('no-conf')) {
await runBuildConfig();
}
// await runUpdateHtml();
await startGzipStyle();
successConsole('Vite Build successfully!');
} catch (error) {
errorConsole('Vite Build Error\n' + error);
......
......@@ -63,11 +63,11 @@ export function getIPAddress() {
return '';
}
export function isDevFn(mode: 'development' | 'production'): boolean {
export function isDevFn(mode: string): boolean {
return mode === 'development';
}
export function isProdFn(mode: 'development' | 'production'): boolean {
export function isProdFn(mode: string): boolean {
return mode === 'production';
}
......@@ -85,13 +85,6 @@ export function isBuildGzip(): boolean {
return process.env.VITE_BUILD_GZIP === 'true';
}
/**
* Whether to generate package site
*/
export function isSiteMode(): boolean {
return process.env.SITE === 'true';
}
export interface ViteEnv {
VITE_PORT: number;
VITE_USE_MOCK: boolean;
......@@ -99,10 +92,12 @@ export interface ViteEnv {
VITE_PUBLIC_PATH: string;
VITE_PROXY: [string, string][];
VITE_GLOB_APP_TITLE: string;
VITE_GLOB_APP_SHORT_NAME: string;
VITE_USE_CDN: boolean;
VITE_DROP_CONSOLE: boolean;
VITE_BUILD_GZIP: boolean;
VITE_DYNAMIC_IMPORT: boolean;
VITE_LEGACY: boolean;
}
// Read all environment variable configuration files to process.env
......
// Baidu statistics code for site deployment
// Only open in 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>
`;
import gzipPlugin from 'rollup-plugin-gzip';
import { isBuildGzip } from '../../utils';
import { Plugin } from 'vite';
export function configGzipPlugin(isBuild: boolean): Plugin | Plugin[] {
const useGzip = isBuild && isBuildGzip;
if (useGzip) {
return gzipPlugin();
}
return [];
}
import { gzip } from 'zlib';
import { readFileSync, writeFileSync } from 'fs';
import { GzipPluginOptions } from './types';
import { readAllFile, getCwdPath, isBuildGzip, isSiteMode } from '../../../utils';
export function startGzip(
fileContent: string | Buffer,
options: GzipPluginOptions = {}
): Promise<Buffer> {
return new Promise((resolve, reject) => {
gzip(fileContent, options.gzipOptions || {}, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
// 手动压缩css
export async function startGzipStyle() {
if (isBuildGzip() || isSiteMode()) {
const outDir = 'dist';
const assets = '_assets';
const allCssFile = readAllFile(getCwdPath(outDir, assets), /\.(css)$/);
for (const path of allCssFile) {
const source = readFileSync(path);
const content = await startGzip(source);
const ds = path.split('/');
const fileName = ds[ds.length - 1];
writeFileSync(getCwdPath(outDir, assets, `${fileName}.gz`), content);
}
}
}
// 修改自https://github.com/kryops/rollup-plugin-gzip
// 因为rollup-plugin-gzip不支持vite
// vite对css打包独立的。所以不能在打包的时候顺带打包css
// TODO rc.9会支持 configurBuild 配置项。到时候重新修改
import { readFile, writeFile } from 'fs';
import { basename } from 'path';
import { promisify } from 'util';
import { gzip } from 'zlib';
import { OutputAsset, OutputChunk, OutputOptions, Plugin } from 'rollup';
import { GzipPluginOptions } from './types';
const isFunction = (arg: unknown): arg is (...args: any[]) => any => typeof arg === 'function';
const isRegExp = (arg: unknown): arg is RegExp =>
Object.prototype.toString.call(arg) === '[object RegExp]';
export type StringMappingOption = (originalString: string) => string;
export type CustomCompressionOption = (
content: string | Buffer
) => string | Buffer | Promise<string | Buffer>;
const readFilePromise = promisify(readFile);
const writeFilePromise = promisify(writeFile);
// functionality partially copied from rollup
/**
* copied from https://github.com/rollup/rollup/blob/master/src/rollup/index.ts#L450
*/
function isOutputChunk(file: OutputAsset | OutputChunk): file is OutputChunk {
return typeof (file as OutputChunk).code === 'string';
}
/**
* Gets the string/buffer content from a file object.
* Important for adding source map comments
*
* Copied partially from rollup.writeOutputFile
* https://github.com/rollup/rollup/blob/master/src/rollup/index.ts#L454
*/
function getOutputFileContent(
outputFileName: string,
outputFile: OutputAsset | OutputChunk,
outputOptions: OutputOptions
): string | Buffer {
if (isOutputChunk(outputFile)) {
let source: string | Buffer;
source = outputFile.code;
if (outputOptions.sourcemap && outputFile.map) {
const url =
outputOptions.sourcemap === 'inline'
? outputFile.map.toUrl()
: `${basename(outputFileName)}.map`;
// https://github.com/rollup/rollup/blob/master/src/utils/sourceMappingURL.ts#L1
source += `//# source` + `MappingURL=${url}\n`;
}
return source;
} else {
return typeof outputFile.source === 'string'
? outputFile.source
: // just to be sure, as it is typed string | Uint8Array in rollup 2.0.0
Buffer.from(outputFile.source);
}
}
// actual plugin code
function gzipPlugin(options: GzipPluginOptions = {}): Plugin {
// check for old options
if ('algorithm' in options) {
console.warn(
'[rollup-plugin-gzip] The "algorithm" option is not supported any more! ' +
'Use "customCompression" instead to specify a different compression algorithm.'
);
}
if ('options' in options) {
console.warn('[rollup-plugin-gzip] The "options" option was renamed to "gzipOptions"!');
}
if ('additional' in options) {
console.warn('[rollup-plugin-gzip] The "additional" option was renamed to "additionalFiles"!');
}
if ('delay' in options) {
console.warn('[rollup-plugin-gzip] The "delay" option was renamed to "additionalFilesDelay"!');
}
const compressGzip: CustomCompressionOption = (fileContent) => {
return new Promise((resolve, reject) => {
gzip(fileContent, options.gzipOptions || {}, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
};
const doCompress = options.customCompression || compressGzip;
const mapFileName: StringMappingOption = isFunction(options.fileName)
? (options.fileName as StringMappingOption)
: (fileName: string) => fileName + (options.fileName || '.gz');
const plugin: Plugin = {
name: 'gzip',
generateBundle(outputOptions, bundle) {
return Promise.all(
Object.keys(bundle)
.map((fileName) => {
const fileEntry = bundle[fileName];
// file name filter option check
const fileNameFilter = options.filter || /\.(js|mjs|json|css|html)$/;
if (isRegExp(fileNameFilter) && !fileName.match(fileNameFilter)) {
return Promise.resolve();
}
if (
isFunction(fileNameFilter) &&
!(fileNameFilter as (x: string) => boolean)(fileName)
) {
return Promise.resolve();
}
const fileContent = getOutputFileContent(fileName, fileEntry, outputOptions);
// minSize option check
if (options.minSize && options.minSize > fileContent.length) {
return Promise.resolve();
}
return Promise.resolve(doCompress(fileContent))
.then((compressedContent) => {
const compressedFileName = mapFileName(fileName);
bundle[compressedFileName] = {
type: 'asset', // Rollup >= 1.21
name: compressedFileName,
fileName: compressedFileName,
isAsset: true, // Rollup < 1.21
source: compressedContent,
};
})
.catch((err: any) => {
console.error(err);
return Promise.reject('[rollup-plugin-gzip] Error compressing file ' + fileName);
});
})
.concat([
(() => {
if (!options.additionalFiles || !options.additionalFiles.length)
return Promise.resolve();
const compressAdditionalFiles = () =>
Promise.all(
options.additionalFiles!.map((filePath) =>
readFilePromise(filePath)
.then((fileContent) => doCompress(fileContent))
.then((compressedContent) => {
return writeFilePromise(mapFileName(filePath), compressedContent);
})
.catch(() => {
return Promise.reject(
'[rollup-plugin-gzip] Error compressing additional file ' +
filePath +
'. Please check the spelling of your configured additionalFiles. ' +
'You might also have to increase the value of the additionalFilesDelay option.'
);
})
)
) as Promise<any>;
// additional files can be processed outside of rollup after a delay
// for older plugins or plugins that write to disk (curcumventing rollup) without awaiting
const additionalFilesDelay = options.additionalFilesDelay || 0;
if (additionalFilesDelay) {
setTimeout(compressAdditionalFiles, additionalFilesDelay);
return Promise.resolve();
} else {
return compressAdditionalFiles();
}
})(),
])
) as Promise<any>;
},
};
return plugin;
}
export default gzipPlugin;
import type { ZlibOptions } from 'zlib';
export type StringMappingOption = (originalString: string) => string;
export type CustomCompressionOption = (
content: string | Buffer
) => string | Buffer | Promise<string | Buffer>;
export interface GzipPluginOptions {
/**
* Control which of the output files to compress
*
* Defaults to `/\.(js|mjs|json|css|html)$/`
*/
filter?: RegExp | ((fileName: string) => boolean);
/**
* GZIP compression options, see https://nodejs.org/api/zlib.html#zlib_class_options
*/
gzipOptions?: ZlibOptions;
/**
* Specified the minimum size in Bytes for a file to get compressed.
* Files that are smaller than this threshold will not be compressed.
* This does not apply to the files specified through `additionalFiles`!
*/
minSize?: number;
/**
* This option allows you to compress additional files outside of the main rollup bundling process.
* The processing is delayed to make sure the files are written on disk; the delay is controlled
* through `additionalFilesDelay`.
*/
additionalFiles?: string[];
/**
* This options sets a delay (ms) before the plugin compresses the files specified through `additionalFiles`.
* Increase this value if your artifacts take a long time to generate.
*
* Defaults to `2000`
*/
additionalFilesDelay?: number;
/**
* Set a custom compression algorithm. The function can either return the compressed contents synchronously,
* or otherwise return a promise for asynchronous processing.
*/
customCompression?: CustomCompressionOption;
/**
* Set a custom file name convention for the compressed files. Can be a suffix string or a function
* returning the file name.
*
* Defaults to `.gz`
*/
fileName?: string | StringMappingOption;
}
import type { Plugin } from 'vite';
import ViteHtmlPlugin from 'vite-plugin-html';
import { isProdFn, isSiteMode, ViteEnv } from '../../utils';
import html from 'vite-plugin-html';
import { ViteEnv } from '../../utils';
import { hmScript } from '../hm';
// @ts-ignore
import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
export function setupHtmlPlugin(
plugins: Plugin[],
env: ViteEnv,
mode: 'development' | 'production'
) {
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
const htmlPlugin = ViteHtmlPlugin({
// html title
title: VITE_GLOB_APP_TITLE,
minify: isProdFn(mode),
options: {
publicPath: VITE_PUBLIC_PATH,
// Package and insert additional configuration files
injectConfig: isProdFn(mode)
? `<script src='${VITE_PUBLIC_PATH || './'}${GLOB_CONFIG_FILE_NAME}?v=${
pkg.version
}-${new Date().getTime()}'></script>`
: '',
// Insert Baidu statistics code
hmScript: isSiteMode() ? hmScript : '',
title: VITE_GLOB_APP_TITLE,
const htmlPlugin: Plugin[] = html({
minify: isBuild,
inject: {
injectData: {
title: VITE_GLOB_APP_TITLE,
},
tags: isBuild
? [
{
tag: 'script',
attrs: {
src: `${VITE_PUBLIC_PATH || './'}${GLOB_CONFIG_FILE_NAME}?v=${
pkg.version
}-${new Date().getTime()}`,
},
},
]
: [],
},
});
plugins.push(htmlPlugin);
return plugins;
return htmlPlugin;
}
import dynamicImport from 'vite-plugin-import-context';
import type { ViteEnv } from '../../utils';
import type { Plugin } from 'vite';
export function configDynamicImport(env: ViteEnv) {
const { VITE_DYNAMIC_IMPORT } = env;
const dynamicImportPlugin: Plugin = dynamicImport({
include: ['**/*.ts'],
autoImportRoute: VITE_DYNAMIC_IMPORT,
});
return dynamicImportPlugin;
}
import type { Plugin as VitePlugin } from 'vite';
import type { Plugin as rollupPlugin } from 'rollup';
import type { Plugin } from 'vite';
import PurgeIcons from 'vite-plugin-purge-icons';
import visualizer from 'rollup-plugin-visualizer';
import gzipPlugin from './gzip/index';
// @ts-ignore
import pkg from '../../../package.json';
import { isSiteMode, ViteEnv, isReportMode, isBuildGzip } from '../../utils';
import { setupHtmlPlugin } from './html';
import { setupPwaPlugin } from './pwa';
import { setupMockPlugin } from './mock';
import { ViteEnv, isReportMode } from '../../utils';
import { configHtmlPlugin } from './html';
import { configPwaConfig } from './pwa';
import { configMockPlugin } from './mock';
import { configDynamicImport } from './importContext';
import { configGzipPlugin } from './gzip';
// gen vite plugins
export function createVitePlugins(viteEnv: ViteEnv, mode: 'development' | 'production') {
const vitePlugins: VitePlugin[] = [];
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, mode: string) {
const vitePlugins: (Plugin | Plugin[])[] = [];
// vite-plugin-html
setupHtmlPlugin(vitePlugins, viteEnv, mode);
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
// vite-plugin-pwa
setupPwaPlugin(vitePlugins, viteEnv, mode);
vitePlugins.push(configPwaConfig(viteEnv, isBuild));
// vite-plugin-mock
setupMockPlugin(vitePlugins, viteEnv, mode);
vitePlugins.push(configMockPlugin(viteEnv, isBuild));
// vite-plugin-import-context
vitePlugins.push(configDynamicImport(viteEnv));
// vite-plugin-purge-icons
vitePlugins.push(PurgeIcons());
return vitePlugins;
}
// gen rollup plugins
export function createRollupPlugin() {
const rollupPlugins: rollupPlugin[] = [];
// rollup-plugin-gzip
vitePlugins.push(configGzipPlugin(isBuild));
// rollup-plugin-visualizer
if (isReportMode()) {
// rollup-plugin-visualizer
rollupPlugins.push(visualizer({ filename: './build/.cache/stats.html', open: true }) as Plugin);
}
if (isBuildGzip() || isSiteMode()) {
// rollup-plugin-gizp
rollupPlugins.push(gzipPlugin());
vitePlugins.push(visualizer({ filename: './build/.cache/stats.html', open: true }) as Plugin);
}
return rollupPlugins;
return vitePlugins;
}
import { createMockServer } from 'vite-plugin-mock';
import type { Plugin } from 'vite';
import { isDevFn, ViteEnv } from '../../utils';
import { viteMockServe } from 'vite-plugin-mock';
import { ViteEnv } from '../../utils';
export function setupMockPlugin(
plugins: Plugin[],
env: ViteEnv,
mode: 'development' | 'production'
) {
export function configMockPlugin(env: ViteEnv, isBuild: boolean) {
const { VITE_USE_MOCK } = env;
const useMock = isDevFn(mode) && VITE_USE_MOCK;
const useMock = !isBuild && VITE_USE_MOCK;
if (useMock) {
const mockPlugin = createMockServer({
const mockPlugin = viteMockServe({
ignore: /^\_/,
mockPath: 'mock',
showTime: true,
localEnabled: useMock,
});
plugins.push(mockPlugin);
return mockPlugin;
}
return plugins;
return [];
}
import { VitePWA } from 'vite-plugin-pwa';
import type { Plugin } from 'vite';
import { ViteEnv } from '../../utils';
export function setupPwaPlugin(
plugins: Plugin[],
env: ViteEnv,
// @ts-ignore
mode: 'development' | 'production'
) {
const { VITE_USE_PWA } = env;
export function configPwaConfig(env: ViteEnv, isBulid: boolean) {
const { VITE_USE_PWA, VITE_GLOB_APP_TITLE, VITE_GLOB_APP_SHORT_NAME } = env;
const pwaPlugin = VitePWA({
manifest: {
name: 'Vben Admin',
short_name: 'vben_admin',
icons: [
{
src: './resource/img/pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: './resource/img/pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
});
if (VITE_USE_PWA) {
plugins.push(pwaPlugin);
if (VITE_USE_PWA && isBulid) {
// vite-plugin-pwa
const pwaPlugin = VitePWA({
manifest: {
name: VITE_GLOB_APP_TITLE,
short_name: VITE_GLOB_APP_SHORT_NAME,
icons: [
{
src: './resource/img/pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: './resource/img/pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
});
return pwaPlugin;
}
return plugins;
return [];
}
// Used to import all files under `src/views`
// The built-in dynamic import of vite cannot meet the needs of importing all files under views
// Special usage ,Only for this project
import glob from 'glob';
import { Transform } from 'vite/dist/node/transform.js';
function getPath(path: string) {
const lastIndex = path.lastIndexOf('.');
if (lastIndex !== -1) {
path = path.substring(0, lastIndex);
}
return path.replace('src/views', '');
}
const dynamicImportTransform = function (enableDynamicImport: boolean): Transform {
return {
test({ path }) {
// Only convert the file
return (
path.includes('/src/router/helper/dynamicImport.ts') ||
path.includes(`\\src\\router\\helper\\dynamicImport.ts`)
);
},
transform({ code }) {
if (!enableDynamicImport) {
return code;
}
// Only convert the dir
try {
const files = glob.sync('src/views/**/**.{vue,tsx}', { cwd: process.cwd() });
return `
export default function (id) {
switch (id) {
${files
.map((p) =>
` case '${getPath(p)}': return () => import('${p
.replace('src/views', '/@/views')
.replace(/\/\//g, '/')}');`.replace('.tsx', '')
)
.join('\n ')}
default: return Promise.reject(new Error("Unknown variable dynamic import: " + id));
}
}\n\n
`;
} catch (error) {
console.error(error);
return code;
}
},
};
};
export default dynamicImportTransform;
// Modified from
// https://github.com/luxueyan/vite-transform-globby-import/blob/master/src/index.ts
// TODO Deleting files requires re-running the project
import { join } from 'path';
import { lstatSync } from 'fs';
import glob from 'glob';
import globrex from 'globrex';
import dotProp from 'dot-prop';
import { createResolver, Resolver } from 'vite/dist/node/resolver.js';
import { Transform } from 'vite/dist/node/transform.js';
const modulesDir: string = join(process.cwd(), '/node_modules/');
interface SharedConfig {
root?: string;
alias?: Record<string, string>;
resolvers?: Resolver[];
includes?: string[];
}
function template(template: string) {
return (data: { [x: string]: any }) => {
return template.replace(/#([^#]+)#/g, (_, g1) => data[g1] || g1);
};
}
// TODO support hmr
function hmr(isBuild = false) {
if (isBuild) return '';
return `
if (import.meta.hot) {
import.meta.hot.accept();
}`;
}
// handle includes
function fileInclude(includes: string | string[] | undefined, filePath: string) {
return !includes || !Array.isArray(includes)
? true
: includes.some((item) => filePath.startsWith(item));
}
// Bare exporter
function compareString(modify: any, data: string[][]) {
return modify ? '\n' + data.map((v) => `${v[0]}._path = ${v[1]}`).join('\n') : '';
}
function varTemplate(data: string[][], name: string) {
//prepare deep data (for locales)
let deepData: Record<string, object | string> = {};
let hasDeepData = false;
//data modify
data.map((v) => {
//check for has deep data
if (v[0].includes('/')) {
hasDeepData = true;
}
// lastKey is a data
let pathValue = v[0].replace(/\//g, '.').split('.');
// let scopeKey = '';
// const len=pathValue.length
// const scope=pathValue[len-2]
let lastKey: string | undefined = pathValue.pop();
let deepValue: Record<any, any> = {};
if (lastKey) {
// Solve the problem of files with the same name in different folders
const lastKeyList = lastKey.replace('_' + pathValue[0], '').split('_');
const key = lastKeyList.pop();
if (key) {
deepValue[key] = lastKey;
}
}
// Set Deep Value
deepValue = Object.assign(deepValue, dotProp.get(deepData, pathValue.join('.')));
dotProp.set(deepData, pathValue.join('.'), deepValue);
});
if (hasDeepData) {
return `const ${name} = ` + JSON.stringify(deepData).replace(/\"|\'/g, '');
}
return `const ${name} = { ${data.map((v) => v[0]).join(',')} }`;
}
const globTransform = function (config: SharedConfig): Transform {
const resolver = createResolver(
config.root || process.cwd(),
config.resolvers || [],
config.alias || {}
);
const { includes } = config;
const cache = new Map();
const urlMap = new Map();
return {
test({ path }) {
const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
try {
return (
!filePath.startsWith(modulesDir) &&
/\.(vue|js|jsx|ts|tsx)$/.test(filePath) &&
fileInclude(includes, filePath) &&
lstatSync(filePath).isFile()
);
} catch {
return false;
}
},
transform({ code, path, isBuild }) {
let result = cache.get(path);
if (!result) {
const reg = /import\s+([\w\s{}*]+)\s+from\s+(['"])globby(\?locale)?(\?path)?!([^'"]+)\2/g;
const match = code.match(reg);
if (!match) return code;
const lastImport = urlMap.get(path);
if (lastImport && match) {
code = code.replace(lastImport, match[0]);
}
result = code.replace(
reg,
(
_,
// variable to export
exportName,
// bare export or not
bareExporter,
// is locale import
isLocale,
// inject _path attr
injectPath,
// path export
globPath
) => {
const filePath = path.replace('\u0000', ''); // why some path startsWith '\u0000'?
// resolve path
const resolvedFilePath = globPath.startsWith('.')
? resolver.resolveRelativeRequest(filePath, globPath)
: { pathname: resolver.requestToFile(globPath) };
const files = glob.sync(resolvedFilePath.pathname, { dot: true });
let templateStr = 'import #name# from #file#'; // import default
let name = exportName;
const m = exportName.match(/\{\s*(\w+)(\s+as\s+(\w+))?\s*\}/); // import module
const m2 = exportName.match(/\*\s+as\s+(\w+)/); // import * as all module
if (m) {
templateStr = `import { ${m[1]} as #name# } from #file#`;
name = m[3] || m[1];
} else if (m2) {
templateStr = 'import * as #name# from #file#';
name = m2[1];
}
const templateRender = template(templateStr);
const groups: Array<string>[] = [];
const replaceFiles = files.map((f, i) => {
const filePath = resolver.fileToRequest(f);
const file = bareExporter + filePath + bareExporter;
if (isLocale) {
const globrexRes = globrex(globPath, { extended: true, globstar: true });
// Get segments for files like an en/system ch/modules for:
// ['en', 'system'] ['ch', 'modules']
// TODO The window system and mac system path are inconsistent?
const fileNameWithAlias = filePath.replace(/^(\/src\/)/, '/@/');
const matchedGroups = globrexRes.regex.exec(fileNameWithAlias);
if (matchedGroups && matchedGroups.length) {
const matchedSegments = matchedGroups[1]; //first everytime "Full Match"
const matchList = matchedSegments.split('/').filter(Boolean);
const lang = matchList.shift();
const scope = matchList.pop();
// Solve the problem of files with the same name in different folders
const scopeKey = scope ? `${scope}_` : '';
const fileName = matchedGroups[2];
const name = scopeKey + fileName + '_' + lang;
//send deep way like an (en/modules/system/dashboard) into groups
groups.push([matchedSegments + name, file]);
return templateRender({
name,
file,
});
}
} else {
groups.push([name + i, file]);
return templateRender({ name: name + i, file });
}
});
// save in memory used result
const filesJoined = replaceFiles.join('\n');
urlMap.set(path, filesJoined);
// console.log('======================');
// console.log(filesJoined, varTemplate(groups, name));
// console.log('======================');
return [
filesJoined,
compareString(injectPath, groups),
varTemplate(groups, name),
'',
].join('\n');
}
);
if (isBuild) cache.set(path, result);
}
return `${result}${hmr(isBuild)}`;
},
};
};
export default globTransform;
import type { ServerOptions } from 'http-proxy';
type ProxyItem = [string, string];
type ProxyList = ProxyItem[];
type ProxyTargetList = Record<
string,
{
target: string;
changeOrigin: boolean;
rewrite: (path: string) => any;
secure?: boolean;
}
>;
type ProxyTargetList = Record<string, ServerOptions & { rewrite: (path: string) => string }>;
const httpsRE = /^https:\/\//;
......@@ -23,9 +17,11 @@ export function createProxy(list: ProxyList = []) {
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 } : {}),
......
<!DOCTYPE html>
<html lang="en">
<head>
<%= viteHtmlPluginOptions.hmScript %>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
......@@ -10,9 +9,8 @@
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<title></title>
<title><%= title %></title>
<link rel="icon" href="/favicon.ico" />
<%= viteHtmlPluginOptions.injectConfig %>
</head>
<body>
<div id="app">
......@@ -137,15 +135,11 @@
</style>
<div class="app-loading">
<div class="app-loading-wrap">
<img
src="<%= viteHtmlPluginOptions.publicPath %>resource/img/logo.png"
class="app-loading-logo"
alt="Logo"
/>
<img src="/resource/img/logo.png" class="app-loading-logo" alt="Logo" />
<div class="app-loading-dots">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
<div class="app-loading-title"><%= viteHtmlPluginOptions.title %></div>
<div class="app-loading-title"><%= title %></div>
</div>
</div>
</div>
......
{
"name": "vben-admin",
"version": "2.0.0-rc.15",
"version": "2.0.0-rc.16",
"scripts": {
"bootstrap": "yarn install",
"serve": "cross-env vite --mode=development",
"build": "cross-env vite build --mode=production && esno ./build/script/postBuild.ts",
"build:site": "cross-env SITE=true npm run build ",
"serve": "cross-env vite ",
"build": "cross-env vite build && esno ./build/script/postBuild.ts",
"build:no-cache": "yarn clean:cache && npm run build",
"typecheck": "vuedx-typecheck .",
"report": "cross-env REPORT=true npm run build ",
"preview": "npm run build && esno ./build/script/preview.ts",
"preview:dist": "esno ./build/script/preview.ts",
"log": "esno ./build/script/changelog.ts",
"log": "conventional-changelog -p custom-config -i CHANGELOG.md -s -r 0",
"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/",
......@@ -22,7 +19,7 @@
},
"dependencies": {
"@iconify/iconify": "^2.0.0-rc.5",
"@vueuse/core": "^4.0.2",
"@vueuse/core": "^4.0.5",
"ant-design-vue": "^2.0.0-rc.8",
"apexcharts": "^3.23.1",
"axios": "^0.21.1",
......@@ -34,26 +31,27 @@
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0",
"qrcode": "^1.4.4",
"sortablejs": "^1.12.0",
"sortablejs": "^1.13.0",
"vditor": "^3.7.5",
"vue": "^3.0.5",
"vue-i18n": "9.0.0-beta.14",
"vue-i18n": "^9.0.0-rc.1",
"vue-router": "^4.0.2",
"vue-types": "^3.0.1",
"vuex": "^4.0.0-rc.2",
"vuex-module-decorators": "^1.0.1",
"xlsx": "^0.16.9",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@iconify/json": "^1.1.282",
"@iconify/json": "^1.1.283",
"@ls-lint/ls-lint": "^1.9.2",
"@purge-icons/generated": "^0.4.1",
"@purge-icons/generated": "^0.5.0",
"@types/echarts": "^4.9.3",
"@types/fs-extra": "^9.0.6",
"@types/globrex": "^0.1.0",
"@types/http-proxy": "^1.17.4",
"@types/koa-static": "^4.0.1",
"@types/lodash-es": "^4.17.4",
"@types/mockjs": "^1.0.3",
......@@ -65,10 +63,13 @@
"@types/zxcvbn": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.12.0",
"@vitejs/plugin-legacy": "^1.1.0",
"@vitejs/plugin-vue": "^1.0.4",
"@vitejs/plugin-vue-jsx": "^1.0.1",
"@vue/compiler-sfc": "^3.0.5",
"@vuedx/typecheck": "^0.4.1",
"@vuedx/typescript-plugin-vue": "^0.4.1",
"autoprefixer": "^9.8.6",
"autoprefixer": "^10.2.1",
"commitizen": "^4.2.2",
"conventional-changelog-cli": "^2.1.1",
"conventional-changelog-custom-config": "^0.3.1",
......@@ -82,14 +83,15 @@
"esno": "^0.4.0",
"fs-extra": "^9.0.1",
"globrex": "^0.1.2",
"husky": "^4.3.6",
"husky": "^4.3.7",
"koa-static": "^5.0.0",
"less": "^4.0.0",
"lint-staged": "^10.5.3",
"portfinder": "^1.0.28",
"postcss-import": "^12.0.1",
"postcss-import": "^14.0.0",
"prettier": "^2.2.1",
"rimraf": "^3.0.2",
"rollup-plugin-gzip": "^2.5.0",
"rollup-plugin-visualizer": "^4.1.2",
"stylelint": "^13.8.0",
"stylelint-config-prettier": "^8.0.2",
......@@ -98,11 +100,12 @@
"tasksfile": "^5.1.1",
"ts-node": "^9.1.0",
"typescript": "^4.1.3",
"vite": "^1.0.0-rc.13",
"vite-plugin-html": "^1.0.0-beta.2",
"vite-plugin-mock": "^1.0.9",
"vite-plugin-purge-icons": "^0.4.5",
"vite-plugin-pwa": "^0.2.1",
"vite": "^2.0.0-beta.15",
"vite-plugin-html": "^2.0.0-beta.5",
"vite-plugin-import-context": "^1.0.0-rc.1",
"vite-plugin-mock": "^2.0.0-beta.1",
"vite-plugin-purge-icons": "^0.5.0",
"vite-plugin-pwa": "^0.3.2",
"vue-eslint-parser": "^7.3.0",
"yargs": "^16.2.0"
},
......
......@@ -13,7 +13,7 @@
import { initAppConfigStore } from '/@/setup/App';
import { useLockPage } from '/@/hooks/web/useLockPage';
import { useLocale } from '/@/hooks/web/useLocale';
import { useLocale } from '/@/locales/useLocale';
import { AppProvider } from '/@/components/Application';
......@@ -21,6 +21,9 @@
name: 'App',
components: { ConfigProvider, AppProvider },
setup() {
const { antConfigLocale, setLocale } = useLocale();
setLocale();
// Initialize vuex internal system configuration
initAppConfigStore();
......@@ -28,7 +31,6 @@
const lockEvent = useLockPage();
// support Multi-language
const { antConfigLocale } = useLocale();
return {
antConfigLocale,
......
......@@ -22,7 +22,7 @@
import { Dropdown, DropMenu } from '/@/components/Dropdown';
import { GlobalOutlined } from '@ant-design/icons-vue';
import { useLocale } from '/@/hooks/web/useLocale';
import { useLocale } from '/@/locales/useLocale';
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
import { LocaleType } from '/@/locales/types';
......@@ -75,7 +75,6 @@
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-app-locale-picker';
:global(.@{prefix-cls}-overlay) {
......
......@@ -59,7 +59,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-app-logo';
.@{prefix-cls} {
......
......@@ -44,7 +44,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-app-search';
.@{prefix-cls} {
......
......@@ -37,7 +37,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-app-search-footer';
.@{prefix-cls} {
......
......@@ -123,7 +123,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-app-search-modal';
@footer-prefix-cls: ~'@{namespace}-app-search-footer';
.@{prefix-cls} {
......
......@@ -46,7 +46,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-arrow';
.@{prefix-cls} {
......
......@@ -112,7 +112,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-help';
.@{prefix-cls} {
......
......@@ -30,7 +30,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-title';
.@{prefix-cls} {
......
......@@ -140,7 +140,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-lazy-container';
.@{prefix-cls} {
......
......@@ -12,7 +12,7 @@
export default defineComponent({
name: 'ScrollContainer',
inheritAttrs: false,
// inheritAttrs: false,
components: { Scrollbar },
setup() {
const scrollbarRef = ref<Nullable<ScrollbarType>>(null);
......
......@@ -87,7 +87,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-collapse-container';
.@{prefix-cls} {
......
@import (reference) '../../../design/index.less';
@default-height: 42px !important;
@small-height: 36px !important;
......
......@@ -76,9 +76,7 @@ export default defineComponent({
return (
<>
<Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
{() => [
<ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />,
]}
<ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />
</Menu.Item>
{DividerComp}
</>
......@@ -109,7 +107,7 @@ export default defineComponent({
ref={wrapRef}
style={unref(getStyle)}
>
{() => renderMenuItem(items)}
{renderMenuItem(items)}
</Menu>
);
};
......
......@@ -114,7 +114,7 @@ export default defineComponent({
const renderDesc = () => {
return (
<Descriptions class={`${prefixCls}`} {...(unref(getDescriptionsProps) as any)}>
{() => renderItem()}
{renderItem()}
</Descriptions>
);
};
......
......@@ -199,7 +199,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@header-height: 60px;
@detail-header-height: 40px;
@prefix-cls: ~'@{namespace}-basic-drawer';
......
......@@ -66,7 +66,6 @@
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-drawer-footer';
@footer-height: 60px;
.@{prefix-cls} {
......
......@@ -45,7 +45,6 @@
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-drawer-header';
@footer-height: 60px;
.@{prefix-cls} {
......
......@@ -247,7 +247,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-form';
.@{prefix-cls} {
......
......@@ -173,7 +173,8 @@ export default defineComponent({
const characterInx = rules.findIndex((val) => val.max);
if (characterInx !== -1 && !rules[characterInx].validator) {
rules[characterInx].message =
rules[characterInx].message || t('component.form.maxTip', [rules[characterInx].max]);
rules[characterInx].message ||
t('component.form.maxTip', [rules[characterInx].max] as Recordable);
}
return rules;
}
......@@ -294,12 +295,10 @@ export default defineComponent({
labelCol={labelCol}
wrapperCol={wrapperCol}
>
{() => (
<>
{getContent()}
{showSuffix && <span class="suffix">{getSuffix}</span>}
</>
)}
<>
{getContent()}
{showSuffix && <span class="suffix">{getSuffix}</span>}
</>
</Form.Item>
);
}
......@@ -323,7 +322,7 @@ export default defineComponent({
return (
isIfShow && (
<Col {...realColProps} class={{ hidden: !isShow }}>
{() => getContent()}
{getContent()}
</Col>
)
);
......
......@@ -83,8 +83,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
.app-iconify {
display: inline-block;
vertical-align: middle;
......
......@@ -7,7 +7,7 @@
import 'vditor/dist/index.css';
import { propTypes } from '/@/utils/propTypes';
import { useLocale } from '/@/hooks/web/useLocale';
import { useLocale } from '/@/locales/useLocale';
import { useModalContext } from '../../Modal';
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
......
@import (reference) '../../../design/index.less';
@basic-menu-prefix-cls: ~'@{namespace}-basic-menu';
@basic-menu-content-prefix-cls: ~'@{namespace}-basic-menu-item-content';
@basic-menu-tag-prefix-cls: ~'@{namespace}-basic-menu-item-tag';
......
......@@ -54,7 +54,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-basic-modal-close';
.@{prefix-cls} {
display: flex;
......
@import (reference) '../../../design/index.less';
.fullscreen-modal {
overflow: hidden;
......@@ -79,7 +77,7 @@
&-confirm-body {
.ant-modal-confirm-content {
color: #fff;
// color: #fff;
> * {
color: @text-color-help-dark;
......
export { createImgPreview } from './src/functional';
// export { createImgPreview } from './src/functional';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export const ImagePreview = createAsyncComponent(() => import('./src/index.vue'));
export const createImgPreview = () => {};
// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
// export const ImagePreview = createAsyncComponent(() => import('./src/index.vue'));
export { default as ImagePreview } from './src/index.vue';
@import (reference) '../../../design/index.less';
.img-preview {
position: fixed;
top: 0;
......
......@@ -58,7 +58,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-image-preview';
.@{prefix-cls} {
......
......@@ -34,7 +34,7 @@
export default defineComponent({
name: 'Scrollbar',
inheritAttrs: false,
// inheritAttrs: false,
components: { Bar },
props: {
native: {
......
......@@ -83,7 +83,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-strength-meter';
.@{prefix-cls} {
......
@import (reference) '../../../design/index.less';
.file-table {
width: 100%;
border-collapse: collapse;
......
@import (reference) '../../../design/index.less';
@radius: 4px;
.darg-verify {
......
@import (reference) '../../../design/index.less';
.ir-dv {
position: relative;
display: flex;
......
......@@ -35,25 +35,21 @@ import {
Menu,
Breadcrumb,
} from 'ant-design-vue';
import { getApp } from '/@/setup/App';
import { App } from 'vue';
const compList = [Icon, Button, AntButton.Group];
// Fix hmr multiple registered components
let registered = false;
export function registerGlobComp() {
if (registered) return;
export function registerGlobComp(app: App) {
compList.forEach((comp: any) => {
getApp().component(comp.name, comp);
app.component(comp.name, comp);
});
registered = true;
// Optional
// Why register here: The main reason for registering here is not to increase the size of the first screen code
// If you need to customize global components, you can write here
// If you don’t need it, you can delete it
getApp()
app
.use(Select)
.use(Alert)
.use(Breadcrumb)
......
......@@ -4,7 +4,7 @@ import { computed, unref } from 'vue';
import { appStore } from '/@/store/modules/app';
import getProjectSetting from '/@/settings/projectSetting';
import { localeList } from '/@/locales';
import { localeList } from '/@/locales/constant';
// Get locale configuration
const getLocale = computed(() => appStore.getProjectConfig.locale || getProjectSetting.locale);
......
import { getI18n } from '/@/setup/i18n';
import { i18n } from '/@/locales/setupI18n';
export function useI18n(namespace?: string) {
function getKey(key: string) {
......@@ -16,18 +16,19 @@ export function useI18n(namespace?: string) {
},
};
if (!getI18n()) {
if (!i18n) {
return normalFn;
}
const { t, ...methods } = getI18n().global;
const { t, ...methods } = i18n.global;
const tFn = function (...arg: Parameters<typeof t>) {
if (!arg[0]) return '';
return t(getKey(arg[0]), ...(arg as Parameters<typeof t>));
};
return {
...methods,
t: (key: string, ...arg: any): string => {
if (!key) return '';
return t(getKey(key), ...(arg as Parameters<typeof t>));
},
t: tFn,
};
}
......
......@@ -59,7 +59,7 @@ function createConfirm(options: ModalOptionsEx): ConfirmOptions {
icon: getIcon(iconType),
...options,
};
return Modal.confirm(opt) as any;
return (Modal.confirm(opt) as unknown) as ConfirmOptions;
}
const baseOptions = {
......
......@@ -18,7 +18,7 @@
import { useDesign } from '/@/hooks/web/useDesign';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
import PageLayout from '/@/layouts/page/index';
import PageLayout from '/@/layouts/page/index.vue';
import { useContentViewHeight } from './useContentViewHeight';
import { Loading } from '/@/components/Loading';
......@@ -41,7 +41,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-content';
.@{prefix-cls} {
......
......@@ -40,7 +40,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-footer';
@normal-color: rgba(0, 0, 0, 0.45);
......
......@@ -109,7 +109,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-multiple-header';
.@{prefix-cls} {
......
......@@ -147,7 +147,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-breadcrumb';
.@{prefix-cls} {
......
......@@ -80,7 +80,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../../design/index.less';
@prefix-cls: ~'@{namespace}-header-lock-modal';
.@{prefix-cls} {
......
......@@ -49,7 +49,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../../design/index.less';
@prefix-cls: ~'@{namespace}-header-notify-list';
.@{prefix-cls} {
......
......@@ -49,7 +49,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../../design/index.less';
@prefix-cls: ~'@{namespace}-header-notify';
.@{prefix-cls} {
......
......@@ -99,7 +99,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../../design/index.less';
@prefix-cls: ~'@{namespace}-header-user-dropdown';
.@{prefix-cls} {
......
@import (reference) '../../../design/index.less';
@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger';
@header-prefix-cls: ~'@{namespace}-layout-header';
@locale-prefix-cls: ~'@{namespace}-app-locale-picker';
......
......@@ -31,7 +31,6 @@
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign';
import { registerGlobComp } from '/@/components/registerGlobComp';
import { useAppInject } from '/@/hooks/web/useAppInject';
export default defineComponent({
......@@ -46,11 +45,6 @@
Layout,
},
setup() {
// ! Only register global components here
// ! Can reduce the size of the first screen code
// default layout It is loaded after login. So it won’t be packaged to the first screen
registerGlobComp();
const { prefixCls } = useDesign('default-layout');
const { getIsMobile } = useAppInject();
......@@ -70,7 +64,6 @@
});
</script>
<style lang="less">
@import (reference) '../../design/index.less';
@prefix-cls: ~'@{namespace}-default-layout';
.@{prefix-cls} {
......
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-menu';
@logo-prefix-cls: ~'@{namespace}-app-logo';
......
......@@ -389,26 +389,20 @@ export default defineComponent({
width={330}
wrapClassName="setting-drawer"
>
{{
default: () => (
<>
<Divider>{() => t('layout.setting.navMode')}</Divider>
{renderSidebar()}
<Divider>{() => t('layout.setting.headerTheme')}</Divider>
{renderHeaderTheme()}
<Divider>{() => t('layout.setting.sidebarTheme')}</Divider>
{renderSiderTheme()}
<Divider>{() => t('layout.setting.interfaceFunction')}</Divider>
{renderFeatures()}
<Divider>{() => t('layout.setting.interfaceDisplay')}</Divider>
{renderContent()}
<Divider>{() => t('layout.setting.animation')}</Divider>
{renderTransition()}
<Divider />
<SettingFooter />
</>
),
}}
<Divider>{() => t('layout.setting.navMode')}</Divider>
{renderSidebar()}
<Divider>{() => t('layout.setting.headerTheme')}</Divider>
{renderHeaderTheme()}
<Divider>{() => t('layout.setting.sidebarTheme')}</Divider>
{renderSiderTheme()}
<Divider>{() => t('layout.setting.interfaceFunction')}</Divider>
{renderFeatures()}
<Divider>{() => t('layout.setting.interfaceDisplay')}</Divider>
{renderContent()}
<Divider>{() => t('layout.setting.animation')}</Divider>
{renderTransition()}
<Divider />
<SettingFooter />
</BasicDrawer>
);
},
......
......@@ -43,7 +43,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-setting-input-number-item';
.@{prefix-cls} {
......
......@@ -62,7 +62,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-setting-select-item';
.@{prefix-cls} {
......
......@@ -75,7 +75,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-setting-footer';
.@{prefix-cls} {
......
......@@ -57,7 +57,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-setting-switch-item';
.@{prefix-cls} {
......
......@@ -55,7 +55,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-setting-theme-picker';
.@{prefix-cls} {
......
......@@ -51,7 +51,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'@{namespace}-setting-menu-type-picker';
.@{prefix-cls} {
......
......@@ -28,7 +28,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-setting-button';
.@{prefix-cls} {
......
......@@ -41,7 +41,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-darg-bar';
.@{prefix-cls} {
......
......@@ -128,7 +128,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-sideBar';
.@{prefix-cls} {
......
......@@ -333,7 +333,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-mix-sider';
@tag-prefix-cls: ~'@{namespace}-basic-menu-item-tag';
@width: 80px;
......
......@@ -41,7 +41,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-layout-sider-wrapper';
.@{prefix-cls} {
.ant-drawer-body {
......
@import (reference) '../../../design/index.less';
@prefix-cls: ~'@{namespace}-multiple-tabs';
.@{prefix-cls} {
......
import type { DefaultContext } from './transition';
import { computed, defineComponent, unref, Transition, KeepAlive } from 'vue';
import { RouterView } from 'vue-router';
import FrameLayout from '/@/layouts/iframe/index.vue';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
import { useCache } from './useCache';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { getTransitionName } from './transition';
export default defineComponent({
name: 'PageLayout',
setup() {
const { getCaches } = useCache(true);
const { getShowMultipleTab } = useMultipleTabSetting();
const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting();
const { getBasicTransition, getEnableTransition } = useTransitionSetting();
const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab));
return () => {
return (
<>
<RouterView>
{{
default: ({ Component, route }: DefaultContext) => {
// No longer show animations that are already in the tab
const cacheTabs = unref(getCaches);
const name = getTransitionName({
route,
openCache: unref(openCache),
enableTransition: unref(getEnableTransition),
cacheTabs,
def: unref(getBasicTransition),
});
// When the child element is the parentView, adding the key will cause the component to be executed multiple times. When it is not parentView, you need to add a key, because it needs to be compatible with the same route carrying different parameters
const isParentView = Component?.type.parentView;
const componentKey = isParentView ? {} : { key: route.fullPath };
const renderComp = () => <Component {...componentKey} />;
const PageContent = unref(openCache) ? (
<KeepAlive include={cacheTabs}>{renderComp()}</KeepAlive>
) : (
renderComp()
);
if (!unref(getEnableTransition)) {
return PageContent;
}
return (
<Transition name={name} mode="out-in" appear={true}>
{() => PageContent}
</Transition>
);
},
}}
</RouterView>
{unref(getCanEmbedIFramePage) && <FrameLayout />}
</>
);
};
},
});
<template>
<div>
<router-view>
<template v-slot="{ Component, route }">
<transition
:name="
getTransitionName({
route,
openCache,
enableTransition: getEnableTransition,
cacheTabs: getCaches,
def: getBasicTransition,
})
"
mode="out-in"
appear
>
<keep-alive v-if="openCache" :include="getCaches">
<component :is="Component" v-bind="getKey(Component, route)" />
</keep-alive>
<component v-else :is="Component" v-bind="getKey(Component, route)" />
</transition>
</template>
</router-view>
<FrameLayout v-if="getCanEmbedIFramePage" />
</div>
</template>
<script lang="ts">
import type { FunctionalComponent } from 'vue';
import type { RouteLocation } from 'vue-router';
import { computed, defineComponent, unref } from 'vue';
import FrameLayout from '/@/layouts/iframe/index.vue';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
import { useCache } from './useCache';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { getTransitionName } from './transition';
export default defineComponent({
name: 'PageLayout',
components: { FrameLayout },
setup() {
const { getCaches } = useCache(true);
const { getShowMultipleTab } = useMultipleTabSetting();
const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting();
const { getBasicTransition, getEnableTransition } = useTransitionSetting();
const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMultipleTab));
function getKey(component: FunctionalComponent & { type: Indexable }, route: RouteLocation) {
return !!component?.type.parentView ? {} : { key: route.fullPath };
}
return {
getTransitionName,
openCache,
getEnableTransition,
getBasicTransition,
getCaches,
getCanEmbedIFramePage,
getKey,
};
},
});
</script>
import messages from 'globby?locale!/@/locales/lang/**/*.@(ts)';
import type { DropMenu } from '/@/components/Dropdown';
// locale list
......@@ -13,4 +11,3 @@ export const localeList: DropMenu[] = [
event: 'en',
},
];
export default messages;
import { genMessage } from './helper';
import modules from 'glob:./lang/**/*.ts';
export default genMessage(modules);
import { set } from 'lodash-es';
export function genMessage(langs: Record<string, Record<string, any>>, prefix = 'lang') {
const obj: Recordable = {};
Object.keys(langs).forEach((key) => {
const mod = langs[key].default;
let k = key.replace(`./${prefix}/`, '').replace(/^\.\//, '');
const lastIndex = k.lastIndexOf('.');
k = k.substring(0, lastIndex);
const keyList = k.split('/');
const lang = keyList.shift();
const objKey = keyList.join('.');
if (lang) {
set(obj, lang, obj[lang] || {});
set(obj[lang], objKey, mod);
}
});
return obj;
}
import { App } from 'vue';
import type { App } from 'vue';
import type { I18n, I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';
import localeMessages from '/@/locales';
import { useLocale } from '/@/hooks/web/useLocale';
import 'moment/dist/locale/zh-cn';
import projectSetting from '/@/settings/projectSetting';
const { setupLocale } = useLocale();
import messages from './getMessage';
const { lang, availableLocales, fallback } = projectSetting?.locale;
const localeData: I18nOptions = {
legacy: false,
locale: lang,
fallbackLocale: fallback,
messages: localeMessages,
messages,
availableLocales: availableLocales,
sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false.
silentTranslationWarn: true, // true - warning off
missingWarn: false,
silentFallbackWarn: true,
};
let i18n: I18n;
export let i18n: I18n;
// setup i18n instance with glob
export function setupI18n(app: App) {
i18n = createI18n(localeData) as I18n;
setupLocale();
app.use(i18n);
}
export function getI18n(): I18n {
return i18n;
}
......@@ -2,18 +2,16 @@
* Multi-language related operations
*/
import type { LocaleType } from '/@/locales/types';
import type { Ref } from 'vue';
import { unref, ref } from 'vue';
import { getI18n } from '/@/setup/i18n';
import { useLocaleSetting } from '/@/hooks/setting/useLocaleSetting';
import moment from 'moment';
import 'moment/dist/locale/zh-cn';
moment.locale('zh-cn');
import { i18n } from './setupI18n';
const antConfigLocaleRef = ref<any>(null);
......@@ -23,7 +21,11 @@ export function useLocale() {
// Switching the language will change the locale of useI18n
// And submit to configuration modification
function changeLocale(lang: LocaleType): void {
(getI18n().global.locale as any).value = lang;
if (i18n.mode === 'legacy') {
i18n.global.locale = lang;
} else {
((i18n.global.locale as unknown) as Ref<string>).value = lang;
}
setLocalSetting({ lang });
// i18n.global.setLocaleMessage(locale, messages);
......@@ -51,13 +53,13 @@ export function useLocale() {
}
// initialization
function setupLocale() {
function setLocale() {
const lang = unref(getLang);
lang && changeLocale(lang);
}
return {
setupLocale,
setLocale,
getLocale,
getLang,
changeLocale,
......
......@@ -6,18 +6,19 @@ import { setupStore } from '/@/store';
import { setupAntd } from '/@/setup/ant-design-vue';
import { setupErrorHandle } from '/@/setup/error-handle';
import { setupGlobDirectives } from '/@/directives';
import { setupI18n } from '/@/setup/i18n';
import { setupI18n } from '/@/locales/setupI18n';
import { setupProdMockServer } from '../mock/_createProductionServer';
import { setApp } from '/@/setup/App';
import { registerGlobComp } from '/@/components/registerGlobComp';
import { isDevMode, isProdMode, isUseMock } from '/@/utils/env';
import '/@/design/index.less';
import '/@/locales/index';
const app = createApp(App);
registerGlobComp(app);
// Configure component library
setupAntd(app);
......@@ -51,5 +52,3 @@ if (isDevMode()) {
if (isProdMode() && isUseMock()) {
setupProdMockServer();
}
// Used to share app instances in other modules
setApp(app);
// The content here is just for type approval. The actual file content is overwritten by transform
// For specific coverage, see build/vite/plugin/transform/dynamic-import/index.ts
export default function (name: string) {
return name as any;
}
......@@ -2,8 +2,8 @@ import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types';
import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import dynamicImport from './dynamicImport';
import { cloneDeep } from 'lodash-es';
import { warn } from '/@/utils/log';
export type LayoutMapKey = 'LAYOUT';
......@@ -11,12 +11,20 @@ const LayoutMap = new Map<LayoutMapKey, () => Promise<typeof import('*.vue')>>()
// 动态引入
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
// TODO Because xlsx does not support vite2.0 temporarily. So filter the excel example first
const dynamicViewsModules = importContext({
dir: '/@/views',
deep: true,
regexp: /^(?!.*\/demo\/excel).*\.(tsx?|vue)$/,
dynamicImport: true,
dynamicEnabled: 'autoImportRoute',
});
if (!routes) return;
routes.forEach((item) => {
const { component, name } = item;
const { children } = item;
if (component) {
item.component = dynamicImport(component as string);
item.component = dynamicImport(dynamicViewsModules, component as string);
} else if (name) {
item.component = getParentLayout(name);
}
......@@ -24,6 +32,24 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
});
}
function dynamicImport(dynamicViewsModules: DynamicImportContextResult, component: string) {
const keys = dynamicViewsModules.keys();
const matchKeys = keys.filter((key) => {
const k = key.substr(1);
return k.startsWith(component) || k.startsWith(`/${component}`);
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules(matchKey);
}
if (matchKeys?.length > 1) {
warn(
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure'
);
return;
}
}
// Turn background objects into routing objects
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
LayoutMap.set('LAYOUT', LAYOUT);
......
......@@ -9,18 +9,18 @@ import router from '/@/router';
import { PermissionModeEnum } from '/@/enums/appEnum';
import { pathToRegexp } from 'path-to-regexp';
import modules from 'globby!/@/router/menus/modules/**/*.@(ts)';
const reg = /(((https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
import modules from 'glob:./modules/**/*.ts';
const menuModules: MenuModule[] = [];
Object.keys(modules).forEach((key) => {
const moduleItem = modules[key];
const menuModule = Array.isArray(moduleItem) ? [...moduleItem] : [moduleItem];
menuModules.push(...menuModule);
const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
menuModules.push(...modList);
});
const reg = /(((https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
// ===========================
// ==========Helper===========
// ===========================
......
......@@ -2,17 +2,18 @@ import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types';
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '../constant';
import modules from 'globby!/@/router/routes/modules/**/*.@(ts)';
import { mainOutRoutes } from './mainOut';
import { PageEnum } from '/@/enums/pageEnum';
import { t } from '/@/hooks/web/useI18n';
import modules from 'glob:./modules/**/*.ts';
const routeModuleList: AppRouteModule[] = [];
Object.keys(modules).forEach((key) => {
const mod = Array.isArray(modules[key]) ? [...modules[key]] : [modules[key]];
routeModuleList.push(...mod);
const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
routeModuleList.push(...modList);
});
export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList];
......
......@@ -172,51 +172,51 @@ const feat: AppRouteModule = {
title: t('routes.demo.feat.errorLog'),
},
},
{
path: 'excel',
name: 'Excel',
redirect: '/feat/excel/customExport',
component: getParentLayout('Excel'),
meta: {
// icon: 'mdi:microsoft-excel',
title: t('routes.demo.excel.excel'),
},
// {
// path: 'excel',
// name: 'Excel',
// redirect: '/feat/excel/customExport',
// component: getParentLayout('Excel'),
// meta: {
// // icon: 'mdi:microsoft-excel',
// title: t('routes.demo.excel.excel'),
// },
children: [
{
path: 'customExport',
name: 'CustomExport',
component: () => import('/@/views/demo/excel/CustomExport.vue'),
meta: {
title: t('routes.demo.excel.customExport'),
},
},
{
path: 'jsonExport',
name: 'JsonExport',
component: () => import('/@/views/demo/excel/JsonExport.vue'),
meta: {
title: t('routes.demo.excel.jsonExport'),
},
},
{
path: 'arrayExport',
name: 'ArrayExport',
component: () => import('/@/views/demo/excel/ArrayExport.vue'),
meta: {
title: t('routes.demo.excel.arrayExport'),
},
},
{
path: 'importExcel',
name: 'ImportExcel',
component: () => import('/@/views/demo/excel/ImportExcel.vue'),
meta: {
title: t('routes.demo.excel.importExcel'),
},
},
],
},
// children: [
// {
// path: 'customExport',
// name: 'CustomExport',
// component: () => import('/@/views/demo/excel/CustomExport.vue'),
// meta: {
// title: t('routes.demo.excel.customExport'),
// },
// },
// {
// path: 'jsonExport',
// name: 'JsonExport',
// component: () => import('/@/views/demo/excel/JsonExport.vue'),
// meta: {
// title: t('routes.demo.excel.jsonExport'),
// },
// },
// {
// path: 'arrayExport',
// name: 'ArrayExport',
// component: () => import('/@/views/demo/excel/ArrayExport.vue'),
// meta: {
// title: t('routes.demo.excel.arrayExport'),
// },
// },
// {
// path: 'importExcel',
// name: 'ImportExcel',
// component: () => import('/@/views/demo/excel/ImportExcel.vue'),
// meta: {
// title: t('routes.demo.excel.importExcel'),
// },
// },
// ],
// },
{
path: 'testTab/:id',
name: 'TestTab',
......
......@@ -172,7 +172,7 @@ const setting: ProjectConfig = {
// Whether to cancel the http request that has been sent but not responded when switching the interface.
// If it is enabled, I want to overwrite a single interface. Can be set in a separate interface
removeAllHttpPending: true,
removeAllHttpPending: false,
};
export default setting;
......@@ -3,7 +3,6 @@
*/
import type { ProjectConfig } from '/@/types/config';
import type { App } from 'vue';
import { computed, ref } from 'vue';
import { ThemeModeEnum } from '/@/enums/appEnum';
......@@ -21,17 +20,6 @@ import {
import { appStore } from '/@/store/modules/app';
import { deepMerge } from '/@/utils';
// Used to share global app instances
let app: App;
export function setApp(_app: App): void {
app = _app;
}
export function getApp(): App {
return app;
}
// TODO Theme switching
export function useThemeMode(mode: ThemeModeEnum) {
const modeRef = ref(mode);
......
......@@ -23,81 +23,3 @@ declare namespace NodeJS {
}
declare let process: NodeJS.Process;
declare module '*.bmp' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
const src: string;
export default src;
}
declare module '*.json' {
const content: any | any[];
export default content;
}
declare module '*.scss' {
const content: {
readonly [className: string]: string;
};
export default content;
}
declare module '*.less' {
const content: {
readonly [className: string]: string;
};
export default content;
}
declare module '*.styl' {
const content: {
readonly [className: string]: string;
};
export default content;
}
declare module '*.css' {
const content: any;
export default content;
}
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}
......@@ -4,5 +4,6 @@ declare global {
declare interface Window {
// Global vue app instance
__APP__: App<Element>;
__VERSION__: string;
}
}
......@@ -41,8 +41,6 @@
});
</script>
<style lang="less">
@import (reference) '../../../../design/index.less';
.grow-card {
display: flex;
width: calc(100% - 12px);
......
......@@ -68,8 +68,6 @@
});
</script>
<style lang="less" scoped>
@import (reference) '../../../design/index.less';
.analysis {
width: 100%;
......
@import (reference) '../../../design/index.less';
.house-wrap {
position: relative;
width: 600px;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论