代码优化

This commit is contained in:
wong
2025-12-03 16:18:10 +08:00
parent 71def21369
commit 5bb5aa3d5a
3 changed files with 559 additions and 72 deletions

View File

@@ -70,24 +70,72 @@ const PlanApi: React.FC<PlanApiProps> = ({
// 处理webhook URL确保包含完整的API地址
const fullWebhookUrl = useMemo(() => {
return buildApiUrl(webhookUrl);
return buildApiUrl('');
}, [webhookUrl]);
// 生成测试URL
// 快速测试使用的 GET 地址(携带示例查询参数,方便在浏览器中直接访问)
const testUrl = useMemo(() => {
if (!fullWebhookUrl) return "";
return `${fullWebhookUrl}?name=测试客户&phone=13800138000&source=API测试`;
}, [fullWebhookUrl]);
return buildApiUrl(webhookUrl);
}, [webhookUrl]);
// 检测是否为移动端
const isMobile = window.innerWidth <= 768;
const handleCopy = (text: string, type: string) => {
navigator.clipboard.writeText(text);
Toast.show({
content: `${type}已复制到剪贴板`,
position: "top",
});
// 先尝试使用 Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard
.writeText(text)
.then(() => {
Toast.show({
content: `${type}已复制到剪贴板`,
position: "top",
});
})
.catch(() => {
// 回退到传统的 textarea 复制方式
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand("copy");
Toast.show({
content: `${type}已复制到剪贴板`,
position: "top",
});
} catch {
Toast.show({
content: `${type}复制失败,请手动复制`,
position: "top",
});
}
document.body.removeChild(textarea);
});
} else {
// 不支持 Clipboard API 时直接使用回退方案
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand("copy");
Toast.show({
content: `${type}已复制到剪贴板`,
position: "top",
});
} catch {
Toast.show({
content: `${type}复制失败,请手动复制`,
position: "top",
});
}
document.body.removeChild(textarea);
}
};
const handleTestInBrowser = () => {
@@ -96,7 +144,7 @@ const PlanApi: React.FC<PlanApiProps> = ({
const renderConfigTab = () => (
<div className={style["config-content"]}>
{/* API密钥配置 */}
{/* 鉴权参数配置 */}
<div className={style["config-section"]}>
<div className={style["section-header"]}>
<div className={style["section-title"]}>
@@ -122,7 +170,7 @@ const PlanApi: React.FC<PlanApiProps> = ({
</div>
</div>
{/* 接口地址配置 */}
{/* 接口地址与参数说明 */}
<div className={style["config-section"]}>
<div className={style["section-header"]}>
<div className={style["section-title"]}>
@@ -150,27 +198,42 @@ const PlanApi: React.FC<PlanApiProps> = ({
{/* 参数说明 */}
<div className={style["params-grid"]}>
<div className={style["param-section"]}>
<h4></h4>
<h4></h4>
<div className={style["param-list"]}>
<div>
<code>name</code> -
<code>apiKey</code> -
</div>
<div>
<code>phone</code> -
<code>sign</code> -
</div>
<div>
<code>timestamp</code> - 5
</div>
</div>
</div>
<div className={style["param-section"]}>
<h4></h4>
<h4></h4>
<div className={style["param-list"]}>
<div>
<code>source</code> -
<code>wechatId</code> -
</div>
<div>
<code>phone</code> - <code>wechatId</code>
</div>
<div>
<code>name</code> -
</div>
<div>
<code>source</code> - 线广
</div>
<div>
<code>remark</code> -
</div>
<div>
<code>tags</code> -
<code>tags</code> - <code>"高意向,电商,女装"</code>
</div>
<div>
<code>siteTags</code> -
</div>
</div>
</div>
@@ -179,32 +242,34 @@ const PlanApi: React.FC<PlanApiProps> = ({
</div>
);
const renderQuickTestTab = () => (
<div className={style["test-content"]}>
<div className={style["test-section"]}>
<h3>URL</h3>
<div className={style["input-group"]}>
<Input value={testUrl} disabled className={style["test-input"]} />
</div>
<div className={style["test-buttons"]}>
<Button
onClick={() => handleCopy(testUrl, "测试URL")}
className={style["test-btn"]}
>
<CopyOutlined />
URL
</Button>
<Button
type="primary"
onClick={handleTestInBrowser}
className={style["test-btn"]}
>
</Button>
const renderQuickTestTab = () => {
return (
<div className={style["test-content"]}>
<div className={style["test-section"]}>
<h3> URLGET </h3>
<div className={style["input-group"]}>
<Input value={testUrl} disabled className={style["test-input"]} />
</div>
<div className={style["test-buttons"]}>
<Button
onClick={() => handleCopy(testUrl, "测试URL")}
className={style["test-btn"]}
>
<CopyOutlined />
URL
</Button>
<Button
color="primary"
onClick={handleTestInBrowser}
className={style["test-btn"]}
>
</Button>
</div>
</div>
</div>
</div>
);
);
};
const renderDocsTab = () => (
<div className={style["docs-content"]}>
@@ -213,15 +278,8 @@ const PlanApi: React.FC<PlanApiProps> = ({
<div className={style["doc-icon"]}>
<BookOutlined />
</div>
<h4>API文档</h4>
<p></p>
</Card>
<Card className={style["doc-card"]}>
<div className={style["doc-icon"]}>
<LinkOutlined />
</div>
<h4></h4>
<p></p>
<h4>线V1</h4>
<p></p>
</Card>
</div>
</div>
@@ -229,43 +287,63 @@ const PlanApi: React.FC<PlanApiProps> = ({
const renderCodeTab = () => {
const codeExamples = {
javascript: `fetch('${fullWebhookUrl}', {
javascript: `// 参考 api_v1 文档示例,使用 JSON 方式 POST
fetch('${fullWebhookUrl}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${apiKey}'
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '张三',
apiKey: '${apiKey}',
timestamp: 1710000000, // 秒级时间戳
phone: '13800138000',
name: '张三',
source: '官网表单',
remark: '通过H5表单提交',
tags: '高意向,电商',
siteTags: '新客,女装',
// sign 需要根据签名规则生成
sign: '根据签名规则生成的MD5字符串'
})
})`,
python: `import requests
url = '${fullWebhookUrl}'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${apiKey}'
'Content-Type': 'application/json'
}
data = {
'name': '张三',
'phone': '13800138000',
'source': '官网表单'
"apiKey": "${apiKey}",
"timestamp": 1710000000,
"phone": "13800138000",
"name": "张三",
"source": "官网表单",
"remark": "通过H5表单提交",
"tags": "高意向,电商",
"siteTags": "新客,女装",
# sign 需要根据签名规则生成
"sign": "根据签名规则生成的MD5字符串"
}
response = requests.post(url, json=data, headers=headers)`,
php: `<?php
$url = '${fullWebhookUrl}';
$data = array(
'name' => '张三',
'apiKey' => '${apiKey}',
'timestamp' => 1710000000,
'phone' => '13800138000',
'source' => '官网表单'
'name' => '张三',
'source' => '官网表单',
'remark' => '通过H5表单提交',
'tags' => '高意向,电商',
'siteTags' => '新客,女装',
// sign 需要根据签名规则生成
'sign' => '根据签名规则生成的MD5字符串'
);
$options = array(
'http' => array(
'header' => "Content-type: application/json\\r\\nAuthorization: Bearer ${apiKey}\\r\\n",
'header' => "Content-type: application/json\\r\\n",
'method' => 'POST',
'content' => json_encode($data)
)
@@ -279,12 +357,11 @@ import java.net.http.HttpResponse;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
String json = "{\\"name\\":\\"张三\\",\\"phone\\":\\"13800138000\\",\\"source\\":\\"官网表单\\"}";
String json = "{\\"apiKey\\":\\"${apiKey}\\",\\"timestamp\\":1710000000,\\"phone\\":\\"13800138000\\",\\"name\\":\\"张三\\",\\"source\\":\\"官网表单\\",\\"remark\\":\\"通过H5表单提交\\",\\"tags\\":\\"高意向,电商\\",\\"siteTags\\":\\"新客,女装\\",\\"sign\\":\\"根据签名规则生成的MD5字符串\\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("${fullWebhookUrl}"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer ${apiKey}")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
@@ -394,11 +471,7 @@ HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.o
<SafetyOutlined />
HTTPS加密
</div>
<Button
type="primary"
onClick={onClose}
className={style["complete-btn"]}
>
<Button color="primary" onClick={onClose} className={style["complete-btn"]}>
</Button>
</div>

View File

@@ -33,14 +33,15 @@ export const getFullApiPath = (): string => {
* - buildApiUrl('https://api.example.com/webhook/123') → 'https://api.example.com/webhook/123'
*/
export const buildApiUrl = (path: string): string => {
if (!path) return "";
const fullApiPath = getFullApiPath();
if (!path) return `${fullApiPath}`;
// 如果已经是完整的URL包含http或https直接返回
if (path.startsWith("http://") || path.startsWith("https://")) {
return path;
}
const fullApiPath = getFullApiPath();
// 如果是相对路径拼接完整API路径
if (path.startsWith("/")) {