@@ -3,7 +3,8 @@ import { Button } from '@/components/ui/button'
33import { Input } from ' @/components/ui/input'
44import { Card , CardContent , CardDescription , CardHeader , CardTitle } from ' @/components/ui/card'
55import { 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'
78import {
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 , ' &' )
180+ .replace (/ </ g , ' <' )
181+ .replace (/ >/ g , ' >' )
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