# 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
<!-- 在 index.html 的主内容区添加 -->
<div v-if="currentPage === 'devices'">
    <h2 class="text-2xl font-bold mb-6">设备管理</h2>
    
    <div class="card p-6">
        <el-button type="primary" @click="showCreateDevice = true">
            <el-icon><Plus /></el-icon>
            添加设备
        </el-button>
        
        <el-table :data="devices" style="width: 100%; margin-top: 20px;">
            <el-table-column prop="name" label="名称"></el-table-column>
            <el-table-column prop="ip" label="IP 地址"></el-table-column>
            <el-table-column label="操作">
                <template #default="{ row }">
                    <el-button size="small" @click="editDevice(row)">编辑</el-button>
                    <el-button size="small" type="danger" @click="deleteDevice(row)">删除</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</div>
```

#### 步骤 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
<el-dialog v-model="showDialog" title="标题" width="500px">
    <el-form :model="formData" label-width="100px">
        <el-form-item label="名称">
            <el-input v-model="formData.name" placeholder="请输入名称"></el-input>
        </el-form-item>
    </el-form>
    <template #footer>
        <el-button @click="showDialog = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
    </template>
</el-dialog>
```

#### 消息提示

```javascript
// 成功消息
ElementPlus.ElMessage.success('操作成功');

// 错误消息
ElementPlus.ElMessage.error('操作失败');

// 警告消息
ElementPlus.ElMessage.warning('请注意');

// 确认对话框
await ElementPlus.ElMessageBox.confirm('确定执行？', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
});

// 提示框
ElementPlus.ElMessageBox.alert('这是一段提示信息', '标题', {
    confirmButtonText: '确定'
});
```

#### 表格 (Table)

```html
<el-table :data="items" style="width: 100%">
    <el-table-column prop="id" label="ID" width="80"></el-table-column>
    <el-table-column prop="name" label="名称"></el-table-column>
    <el-table-column prop="status" label="状态">
        <template #default="{ row }">
            <el-tag :type="row.status === 'active' ? 'success' : 'danger'">
                {{ row.status === 'active' ? '运行中' : '已停止' }}
            </el-tag>
        </template>
    </el-table-column>
    <el-table-column label="操作" width="200">
        <template #default="{ row }">
            <el-button size="small" @click="view(row)">查看</el-button>
            <el-button size="small" type="primary" @click="edit(row)">编辑</el-button>
        </template>
    </el-table-column>
</el-table>
```

---

### 4. 使用 Tailwind CSS

#### 布局

```html
<!-- Flexbox 布局 -->
<div class="flex items-center justify-between">
    <div>左侧内容</div>
    <div>右侧内容</div>
</div>

<!-- Grid 布局 -->
<div class="grid grid-cols-3 gap-4">
    <div>列 1</div>
    <div>列 2</div>
    <div>列 3</div>
</div>
```

#### 间距

```html
<!-- 外边距 -->
<div class="m-4">上下左右 1rem</div>
<div class="mt-2">上边距 0.5rem</div>
<div class="mb-4">下边距 1rem</div>
<div class="mx-auto">水平居中</div>

<!-- 内边距 -->
<div class="p-4">上下左右 1rem</div>
<div class="px-6">左右 1.5rem</div>
<div class="py-2">上下 0.5rem</div>
```

#### 颜色

```html
<div class="bg-blue-500 text-white">蓝色背景白字</div>
<div class="bg-green-100 text-green-800">浅绿背景深绿字</div>
<div class="hover:bg-red-500">悬停变红</div>
```

#### 响应式

```html
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
    <!-- 手机 1 列，平板 2 列，桌面 4 列 -->
</div>
```

---

## 🔧 常用工具函数

### 格式化日期

```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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>页面标题</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
    <script src="https://unpkg.com/element-plus"></script>
    <script src="https://unpkg.com/@element-plus/icons-vue"></script>
</head>
<body>
    <div id="app">
        <h1 class="text-3xl font-bold">{{ title }}</h1>
        <button class="bg-blue-500 text-white px-4 py-2 rounded" @click="handleClick">
            点击我
        </button>
    </div>
    
    <script>
        const { createApp, ref } = Vue;
        
        createApp({
            setup() {
                const title = ref('我的页面');
                
                const handleClick = () => {
                    ElementPlus.ElMessage.success('按钮被点击了');
                };
                
                return { title, handleClick };
            }
        }).use(ElementPlus).mount('#app');
    </script>
</body>
</html>
```

---

## 🎯 最佳实践

### 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 显示
<el-button :loading="loading">加载数据</el-button>
```

---

## 🐛 调试技巧

### 控制台日志

```javascript
console.log('当前状态:', stats);
console.error('发生错误:', error);
console.warn('警告信息:', warning);
```

### Vue DevTools

安装 [Vue DevTools](https://devtools.vuejs.org/) 浏览器扩展：
- 查看组件树
- 检查响应式数据
- 调试事件

### Network 面板

浏览器开发者工具 → Network：
- 查看 API 请求
- 检查请求参数
- 分析响应数据

---

## 📞 常见问题

### Q: 如何添加新的 CDN 资源？

A: 在 `<head>` 标签中添加：

```html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
```

### Q: 如何使用自定义样式？

A: 在 `<style>` 标签中添加：

```html
<style>
.custom-class {
    color: red;
}
</style>
```

### Q: 如何访问后端 API？

A: 使用相对路径，Gin 会自动代理：

```javascript
fetch('/api/v1/networks')  // ✅ 正确
fetch('http://localhost:8080/api/v1/networks')  // ❌ 错误
```

---

**最后更新**: 2026-03-20  
**适用版本**: MeshRay 2.0+
