初始化

This commit is contained in:
2026-03-26 20:18:20 +08:00
commit 120a5b4dfd
368 changed files with 35926 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
/* eslint-disable */
import process from 'node:process'
import readline from 'node:readline'
function clearScreen() {
const repeatCount = process.stdout.rows - 2
const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : ''
console.log(blank)
readline.cursorTo(process.stdout, 0, 0)
readline.clearScreenDown(process.stdout)
}
function formatter(open: string, close: string, replace = open) {
return (input: string) => {
const string = `${input}`
const index = string.indexOf(close, open.length)
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close
}
}
function replaceClose(string: string, close: string, replace: string, index: number): string {
const start = string.substring(0, index) + replace
const end = string.substring(index + close.length)
const nextIndex = end.indexOf(close)
return ~nextIndex ? start + replaceClose(end, close, replace, nextIndex) : start + end
}
function createColors() {
return {
bgBlack: formatter('\x1B[40m', '\x1B[49m'),
bgBlue: formatter('\x1B[44m', '\x1B[49m'),
bgCyan: formatter('\x1B[46m', '\x1B[49m'),
bgGreen: formatter('\x1B[42m', '\x1B[49m'),
bgMagenta: formatter('\x1B[45m', '\x1B[49m'),
bgRed: formatter('\x1B[41m', '\x1B[49m', '\x1B[22m\x1B[1m'),
bgWhite: formatter('\x1B[47m', '\x1B[49m'),
bgYellow: formatter('\x1B[43m', '\x1B[49m'),
black: formatter('\x1B[30m', '\x1B[39m'),
blue: formatter('\x1B[34m', '\x1B[39m'),
bold: formatter('\x1B[1m', '\x1B[22m', '\x1B[22m\x1B[1m'),
cyan: formatter('\x1B[36m', '\x1B[39m'),
dim: formatter('\x1B[2m', '\x1B[22m', '\x1B[22m\x1B[2m'),
gray: formatter('\x1B[90m', '\x1B[39m'),
green: formatter('\x1B[32m', '\x1B[39m'),
hidden: formatter('\x1B[8m', '\x1B[28m'),
inverse: formatter('\x1B[7m', '\x1B[27m'),
italic: formatter('\x1B[3m', '\x1B[23m'),
magenta: formatter('\x1B[35m', '\x1B[39m'),
red: formatter('\x1B[31m', '\x1B[39m'),
reset: (s: string) => `\x1B[0m${s}\x1B[0m`,
strikethrough: formatter('\x1B[9m', '\x1B[29m'),
underline: formatter('\x1B[4m', '\x1B[24m'),
white: formatter('\x1B[37m', '\x1B[39m'),
yellow: formatter('\x1B[33m', '\x1B[39m'),
}
}
export { clearScreen, createColors }

2
build/config/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './proxy';
export * from './time';

57
build/config/proxy.ts Normal file
View File

@@ -0,0 +1,57 @@
import type { ProxyOptions } from 'vite';
import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
import { consola } from 'consola';
import { createServiceConfig } from '../../src/utils/service';
/**
* Set http proxy
*
* @param env - The current env
* @param enable - If enable http proxy
*/
export function createViteProxy(env: Env.ImportMeta, enable: boolean) {
const isEnableHttpProxy = enable && env.VITE_HTTP_PROXY === 'Y';
if (!isEnableHttpProxy) return undefined;
const isEnableProxyLog = env.VITE_PROXY_LOG === 'Y';
const { baseURL, proxyPattern, other } = createServiceConfig(env);
const proxy: Record<string, ProxyOptions> = createProxyItem({ baseURL, proxyPattern }, isEnableProxyLog);
other.forEach(item => {
Object.assign(proxy, createProxyItem(item, isEnableProxyLog));
});
return proxy;
}
function createProxyItem(item: App.Service.ServiceConfigItem, enableLog: boolean) {
const proxy: Record<string, ProxyOptions> = {};
proxy[item.proxyPattern] = {
target: item.baseURL,
changeOrigin: true,
configure: (_proxy, options) => {
_proxy.on('proxyReq', (_proxyReq, req, _res) => {
if (!enableLog) return;
const requestUrl = `${lightBlue('[proxy url]')}: ${bgYellow(` ${req.method} `)} ${green(
`${item.proxyPattern}${req.url}`
)}`;
const proxyUrl = `${lightBlue('[real request url]')}: ${green(`${options.target}${req.url}`)}`;
consola.log(`${requestUrl}\n${proxyUrl}`);
});
_proxy.on('error', (_err, req, _res) => {
if (!enableLog) return;
consola.log(bgRed(`Error: ${req.method} `), green(`${options.target}${req.url}`));
});
},
rewrite: path => path.replace(new RegExp(`^${item.proxyPattern}`), '')
};
return proxy;
}

12
build/config/time.ts Normal file
View File

@@ -0,0 +1,12 @@
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
export function getBuildTime() {
dayjs.extend(utc);
dayjs.extend(timezone);
const buildTime = dayjs.tz(Date.now(), 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
return buildTime;
}

13
build/plugins/devtools.ts Normal file
View File

@@ -0,0 +1,13 @@
import VueDevtools from 'vite-plugin-vue-devtools';
export function setupDevtoolsPlugin(viteEnv: Env.ImportMeta) {
const { VITE_DEVTOOLS_ENABLED, VITE_DEVTOOLS_LAUNCH_EDITOR } = viteEnv;
if (VITE_DEVTOOLS_ENABLED !== 'Y') {
return null;
}
return VueDevtools({
launchEditor: VITE_DEVTOOLS_LAUNCH_EDITOR
});
}

13
build/plugins/html.ts Normal file
View File

@@ -0,0 +1,13 @@
import type { Plugin } from 'vite';
export function setupHtmlPlugin(buildTime: string) {
const plugin: Plugin = {
name: 'html-plugin',
apply: 'build',
transformIndexHtml(html) {
return html.replace('<head>', `<head>\n <meta name="buildTime" content="${buildTime}">`);
}
};
return plugin;
}

24
build/plugins/index.ts Normal file
View File

@@ -0,0 +1,24 @@
import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import progress from 'vite-plugin-progress';
import { setupElegantRouter } from './router';
import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin';
import { setupHtmlPlugin } from './html';
import { setupDevtoolsPlugin } from './devtools';
export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
const plugins: PluginOption = [
vue(),
vueJsx(),
setupDevtoolsPlugin(viteEnv),
setupElegantRouter(),
setupUnocss(viteEnv),
...setupUnplugin(viteEnv),
progress(),
setupHtmlPlugin(buildTime)
];
return plugins;
}

79
build/plugins/router.ts Normal file
View File

@@ -0,0 +1,79 @@
import type { RouteMeta } from 'vue-router';
import ElegantVueRouter from '@elegant-router/vue/vite';
import type { RouteKey } from '@elegant-router/types';
export function setupElegantRouter() {
return ElegantVueRouter({
layouts: {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
customRoutes: {
names: ['exception_403', 'exception_404', 'exception_500']
},
routePathTransformer(routeName, routePath) {
const key = routeName as RouteKey;
if (key === 'login') {
const modules: UnionKey.LoginModule[] = ['pwd-login', 'reset-pwd'];
const moduleReg = modules.join('|');
return `/login/:module(${moduleReg})?`;
}
return routePath;
},
onRouteMetaGen(routeName) {
const key = routeName as RouteKey;
const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
const routeMetaMap: Partial<Record<RouteKey, Partial<RouteMeta>>> = {
system: {
icon: 'carbon:cloud-service-management',
order: 9,
roles: ['R_ADMIN']
},
system_menu: {
icon: 'material-symbols:route',
order: 3,
roles: ['R_ADMIN'],
keepAlive: true
},
system_dict: {
icon: 'mdi:book-open-page-variant-outline',
order: 4,
roles: ['R_ADMIN'],
keepAlive: true
},
system_role: {
icon: 'carbon:user-role',
order: 2,
roles: ['R_SUPER']
},
system_user: {
icon: 'ic:round-manage-accounts',
order: 1,
roles: ['R_ADMIN']
},
'system_user-detail': {
hideInMenu: true,
roles: ['R_ADMIN'],
activeMenu: 'system_user'
}
};
const meta: Partial<RouteMeta> = {
title: key,
i18nKey: `route.${key}` as App.I18n.I18nKey,
...routeMetaMap[key]
};
if (constantRoutes.includes(key)) {
meta.constant = true;
}
return meta;
}
});
}

32
build/plugins/unocss.ts Normal file
View File

@@ -0,0 +1,32 @@
import process from 'node:process';
import path from 'node:path';
import unocss from '@unocss/vite';
import presetIcons from '@unocss/preset-icons';
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
export function setupUnocss(viteEnv: Env.ImportMeta) {
const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
/** The name of the local icon collection */
const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
return unocss({
presets: [
presetIcons({
prefix: `${VITE_ICON_PREFIX}-`,
scale: 1,
extraProperties: {
display: 'inline-block'
},
collections: {
[collectionName]: FileSystemIconLoader(localIconPath, svg =>
svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
)
},
warn: true
})
]
});
}

51
build/plugins/unplugin.ts Normal file
View File

@@ -0,0 +1,51 @@
import process from 'node:process';
import path from 'node:path';
import type { PluginOption } from 'vite';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import Icons from 'unplugin-icons/vite';
import IconsResolver from 'unplugin-icons/resolver';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
export function setupUnplugin(viteEnv: Env.ImportMeta) {
const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
/** The name of the local icon collection */
const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
const plugins: PluginOption[] = [
Icons({
compiler: 'vue3',
customCollections: {
[collectionName]: FileSystemIconLoader(localIconPath, svg =>
svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
)
},
scale: 1,
defaultClass: 'inline-block'
}),
Components({
dts: 'src/typings/components.d.ts',
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
resolvers: [
// auto import Element Plus components。 full import to see /src/plugins/ui.ts
ElementPlusResolver({
// no to import style, full import to see /src/plugins/assets.ts
importStyle: false
}),
IconsResolver({ customCollections: [collectionName], componentPrefix: VITE_ICON_PREFIX })
]
}),
createSvgIconsPlugin({
iconDirs: [localIconPath],
symbolId: `${VITE_ICON_LOCAL_PREFIX}-[dir]-[name]`,
inject: 'body-last',
customDomId: '__SVG_ICON_LOCAL__'
})
];
return plugins;
}