feat(流量池选择): 新增流量池选择组件及相关API和类型定义
添加流量池选择组件,包含以下功能: - 流量池列表获取API - 流量池项和组件属性类型定义 - 选择弹窗组件实现 - 样式文件和测试页面集成 - 支持搜索、分页和多选功能
This commit is contained in:
15
Cunkebao/src/components/PoolSelection/api.ts
Normal file
15
Cunkebao/src/components/PoolSelection/api.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import request from "@/api/request";
|
||||
|
||||
// 获取流量池列表
|
||||
export function getPoolList(params: {
|
||||
page?: string;
|
||||
pageSize?: string;
|
||||
keyword?: string;
|
||||
addStatus?: string;
|
||||
deviceId?: string;
|
||||
packageId?: string;
|
||||
userValue?: string;
|
||||
[property: string]: any;
|
||||
}) {
|
||||
return request("/v1/traffic/pool", params, "GET");
|
||||
}
|
||||
50
Cunkebao/src/components/PoolSelection/data.ts
Normal file
50
Cunkebao/src/components/PoolSelection/data.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// 流量池接口类型
|
||||
export interface PoolItem {
|
||||
id: number;
|
||||
identifier: string;
|
||||
mobile: string;
|
||||
wechatId: string;
|
||||
fromd: string;
|
||||
status: number;
|
||||
createTime: string;
|
||||
companyId: number;
|
||||
sourceId: string;
|
||||
type: number;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
gender: number;
|
||||
phone: string;
|
||||
alias: string;
|
||||
packages: any[];
|
||||
tags: any[];
|
||||
}
|
||||
|
||||
export interface GroupSelectionItem {
|
||||
id: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
wechatId?: string;
|
||||
mobile?: string;
|
||||
nickname?: string;
|
||||
createTime?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 组件属性接口
|
||||
export interface GroupSelectionProps {
|
||||
selectedOptions: GroupSelectionItem[];
|
||||
onSelect: (groups: GroupSelectionItem[]) => void;
|
||||
onSelectDetail?: (groups: PoolItem[]) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
visible?: boolean;
|
||||
onVisibleChange?: (visible: boolean) => void;
|
||||
selectedListMaxHeight?: number;
|
||||
showInput?: boolean;
|
||||
showSelectedList?: boolean;
|
||||
readonly?: boolean;
|
||||
onConfirm?: (
|
||||
selectedIds: string[],
|
||||
selectedItems: GroupSelectionItem[],
|
||||
) => void;
|
||||
}
|
||||
206
Cunkebao/src/components/PoolSelection/index.module.scss
Normal file
206
Cunkebao/src/components/PoolSelection/index.module.scss
Normal file
@@ -0,0 +1,206 @@
|
||||
.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;
|
||||
}
|
||||
.selectedListRow {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.selectedListRowContent {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.selectedListRowContentText {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.groupList {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.groupListInner {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
.groupItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background 0.2s;
|
||||
&:hover {
|
||||
background: #f5f6fa;
|
||||
}
|
||||
}
|
||||
.groupInfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
.groupAvatar {
|
||||
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;
|
||||
}
|
||||
.groupDetail {
|
||||
flex: 1;
|
||||
}
|
||||
.groupName {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #222;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.groupId {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.groupOwner {
|
||||
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;
|
||||
}
|
||||
126
Cunkebao/src/components/PoolSelection/index.tsx
Normal file
126
Cunkebao/src/components/PoolSelection/index.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import React, { useState } from "react";
|
||||
import { SearchOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
import { Button, Input } from "antd";
|
||||
import { Avatar } from "antd-mobile";
|
||||
import style from "./index.module.scss";
|
||||
import SelectionPopup from "./selectionPopup";
|
||||
import { GroupSelectionProps } from "./data";
|
||||
export default function PoolSelection({
|
||||
selectedOptions,
|
||||
onSelect,
|
||||
onSelectDetail,
|
||||
placeholder = "选择流量池",
|
||||
className = "",
|
||||
visible,
|
||||
onVisibleChange,
|
||||
selectedListMaxHeight = 300,
|
||||
showInput = true,
|
||||
showSelectedList = true,
|
||||
readonly = false,
|
||||
onConfirm,
|
||||
}: GroupSelectionProps) {
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
|
||||
// 删除已选流量池项
|
||||
const handleRemoveItem = (id: string) => {
|
||||
if (readonly) return;
|
||||
onSelect(selectedOptions.filter(item => item.id !== id));
|
||||
};
|
||||
|
||||
// 受控弹窗逻辑
|
||||
const realVisible = visible !== undefined ? visible : popupVisible;
|
||||
const setRealVisible = (v: boolean) => {
|
||||
if (onVisibleChange) onVisibleChange(v);
|
||||
if (visible === undefined) setPopupVisible(v);
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const openPopup = () => {
|
||||
if (readonly) return;
|
||||
setRealVisible(true);
|
||||
};
|
||||
|
||||
// 获取显示文本
|
||||
const getDisplayText = () => {
|
||||
if (selectedOptions.length === 0) return "";
|
||||
return `已选择 ${selectedOptions.length} 个流量池项`;
|
||||
};
|
||||
|
||||
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 && selectedOptions.length > 0 && (
|
||||
<div
|
||||
className={style.selectedListWindow}
|
||||
style={{
|
||||
maxHeight: selectedListMaxHeight,
|
||||
overflowY: "auto",
|
||||
marginTop: 8,
|
||||
border: "1px solid #e5e6eb",
|
||||
borderRadius: 8,
|
||||
background: "#fff",
|
||||
}}
|
||||
>
|
||||
{selectedOptions.map(item => (
|
||||
<div key={item.id} className={style.selectedListRow}>
|
||||
<div className={style.selectedListRowContent}>
|
||||
<Avatar src={item.avatar} />
|
||||
<div className={style.selectedListRowContentText}>
|
||||
<div>{item.nickname || item.name}</div>
|
||||
<div>{item.wechatId || item.mobile}</div>
|
||||
</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={() => handleRemoveItem(item.id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* 弹窗 */}
|
||||
<SelectionPopup
|
||||
visible={realVisible}
|
||||
onVisibleChange={setRealVisible}
|
||||
selectedOptions={selectedOptions}
|
||||
onSelect={onSelect}
|
||||
onSelectDetail={onSelectDetail}
|
||||
readonly={readonly}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
227
Cunkebao/src/components/PoolSelection/selectionPopup.tsx
Normal file
227
Cunkebao/src/components/PoolSelection/selectionPopup.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Popup, Checkbox } from "antd-mobile";
|
||||
|
||||
import { getPoolList } 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";
|
||||
import { GroupSelectionItem, PoolItem } from "./data";
|
||||
|
||||
// 弹窗属性接口
|
||||
interface SelectionPopupProps {
|
||||
visible: boolean;
|
||||
onVisibleChange: (visible: boolean) => void;
|
||||
selectedOptions: GroupSelectionItem[];
|
||||
onSelect: (items: GroupSelectionItem[]) => void;
|
||||
onSelectDetail?: (items: PoolItem[]) => void;
|
||||
readonly?: boolean;
|
||||
onConfirm?: (
|
||||
selectedIds: string[],
|
||||
selectedItems: GroupSelectionItem[],
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function SelectionPopup({
|
||||
visible,
|
||||
onVisibleChange,
|
||||
selectedOptions,
|
||||
onSelect,
|
||||
onSelectDetail,
|
||||
readonly = false,
|
||||
onConfirm,
|
||||
}: SelectionPopupProps) {
|
||||
const [poolItems, setPoolItems] = useState<PoolItem[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [totalItems, setTotalItems] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 获取流量池列表API
|
||||
const fetchPoolItems = async (page: number, keyword: string = "") => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: any = {
|
||||
page: String(page),
|
||||
pageSize: "20",
|
||||
};
|
||||
|
||||
if (keyword.trim()) {
|
||||
params.keyword = keyword.trim();
|
||||
}
|
||||
|
||||
const response = await getPoolList(params);
|
||||
if (response && response.list) {
|
||||
setPoolItems(response.list);
|
||||
setTotalItems(response.total || 0);
|
||||
setTotalPages(Math.ceil((response.total || 0) / 20));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取流量池列表失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理流量池项选择
|
||||
const handleItemToggle = (item: PoolItem) => {
|
||||
if (readonly) return;
|
||||
|
||||
// 将PoolItem转换为GroupSelectionItem格式
|
||||
const selectionItem: GroupSelectionItem = {
|
||||
id: String(item.id),
|
||||
name: item.nickname || item.wechatId,
|
||||
avatar: item.avatar,
|
||||
wechatId: item.wechatId,
|
||||
mobile: item.mobile,
|
||||
nickname: item.nickname,
|
||||
createTime: item.createTime,
|
||||
// 保留原始数据
|
||||
originalData: item,
|
||||
};
|
||||
|
||||
const newSelectedItems = selectedOptions.some(g => g.id === String(item.id))
|
||||
? selectedOptions.filter(g => g.id !== String(item.id))
|
||||
: selectedOptions.concat(selectionItem);
|
||||
|
||||
onSelect(newSelectedItems);
|
||||
|
||||
// 如果有 onSelectDetail 回调,传递完整的流量池对象
|
||||
if (onSelectDetail) {
|
||||
const selectedItemObjs = poolItems.filter(poolItem =>
|
||||
newSelectedItems.some(g => g.id === String(poolItem.id)),
|
||||
);
|
||||
onSelectDetail(selectedItemObjs);
|
||||
}
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const handleConfirm = () => {
|
||||
if (onConfirm) {
|
||||
onConfirm(
|
||||
selectedOptions.map(item => item.id),
|
||||
selectedOptions,
|
||||
);
|
||||
}
|
||||
onVisibleChange(false);
|
||||
};
|
||||
|
||||
// 弹窗打开时初始化数据(只执行一次)
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setCurrentPage(1);
|
||||
setSearchQuery("");
|
||||
fetchPoolItems(1, "");
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
// 搜索防抖(只在弹窗打开且搜索词变化时执行)
|
||||
useEffect(() => {
|
||||
if (!visible || searchQuery === "") return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setCurrentPage(1);
|
||||
fetchPoolItems(1, searchQuery);
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchQuery, visible]);
|
||||
|
||||
// 页码变化时请求数据(只在弹窗打开且页码不是1时执行)
|
||||
useEffect(() => {
|
||||
if (!visible || currentPage === 1) return;
|
||||
fetchPoolItems(currentPage, searchQuery);
|
||||
}, [currentPage, visible, searchQuery]);
|
||||
|
||||
return (
|
||||
<Popup
|
||||
visible={visible}
|
||||
onMaskClick={() => onVisibleChange(false)}
|
||||
position="bottom"
|
||||
bodyStyle={{ height: "100vh" }}
|
||||
>
|
||||
<Layout
|
||||
header={
|
||||
<PopupHeader
|
||||
title="选择流量池"
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
searchPlaceholder="搜索流量池"
|
||||
loading={loading}
|
||||
onRefresh={() => fetchPoolItems(currentPage, searchQuery)}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<PopupFooter
|
||||
total={totalItems}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
loading={loading}
|
||||
selectedCount={selectedOptions.length}
|
||||
onPageChange={setCurrentPage}
|
||||
onCancel={() => onVisibleChange(false)}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={style.groupList}>
|
||||
{loading ? (
|
||||
<div className={style.loadingBox}>
|
||||
<div className={style.loadingText}>加载中...</div>
|
||||
</div>
|
||||
) : poolItems.length > 0 ? (
|
||||
<div className={style.groupListInner}>
|
||||
{poolItems.map(item => (
|
||||
<div key={item.id} className={style.groupItem}>
|
||||
<Checkbox
|
||||
checked={selectedOptions.some(
|
||||
g => g.id === String(item.id),
|
||||
)}
|
||||
onChange={() => !readonly && handleItemToggle(item)}
|
||||
disabled={readonly}
|
||||
style={{ marginRight: 12 }}
|
||||
/>
|
||||
<div className={style.groupInfo}>
|
||||
<div className={style.groupAvatar}>
|
||||
{item.avatar ? (
|
||||
<img
|
||||
src={item.avatar}
|
||||
alt={item.nickname || item.wechatId}
|
||||
className={style.avatarImg}
|
||||
/>
|
||||
) : (
|
||||
(item.nickname || item.wechatId || "").charAt(0)
|
||||
)}
|
||||
</div>
|
||||
<div className={style.groupDetail}>
|
||||
<div className={style.groupName}>
|
||||
{item.nickname || item.wechatId}
|
||||
</div>
|
||||
<div className={style.groupId}>
|
||||
微信ID: {item.wechatId}
|
||||
</div>
|
||||
{item.mobile && (
|
||||
<div className={style.groupOwner}>
|
||||
手机号: {item.mobile}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={style.emptyBox}>
|
||||
<div className={style.emptyText}>
|
||||
{searchQuery
|
||||
? `没有找到包含"${searchQuery}"的流量池项`
|
||||
: "没有找到流量池项"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
@@ -7,14 +7,16 @@ import FriendSelection from "@/components/FriendSelection";
|
||||
import GroupSelection from "@/components/GroupSelection";
|
||||
import ContentSelection from "@/components/ContentSelection";
|
||||
import AccountSelection from "@/components/AccountSelection";
|
||||
import PoolSelection from "@/components/PoolSelection";
|
||||
import { isDevelopment } from "@/utils/env";
|
||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { AccountItem } from "@/components/AccountSelection/data";
|
||||
import { GroupSelectionItem as PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
const ComponentTest: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState("devices");
|
||||
const [activeTab, setActiveTab] = useState("pools");
|
||||
|
||||
// 设备选择状态
|
||||
const [selectedDevices, setSelectedDevices] = useState<DeviceSelectionItem[]>(
|
||||
@@ -34,6 +36,9 @@ const ComponentTest: React.FC = () => {
|
||||
const [selectedFriendsOptions, setSelectedFriendsOptions] = useState<
|
||||
FriendSelectionItem[]
|
||||
>([]);
|
||||
|
||||
// 流量池选择状态
|
||||
const [selectedPools, setSelectedPools] = useState<PoolSelectionItem[]>([]);
|
||||
return (
|
||||
<Layout header={<NavCommon title="组件调试" />}>
|
||||
<div style={{ padding: 16 }}>
|
||||
@@ -155,6 +160,32 @@ const ComponentTest: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="流量池选择" key="pools">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>PoolSelection 组件测试</h3>
|
||||
<PoolSelection
|
||||
selectedOptions={selectedPools}
|
||||
onSelect={setSelectedPools}
|
||||
placeholder="请选择流量池"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<strong>已选流量池:</strong> {selectedPools.length} 个
|
||||
<br />
|
||||
<strong>流量池ID:</strong>{" "}
|
||||
{selectedPools.map(p => p.id).join(", ") || "无"}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user