背景
在开发面向多学校的Vue项目时,每个学校都需要独立的配置(名称、Logo、背景图、API地址等)。传统的多环境配置方案会产生大量脚本命令,维护成本较高。为此,设计了一套更简洁的单一入口 方案,通过交互式选择 实现配置的动态切换。
核心思想
使用一个统一的入口文件,在运行或构建前动态生成配置。用户只需选择目标学校,系统会自动完成所有配置更新并执行相应命令。相比传统的多脚本方案,它更加简洁、直观,特别适合需要频繁切换不同客户配置的项目。
整体架构

详细实现
1. package.json
javascript
{
"scripts": {
"dev": "node src/config/index.js dev",
"build": "node src/config/index.js build"
}
}
2. 环境变量文件配置 (.env.*)
- .env.development 和 .env.production 保持变量占位符:
bash
// .env.development
# 开发环境配置
ENV = 'development'
// .env.production
# 生产环境配置
ENV = 'production'
VUE_APP_BASE_TARGET = ''
VUE_APP_BASE_API = ''
VUE_APP_CLIENT_NAME = ''
VUE_APP_CLIENT_LOGO = ''
VUE_APP_CLIENT_BACKGROUND = ''
设计思路:
- 使用空值模板,运行时动态填充
- 保持文件结构一致性
- 避免硬编码,提高灵活性
3. 学校配置数据 (config/js/schools.js)
javascript
// 学校配置选项
const schools = [
{
name: 'XX学校', // 显示名称
key: 'school1', // 项目标识符
logo: '@/assets/logo.png', // 学校Logo路径
background: '@/assets/school1-bg.png', // 背景图
https: [ // 环境配置数组
{
key: 'development', // 开发环境
target: 'http://xxx:8080', // 代理目标
base: '/dev-api' // API基础路径
},
{
key: 'production', // 生产环境
target: 'http://xxx:8888',
base: '/pro-api'
}
]
},
// 可扩展更多学校配置
{
name: 'XX学校', // 显示名称
key: 'school2', // 项目标识符
logo: '@/assets/logo.png', // 学校Logo路径
background: '@/assets/school2-bg.png', // 背景图
https: [ // 环境配置数组
{
key: 'development', // 开发环境
target: 'http://xxx:8080', // 代理目标
base: '/dev-api' // API基础路径
},
{
key: 'production', // 生产环境
target: 'http://xxx:8888',
base: '/pro-api'
}
]
}
]
module.exports = schools
配置特点:
- 统一管理所有学校配置
- 支持不同环境的API配置
- 清晰的JSON结构,易于扩展
4. 配置管理工具 (config/js/utils.js)
javascript
const readline = require('readline');
const {execSync} = require('child_process');
const fs = require('fs');
const path = require('path');
class ConfigManager {
constructor(mode) {
this.mode = mode;
this.validateMode();
this.config = {
dev: {
envFile: '.env.development',
env: 'development',
command: 'vue-cli-service serve',
message: '正在启动项目...'
},
build: {
envFile: '.env.production',
env: 'production',
command: 'vue-cli-service build',
message: '正在构建项目...'
}
}[this.mode];
}
// 显示选择菜单
showWelcomeMessage(schools) {
console.log('========================================');
console.log(` 请选择要使用的学校配置 (${this.mode}模式) `);
console.log();
schools.forEach((school, index) => {
console.log(`${index + 1}. ${school.name}`);
});
console.log('========================================');
console.log();
}
// 获取用户输入
async getUserInput(schools) {
return new Promise((resolve, reject) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('请输入对应数字选择配置: ', (answer) => {
const choice = parseInt(answer);
if (isNaN(choice) || choice < 1 || choice > schools.length) {
rl.close();
reject(new Error('请输入有效的数字选项!'));
return;
}
const selectedSchool = schools[choice - 1];
console.log(`\n\x1b[32m已选择:${selectedSchool.name}\x1b[0m`);
rl.close();
resolve(selectedSchool);
});
});
}
// 更新package.json
updatePackageJson(selectedSchool) {
try {
const packageJsonPath = path.join(__dirname, '../../../package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
packageJson.name = selectedSchool.key;
packageJson.description = selectedSchool.name;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
console.log('\x1b[32m已更新 package.json 配置\x1b[0m');
} catch (error) {
throw new Error(`更新 package.json 失败:${error.message}`);
}
}
// 更新环境变量文件
updateEnvFile(selectedSchool) {
try {
const envPath = path.join(__dirname, `../../../${this.config.envFile}`);
let envContent = fs.readFileSync(envPath, 'utf8');
// 根据当前模式选择对应的API配置
const envConfig = selectedSchool.https.find(config =>
config.key === this.config.env
) || selectedSchool.https[0];
const updates = {
'VUE_APP_CLIENT_NAME': selectedSchool.name,
'VUE_APP_CLIENT_LOGO': selectedSchool.logo,
'VUE_APP_CLIENT_BACKGROUND': selectedSchool.background,
'VUE_APP_BASE_TARGET': envConfig.target,
'VUE_APP_BASE_API': envConfig.base
};
// 动态替换环境变量
Object.entries(updates).forEach(([key, value]) => {
const regex = new RegExp(`^${key} = '.*'`, 'gm');
envContent = envContent.replace(regex, `${key} = '${value}'`);
});
fs.writeFileSync(envPath, envContent);
console.log(`\x1b[32m已更新 ${this.config.envFile} 配置\x1b[0m\n`);
} catch (error) {
throw new Error(`更新 ${this.config.envFile} 失败:${error.message}`);
}
}
// 执行Vue命令
executeCommand() {
console.log(`${this.config.message}\n`);
try {
execSync(this.config.command, {stdio: 'inherit'});
} catch (error) {
throw new Error(`执行失败:${error.message}`);
}
}
}
// 辅助工具类
class Logger {
static success(message) {
console.log(`\x1b[32m✓ ${message}\x1b[0m`);
}
static error(message) {
console.log(`\x1b[31m✗ ${message}\x1b[0m`);
}
}
class Validator {
static validateArgs(args) {
if (args.length < 3) {
Logger.error('请指定运行模式');
Logger.info('用法: node common.js [dev|build]');
process.exit(1);
}
}
}
module.exports = {
ConfigManager,
Logger,
Validator
};
5. 统一入口文件 (config/index.js)
javascript
const {ConfigManager, Logger, Validator} = require('./js/utils');
const schools = require('./js/schools');
class Application {
constructor() {
this.mode = process.argv[2];
Validator.validateArgs(process.argv);
this.configManager = new ConfigManager(this.mode);
}
async run() {
try {
// 1. 显示欢迎信息
this.configManager.showWelcomeMessage(schools);
// 2. 获取用户选择的学校
const selectedSchool = await this.configManager.getUserInput(schools);
// 3. 更新配置文件
this.configManager.updatePackageJson(selectedSchool);
this.configManager.updateEnvFile(selectedSchool);
// 4. 执行命令
this.configManager.executeCommand();
} catch (error) {
Logger.error(error.message);
process.exit(1);
}
}
}
// 启动应用
(async () => {
const app = new Application();
await app.run();
})();
}
工作流程

项目中使用配置
javascript
<template>
<div class="school-app">
<!-- 使用动态Logo -->
<img :src="schoolLogo" alt="学校Logo" class="logo" />
<!-- 显示学校名称 -->
<h1>{{ schoolName }}</h1>
<!-- 动态背景 -->
<div class="login-container" :style="backgroundStyle">
<!-- 登录表单 -->
</div>
</div>
</template>
<script>
export default {
computed: {
schoolName() {
return process.env.VUE_APP_CLIENT_NAME;
},
schoolLogo() {
// 处理Webpack别名路径
const logoPath = process.env.VUE_APP_CLIENT_LOGO;
return require(logoPath.replace('@', '..'));
},
backgroundStyle() {
const bgPath = process.env.VUE_APP_CLIENT_BACKGROUND;
return {
backgroundImage: `url(${require(bgPath.replace('@', '..'))})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
};
},
apiBaseUrl() {
return process.env.VUE_APP_BASE_API;
}
},
mounted() {
// 设置页面标题
document.title = this.schoolName;
// 配置axios实例
this.$axios.defaults.baseURL = process.env.VUE_APP_BASE_TARGET;
}
};
</script>
方案优势
- 统一入口:一个入口文件处理所有配置逻辑
- 动态配置:运行时生成配置,无需预定义大量环境文件
- 用户友好:交互式选择,降低使用门槛
- 易于维护:配置集中管理,扩展方便