diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index 1fc771a83..454623cd5 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -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"
]
}
}
\ No newline at end of file
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 8a58e165a..76889c669 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,13 +11,13 @@
-
+
-
+
-
+
-
+
diff --git a/Cunkebao/src/components/PoolSelection/api.ts b/Cunkebao/src/components/PoolSelection/api.ts
new file mode 100644
index 000000000..552be7e6e
--- /dev/null
+++ b/Cunkebao/src/components/PoolSelection/api.ts
@@ -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");
+}
diff --git a/Cunkebao/src/components/PoolSelection/data.ts b/Cunkebao/src/components/PoolSelection/data.ts
new file mode 100644
index 000000000..95ff55d4f
--- /dev/null
+++ b/Cunkebao/src/components/PoolSelection/data.ts
@@ -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;
+}
diff --git a/Cunkebao/src/components/PoolSelection/index.module.scss b/Cunkebao/src/components/PoolSelection/index.module.scss
new file mode 100644
index 000000000..f0421ca1f
--- /dev/null
+++ b/Cunkebao/src/components/PoolSelection/index.module.scss
@@ -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;
+}
diff --git a/Cunkebao/src/components/PoolSelection/index.tsx b/Cunkebao/src/components/PoolSelection/index.tsx
new file mode 100644
index 000000000..5118bacde
--- /dev/null
+++ b/Cunkebao/src/components/PoolSelection/index.tsx
@@ -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 && (
+
+ }
+ allowClear={!readonly}
+ size="large"
+ readOnly={readonly}
+ disabled={readonly}
+ style={
+ readonly ? { background: "#f5f5f5", cursor: "not-allowed" } : {}
+ }
+ />
+
+ )}
+ {/* 已选流量池列表窗口 */}
+ {showSelectedList && selectedOptions.length > 0 && (
+
+ {selectedOptions.map(item => (
+
+
+
+ {(item.nickname || item.name || "").charAt(0)}
+
+
+
{item.nickname || item.name}
+
{item.wechatId || item.mobile}
+
+ {!readonly && (
+
}
+ 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)}
+ />
+ )}
+
+
+ ))}
+
+ )}
+ {/* 弹窗 */}
+
+ >
+ );
+}
diff --git a/Cunkebao/src/components/PoolSelection/selectionPopup.tsx b/Cunkebao/src/components/PoolSelection/selectionPopup.tsx
new file mode 100644
index 000000000..d952efc8a
--- /dev/null
+++ b/Cunkebao/src/components/PoolSelection/selectionPopup.tsx
@@ -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([]);
+ 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 (
+ onVisibleChange(false)}
+ position="bottom"
+ bodyStyle={{ height: "100vh" }}
+ >
+ fetchPoolPackages(currentPage, searchQuery)}
+ />
+ }
+ footer={
+ onVisibleChange(false)}
+ onConfirm={handleConfirm}
+ />
+ }
+ >
+
+ {loading ? (
+
+ ) : poolPackages.length > 0 ? (
+
+ {poolPackages.map(item => (
+
+
g.id === String(item.id),
+ )}
+ onChange={() => !readonly && handlePackageToggle(item)}
+ disabled={readonly}
+ style={{ marginRight: 12 }}
+ />
+
+
+ {item.name ? item.name.charAt(0) : "?"}
+
+
+
{item.name}
+
+ 描述: {item.description || "无描述"}
+
+
+ 创建时间: {item.createTime}
+
+
+ 包含数量: {item.num}
+
+
+
+
+ ))}
+
+ ) : (
+
+
+ {searchQuery
+ ? `没有找到包含"${searchQuery}"的流量池包`
+ : "没有找到流量池包"}
+
+
+ )}
+
+
+
+ );
+}
diff --git a/Cunkebao/src/pages/mobile/test/select.tsx b/Cunkebao/src/pages/mobile/test/select.tsx
index d7c73a695..f7c1edd7f 100644
--- a/Cunkebao/src/pages/mobile/test/select.tsx
+++ b/Cunkebao/src/pages/mobile/test/select.tsx
@@ -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(
@@ -34,6 +36,9 @@ const ComponentTest: React.FC = () => {
const [selectedFriendsOptions, setSelectedFriendsOptions] = useState<
FriendSelectionItem[]
>([]);
+
+ // 流量池选择状态
+ const [selectedPools, setSelectedPools] = useState([]);
return (
}>
@@ -155,6 +160,58 @@ const ComponentTest: React.FC = () => {
+
+
+
+
GroupSelection 组件测试
+
+
+ 已选群组: {selectedGroups.length} 个
+
+ 群组ID:{" "}
+ {selectedGroups.map(g => g.id).join(", ") || "无"}
+
+
+
+
+
+
+
PoolSelection 组件测试
+
+
+ 已选流量池: {selectedPools.length} 个
+
+ 流量池ID:{" "}
+ {selectedPools.map(p => p.id).join(", ") || "无"}
+
+
+
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/BasicSettings.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/BasicSettings.tsx
new file mode 100644
index 000000000..39712e2dd
--- /dev/null
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/BasicSettings.tsx
@@ -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;
+ getValues: () => any;
+}
+
+const BasicSettings = forwardRef(
+ (
+ {
+ 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 (
+
+
+
+
+
+
+ {/* 允许建群的时间段 */}
+
+
+
+
+
+ 至
+
+
+
+
+
+
+ {/* 每日最大建群数 */}
+ {
+ const numValue = Number(value);
+ if (value && (numValue < 1 || numValue > 100)) {
+ return Promise.reject(
+ new Error("每日最大建群数在1-100之间"),
+ );
+ }
+ return Promise.resolve();
+ },
+ },
+ ]}
+ >
+ form.setFieldValue("maxGroupsPerDay", value)}
+ addonBefore={
+
+ }
+ addonAfter={
+
+ }
+ />
+
+
+ {/* 群组最小人数 */}
+ {
+ const numValue = Number(value);
+ if (value && (numValue < 1 || numValue > 500)) {
+ return Promise.reject(
+ new Error("群组最小人数在1-500之间"),
+ );
+ }
+ return Promise.resolve();
+ },
+ },
+ ]}
+ >
+ form.setFieldValue("groupSizeMin", value)}
+ addonBefore={
+
+ }
+ addonAfter={
+
+ }
+ />
+
+
+ {/* 群组最大人数 */}
+ {
+ const numValue = Number(value);
+ if (value && (numValue < 1 || numValue > 500)) {
+ return Promise.reject(
+ new Error("群组最大人数在1-500之间"),
+ );
+ }
+ return Promise.resolve();
+ },
+ },
+ ]}
+ >
+ form.setFieldValue("groupSizeMax", value)}
+ addonBefore={
+
+ }
+ addonAfter={
+
+ }
+ />
+
+
+ {/* 群名称模板 */}
+
+
+
+
+ {/* 群描述 */}
+
+
+
+
+ {/* 是否启用 */}
+ (checked ? 1 : 0)}
+ getValueProps={value => ({ checked: value === 1 })}
+ >
+
+ 状态
+
+
+
+
+
+
+ );
+ },
+);
+
+BasicSettings.displayName = "BasicSettings";
+
+export default BasicSettings;
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/DeviceSelector.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/DeviceSelector.tsx
new file mode 100644
index 000000000..d3b3bb162
--- /dev/null
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/DeviceSelector.tsx
@@ -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;
+ getValues: () => any;
+}
+
+const DeviceSelector = forwardRef(
+ ({ 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 (
+
+
+
+
+
+
+ );
+ },
+);
+
+DeviceSelector.displayName = "DeviceSelector";
+
+export default DeviceSelector;
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/PoolSelector.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/PoolSelector.tsx
new file mode 100644
index 000000000..33c164a85
--- /dev/null
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/PoolSelector.tsx
@@ -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;
+ getValues: () => any;
+}
+
+const PoolSelector = forwardRef(
+ ({ 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 (
+
+ );
+ },
+);
+
+PoolSelector.displayName = "PoolSelector";
+
+export default PoolSelector;
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx
index 2c946b0b7..ea0a78977 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx
@@ -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(defaultForm);
+ const [currentStep, setCurrentStep] = useState(1);
const [loading, setLoading] = useState(false);
+ const [dataLoaded, setDataLoaded] = useState(!isEdit); // 非编辑模式直接标记为已加载
+ const [formData, setFormData] = useState(defaultForm);
+ const [deviceGroupsOptions, setDeviceGroupsOptions] = useState<
+ DeviceSelectionItem[]
+ >([]);
+ const [poolGroupsOptions, setpoolGroupsOptions] = useState<
+ PoolSelectionItem[]
+ >([]);
+
+ // 创建子组件的ref
+ const basicSettingsRef = useRef(null);
+ const deviceSelectorRef = useRef(null);
+ const poolSelectorRef = useRef(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) => {
+ 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 (
+ 加载中...
+ );
+ }
+
+ switch (currentStep) {
+ case 1:
+ return (
+
+ );
+ case 2:
+ return (
+
+ );
+ case 3:
+ return (
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ const renderFooter = () => {
+ return (
+
+ {currentStep > 1 && (
+
+ )}
+ {currentStep === 3 ? (
+
+ ) : (
+
+ )}
+
+ );
+ };
+
return (
navigate(-1)}
- />
+ <>
+ navigate(-1)}
+ />
+
+ >
}
+ footer={renderFooter()}
>
-
-
- setTaskName(val.target.value)}
- placeholder="请输入任务名称"
- />
-
-
-
-
-
-
-
-
- setForm((f: any) => ({ ...f, maxGroupsPerDay: val || 1 }))
- }
- placeholder="请输入最大建群数"
- step={1}
- style={{ flex: 1 }}
- />
-
-
-
-
- {
- setForm((f: any) => ({ ...f, startTime: e.target.value }));
- }}
- />
-
-
- {
- setForm((f: any) => ({ ...f, endTime: e.target.value }));
- }}
- />
-
-
-
-
- {
- const newValue = val || 1;
- setForm((f: any) => ({
- ...f,
- groupSizeMin: Math.min(newValue, f.groupSizeMax),
- }));
- }}
- placeholder="请输入最小人数"
- step={1}
- style={{ flex: 1 }}
- />
-
-
-
-
-
-
- {
- const newValue = val || 1;
- setForm((f: any) => ({
- ...f,
- groupSizeMax: Math.max(newValue, f.groupSizeMin),
- }));
- }}
- placeholder="请输入最大人数"
- step={1}
- style={{ flex: 1 }}
- />
-
-
-
-
-
-
- setForm((f: any) => ({
- ...f,
- groupNameTemplate: val.target.value,
- }))
- }
- placeholder="请输入群名称模板"
- />
-
-
-
-
-
- 状态
-
- setForm((f: any) => ({ ...f, status: checked ? 1 : 0 }))
- }
- />
-
-
-
-
+ {renderCurrentStep()}
);
};
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts b/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts
index 6a07b8152..83ad789d7 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts
@@ -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: [
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/list/api.ts b/Cunkebao/src/pages/mobile/workspace/auto-group/list/api.ts
index 8ad39e81f..fede6244c 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/list/api.ts
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/list/api.ts
@@ -9,3 +9,8 @@ export const getAutoGroupList = (params: any) =>
export function copyAutoGroupTask(id: string): Promise {
return request("/v1/workbench/copy", { id }, "POST");
}
+
+// 删除自动建群任务
+export function deleteAutoGroupTask(id: string): Promise {
+ return request("/v1/workbench/delete", { id }, "DELETE");
+}
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/list/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/list/index.tsx
index 8e44c6000..f19cc7325 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-group/list/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-group/list/index.tsx
@@ -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`);
};
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
index b7bc9ba40..5be27d579 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-like/new/index.tsx
@@ -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 = () => {
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}
>
下一步