Merge branch 'yongpxu-dev' into yongpxu-dev2
This commit is contained in:
26
Cunkebao/dist/.vite/manifest.json
vendored
26
Cunkebao/dist/.vite/manifest.json
vendored
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"_charts-CLRTJ7Uf.js": {
|
||||
"file": "assets/charts-CLRTJ7Uf.js",
|
||||
"_charts-DRkEUjsu.js": {
|
||||
"file": "assets/charts-DRkEUjsu.js",
|
||||
"name": "charts",
|
||||
"imports": [
|
||||
"_ui-BFvqeNzU.js",
|
||||
"_ui-ltFujOOi.js",
|
||||
"_vendor-2vc8h_ct.js"
|
||||
]
|
||||
},
|
||||
"_ui-BFvqeNzU.js": {
|
||||
"file": "assets/ui-BFvqeNzU.js",
|
||||
"_ui-D0C0OGrH.css": {
|
||||
"file": "assets/ui-D0C0OGrH.css",
|
||||
"src": "_ui-D0C0OGrH.css"
|
||||
},
|
||||
"_ui-ltFujOOi.js": {
|
||||
"file": "assets/ui-ltFujOOi.js",
|
||||
"name": "ui",
|
||||
"imports": [
|
||||
"_vendor-2vc8h_ct.js"
|
||||
@@ -17,10 +21,6 @@
|
||||
"assets/ui-D0C0OGrH.css"
|
||||
]
|
||||
},
|
||||
"_ui-D0C0OGrH.css": {
|
||||
"file": "assets/ui-D0C0OGrH.css",
|
||||
"src": "_ui-D0C0OGrH.css"
|
||||
},
|
||||
"_utils-6WF66_dS.js": {
|
||||
"file": "assets/utils-6WF66_dS.js",
|
||||
"name": "utils",
|
||||
@@ -33,18 +33,18 @@
|
||||
"name": "vendor"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/index-C48GlG01.js",
|
||||
"file": "assets/index-DV2QF8R9.js",
|
||||
"name": "index",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_vendor-2vc8h_ct.js",
|
||||
"_ui-BFvqeNzU.js",
|
||||
"_ui-ltFujOOi.js",
|
||||
"_utils-6WF66_dS.js",
|
||||
"_charts-CLRTJ7Uf.js"
|
||||
"_charts-DRkEUjsu.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/index-Ta4vyxDJ.css"
|
||||
"assets/index-DqyE1UEk.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
8
Cunkebao/dist/index.html
vendored
8
Cunkebao/dist/index.html
vendored
@@ -11,13 +11,13 @@
|
||||
</style>
|
||||
<!-- 引入 uni-app web-view SDK(必须) -->
|
||||
<script type="text/javascript" src="/websdk.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-C48GlG01.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DV2QF8R9.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-2vc8h_ct.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ui-BFvqeNzU.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ui-ltFujOOi.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/utils-6WF66_dS.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-CLRTJ7Uf.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-DRkEUjsu.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/ui-D0C0OGrH.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Ta4vyxDJ.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DqyE1UEk.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
34
Cunkebao/src/components/PoolSelection/api.ts
Normal file
34
Cunkebao/src/components/PoolSelection/api.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import request from "@/api/request";
|
||||
|
||||
// 请求参数接口
|
||||
export interface Request {
|
||||
keyword: string;
|
||||
/**
|
||||
* 条数
|
||||
*/
|
||||
limit: string;
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
page: string;
|
||||
[property: string]: any;
|
||||
}
|
||||
|
||||
// 获取流量池包列表
|
||||
export function getPoolPackages(params: Request) {
|
||||
return request("/v1/traffic/pool/getPackage", params, "GET");
|
||||
}
|
||||
|
||||
// 保留原接口以兼容现有代码
|
||||
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");
|
||||
}
|
||||
61
Cunkebao/src/components/PoolSelection/data.ts
Normal file
61
Cunkebao/src/components/PoolSelection/data.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// 流量池包接口类型
|
||||
export interface PoolPackageItem {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
createTime: string;
|
||||
num: number;
|
||||
}
|
||||
|
||||
// 原流量池接口类型(保留以兼容现有代码)
|
||||
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 PoolSelectionItem {
|
||||
id: string;
|
||||
avatar?: string;
|
||||
name: string;
|
||||
wechatId?: string;
|
||||
mobile?: string;
|
||||
nickname?: string;
|
||||
createTime?: string;
|
||||
description?: string;
|
||||
num?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 组件属性接口
|
||||
export interface PoolSelectionProps {
|
||||
selectedOptions: PoolSelectionItem[];
|
||||
onSelect: (Pools: PoolSelectionItem[]) => void;
|
||||
onSelectDetail?: (Pools: PoolPackageItem[]) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
visible?: boolean;
|
||||
onVisibleChange?: (visible: boolean) => void;
|
||||
selectedListMaxHeight?: number;
|
||||
showInput?: boolean;
|
||||
showSelectedList?: boolean;
|
||||
readonly?: boolean;
|
||||
onConfirm?: (
|
||||
selectedIds: string[],
|
||||
selectedItems: PoolSelectionItem[],
|
||||
) => 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: 8px;
|
||||
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;
|
||||
}
|
||||
127
Cunkebao/src/components/PoolSelection/index.tsx
Normal file
127
Cunkebao/src/components/PoolSelection/index.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React, { useState } from "react";
|
||||
import { SearchOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
import { Button, Input } from "antd";
|
||||
import style from "./index.module.scss";
|
||||
import SelectionPopup from "./selectionPopup";
|
||||
import { PoolSelectionProps } from "./data";
|
||||
export default function PoolSelection({
|
||||
selectedOptions,
|
||||
onSelect,
|
||||
onSelectDetail,
|
||||
placeholder = "选择流量池",
|
||||
className = "",
|
||||
visible,
|
||||
onVisibleChange,
|
||||
selectedListMaxHeight = 300,
|
||||
showInput = true,
|
||||
showSelectedList = true,
|
||||
readonly = false,
|
||||
onConfirm,
|
||||
}: PoolSelectionProps) {
|
||||
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}>
|
||||
<div className={style.groupAvatar}>
|
||||
{(item.nickname || item.name || "").charAt(0)}
|
||||
</div>
|
||||
<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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
213
Cunkebao/src/components/PoolSelection/selectionPopup.tsx
Normal file
213
Cunkebao/src/components/PoolSelection/selectionPopup.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Popup, Checkbox } from "antd-mobile";
|
||||
|
||||
import { getPoolPackages, Request } 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 { PoolSelectionItem, PoolPackageItem } from "./data";
|
||||
|
||||
// 弹窗属性接口
|
||||
interface SelectionPopupProps {
|
||||
visible: boolean;
|
||||
onVisibleChange: (visible: boolean) => void;
|
||||
selectedOptions: PoolSelectionItem[];
|
||||
onSelect: (items: PoolSelectionItem[]) => void;
|
||||
onSelectDetail?: (items: PoolPackageItem[]) => void;
|
||||
readonly?: boolean;
|
||||
onConfirm?: (
|
||||
selectedIds: string[],
|
||||
selectedItems: PoolSelectionItem[],
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function SelectionPopup({
|
||||
visible,
|
||||
onVisibleChange,
|
||||
selectedOptions,
|
||||
onSelect,
|
||||
onSelectDetail,
|
||||
readonly = false,
|
||||
onConfirm,
|
||||
}: SelectionPopupProps) {
|
||||
const [poolPackages, setPoolPackages] = useState<PoolPackageItem[]>([]);
|
||||
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 fetchPoolPackages = async (page: number, keyword: string = "") => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: Request = {
|
||||
page: String(page),
|
||||
limit: "20",
|
||||
keyword: keyword.trim(),
|
||||
};
|
||||
|
||||
const response = await getPoolPackages(params);
|
||||
if (response && response.list) {
|
||||
setPoolPackages(response.list);
|
||||
setTotalItems(response.total || 0);
|
||||
setTotalPages(Math.ceil((response.total || 0) / 20));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取流量池包列表失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理流量池包选择
|
||||
const handlePackageToggle = (item: PoolPackageItem) => {
|
||||
if (readonly) return;
|
||||
|
||||
// 将PoolPackageItem转换为GroupSelectionItem格式
|
||||
const selectionItem: PoolSelectionItem = {
|
||||
id: String(item.id),
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
createTime: item.createTime,
|
||||
num: item.num,
|
||||
// 保留原始数据
|
||||
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 = poolPackages.filter(packageItem =>
|
||||
newSelectedItems.some(g => g.id === String(packageItem.id)),
|
||||
);
|
||||
onSelectDetail(selectedItemObjs);
|
||||
}
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const handleConfirm = () => {
|
||||
if (onConfirm) {
|
||||
onConfirm(
|
||||
selectedOptions.map(item => item.id),
|
||||
selectedOptions,
|
||||
);
|
||||
}
|
||||
onVisibleChange(false);
|
||||
};
|
||||
|
||||
// 弹窗打开时初始化数据(只执行一次)
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setCurrentPage(1);
|
||||
setSearchQuery("");
|
||||
fetchPoolPackages(1, "");
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
// 搜索防抖(只在弹窗打开且搜索词变化时执行)
|
||||
useEffect(() => {
|
||||
if (!visible || searchQuery === "") return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setCurrentPage(1);
|
||||
fetchPoolPackages(1, searchQuery);
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchQuery, visible]);
|
||||
|
||||
// 页码变化时请求数据(只在弹窗打开且页码不是1时执行)
|
||||
useEffect(() => {
|
||||
if (!visible || currentPage === 1) return;
|
||||
fetchPoolPackages(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={() => fetchPoolPackages(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>
|
||||
) : poolPackages.length > 0 ? (
|
||||
<div className={style.groupListInner}>
|
||||
{poolPackages.map(item => (
|
||||
<div key={item.id} className={style.groupItem}>
|
||||
<Checkbox
|
||||
checked={selectedOptions.some(
|
||||
g => g.id === String(item.id),
|
||||
)}
|
||||
onChange={() => !readonly && handlePackageToggle(item)}
|
||||
disabled={readonly}
|
||||
style={{ marginRight: 12 }}
|
||||
/>
|
||||
<div className={style.groupInfo}>
|
||||
<div className={style.groupAvatar}>
|
||||
{item.name ? item.name.charAt(0) : "?"}
|
||||
</div>
|
||||
<div className={style.groupDetail}>
|
||||
<div className={style.groupName}>{item.name}</div>
|
||||
<div className={style.groupId}>
|
||||
描述: {item.description || "无描述"}
|
||||
</div>
|
||||
<div className={style.groupOwner}>
|
||||
创建时间: {item.createTime}
|
||||
</div>
|
||||
<div className={style.groupOwner}>
|
||||
包含数量: {item.num}
|
||||
</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 { 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,58 @@ const ComponentTest: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="群组选择" key="groups">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>GroupSelection 组件测试</h3>
|
||||
<GroupSelection
|
||||
selectedOptions={selectedGroups}
|
||||
onSelect={setSelectedGroups}
|
||||
placeholder="请选择微信群组"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<strong>已选群组:</strong> {selectedGroups.length} 个
|
||||
<br />
|
||||
<strong>群组ID:</strong>{" "}
|
||||
{selectedGroups.map(g => g.id).join(", ") || "无"}
|
||||
</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>
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
import React, { useImperativeHandle, forwardRef, useEffect } from "react";
|
||||
import { Button, Card, Switch, Form, InputNumber } from "antd";
|
||||
import { Input } from "antd";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
interface BasicSettingsProps {
|
||||
initialValues?: {
|
||||
name: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
groupSizeMin: number;
|
||||
groupSizeMax: number;
|
||||
maxGroupsPerDay: number;
|
||||
groupNameTemplate: string;
|
||||
groupDescription: string;
|
||||
status: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BasicSettingsRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
(
|
||||
{
|
||||
initialValues = {
|
||||
name: "",
|
||||
startTime: "06:00",
|
||||
endTime: "23:59",
|
||||
groupSizeMin: 20,
|
||||
groupSizeMax: 50,
|
||||
maxGroupsPerDay: 10,
|
||||
groupNameTemplate: "",
|
||||
groupDescription: "",
|
||||
status: 1,
|
||||
},
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 当initialValues变化时,重新设置表单值
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(initialValues);
|
||||
}, [form, initialValues]);
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("BasicSettings 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
key={JSON.stringify(initialValues)}
|
||||
onValuesChange={(changedValues, allValues) => {
|
||||
// 可以在这里处理表单值变化
|
||||
}}
|
||||
>
|
||||
{/* 任务名称 */}
|
||||
<Form.Item
|
||||
label="任务名称"
|
||||
name="name"
|
||||
rules={[
|
||||
{ required: true, message: "请输入任务名称" },
|
||||
{ min: 2, max: 50, message: "任务名称长度在2-50个字符之间" },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入任务名称" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 允许建群的时间段 */}
|
||||
<Form.Item label="允许建群的时间段">
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<Form.Item
|
||||
name="startTime"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请选择开始时间" }]}
|
||||
>
|
||||
<Input type="time" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
<span style={{ color: "#888" }}>至</span>
|
||||
<Form.Item
|
||||
name="endTime"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请选择结束时间" }]}
|
||||
>
|
||||
<Input type="time" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* 每日最大建群数 */}
|
||||
<Form.Item
|
||||
label="每日最大建群数"
|
||||
name="maxGroupsPerDay"
|
||||
rules={[
|
||||
{ required: true, message: "请输入每日最大建群数" },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
const numValue = Number(value);
|
||||
if (value && (numValue < 1 || numValue > 100)) {
|
||||
return Promise.reject(
|
||||
new Error("每日最大建群数在1-100之间"),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
placeholder="请输入最大建群数"
|
||||
step={1}
|
||||
style={{ width: "100%" }}
|
||||
value={form.getFieldValue("maxGroupsPerDay")}
|
||||
onChange={value => form.setFieldValue("maxGroupsPerDay", value)}
|
||||
addonBefore={
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
const currentValue =
|
||||
form.getFieldValue("maxGroupsPerDay") || 1;
|
||||
const newValue = Math.max(1, currentValue - 1);
|
||||
form.setFieldValue("maxGroupsPerDay", newValue);
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
}
|
||||
addonAfter={
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
const currentValue =
|
||||
form.getFieldValue("maxGroupsPerDay") || 1;
|
||||
const newValue = Math.min(100, currentValue + 1);
|
||||
form.setFieldValue("maxGroupsPerDay", newValue);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 群组最小人数 */}
|
||||
<Form.Item
|
||||
label="群组最小人数"
|
||||
name="groupSizeMin"
|
||||
rules={[
|
||||
{ required: true, message: "请输入群组最小人数" },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
const numValue = Number(value);
|
||||
if (value && (numValue < 1 || numValue > 500)) {
|
||||
return Promise.reject(
|
||||
new Error("群组最小人数在1-500之间"),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
placeholder="请输入最小人数"
|
||||
step={1}
|
||||
style={{ width: "100%" }}
|
||||
value={form.getFieldValue("groupSizeMin")}
|
||||
onChange={value => form.setFieldValue("groupSizeMin", value)}
|
||||
addonBefore={
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
const currentValue =
|
||||
form.getFieldValue("groupSizeMin") || 1;
|
||||
const newValue = Math.max(1, currentValue - 1);
|
||||
form.setFieldValue("groupSizeMin", newValue);
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
}
|
||||
addonAfter={
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
const currentValue =
|
||||
form.getFieldValue("groupSizeMin") || 1;
|
||||
const newValue = Math.min(500, currentValue + 1);
|
||||
form.setFieldValue("groupSizeMin", newValue);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 群组最大人数 */}
|
||||
<Form.Item
|
||||
label="群组最大人数"
|
||||
name="groupSizeMax"
|
||||
rules={[
|
||||
{ required: true, message: "请输入群组最大人数" },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
const numValue = Number(value);
|
||||
if (value && (numValue < 1 || numValue > 500)) {
|
||||
return Promise.reject(
|
||||
new Error("群组最大人数在1-500之间"),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
placeholder="请输入最大人数"
|
||||
step={1}
|
||||
style={{ width: "100%" }}
|
||||
value={form.getFieldValue("groupSizeMax")}
|
||||
onChange={value => form.setFieldValue("groupSizeMax", value)}
|
||||
addonBefore={
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
const currentValue =
|
||||
form.getFieldValue("groupSizeMax") || 1;
|
||||
const newValue = Math.max(1, currentValue - 1);
|
||||
form.setFieldValue("groupSizeMax", newValue);
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
}
|
||||
addonAfter={
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
const currentValue =
|
||||
form.getFieldValue("groupSizeMax") || 1;
|
||||
const newValue = Math.min(500, currentValue + 1);
|
||||
form.setFieldValue("groupSizeMax", newValue);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 群名称模板 */}
|
||||
<Form.Item
|
||||
label="群名称模板"
|
||||
name="groupNameTemplate"
|
||||
rules={[
|
||||
{ required: true, message: "请输入群名称模板" },
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: "群名称模板长度在2-100个字符之间",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入群名称模板" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 群描述 */}
|
||||
<Form.Item
|
||||
label="群描述"
|
||||
name="groupDescription"
|
||||
rules={[{ max: 200, message: "群描述不能超过200个字符" }]}
|
||||
>
|
||||
<TextArea
|
||||
placeholder="请输入群描述"
|
||||
rows={3}
|
||||
maxLength={200}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否启用 */}
|
||||
<Form.Item
|
||||
label="是否启用"
|
||||
name="status"
|
||||
valuePropName="checked"
|
||||
getValueFromEvent={checked => (checked ? 1 : 0)}
|
||||
getValueProps={value => ({ checked: value === 1 })}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>状态</span>
|
||||
<Switch />
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BasicSettings.displayName = "BasicSettings";
|
||||
|
||||
export default BasicSettings;
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
interface DeviceSelectorProps {
|
||||
selectedDevices: DeviceSelectionItem[];
|
||||
onNext: (data: {
|
||||
deveiceGroups: string[];
|
||||
deveiceGroupsOptions: DeviceSelectionItem[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface DeviceSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const DeviceSelector = forwardRef<DeviceSelectorRef, DeviceSelectorProps>(
|
||||
({ selectedDevices, onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("DeviceSelector 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
// 设备选择
|
||||
const handleDeviceSelect = (
|
||||
deveiceGroupsOptions: DeviceSelectionItem[],
|
||||
) => {
|
||||
const deveiceGroups = deveiceGroupsOptions.map(item => item.id);
|
||||
form.setFieldValue("deveiceGroups", deveiceGroups);
|
||||
// 通知父组件数据变化
|
||||
onNext({
|
||||
deveiceGroups: deveiceGroups.map(id => String(id)),
|
||||
deveiceGroupsOptions,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
deveiceGroups: selectedDevices.map(item => item.id),
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择设备组
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择要用于建群的设备组
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="deveiceGroups"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
type: "array",
|
||||
min: 1,
|
||||
message: "请选择至少一个设备组",
|
||||
},
|
||||
{ type: "array", max: 20, message: "最多只能选择20个设备组" },
|
||||
]}
|
||||
>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={handleDeviceSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DeviceSelector.displayName = "DeviceSelector";
|
||||
|
||||
export default DeviceSelector;
|
||||
@@ -0,0 +1,98 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import PoolSelection from "@/components/PoolSelection";
|
||||
import {
|
||||
PoolSelectionItem,
|
||||
PoolPackageItem,
|
||||
} from "@/components/PoolSelection/data";
|
||||
|
||||
interface PoolSelectorProps {
|
||||
selectedPools: PoolSelectionItem[];
|
||||
onNext: (data: {
|
||||
poolGroups: string[];
|
||||
poolGroupsOptions: PoolSelectionItem[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface PoolSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const PoolSelector = forwardRef<PoolSelectorRef, PoolSelectorProps>(
|
||||
({ selectedPools, onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("PoolSelector 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
// 处理选择变化
|
||||
const handlePoolChange = (poolGroupsOptions: PoolSelectionItem[]) => {
|
||||
const poolGroups = poolGroupsOptions.map(c => c.id.toString());
|
||||
form.setFieldValue("poolGroups", poolGroups);
|
||||
onNext({
|
||||
poolGroups,
|
||||
poolGroupsOptions,
|
||||
});
|
||||
};
|
||||
|
||||
// 处理详细选择数据
|
||||
const handleSelectDetail = (poolPackages: PoolPackageItem[]) => {
|
||||
// 如果需要处理原始流量池包数据,可以在这里添加逻辑
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
poolGroups: selectedPools.map(c => c.id.toString()),
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择流量池包
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择要用于建群的流量池包
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="poolGroups"
|
||||
rules={[
|
||||
{ required: true, message: "请选择至少一个流量池包" },
|
||||
{ type: "array", min: 1, message: "请选择至少一个流量池包" },
|
||||
{ type: "array", max: 20, message: "最多只能选择20个流量池包" },
|
||||
]}
|
||||
>
|
||||
<PoolSelection
|
||||
selectedOptions={selectedPools}
|
||||
onSelect={handlePoolChange}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
PoolSelector.displayName = "PoolSelector";
|
||||
|
||||
export default PoolSelector;
|
||||
@@ -1,21 +1,32 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Form, Toast, TextArea } from "antd-mobile";
|
||||
import { Input, InputNumber, Button, Switch } from "antd";
|
||||
import { Toast } from "antd-mobile";
|
||||
import { Button } from "antd";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./index.module.scss";
|
||||
import { createAutoGroup, updateAutoGroup, getAutoGroupDetail } from "./api";
|
||||
import { AutoGroupFormData } from "./types";
|
||||
import DeviceSelection from "@/components/DeviceSelection/index";
|
||||
import { AutoGroupFormData, StepItem } from "./types";
|
||||
import StepIndicator from "@/components/StepIndicator";
|
||||
import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings";
|
||||
import DeviceSelector, { DeviceSelectorRef } from "./components/DeviceSelector";
|
||||
import PoolSelector, { PoolSelectorRef } from "./components/PoolSelector";
|
||||
import NavCommon from "@/components/NavCommon/index";
|
||||
import dayjs from "dayjs";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
|
||||
const steps: StepItem[] = [
|
||||
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
|
||||
{ id: 2, title: "步骤 2", subtitle: "选择设备" },
|
||||
{ id: 3, title: "步骤 3", subtitle: "选择流量池包" },
|
||||
];
|
||||
|
||||
const defaultForm: AutoGroupFormData = {
|
||||
name: "",
|
||||
type: 4,
|
||||
deveiceGroups: [], // 设备组
|
||||
deveiceGroupsOptions: [], // 设备组选项
|
||||
poolGroups: [], // 内容库
|
||||
poolGroupsOptions: [], // 内容库选项
|
||||
startTime: dayjs().format("HH:mm"), // 开始时间 (HH:mm)
|
||||
endTime: dayjs().add(1, "hour").format("HH:mm"), // 结束时间 (HH:mm)
|
||||
groupSizeMin: 20, // 群组最小人数
|
||||
@@ -30,17 +41,33 @@ const AutoGroupForm: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const isEdit = Boolean(id);
|
||||
const [form, setForm] = useState<AutoGroupFormData>(defaultForm);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataLoaded, setDataLoaded] = useState(!isEdit); // 非编辑模式直接标记为已加载
|
||||
const [formData, setFormData] = useState<AutoGroupFormData>(defaultForm);
|
||||
const [deviceGroupsOptions, setDeviceGroupsOptions] = useState<
|
||||
DeviceSelectionItem[]
|
||||
>([]);
|
||||
const [poolGroupsOptions, setpoolGroupsOptions] = useState<
|
||||
PoolSelectionItem[]
|
||||
>([]);
|
||||
|
||||
// 创建子组件的ref
|
||||
const basicSettingsRef = useRef<BasicSettingsRef>(null);
|
||||
const deviceSelectorRef = useRef<DeviceSelectorRef>(null);
|
||||
const poolSelectorRef = useRef<PoolSelectorRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
// 这里应请求详情接口,回填表单,演示用mock
|
||||
getAutoGroupDetail(id).then(res => {
|
||||
setForm({
|
||||
const updatedForm = {
|
||||
...defaultForm,
|
||||
name: res.name,
|
||||
deveiceGroups: res.config.deveiceGroups || [],
|
||||
deveiceGroupsOptions: res.config.deveiceGroupsOptions || [],
|
||||
poolGroups: res.config.poolGroups || [],
|
||||
poolGroupsOptions: res.config.poolGroupsOptions || [],
|
||||
startTime: res.config.startTime,
|
||||
endTime: res.config.endTime,
|
||||
groupSizeMin: res.config.groupSizeMin,
|
||||
@@ -51,19 +78,66 @@ const AutoGroupForm: React.FC = () => {
|
||||
status: res.status,
|
||||
type: res.type,
|
||||
id: res.id,
|
||||
});
|
||||
console.log(form);
|
||||
};
|
||||
setFormData(updatedForm);
|
||||
setDeviceGroupsOptions(res.config.deveiceGroupsOptions || []);
|
||||
setpoolGroupsOptions(res.config.poolGroupsOptions || []);
|
||||
setDataLoaded(true); // 标记数据已加载
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const handleBasicSettingsChange = (values: Partial<AutoGroupFormData>) => {
|
||||
setFormData(prev => ({ ...prev, ...values }));
|
||||
};
|
||||
|
||||
// 设备组选择
|
||||
const handleDevicesChange = (data: {
|
||||
deveiceGroups: string[];
|
||||
deveiceGroupsOptions: DeviceSelectionItem[];
|
||||
}) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
deveiceGroups: data.deveiceGroups,
|
||||
}));
|
||||
setDeviceGroupsOptions(data.deveiceGroupsOptions);
|
||||
};
|
||||
|
||||
// 流量池包选择
|
||||
const handlePoolsChange = (data: {
|
||||
poolGroups: string[];
|
||||
poolGroupsOptions: PoolSelectionItem[];
|
||||
}) => {
|
||||
setFormData(prev => ({ ...prev, poolGroups: data.poolGroups }));
|
||||
setpoolGroupsOptions(data.poolGroupsOptions);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formData.name.trim()) {
|
||||
Toast.show({ content: "请输入任务名称" });
|
||||
return;
|
||||
}
|
||||
if (formData.deveiceGroups.length === 0) {
|
||||
Toast.show({ content: "请选择至少一个设备组" });
|
||||
return;
|
||||
}
|
||||
if (formData.poolGroups.length === 0) {
|
||||
Toast.show({ content: "请选择至少一个流量池包" });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const submitData = {
|
||||
...formData,
|
||||
deveiceGroupsOptions: deviceGroupsOptions,
|
||||
poolGroupsOptions: poolGroupsOptions,
|
||||
};
|
||||
|
||||
if (isEdit) {
|
||||
await updateAutoGroup(form);
|
||||
await updateAutoGroup(submitData);
|
||||
Toast.show({ content: "编辑成功" });
|
||||
} else {
|
||||
await createAutoGroup(form);
|
||||
await createAutoGroup(submitData);
|
||||
Toast.show({ content: "创建成功" });
|
||||
}
|
||||
navigate("/workspace/auto-group");
|
||||
@@ -74,227 +148,134 @@ const AutoGroupForm: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const setTaskName = (val: string) => {
|
||||
setForm((f: any) => ({ ...f, name: val }));
|
||||
const handlePrevious = () => {
|
||||
if (currentStep > 1) {
|
||||
setCurrentStep(currentStep - 1);
|
||||
}
|
||||
};
|
||||
const setDeviceGroups = (val: DeviceSelectionItem[]) => {
|
||||
console.log(val);
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
deveiceGroups: val.map(item => item.id),
|
||||
deveiceGroupsOptions: val,
|
||||
}));
|
||||
|
||||
const handleNext = async () => {
|
||||
if (currentStep < 3) {
|
||||
try {
|
||||
let isValid = false;
|
||||
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
// 调用 BasicSettings 的表单校验
|
||||
isValid = (await basicSettingsRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
const values = basicSettingsRef.current?.getValues();
|
||||
if (values) {
|
||||
handleBasicSettingsChange(values);
|
||||
}
|
||||
setCurrentStep(2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// 调用 DeviceSelector 的表单校验
|
||||
isValid = (await deviceSelectorRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
setCurrentStep(3);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
setCurrentStep(currentStep + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("表单验证失败:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderCurrentStep = () => {
|
||||
// 编辑模式下,等待数据加载完成后再渲染
|
||||
if (isEdit && !dataLoaded) {
|
||||
return (
|
||||
<div style={{ textAlign: "center", padding: "50px" }}>加载中...</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return (
|
||||
<BasicSettings
|
||||
ref={basicSettingsRef}
|
||||
initialValues={{
|
||||
name: formData.name,
|
||||
startTime: formData.startTime,
|
||||
endTime: formData.endTime,
|
||||
groupSizeMin: formData.groupSizeMin,
|
||||
groupSizeMax: formData.groupSizeMax,
|
||||
maxGroupsPerDay: formData.maxGroupsPerDay,
|
||||
groupNameTemplate: formData.groupNameTemplate,
|
||||
groupDescription: formData.groupDescription,
|
||||
status: formData.status,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<DeviceSelector
|
||||
ref={deviceSelectorRef}
|
||||
selectedDevices={deviceGroupsOptions}
|
||||
onNext={handleDevicesChange}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<PoolSelector
|
||||
ref={poolSelectorRef}
|
||||
selectedPools={poolGroupsOptions}
|
||||
onNext={handlePoolsChange}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<div className="footer-btn-group">
|
||||
{currentStep > 1 && (
|
||||
<Button size="large" onClick={handlePrevious}>
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 3 ? (
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{isEdit ? "保存修改" : "创建任务"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="large" type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavCommon
|
||||
title={isEdit ? "编辑建群任务" : "新建建群任务"}
|
||||
backFn={() => navigate(-1)}
|
||||
/>
|
||||
<>
|
||||
<NavCommon
|
||||
title={isEdit ? "编辑建群任务" : "新建建群任务"}
|
||||
backFn={() => navigate(-1)}
|
||||
/>
|
||||
<StepIndicator currentStep={currentStep} steps={steps} />
|
||||
</>
|
||||
}
|
||||
footer={renderFooter()}
|
||||
>
|
||||
<div className={style.autoGroupForm}>
|
||||
<Form
|
||||
layout="vertical"
|
||||
footer={
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{isEdit ? "保存修改" : "创建任务"}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Form.Item label="任务名称" required>
|
||||
<Input
|
||||
value={form.name}
|
||||
onChange={val => setTaskName(val.target.value)}
|
||||
placeholder="请输入任务名称"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="设备组" required>
|
||||
<DeviceSelection
|
||||
selectedOptions={form.deveiceGroupsOptions}
|
||||
onSelect={setDeviceGroups}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="每日最大建群数" name="maxGroupsPerDay" required>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.max(1, (form.maxGroupsPerDay || 1) - 1);
|
||||
setForm((f: any) => ({ ...f, maxGroupsPerDay: newValue }));
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
value={form.maxGroupsPerDay}
|
||||
onChange={val =>
|
||||
setForm((f: any) => ({ ...f, maxGroupsPerDay: val || 1 }))
|
||||
}
|
||||
placeholder="请输入最大建群数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.min(
|
||||
100,
|
||||
(form.maxGroupsPerDay || 1) + 1,
|
||||
);
|
||||
setForm((f: any) => ({ ...f, maxGroupsPerDay: newValue }));
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="开始时间" required>
|
||||
<Input
|
||||
type="time"
|
||||
style={{ width: 120 }}
|
||||
value={form.startTime || ""}
|
||||
onChange={e => {
|
||||
setForm((f: any) => ({ ...f, startTime: e.target.value }));
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="结束时间" required>
|
||||
<Input
|
||||
type="time"
|
||||
style={{ width: 120 }}
|
||||
value={form.endTime || ""}
|
||||
onChange={e => {
|
||||
setForm((f: any) => ({ ...f, endTime: e.target.value }));
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="群组最小人数" name="groupSizeMin" required>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.max(1, (form.groupSizeMin || 1) - 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMin: newValue }));
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
value={form.groupSizeMin}
|
||||
onChange={val => {
|
||||
const newValue = val || 1;
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
groupSizeMin: Math.min(newValue, f.groupSizeMax),
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入最小人数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.min(500, (form.groupSizeMin || 1) + 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMin: newValue }));
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="群组最大人数" name="groupSizeMax" required>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.max(1, (form.groupSizeMax || 1) - 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMax: newValue }));
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
value={form.groupSizeMax}
|
||||
onChange={val => {
|
||||
const newValue = val || 1;
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
groupSizeMax: Math.max(newValue, f.groupSizeMin),
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入最大人数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.min(500, (form.groupSizeMax || 1) + 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMax: newValue }));
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="群名称模板" required>
|
||||
<Input
|
||||
value={form.groupNameTemplate}
|
||||
onChange={val =>
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
groupNameTemplate: val.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="请输入群名称模板"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="群描述" name="groupDescription">
|
||||
<TextArea
|
||||
value={form.groupDescription}
|
||||
onChange={val =>
|
||||
setForm((f: any) => ({ ...f, groupDescription: val }))
|
||||
}
|
||||
placeholder="请输入群描述"
|
||||
rows={3}
|
||||
maxLength={100}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="是否开启" name="status">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>状态</span>
|
||||
<Switch
|
||||
checked={form.status === 1}
|
||||
onChange={checked =>
|
||||
setForm((f: any) => ({ ...f, status: checked ? 1 : 0 }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
<div style={{ padding: 12 }}>{renderCurrentStep()}</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
|
||||
// 自动建群表单数据类型定义
|
||||
export interface AutoGroupFormData {
|
||||
id?: string; // 任务ID
|
||||
@@ -6,6 +8,8 @@ export interface AutoGroupFormData {
|
||||
name: string; // 任务名称
|
||||
deveiceGroups: string[]; // 设备组
|
||||
deveiceGroupsOptions: DeviceSelectionItem[]; // 设备组选项
|
||||
poolGroups: string[]; // 流量池
|
||||
poolGroupsOptions: PoolSelectionItem[]; // 流量池选项
|
||||
startTime: string; // 开始时间 (YYYY-MM-DD HH:mm:ss)
|
||||
endTime: string; // 结束时间 (YYYY-MM-DD HH:mm:ss)
|
||||
groupSizeMin: number; // 群组最小人数
|
||||
@@ -17,6 +21,13 @@ export interface AutoGroupFormData {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 步骤定义
|
||||
export interface StepItem {
|
||||
id: number;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const formValidationRules = {
|
||||
name: [
|
||||
@@ -27,6 +38,10 @@ export const formValidationRules = {
|
||||
{ required: true, message: "请选择设备组" },
|
||||
{ type: "array", min: 1, message: "至少选择一个设备组" },
|
||||
],
|
||||
poolGroups: [
|
||||
{ required: true, message: "请选择内容库" },
|
||||
{ type: "array", min: 1, message: "至少选择一个内容库" },
|
||||
],
|
||||
startTime: [{ required: true, message: "请选择开始时间" }],
|
||||
endTime: [{ required: true, message: "请选择结束时间" }],
|
||||
groupSizeMin: [
|
||||
|
||||
@@ -9,3 +9,8 @@ export const getAutoGroupList = (params: any) =>
|
||||
export function copyAutoGroupTask(id: string): Promise<any> {
|
||||
return request("/v1/workbench/copy", { id }, "POST");
|
||||
}
|
||||
|
||||
// 删除自动建群任务
|
||||
export function deleteAutoGroupTask(id: string): Promise<any> {
|
||||
return request("/v1/workbench/delete", { id }, "DELETE");
|
||||
}
|
||||
|
||||
@@ -14,7 +14,12 @@ import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { getAutoGroupList, copyAutoGroupTask } from "./api";
|
||||
import {
|
||||
getAutoGroupList,
|
||||
copyAutoGroupTask,
|
||||
deleteAutoGroupTask,
|
||||
} from "./api";
|
||||
import { comfirm } from "@/utils/common";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./index.module.scss";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
@@ -110,14 +115,27 @@ const AutoGroupList: React.FC = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleDelete = (taskId: string) => {
|
||||
const handleDelete = async (taskId: string) => {
|
||||
const taskToDelete = tasks.find(task => task.id === taskId);
|
||||
if (!taskToDelete) return;
|
||||
window.confirm(`确定要删除"${taskToDelete.name}"吗?`) &&
|
||||
setTasks(tasks.filter(task => task.id !== taskId));
|
||||
Toast.show({ content: "删除成功" });
|
||||
};
|
||||
|
||||
try {
|
||||
await comfirm("确定要删除吗?", {
|
||||
title: "删除确认",
|
||||
cancelText: "取消",
|
||||
confirmText: "删除",
|
||||
});
|
||||
|
||||
await deleteAutoGroupTask(taskId);
|
||||
setTasks(tasks.filter(task => task.id !== taskId));
|
||||
Toast.show({ content: "删除成功" });
|
||||
} catch (error) {
|
||||
// 用户取消删除或删除失败
|
||||
if (error !== "cancel") {
|
||||
Toast.show({ content: "删除失败" });
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleEdit = (taskId: string) => {
|
||||
navigate(`/workspace/auto-group/${taskId}/edit`);
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ const NewAutoLike: React.FC = () => {
|
||||
startTime: config.timeRange?.start || config.startTime || "08:00",
|
||||
endTime: config.timeRange?.end || config.endTime || "22:00",
|
||||
contentTypes: config.contentTypes || ["text", "image", "video"],
|
||||
deveiceGroups: config.deveicegroups || [],
|
||||
deveiceGroups: config.deveiceGroups || [],
|
||||
deveiceGroupsOptions: config.deveiceGroupsOptions || [],
|
||||
friendsGroups: config.friendsgroups || [],
|
||||
friendsGroupsOptions: config.friendsGroupsOptions || [],
|
||||
@@ -121,7 +121,7 @@ const NewAutoLike: React.FC = () => {
|
||||
message.warning("请输入任务名称");
|
||||
return;
|
||||
}
|
||||
if (!formData.deveicegroups || formData.deveicegroups.length === 0) {
|
||||
if (!formData.deveiceGroups || formData.deveiceGroups.length === 0) {
|
||||
message.warning("请选择执行设备");
|
||||
return;
|
||||
}
|
||||
@@ -329,7 +329,7 @@ const NewAutoLike: React.FC = () => {
|
||||
<div className={style.basicSection}>
|
||||
<div className={style.formItem}>
|
||||
<DeviceSelection
|
||||
selectedOptions={formData.deveicegroups}
|
||||
selectedOptions={formData.deveiceGroupsOptions}
|
||||
onSelect={devices => handleUpdateFormData({ devices })}
|
||||
showInput={true}
|
||||
showSelectedList={true}
|
||||
@@ -348,7 +348,7 @@ const NewAutoLike: React.FC = () => {
|
||||
onClick={handleNext}
|
||||
className={style.nextBtn}
|
||||
size="large"
|
||||
disabled={formData.deveicegroups.length === 0}
|
||||
disabled={formData.deveiceGroups.length === 0}
|
||||
>
|
||||
下一步
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user