// 伺服驱动选型工具 - 核心计算逻辑
class ServoSizer {
constructor() {
this.currentType = 'ballscrew';
this.chart = null;
// 确保在 DOM 完全加载后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.init();
});
} else {
this.init();
}
}
init() {
// 检查必要的 DOM 元素是否存在
const requiredElements = [
'transmissionType', 'dynamicParameters', 'calculateBtn',
'resetBtn', 'exportBtn', 'resultsDisplay'
];
for (const elementId of requiredElements) {
if (!document.getElementById(elementId)) {
console.error(`Required element #${elementId} not found`);
return;
}
}
this.bindEvents();
this.updateParametersForm();
this.loadDefaults();
}
bindEvents() {
// 传动类型切换
const transmissionSelect = document.getElementById('transmissionType');
if (transmissionSelect) {
transmissionSelect.addEventListener('change', (e) => {
this.currentType = e.target.value;
this.updateParametersForm();
this.clearResults();
});
}
// 计算按钮
const calculateBtn = document.getElementById('calculateBtn');
if (calculateBtn) {
calculateBtn.addEventListener('click', () => {
this.calculate();
});
}
// 重置按钮
const resetBtn = document.getElementById('resetBtn');
if (resetBtn) {
resetBtn.addEventListener('click', () => {
this.resetForm();
});
}
// 导出按钮
const exportBtn = document.getElementById('exportBtn');
if (exportBtn) {
exportBtn.addEventListener('click', () => {
this.exportResults();
});
}
// 保存配置按钮
const saveConfigBtn = document.getElementById('saveConfigBtn');
if (saveConfigBtn) {
saveConfigBtn.addEventListener('click', () => {
this.saveConfiguration();
});
}
// 加载配置按钮
const loadConfigBtn = document.getElementById('loadConfigBtn');
if (loadConfigBtn) {
loadConfigBtn.addEventListener('click', () => {
this.loadConfiguration();
});
}
}
validateInput(input) {
const value = parseFloat(input.value);
const min = parseFloat(input.min) || -Infinity;
const max = parseFloat(input.max) || Infinity;
const step = parseFloat(input.step) || 1;
// 检查是否为空值
if (input.value.trim() === '') {
input.classList.add('error');
this.showValidationMessage(input, '此字段为必填项');
return false;
}
// 检查数值有效性
if (isNaN(value)) {
input.classList.add('error');
this.showValidationMessage(input, '请输入有效数字');
return false;
}
// 检查范围
if (value < min || value > max) {
input.classList.add('error');
this.showValidationMessage(input, `值应在 ${min} 到 ${max} 之间`);
return false;
}
// 检查步长(可选)
if (step > 0 && Math.abs((value - min) % step) > 1e-10) {
// 对于小数步长,允许一定的精度误差
const roundedValue = Math.round(value / step) * step;
if (Math.abs(value - roundedValue) > 1e-10) {
input.classList.add('error');
this.showValidationMessage(input, `值应为 ${step} 的倍数`);
return false;
}
}
input.classList.remove('error');
this.hideValidationMessage(input);
return true;
}
showValidationMessage(input, message) {
// 移除现有的验证消息
this.hideValidationMessage(input);
// 创建验证消息元素
const messageEl = document.createElement('div');
messageEl.className = 'validation-message';
messageEl.textContent = message;
messageEl.style.cssText = `
color: #e74c3c;
font-size: 12px;
margin-top: 5px;
display: block;
`;
// 插入到输入框后面
input.parentNode.insertBefore(messageEl, input.nextSibling);
}
hideValidationMessage(input) {
const existingMessage = input.parentNode.querySelector('.validation-message');
if (existingMessage) {
existingMessage.remove();
}
}
loadDefaults() {
// 默认值已经在HTML中设置
}
// 获取特定传动类型的参数表单
getParameterForm(type) {
const forms = {
ballscrew: `
丝杠参数
`,
timingBelt: `
同步带参数
`,
gearbox: `
减速机参数
`,
rotaryTable: `
转盘参数
`,
winder: `
收放卷参数
`,
directDrive: `
直接驱动参数
`,
combined: `
组合传动参数
`
};
return forms[type] || forms.ballscrew;
}
updateParametersForm() {
const dynamicParams = document.getElementById('dynamicParameters');
if (dynamicParams) {
dynamicParams.innerHTML = this.getParameterForm(this.currentType);
// 绑定输入验证
document.querySelectorAll('#dynamicParameters input[type="number"]').forEach(input => {
input.addEventListener('input', () => {
this.validateInput(input);
});
});
}
}
// 安全的数值计算函数
safeNumber(value, defaultValue = 0) {
const num = parseFloat(value);
return isNaN(num) || !isFinite(num) ? defaultValue : num;
}
// 丝杠计算
calculateBallScrew(params) {
const {
loadMass, screwDiameter, screwLead, frictionCoeff, efficiency,
maxSpeed, accelerationTime, travelDistance
} = params;
// 转换为标准单位
const lead = this.safeNumber(screwLead, 5) / 1000; // mm to m
const diameter = this.safeNumber(screwDiameter, 20) / 1000; // mm to m
const travel = this.safeNumber(travelDistance, 500) / 1000; // mm to m
const mass = this.safeNumber(loadMass, 10);
const speed = this.safeNumber(maxSpeed, 100);
const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001); // 避免除零
const friction = this.safeNumber(frictionCoeff, 0.01);
const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); // 避免除零
// 1. 负载转矩计算
const gravity = 9.81;
const frictionForce = mass * gravity * friction;
const frictionTorque = (frictionForce * diameter) / 2;
// 推力转矩
const thrustTorque = (mass * gravity * lead) / (2 * Math.PI);
const totalTorque = (thrustTorque + frictionTorque) / eff;
// 2. 转速计算
const maxRpm = (speed * 60) / (lead * 1000); // mm/s to rpm
// 3. 惯量计算
// 丝杠惯量 (实心圆柱体)
const screwInertia = (Math.PI * 7800 * Math.pow(diameter, 4) * travel) / 32;
// 负载折算到电机轴的惯量
const loadInertia = mass * Math.pow(lead / (2 * Math.PI), 2);
const totalInertia = Math.max(screwInertia + loadInertia, 1e-6); // 避免零惯量
// 4. 加速度转矩
const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
const accelTorque = totalInertia * angularAccel;
// 5. 峰值转矩
const peakTorque = totalTorque + accelTorque;
// 6. 功率计算
const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
return {
speedRange: { min: 0, max: Math.max(maxRpm, 0) },
torque: { continuous: Math.max(totalTorque, 0), peak: Math.max(peakTorque, 0) },
inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 },
power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
};
}
// 同步带计算
calculateTimingBelt(params) {
const {
loadMass, pulleyDiameter, frictionCoeff, efficiency,
maxSpeed, accelerationTime
} = params;
const radius = this.safeNumber(pulleyDiameter, 50) / 2000; // mm to m
const mass = this.safeNumber(loadMass, 5);
const speed = this.safeNumber(maxSpeed, 200);
const accelTime = Math.max(this.safeNumber(accelerationTime, 0.3), 0.001);
const friction = this.safeNumber(frictionCoeff, 0.01);
const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
// 负载转矩
const gravity = 9.81;
const frictionForce = mass * gravity * friction;
const loadTorque = (mass * gravity * radius + frictionForce * radius) / eff;
// 转速
const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(pulleyDiameter, 50));
// 惯量计算 - 更精确的模型
// 滑轮惯量 (实心圆柱) - 假设滑轮材料为铝(密度2700 kg/m³),厚度5mm
const pulleyThickness = 0.005; // 5mm
const pulleyDensity = 2700; // 铝的密度 kg/m³
const pulleyVolume = Math.PI * Math.pow(radius, 2) * pulleyThickness;
const pulleyMass = pulleyDensity * pulleyVolume;
const pulleyInertia = 0.5 * pulleyMass * Math.pow(radius, 2);
// 负载惯量
const loadInertia = mass * Math.pow(radius, 2);
const totalInertia = Math.max(pulleyInertia + loadInertia, 1e-6);
// 加速度转矩
const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
const accelTorque = totalInertia * angularAccel;
const peakTorque = loadTorque + accelTorque;
const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
return {
speedRange: { min: 0, max: Math.max(maxRpm, 0) },
torque: { continuous: Math.max(loadTorque, 0), peak: Math.max(peakTorque, 0) },
inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 },
power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
};
}
// 减速机计算
calculateGearbox(params) {
const {
loadInertia, loadTorque, gearRatio, efficiency,
maxSpeed, accelerationTime
} = params;
const inertia = this.safeNumber(loadInertia, 0.01);
const torque = this.safeNumber(loadTorque, 5);
const ratio = Math.max(this.safeNumber(gearRatio, 10), 0.01);
const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
const speed = this.safeNumber(maxSpeed, 100);
const accelTime = Math.max(this.safeNumber(accelerationTime, 0.2), 0.001);
// 折算到电机侧
const reflectedInertia = inertia / Math.pow(ratio, 2);
const reflectedTorque = torque / (ratio * eff);
// 电机转速
const motorRpm = speed * ratio;
// 加速度转矩
const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime;
const accelTorque = Math.max(reflectedInertia, 1e-6) * angularAccel;
const peakTorque = reflectedTorque + accelTorque;
const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60;
return {
speedRange: { min: 0, max: Math.max(motorRpm, 0) },
torque: { continuous: Math.max(reflectedTorque, 0), peak: Math.max(peakTorque, 0) },
inertia: { motor: 0.001, load: Math.max(reflectedInertia, 1e-6), ratio: Math.max(reflectedInertia, 1e-6) / 0.001 },
power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
};
}
// 转盘计算
calculateRotaryTable(params) {
const {
tableMass, tableRadius, frictionCoeff, efficiency,
maxSpeed, accelerationTime
} = params;
const mass = this.safeNumber(tableMass, 20);
const radius = this.safeNumber(tableRadius, 150) / 1000; // mm to m
const friction = this.safeNumber(frictionCoeff, 0.01);
const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
const speed = this.safeNumber(maxSpeed, 60);
const accelTime = Math.max(this.safeNumber(accelerationTime, 1.0), 0.001);
// 转盘惯量 (实心圆盘)
const tableInertia = 0.5 * mass * Math.pow(radius, 2);
// 摩擦转矩
const gravity = 9.81;
const frictionTorque = mass * gravity * friction * radius;
// 转速
const maxRpm = speed;
// 加速度转矩
const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
const accelTorque = Math.max(tableInertia, 1e-6) * angularAccel;
const peakTorque = (frictionTorque + accelTorque) / eff;
const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
return {
speedRange: { min: 0, max: Math.max(maxRpm, 0) },
torque: { continuous: Math.max(frictionTorque / eff, 0), peak: Math.max(peakTorque, 0) },
inertia: { motor: 0.001, load: Math.max(tableInertia, 1e-6), ratio: Math.max(tableInertia, 1e-6) / 0.001 },
power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
};
}
// 收放卷计算
calculateWinder(params) {
const {
materialDensity, materialWidth, initialDiameter, finalDiameter,
lineSpeed, tension, accelerationTime
} = params;
const density = this.safeNumber(materialDensity, 7800);
const width = this.safeNumber(materialWidth, 100) / 1000;
const initialRadius = this.safeNumber(initialDiameter, 50) / 2000; // mm to m
const finalRadius = this.safeNumber(finalDiameter, 200) / 2000;
const speed = this.safeNumber(lineSpeed, 100);
const tensionVal = this.safeNumber(tension, 50);
const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001);
// 卷筒惯量 (空心圆柱)
const finalMass = density * Math.PI * (Math.pow(finalRadius, 2) - Math.pow(initialRadius, 2)) * width;
const finalInertia = 0.5 * Math.max(finalMass, 0.001) * (Math.pow(finalRadius, 2) + Math.pow(initialRadius, 2));
// 张力转矩
const tensionTorque = tensionVal * finalRadius;
// 转速范围
const minRpm = (speed * 60) / (Math.PI * this.safeNumber(finalDiameter, 200));
const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(initialDiameter, 50));
// 加速度转矩 (使用最大惯量)
const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
const accelTorque = Math.max(finalInertia, 1e-6) * angularAccel;
const peakTorque = tensionTorque + accelTorque;
const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
return {
speedRange: { min: Math.max(minRpm, 0), max: Math.max(maxRpm, 0) },
torque: { continuous: Math.max(tensionTorque, 0), peak: Math.max(peakTorque, 0) },
inertia: { motor: 0.001, load: Math.max(finalInertia, 1e-6), ratio: Math.max(finalInertia, 1e-6) / 0.001 },
power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
};
}
// 直接驱动计算
calculateDirectDrive(params) {
const {
loadInertia, frictionTorque, maxSpeed, accelerationTime
} = params;
const inertia = this.safeNumber(loadInertia, 0.005);
const friction = this.safeNumber(frictionTorque, 0.1);
const speed = this.safeNumber(maxSpeed, 300);
const accelTime = Math.max(this.safeNumber(accelerationTime, 0.1), 0.001);
// 直接驱动,无传动比
const motorRpm = speed;
// 加速度转矩
const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime;
const accelTorque = Math.max(inertia, 1e-6) * angularAccel;
const peakTorque = friction + accelTorque;
const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60;
return {
speedRange: { min: 0, max: Math.max(motorRpm, 0) },
torque: { continuous: Math.max(friction, 0), peak: Math.max(peakTorque, 0) },
inertia: { motor: 0.001, load: Math.max(inertia, 1e-6), ratio: Math.max(inertia, 1e-6) / 0.001 },
power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
};
}
// 组合传动计算(简化版)
calculateCombined(params) {
const {
combinedLoadMass, maxSpeed, accelerationTime, frictionCoeff, efficiency
} = params;
const mass = this.safeNumber(combinedLoadMass, 15);
const speed = this.safeNumber(maxSpeed, 80);
const accelTime = Math.max(this.safeNumber(accelerationTime, 0.4), 0.001);
const friction = this.safeNumber(frictionCoeff, 0.01);
const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
const gravity = 9.81;
const loadTorque = mass * gravity * 0.1; // 简化假设
const maxRpm = speed;
const loadInertia = mass * 0.01; // 简化假设
const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
const accelTorque = Math.max(loadInertia, 1e-6) * angularAccel;
const continuousTorque = loadTorque / eff;
const peakTorque = (loadTorque + accelTorque) / eff;
const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
return {
speedRange: { min: 0, max: Math.max(maxRpm, 0) },
torque: { continuous: Math.max(continuousTorque, 0), peak: Math.max(peakTorque, 0) },
inertia: { motor: 0.001, load: Math.max(loadInertia, 1e-6), ratio: Math.max(loadInertia, 1e-6) / 0.001 },
power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
};
}
getFormValues() {
const values = {};
// 获取所有输入值
document.querySelectorAll('#dynamicParameters input, #dynamicParameters select').forEach(input => {
const id = input.id;
if (input.type === 'number') {
values[id] = this.safeNumber(input.value, 0);
} else {
values[id] = input.value;
}
});
// 获取通用参数
const safetyFactorEl = document.getElementById('safetyFactor');
const inertiaRatioEl = document.getElementById('inertiaRatio');
if (safetyFactorEl) values.safetyFactor = this.safeNumber(safetyFactorEl.value, 1.5);
if (inertiaRatioEl) values.inertiaRatio = this.safeNumber(inertiaRatioEl.value, 10);
return values;
}
calculate() {
const params = this.getFormValues();
// 验证所有输入
let isValid = true;
document.querySelectorAll('input[type="number"]').forEach(input => {
if (!this.validateInput(input)) {
isValid = false;
}
});
if (!isValid) {
this.showAlert('请检查输入参数是否有效', 'error');
return;
}
let results;
try {
switch (this.currentType) {
case 'ballscrew':
results = this.calculateBallScrew(params);
break;
case 'timingBelt':
results = this.calculateTimingBelt(params);
break;
case 'gearbox':
results = this.calculateGearbox(params);
break;
case 'rotaryTable':
results = this.calculateRotaryTable(params);
break;
case 'winder':
results = this.calculateWinder(params);
break;
case 'directDrive':
results = this.calculateDirectDrive(params);
break;
case 'combined':
results = this.calculateCombined(params);
break;
default:
throw new Error('未知的传动类型');
}
// 应用安全系数
results.torque.continuous *= params.safetyFactor;
results.torque.peak *= params.safetyFactor;
results.power.rated *= params.safetyFactor;
// 检查惯量比警告
const inertiaWarning = results.inertia.ratio > params.inertiaRatio;
this.displayResults(results, inertiaWarning);
this.updateChart(results);
this.showAlert('计算完成!', 'success');
} catch (error) {
console.error('计算错误:', error);
this.showAlert('计算出错: ' + error.message, 'error');
}
}
displayResults(results, inertiaWarning) {
const resultsDiv = document.getElementById('resultsDisplay');
const warningDiv = document.getElementById('warningMessage');
const errorDiv = document.getElementById('errorMessage');
if (!resultsDiv) return;
// 隐藏错误消息
if (errorDiv) errorDiv.classList.add('hidden');
resultsDiv.innerHTML = `
转速范围 (RPM)
${results.speedRange.min.toFixed(1)} - ${results.speedRange.max.toFixed(1)}
连续转矩 (N·m)
${results.torque.continuous.toFixed(3)}
峰值转矩 (N·m)
${results.torque.peak.toFixed(3)}
负载惯量 (kg·m²)
${results.inertia.load.toExponential(3)}
惯量比
${results.inertia.ratio.toFixed(1)}
额定功率 (W)
${results.power.rated.toFixed(1)}
`;
// 显示惯量比警告
if (warningDiv) {
if (inertiaWarning) {
warningDiv.textContent = `⚠️ 警告:惯量比 (${results.inertia.ratio.toFixed(1)}) 超过允许值,建议选择更大惯量的电机或添加减速机。`;
warningDiv.classList.remove('hidden');
} else {
warningDiv.classList.add('hidden');
}
}
const resultsCard = document.getElementById('resultsCard');
if (resultsCard) resultsCard.style.display = 'block';
}
updateChart(results) {
const chartCanvas = document.getElementById('performanceChart');
if (!chartCanvas) return;
// 检查 Chart.js 是否可用
if (typeof Chart === 'undefined') {
console.warn('Chart.js not loaded, skipping chart rendering');
return;
}
const ctx = chartCanvas.getContext('2d');
// 销毁现有图表
if (this.chart) {
this.chart.destroy();
}
// 创建新图表
this.chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['连续转矩', '峰值转矩', '额定功率'],
datasets: [{
label: '性能指标',
data: [
results.torque.continuous,
results.torque.peak,
results.power.rated
],
backgroundColor: [
'rgba(54, 162, 235, 0.8)',
'rgba(255, 99, 132, 0.8)',
'rgba(75, 192, 192, 0.8)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(255, 99, 132, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '数值'
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += context.parsed.y.toFixed(3);
if (context.dataIndex === 2) {
label += ' W';
} else {
label += ' N·m';
}
}
return label;
}
}
}
}
}
});
}
clearResults() {
const resultsDiv = document.getElementById('resultsDisplay');
const warningDiv = document.getElementById('warningMessage');
const errorDiv = document.getElementById('errorMessage');
if (resultsDiv) {
resultsDiv.innerHTML = '请输入参数并点击计算获取结果
';
}
if (warningDiv) warningDiv.classList.add('hidden');
if (errorDiv) errorDiv.classList.add('hidden');
// 销毁图表
if (this.chart) {
this.chart.destroy();
this.chart = null;
}
}
resetForm() {
// 重置所有输入框
document.querySelectorAll('input[type="number"]').forEach(input => {
input.value = input.defaultValue || '';
input.classList.remove('error');
});
// 重置选择框
const transmissionSelect = document.getElementById('transmissionType');
if (transmissionSelect) {
transmissionSelect.selectedIndex = 0;
this.currentType = 'ballscrew';
this.updateParametersForm();
}
this.clearResults();
this.showAlert('表单已重置', 'info');
}
exportResults() {
const resultsDiv = document.getElementById('resultsDisplay');
if (!resultsDiv || resultsDiv.querySelector('.placeholder')) {
this.showAlert('请先进行计算', 'warning');
return;
}
const resultsText = resultsDiv.innerText;
const blob = new Blob([resultsText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `servo_sizing_results_${new Date().toISOString().slice(0, 10)}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.showAlert('结果已导出', 'success');
}
showAlert(message, type = 'info') {
const alertContainer = document.getElementById('alertContainer');
if (!alertContainer) return;
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type}`;
alertDiv.textContent = message;
alertContainer.appendChild(alertDiv);
setTimeout(() => {
alertDiv.remove();
}, 3000);
}
}
// 主题切换功能
function toggleTheme() {
const body = document.body;
const themeToggle = document.getElementById('themeToggle');
if (body.classList.contains('dark-mode')) {
body.classList.remove('dark-mode');
localStorage.setItem('theme', 'light');
themeToggle.innerHTML = '🌙';
} else {
body.classList.add('dark-mode');
localStorage.setItem('theme', 'dark');
themeToggle.innerHTML = '☀️';
}
}
// 初始化主题
function initTheme() {
const savedTheme = localStorage.getItem('theme') || 'light';
const body = document.body;
const themeToggle = document.getElementById('themeToggle');
if (savedTheme === 'dark') {
body.classList.add('dark-mode');
if (themeToggle) themeToggle.innerHTML = '☀️';
} else {
body.classList.remove('dark-mode');
if (themeToggle) themeToggle.innerHTML = '🌙';
}
}
// 初始化应用 - 确保在 DOM 加载完成后执行
document.addEventListener('DOMContentLoaded', () => {
new ServoSizer();
initTheme();
// 绑定主题切换按钮事件
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
themeToggle.addEventListener('click', toggleTheme);
}
});