Skip to content

Commit 7b61477

Browse files
committed
chore: add api usage
1 parent 1b48db1 commit 7b61477

1 file changed

Lines changed: 208 additions & 44 deletions

File tree

web/src/views/notify/components/ApiUsage.vue

Lines changed: 208 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { Button } from '@/components/ui/button'
33
import { Input } from '@/components/ui/input'
44
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
55
import { Badge } from '@/components/ui/badge'
6-
import { Copy, Terminal, Key, FileJson, RefreshCw, Check, Hash, Info, AlertTriangle } from 'lucide-vue-next'
6+
import { Copy, Terminal, Key, FileJson, RefreshCw, Check, Hash, Info, AlertTriangle, Code2 } from 'lucide-vue-next'
7+
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
78
import {
89
AlertDialog,
910
AlertDialogAction,
@@ -67,10 +68,174 @@ notify-token: <你的API Token>
6768
"text": "内容"
6869
}`
6970
70-
const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v1/notify/send" \\
71-
-H "Content-Type: application/json" \\
72-
-H "notify-token: ${props.apiToken || 'YOUR_TOKEN'}" \\
73-
-d '{"channel_id":"YOUR_CHANNEL_ID","title":"标题","text":"通知内容"}'`)
71+
const shellExample = computed(() => `send_notification() {
72+
curl -s -X POST "http://${host.value}/api/v1/notify/send" \\
73+
-H "Content-Type: application/json" \\
74+
-H "notify-token: \${1:-${props.apiToken || 'YOUR_TOKEN'}}" \\
75+
-d "{\\"channel_id\\":\\"\$2\\",\\"title\\":\\"\$3\\",\\"text\\":\\"\$4\\"}"
76+
}
77+
78+
# 使用示例: send_notification <Token> <渠道ID> <标题> <内容>
79+
send_notification "${props.apiToken || 'YOUR_TOKEN'}" "ID" "任务完成" "脚本执行完毕"`)
80+
81+
const pythonExample = computed(() => `import requests
82+
83+
def send_notification(token, channel_id, text, title="通知"):
84+
url = "http://${host.value}/api/v1/notify/send"
85+
headers = {
86+
"Content-Type": "application/json",
87+
"notify-token": token
88+
}
89+
payload = {
90+
"channel_id": channel_id,
91+
"title": title,
92+
"text": text
93+
}
94+
return requests.post(url, json=payload, headers=headers).json()
95+
96+
# 使用示例
97+
send_notification("${props.apiToken || 'YOUR_TOKEN'}", "ID", "脚本执行完毕", "任务完成")`)
98+
99+
const javascriptExample = computed(() => `async function sendNotification(token, channelId, text, title = "通知") {
100+
const url = "http://${host.value}/api/v1/notify/send";
101+
const res = await fetch(url, {
102+
method: "POST",
103+
headers: {
104+
"Content-Type": "application/json",
105+
"notify-token": token
106+
},
107+
body: JSON.stringify({
108+
channel_id: channelId,
109+
title: title,
110+
text: text
111+
})
112+
});
113+
return res.json();
114+
}
115+
116+
// 使用示例
117+
sendNotification("${props.apiToken || 'YOUR_TOKEN'}", "ID", "脚本执行完毕", "任务完成");`)
118+
119+
const goExample = computed(() => `package main
120+
121+
import (
122+
"bytes"
123+
"encoding/json"
124+
"fmt"
125+
"net/http"
126+
)
127+
128+
func sendNotification(token, channelID, title, text string) error {
129+
url := "http://${host.value}/api/v1/notify/send"
130+
payload := map[string]string{
131+
"channel_id": channelID,
132+
"title": title,
133+
"text": text,
134+
}
135+
body, _ := json.Marshal(payload)
136+
137+
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
138+
req.Header.Set("Content-Type", "application/json")
139+
req.Header.Set("notify-token", token)
140+
141+
client := &http.Client{}
142+
resp, err := client.Do(req)
143+
if err != nil {
144+
return err
145+
}
146+
defer resp.Body.Close()
147+
return nil
148+
}
149+
150+
func main() {
151+
// 使用示例
152+
sendNotification("${props.apiToken || 'YOUR_TOKEN'}", "ID", "任务完成", "脚本执行完毕")
153+
}`)
154+
155+
const examples = [
156+
{ id: 'shell', name: 'Shell', icon: Terminal, code: shellExample },
157+
{ id: 'python', name: 'Python', icon: Code2, code: pythonExample },
158+
{ id: 'javascript', name: 'JavaScript', icon: Code2, code: javascriptExample },
159+
{ id: 'go', name: 'Go', icon: Code2, code: goExample },
160+
]
161+
162+
const activeLang = ref('shell')
163+
164+
const highlightCode = (code: string, lang: string) => {
165+
if (!code) return ''
166+
167+
const colors = {
168+
keyword: 'text-violet-500 dark:text-violet-400 font-medium',
169+
string: 'text-emerald-600 dark:text-emerald-400',
170+
comment: 'text-zinc-400 dark:text-zinc-500 italic',
171+
type: 'text-amber-600 dark:text-amber-500',
172+
function: 'text-blue-500 dark:text-blue-400',
173+
number: 'text-orange-500',
174+
operator: 'text-zinc-400 dark:text-zinc-600'
175+
}
176+
177+
// 基础转义
178+
let html = code
179+
.replace(/&/g, '&amp;')
180+
.replace(/</g, '&lt;')
181+
.replace(/>/g, '&gt;')
182+
183+
// 1. 处理字符串 (优先处理,防止内部匹配)
184+
html = html.replace(/("(?:\\.|[^"])*")|('(?:\\.|[^'])*')/g, `<span class="${colors.string}">$1</span>`)
185+
186+
// 2. 处理注释 (注意排除 http:// 或 https:// 中的双斜杠)
187+
html = html.replace(/(^|[^\:])(\/\/.+)$|(#.+)$/gm, `$1<span class="${colors.comment}">$2$3</span>`)
188+
189+
// 3. 语言配置
190+
const langConfig: Record<string, { keywords: string[], types: string[], functions: string[] }> = {
191+
shell: {
192+
keywords: ['curl'],
193+
types: [],
194+
functions: ['send_notification']
195+
},
196+
python: {
197+
keywords: ['import', 'def', 'return', 'as', 'from'],
198+
types: ['dict', 'list', 'str', 'int', 'float'],
199+
functions: ['send_notification', 'post', 'json']
200+
},
201+
javascript: {
202+
keywords: ['async', 'await', 'function', 'const', 'return', 'let', 'var', 'if', 'else', 'try', 'catch'],
203+
types: ['JSON', 'Promise', 'fetch'],
204+
functions: ['sendNotification', 'stringify', 'json', 'post']
205+
},
206+
go: {
207+
keywords: ['package', 'import', 'func', 'return', 'map', 'defer', 'if', 'nil', 'go', 'main'],
208+
types: ['string', 'error', 'byte', 'int'],
209+
functions: ['Marshal', 'NewRequest', 'Set', 'Do', 'Close', 'Sprintf']
210+
}
211+
}
212+
213+
const conf = langConfig[lang === 'javascript' ? 'javascript' : (lang === 'shell' ? 'shell' : lang)]
214+
if (conf) {
215+
if (conf.keywords.length) {
216+
const regex = new RegExp(`\\b(${conf.keywords.join('|')})\\b(?![^<]*>)`, 'g')
217+
html = html.replace(regex, `<span class="${colors.keyword}">$1</span>`)
218+
}
219+
if (conf.types.length) {
220+
const regex = new RegExp(`\\b(${conf.types.join('|')})\\b(?![^<]*>)`, 'g')
221+
html = html.replace(regex, `<span class="${colors.type}">$1</span>`)
222+
}
223+
if (conf.functions.length) {
224+
const regex = new RegExp(`\\b(${conf.functions.join('|')})\\b(?![^<]*>)`, 'g')
225+
html = html.replace(regex, `<span class="${colors.function}">$1</span>`)
226+
}
227+
}
228+
229+
// 4. 操作符和括号 (可选,这里处理基础)
230+
// html = html.replace(/[:{}[\],]/g, `<span class="${colors.operator}">$&</span>`)
231+
232+
return html
233+
}
234+
235+
const currentExample = computed(() => {
236+
const code = examples.find(e => e.id === activeLang.value)?.code.value || ''
237+
return highlightCode(code, activeLang.value)
238+
})
74239
</script>
75240

76241
<template>
@@ -113,7 +278,7 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
113278

114279
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
115280
<!-- API 接口规格 -->
116-
<Card class="border bg-card shadow-sm flex flex-col overflow-hidden">
281+
<Card class="border bg-card shadow-sm flex flex-col overflow-hidden h-[520px]">
117282
<CardHeader class="pb-3 shrink-0">
118283
<div class="flex items-center justify-between">
119284
<div class="flex items-center gap-2">
@@ -124,9 +289,9 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
124289
</div>
125290
</div>
126291
</CardHeader>
127-
<CardContent class="p-0 flex-1">
292+
<CardContent class="p-0 flex-1 overflow-y-auto">
128293
<div
129-
class="bg-zinc-50 dark:bg-zinc-950/50 p-5 font-code text-xs sm:text-sm leading-relaxed text-zinc-800 dark:text-zinc-300 relative group h-full">
294+
class="bg-zinc-50 dark:bg-zinc-950/50 p-5 font-code text-xs sm:text-sm leading-relaxed text-zinc-800 dark:text-zinc-300 relative group min-h-full">
130295
<div class="flex items-center justify-between mb-6 border-b border-zinc-200 dark:border-zinc-800/50 pb-3">
131296
<div class="flex items-center gap-2">
132297
<Badge class="bg-emerald-600 text-white border-none py-0 px-2 text-[10px]">POST</Badge>
@@ -189,45 +354,44 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
189354
</CardContent>
190355
</Card>
191356

192-
<!-- Shell 示例 -->
193-
<Card class="border bg-card shadow-sm flex flex-col overflow-hidden">
194-
<CardHeader class="pb-3 shrink-0">
195-
<div class="flex items-center justify-between">
196-
<div class="flex items-center gap-2">
197-
<div class="p-1.5 rounded-md bg-sky-500/10 text-sky-600">
198-
<Terminal class="w-4 h-4" />
357+
<!-- 调用示例 -->
358+
<Card class="border bg-card shadow-sm flex flex-col overflow-hidden h-[520px]">
359+
<Tabs v-model="activeLang" class="w-full flex flex-col h-full overflow-hidden">
360+
<CardHeader class="pb-3 shrink-0 border-b">
361+
<div class="flex items-center justify-between">
362+
<div class="flex items-center gap-2">
363+
<div class="p-1.5 rounded-md bg-sky-500/10 text-sky-600">
364+
<Terminal class="w-4 h-4" />
365+
</div>
366+
<CardTitle class="text-sm font-bold uppercase tracking-wider">脚本调用示例</CardTitle>
367+
</div>
368+
<div class="flex items-center gap-3">
369+
<TabsList class="h-8 p-0.5 bg-muted/50 border">
370+
<TabsTrigger v-for="lang in examples" :key="lang.id" :value="lang.id"
371+
class="h-7 px-2.5 text-[11px] data-[state=active]:bg-background data-[state=active]:shadow-sm">
372+
{{ lang.name }}
373+
</TabsTrigger>
374+
</TabsList>
375+
<Button variant="outline" size="sm"
376+
class="h-8 px-2.5 text-[11px] border-muted-foreground/30 hover:bg-muted transition-all"
377+
@click="copyToClipboard(currentExample, 'example')">
378+
<Check v-if="copiedBlock === 'example'" class="w-3.5 h-3.5 text-emerald-500 mr-1.5" />
379+
<Copy v-else class="w-3.5 h-3.5 mr-1.5" />
380+
复制代码
381+
</Button>
199382
</div>
200-
<CardTitle class="text-sm font-bold uppercase tracking-wider">Shell 脚本示例</CardTitle>
201383
</div>
202-
<Button variant="outline" size="sm"
203-
class="h-7 px-2 text-[10px] border-muted-foreground/30 hover:bg-muted transition-all"
204-
@click="copyToClipboard(shellExample, 'shell')">
205-
<Check v-if="copiedBlock === 'shell'" class="w-3 h-3 text-emerald-500 mr-1.5" />
206-
<Copy v-else class="w-3 h-3 mr-1.5" />
207-
一键复制
208-
</Button>
209-
</div>
210-
</CardHeader>
211-
<CardContent class="p-0 flex-1">
212-
<div
213-
class="bg-zinc-50 dark:bg-zinc-950/50 p-5 font-code text-[12px] sm:text-[13px] leading-relaxed text-zinc-800 dark:text-zinc-300 h-full">
214-
<div class="space-y-1">
215-
<p><span class="text-zinc-500"># 使用 CURL 调用推送接口</span></p>
216-
<p>curl -s -X POST <span class="text-emerald-600 dark:text-emerald-400">"http://{{ host
217-
}}/api/v1/notify/send"</span> \</p>
218-
<p class="pl-4"> -H <span class="text-orange-600 dark:text-orange-400">"Content-Type:
219-
application/json"</span> \</p>
220-
<p class="pl-4"> -H <span class="text-orange-600 dark:text-orange-400">"notify-token: {{ apiToken ||
221-
'YOUR_TOKEN' }}"</span> \
222-
</p>
223-
<p class="pl-4"> -d <span
224-
class="text-orange-600 dark:text-orange-400">'{"channel_id":"ID","title":"任务完成","text":"脚本执行完毕"}'</span>
225-
</p>
384+
</CardHeader>
385+
<CardContent class="p-0 flex-1 flex flex-col overflow-hidden">
386+
<!-- 脚本区域:独立滚动 -->
387+
<div class="flex-1 overflow-y-auto p-5 font-code text-[12px] sm:text-[13px] leading-relaxed text-zinc-800 dark:text-zinc-300 bg-zinc-50 dark:bg-zinc-950/50">
388+
<pre class="whitespace-pre-wrap break-all" v-html="currentExample" />
226389
</div>
227390

228-
<div class="mt-8 pt-4 border-t border-zinc-200 dark:border-zinc-800">
391+
<!-- 渠道列表区域:固定在底部,如有需要可独立滚动 -->
392+
<div class="shrink-0 p-5 pt-4 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-100/20 dark:bg-zinc-900/10 max-h-[180px] overflow-y-auto">
229393
<span
230-
class="text-zinc-500 block mb-2 uppercase text-[10px] font-bold tracking-widest flex items-center gap-1.5">
394+
class="text-zinc-500 block mb-3 uppercase text-[10px] font-bold tracking-widest flex items-center gap-1.5">
231395
<Hash class="w-3 h-3" /> 渠道 ID 快速查找
232396
</span>
233397
<div v-if="channels.length === 0" class="text-xs text-zinc-500 italic">暂无活跃渠道</div>
@@ -246,8 +410,8 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
246410
</div>
247411
</div>
248412
</div>
249-
</div>
250-
</CardContent>
413+
</CardContent>
414+
</Tabs>
251415
</Card>
252416
</div>
253417

0 commit comments

Comments
 (0)