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

存一版
This commit is contained in:
笔记本里的永平
2025-07-24 22:07:14 +08:00
parent 3251a1985e
commit 6f280b5816
5 changed files with 916 additions and 347 deletions

View File

@@ -0,0 +1,10 @@
import request from "@/api/request";
// 获取好友列表
export function getAccountList(params: {
page: number;
limit: number;
keyword?: string;
}) {
return request("/v1/workbench/account-list", params, "GET");
}

View File

@@ -0,0 +1,231 @@
.inputWrapper {
position: relative;
}
.inputIcon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #bdbdbd;
font-size: 20px;
}
.input {
padding-left: 38px !important;
height: 48px;
border-radius: 16px !important;
border: 1px solid #e5e6eb !important;
font-size: 16px;
background: #f8f9fa;
}
.popupContainer {
display: flex;
flex-direction: column;
height: 100vh;
background: #fff;
}
.popupHeader {
padding: 24px;
}
.popupTitle {
text-align: center;
font-size: 20px;
font-weight: 600;
margin-bottom: 24px;
}
.searchWrapper {
position: relative;
margin-bottom: 16px;
}
.searchInput {
padding-left: 40px !important;
padding-top: 8px !important;
padding-bottom: 8px !important;
border-radius: 24px !important;
border: 1px solid #e5e6eb !important;
font-size: 15px;
background: #f8f9fa;
}
.searchIcon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #bdbdbd;
font-size: 16px;
}
.clearBtn {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
height: 24px;
width: 24px;
border-radius: 50%;
min-width: 24px;
}
.friendList {
flex: 1;
overflow-y: auto;
}
.friendListInner {
border-top: 1px solid #f0f0f0;
}
.friendItem {
display: flex;
align-items: center;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: #f5f6fa;
}
}
.radioWrapper {
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.radioSelected {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #1890ff;
display: flex;
align-items: center;
justify-content: center;
}
.radioUnselected {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #e5e6eb;
display: flex;
align-items: center;
justify-content: center;
}
.radioDot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #1890ff;
}
.friendInfo {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.friendAvatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 14px;
font-weight: 500;
overflow: hidden;
}
.avatarImg {
width: 100%;
height: 100%;
object-fit: cover;
}
.friendDetail {
flex: 1;
}
.friendName {
font-weight: 500;
font-size: 16px;
color: #222;
margin-bottom: 2px;
}
.friendId {
font-size: 13px;
color: #888;
margin-bottom: 2px;
}
.friendCustomer {
font-size: 13px;
color: #bdbdbd;
}
.loadingBox {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.loadingText {
color: #888;
font-size: 15px;
}
.emptyBox {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.emptyText {
color: #888;
font-size: 15px;
}
.paginationRow {
border-top: 1px solid #f0f0f0;
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
}
.totalCount {
font-size: 14px;
color: #888;
}
.paginationControls {
display: flex;
align-items: center;
gap: 8px;
}
.pageBtn {
padding: 0 8px;
height: 32px;
min-width: 32px;
}
.pageInfo {
font-size: 14px;
color: #222;
}
.popupFooter {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-top: 1px solid #f0f0f0;
background: #fff;
}
.selectedCount {
font-size: 14px;
color: #888;
}
.footerBtnGroup {
display: flex;
gap: 12px;
}
.cancelBtn {
padding: 0 24px;
border-radius: 24px;
border: 1px solid #e5e6eb;
}
.confirmBtn {
padding: 0 24px;
border-radius: 24px;
}

View File

@@ -0,0 +1,340 @@
import React, { useState, useEffect } from "react";
import { SearchOutlined, DeleteOutlined } from "@ant-design/icons";
import { Popup } from "antd-mobile";
import { Button, Input } from "antd";
import { getAccountList } from "./api";
import style from "./index.module.scss";
import Layout from "@/components/Layout/Layout";
import PopupHeader from "@/components/PopuLayout/header";
import PopupFooter from "@/components/PopuLayout/footer";
// 账号对象类型
export interface AccountItem {
id: number;
userName: string;
realName: string;
departmentName: string;
avatar?: string;
[key: string]: any;
}
// 组件属性接口
interface AccountSelectionProps {
value: number[];
onChange: (ids: number[]) => void;
accounts?: AccountItem[];
placeholder?: string;
className?: string;
visible?: boolean;
onVisibleChange?: (visible: boolean) => void;
selectedListMaxHeight?: number;
showInput?: boolean;
showSelectedList?: boolean;
readonly?: boolean;
onConfirm?: (selectedIds: number[], selectedItems: AccountItem[]) => void;
}
export default function AccountSelection({
value,
onChange,
accounts: propAccounts = [],
placeholder = "选择账号",
className = "",
visible,
onVisibleChange,
selectedListMaxHeight = 300,
showInput = true,
showSelectedList = true,
readonly = false,
onConfirm,
}: AccountSelectionProps) {
const [popupVisible, setPopupVisible] = useState(false);
const [accountsList, setAccountsList] = useState<AccountItem[]>(propAccounts);
const [searchQuery, setSearchQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [loading, setLoading] = useState(false);
// 受控弹窗逻辑
const realVisible = visible !== undefined ? visible : popupVisible;
const setRealVisible = (v: boolean) => {
if (onVisibleChange) onVisibleChange(v);
if (visible === undefined) setPopupVisible(v);
};
// 打开弹窗时先显示弹窗再请求账号数据
const openPopup = () => {
if (readonly) return;
setCurrentPage(1);
setSearchQuery("");
setRealVisible(true);
setTimeout(async () => {
if (typeof getAccountList === "function") {
setLoading(true);
try {
const response = await getAccountList({
page: 1,
limit: 100,
keyword: "",
});
if (response && response.list) {
setAccountsList(response.list);
}
} catch (e) {
} finally {
setLoading(false);
}
}
}, 0);
};
// 搜索时请求账号数据
useEffect(() => {
if (!realVisible) return;
const timer = setTimeout(async () => {
setCurrentPage(1);
if (typeof getAccountList === "function") {
setLoading(true);
try {
const response = await getAccountList({
page: 1,
limit: 100,
keyword: searchQuery,
});
if (response && response.list) {
setAccountsList(response.list);
}
} catch (e) {
} finally {
setLoading(false);
}
}
}, 400);
return () => clearTimeout(timer);
}, [searchQuery, realVisible]);
// 渲染和过滤都依赖内部accountsList
const filteredAccounts = accountsList.filter(
(acc) =>
acc.userName.includes(searchQuery) ||
acc.realName.includes(searchQuery) ||
acc.departmentName.includes(searchQuery)
);
// 处理账号选择
const handleAccountToggle = (accountId: number) => {
if (readonly) return;
const newSelected = value.includes(accountId)
? value.filter((id) => id !== accountId)
: [...value, accountId];
onChange(newSelected);
};
// 获取显示文本
const getDisplayText = () => {
if (value.length === 0) return "";
return `已选择 ${value.length} 个账号`;
};
// 获取已选账号详细信息
const selectedAccountObjs = [
...accountsList.filter((acc) => value.includes(acc.id)),
...value
.filter((id) => !accountsList.some((acc) => acc.id === id))
.map((id) => ({
id,
userName: String(id),
realName: "",
departmentName: "",
})),
];
// 删除已选账号
const handleRemoveAccount = (id: number) => {
if (readonly) return;
onChange(value.filter((d) => d !== id));
};
// 确认选择
const handleConfirm = () => {
if (onConfirm) {
onConfirm(value, selectedAccountObjs);
}
setRealVisible(false);
};
return (
<>
{/* 输入框 */}
{showInput && (
<div className={`${style.inputWrapper} ${className}`}>
<Input
placeholder={placeholder}
value={getDisplayText()}
onClick={openPopup}
prefix={<SearchOutlined />}
allowClear={!readonly}
size="large"
readOnly={readonly}
disabled={readonly}
style={
readonly ? { background: "#f5f5f5", cursor: "not-allowed" } : {}
}
/>
</div>
)}
{/* 已选账号列表窗口 */}
{showSelectedList && selectedAccountObjs.length > 0 && (
<div
className={style.selectedListWindow}
style={{
maxHeight: selectedListMaxHeight,
overflowY: "auto",
marginTop: 8,
border: "1px solid #e5e6eb",
borderRadius: 8,
background: "#fff",
}}
>
{selectedAccountObjs.map((acc) => (
<div
key={acc.id}
className={style.selectedListRow}
style={{
display: "flex",
alignItems: "center",
padding: "4px 8px",
borderBottom: "1px solid #f0f0f0",
fontSize: 14,
}}
>
<div
style={{
flex: 1,
minWidth: 0,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{acc.userName}{acc.realName}- {acc.departmentName}
</div>
{!readonly && (
<Button
type="text"
icon={<DeleteOutlined />}
size="small"
style={{
marginLeft: 4,
color: "#ff4d4f",
border: "none",
background: "none",
minWidth: 24,
height: 24,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
onClick={() => handleRemoveAccount(acc.id)}
/>
)}
</div>
))}
</div>
)}
{/* 弹窗 */}
<Popup
visible={realVisible && !readonly}
onMaskClick={() => setRealVisible(false)}
position="bottom"
bodyStyle={{ height: "100vh" }}
>
<Layout
header={
<PopupHeader
title="选择账号"
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
searchPlaceholder="搜索账号"
loading={loading}
onRefresh={() => {}}
/>
}
footer={
<PopupFooter
total={filteredAccounts.length}
currentPage={currentPage}
totalPages={1}
loading={loading}
selectedCount={value.length}
onPageChange={setCurrentPage}
onCancel={() => setRealVisible(false)}
onConfirm={handleConfirm}
/>
}
>
<div className={style.friendList}>
{loading ? (
<div className={style.loadingBox}>
<div className={style.loadingText}>...</div>
</div>
) : filteredAccounts.length > 0 ? (
<div className={style.friendListInner}>
{filteredAccounts.map((acc) => (
<label
key={acc.id}
className={style.friendItem}
onClick={() => !readonly && handleAccountToggle(acc.id)}
>
<div className={style.radioWrapper}>
<div
className={
value.includes(acc.id)
? style.radioSelected
: style.radioUnselected
}
>
{value.includes(acc.id) && (
<div className={style.radioDot}></div>
)}
</div>
</div>
<div className={style.friendInfo}>
<div className={style.friendAvatar}>
{acc.avatar ? (
<img
src={acc.avatar}
alt={acc.userName}
className={style.avatarImg}
/>
) : (
acc.userName.charAt(0)
)}
</div>
<div className={style.friendDetail}>
<div className={style.friendName}>{acc.userName}</div>
<div className={style.friendId}>
: {acc.realName}
</div>
<div className={style.friendId}>
: {acc.departmentName}
</div>
</div>
</div>
</label>
))}
</div>
) : (
<div className={style.emptyBox}>
<div className={style.emptyText}>
{searchQuery
? `没有找到包含"${searchQuery}"的账号`
: "没有找到账号"}
</div>
</div>
)}
</div>
</Layout>
</Popup>
</>
);
}

View File

@@ -8,6 +8,7 @@ import DeviceSelection from "@/components/DeviceSelection";
import FriendSelection from "@/components/FriendSelection"; import FriendSelection from "@/components/FriendSelection";
import GroupSelection from "@/components/GroupSelection"; import GroupSelection from "@/components/GroupSelection";
import ContentLibrarySelection from "@/components/ContentLibrarySelection"; import ContentLibrarySelection from "@/components/ContentLibrarySelection";
import AccountSelection from "@/components/AccountSelection";
const ComponentTest: React.FC = () => { const ComponentTest: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -25,6 +26,8 @@ const ComponentTest: React.FC = () => {
// 内容库选择状态 // 内容库选择状态
const [selectedLibraries, setSelectedLibraries] = useState<string[]>([]); const [selectedLibraries, setSelectedLibraries] = useState<string[]>([]);
const [selectedAccounts, setSelectedAccounts] = useState<string[]>([]);
return ( return (
<Layout header={<NavCommon title="组件调试" />}> <Layout header={<NavCommon title="组件调试" />}>
<div style={{ padding: 16 }}> <div style={{ padding: 16 }}>
@@ -131,6 +134,23 @@ const ComponentTest: React.FC = () => {
</div> </div>
</div> </div>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab title="账号选择" key="accounts">
<div style={{ padding: "16px 0" }}>
<h3 style={{ marginBottom: 16 }}>AccountSelection </h3>
<AccountSelection
value={selectedAccounts}
onChange={setSelectedAccounts}
// 可根据实际API和props补充其它参数
/>
<div style={{ marginTop: 16 }}>
<strong></strong>
{selectedAccounts.length > 0
? selectedAccounts.join(", ")
: "无"}
</div>
</div>
</Tabs.Tab>
</Tabs> </Tabs>
</div> </div>
</Layout> </Layout>

View File

@@ -1,347 +1,315 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { import {
Form, Form,
Input, Input,
Button, Button,
Radio, Radio,
Slider, Slider,
TimePicker, TimePicker,
message, message,
Checkbox, Checkbox,
} from "antd"; } from "antd";
import { LeftOutlined } from "@ant-design/icons"; import { LeftOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import style from "./index.module.scss"; import style from "./index.module.scss";
import StepIndicator from "@/components/StepIndicator"; import StepIndicator from "@/components/StepIndicator";
import Layout from "@/components/Layout/Layout"; import Layout from "@/components/Layout/Layout";
import NavCommon from "@/components/NavCommon"; import NavCommon from "@/components/NavCommon";
import AccountSelection from "@/components/AccountSelection";
const accountList = [ import { getAccountList } from "@/components/AccountSelection/api";
{ label: "客服A", value: "a" },
{ label: "客服B", value: "b" }, const scenarioList = [
{ label: "客服C", value: "c" }, { label: "海报获客", value: "poster" },
]; { label: "电话获客", value: "phone" },
const scenarioList = [ { label: "抖音获客", value: "douyin" },
{ label: "海报获客", value: "poster" }, { label: "小红书获客", value: "xiaohongshu" },
{ label: "电话获客", value: "phone" }, { label: "微信群获客", value: "weixinqun" },
{ label: "抖音获客", value: "douyin" }, { label: "API获客", value: "api" },
{ label: "小红书获客", value: "xiaohongshu" }, { label: "订单获客", value: "order" },
{ label: "微信群获客", value: "weixinqun" }, { label: "付款码获客", value: "payment" },
{ label: "API获客", value: "api" }, ];
{ label: "订单获客", value: "order" }, const poolList = [
{ label: "付款码获客", value: "payment" }, {
]; id: "pool-1",
const poolList = [ name: "高价值客户池",
{ userCount: 156,
id: "pool-1", tags: ["高价值", "优先添加"],
name: "高价值客户池", },
userCount: 156, { id: "pool-2", name: "潜在客户池", userCount: 289, tags: ["潜在客户"] },
tags: ["高价值", "优先添加"], { id: "pool-3", name: "新用户池", userCount: 432, 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: "目标设置" },
const stepList = [ { id: 3, title: "流量池选择", subtitle: "流量池选择" },
{ id: 1, title: "基本信息", subtitle: "基本信息" }, ];
{ id: 2, title: "目标设置", subtitle: "目标设置" },
{ id: 3, title: "流量池选择", subtitle: "流量池选择" }, const TrafficDistributionForm: React.FC = () => {
]; const [form] = Form.useForm();
const navigate = useNavigate();
const TrafficDistributionForm: React.FC = () => { const [current, setCurrent] = useState(0);
const [form] = Form.useForm(); const [selectedAccountIds, setSelectedAccountIds] = useState<number[]>([]);
const navigate = useNavigate(); const [distributeType, setDistributeType] = useState(1);
const [current, setCurrent] = useState(0); const [maxPerDay, setMaxPerDay] = useState(50);
const [selectedAccounts, setSelectedAccounts] = useState<string[]>([]); const [timeType, setTimeType] = useState(1);
const [distributeType, setDistributeType] = useState(1); const [timeRange, setTimeRange] = useState<any>(null);
const [maxPerDay, setMaxPerDay] = useState(50); const [loading, setLoading] = useState(false);
const [timeType, setTimeType] = useState(1);
const [timeRange, setTimeRange] = useState<any>(null); const [targetUserCount, setTargetUserCount] = useState(100);
const [loading, setLoading] = useState(false); const [targetTypes, setTargetTypes] = useState<string[]>([]);
const [targetScenarios, setTargetScenarios] = useState<string[]>([]);
// 账号搜索(模拟) const [selectedPools, setSelectedPools] = useState<string[]>([]);
const [accountSearch, setAccountSearch] = useState(""); const [poolSearch, setPoolSearch] = useState("");
const filteredAccounts = accountList.filter((acc) =>
acc.label.includes(accountSearch) const handleFinish = async (values: any) => {
); setLoading(true);
try {
const [targetUserCount, setTargetUserCount] = useState(100); // TODO: 提交接口
const [targetTypes, setTargetTypes] = useState<string[]>([]); message.success("新建流量分发成功");
const [targetScenarios, setTargetScenarios] = useState<string[]>([]); navigate(-1);
const [selectedPools, setSelectedPools] = useState<string[]>([]); } catch (e) {
const [poolSearch, setPoolSearch] = useState(""); message.error("新建失败");
} finally {
const handleFinish = async (values: any) => { setLoading(false);
setLoading(true); }
try { };
// TODO: 提交接口
message.success("新建流量分发成功"); // 步骤切换
navigate(-1); const next = () => setCurrent((cur) => cur + 1);
} catch (e) { const prev = () => setCurrent((cur) => cur - 1);
message.error("新建失败");
} finally { // 过滤流量池
setLoading(false); const filteredPools = poolList.filter((pool) =>
} pool.name.includes(poolSearch)
}; );
// 步骤切换 return (
const next = () => setCurrent((cur) => cur + 1); <Layout
const prev = () => setCurrent((cur) => cur - 1); header={
<>
// 过滤流量池 <NavCommon title="新建流量分发" />
const filteredPools = poolList.filter((pool) => <div className={style.formStepsWrap}>
pool.name.includes(poolSearch) <StepIndicator currentStep={current + 1} steps={stepList} />
); </div>
</>
return ( }
<Layout >
header={ <div className={style.formPage}>
<> <div className={style.formBody}>
<NavCommon title="新建流量分发" /> {current === 0 && (
<div className={style.formStepsWrap}> <Form
<StepIndicator currentStep={current + 1} steps={stepList} /> form={form}
</div> layout="vertical"
</> onFinish={() => next()}
} initialValues={{ distributeType: 1, maxPerDay: 50, timeType: 1 }}
> >
<div className={style.formPage}> <div className={style.sectionTitle}></div>
<div className={style.formBody}> <Form.Item
{current === 0 && ( label="计划名称"
<Form name="name"
form={form} rules={[{ required: true, message: "请输入计划名称" }]}
layout="vertical" >
onFinish={() => next()} <Input placeholder="流量分发 20250724 1700" maxLength={30} />
initialValues={{ distributeType: 1, maxPerDay: 50, timeType: 1 }} </Form.Item>
> <Form.Item
<div className={style.sectionTitle}></div> label="选择账号"
<Form.Item required
label="计划名称" className={style.accountSelectItem}
name="name" >
rules={[{ required: true, message: "请输入计划名称" }]} <AccountSelection
> value={selectedAccountIds}
<Input placeholder="流量分发 20250724 1700" maxLength={30} /> onChange={setSelectedAccountIds}
</Form.Item> />
<Form.Item </Form.Item>
label="选择账号" <Form.Item label="分配方式" name="distributeType" required>
required <Radio.Group
className={style.accountSelectItem} value={distributeType}
> onChange={(e) => setDistributeType(e.target.value)}
<Input className={style.radioGroup}
placeholder="请选择账号" >
value={accountSearch} <Radio value={1}>
onChange={(e) => setAccountSearch(e.target.value)} {" "}
suffix={<span className={style.accountSearchIcon} />} <span className={style.radioDesc}>
/> ()
<div className={style.accountListWrap}> </span>
{filteredAccounts.map((acc) => ( </Radio>
<label key={acc.value} className={style.accountItem}> <Radio value={2}>
<input {" "}
type="checkbox" <span className={style.radioDesc}>
checked={selectedAccounts.includes(acc.value)} ()
onChange={(e) => { </span>
setSelectedAccounts((val) => </Radio>
e.target.checked <Radio value={3}>
? [...val, acc.value] {" "}
: val.filter((v) => v !== acc.value) <span className={style.radioDesc}>
); ()
}} </span>
/> </Radio>
<span>{acc.label}</span> </Radio.Group>
</label> </Form.Item>
))} <Form.Item label="分配限制" required>
</div> <div className={style.sliderLabelWrap}>
<div className={style.accountSelectedCount}> <span></span>
<span>{selectedAccounts.length}</span> <span className={style.sliderValue}>{maxPerDay} /</span>
</div> </div>
</Form.Item> <Slider
<Form.Item label="分配方式" name="distributeType" required> min={1}
<Radio.Group max={100}
value={distributeType} value={maxPerDay}
onChange={(e) => setDistributeType(e.target.value)} onChange={setMaxPerDay}
className={style.radioGroup} className={style.slider}
> />
<Radio value={1}> <div className={style.sliderDesc}>
{" "}
<span className={style.radioDesc}> </div>
() </Form.Item>
</span> <Form.Item label="时间限制" name="timeType" required>
</Radio> <Radio.Group
<Radio value={2}> value={timeType}
{" "} onChange={(e) => setTimeType(e.target.value)}
<span className={style.radioDesc}> className={style.radioGroup}
() >
</span> <Radio value={1}></Radio>
</Radio> <Radio value={2}></Radio>
<Radio value={3}> </Radio.Group>
{" "} </Form.Item>
<span className={style.radioDesc}> {timeType === 2 && (
() <Form.Item label="" required>
</span> <div className={style.timeRangeWrap}>
</Radio> <div>
</Radio.Group> <span></span>
</Form.Item> <TimePicker
<Form.Item label="分配限制" required> format="HH:mm"
<div className={style.sliderLabelWrap}> style={{ width: 120 }}
<span></span> value={timeRange?.[0]}
<span className={style.sliderValue}>{maxPerDay} /</span> onChange={(v) => setTimeRange([v, timeRange?.[1]])}
</div> />
<Slider </div>
min={1} <div>
max={100} <span></span>
value={maxPerDay} <TimePicker
onChange={setMaxPerDay} format="HH:mm"
className={style.slider} style={{ width: 120 }}
/> value={timeRange?.[1]}
<div className={style.sliderDesc}> onChange={(v) => setTimeRange([timeRange?.[0], v])}
/>
</div> </div>
</Form.Item> </div>
<Form.Item label="时间限制" name="timeType" required> </Form.Item>
<Radio.Group )}
value={timeType} <Form.Item>
onChange={(e) => setTimeType(e.target.value)} <Button
className={style.radioGroup} type="primary"
> htmlType="submit"
<Radio value={1}></Radio> loading={loading}
<Radio value={2}></Radio> block
</Radio.Group> >
</Form.Item>
{timeType === 2 && ( </Button>
<Form.Item label="" required> </Form.Item>
<div className={style.timeRangeWrap}> </Form>
<div> )}
<span></span> {current === 1 && (
<TimePicker <div>
format="HH:mm" <div className={style.sectionTitle}></div>
style={{ width: 120 }} <div className={style.formBlock}>
value={timeRange?.[0]} <div className={style.formLabel}></div>
onChange={(v) => setTimeRange([v, timeRange?.[1]])} <Slider
/> min={1}
</div> max={1000}
<div> value={targetUserCount}
<span></span> onChange={setTargetUserCount}
<TimePicker className={style.slider}
format="HH:mm" />
style={{ width: 120 }} <div className={style.sliderValue}>{targetUserCount} </div>
value={timeRange?.[1]} </div>
onChange={(v) => setTimeRange([timeRange?.[0], v])} <div className={style.formBlock}>
/> <div className={style.formLabel}></div>
</div> <Checkbox.Group
</div> options={["高价值客户", "新用户", "潜在客户", "流失预警"]}
</Form.Item> value={targetTypes}
)} onChange={setTargetTypes}
<Form.Item> className={style.checkboxGroup}
<Button />
type="primary" </div>
htmlType="submit" <div className={style.formBlock}>
loading={loading} <div className={style.formLabel}></div>
block <Checkbox.Group
> options={scenarioList.map((s) => ({
label: s.label,
</Button> value: s.value,
</Form.Item> }))}
</Form> value={targetScenarios}
)} onChange={setTargetScenarios}
{current === 1 && ( className={style.checkboxGroup}
<div> />
<div className={style.sectionTitle}></div> </div>
<div className={style.formBlock}> <div className={style.formStepBtns}>
<div className={style.formLabel}></div> <Button onClick={prev} style={{ marginRight: 12 }}>
<Slider
min={1} </Button>
max={1000} <Button type="primary" onClick={next}>
value={targetUserCount}
onChange={setTargetUserCount} </Button>
className={style.slider} </div>
/> </div>
<div className={style.sliderValue}>{targetUserCount} </div> )}
</div> {current === 2 && (
<div className={style.formBlock}> <div>
<div className={style.formLabel}></div> <div className={style.sectionTitle}></div>
<Checkbox.Group <div className={style.formBlock}>
options={["高价值客户", "新用户", "潜在客户", "流失预警"]} <Input
value={targetTypes} placeholder="搜索流量池"
onChange={setTargetTypes} value={poolSearch}
className={style.checkboxGroup} onChange={(e) => setPoolSearch(e.target.value)}
/> style={{ marginBottom: 12 }}
</div> />
<div className={style.formBlock}> <div className={style.poolListWrap}>
<div className={style.formLabel}></div> {filteredPools.map((pool) => (
<Checkbox.Group <label key={pool.id} className={style.poolItem}>
options={scenarioList.map((s) => ({ <input
label: s.label, type="checkbox"
value: s.value, checked={selectedPools.includes(pool.id)}
}))} onChange={(e) => {
value={targetScenarios} setSelectedPools((val) =>
onChange={setTargetScenarios} e.target.checked
className={style.checkboxGroup} ? [...val, pool.id]
/> : val.filter((v) => v !== pool.id)
</div> );
<div className={style.formStepBtns}> }}
<Button onClick={prev} style={{ marginRight: 12 }}> />
<span className={style.poolName}>{pool.name}</span>
</Button> <span className={style.poolTags}>
<Button type="primary" onClick={next}> {pool.tags.join("/")}
</span>
</Button> <span className={style.poolCount}>
</div> {pool.userCount}
</div> </span>
)} </label>
{current === 2 && ( ))}
<div> </div>
<div className={style.sectionTitle}></div> <div className={style.poolSelectedCount}>
<div className={style.formBlock}> <span>{selectedPools.length}</span>
<Input </div>
placeholder="搜索流量池" </div>
value={poolSearch} <div className={style.formStepBtns}>
onChange={(e) => setPoolSearch(e.target.value)} <Button onClick={prev} style={{ marginRight: 12 }}>
style={{ marginBottom: 12 }}
/> </Button>
<div className={style.poolListWrap}> <Button
{filteredPools.map((pool) => ( type="primary"
<label key={pool.id} className={style.poolItem}> onClick={() => message.success("提交成功")}
<input >
type="checkbox"
checked={selectedPools.includes(pool.id)} </Button>
onChange={(e) => { </div>
setSelectedPools((val) => </div>
e.target.checked )}
? [...val, pool.id] </div>
: val.filter((v) => v !== pool.id) </div>
); </Layout>
}} );
/> };
<span className={style.poolName}>{pool.name}</span>
<span className={style.poolTags}> export default TrafficDistributionForm;
{pool.tags.join("/")}
</span>
<span className={style.poolCount}>
{pool.userCount}
</span>
</label>
))}
</div>
<div className={style.poolSelectedCount}>
<span>{selectedPools.length}</span>
</div>
</div>
<div className={style.formStepBtns}>
<Button onClick={prev} style={{ marginRight: 12 }}>
</Button>
<Button
type="primary"
onClick={() => message.success("提交成功")}
>
</Button>
</div>
</div>
)}
</div>
</div>
</Layout>
);
};
export default TrafficDistributionForm;