代码优化
This commit is contained in:
@@ -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>快速测试 URL(GET 示例)</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>
|
||||
|
||||
@@ -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("/")) {
|
||||
|
||||
Reference in New Issue
Block a user