# MeshRay 原生前端开发指南
## 🚀 快速开始
### 项目结构
```
web/
├── static/
│ ├── index.html # 主页面(Vue 应用入口)
│ └── js/
│ └── app.js # Vue 应用逻辑
└── embed.go # Go embed 配置
```
---
## 📦 技术栈
- **Vue 3.4** - 渐进式 JavaScript 框架
- **Element Plus** - Vue 3 组件库
- **Tailwind CSS** - 实用优先的 CSS 框架
- **CDN 加载** - 无需本地安装依赖
---
## 🎨 开发示例
### 1. 添加新页面
#### 步骤 1: 在 HTML 中添加页面内容
```html
```
#### 步骤 2: 在 JS 中添加数据和逻辑
```javascript
// app.js
const devices = ref([]);
const showCreateDevice = ref(false);
const loadDevices = async () => {
const response = await fetch(`${API_BASE}/devices`);
const result = await response.json();
devices.value = result.data || [];
};
const editDevice = (device) => {
ElementPlus.ElMessageBox.alert(`编辑设备:${device.name}`);
};
const deleteDevice = async (device) => {
await ElementPlus.ElMessageBox.confirm('确定删除?', '警告', { type: 'warning' });
const response = await fetch(`${API_BASE}/devices/${device.id}`, { method: 'DELETE' });
if (response.ok) {
ElementPlus.ElMessage.success('删除成功');
await loadDevices();
}
};
onMounted(() => {
loadDevices();
});
return { currentPage, devices, showCreateDevice, loadDevices, editDevice, deleteDevice };
```
---
### 2. 调用 API
#### GET 请求
```javascript
const loadStats = async () => {
try {
const response = await fetch('/api/v1/dashboard/stats');
const result = await response.json();
if (response.ok && result.data) {
stats.networkCount = result.data.network_count;
stats.deviceCount = result.data.device_count;
}
} catch (error) {
console.error('加载失败:', error);
ElementPlus.ElMessage.error('加载数据失败');
}
};
```
#### POST 请求
```javascript
const createNetwork = async () => {
try {
const response = await fetch('/api/v1/networks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: newNetwork.name,
subnet_ipv4: newNetwork.subnet_ipv4,
listen_port: newNetwork.listen_port,
mesh_mode: newNetwork.mesh_mode
})
});
const result = await response.json();
if (response.ok) {
ElementPlus.ElMessage.success('创建成功');
showCreateNetworkDialog.value = false;
await loadNetworks();
} else {
ElementPlus.ElMessage.error(result.error || '创建失败');
}
} catch (error) {
ElementPlus.ElMessage.error('创建失败');
}
};
```
#### DELETE 请求
```javascript
const deleteNetwork = async (network) => {
try {
await ElementPlus.ElMessageBox.confirm(
`确定删除 "${network.name}"?`,
'警告',
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' }
);
const response = await fetch(`/api/v1/networks/${network.id}`, { method: 'DELETE' });
if (response.ok) {
ElementPlus.ElMessage.success('删除成功');
await loadNetworks();
} else {
const result = await response.json();
ElementPlus.ElMessage.error(result.error);
}
} catch (error) {
if (error !== 'cancel') {
ElementPlus.ElMessage.error('删除失败');
}
}
};
```
---
### 3. 使用 Element Plus 组件
#### 对话框 (Dialog)
```html
取消
确定
```
#### 消息提示
```javascript
// 成功消息
ElementPlus.ElMessage.success('操作成功');
// 错误消息
ElementPlus.ElMessage.error('操作失败');
// 警告消息
ElementPlus.ElMessage.warning('请注意');
// 确认对话框
await ElementPlus.ElMessageBox.confirm('确定执行?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
// 提示框
ElementPlus.ElMessageBox.alert('这是一段提示信息', '标题', {
confirmButtonText: '确定'
});
```
#### 表格 (Table)
```html
{{ row.status === 'active' ? '运行中' : '已停止' }}
查看
编辑
```
---
### 4. 使用 Tailwind CSS
#### 布局
```html
```
#### 间距
```html
上下左右 1rem
上边距 0.5rem
下边距 1rem
水平居中
上下左右 1rem
左右 1.5rem
上下 0.5rem
```
#### 颜色
```html
蓝色背景白字
浅绿背景深绿字
悬停变红
```
#### 响应式
```html
```
---
## 🔧 常用工具函数
### 格式化日期
```javascript
const formatDate = (dateString) => {
return new Date(dateString).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
// 使用
{{ formatDate(network.created_at) }}
```
### 复制文本
```javascript
const copyToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text);
ElementPlus.ElMessage.success('已复制到剪贴板');
} catch (error) {
ElementPlus.ElMessage.error('复制失败');
}
};
```
### 下载文件
```javascript
const downloadFile = (content, filename, mimeType = 'text/plain') => {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
};
// 使用:下载 WireGuard 配置
downloadFile(configText, 'wg0.conf', 'text/plain');
```
---
## 📋 完整页面模板
```html
页面标题
{{ title }}
```
---
## 🎯 最佳实践
### 1. 代码组织
```javascript
// app.js
const API_BASE = '/api/v1';
// 按功能模块组织代码
const dashboardModule = {
stats: reactive({...}),
loadStats: async () => {...}
};
const networkModule = {
networks: ref([]),
createNetwork: async () => {...},
deleteNetwork: async (network) => {...}
};
// 统一导出
return {
...dashboardModule,
...networkModule
};
```
### 2. 错误处理
```javascript
try {
const response = await fetch(url);
if (!response.ok) throw new Error('网络错误');
const result = await response.json();
// 处理结果
} catch (error) {
console.error('详细错误:', error);
ElementPlus.ElMessage.error('操作失败:' + error.message);
}
```
### 3. Loading 状态
```javascript
const loading = ref(false);
const loadData = async () => {
loading.value = true;
try {
// API 调用
} finally {
loading.value = false;
}
};
// UI 显示
加载数据
```
---
## 🐛 调试技巧
### 控制台日志
```javascript
console.log('当前状态:', stats);
console.error('发生错误:', error);
console.warn('警告信息:', warning);
```
### Vue DevTools
安装 [Vue DevTools](https://devtools.vuejs.org/) 浏览器扩展:
- 查看组件树
- 检查响应式数据
- 调试事件
### Network 面板
浏览器开发者工具 → Network:
- 查看 API 请求
- 检查请求参数
- 分析响应数据
---
## 📞 常见问题
### Q: 如何添加新的 CDN 资源?
A: 在 `` 标签中添加:
```html
```
### Q: 如何使用自定义样式?
A: 在 `
```
### Q: 如何访问后端 API?
A: 使用相对路径,Gin 会自动代理:
```javascript
fetch('/api/v1/networks') // ✅ 正确
fetch('http://localhost:8080/api/v1/networks') // ❌ 错误
```
---
**最后更新**: 2026-03-20
**适用版本**: MeshRay 2.0+