# 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 ``` --- ### 4. 使用 Tailwind CSS #### 布局 ```html
左侧内容
右侧内容
列 1
列 2
列 3
``` #### 间距 ```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+