feat: add baihu notify
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@ agent/config.ini
|
||||
agent/agent.pid
|
||||
agent/baihu-agent
|
||||
web/dist/
|
||||
|
||||
web/dev-dist/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const { URL } = require('url');
|
||||
|
||||
/**
|
||||
* 环境变量强校验:导入期进行
|
||||
*/
|
||||
const TOKEN = process.env.BHPKG_NOTIFY_TOKEN;
|
||||
const CHANNEL = process.env.BHPKG_NOTIFY_CHANNEL;
|
||||
|
||||
if (!TOKEN || !CHANNEL) {
|
||||
const missing = [];
|
||||
if (!TOKEN) missing.push("BHPKG_NOTIFY_TOKEN");
|
||||
if (!CHANNEL) missing.push("BHPKG_NOTIFY_CHANNEL");
|
||||
|
||||
throw new Error(`缺少必要的环境变量以使用 baihu 模块: ${missing.join(", ")}。请在白虎面板的任务设置中配置这些 Key。`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知的辅助函数 (仅使用 Node.js 标准库)
|
||||
*/
|
||||
function notify(title, text, channelId) {
|
||||
const notifyUrl = process.env.BHPKG_NOTIFY_URL || 'http://localhost:8052/api/v1/notify/send';
|
||||
const cid = channelId || CHANNEL;
|
||||
|
||||
if (!notifyUrl || !TOKEN || !cid) return;
|
||||
|
||||
try {
|
||||
const parsedUrl = new URL(notifyUrl);
|
||||
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
||||
|
||||
const data = JSON.stringify({
|
||||
channel_id: cid,
|
||||
title: title || '系统通知',
|
||||
text: text
|
||||
});
|
||||
|
||||
const options = {
|
||||
hostname: parsedUrl.hostname,
|
||||
port: parsedUrl.port,
|
||||
path: parsedUrl.pathname + (parsedUrl.search || ''),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'notify-token': TOKEN,
|
||||
'Content-Length': Buffer.byteLength(data)
|
||||
}
|
||||
};
|
||||
|
||||
const req = protocol.request(options);
|
||||
req.on('error', (e) => {});
|
||||
req.write(data);
|
||||
req.end();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
module.exports = { notify };
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "baihu",
|
||||
"version": "1.0.0",
|
||||
"description": "Baihu Panel internal helper for Node.js",
|
||||
"main": "index.js",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='baihu',
|
||||
version='1.0.0',
|
||||
description='Baihu Panel internal helper for Python',
|
||||
packages=find_packages(),
|
||||
python_requires='>=3.6',
|
||||
)
|
||||
@@ -0,0 +1,87 @@
|
||||
package builtininstall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/engigu/baihu-panel/internal/logger"
|
||||
"github.com/engigu/baihu-panel/internal/utils"
|
||||
)
|
||||
|
||||
// Run 执行内建包安装逻辑
|
||||
func Run(args []string) {
|
||||
logger.Infof("[Builtin] 开始为 mise 环境安装内建包...")
|
||||
|
||||
// 1. 确定内建包路径
|
||||
// 优先使用 /www/builtin (Docker 环境),否则尝试相对于二进制文件的当前目录
|
||||
builtinPath := "/www/builtin"
|
||||
if _, err := os.Stat(builtinPath); os.IsNotExist(err) {
|
||||
// 回退到当前目录下的 builtin
|
||||
pwd, _ := os.Getwd()
|
||||
builtinPath = filepath.Join(pwd, "builtin")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(builtinPath); os.IsNotExist(err) {
|
||||
logger.Errorf("[Builtin] 找不到内建包目录: %s", builtinPath)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 安装 Node.js 包
|
||||
installForLanguage("node", filepath.Join(builtinPath, "nodejs"), "npm install")
|
||||
|
||||
// 3. 安装 Python 包
|
||||
installForLanguage("python", filepath.Join(builtinPath, "python"), "pip install -e")
|
||||
|
||||
logger.Infof("[Builtin] 内建包安装流程完成")
|
||||
}
|
||||
|
||||
func installForLanguage(lang, pkgPath, installBaseCmd string) {
|
||||
if _, err := os.Stat(pkgPath); os.IsNotExist(err) {
|
||||
logger.Warnf("[Builtin] %s 的内建包目录不存在: %s", lang, pkgPath)
|
||||
return
|
||||
}
|
||||
|
||||
versions, err := utils.ListMiseInstalledVersions(lang)
|
||||
if err != nil {
|
||||
logger.Errorf("[Builtin] 获取 %s 的 mise 版本列表失败: %v", lang, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(versions) == 0 {
|
||||
logger.Infof("[Builtin] 未发现已安装的 %s 版本,跳过", lang)
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
logger.Infof("[Builtin] 正在为 %s@%s 安装内建包...", lang, v)
|
||||
|
||||
var cmdStr string
|
||||
if lang == "node" {
|
||||
// npm install 不需要 -e
|
||||
cmdStr = fmt.Sprintf("npm install %s", pkgPath)
|
||||
} else {
|
||||
// python 建议使用 -e (editable) 或直接安装
|
||||
cmdStr = fmt.Sprintf("pip install -e %s", pkgPath)
|
||||
}
|
||||
|
||||
// 使用 mise exec 跨版本执行
|
||||
fullCmd := utils.BuildMiseCommandSimple(cmdStr, lang, v)
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("cmd", "/c", fullCmd)
|
||||
} else {
|
||||
cmd = exec.Command("sh", "-c", fullCmd)
|
||||
}
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logger.Errorf("[Builtin] 为 %s@%s 安装失败: %v\n输出: %s", lang, v, err, string(out))
|
||||
} else {
|
||||
logger.Infof("[Builtin] 为 %s@%s 安装成功", lang, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/engigu/baihu-panel/cmd/builtininstall"
|
||||
"github.com/engigu/baihu-panel/cmd/reposync"
|
||||
"github.com/engigu/baihu-panel/cmd/resetpwd"
|
||||
"github.com/engigu/baihu-panel/cmd/restore"
|
||||
@@ -12,8 +13,9 @@ type CommandHandler func(args []string)
|
||||
|
||||
// Handlers 维护了除了 server 之外的命令的执行入口
|
||||
var Handlers = map[string]CommandHandler{
|
||||
"reposync": reposync.Run,
|
||||
"resetpwd": resetpwd.Run,
|
||||
"restore": restore.Run,
|
||||
"reposync": reposync.Run,
|
||||
"resetpwd": resetpwd.Run,
|
||||
"restore": restore.Run,
|
||||
"builtininstall": builtininstall.Run,
|
||||
// "migrate": migrate.Run,
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ COPY --from=frontend-builder /app/web/dist /www/baihu
|
||||
# Copy configs and entrypoint
|
||||
COPY --from=backend-builder /app/configs ./configs
|
||||
COPY --from=backend-builder /app/example ./example
|
||||
COPY builtin/ /www/builtin
|
||||
COPY docker/docker-entrypoint.sh .
|
||||
|
||||
|
||||
|
||||
+44
-35
@@ -35,56 +35,65 @@
|
||||
|
||||
---
|
||||
|
||||
### 路径二:脚本手动调用 (API)
|
||||
### 路径二:脚本手动调用 (内置助手库 - 推荐)
|
||||
|
||||
如果您需要在脚本逻辑内部(例如:当抓取到特定数据时)主动触发通知,可以使用此方式。
|
||||
白虎面板提供了一套**零配置**的内建助手库(Built-in SDK),支持 Python 和 Node.js。它会自动读取系统注入的环境变量,让您在脚本中只需一行代码即可实现通知投递。
|
||||
|
||||
#### 1. 环境初始化
|
||||
在开始编写脚本前,您需要在终端执行以下命令,为面板管理的所有语言环境安装 `baihu` 包:
|
||||
|
||||
```bash
|
||||
baihu builtininstall
|
||||
```
|
||||
*该操作会将助手库安装到 mise 管理的所有版本中,确保 import 成功。*
|
||||
|
||||
#### 2. 代码示例
|
||||
|
||||
##### Python (同步调用)
|
||||
```python
|
||||
import baihu
|
||||
|
||||
# 内部自动通过环境变量鉴权,无需填 TOKEN 和 URL
|
||||
baihu.notify("任务标题", "通知正文内容")
|
||||
```
|
||||
|
||||
##### Node.js (异步调用)
|
||||
```javascript
|
||||
const baihu = require('baihu');
|
||||
|
||||
// 极简调用,支持在 CommonJS/ESM 中使用
|
||||
baihu.notify("任务标题", "通知正文内容");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 路径三:其他语言/高级调用 (原始 API)
|
||||
|
||||
如果您使用 Shell 或其他尚未提供助手库的语言,可以通过标准 HTTP POST 请求调用。
|
||||
|
||||
#### 1. 快速获取代码
|
||||
为了极大降低集成门槛,面板内置了代码生成器:
|
||||
- 进入 **「消息推送」** -> **「脚本调用」** 标签。
|
||||
- 页面会根据您已配置的渠道,自动生成包含 **通知 Token** 和 **渠道 ID** 的完整代码示例。
|
||||
- 支持 **Python**、**Node.js** 和 **Shell (Curl)** 格式,直接复制即可使用。
|
||||
进入 **「消息推送」** -> **「脚本调用说明」** 标签,页面会根据您的配置自动生成包含 **通知 Token** 和 **默认渠道 ID** 的完整代码。
|
||||
|
||||
#### 2. 代码参考示例
|
||||
如果您需要手动编写逻辑,请参考以下实现:
|
||||
|
||||
##### Python 示例
|
||||
##### Shell (Curl)
|
||||
```bash
|
||||
curl -X POST "http://localhost:8052/api/v1/notify/send" \
|
||||
-H "notify-token: 您的_NOTIFY_TOKEN" \
|
||||
-d '{"channel_id":"渠道ID", "title":"标题", "text":"内容"}'
|
||||
```
|
||||
|
||||
##### 基础 Python (requests)
|
||||
```python
|
||||
import requests
|
||||
|
||||
def send_notify(title, content):
|
||||
url = "http://localhost:8052/api/v1/notify/send"
|
||||
headers = { "notify-token": "您的_NOTIFY_TOKEN" }
|
||||
data = {
|
||||
"channel_id": "您的_渠道_ID",
|
||||
"title": title,
|
||||
"text": content
|
||||
}
|
||||
data = {"channel_id": "您的_渠道_ID", "title": title, "text": content}
|
||||
requests.post(url, headers=headers, json=data)
|
||||
```
|
||||
|
||||
##### Node.js 示例
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
async function sendNotify(title, content) {
|
||||
await axios.post('http://localhost:8052/api/v1/notify/send', {
|
||||
channel_id: '您的_渠道_ID',
|
||||
title: title,
|
||||
text: content
|
||||
}, {
|
||||
headers: { 'notify-token': '您的_NOTIFY_TOKEN' }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
##### Shell 示例
|
||||
```bash
|
||||
curl -X POST "http://localhost:8052/api/v1/notify/send" \
|
||||
-H "notify-token: 您的_NOTIFY_TOKEN" \
|
||||
-d '{"channel_id":"渠道ID", "title":"标题", "text":"内容"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 消息中心管理
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const baihu = require('baihu');
|
||||
|
||||
/**
|
||||
* 内建通知测试示例 (Node.js)
|
||||
*
|
||||
* 使用说明:
|
||||
* 1. 确保已在该 Node.js 环境下安装过内建包。
|
||||
* 2. 系统会自动注入 BHPKG_NOTIFY_TOKEN 和 BHPKG_NOTIFY_CHANNEL。
|
||||
*/
|
||||
|
||||
console.log("正在尝试发送 Node.js 内建通知...");
|
||||
|
||||
try {
|
||||
// 简单的一行代码即可完成推送
|
||||
baihu.notify(
|
||||
"Node.js 任务提醒",
|
||||
"这是一条来自 Node.js 示例脚本的通知消息。无需配置 API 地址或 Token。"
|
||||
);
|
||||
|
||||
console.log("发送请求已提交。");
|
||||
console.log("提示:内建包采用异步非阻塞发送,不会干扰主逻辑执行。");
|
||||
|
||||
} catch (e) {
|
||||
console.error(`通知失败: ${e.message}`);
|
||||
console.error("请检查环境变量是否注入,或是否已运行过 'baihu builtininstall'。");
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import baihu
|
||||
|
||||
# 内建通知测试示例 (Python)
|
||||
#
|
||||
# 前提条件:
|
||||
# 1. 已经在环境中安装了 baihu 包(例如通过 `baihu builtininstall`)
|
||||
# 2. 环境中已注入有效环境变量:
|
||||
# - BHPKG_NOTIFY_TOKEN
|
||||
# - BHPKG_NOTIFY_CHANNEL
|
||||
|
||||
def main():
|
||||
print("正在尝试发送 Python 内建通知...")
|
||||
try:
|
||||
# 调用内建 notify 函数
|
||||
# 内部会自动使用 BHPKG_NOTIFY_TOKEN & BHPKG_NOTIFY_CHANNEL 进行鉴权和投递
|
||||
response = baihu.notify(
|
||||
title="Python 任务提醒",
|
||||
text="这是一条来自 Python 示例脚本的通知消息。调用非常简单!"
|
||||
)
|
||||
print("发送请求已处理。")
|
||||
if response:
|
||||
print(f"服务器响应: {response}")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"错误: 库未正确加载。请确保已执行过环境初始化。详情: {e}")
|
||||
except Exception as e:
|
||||
print(f"发送过程发生异常: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -24,4 +24,8 @@ var Commands = []CommandInfo{
|
||||
Name: "restore",
|
||||
Description: "从本地 zip 文件中全量恢复系统级备份数据",
|
||||
},
|
||||
{
|
||||
Name: "builtininstall",
|
||||
Description: "为所有 mise 管理的 Node.js 和 Python 环境安装内建助手库",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -117,3 +117,31 @@ func BuildMiseCommandArgsSimple(cmdArgs []string, language, version string) []st
|
||||
}
|
||||
return append([]string{"mise", "exec", spec, "--"}, cmdArgs...)
|
||||
}
|
||||
|
||||
// ListMiseInstalledVersions 获取指定语言已安装的所有版本列表
|
||||
func ListMiseInstalledVersions(language string) ([]string, error) {
|
||||
// 执行 mise ls <language> 命令
|
||||
cmd := exec.Command("mise", "ls", language)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var versions []string
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
v := strings.TrimSpace(line)
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
// mise ls 的输出类似:
|
||||
// 3.12.1
|
||||
// 3.11.5
|
||||
// 我们取第一个字段即可
|
||||
fields := strings.Fields(v)
|
||||
if len(fields) > 0 {
|
||||
versions = append(versions, fields[0])
|
||||
}
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
@@ -332,75 +332,106 @@ const currentExample = computed(() => {
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- API 接口规格 -->
|
||||
<Card class="border bg-card shadow-sm flex flex-col overflow-hidden h-[520px]">
|
||||
<CardHeader class="pb-3 shrink-0">
|
||||
<CardHeader class="pb-2 shrink-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="p-1.5 rounded-md bg-emerald-500/10 text-emerald-600">
|
||||
<FileJson class="w-4 h-4" />
|
||||
<Code2 class="w-4 h-4" />
|
||||
</div>
|
||||
<CardTitle class="text-sm font-bold uppercase tracking-wider whitespace-nowrap">API 接口说明</CardTitle>
|
||||
<CardTitle class="text-sm font-bold uppercase tracking-wider whitespace-nowrap">内建助手库说明</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="p-0 flex-1 overflow-y-auto">
|
||||
<div
|
||||
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">
|
||||
<div class="flex items-center justify-between mb-6 border-b border-zinc-200 dark:border-zinc-800/50 pb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge class="bg-emerald-600 text-white border-none py-0 px-2 text-[10px]">POST</Badge>
|
||||
<code
|
||||
class="px-2 py-0.5 bg-zinc-200/50 dark:bg-zinc-800/60 border border-zinc-300/50 dark:border-zinc-700/50 rounded-md text-[13px] text-zinc-700 dark:text-zinc-300 font-mono shadow-sm tracking-tight">/api/v1/notify/send</code>
|
||||
class="bg-zinc-50 dark:bg-zinc-950/50 p-4 text-xs sm:text-sm leading-relaxed text-zinc-800 dark:text-zinc-300 relative group min-h-full">
|
||||
|
||||
<div class="mb-4 border-b border-zinc-200 dark:border-zinc-800/50 pb-3">
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<Badge class="bg-primary text-primary-foreground border-none py-0 px-2 text-[10px]">RECOMMENDED</Badge>
|
||||
<span class="text-sm font-bold text-zinc-900 dark:text-zinc-100">内建助手库 (Built-in)</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon"
|
||||
class="h-7 w-7 text-zinc-500 hover:text-zinc-900 dark:hover:text-white hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-all rounded-md"
|
||||
@click="copyToClipboard(apiExample, 'api')">
|
||||
<Check v-if="copiedBlock === 'api'" class="w-3.5 h-3.5 text-emerald-500" />
|
||||
<Copy v-else class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<p class="text-[12px] text-zinc-500 leading-normal">
|
||||
白虎面板内置了跨语言的脚本推送工具。通过环境层预装该库,您的脚本可以实现完全的“零配置”调用。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<span class="block mb-2 text-zinc-500 uppercase text-[10px] font-bold tracking-widest">Headers</span>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-zinc-500">Content-Type:</span>
|
||||
<span>application/json</span>
|
||||
<div class="space-y-5">
|
||||
<!-- 一键安装说明 -->
|
||||
<div class="space-y-2">
|
||||
<span class="block text-zinc-500 uppercase text-[10px] font-bold tracking-widest flex items-center gap-1.5">
|
||||
<Terminal class="w-3.5 h-3.5" />
|
||||
环境初始化 (一键安装)
|
||||
</span>
|
||||
<div class="group relative">
|
||||
<div class="bg-zinc-900 dark:bg-black p-3 rounded-lg font-mono text-[12px] text-emerald-500 shadow-inner">
|
||||
<span class="text-zinc-500 select-none">$ </span>baihu builtininstall
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-zinc-500">notify-token:</span>
|
||||
<span class="text-primary"><TOKEN></span>
|
||||
<Button variant="ghost" size="icon"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 h-7 w-7 text-zinc-500 hover:text-white hover:bg-white/10 opacity-0 group-hover:opacity-100 transition-all"
|
||||
@click="copyToClipboard('baihu builtininstall', 'install-cmd')">
|
||||
<Check v-if="copiedBlock === 'install-cmd'" class="w-3.5 h-3.5 text-emerald-500" />
|
||||
<Copy v-else class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<p class="text-[10px] text-zinc-500 italic pl-1">
|
||||
* 该命令会为 mise 管理的所有 Python 和 Node.js 版本安装 <code class="text-primary font-bold">baihu</code> 包。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 脚本调用方案 -->
|
||||
<div class="space-y-3">
|
||||
<span class="block text-zinc-500 uppercase text-[10px] font-bold tracking-widest flex items-center gap-1.5">
|
||||
<Code2 class="w-3.5 h-3.5" />
|
||||
导入并使用
|
||||
</span>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
<!-- Python 示例 -->
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center justify-between px-1">
|
||||
<span class="text-[10px] font-medium text-zinc-400">Python</span>
|
||||
<badge variant="outline" class="text-[8px] h-3.5 px-1 border-zinc-700 text-zinc-500">BAIHU-PY</badge>
|
||||
</div>
|
||||
<div class="bg-zinc-200/50 dark:bg-zinc-800/60 p-3 rounded-lg border border-zinc-200 dark:border-zinc-700/50 relative group shadow-sm">
|
||||
<pre class="text-[11px] leading-snug"><span class="text-violet-500">import</span> baihu<br/>baihu.notify(<span class="text-emerald-600">"标题"</span>, <span class="text-emerald-600">"内容"</span>)</pre>
|
||||
<Button variant="ghost" size="icon"
|
||||
class="absolute right-2 top-2 h-6 w-6 text-zinc-400 opacity-0 group-hover:opacity-100 transition-all"
|
||||
@click="copyToClipboard('import baihu\nbaihu.notify(\'标题\', \'内容\')', 'py-builtin')">
|
||||
<Check v-if="copiedBlock === 'py-builtin'" class="w-3.5 h-3.5 text-emerald-500" />
|
||||
<Copy v-else class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Node.js 示例 -->
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center justify-between px-1">
|
||||
<span class="text-[10px] font-medium text-zinc-400">Node.js</span>
|
||||
<badge variant="outline" class="text-[8px] h-3.5 px-1 border-zinc-700 text-zinc-500">BAIHU-JS</badge>
|
||||
</div>
|
||||
<div class="bg-zinc-200/50 dark:bg-zinc-800/60 p-3 rounded-lg border border-zinc-200 dark:border-zinc-700/50 relative group shadow-sm">
|
||||
<pre class="text-[11px] leading-snug"><span class="text-violet-500">const</span> baihu = require(<span class="text-emerald-600">'baihu'</span>);<br/>baihu.notify(<span class="text-emerald-600">"标题"</span>, <span class="text-emerald-600">"内容"</span>);</pre>
|
||||
<Button variant="ghost" size="icon"
|
||||
class="absolute right-2 top-2 h-6 w-6 text-zinc-400 opacity-0 group-hover:opacity-100 transition-all"
|
||||
@click="copyToClipboard('const baihu = require(\'baihu\');\nbaihu.notify(\'标题\', \'内容\');', 'js-builtin')">
|
||||
<Check v-if="copiedBlock === 'js-builtin'" class="w-3.5 h-3.5 text-emerald-500" />
|
||||
<Copy v-else class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="block mb-2 text-zinc-500 uppercase text-[10px] font-bold tracking-widest">Body
|
||||
(JSON)</span>
|
||||
<div class="pl-2 font-mono">
|
||||
<p class="text-zinc-600 dark:text-zinc-400 mb-1">{</p>
|
||||
<div class="pl-4 space-y-2">
|
||||
<div class="flex flex-wrap items-center gap-x-2">
|
||||
<span class="text-orange-600 dark:text-orange-400">"channel_id":</span>
|
||||
<span class="text-emerald-600 dark:text-emerald-400">"ID"</span><span
|
||||
class="text-zinc-600 dark:text-zinc-400">,</span>
|
||||
<span class="text-zinc-500 font-sans italic ml-auto whitespace-nowrap">//
|
||||
渠道唯一标识</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-2">
|
||||
<span class="text-orange-600 dark:text-orange-400">"title":</span>
|
||||
<span class="text-emerald-600 dark:text-emerald-400">"标题"</span><span
|
||||
class="text-zinc-600 dark:text-zinc-400">,</span>
|
||||
<span class="text-zinc-500 font-sans italic ml-auto whitespace-nowrap">// 可选</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-2">
|
||||
<span class="text-orange-600 dark:text-orange-400">"text":</span>
|
||||
<span class="text-emerald-600 dark:text-emerald-400">"内容"</span>
|
||||
<span class="text-zinc-500 font-sans italic ml-auto whitespace-nowrap">// 必填</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-zinc-600 dark:text-zinc-400 mt-1">}</p>
|
||||
<!-- 核心机制说明 -->
|
||||
<div class="p-3 rounded-lg bg-orange-500/5 border border-orange-500/10 space-y-1.5">
|
||||
<div class="flex items-center gap-2 text-[11px] font-bold text-orange-600 dark:text-orange-400 uppercase tracking-tight">
|
||||
<AlertTriangle class="w-3 h-3" />
|
||||
核心机制
|
||||
</div>
|
||||
<p class="text-[10px] text-zinc-500 leading-normal">
|
||||
系统在执行脚本时会默认注入 <code class="text-zinc-700 dark:text-zinc-300">BHPKG_NOTIFY_TOKEN</code> & <code class="text-zinc-700 dark:text-zinc-300">BHPKG_NOTIFY_CHANNEL</code>。库会自动读取这些值,实现真正的免配置调用。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -439,7 +470,7 @@ const currentExample = computed(() => {
|
||||
</CardHeader>
|
||||
<CardContent class="p-0 flex-1 flex flex-col overflow-hidden">
|
||||
<!-- 脚本区域:独立滚动 -->
|
||||
<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">
|
||||
<div class="flex-1 overflow-y-auto p-5 text-[12px] sm:text-[13px] leading-relaxed text-zinc-800 dark:text-zinc-300 bg-zinc-50 dark:bg-zinc-950/50">
|
||||
<pre class="whitespace-pre-wrap break-all" v-html="currentExample" />
|
||||
</div>
|
||||
|
||||
@@ -492,36 +523,4 @@ const currentExample = computed(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.font-code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.font-code ::selection {
|
||||
background-color: rgba(59, 130, 246, 0.4);
|
||||
/* 鲜亮的蓝色选中背景 */
|
||||
color: #fff;
|
||||
/* 选中时文字为白色 */
|
||||
}
|
||||
|
||||
/* 兼容 Safari */
|
||||
.font-code ::-moz-selection {
|
||||
background-color: rgba(59, 130, 246, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 优化滚动条样式 if needed */
|
||||
.font-code::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.font-code::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.font-code::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user