diff --git a/nkebao/.env.development b/nkebao/.env.development
index da6a111b..05c62ec4 100644
--- a/nkebao/.env.development
+++ b/nkebao/.env.development
@@ -1,4 +1,5 @@
# 基础环境变量示例
-VITE_API_BASE_URL=http://www.yishi.com
+# VITE_API_BASE_URL=http://www.yishi.com
+VITE_API_BASE_URL=https://ckbapi.quwanzhi.com
VITE_APP_TITLE=Nkebao Base
diff --git a/nkebao/src/pages/workspace/traffic-distribution/form/index.module.scss b/nkebao/src/pages/workspace/traffic-distribution/form/index.module.scss
index e69de29b..f54cd52f 100644
--- a/nkebao/src/pages/workspace/traffic-distribution/form/index.module.scss
+++ b/nkebao/src/pages/workspace/traffic-distribution/form/index.module.scss
@@ -0,0 +1,207 @@
+.formPage {
+}
+
+.formHeader {
+ background: #fff;
+ padding: 0 16px;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ border-bottom: 1px solid #f0f0f0;
+ position: relative;
+}
+.formTitle {
+ font-size: 18px;
+ font-weight: 600;
+ color: #222;
+ flex: 1;
+ text-align: center;
+}
+.backBtn {
+ position: absolute;
+ left: 8px;
+ top: 10px;
+ font-size: 18px;
+ color: #222;
+ border: none;
+ background: none;
+}
+.cancelBtn {
+ position: absolute;
+ right: 8px;
+ top: 10px;
+ color: #888;
+ border: none;
+ background: none;
+}
+
+.formStepsWrap {
+ background: #fff;
+ padding: 0 0 8px 0;
+}
+.formSteps {
+ padding: 0 24px;
+ margin-top: 8px;
+}
+
+.formBody {
+ background: #fff;
+ padding: 12px;
+ border-radius: 10px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.04);
+}
+.sectionTitle {
+ font-size: 17px;
+ font-weight: 600;
+ margin-bottom: 18px;
+ color: #222;
+}
+
+.accountSelectItem {
+ margin-bottom: 0 !important;
+}
+.accountListWrap {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 16px;
+ margin: 10px 0 4px 0;
+}
+.accountItem {
+ display: flex;
+ align-items: center;
+ font-size: 15px;
+ background: #f7f8fa;
+ border-radius: 6px;
+ padding: 4px 10px;
+ cursor: pointer;
+ border: 1px solid #e5e6eb;
+ transition: border 0.2s;
+}
+.accountItem input[type="checkbox"] {
+ margin-right: 6px;
+}
+.accountSelectedCount {
+ font-size: 13px;
+ color: #888;
+ margin-bottom: 8px;
+}
+
+.radioGroup {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.radioDesc {
+ font-size: 13px;
+ color: #888;
+ margin-left: 6px;
+}
+
+.sliderLabelWrap {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 2px;
+}
+.sliderValue {
+ font-size: 15px;
+ color: #222;
+ font-weight: 500;
+}
+.slider {
+ margin: 0 0 2px 0;
+}
+.sliderDesc {
+ font-size: 13px;
+ color: #888;
+ margin-bottom: 8px;
+}
+
+.timeRangeWrap {
+ display: flex;
+ gap: 24px;
+ align-items: flex-end;
+}
+.timeRangeWrap > div {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ font-size: 13px;
+ color: #888;
+}
+
+.formBlock {
+ margin-bottom: 24px;
+}
+.formLabel {
+ font-size: 15px;
+ font-weight: 500;
+ margin-bottom: 8px;
+ color: #222;
+}
+.checkboxGroup {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px 24px;
+}
+.poolListWrap {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-bottom: 8px;
+}
+.poolItem {
+ display: flex;
+ align-items: center;
+ background: #f7f8fa;
+ border-radius: 6px;
+ padding: 8px 12px;
+ border: 1px solid #e5e6eb;
+ font-size: 15px;
+ gap: 10px;
+ cursor: pointer;
+ transition: border 0.2s;
+}
+.poolItem input[type="checkbox"] {
+ margin-right: 6px;
+}
+.poolName {
+ font-weight: 500;
+ color: #222;
+}
+.poolTags {
+ font-size: 13px;
+ color: #888;
+ margin-left: 8px;
+}
+.poolCount {
+ font-size: 13px;
+ color: #888;
+ margin-left: auto;
+}
+.poolSelectedCount {
+ font-size: 13px;
+ color: #888;
+ margin-bottom: 8px;
+}
+.formStepBtns {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ margin-top: 16px;
+}
+
+// 步骤条美化
+.formSteps :global(.ant-steps-item-title) {
+ font-size: 15px;
+}
+.formSteps :global(.ant-steps-item-process .ant-steps-item-title) {
+ color: #1677ff;
+ font-weight: 600;
+}
+.formSteps :global(.ant-steps-item-finish .ant-steps-item-title) {
+ color: #222;
+}
+.formSteps :global(.ant-steps-item-wait .ant-steps-item-title) {
+ color: #888;
+}
diff --git a/nkebao/src/pages/workspace/traffic-distribution/form/index.tsx b/nkebao/src/pages/workspace/traffic-distribution/form/index.tsx
index 52e3530a..617db51b 100644
--- a/nkebao/src/pages/workspace/traffic-distribution/form/index.tsx
+++ b/nkebao/src/pages/workspace/traffic-distribution/form/index.tsx
@@ -1,3 +1,347 @@
-export default function TrafficDistributionForm() {
- return
TrafficDistributionForm
;
-}
+import React, { useState } from "react";
+import {
+ Form,
+ Input,
+ Button,
+ Radio,
+ Slider,
+ TimePicker,
+ message,
+ Checkbox,
+} from "antd";
+import { LeftOutlined } from "@ant-design/icons";
+import { useNavigate } from "react-router-dom";
+import style from "./index.module.scss";
+import StepIndicator from "@/components/StepIndicator";
+import Layout from "@/components/Layout/Layout";
+import NavCommon from "@/components/NavCommon";
+
+const accountList = [
+ { label: "客服A", value: "a" },
+ { label: "客服B", value: "b" },
+ { label: "客服C", value: "c" },
+];
+const scenarioList = [
+ { label: "海报获客", value: "poster" },
+ { label: "电话获客", value: "phone" },
+ { label: "抖音获客", value: "douyin" },
+ { label: "小红书获客", value: "xiaohongshu" },
+ { label: "微信群获客", value: "weixinqun" },
+ { label: "API获客", value: "api" },
+ { label: "订单获客", value: "order" },
+ { label: "付款码获客", value: "payment" },
+];
+const poolList = [
+ {
+ id: "pool-1",
+ name: "高价值客户池",
+ userCount: 156,
+ tags: ["高价值", "优先添加"],
+ },
+ { id: "pool-2", name: "潜在客户池", userCount: 289, tags: ["潜在客户"] },
+ { id: "pool-3", name: "新用户池", userCount: 432, tags: ["新用户"] },
+];
+
+const stepList = [
+ { id: 1, title: "基本信息", subtitle: "基本信息" },
+ { id: 2, title: "目标设置", subtitle: "目标设置" },
+ { id: 3, title: "流量池选择", subtitle: "流量池选择" },
+];
+
+const TrafficDistributionForm: React.FC = () => {
+ const [form] = Form.useForm();
+ const navigate = useNavigate();
+ const [current, setCurrent] = useState(0);
+ const [selectedAccounts, setSelectedAccounts] = useState([]);
+ const [distributeType, setDistributeType] = useState(1);
+ const [maxPerDay, setMaxPerDay] = useState(50);
+ const [timeType, setTimeType] = useState(1);
+ const [timeRange, setTimeRange] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ // 账号搜索(模拟)
+ const [accountSearch, setAccountSearch] = useState("");
+ const filteredAccounts = accountList.filter((acc) =>
+ acc.label.includes(accountSearch)
+ );
+
+ const [targetUserCount, setTargetUserCount] = useState(100);
+ const [targetTypes, setTargetTypes] = useState([]);
+ const [targetScenarios, setTargetScenarios] = useState([]);
+ const [selectedPools, setSelectedPools] = useState([]);
+ const [poolSearch, setPoolSearch] = useState("");
+
+ const handleFinish = async (values: any) => {
+ setLoading(true);
+ try {
+ // TODO: 提交接口
+ message.success("新建流量分发成功");
+ navigate(-1);
+ } catch (e) {
+ message.error("新建失败");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 步骤切换
+ const next = () => setCurrent((cur) => cur + 1);
+ const prev = () => setCurrent((cur) => cur - 1);
+
+ // 过滤流量池
+ const filteredPools = poolList.filter((pool) =>
+ pool.name.includes(poolSearch)
+ );
+
+ return (
+
+
+
+
+
+ >
+ }
+ >
+
+
+ {current === 0 && (
+
+
+
+
+ setAccountSearch(e.target.value)}
+ suffix={}
+ />
+
+ {filteredAccounts.map((acc) => (
+
+ ))}
+
+
+ 已选账号:{selectedAccounts.length} 个
+
+
+
+ setDistributeType(e.target.value)}
+ className={style.radioGroup}
+ >
+
+ 均分配{" "}
+
+ (流量将均分分配给所有客服)
+
+
+
+ 优先级分配{" "}
+
+ (按客服优先级顺序分配)
+
+
+
+ 比例分配{" "}
+
+ (按设置比例分配流量)
+
+
+
+
+
+
+ 每日最大分配量
+ {maxPerDay} 人/天
+
+
+
+ 限制每天最多分配的流量数量
+
+
+
+ setTimeType(e.target.value)}
+ className={style.radioGroup}
+ >
+ 全天分配
+ 自定义时间段
+
+
+ {timeType === 2 && (
+
+
+
+ 开始时间
+ setTimeRange([v, timeRange?.[1]])}
+ />
+
+
+ 结束时间
+ setTimeRange([timeRange?.[0], v])}
+ />
+
+
+
+ )}
+
+
+
+
+ )}
+ {current === 1 && (
+
+
目标设置
+
+
目标用户数
+
+
{targetUserCount} 人
+
+
+
+
获客场景
+
({
+ label: s.label,
+ value: s.value,
+ }))}
+ value={targetScenarios}
+ onChange={setTargetScenarios}
+ className={style.checkboxGroup}
+ />
+
+
+
+
+
+
+ )}
+ {current === 2 && (
+
+
流量池选择
+
+
setPoolSearch(e.target.value)}
+ style={{ marginBottom: 12 }}
+ />
+
+ {filteredPools.map((pool) => (
+
+ ))}
+
+
+ 已选流量池:{selectedPools.length} 个
+
+
+
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default TrafficDistributionForm;
diff --git a/nkebao/src/pages/workspace/traffic-distribution/list/api.ts b/nkebao/src/pages/workspace/traffic-distribution/list/api.ts
index a607f207..1b44626e 100644
--- a/nkebao/src/pages/workspace/traffic-distribution/list/api.ts
+++ b/nkebao/src/pages/workspace/traffic-distribution/list/api.ts
@@ -8,3 +8,21 @@ export function fetchDistributionRuleList(params: {
}): Promise {
return request("/v1/workbench/list?type=5", params, "GET");
}
+
+// 编辑计划(更新)
+export function updateDistributionRule(data: any): Promise {
+ return request("/v1/workbench/update", { ...data, type: 5 }, "POST");
+}
+
+// 暂停/启用计划
+export function toggleDistributionRuleStatus(
+ id: number,
+ status: 0 | 1
+): Promise {
+ return request("/v1/workbench/update-status", { id, status }, "POST");
+}
+
+// 删除计划
+export function deleteDistributionRule(id: number): Promise {
+ return request("/v1/workbench/delete", { id }, "POST");
+}
diff --git a/nkebao/src/pages/workspace/traffic-distribution/list/index.module.scss b/nkebao/src/pages/workspace/traffic-distribution/list/index.module.scss
index df66cbda..2e64f9a9 100644
--- a/nkebao/src/pages/workspace/traffic-distribution/list/index.module.scss
+++ b/nkebao/src/pages/workspace/traffic-distribution/list/index.module.scss
@@ -99,11 +99,15 @@
font-size: 13px;
color: #888;
margin-top: 6px;
+ align-items: center;
}
.ruleFooterIcon {
margin-right: 4px;
vertical-align: middle;
+ font-size: 15px;
+ position: relative;
+ top: -2px;
}
.empty {
diff --git a/nkebao/src/pages/workspace/traffic-distribution/list/index.tsx b/nkebao/src/pages/workspace/traffic-distribution/list/index.tsx
index 9edbf881..9b4f78dd 100644
--- a/nkebao/src/pages/workspace/traffic-distribution/list/index.tsx
+++ b/nkebao/src/pages/workspace/traffic-distribution/list/index.tsx
@@ -1,23 +1,37 @@
import React, { useEffect, useState } from "react";
import Layout from "@/components/Layout/Layout";
-import { Input, Button } from "antd";
-import NavCommon from "@/components/NavCommon";
-import { fetchDistributionRuleList } from "./api";
-import type { DistributionRule } from "./data";
-import { Tag, Pagination, Spin, message, Switch } from "antd";
import {
- EllipsisOutlined,
- UserOutlined,
- ClusterOutlined,
- AppstoreOutlined,
- BarChartOutlined,
- TeamOutlined,
+ Input,
+ Button,
+ Tag,
+ Pagination,
+ Spin,
+ message,
+ Switch,
+ Dropdown,
+ Menu,
+} from "antd";
+import NavCommon from "@/components/NavCommon";
+import {
+ fetchDistributionRuleList,
+ updateDistributionRule,
+ toggleDistributionRuleStatus,
+ deleteDistributionRule,
+} from "./api";
+import type { DistributionRule } from "./data";
+import {
+ MoreOutlined,
+ PlusOutlined,
+ ReloadOutlined,
+ EditOutlined,
+ PauseOutlined,
+ DeleteOutlined,
ClockCircleOutlined,
SearchOutlined,
- ReloadOutlined,
CalendarOutlined,
} from "@ant-design/icons";
import style from "./index.module.scss";
+import { useNavigate } from "react-router-dom";
const PAGE_SIZE = 10;
@@ -35,6 +49,9 @@ const TrafficDistributionList: React.FC = () => {
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
+ // 优化:用menuLoadingId标记当前操作的item
+ const [menuLoadingId, setMenuLoadingId] = useState(null);
+ const navigate = useNavigate();
useEffect(() => {
fetchList(page, searchQuery);
@@ -73,96 +90,204 @@ const TrafficDistributionList: React.FC = () => {
fetchList(page, searchQuery);
};
- const renderCard = (item: DistributionRule) => (
-
-
-
{item.name}
-
-
- {statusMap[item.status]?.text || "未知"}
-
-
-
-
-
-
-
-
-
- {item.config?.account?.length || 0}
+ // 优化:菜单点击事件,menuLoadingId标记当前item
+ const handleMenuClick = async (key: string, item: DistributionRule) => {
+ setMenuLoadingId(item.id);
+ try {
+ if (key === "edit") {
+ navigate(`/workspace/traffic-distribution/edit/${item.id}`);
+ } else if (key === "pause") {
+ await toggleDistributionRuleStatus(item.id, item.status === 1 ? 0 : 1);
+ message.success(item.status === 1 ? "已暂停" : "已启用");
+ handleRefresh();
+ } else if (key === "delete") {
+ await deleteDistributionRule(item.id);
+ message.success("删除成功");
+ handleRefresh();
+ }
+ } catch (e) {
+ message.error("操作失败");
+ } finally {
+ setMenuLoadingId(null);
+ }
+ };
+
+ // 新增:Switch点击切换计划状态
+ const handleSwitchChange = async (
+ checked: boolean,
+ item: DistributionRule
+ ) => {
+ setMenuLoadingId(item.id);
+ try {
+ await toggleDistributionRuleStatus(item.id, checked ? 1 : 0);
+ message.success(checked ? "已启用" : "已暂停");
+ // 本地只更新当前item的status,不刷新全列表
+ setList((prevList) =>
+ prevList.map((rule) =>
+ rule.id === item.id ? { ...rule, status: checked ? 1 : 0 } : rule
+ )
+ );
+ } catch (e) {
+ message.error("操作失败");
+ } finally {
+ setMenuLoadingId(null);
+ }
+ };
+
+ const renderCard = (item: DistributionRule) => {
+ const menu = (
+
+ );
+
+ return (
+
+
+
{item.name}
+
+
+ {statusMap[item.status]?.text || "未知"}
+
+
handleSwitchChange(checked, item)}
+ />
+ {/* Dropdown 只允许传递单一元素给 menu 属性 */}
+ ,
+ label: "编辑计划",
+ disabled: menuLoadingId === item.id,
+ },
+ {
+ key: "pause",
+ icon: ,
+ label: item.status === 1 ? "暂停计划" : "启用计划",
+ disabled: menuLoadingId === item.id,
+ },
+ {
+ key: "delete",
+ icon: ,
+ label: "删除计划",
+ disabled: menuLoadingId === item.id,
+ danger: true,
+ },
+ ],
+ onClick: ({ key }) => handleMenuClick(key, item),
+ }}
+ trigger={["click"]}
+ placement="bottomRight"
+ disabled={menuLoadingId === item.id}
+ >
+
+
-
分发账号
-
-
-
- {item.config?.devices?.length || 0}
+
+
+
+ {item.config?.account?.length || 0}
+
+
分发账号
-
分发设备
-
-
-
-
- {item.config?.pools?.length || 0}
+
+
+ {item.config?.devices?.length || 0}
+
+
分发设备
+
+
+
+ {item.config?.pools?.length || 0}
+
+
流量池
-
流量池
-
-
-
-
-
-
- {item.config?.total?.dailyAverage || 0}
+
+
+
+
+ {item.config?.total?.dailyAverage || 0}
+
+
+ 日均分发量
+
+
+
+
+ {item.config?.total?.totalUsers || 0}
+
+
+ 总流量池数量
+
+
+
+
+
+
+ 上次执行:{item.config?.lastUpdated || "-"}
-
- 日均分发量
-
-
-
-
-
- {item.config?.total?.totalUsers || 0}
+
+
+ 创建时间:{item.createTime || "-"}
-
- 总流量池数量
-
-
-
-
- 上次执行:{item.config?.lastUpdated || "-"}
-
-
-
- 创建时间:{item.createTime || "-"}
-
-
-
- );
+ );
+ };
return (
-
<>
-
+
{
+ navigate("/workspace/traffic-distribution/new");
+ }}
+ >
+ 新建分发
+
+ }
+ />
{/* 搜索栏 */}
@@ -176,13 +301,10 @@ const TrafficDistributionList: React.FC = () => {
/>
+ icon={
}
+ size="large"
+ >
>
}