feat: 本次提交更新内容如下

计划列表构建完成
This commit is contained in:
笔记本里的永平
2025-07-21 12:22:22 +08:00
parent 881dd0f067
commit 3447646fc5
5 changed files with 1075 additions and 197 deletions

View File

@@ -1,18 +1,20 @@
import React, { useEffect, useState } from "react";
import { NavBar } from "antd-mobile";
import {
AppOutline,
UserOutline,
ClockCircleOutline,
SendOutline,
StarOutline,
Bell,
Smartphone,
Users,
Activity,
MessageSquare,
TrendingUp,
} from "antd-mobile-icons";
import {
BellOutlined,
MobileOutlined,
TeamOutlined,
LineChartOutlined,
MessageOutlined,
RiseOutlined,
} from "@ant-design/icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss";
@@ -73,28 +75,28 @@ const Home: React.FC = () => {
{
title: "朋友圈同步",
value: "12",
icon: <MessageSquare className="h-4 w-4" />,
icon: <MessageOutlined className="h-4 w-4" />,
color: "text-purple-600",
path: "/workspace/moments-sync",
},
{
title: "群发任务",
value: "8",
icon: <Users className="h-4 w-4" />,
icon: <TeamOutlined className="h-4 w-4" />,
color: "text-orange-600",
path: "/workspace/group-push",
},
{
title: "获客转化",
value: "85%",
icon: <TrendingUp className="h-4 w-4" />,
icon: <RiseOutlined className="h-4 w-4" />,
color: "text-green-600",
path: "/scenarios",
},
{
title: "系统活跃度",
value: "98%",
icon: <Activity className="h-4 w-4" />,
icon: <LineChartOutlined className="h-4 w-4" />,
color: "text-blue-600",
path: "/workspace",
},
@@ -240,7 +242,7 @@ const Home: React.FC = () => {
</div>
)}
<button className="p-2 hover:bg-gray-100 rounded-full">
<Bell className="h-5 w-5 text-gray-600" />
<BellOutlined className="h-5 w-5 text-gray-600" />
</button>
</div>
</div>
@@ -260,7 +262,7 @@ const Home: React.FC = () => {
<span className="text-lg font-bold text-blue-600">
{dashboard.deviceNum || 42}
</span>
<Smartphone className="w-5 h-5 text-blue-600" />
<MobileOutlined className="w-5 h-5 text-blue-600" />
</div>
<div className="h-2"></div>
</div>
@@ -274,7 +276,7 @@ const Home: React.FC = () => {
<span className="text-lg font-bold text-blue-600">
{dashboard.wechatNum || 42}
</span>
<Users className="w-5 h-5 text-blue-600" />
<TeamOutlined className="w-5 h-5 text-blue-600" />
</div>
</div>
<div className="h-2"></div>
@@ -287,7 +289,7 @@ const Home: React.FC = () => {
<span className="text-lg font-bold text-blue-600">
{dashboard.aliveWechatNum || 35}
</span>
<Activity className="w-5 h-5 text-blue-600" />
<LineChartOutlined className="w-5 h-5 text-blue-600" />
</div>
<div className="h-1 bg-gray-200 rounded-full">
<div

View File

@@ -275,15 +275,6 @@
font-weight: 500;
}
.api-dialog {
background: white;
border-radius: 16px 16px 0 0;
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.dialog-header {
display: flex;
justify-content: space-between;
@@ -302,84 +293,11 @@
.dialog-content {
flex: 1;
overflow-y: auto;
}
.api-item {
margin-bottom: 20px;
label {
display: block;
font-size: 14px;
color: #333;
margin-bottom: 8px;
font-weight: 500;
}
.ant-input {
border-radius: 8px;
}
}
.input-with-button {
text-align: center;
display: flex;
gap: 8px;
flex-direction: column;
align-items: center;
.ant-input {
flex: 1;
}
}
// API设置相关样式
.api-tip {
margin-top: 12px;
padding: 12px;
background-color: #fff7e6;
border: 1px solid #ffd591;
border-radius: 8px;
font-size: 12px;
color: #d46b08;
line-height: 1.5;
}
.api-params {
margin-top: 16px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.param-section {
background-color: #f8f9fa;
border-radius: 8px;
padding: 12px;
border: 1px solid #e9ecef;
h4 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 600;
color: #333;
}
}
.param-list {
font-size: 12px;
color: #666;
line-height: 1.6;
div {
margin-bottom: 4px;
}
code {
background-color: #e9ecef;
padding: 2px 4px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 11px;
}
justify-content: center;
}
.qr-dialog {

View File

@@ -24,6 +24,7 @@ import {
ClockCircleOutlined,
DownOutlined,
} from "@ant-design/icons";
import { LeftOutline } from "antd-mobile-icons";
import Layout from "@/components/Layout/Layout";
import {
@@ -35,6 +36,7 @@ import {
} from "./api";
import style from "./index.module.scss";
import { Task, ApiSettings, PlanDetail } from "./data";
import PlanApi from "./planApi";
const ScenarioList: React.FC = () => {
const { scenarioId, scenarioName } = useParams<{
@@ -217,20 +219,12 @@ const ScenarioList: React.FC = () => {
}
} catch (error) {
Toast.show({
content: "获取API设置失败",
content: "获取计划接口失败",
position: "top",
});
}
};
const handleCopyApiUrl = (url: string) => {
navigator.clipboard.writeText(url);
Toast.show({
content: "API地址已复制到剪贴板",
position: "top",
});
};
const handleCreateNewPlan = () => {
navigate(`/scenarios/new/${scenarioId}`);
};
@@ -294,31 +288,31 @@ const ScenarioList: React.FC = () => {
const getActionMenu = (task: Task) => [
{
key: "edit",
text: "编辑",
text: "编辑计划",
icon: <EditOutlined />,
onClick: () => {
setShowActionMenu(null);
navigate(`/scenarios/edit/${task.id}`);
},
},
{
key: "settings",
text: "API设置",
icon: <SettingOutlined />,
onClick: () => {
setShowActionMenu(null);
handleOpenApiSettings(task.id);
},
},
{
key: "copy",
text: "复制",
text: "复制计划",
icon: <CopyOutlined />,
onClick: () => {
setShowActionMenu(null);
handleCopyPlan(task.id);
},
},
{
key: "settings",
text: "计划接口",
icon: <SettingOutlined />,
onClick: () => {
setShowActionMenu(null);
handleOpenApiSettings(task.id);
},
},
{
key: "qrcode",
text: "二维码",
@@ -330,7 +324,7 @@ const ScenarioList: React.FC = () => {
},
{
key: "delete",
text: "删除",
text: "删除计划",
icon: <DeleteOutlined />,
onClick: () => {
setShowActionMenu(null);
@@ -355,7 +349,14 @@ const ScenarioList: React.FC = () => {
<NavBar
back={null}
style={{ background: "#fff" }}
left={<div className={style["nav-title"]}>{scenarioName}</div>}
left={
<div className={style["nav-title"]}>
<span style={{ verticalAlign: "middle" }}>
<LeftOutline onClick={() => navigate(-1)} fontSize={24} />
</span>
{scenarioName}
</div>
}
right={
<Button
size="small"
@@ -504,81 +505,14 @@ const ScenarioList: React.FC = () => {
)}
</div>
{/* API设置弹窗 */}
<Popup
{/* 计划接口弹窗 */}
<PlanApi
visible={showApiDialog}
onMaskClick={() => setShowApiDialog(false)}
position="bottom"
bodyStyle={{ height: "70vh" }}
>
<div className={style["api-dialog"]}>
<div className={style["dialog-header"]}>
<h3>API设置</h3>
<Button size="small" onClick={() => setShowApiDialog(false)}>
</Button>
</div>
<div className={style["dialog-content"]}>
<div className={style["api-item"]}>
<label>API Key:</label>
<div className={style["input-with-button"]}>
<Input value={currentApiSettings.apiKey} disabled />
<Button
size="mini"
onClick={() => handleCopyApiUrl(currentApiSettings.apiKey)}
>
</Button>
</div>
<div className={style["api-tip"]}>
<strong></strong>
API密钥使
</div>
</div>
<div className={style["api-item"]}>
<label>Webhook URL:</label>
<div className={style["input-with-button"]}>
<Input value={currentApiSettings.webhookUrl} disabled />
<Button
size="mini"
onClick={() =>
handleCopyApiUrl(currentApiSettings.webhookUrl)
}
>
</Button>
</div>
<div className={style["api-params"]}>
<div className={style["param-section"]}>
<h4></h4>
<div className={style["param-list"]}>
<div>
<code>name</code> -
</div>
<div>
<code>phone</code> -
</div>
</div>
</div>
<div className={style["param-section"]}>
<h4></h4>
<div className={style["param-list"]}>
<div>
<code>source</code> -
</div>
<div>
<code>remark</code> -
</div>
<div>
<code>tags</code> -
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Popup>
onClose={() => setShowApiDialog(false)}
apiKey={currentApiSettings.apiKey}
webhookUrl={currentApiSettings.webhookUrl}
taskId={currentApiSettings.taskId}
/>
{/* 操作菜单弹窗 */}
<Popup

View File

@@ -0,0 +1,601 @@
// 移动端样式
.plan-api-dialog {
background: white;
border-radius: 16px 16px 0 0;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 20px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
.header-left {
display: flex;
align-items: flex-start;
gap: 12px;
flex: 1;
}
.header-icon {
font-size: 24px;
color: #1890ff;
margin-top: 4px;
}
.header-content {
flex: 1;
h3 {
margin: 0 0 8px 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
p {
margin: 0;
font-size: 14px;
color: #666;
line-height: 1.5;
}
}
.close-btn {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #999;
background: transparent;
border: none;
cursor: pointer;
&:hover {
background: #f5f5f5;
}
}
}
.nav-tabs {
display: flex;
background: white;
border-bottom: 1px solid #f0f0f0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
.nav-tab {
flex: 1;
min-width: 80px;
padding: 12px 8px;
border: none;
background: transparent;
color: #666;
font-size: 14px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
transition: all 0.2s ease;
white-space: nowrap;
svg {
font-size: 16px;
}
&:hover {
color: #1890ff;
}
&.active {
color: #1890ff;
border-bottom: 2px solid #1890ff;
}
}
}
.dialog-content {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.dialog-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
.security-note {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #666;
svg {
color: #52c41a;
}
}
.complete-btn {
height: 36px;
padding: 0 24px;
border-radius: 18px;
}
}
// 配置内容样式
.config-content {
.config-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #333;
.section-icon {
color: #1890ff;
}
}
}
.input-group {
display: flex;
gap: 8px;
margin-bottom: 12px;
.api-input {
flex: 1;
border-radius: 8px;
}
.copy-btn {
height: 40px;
padding: 0 16px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 4px;
}
}
.security-tip {
padding: 12px;
background: #fff7e6;
border: 1px solid #ffd591;
border-radius: 8px;
font-size: 12px;
color: #d46b08;
line-height: 1.5;
}
.params-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 16px;
}
.param-section {
background: #f8f9fa;
border-radius: 8px;
padding: 12px;
border: 1px solid #e9ecef;
h4 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 600;
color: #333;
}
.param-list {
font-size: 12px;
color: #666;
line-height: 1.6;
div {
margin-bottom: 4px;
}
code {
background: #e9ecef;
padding: 2px 4px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 11px;
}
}
}
}
// 测试内容样式
.test-content {
.test-section {
h3 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.test-input {
margin-bottom: 16px;
border-radius: 8px;
}
.test-buttons {
display: flex;
gap: 12px;
.test-btn {
flex: 1;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
}
}
}
// 文档内容样式
.docs-content {
.docs-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.doc-card {
text-align: center;
padding: 24px 16px;
border-radius: 12px;
border: 1px solid #f0f0f0;
transition: all 0.2s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.doc-icon {
width: 48px;
height: 48px;
border-radius: 50%;
background: #f0f8ff;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
font-size: 24px;
color: #1890ff;
}
h4 {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
p {
margin: 0;
font-size: 14px;
color: #666;
line-height: 1.5;
}
}
}
// 代码内容样式
.code-content {
.language-tabs {
display: flex;
gap: 8px;
margin-bottom: 16px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
.lang-tab {
padding: 8px 16px;
border: 1px solid #d9d9d9;
background: white;
border-radius: 6px;
font-size: 14px;
color: #666;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
&:hover {
border-color: #1890ff;
color: #1890ff;
}
&.active {
background: #1890ff;
border-color: #1890ff;
color: white;
}
}
}
.code-block {
position: relative;
background: #f6f8fa;
border-radius: 8px;
border: 1px solid #e1e4e8;
overflow: hidden;
.code {
margin: 0;
padding: 16px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.5;
color: #24292e;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
.copy-code-btn {
position: absolute;
top: 8px;
right: 8px;
height: 32px;
padding: 0 12px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
}
}
}
// PC端样式覆盖
.plan-api-modal {
.plan-api-dialog {
border-radius: 12px;
height: auto;
max-height: 80vh;
}
.nav-tabs {
.nav-tab {
min-width: 100px;
padding: 16px 12px;
font-size: 15px;
svg {
font-size: 18px;
}
}
}
.dialog-content {
padding: 24px;
}
.params-grid {
grid-template-columns: 1fr 1fr;
}
.docs-grid {
grid-template-columns: 1fr 1fr;
}
.test-buttons {
flex-direction: row;
}
}
// 响应式设计
@media (max-width: 768px) {
.plan-api-dialog {
.header-content {
h3 {
font-size: 16px;
}
p {
font-size: 13px;
}
}
}
.nav-tabs {
.nav-tab {
font-size: 13px;
padding: 10px 6px;
svg {
font-size: 14px;
}
}
}
.dialog-content {
padding: 16px;
}
.config-content {
.params-grid {
grid-template-columns: 1fr;
gap: 8px;
}
}
.docs-content {
.docs-grid {
grid-template-columns: 1fr;
gap: 12px;
}
}
.test-content {
.test-buttons {
flex-direction: column;
gap: 8px;
}
}
.code-content {
.language-tabs {
.lang-tab {
padding: 6px 12px;
font-size: 13px;
}
}
.code-block {
.code {
font-size: 12px;
padding: 12px;
}
}
}
}
// 暗色主题支持
@media (prefers-color-scheme: dark) {
.plan-api-dialog {
background: #1f1f1f;
color: #fff;
.dialog-header {
background: #262626;
border-bottom-color: #434343;
.header-content {
h3 {
color: #fff;
}
p {
color: #a6a6a6;
}
}
}
.nav-tabs {
background: #262626;
border-bottom-color: #434343;
.nav-tab {
color: #a6a6a6;
&:hover {
color: #1890ff;
}
&.active {
color: #1890ff;
border-bottom-color: #1890ff;
}
}
}
.dialog-footer {
background: #262626;
border-top-color: #434343;
.security-note {
color: #a6a6a6;
}
}
}
.config-content {
.section-title {
color: #fff;
}
.security-tip {
background: #2a1f00;
border-color: #d48806;
color: #ffc53d;
}
.param-section {
background: #262626;
border-color: #434343;
h4 {
color: #fff;
}
.param-list {
color: #a6a6a6;
code {
background: #434343;
}
}
}
}
.test-content {
h3 {
color: #fff;
}
}
.docs-content {
.doc-card {
background: #262626;
border-color: #434343;
h4 {
color: #fff;
}
p {
color: #a6a6a6;
}
}
}
.code-content {
.code-block {
background: #0d1117;
border-color: #30363d;
.code {
color: #c9d1d9;
}
}
}
}

View File

@@ -0,0 +1,423 @@
import React, { useState } from "react";
import { Popup, Button, Toast, SpinLoading } from "antd-mobile";
import { Modal, Input, Tabs, Card, Tag, Space } from "antd";
import {
CopyOutlined,
CodeOutlined,
BookOutlined,
ThunderboltOutlined,
SettingOutlined,
LinkOutlined,
SafetyOutlined,
CheckCircleOutlined,
} from "@ant-design/icons";
import style from "./planApi.module.scss";
/**
* 计划接口配置弹窗组件
*
* 使用示例:
* ```tsx
* const [showApiDialog, setShowApiDialog] = useState(false);
* const [apiSettings, setApiSettings] = useState({
* apiKey: "your-api-key",
* webhookUrl: "https://api.example.com/webhook",
* taskId: "task-123"
* });
*
* <PlanApi
* visible={showApiDialog}
* onClose={() => setShowApiDialog(false)}
* apiKey={apiSettings.apiKey}
* webhookUrl={apiSettings.webhookUrl}
* taskId={apiSettings.taskId}
* />
* ```
*
* 特性:
* - 移动端使用 PopupPC端使用 Modal
* - 支持四个标签页:接口配置、快速测试、开发文档、代码示例
* - 支持多种编程语言的代码示例
* - 响应式设计,自适应不同屏幕尺寸
* - 支持暗色主题
*/
interface PlanApiProps {
visible: boolean;
onClose: () => void;
apiKey: string;
webhookUrl: string;
taskId: string;
}
interface ApiSettings {
apiKey: string;
webhookUrl: string;
taskId: string;
}
const PlanApi: React.FC<PlanApiProps> = ({
visible,
onClose,
apiKey,
webhookUrl,
taskId,
}) => {
const [activeTab, setActiveTab] = useState("config");
const [activeLanguage, setActiveLanguage] = useState("javascript");
const [testUrl, setTestUrl] = useState(
`${webhookUrl}?name=测试客户&phone=13800138000&source=API测试`
);
// 检测是否为移动端
const isMobile = window.innerWidth <= 768;
const handleCopy = (text: string, type: string) => {
navigator.clipboard.writeText(text);
Toast.show({
content: `${type}已复制到剪贴板`,
position: "top",
});
};
const handleTestInBrowser = () => {
window.open(testUrl, "_blank");
};
const renderConfigTab = () => (
<div className={style["config-content"]}>
{/* API密钥配置 */}
<div className={style["config-section"]}>
<div className={style["section-header"]}>
<div className={style["section-title"]}>
<CheckCircleOutlined className={style["section-icon"]} />
API密钥
</div>
<Tag color="green"></Tag>
</div>
<div className={style["input-group"]}>
<Input value={apiKey} disabled className={style["api-input"]} />
<Button
size="small"
onClick={() => handleCopy(apiKey, "API密钥")}
className={style["copy-btn"]}
>
<CopyOutlined />
</Button>
</div>
<div className={style["security-tip"]}>
<strong></strong>
API密钥使
</div>
</div>
{/* 接口地址配置 */}
<div className={style["config-section"]}>
<div className={style["section-header"]}>
<div className={style["section-title"]}>
<LinkOutlined className={style["section-icon"]} />
</div>
<Tag color="blue">POST请求</Tag>
</div>
<div className={style["input-group"]}>
<Input value={webhookUrl} disabled className={style["api-input"]} />
<Button
size="small"
onClick={() => handleCopy(webhookUrl, "接口地址")}
className={style["copy-btn"]}
>
<CopyOutlined />
</Button>
</div>
{/* 参数说明 */}
<div className={style["params-grid"]}>
<div className={style["param-section"]}>
<h4></h4>
<div className={style["param-list"]}>
<div>
<code>name</code> -
</div>
<div>
<code>phone</code> -
</div>
</div>
</div>
<div className={style["param-section"]}>
<h4></h4>
<div className={style["param-list"]}>
<div>
<code>source</code> -
</div>
<div>
<code>remark</code> -
</div>
<div>
<code>tags</code> -
</div>
</div>
</div>
</div>
</div>
</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>
</div>
</div>
</div>
);
const renderDocsTab = () => (
<div className={style["docs-content"]}>
<div className={style["docs-grid"]}>
<Card className={style["doc-card"]}>
<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>
</Card>
</div>
</div>
);
const renderCodeTab = () => {
const codeExamples = {
javascript: `fetch('${webhookUrl}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${apiKey}'
},
body: JSON.stringify({
name: '张三',
phone: '13800138000',
source: '官网表单',
})
})`,
python: `import requests
url = '${webhookUrl}'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${apiKey}'
}
data = {
'name': '张三',
'phone': '13800138000',
'source': '官网表单'
}
response = requests.post(url, json=data, headers=headers)`,
php: `<?php
$url = '${webhookUrl}';
$data = array(
'name' => '张三',
'phone' => '13800138000',
'source' => '官网表单'
);
$options = array(
'http' => array(
'header' => "Content-type: application/json\\r\\nAuthorization: Bearer ${apiKey}\\r\\n",
'method' => 'POST',
'content' => json_encode($data)
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);`,
java: `import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
String json = "{\\"name\\":\\"张三\\",\\"phone\\":\\"13800138000\\",\\"source\\":\\"官网表单\\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("${webhookUrl}"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer ${apiKey}")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());`,
};
return (
<div className={style["code-content"]}>
<div className={style["language-tabs"]}>
{Object.keys(codeExamples).map((lang) => (
<button
key={lang}
className={`${style["lang-tab"]} ${
activeLanguage === lang ? style["active"] : ""
}`}
onClick={() => setActiveLanguage(lang)}
>
{lang.charAt(0).toUpperCase() + lang.slice(1)}
</button>
))}
</div>
<div className={style["code-block"]}>
<pre className={style["code"]}>
<code>
{codeExamples[activeLanguage as keyof typeof codeExamples]}
</code>
</pre>
<Button
size="small"
onClick={() =>
handleCopy(
codeExamples[activeLanguage as keyof typeof codeExamples],
"代码"
)
}
className={style["copy-code-btn"]}
>
<CopyOutlined />
</Button>
</div>
</div>
);
};
const renderContent = () => (
<div className={style["plan-api-dialog"]}>
{/* 头部 */}
<div className={style["dialog-header"]}>
<div className={style["header-left"]}>
<CodeOutlined className={style["header-icon"]} />
<div className={style["header-content"]}>
<h3></h3>
<p>
API接口直接导入客资到该获客计划
</p>
</div>
</div>
<Button size="small" onClick={onClose} className={style["close-btn"]}>
×
</Button>
</div>
{/* 导航标签 */}
<div className={style["nav-tabs"]}>
<button
className={`${style["nav-tab"]} ${activeTab === "config" ? style["active"] : ""}`}
onClick={() => setActiveTab("config")}
>
<SettingOutlined />
</button>
<button
className={`${style["nav-tab"]} ${activeTab === "test" ? style["active"] : ""}`}
onClick={() => setActiveTab("test")}
>
<ThunderboltOutlined />
</button>
<button
className={`${style["nav-tab"]} ${activeTab === "docs" ? style["active"] : ""}`}
onClick={() => setActiveTab("docs")}
>
<BookOutlined />
</button>
<button
className={`${style["nav-tab"]} ${activeTab === "code" ? style["active"] : ""}`}
onClick={() => setActiveTab("code")}
>
<CodeOutlined />
</button>
</div>
{/* 内容区域 */}
<div className={style["dialog-content"]}>
{activeTab === "config" && renderConfigTab()}
{activeTab === "test" && renderQuickTestTab()}
{activeTab === "docs" && renderDocsTab()}
{activeTab === "code" && renderCodeTab()}
</div>
{/* 底部 */}
<div className={style["dialog-footer"]}>
<div className={style["security-note"]}>
<SafetyOutlined />
HTTPS加密
</div>
<Button
type="primary"
onClick={onClose}
className={style["complete-btn"]}
>
</Button>
</div>
</div>
);
// 移动端使用Popup
if (isMobile) {
return (
<Popup
visible={visible}
onMaskClick={onClose}
position="bottom"
bodyStyle={{ height: "90vh" }}
>
{renderContent()}
</Popup>
);
}
// PC端使用Modal
return (
<Modal
open={visible}
onCancel={onClose}
footer={null}
width={800}
centered
className={style["plan-api-modal"]}
>
{renderContent()}
</Modal>
);
};
export default PlanApi;