Procházet zdrojové kódy

feat(about页面): 添加DTU重启功能和相关UI样式

1.  在About页面新增系统操作区域,添加重启DTU按钮
2.  增加重启DTU的确认弹窗和接口调用逻辑,添加加载状态处理
3.  新增系统操作区域的CSS样式
4.  新增Claude的OmniGraffle生成技能文档
wenhongquan před 1 dnem
rodič
revize
a366ea1bff
2 změnil soubory, kde provedl 248 přidání a 3 odebrání
  1. 197 0
      .claude/omnigraffle-generatorSKILL.md
  2. 51 3
      frontend/src/views/About.vue

+ 197 - 0
.claude/omnigraffle-generatorSKILL.md

@@ -0,0 +1,197 @@
+---
+name: omnigraffle
+description: >
+  Generate native OmniGraffle .graffle files programmatically. Use this skill whenever the user
+  asks to create, build, or generate an OmniGraffle diagram, network diagram, architecture diagram,
+  flowchart, or any visual diagram that should be saved as a .graffle file. Also trigger when the
+  user mentions "OmniGraffle", ".graffle", "graffle file", or asks for diagrams specifically for
+  macOS/iOS diagramming. This skill generates zipped binary plist packages that open natively in
+  OmniGraffle 7+ without any conversion or import steps. Supports: rectangles with colored fills,
+  text labels (RTF), named layers, line connections between shapes, arrows, groups, rotation,
+  dashed lines, multi-point paths, and magnets for connection snapping.
+---
+
+# OmniGraffle File Generator
+
+Generate native `.graffle` files that open directly in OmniGraffle 7+ on macOS and iOS.
+
+## How It Works
+
+OmniGraffle files are ZIP archives containing a binary Apple plist (`data.plist`) and a preview
+JPEG. This skill uses Python's `plistlib` to assemble the document structure and `zipfile` to
+package it. The output matches OmniGraffle 7's native format (GraphDocumentVersion 16, zipped).
+
+## Step 1: Copy Scripts
+
+Before generating any file, copy the builder scripts to your working directory:
+
+```bash
+cp -r /path/to/this/skill/src/ /home/claude/omnigraffle-src/
+```
+
+Then import:
+```python
+import sys
+sys.path.insert(0, '/home/claude/omnigraffle-scripts')
+from src.graffle_builder import GraffleBuilder, create_network_diagram, COLORS
+```
+
+## Step 2: Understand the User's Diagram
+
+Ask or infer:
+1. **What type of diagram?** Network architecture, flowchart, org chart, system diagram
+2. **What are the nodes/shapes?** Names, positions, colors, groupings
+3. **What are the connections?** Which nodes connect, line colors, arrows
+4. **What layers are needed?** Logical groupings (e.g., "hardware", "network", "labels")
+
+## Step 3: Build the Diagram
+
+### Option A: Low-Level Builder API
+
+For full control over every element:
+
+```python
+from src.graffle_builder import GraffleBuilder
+
+b = GraffleBuilder(title="My Diagram", creator="User Name")
+
+# Clear default layer and add custom ones
+b._layers = []
+l_hw = b.add_layer("hardware")
+l_net = b.add_layer("network")
+
+# Add shapes (returns shape ID for connections)
+srv = b.add_shape(x=100, y=100, w=120, h=40,
+                  text="Server1", fill_color="blue",
+                  layer=l_hw, name="Server1")
+
+sw = b.add_shape(x=300, y=100, w=120, h=40,
+                 text="Switch", fill_color="green",
+                 layer=l_net, name="Switch")
+
+# Connect shapes
+b.add_line(from_id=srv, to_id=sw, color="blue", width=2.0,
+           layer=l_net, head_arrow="FilledArrow")
+
+# Add floating text labels
+b.add_text_label(100, 60, "Rack 1", font_size=14,
+                 text_color=(0,0,0), bold=True)
+
+# Group shapes together
+group_id = b.add_group([srv, sw], layer=l_hw)
+
+# Save
+b.save("/mnt/user-data/outputs/diagram.graffle")
+```
+
+### Option B: High-Level Network Diagram
+
+For quick network/architecture diagrams from structured data:
+
+```python
+from src.graffle_builder import create_network_diagram
+
+builder = create_network_diagram(
+    title="Data Center Network",
+    layers=["servers", "switches", "storage"],
+    nodes=[
+        {"name": "Web-1", "x": 100, "y": 50, "color": "blue"},
+        {"name": "Web-2", "x": 250, "y": 50, "color": "blue"},
+        {"name": "LB",    "x": 175, "y": 150, "w": 140, "color": "green"},
+        {"name": "DB",    "x": 175, "y": 250, "color": "red", "layer": 2},
+    ],
+    connections=[
+        {"from_name": "Web-1", "to_name": "LB", "color": "yellow", "width": 2},
+        {"from_name": "Web-2", "to_name": "LB", "color": "yellow", "width": 2},
+        {"from_name": "LB", "to_name": "DB", "arrow": True, "width": 2},
+    ],
+)
+builder.save("/mnt/user-data/outputs/network.graffle")
+```
+
+## API Reference
+
+### GraffleBuilder(title, creator, orientation, paper_size)
+
+| Param | Default | Values |
+|-------|---------|--------|
+| title | "Canvas 1" | Canvas name |
+| creator | "Claude" | Author metadata |
+| orientation | "landscape" | "landscape" or "portrait" |
+| paper_size | "A4" | "A4", "letter", "A3" |
+
+### add_layer(name, locked, visible, printable) → layer_index
+
+### add_shape(x, y, w, h, ...) → shape_id
+
+| Param | Default | Notes |
+|-------|---------|-------|
+| text | "" | Label inside shape |
+| fill_color | None | RGB (0-1) tuple or color name string |
+| text_color | (255,255,255) | RGB (0-255) |
+| font_size | 10 | Points |
+| font | "HelveticaNeue" | Font name |
+| layer | 0 | Layer index |
+| name | None | Shape identifier |
+| rotation | None | Degrees (e.g., 90.0) |
+| magnets | True | Connection snap points |
+| stroke | False | Draw border |
+| stroke_color | None | Border RGB (0-1) |
+| stroke_width | 1.0 | Border width |
+| bold | False | Bold text |
+| fit_text | False | Auto-resize to fit text |
+
+### add_text_label(x, y, text, ...) → shape_id
+
+Convenience for borderless/fillless text. Same params as add_shape minus fill/stroke.
+
+### add_line(from_id, to_id, ...) → line_id
+
+| Param | Default | Notes |
+|-------|---------|-------|
+| from_id/to_id | None | Shape IDs for connected lines |
+| from_point/to_point | None | (x,y) for unconnected endpoints |
+| color | None | RGB (0-1) or color name |
+| width | 1.0 | Line width |
+| layer | 0 | Layer index |
+| head_arrow | None | "FilledArrow" for arrowhead |
+| dashed | False | Dashed line style |
+
+### add_line_multi_point(points, ...) → line_id
+
+For multi-segment paths. `points` is a list of (x,y) tuples.
+
+### add_group(shape_ids, layer) → group_id
+
+Groups existing shapes. Removes them from main list and nests under group.
+
+### save(filepath)
+
+Writes the .graffle ZIP file.
+
+## Available Named Colors
+
+Use these strings for `fill_color` or `color` parameters:
+
+blue, red, green, yellow, orange, purple, cyan, pink, brown, white,
+lightgray, darkgray, black, lightblue, lightgreen, lightcyan, lightpink,
+lightyellow, darkblue, darkgreen, darkred
+
+Or pass any RGB tuple like `(0.5, 0.8, 0.2)` in the 0-1 range.
+
+## Layout Tips
+
+- OmniGraffle coordinate system: origin is top-left, units are points (1/72 inch)
+- Typical node sizes: 100-150w × 30-50h for labels, 40-60w × 14-20h for small tags
+- Vertical spacing: 80-120 points between rows
+- Horizontal spacing: 40-80 points between columns
+- Use layers to separate logical diagram elements (hardware, networks, labels)
+- Name important shapes so they can be found in OmniGraffle's sidebar
+
+## Limitations
+
+- All shapes are rectangles (no circles, ovals, or custom paths yet)
+- No image fills or embedded images
+- No stencil references
+- Auto-layout must be done in OmniGraffle after opening
+- Preview JPEG is a blank placeholder (OmniGraffle regenerates it on first save)

+ 51 - 3
frontend/src/views/About.vue

@@ -105,14 +105,56 @@
             <a-descriptions-item label="更新日期">2024年</a-descriptions-item>
           </a-descriptions>
         </div>
+
+        <!-- 系统操作 -->
+        <div class="system-operations">
+          <h3>系统操作</h3>
+          <a-space>
+            <a-button type="primary" danger :loading="isRebooting" @click="rebootDTU">
+              <template #icon><PoweroffOutlined /></template>
+              重启DTU
+            </a-button>
+          </a-space>
+        </div>
       </div>
     </a-card>
   </div>
 </template>
 
 <script setup>
-import { onMounted } from 'vue'
-import { message } from 'ant-design-vue'
+import { onMounted, ref } from 'vue'
+import { message, Modal } from 'ant-design-vue'
+import { ReloadOutlined, PoweroffOutlined } from '@ant-design/icons-vue'
+import apiService from '../api/apiService'
+
+const isRebooting = ref(false)
+
+const rebootDTU = async () => {
+  Modal.confirm({
+    title: '确认重启DTU',
+    content: '确定要发送重启命令到DTU吗?这将中断当前的通信。',
+    okText: '确认重启',
+    cancelText: '取消',
+    onOk: async () => {
+      try {
+        isRebooting.value = true
+        const res = await apiService.dtu.sendControl({
+          command: 'REBOOT',
+          params: {}
+        })
+        if (res.data.success) {
+          message.success('重启命令已发送')
+        } else {
+          message.error(res.data.message || '重启失败')
+        }
+      } catch (error) {
+        message.error('重启失败: ' + (error.response?.data?.message || error.message))
+      } finally {
+        isRebooting.value = false
+      }
+    }
+  })
+}
 
 onMounted(() => {
   message.info('欢迎查看关于页面')
@@ -200,11 +242,17 @@ onMounted(() => {
 }
 
 /* 许可证和联系信息部分 */
-.license-section, .contact-section {
+.license-section, .contact-section, .system-operations {
   border-top: 1px solid #f0f0f0;
   padding-top: 24px;
 }
 
+.system-operations h3 {
+  margin-bottom: 16px;
+  font-size: 20px;
+  font-weight: 600;
+}
+
 .license-section h3, .contact-section h3 {
   margin-bottom: 16px;
   font-size: 20px;