EMS-vite/vite.config.dev.ts
miaoda 95098991f3 # 技术实现详情
## 架构设计

**前端技术栈**
- React 18 + TypeScript - 提供类型安全的现代化前端开发
- Tailwind CSS + shadcn/ui - 实现美观一致的用户界面
- Recharts - 专业的数据可视化图表库
- React Router - 单页应用路由管理
- React Hook Form - 高效的表单状态管理

**后端数据层**
- Supabase - 现代化的后端即服务平台
- PostgreSQL - 可靠的关系型数据库
- 实时订阅 - 支持数据变更的实时推送

## 数据库设计

创建了完整的数据库架构,包含5个核心表:

1. **devices** - 设备管理表,存储BBox设备信息、状态和配置
2. **battery_data** - 电池数据表,记录电压、电流、温度等实时数据
3. **ota_tasks** - OTA升级任务表,管理固件升级流程和状态
4. **mqtt_logs** - MQTT通信日志表,记录设备通信历史
5. **system_config** - 系统配置表,存储报警阈值和系统参数

## 功能实现

**设备管理模块**
- 实现了完整的CRUD操作,支持设备的创建、查看、编辑和删除
- 设备状态实时监控,包括在线/离线/维护/故障四种状态
- 设备详情页面展示最新电池数据和设备信息
- 智能搜索和状态筛选功能

**实时监控模块**
- 多维度数据图表展示,支持电压、电流、温度、电量趋势分析
- 可配置时间范围查询(1小时到7天)
- 实时数据更新机制,支持自动刷新
- 数据趋势指示器,显示数值变化方向

**OTA管理模块**
- 升级任务创建和管理,支持批量设备升级
- 实时进度跟踪,模拟真实的下载和安装过程
- 详细的升级日志查看,便于故障排查
- 任务状态管理,支持重试和删除操作

**MQTT管理模块**
- 连接状态监控和自动重连功能
- 消息日志实时查看和筛选
- 通信统计分析,包括消息总数和错误率
- 日志导出功能,支持CSV格式

**系统设置模块**
- 分类配置管理,包括常规设置、MQTT配置、报警设置
- 表单验证和错误处理
- 系统信息展示,包括运行状态和存储统计
- 通知设置和权限管理

## 代码质量

- 完整的TypeScript类型定义,确保类型安全
- 模块化的组件设计,便于维护和扩展
- 统一的错误处理和用户反馈机制
- 响应式设计,适配各种屏幕尺寸
- 代码通过ESLint检查,符合最佳实践

## 数据初始化

系统包含丰富的示例数据:
- 5个示例BBox设备,涵盖各种状态
- 历史电池数据,用于图表展示
- OTA升级任务示例,展示不同阶段状态
- MQTT通信日志,模拟真实设备通信
- 完整的系统配置参数

应用已完全实现需求文档中的所有功能,提供了专业级的电池管理解决方案。
2025-11-17 16:52:12 +08:00

197 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { defineConfig, loadConfigFromFile } from "vite";
import type { Plugin, ConfigEnv } from "vite";
import tailwindcss from "tailwindcss";
import autoprefixer from "autoprefixer";
import fs from "fs/promises";
import path from "path";
import {
makeTagger,
injectedGuiListenerPlugin,
injectOnErrorPlugin
} from "miaoda-sc-plugin";
const tailwindConfig = {
plugins: [
function ({ addUtilities }) {
addUtilities(
{
".border-t-solid": { "border-top-style": "solid" },
".border-r-solid": { "border-right-style": "solid" },
".border-b-solid": { "border-bottom-style": "solid" },
".border-l-solid": { "border-left-style": "solid" },
".border-t-dashed": { "border-top-style": "dashed" },
".border-r-dashed": { "border-right-style": "dashed" },
".border-b-dashed": { "border-bottom-style": "dashed" },
".border-l-dashed": { "border-left-style": "dashed" },
".border-t-dotted": { "border-top-style": "dotted" },
".border-r-dotted": { "border-right-style": "dotted" },
".border-b-dotted": { "border-bottom-style": "dotted" },
".border-l-dotted": { "border-left-style": "dotted" },
},
["responsive"]
);
},
],
};
export async function tryLoadConfigFromFile(
filePath: string,
env: ConfigEnv = { command: "serve", mode: "development" }
): Promise<any | null> {
try {
const result = await loadConfigFromFile(env, filePath);
return result ? result.config : null;
} catch (error) {
console.warn(`加载配置文件失败: ${filePath}尝试加载cjs版本`);
console.warn(error);
// 👇 创建 .cjs 临时文件重试
const tempFilePath =
filePath.replace(/\.(js|ts|mjs|mts)$/, "") + `.temp.cjs`;
try {
const originalContent = await fs.readFile(filePath, "utf-8");
// 补充逻辑:如果是 ESM 语法,无法直接 require会失败
if (/^\s*import\s+/m.test(originalContent)) {
console.error(
`配置文件包含 import 语法,无法自动转为 CommonJS: ${filePath}`
);
return null;
}
await fs.writeFile(tempFilePath, originalContent, "utf-8");
const result = await loadConfigFromFile(env, tempFilePath);
return result ? result.config : null;
} catch (innerError) {
console.error(`重试加载临时 .cjs 文件失败: ${tempFilePath}`);
console.error(innerError);
return null;
} finally {
// 🧹 尝试删除临时文件
try {
await fs.unlink(tempFilePath);
} catch (_) {}
}
}
}
const env: ConfigEnv = { command: "serve", mode: "development" };
const configFile = path.resolve(__dirname, "vite.config.ts");
const result = await loadConfigFromFile(env, configFile);
const userConfig = result?.config;
const tailwindConfigFile = path.resolve(__dirname, "tailwind.config.js");
const tailwindResult = await tryLoadConfigFromFile(tailwindConfigFile, env);
const root = path.resolve(__dirname);
export default defineConfig({
...userConfig,
plugins: [
makeTagger(),
injectedGuiListenerPlugin({
path: 'https://resource-static.cdn.bcebos.com/common/v2/injected.js'
}),
injectOnErrorPlugin(),
...(userConfig?.plugins || []),
{
name: 'hmr-toggle',
configureServer(server) {
let hmrEnabled = true;
// 包装原来的 send 方法
const _send = server.ws.send;
server.ws.send = (payload) => {
if (hmrEnabled) {
return _send.call(server.ws, payload);
} else {
console.log('[HMR disabled] skipped payload:', payload.type);
}
};
// 提供接口切换 HMR
server.middlewares.use('/innerapi/v1/sourcecode/__hmr_off', (req, res) => {
hmrEnabled = false;
let body = {
status: 0,
msg: 'HMR disabled'
};
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(body));
});
server.middlewares.use('/innerapi/v1/sourcecode/__hmr_on', (req, res) => {
hmrEnabled = true;
let body = {
status: 0,
msg: 'HMR enabled'
};
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(body));
});
// 注册一个 HTTP API用来手动触发一次整体刷新
server.middlewares.use('/innerapi/v1/sourcecode/__hmr_reload', (req, res) => {
if (hmrEnabled) {
server.ws.send({
type: 'full-reload',
path: '*', // 整页刷新
});
}
res.statusCode = 200;
let body = {
status: 0,
msg: 'Manual full reload triggered'
};
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(body));
});
},
load(id) {
if (id === 'virtual:after-update') {
return `
if (import.meta.hot) {
import.meta.hot.on('vite:afterUpdate', () => {
window.postMessage(
{
type: 'editor-update'
},
'*'
);
});
}
`;
}
},
transformIndexHtml(html) {
return {
html,
tags: [
{
tag: 'script',
attrs: {
type: 'module',
src: '/@id/virtual:after-update'
},
injectTo: 'body'
}
]
};
}
},
],
css: {
postcss: {
plugins: [
tailwindcss({
...(tailwindResult as any),
content: [`${root}/index.html`, `${root}/src/**/*.{js,ts,jsx,tsx}`],
}),
autoprefixer(),
],
},
}
});