From e63441c2544322ddfab0a3c96ed85a2db1451cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Mon, 4 Aug 2025 17:11:48 +0800 Subject: [PATCH 01/31] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=87=89=E7=94=A8?= =?UTF-8?q?=E7=A8=8B=E5=BC=8F=E5=9C=96=E6=A8=99=E8=B7=AF=E5=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=B0=87"/public/logo.png"=E6=9B=B4=E6=94=B9=E7=82=BA"/logo.pn?= =?UTF-8?q?g"=E4=BB=A5=E7=A2=BA=E4=BF=9D=E6=AD=A3=E7=A2=BA=E9=A1=AF?= =?UTF-8?q?=E7=A4=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/pages/mobile/mine/setting/About.tsx | 2 +- nkebao/src/pages/mobile/mine/setting/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nkebao/src/pages/mobile/mine/setting/About.tsx b/nkebao/src/pages/mobile/mine/setting/About.tsx index e1f22402..7bcfcf40 100644 --- a/nkebao/src/pages/mobile/mine/setting/About.tsx +++ b/nkebao/src/pages/mobile/mine/setting/About.tsx @@ -87,7 +87,7 @@ const About: React.FC = () => {
- logo + logo
diff --git a/nkebao/src/pages/mobile/mine/setting/index.tsx b/nkebao/src/pages/mobile/mine/setting/index.tsx index 8090bdc2..dc341874 100644 --- a/nkebao/src/pages/mobile/mine/setting/index.tsx +++ b/nkebao/src/pages/mobile/mine/setting/index.tsx @@ -258,7 +258,7 @@ const Setting: React.FC = () => {
- +
存客宝
From 662a192b811c23091274eec7fb80e969bd35d40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 7 Aug 2025 14:52:06 +0800 Subject: [PATCH 02/31] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E7=BE=A4?= =?UTF-8?q?=E7=BB=84=E9=80=89=E6=8B=A9=E6=9E=84=E5=BB=BA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/components/GroupSelection/data.ts | 43 +++ .../GroupSelection/index.module.scss | 46 +-- .../src/components/GroupSelection/index.tsx | 299 +++--------------- .../GroupSelection/selectionPopup.tsx | 220 +++++++++++++ .../plan/new/steps/BasicSettings.tsx | 35 +- nkebao/src/pages/mobile/test/select.tsx | 63 ++-- .../workspace/group-push/detail/index.tsx | 1 - .../form/components/BasicSettings.tsx | 8 +- .../form/components/GroupSelector.tsx | 255 +++------------ .../workspace/group-push/list/index.tsx | 1 - 10 files changed, 403 insertions(+), 568 deletions(-) create mode 100644 nkebao/src/components/GroupSelection/data.ts create mode 100644 nkebao/src/components/GroupSelection/selectionPopup.tsx diff --git a/nkebao/src/components/GroupSelection/data.ts b/nkebao/src/components/GroupSelection/data.ts new file mode 100644 index 00000000..1bee0032 --- /dev/null +++ b/nkebao/src/components/GroupSelection/data.ts @@ -0,0 +1,43 @@ +// 群组接口类型 +export interface WechatGroup { + id: string; + chatroomId: string; + name: string; + avatar: string; + ownerWechatId: string; + ownerNickname: string; + ownerAvatar: string; +} + +export interface GroupSelectionItem { + id: string; + avatar: string; + chatroomId?: string; + createTime?: number; + identifier?: string; + name: string; + ownerAlias?: string; + ownerAvatar?: string; + ownerNickname?: string; + ownerWechatId?: string; + [key: string]: any; +} + +// 组件属性接口 +export interface GroupSelectionProps { + selectedGroups: GroupSelectionItem[]; + onSelect: (groups: GroupSelectionItem[]) => void; + onSelectDetail?: (groups: WechatGroup[]) => 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; // 新增 +} diff --git a/nkebao/src/components/GroupSelection/index.module.scss b/nkebao/src/components/GroupSelection/index.module.scss index 8eb2b72e..bedba3ef 100644 --- a/nkebao/src/components/GroupSelection/index.module.scss +++ b/nkebao/src/components/GroupSelection/index.module.scss @@ -17,6 +17,21 @@ 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; @@ -77,42 +92,11 @@ align-items: center; padding: 16px 24px; border-bottom: 1px solid #f0f0f0; - cursor: pointer; transition: background 0.2s; &:hover { background: #f5f6fa; } } -.radioWrapper { - margin-right: 12px; - display: flex; - align-items: center; - justify-content: center; -} -.radioSelected { - width: 20px; - height: 20px; - border-radius: 50%; - border: 2px solid #1890ff; - display: flex; - align-items: center; - justify-content: center; -} -.radioUnselected { - width: 20px; - height: 20px; - border-radius: 50%; - border: 2px solid #e5e6eb; - display: flex; - align-items: center; - justify-content: center; -} -.radioDot { - width: 12px; - height: 12px; - border-radius: 50%; - background: #1890ff; -} .groupInfo { display: flex; align-items: center; diff --git a/nkebao/src/components/GroupSelection/index.tsx b/nkebao/src/components/GroupSelection/index.tsx index 423cf142..decb8288 100644 --- a/nkebao/src/components/GroupSelection/index.tsx +++ b/nkebao/src/components/GroupSelection/index.tsx @@ -1,40 +1,10 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { SearchOutlined, DeleteOutlined } from "@ant-design/icons"; import { Button, Input } from "antd"; -import { Popup } from "antd-mobile"; -import { getGroupList } from "./api"; +import { Avatar } from "antd-mobile"; import style from "./index.module.scss"; -import Layout from "@/components/Layout/Layout"; -import PopupHeader from "@/components/PopuLayout/header"; -import PopupFooter from "@/components/PopuLayout/footer"; - -// 群组接口类型 -interface WechatGroup { - id: string; - chatroomId: string; - name: string; - avatar: string; - ownerWechatId: string; - ownerNickname: string; - ownerAvatar: string; -} - -// 组件属性接口 -interface GroupSelectionProps { - selectedGroups: string[]; - onSelect: (groups: string[]) => void; - onSelectDetail?: (groups: WechatGroup[]) => void; - placeholder?: string; - className?: string; - visible?: boolean; - onVisibleChange?: (visible: boolean) => void; - selectedListMaxHeight?: number; - showInput?: boolean; - showSelectedList?: boolean; - readonly?: boolean; - onConfirm?: (selectedIds: string[], selectedItems: WechatGroup[]) => void; // 新增 -} - +import SelectionPopup from "./selectionPopup"; +import { GroupSelectionProps } from "./data"; export default function GroupSelection({ selectedGroups, onSelect, @@ -50,22 +20,11 @@ export default function GroupSelection({ onConfirm, }: GroupSelectionProps) { const [popupVisible, setPopupVisible] = useState(false); - const [groups, setGroups] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [currentPage, setCurrentPage] = useState(1); - const [totalPages, setTotalPages] = useState(1); - const [totalGroups, setTotalGroups] = useState(0); - const [loading, setLoading] = useState(false); - - // 获取已选群聊详细信息 - const selectedGroupObjs = groups.filter(group => - selectedGroups.includes(group.id), - ); // 删除已选群聊 const handleRemoveGroup = (id: string) => { if (readonly) return; - onSelect(selectedGroups.filter(g => g !== id)); + onSelect(selectedGroups.filter(g => g.id !== id)); }; // 受控弹窗逻辑 @@ -78,73 +37,7 @@ export default function GroupSelection({ // 打开弹窗 const openPopup = () => { if (readonly) return; - setCurrentPage(1); - setSearchQuery(""); setRealVisible(true); - fetchGroups(1, ""); - }; - - // 当页码变化时,拉取对应页数据(弹窗已打开时) - useEffect(() => { - if (realVisible && currentPage !== 1) { - fetchGroups(currentPage, searchQuery); - } - }, [currentPage, realVisible, searchQuery]); - - // 搜索防抖 - useEffect(() => { - if (!realVisible) return; - const timer = setTimeout(() => { - setCurrentPage(1); - fetchGroups(1, searchQuery); - }, 500); - - return () => clearTimeout(timer); - }, [searchQuery, realVisible]); - - // 获取群聊列表API - const fetchGroups = async (page: number, keyword: string = "") => { - setLoading(true); - try { - const params: any = { - page, - limit: 20, - }; - - if (keyword.trim()) { - params.keyword = keyword.trim(); - } - - const response = await getGroupList(params); - if (response && response.list) { - setGroups(response.list); - setTotalGroups(response.total || 0); - setTotalPages(Math.ceil((response.total || 0) / 20)); - } - } catch (error) { - console.error("获取群聊列表失败:", error); - } finally { - setLoading(false); - } - }; - - // 处理群聊选择 - const handleGroupToggle = (groupId: string) => { - if (readonly) return; - - const newSelectedGroups = selectedGroups.includes(groupId) - ? selectedGroups.filter(id => id !== groupId) - : [...selectedGroups, groupId]; - - onSelect(newSelectedGroups); - - // 如果有 onSelectDetail 回调,传递完整的群聊对象 - if (onSelectDetail) { - const selectedGroupObjs = groups.filter(group => - newSelectedGroups.includes(group.id), - ); - onSelectDetail(selectedGroupObjs); - } }; // 获取显示文本 @@ -153,14 +46,6 @@ export default function GroupSelection({ return `已选择 ${selectedGroups.length} 个群聊`; }; - // 确认选择 - const handleConfirm = () => { - if (onConfirm) { - onConfirm(selectedGroups, selectedGroupObjs); - } - setRealVisible(false); - }; - return ( <> {/* 输入框 */} @@ -182,7 +67,7 @@ export default function GroupSelection({
)} {/* 已选群聊列表窗口 */} - {showSelectedList && selectedGroupObjs.length > 0 && ( + {showSelectedList && selectedGroups.length > 0 && (
- {selectedGroupObjs.map(group => ( -
-
- {group.name || group.chatroomId || group.id} + {selectedGroups.map(group => ( +
+
+ +
+
{group.name}
+
{group.chatroomId}
+
+ {!readonly && ( +
- {!readonly && ( -
))}
)} {/* 弹窗 */} - setRealVisible(false)} - position="bottom" - bodyStyle={{ height: "100vh" }} - > - fetchGroups(currentPage, searchQuery)} - /> - } - footer={ - setRealVisible(false)} - onConfirm={handleConfirm} - /> - } - > -
- {loading ? ( -
-
加载中...
-
- ) : groups.length > 0 ? ( -
- {groups.map(group => ( - - ))} -
- ) : ( -
-
- {searchQuery - ? `没有找到包含"${searchQuery}"的群聊` - : "没有找到群聊"} -
-
- )} -
-
-
+ ); } diff --git a/nkebao/src/components/GroupSelection/selectionPopup.tsx b/nkebao/src/components/GroupSelection/selectionPopup.tsx new file mode 100644 index 00000000..de7fa8af --- /dev/null +++ b/nkebao/src/components/GroupSelection/selectionPopup.tsx @@ -0,0 +1,220 @@ +import React, { useState, useEffect } from "react"; +import { Popup, Checkbox } from "antd-mobile"; + +import { getGroupList } 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 } from "./data"; +// 群组接口类型 +interface WechatGroup { + id: string; + name: string; + avatar: string; + chatroomId?: string; + ownerWechatId?: string; + ownerNickname?: string; + ownerAvatar?: string; +} + +// 弹窗属性接口 +interface SelectionPopupProps { + visible: boolean; + onVisibleChange: (visible: boolean) => void; + selectedGroups: GroupSelectionItem[]; + onSelect: (groups: GroupSelectionItem[]) => void; + onSelectDetail?: (groups: WechatGroup[]) => void; + readonly?: boolean; + onConfirm?: ( + selectedIds: string[], + selectedItems: GroupSelectionItem[], + ) => void; +} + +export default function SelectionPopup({ + visible, + onVisibleChange, + selectedGroups, + onSelect, + onSelectDetail, + readonly = false, + onConfirm, +}: SelectionPopupProps) { + const [groups, setGroups] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalGroups, setTotalGroups] = useState(0); + const [loading, setLoading] = useState(false); + + // 获取群聊列表API + const fetchGroups = async (page: number, keyword: string = "") => { + setLoading(true); + try { + const params: any = { + page, + limit: 20, + }; + + if (keyword.trim()) { + params.keyword = keyword.trim(); + } + + const response = await getGroupList(params); + if (response && response.list) { + setGroups(response.list); + setTotalGroups(response.total || 0); + setTotalPages(Math.ceil((response.total || 0) / 20)); + } + } catch (error) { + console.error("获取群聊列表失败:", error); + } finally { + setLoading(false); + } + }; + + // 处理群聊选择 + const handleGroupToggle = (group: GroupSelectionItem) => { + if (readonly) return; + + const newSelectedGroups = selectedGroups.some(g => g.id === group.id) + ? selectedGroups.filter(g => g.id !== group.id) + : selectedGroups.concat(group); + + onSelect(newSelectedGroups); + + // 如果有 onSelectDetail 回调,传递完整的群聊对象 + if (onSelectDetail) { + const selectedGroupObjs = groups.filter(group => + newSelectedGroups.some(g => g.id === group.id), + ); + onSelectDetail(selectedGroupObjs); + } + }; + + // 确认选择 + const handleConfirm = () => { + if (onConfirm) { + onConfirm( + selectedGroups.map(g => g.id), + selectedGroups, + ); + } + onVisibleChange(false); + }; + + // 弹窗打开时初始化数据(只执行一次) + useEffect(() => { + if (visible) { + setCurrentPage(1); + setSearchQuery(""); + fetchGroups(1, ""); + } + }, [visible]); + + // 搜索防抖(只在弹窗打开且搜索词变化时执行) + useEffect(() => { + if (!visible || searchQuery === "") return; + + const timer = setTimeout(() => { + setCurrentPage(1); + fetchGroups(1, searchQuery); + }, 500); + + return () => clearTimeout(timer); + }, [searchQuery, visible]); + + // 页码变化时请求数据(只在弹窗打开且页码不是1时执行) + useEffect(() => { + if (!visible || currentPage === 1) return; + fetchGroups(currentPage, searchQuery); + }, [currentPage, visible, searchQuery]); + + return ( + onVisibleChange(false)} + position="bottom" + bodyStyle={{ height: "100vh" }} + > + fetchGroups(currentPage, searchQuery)} + /> + } + footer={ + onVisibleChange(false)} + onConfirm={handleConfirm} + /> + } + > +
+ {loading ? ( +
+
加载中...
+
+ ) : groups.length > 0 ? ( +
+ {groups.map(group => ( +
+ g.id === group.id)} + onChange={() => !readonly && handleGroupToggle(group)} + disabled={readonly} + style={{ marginRight: 12 }} + /> +
+
+ {group.avatar ? ( + {group.name} + ) : ( + group.name.charAt(0) + )} +
+
+
{group.name}
+
+ 群ID: {group.chatroomId} +
+ {group.ownerNickname && ( +
+ 群主: {group.ownerNickname} +
+ )} +
+
+
+ ))} +
+ ) : ( +
+
+ {searchQuery + ? `没有找到包含"${searchQuery}"的群聊` + : "没有找到群聊"} +
+
+ )} +
+
+
+ ); +} diff --git a/nkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx b/nkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx index 0b9fbd7a..b26279fd 100644 --- a/nkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx +++ b/nkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx @@ -86,12 +86,6 @@ const BasicSettings: React.FC = ({ } }, [formData, onChange]); - useEffect(() => { - const today = new Date().toLocaleDateString("zh-CN").replace(/\//g, ""); - const sceneItem = sceneList.find(v => formData.scenario === v.id); - onChange({ ...formData, name: `${sceneItem?.name || "海报"}${today}` }); - }, [isEdit]); - useEffect(() => { setTips(formData.tips || ""); }, [formData.tips]); @@ -202,11 +196,6 @@ const BasicSettings: React.FC = ({ window.URL.revokeObjectURL(url); }; - // 图片预览关闭 - const handleImagePreviewClose = () => { - setIsPreviewOpen(false); - }; - // 当前选中的场景对象 const currentScene = sceneList.find(s => s.id === formData.scenario); //打开订单 @@ -537,29 +526,7 @@ const BasicSettings: React.FC = ({
)} - {/* 微信群设置区块,仅在选择微信群场景时显示 */} - {formData.scenario === 7 && ( -
-
- onChange({ ...formData, weixinqunName })} - /> -
-
- onChange({ ...formData, weixinqunNotice })} - /> -
-
- )} +
是否启用 { - const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState("devices"); + const [activeTab, setActiveTab] = useState("groups"); // 设备选择状态 const [selectedDevices, setSelectedDevices] = useState([]); @@ -22,7 +20,9 @@ const ComponentTest: React.FC = () => { const [selectedFriends, setSelectedFriends] = useState([]); // 群组选择状态 - const [selectedGroups, setSelectedGroups] = useState([]); + const [selectedGroups, setSelectedGroups] = useState( + [], + ); // 内容库选择状态 const [selectedLibraries, setSelectedLibraries] = useState([]); @@ -41,6 +41,32 @@ const ComponentTest: React.FC = () => { )} + +
+

GroupSelection 组件测试

+ +
+ 已选群组: {selectedGroups.length} 个 +
+ 群组ID:{" "} + {selectedGroups.map(g => g.id).join(", ") || "无"} +
+
+
+

DeviceSelection 组件测试

@@ -91,31 +117,6 @@ const ComponentTest: React.FC = () => {
- -
-

GroupSelection 组件测试

- -
- 已选群组: {selectedGroups.length} 个 -
- 群组ID: {selectedGroups.join(", ") || "无"} -
-
-
-

diff --git a/nkebao/src/pages/mobile/workspace/group-push/detail/index.tsx b/nkebao/src/pages/mobile/workspace/group-push/detail/index.tsx index ab2a9a0f..c38ba67e 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/detail/index.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/detail/index.tsx @@ -140,7 +140,6 @@ const Detail: React.FC = () => { 群发推送详情

} - footer={} >
diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx index 34ba3f9e..2180c9d7 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx @@ -53,7 +53,7 @@ const BasicSettings: React.FC = ({ return (
-
+
{/* 任务名称 */}
*任务名称: @@ -123,11 +123,12 @@ const BasicSettings: React.FC = ({ {/* 推送顺序 */}
推送顺序: - +
@@ -135,10 +136,11 @@ const BasicSettings: React.FC = ({ type={values.pushOrder === "latest" ? "primary" : "default"} onClick={() => handleChange("pushOrder", "latest")} disabled={loading} + style={{ borderRadius: "0 6px 6px 0", marginLeft: -1 }} > 按最新 - +
{/* 是否循环推送 */}
void; + selectedGroups: string[]; + onGroupsChange: (groups: string[]) => void; onPrevious: () => void; onNext: () => void; onSave: () => void; @@ -23,69 +12,6 @@ interface GroupSelectorProps { loading?: boolean; } -const mockGroups: WechatGroup[] = [ - { - id: "1", - name: "VIP客户群", - avatar: "https://via.placeholder.com/40", - serviceAccount: { - id: "1", - name: "客服小美", - avatar: "https://via.placeholder.com/32", - }, - }, - { - id: "2", - name: "潜在客户群", - avatar: "https://via.placeholder.com/40", - serviceAccount: { - id: "1", - name: "客服小美", - avatar: "https://via.placeholder.com/32", - }, - }, - { - id: "3", - name: "活动群", - avatar: "https://via.placeholder.com/40", - serviceAccount: { - id: "2", - name: "推广专员", - avatar: "https://via.placeholder.com/32", - }, - }, - { - id: "4", - name: "推广群", - avatar: "https://via.placeholder.com/40", - serviceAccount: { - id: "2", - name: "推广专员", - avatar: "https://via.placeholder.com/32", - }, - }, - { - id: "5", - name: "新客户群", - avatar: "https://via.placeholder.com/40", - serviceAccount: { - id: "3", - name: "销售小王", - avatar: "https://via.placeholder.com/32", - }, - }, - { - id: "6", - name: "体验群", - avatar: "https://via.placeholder.com/40", - serviceAccount: { - id: "3", - name: "销售小王", - avatar: "https://via.placeholder.com/32", - }, - }, -]; - const GroupSelector: React.FC = ({ selectedGroups, onGroupsChange, @@ -95,150 +21,59 @@ const GroupSelector: React.FC = ({ onCancel, loading = false, }) => { - const [searchTerm, setSearchTerm] = useState(""); - const [groups] = useState(mockGroups); + // 将WechatGroup转换为GroupSelection需要的格式 + // 群组选择状态 - const filteredGroups = groups.filter( - group => - group.name.toLowerCase().includes(searchTerm.toLowerCase()) || - group.serviceAccount.name - .toLowerCase() - .includes(searchTerm.toLowerCase()), - ); - - const handleGroupToggle = (group: WechatGroup, checked: boolean) => { - if (checked) { - onGroupsChange([...selectedGroups, group]); - } else { - onGroupsChange(selectedGroups.filter(g => g.id !== group.id)); - } - }; - - const handleSelectAll = () => { - if (selectedGroups.length === filteredGroups.length) { - onGroupsChange([]); - } else { - onGroupsChange(filteredGroups); - } - }; - - const isGroupSelected = (groupId: string) => { - return selectedGroups.some(group => group.id === groupId); + const handleGroupSelect = (groupIds: string[]) => { + onGroupsChange(groupIds); }; return ( -
- -
-
- 搜索群组: - } - placeholder="搜索群组名称或客服名称" - value={searchTerm} - onChange={e => setSearchTerm(e.target.value)} - disabled={loading} - style={{ marginTop: 4 }} - /> -
-
- 0 - } - onChange={handleSelectAll} - disabled={loading} - > - 全选 ({selectedGroups.length}/{filteredGroups.length}) - -
-
- {filteredGroups.map(group => ( -
- handleGroupToggle(group, e.target.checked)} - disabled={loading} - style={{ marginRight: 8 }} - /> - } - style={{ marginRight: 8 }} - /> -
-
{group.name}
-
- - {group.serviceAccount.name} -
-
-
- ))} - {filteredGroups.length === 0 && ( -
- - 没有找到匹配的群组 -
- )} -
-
-
+
+
+

+ 选择推送群组 +

+

+ 请选择要推送消息的微信群组 +

+
+ + +
- - - +
+ + + +
); diff --git a/nkebao/src/pages/mobile/workspace/group-push/list/index.tsx b/nkebao/src/pages/mobile/workspace/group-push/list/index.tsx index b12dedc5..e578a82e 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/list/index.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/list/index.tsx @@ -9,7 +9,6 @@ import { ClockCircleOutlined, EditOutlined, DeleteOutlined, - EyeOutlined, CopyOutlined, SendOutlined, CarryOutOutlined, From 67c76d8b0483e777b771a47ed0fb69b2f5756da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 7 Aug 2025 16:15:45 +0800 Subject: [PATCH 03/31] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E5=AD=98?= =?UTF-8?q?=E4=B8=AA=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/pages/mobile/test/select.tsx | 58 ++--- .../form/components/BasicSettings.tsx | 36 +-- .../form/components/ContentSelector.tsx | 239 +++--------------- .../form/components/GroupSelector.tsx | 47 +--- .../workspace/group-push/form/index.tsx | 149 +++++++---- nkebao/src/styles/global.scss | 12 + 6 files changed, 188 insertions(+), 353 deletions(-) diff --git a/nkebao/src/pages/mobile/test/select.tsx b/nkebao/src/pages/mobile/test/select.tsx index d1a1d63c..6255ffce 100644 --- a/nkebao/src/pages/mobile/test/select.tsx +++ b/nkebao/src/pages/mobile/test/select.tsx @@ -11,7 +11,7 @@ import { isDevelopment } from "@/utils/env"; import { GroupSelectionItem } from "@/components/GroupSelection/data"; const ComponentTest: React.FC = () => { - const [activeTab, setActiveTab] = useState("groups"); + const [activeTab, setActiveTab] = useState("libraries"); // 设备选择状态 const [selectedDevices, setSelectedDevices] = useState([]); @@ -41,6 +41,34 @@ const ComponentTest: React.FC = () => { )} + +
+

+ ContentLibrarySelection 组件测试 +

+ +
+ 已选内容库: {selectedLibraries.length} 个 +
+ 内容库ID:{" "} + {selectedLibraries.join(", ") || "无"} +
+
+
+

GroupSelection 组件测试

@@ -117,34 +145,6 @@ const ComponentTest: React.FC = () => {
- -
-

- ContentLibrarySelection 组件测试 -

- -
- 已选内容库: {selectedLibraries.length} 个 -
- 内容库ID:{" "} - {selectedLibraries.join(", ") || "无"} -
-
-
-

AccountSelection 组件测试

diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx index 2180c9d7..255842e4 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx @@ -15,7 +15,6 @@ interface BasicSettingsProps { }; onNext: (values: any) => void; onSave: (values: any) => void; - onCancel: () => void; loading?: boolean; } @@ -32,7 +31,6 @@ const BasicSettings: React.FC = ({ }, onNext, onSave, - onCancel, loading = false, }) => { const [values, setValues] = useState(defaultValues); @@ -123,7 +121,7 @@ const BasicSettings: React.FC = ({ {/* 推送顺序 */}
推送顺序: -
+
-
- - - -
); }; diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx index ebcb1b52..1c6f7634 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; -import { Button, Card, Input, Checkbox, Avatar } from "antd"; -import { FileTextOutlined, SearchOutlined } from "@ant-design/icons"; +import ContentLibrarySelection from "@/components/ContentLibrarySelection"; interface ContentLibrary { id: string; @@ -17,226 +16,60 @@ interface ContentSelectorProps { onPrevious: () => void; onNext: () => void; onSave: () => void; - onCancel: () => void; loading?: boolean; } -const mockLibraries: ContentLibrary[] = [ - { - id: "1", - name: "产品推广内容库", - targets: [ - { id: "1", avatar: "https://via.placeholder.com/32" }, - { id: "2", avatar: "https://via.placeholder.com/32" }, - { id: "3", avatar: "https://via.placeholder.com/32" }, - ], - }, - { - id: "2", - name: "活动宣传内容库", - targets: [ - { id: "4", avatar: "https://via.placeholder.com/32" }, - { id: "5", avatar: "https://via.placeholder.com/32" }, - ], - }, - { - id: "3", - name: "客户服务内容库", - targets: [ - { id: "6", avatar: "https://via.placeholder.com/32" }, - { id: "7", avatar: "https://via.placeholder.com/32" }, - { id: "8", avatar: "https://via.placeholder.com/32" }, - { id: "9", avatar: "https://via.placeholder.com/32" }, - ], - }, - { - id: "4", - name: "节日问候内容库", - targets: [ - { id: "10", avatar: "https://via.placeholder.com/32" }, - { id: "11", avatar: "https://via.placeholder.com/32" }, - ], - }, - { - id: "5", - name: "新品发布内容库", - targets: [ - { id: "12", avatar: "https://via.placeholder.com/32" }, - { id: "13", avatar: "https://via.placeholder.com/32" }, - { id: "14", avatar: "https://via.placeholder.com/32" }, - ], - }, -]; - const ContentSelector: React.FC = ({ selectedLibraries, onLibrariesChange, onPrevious, onNext, onSave, - onCancel, loading = false, }) => { - const [searchTerm, setSearchTerm] = useState(""); - const [libraries] = useState(mockLibraries); + // 将 ContentLibrary[] 转换为 string[] 用于 ContentLibrarySelection + const selectedLibraryIds = selectedLibraries.map(lib => lib.id); - const filteredLibraries = libraries.filter(library => - library.name.toLowerCase().includes(searchTerm.toLowerCase()), - ); - - const handleLibraryToggle = (library: ContentLibrary, checked: boolean) => { - if (checked) { - onLibrariesChange([...selectedLibraries, library]); - } else { - onLibrariesChange(selectedLibraries.filter(l => l.id !== library.id)); - } + // 处理选择变化 + const handleLibrariesChange = (libraryIds: string[]) => { + // 这里需要根据选中的ID重新构建ContentLibrary对象 + // 由于ContentLibrarySelection只返回ID,我们需要从原始数据中获取完整信息 + // 暂时使用简化的处理方式 + const newSelectedLibraries = libraryIds.map(id => ({ + id, + name: `内容库 ${id}`, // 这里应该从API获取完整信息 + targets: [], // 这里应该从API获取完整信息 + })); + onLibrariesChange(newSelectedLibraries); }; - const handleSelectAll = () => { - if (selectedLibraries.length === filteredLibraries.length) { - onLibrariesChange([]); - } else { - onLibrariesChange(filteredLibraries); - } - }; - - const isLibrarySelected = (libraryId: string) => { - return selectedLibraries.some(library => library.id === libraryId); + // 处理选择详情变化 + const handleSelectDetail = (libraries: any[]) => { + // 将API返回的数据转换为ContentLibrary格式 + const convertedLibraries = libraries.map(lib => ({ + id: lib.id, + name: lib.name, + targets: [], // 这里需要根据实际情况获取targets数据 + })); + onLibrariesChange(convertedLibraries); }; return (
- -
-
- 搜索内容库: - } - placeholder="搜索内容库名称" - value={searchTerm} - onChange={e => setSearchTerm(e.target.value)} - disabled={loading} - style={{ marginTop: 4 }} - /> -
-
- 0 - } - onChange={handleSelectAll} - disabled={loading} - > - 全选 ({selectedLibraries.length}/{filteredLibraries.length}) - -
-
- {filteredLibraries.map(library => ( -
- handleLibraryToggle(library, e.target.checked)} - disabled={loading} - style={{ marginRight: 8 }} - /> - } - size={40} - style={{ - marginRight: 8, - background: "#e6f7ff", - color: "#1890ff", - }} - /> -
-
{library.name}
-
- 包含 {library.targets.length} 条内容 -
-
-
- {library.targets.slice(0, 3).map(target => ( - - ))} - {library.targets.length > 3 && ( -
- +{library.targets.length - 3} -
- )} -
-
- ))} - {filteredLibraries.length === 0 && ( -
- - 没有找到匹配的内容库 -
- )} -
+
+
+ 选择内容库:
- -
- - - - +
); diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx index 7c18cee3..b77cecaf 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import { Button } from "antd"; import GroupSelection from "@/components/GroupSelection"; +import { GroupSelectionItem } from "@/components/GroupSelection/data"; interface GroupSelectorProps { selectedGroups: string[]; @@ -8,7 +8,6 @@ interface GroupSelectorProps { onPrevious: () => void; onNext: () => void; onSave: () => void; - onCancel: () => void; loading?: boolean; } @@ -18,13 +17,19 @@ const GroupSelector: React.FC = ({ onPrevious, onNext, onSave, - onCancel, loading = false, }) => { - // 将WechatGroup转换为GroupSelection需要的格式 - // 群组选择状态 + // 将string[]转换为GroupSelectionItem[] + const selectedGroupItems: GroupSelectionItem[] = selectedGroups.map(id => ({ + id, + name: `群组 ${id}`, + avatar: "", + chatroomId: id, + })); - const handleGroupSelect = (groupIds: string[]) => { + const handleGroupSelect = (groupItems: GroupSelectionItem[]) => { + // 将GroupSelectionItem[]转换回string[] + const groupIds = groupItems.map(item => item.id); onGroupsChange(groupIds); }; @@ -40,41 +45,13 @@ const GroupSelector: React.FC = ({
- -
- -
- - - -
-
); }; diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx index 198b8ab0..71d3b2ae 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx @@ -1,16 +1,14 @@ -import React, { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { Button } from "antd-mobile"; -import { NavBar } from "antd-mobile"; -import { ArrowLeftOutlined } from "@ant-design/icons"; +import React, { useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { Button } from "antd"; import { createGroupPushTask } from "./index.api"; import Layout from "@/components/Layout/Layout"; import StepIndicator from "@/components/StepIndicator"; import BasicSettings from "./components/BasicSettings"; import GroupSelector from "./components/GroupSelector"; import ContentSelector from "./components/ContentSelector"; -import type { WechatGroup, ContentLibrary, FormData } from "./index.data"; - +import type { ContentLibrary, FormData } from "./index.data"; +import NavCommon from "@/components/NavCommon"; const steps = [ { id: 1, title: "步骤 1", subtitle: "基础设置" }, { id: 2, title: "步骤 2", subtitle: "选择社群" }, @@ -19,6 +17,7 @@ const steps = [ ]; const NewGroupPush: React.FC = () => { + const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [currentStep, setCurrentStep] = useState(1); const [loading, setLoading] = useState(false); @@ -36,13 +35,28 @@ const NewGroupPush: React.FC = () => { }); const [isEditMode, setIsEditMode] = useState(false); - const handleBasicSettingsNext = (values: Partial) => { + useEffect(() => { + if (!id) return; + setIsEditMode(true); + }, [id]); + + const handleBasicSettingsChange = (values: Partial) => { setFormData(prev => ({ ...prev, ...values })); - setCurrentStep(2); }; - const handleGroupsChange = (groups: WechatGroup[]) => { - setFormData(prev => ({ ...prev, groups })); + const handleGroupsChange = (groups: string[]) => { + // 将string[]转换为WechatGroup[] + const convertedGroups = groups.map(id => ({ + id, + name: `群组 ${id}`, + avatar: "", + serviceAccount: { + id: "", + name: "", + avatar: "", + }, + })); + setFormData(prev => ({ ...prev, groups: convertedGroups })); }; const handleLibrariesChange = (contentLibraries: ContentLibrary[]) => { @@ -99,30 +113,78 @@ const NewGroupPush: React.FC = () => { } }; - const handleCancel = () => { - navigate("/workspace/group-push"); + const handlePrevious = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1); + } + }; + + const handleNext = () => { + if (currentStep < 4) { + setCurrentStep(currentStep + 1); + } + }; + + const canGoNext = () => { + switch (currentStep) { + case 1: { + return formData.name.trim() !== ""; + } + case 2: { + // 选择社群:检查是否选择了群组 + const groupsValid = + formData.groups.length > 0 && formData.groups.length <= 50; // 添加上限检查 + return groupsValid; + } + case 3: { + // 选择内容库:检查是否选择了内容库 + const librariesValid = + formData.contentLibraries.length > 0 && + formData.contentLibraries.length <= 20; // 添加上限检查 + return librariesValid; + } + case 4: { + // 京东联盟:可以进入下一步(保存) + // 这里可以添加京东联盟相关的验证逻辑 + return true; + } + default: + return false; + } + }; + + const renderFooter = () => { + if (currentStep === 4) { + return ( +
+ + +
+ ); + } + + return ( +
+ {currentStep > 1 && ( + + )} + +
+ ); }; return ( - navigate(-1)} - /> -
- } - > - - {isEditMode ? "编辑任务" : "新建任务"} - - - } + header={} + footer={renderFooter()} >
@@ -139,20 +201,18 @@ const NewGroupPush: React.FC = () => { isImmediatePush: formData.isImmediatePush, isEnabled: formData.isEnabled, }} - onNext={handleBasicSettingsNext} + onNext={handleBasicSettingsChange} onSave={handleSave} - onCancel={handleCancel} loading={loading} /> )} {currentStep === 2 && ( g.id)} onGroupsChange={handleGroupsChange} onPrevious={() => setCurrentStep(1)} onNext={() => setCurrentStep(3)} onSave={handleSave} - onCancel={handleCancel} loading={loading} /> )} @@ -163,31 +223,12 @@ const NewGroupPush: React.FC = () => { onPrevious={() => setCurrentStep(2)} onNext={() => setCurrentStep(4)} onSave={handleSave} - onCancel={handleCancel} loading={loading} /> )} {currentStep === 4 && (
京东联盟设置(此步骤为占位,实际功能待开发) -
- - - -
)}
diff --git a/nkebao/src/styles/global.scss b/nkebao/src/styles/global.scss index 6e4995b9..f822f8eb 100644 --- a/nkebao/src/styles/global.scss +++ b/nkebao/src/styles/global.scss @@ -304,3 +304,15 @@ button { } } } + +//底部下一步上一步组合按钮 +.footer-btn-group { + padding: 12px; + display: flex; + gap: 8px; + justify-content: center; + background-color: #fff; + .ant-btn { + flex: 1; + } +} From 504e8ff9b61b5ee2f7f3fb36550e42e275c19672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 7 Aug 2025 17:43:20 +0800 Subject: [PATCH 04/31] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=A4=BE=E7=BE=A4=E6=9E=84=E5=BB=BA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/components/BasicSettings.tsx | 372 +++++++++--------- .../form/components/ContentSelector.tsx | 159 +++++--- .../form/components/GroupSelector.tsx | 130 +++--- .../workspace/group-push/form/index.data.ts | 3 +- .../workspace/group-push/form/index.tsx | 152 +++---- 5 files changed, 463 insertions(+), 353 deletions(-) diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx index 255842e4..e83332ad 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/BasicSettings.tsx @@ -1,6 +1,5 @@ -import React, { useState } from "react"; -import { Input, Button, Card, Switch } from "antd"; -import { MinusOutlined, PlusOutlined } from "@ant-design/icons"; +import React, { useImperativeHandle, forwardRef } from "react"; +import { Input, Button, Card, Switch, Form, InputNumber } from "antd"; interface BasicSettingsProps { defaultValues?: { @@ -18,194 +17,199 @@ interface BasicSettingsProps { loading?: boolean; } -const BasicSettings: React.FC = ({ - defaultValues = { - name: "", - pushTimeStart: "06:00", - pushTimeEnd: "23:59", - dailyPushCount: 20, - pushOrder: "latest", - isLoopPush: false, - isImmediatePush: false, - isEnabled: false, - }, - onNext, - onSave, - loading = false, -}) => { - const [values, setValues] = useState(defaultValues); +export interface BasicSettingsRef { + validate: () => Promise; + getValues: () => any; +} - const handleChange = (field: string, value: any) => { - setValues(prev => ({ ...prev, [field]: value })); - }; +const BasicSettings = forwardRef( + ( + { + defaultValues = { + name: "", + pushTimeStart: "06:00", + pushTimeEnd: "23:59", + dailyPushCount: 20, + pushOrder: "latest", + isLoopPush: false, + isImmediatePush: false, + isEnabled: false, + }, + }, + ref, + ) => { + const [form] = Form.useForm(); - const handleCountChange = (increment: boolean) => { - setValues(prev => ({ - ...prev, - dailyPushCount: increment - ? prev.dailyPushCount + 1 - : Math.max(1, prev.dailyPushCount - 1), + // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + validate: async () => { + try { + await form.validateFields(); + return true; + } catch (error) { + console.log("BasicSettings 表单验证失败:", error); + return false; + } + }, + getValues: () => { + return form.getFieldsValue(); + }, })); - }; - return ( -
- -
- {/* 任务名称 */} -
- *任务名称: - handleChange("name", e.target.value)} - placeholder="请输入任务名称" - style={{ marginTop: 4 }} - /> -
- {/* 允许推送的时间段 */} -
- 允许推送的时间段: -
- handleChange("pushTimeStart", e.target.value)} - style={{ width: 120 }} - /> - - handleChange("pushTimeEnd", e.target.value)} - style={{ width: 120 }} - /> -
-
- {/* 每日推送 */} -
- 每日推送: -
+ +
{ + // 可以在这里处理表单值变化 + }} + > + {/* 任务名称 */} + -
-
- {/* 推送顺序 */} -
- 推送顺序: -
- - -
-
- {/* 是否循环推送 */} -
- 是否循环推送: - handleChange("isLoopPush", checked)} - disabled={loading} - /> -
- {/* 是否立即推送 */} -
- 是否立即推送: - handleChange("isImmediatePush", checked)} - disabled={loading} - /> -
- {values.isImmediatePush && ( -
+ + {/* 推送顺序 */} + - 如果启用,系统会把内容库里所有的内容按顺序推送到指定的社群 -
- )} - {/* 是否启用 */} -
- 是否启用: - handleChange("isEnabled", checked)} - disabled={loading} - /> -
-
-
-
- ); -}; +
+ + +
+ + + {/* 是否循环推送 */} + + + + + {/* 是否立即推送 */} + + + + + {/* 是否启用 */} + + + + + {/* 立即推送提示 */} + + {() => { + const isImmediatePush = form.getFieldValue("isImmediatePush"); + return isImmediatePush ? ( +
+ 如果启用,系统会把内容库里所有的内容按顺序推送到指定的社群 +
+ ) : null; + }} +
+ + +
+ ); + }, +); + +BasicSettings.displayName = "BasicSettings"; export default BasicSettings; diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx index 1c6f7634..e96c6e14 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx @@ -1,4 +1,5 @@ -import React, { useState } from "react"; +import React, { useImperativeHandle, forwardRef } from "react"; +import { Form, Card } from "antd"; import ContentLibrarySelection from "@/components/ContentLibrarySelection"; interface ContentLibrary { @@ -19,60 +20,116 @@ interface ContentSelectorProps { loading?: boolean; } -const ContentSelector: React.FC = ({ - selectedLibraries, - onLibrariesChange, - onPrevious, - onNext, - onSave, - loading = false, -}) => { - // 将 ContentLibrary[] 转换为 string[] 用于 ContentLibrarySelection - const selectedLibraryIds = selectedLibraries.map(lib => lib.id); +export interface ContentSelectorRef { + validate: () => Promise; + getValues: () => any; +} - // 处理选择变化 - const handleLibrariesChange = (libraryIds: string[]) => { - // 这里需要根据选中的ID重新构建ContentLibrary对象 - // 由于ContentLibrarySelection只返回ID,我们需要从原始数据中获取完整信息 - // 暂时使用简化的处理方式 - const newSelectedLibraries = libraryIds.map(id => ({ - id, - name: `内容库 ${id}`, // 这里应该从API获取完整信息 - targets: [], // 这里应该从API获取完整信息 +const ContentSelector = forwardRef( + ( + { + selectedLibraries, + onLibrariesChange, + onPrevious, + onNext, + onSave, + loading = false, + }, + ref, + ) => { + const [form] = Form.useForm(); + + // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + validate: async () => { + try { + await form.validateFields(); + return true; + } catch (error) { + console.log("ContentSelector 表单验证失败:", error); + return false; + } + }, + getValues: () => { + return form.getFieldsValue(); + }, })); - onLibrariesChange(newSelectedLibraries); - }; - // 处理选择详情变化 - const handleSelectDetail = (libraries: any[]) => { - // 将API返回的数据转换为ContentLibrary格式 - const convertedLibraries = libraries.map(lib => ({ - id: lib.id, - name: lib.name, - targets: [], // 这里需要根据实际情况获取targets数据 - })); - onLibrariesChange(convertedLibraries); - }; + // 将 ContentLibrary[] 转换为 string[] 用于 ContentLibrarySelection + const selectedLibraryIds = selectedLibraries.map(lib => lib.id); - return ( -
-
-
- 选择内容库: -
- + // 处理选择变化 + const handleLibrariesChange = (libraryIds: string[]) => { + // 这里需要根据选中的ID重新构建ContentLibrary对象 + // 由于ContentLibrarySelection只返回ID,我们需要从原始数据中获取完整信息 + // 暂时使用简化的处理方式 + const newSelectedLibraries = libraryIds.map(id => ({ + id, + name: `内容库 ${id}`, // 这里应该从API获取完整信息 + targets: [], // 这里应该从API获取完整信息 + })); + onLibrariesChange(newSelectedLibraries); + form.setFieldValue("contentLibraries", libraryIds); + }; + + // 处理选择详情变化 + const handleSelectDetail = (libraries: any[]) => { + // 将API返回的数据转换为ContentLibrary格式 + const convertedLibraries = libraries.map(lib => ({ + id: lib.id, + name: lib.name, + targets: [], // 这里需要根据实际情况获取targets数据 + })); + onLibrariesChange(convertedLibraries); + form.setFieldValue( + "contentLibraries", + libraries.map(lib => lib.id), + ); + }; + + return ( +
+ +
+
+

+ 选择内容库 +

+

+ 请选择要推送的内容库 +

+
+ + + + +
+
-
- ); -}; + ); + }, +); + +ContentSelector.displayName = "ContentSelector"; export default ContentSelector; diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx index b77cecaf..f744295e 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/GroupSelector.tsx @@ -1,59 +1,95 @@ -import React, { useState } from "react"; +import React, { useImperativeHandle, forwardRef } from "react"; +import { Form, Card } from "antd"; import GroupSelection from "@/components/GroupSelection"; import { GroupSelectionItem } from "@/components/GroupSelection/data"; interface GroupSelectorProps { - selectedGroups: string[]; - onGroupsChange: (groups: string[]) => void; + selectedGroups: GroupSelectionItem[]; onPrevious: () => void; - onNext: () => void; - onSave: () => void; - loading?: boolean; + onNext: (data: { + wechatGroups: string[]; + wechatGroupsOptions: GroupSelectionItem[]; + }) => void; } -const GroupSelector: React.FC = ({ - selectedGroups, - onGroupsChange, - onPrevious, - onNext, - onSave, - loading = false, -}) => { - // 将string[]转换为GroupSelectionItem[] - const selectedGroupItems: GroupSelectionItem[] = selectedGroups.map(id => ({ - id, - name: `群组 ${id}`, - avatar: "", - chatroomId: id, - })); +export interface GroupSelectorRef { + validate: () => Promise; + getValues: () => any; +} - const handleGroupSelect = (groupItems: GroupSelectionItem[]) => { - // 将GroupSelectionItem[]转换回string[] - const groupIds = groupItems.map(item => item.id); - onGroupsChange(groupIds); - }; +const GroupSelector = forwardRef( + ({ selectedGroups, onNext }, ref) => { + const [form] = Form.useForm(); - return ( -
-
-

- 选择推送群组 -

-

- 请选择要推送消息的微信群组 -

-
+ // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + validate: async () => { + try { + form.setFieldsValue({ + wechatGroups: selectedGroups.map(item => item.id), + }); + await form.validateFields(); + return true; + } catch (error) { + console.log("GroupSelector 表单验证失败:", error); + return false; + } + }, + getValues: () => { + return form.getFieldsValue(); + }, + })); - -
- ); -}; + // 群组选择 + const handleGroupSelect = (wechatGroupsOptions: GroupSelectionItem[]) => { + const wechatGroups = wechatGroupsOptions.map(item => item.id); + form.setFieldValue("wechatGroups", wechatGroups); + onNext({ wechatGroups, wechatGroupsOptions }); + }; + + return ( + +
+
+

+ 选择推送群组 +

+

+ 请选择要推送消息的微信群组 +

+
+ + + + +
+
+ ); + }, +); + +GroupSelector.displayName = "GroupSelector"; export default GroupSelector; diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/index.data.ts b/nkebao/src/pages/mobile/workspace/group-push/form/index.data.ts index 1604aef2..8d7e81a3 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/index.data.ts +++ b/nkebao/src/pages/mobile/workspace/group-push/form/index.data.ts @@ -27,6 +27,7 @@ export interface FormData { isLoopPush: boolean; isImmediatePush: boolean; isEnabled: boolean; - groups: WechatGroup[]; contentLibraries: ContentLibrary[]; + wechatGroups: string[]; + [key: string]: any; } diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx index 71d3b2ae..158574b6 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/index.tsx @@ -1,14 +1,17 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Button } from "antd"; import { createGroupPushTask } from "./index.api"; import Layout from "@/components/Layout/Layout"; import StepIndicator from "@/components/StepIndicator"; -import BasicSettings from "./components/BasicSettings"; -import GroupSelector from "./components/GroupSelector"; -import ContentSelector from "./components/ContentSelector"; +import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings"; +import GroupSelector, { GroupSelectorRef } from "./components/GroupSelector"; +import ContentSelector, { + ContentSelectorRef, +} from "./components/ContentSelector"; import type { ContentLibrary, FormData } from "./index.data"; import NavCommon from "@/components/NavCommon"; +import { GroupSelectionItem } from "@/components/GroupSelection/data"; const steps = [ { id: 1, title: "步骤 1", subtitle: "基础设置" }, { id: 2, title: "步骤 2", subtitle: "选择社群" }, @@ -21,6 +24,9 @@ const NewGroupPush: React.FC = () => { const navigate = useNavigate(); const [currentStep, setCurrentStep] = useState(1); const [loading, setLoading] = useState(false); + const [wechatGroupsOptions, setWechatGroupsOptions] = useState< + GroupSelectionItem[] + >([]); const [formData, setFormData] = useState({ name: "", pushTimeStart: "06:00", @@ -30,11 +36,16 @@ const NewGroupPush: React.FC = () => { isLoopPush: false, isImmediatePush: false, isEnabled: false, - groups: [], + wechatGroups: [], contentLibraries: [], }); const [isEditMode, setIsEditMode] = useState(false); + // 创建子组件的ref + const basicSettingsRef = useRef(null); + const groupSelectorRef = useRef(null); + const contentSelectorRef = useRef(null); + useEffect(() => { if (!id) return; setIsEditMode(true); @@ -44,19 +55,16 @@ const NewGroupPush: React.FC = () => { setFormData(prev => ({ ...prev, ...values })); }; - const handleGroupsChange = (groups: string[]) => { - // 将string[]转换为WechatGroup[] - const convertedGroups = groups.map(id => ({ - id, - name: `群组 ${id}`, - avatar: "", - serviceAccount: { - id: "", - name: "", - avatar: "", - }, + //群组选择 + const handleGroupsChange = (data: { + wechatGroups: string[]; + wechatGroupsOptions: GroupSelectionItem[]; + }) => { + setFormData(prev => ({ + ...prev, + wechatGroups: data.wechatGroups, })); - setFormData(prev => ({ ...prev, groups: convertedGroups })); + setWechatGroupsOptions(data.wechatGroupsOptions); }; const handleLibrariesChange = (contentLibraries: ContentLibrary[]) => { @@ -119,64 +127,66 @@ const NewGroupPush: React.FC = () => { } }; - const handleNext = () => { + const handleNext = async () => { if (currentStep < 4) { - setCurrentStep(currentStep + 1); - } - }; + try { + let isValid = false; - const canGoNext = () => { - switch (currentStep) { - case 1: { - return formData.name.trim() !== ""; + 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: + // 调用 GroupSelector 的表单校验 + isValid = (await groupSelectorRef.current?.validate()) || false; + if (isValid) { + setCurrentStep(3); + } + break; + + case 3: + // 调用 ContentSelector 的表单校验 + isValid = (await contentSelectorRef.current?.validate()) || false; + if (isValid) { + setCurrentStep(4); + } + break; + + default: + setCurrentStep(currentStep + 1); + } + } catch (error) { + console.log("表单验证失败:", error); } - case 2: { - // 选择社群:检查是否选择了群组 - const groupsValid = - formData.groups.length > 0 && formData.groups.length <= 50; // 添加上限检查 - return groupsValid; - } - case 3: { - // 选择内容库:检查是否选择了内容库 - const librariesValid = - formData.contentLibraries.length > 0 && - formData.contentLibraries.length <= 20; // 添加上限检查 - return librariesValid; - } - case 4: { - // 京东联盟:可以进入下一步(保存) - // 这里可以添加京东联盟相关的验证逻辑 - return true; - } - default: - return false; } }; const renderFooter = () => { - if (currentStep === 4) { - return ( -
- - -
- ); - } - return (
{currentStep > 1 && ( - )} - + {currentStep === 4 ? ( + + ) : ( + + )}
); }; @@ -186,11 +196,14 @@ const NewGroupPush: React.FC = () => { header={} footer={renderFooter()} > -
- -
+
+
+ +
+
{currentStep === 1 && ( { )} {currentStep === 2 && ( g.id)} - onGroupsChange={handleGroupsChange} + ref={groupSelectorRef} + selectedGroups={wechatGroupsOptions} onPrevious={() => setCurrentStep(1)} - onNext={() => setCurrentStep(3)} - onSave={handleSave} - loading={loading} + onNext={handleGroupsChange} /> )} {currentStep === 3 && ( setCurrentStep(2)} From bb06d3e4f8a35379200c09a783c346b8eb35bfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 7 Aug 2025 18:21:35 +0800 Subject: [PATCH 05/31] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=BA=93=E5=A4=84=E7=90=86=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api.ts | 0 .../src/components/ContentSelection/data.ts | 21 +++++ .../index.module.scss | 0 .../index.tsx | 79 +++++-------------- nkebao/src/pages/mobile/test/select.tsx | 20 +++-- .../form/components/ContentSelector.tsx | 55 ++++--------- .../workspace/group-push/form/index.data.ts | 3 +- .../workspace/group-push/form/index.tsx | 24 +++--- .../workspace/moments-sync/new/index.tsx | 2 +- 9 files changed, 84 insertions(+), 120 deletions(-) rename nkebao/src/components/{ContentLibrarySelection => ContentSelection}/api.ts (100%) create mode 100644 nkebao/src/components/ContentSelection/data.ts rename nkebao/src/components/{ContentLibrarySelection => ContentSelection}/index.module.scss (100%) rename nkebao/src/components/{ContentLibrarySelection => ContentSelection}/index.tsx (81%) diff --git a/nkebao/src/components/ContentLibrarySelection/api.ts b/nkebao/src/components/ContentSelection/api.ts similarity index 100% rename from nkebao/src/components/ContentLibrarySelection/api.ts rename to nkebao/src/components/ContentSelection/api.ts diff --git a/nkebao/src/components/ContentSelection/data.ts b/nkebao/src/components/ContentSelection/data.ts new file mode 100644 index 00000000..5ea44b90 --- /dev/null +++ b/nkebao/src/components/ContentSelection/data.ts @@ -0,0 +1,21 @@ +// 内容库接口类型 +export interface ContentItem { + id: number; + name: string; + [key: string]: any; +} + +// 组件属性接口 +export interface ContentSelectionProps { + selectedContent: ContentItem[]; + onSelect: (selectedItems: ContentItem[]) => void; + placeholder?: string; + className?: string; + visible?: boolean; + onVisibleChange?: (visible: boolean) => void; + selectedListMaxHeight?: number; + showInput?: boolean; + showSelectedList?: boolean; + readonly?: boolean; + onConfirm?: (selectedItems: ContentItem[]) => void; +} diff --git a/nkebao/src/components/ContentLibrarySelection/index.module.scss b/nkebao/src/components/ContentSelection/index.module.scss similarity index 100% rename from nkebao/src/components/ContentLibrarySelection/index.module.scss rename to nkebao/src/components/ContentSelection/index.module.scss diff --git a/nkebao/src/components/ContentLibrarySelection/index.tsx b/nkebao/src/components/ContentSelection/index.tsx similarity index 81% rename from nkebao/src/components/ContentLibrarySelection/index.tsx rename to nkebao/src/components/ContentSelection/index.tsx index b053f1d9..9c022e80 100644 --- a/nkebao/src/components/ContentLibrarySelection/index.tsx +++ b/nkebao/src/components/ContentSelection/index.tsx @@ -7,17 +7,7 @@ import Layout from "@/components/Layout/Layout"; import PopupHeader from "@/components/PopuLayout/header"; import PopupFooter from "@/components/PopuLayout/footer"; import { getContentLibraryList } from "./api"; - -// 内容库接口类型 -interface ContentLibraryItem { - id: string; - name: string; - description?: string; - sourceType?: number; // 1=文本 2=图片 3=视频 - creatorName?: string; - updateTime?: string; - [key: string]: any; -} +import { ContentItem, ContentSelectionProps } from "./data"; // 类型标签文本 const getTypeText = (type?: number) => { @@ -43,29 +33,9 @@ const formatDate = (dateStr?: string) => { .padStart(2, "0")}`; }; -// 组件属性接口 -interface ContentLibrarySelectionProps { - selectedLibraries: (string | number)[]; - onSelect: (libraries: string[]) => void; - onSelectDetail?: (libraries: ContentLibraryItem[]) => void; - placeholder?: string; - className?: string; - visible?: boolean; - onVisibleChange?: (visible: boolean) => void; - selectedListMaxHeight?: number; - showInput?: boolean; - showSelectedList?: boolean; - readonly?: boolean; - onConfirm?: ( - selectedIds: string[], - selectedItems: ContentLibraryItem[], - ) => void; -} - -export default function ContentLibrarySelection({ - selectedLibraries, +export default function ContentSelection({ + selectedContent, onSelect, - onSelectDetail, placeholder = "选择内容库", className = "", visible, @@ -75,24 +45,19 @@ export default function ContentLibrarySelection({ showSelectedList = true, readonly = false, onConfirm, -}: ContentLibrarySelectionProps) { +}: ContentSelectionProps) { const [popupVisible, setPopupVisible] = useState(false); - const [libraries, setLibraries] = useState([]); + const [libraries, setLibraries] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalLibraries, setTotalLibraries] = useState(0); const [loading, setLoading] = useState(false); - // 获取已选内容库详细信息 - const selectedLibraryObjs = libraries.filter(item => - selectedLibraries.includes(item.id), - ); - // 删除已选内容库 - const handleRemoveLibrary = (id: string) => { + const handleRemoveLibrary = (id: number) => { if (readonly) return; - onSelect(selectedLibraries.filter(g => g !== id)); + onSelect(selectedContent.filter(c => c.id !== id)); }; // 受控弹窗逻辑 @@ -153,30 +118,24 @@ export default function ContentLibrarySelection({ }; // 处理内容库选择 - const handleLibraryToggle = (libraryId: string) => { + const handleLibraryToggle = (library: ContentItem) => { if (readonly) return; - const newSelected = selectedLibraries.includes(libraryId) - ? selectedLibraries.filter(id => id !== libraryId) - : [...selectedLibraries, libraryId]; + const newSelected = selectedContent.some(c => c.id === library.id) + ? selectedContent.filter(c => c.id !== library.id) + : [...selectedContent, library]; onSelect(newSelected); - if (onSelectDetail) { - const selectedObjs = libraries.filter(item => - newSelected.includes(item.id), - ); - onSelectDetail(selectedObjs); - } }; // 获取显示文本 const getDisplayText = () => { - if (selectedLibraries.length === 0) return ""; - return `已选择 ${selectedLibraries.length} 个内容库`; + if (selectedContent.length === 0) return ""; + return `已选择 ${selectedContent.length} 个内容库`; }; // 确认选择 const handleConfirm = () => { if (onConfirm) { - onConfirm(selectedLibraries, selectedLibraryObjs); + onConfirm(selectedContent); } setRealVisible(false); }; @@ -202,7 +161,7 @@ export default function ContentLibrarySelection({
)} {/* 已选内容库列表窗口 */} - {showSelectedList && selectedLibraryObjs.length > 0 && ( + {showSelectedList && selectedContent.length > 0 && (
- {selectedLibraryObjs.map(item => ( + {selectedContent.map(item => (
setRealVisible(false)} onConfirm={handleConfirm} @@ -301,8 +260,8 @@ export default function ContentLibrarySelection({ {libraries.map(item => (
diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx index e96c6e14..1e3adff0 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx @@ -1,23 +1,15 @@ import React, { useImperativeHandle, forwardRef } from "react"; import { Form, Card } from "antd"; -import ContentLibrarySelection from "@/components/ContentLibrarySelection"; - -interface ContentLibrary { - id: string; - name: string; - targets: Array<{ - id: string; - avatar: string; - }>; -} +import ContentSelection from "@/components/ContentSelection"; +import { ContentItem } from "@/components/ContentSelection/data"; interface ContentSelectorProps { - selectedLibraries: ContentLibrary[]; - onLibrariesChange: (libraries: ContentLibrary[]) => void; + selectedContent: ContentItem[]; onPrevious: () => void; - onNext: () => void; - onSave: () => void; - loading?: boolean; + onNext: (data: { + contentGroups: string[]; + contentGroupsOptions: ContentItem[]; + }) => void; } export interface ContentSelectorRef { @@ -26,17 +18,7 @@ export interface ContentSelectorRef { } const ContentSelector = forwardRef( - ( - { - selectedLibraries, - onLibrariesChange, - onPrevious, - onNext, - onSave, - loading = false, - }, - ref, - ) => { + ({ selectedContent, onNext }, ref) => { const [form] = Form.useForm(); // 暴露方法给父组件 @@ -55,20 +37,17 @@ const ContentSelector = forwardRef( }, })); - // 将 ContentLibrary[] 转换为 string[] 用于 ContentLibrarySelection - const selectedLibraryIds = selectedLibraries.map(lib => lib.id); - // 处理选择变化 - const handleLibrariesChange = (libraryIds: string[]) => { - // 这里需要根据选中的ID重新构建ContentLibrary对象 - // 由于ContentLibrarySelection只返回ID,我们需要从原始数据中获取完整信息 - // 暂时使用简化的处理方式 - const newSelectedLibraries = libraryIds.map(id => ({ + const handleLibrariesChange = (contentGroups: string[]) => { + const newSelectedLibraries = contentGroups.map(id => ({ id, name: `内容库 ${id}`, // 这里应该从API获取完整信息 targets: [], // 这里应该从API获取完整信息 })); - onLibrariesChange(newSelectedLibraries); + onNext({ + contentGroups: libraryIds, + contentGroupsOptions: newSelectedLibraries, + }); form.setFieldValue("contentLibraries", libraryIds); }; @@ -80,7 +59,7 @@ const ContentSelector = forwardRef( name: lib.name, targets: [], // 这里需要根据实际情况获取targets数据 })); - onLibrariesChange(convertedLibraries); + onNext(convertedLibraries); form.setFieldValue( "contentLibraries", libraries.map(lib => lib.id), @@ -112,8 +91,8 @@ const ContentSelector = forwardRef( { type: "array", max: 20, message: "最多只能选择20个内容库" }, ]} > - { const [wechatGroupsOptions, setWechatGroupsOptions] = useState< GroupSelectionItem[] >([]); + const [contentGroupsOptions, setContentGroupsOptions] = useState< + ContentItem[] + >([]); + const [formData, setFormData] = useState({ name: "", pushTimeStart: "06:00", @@ -37,7 +42,7 @@ const NewGroupPush: React.FC = () => { isImmediatePush: false, isEnabled: false, wechatGroups: [], - contentLibraries: [], + contentGroups: [], }); const [isEditMode, setIsEditMode] = useState(false); @@ -66,9 +71,13 @@ const NewGroupPush: React.FC = () => { })); setWechatGroupsOptions(data.wechatGroupsOptions); }; - - const handleLibrariesChange = (contentLibraries: ContentLibrary[]) => { - setFormData(prev => ({ ...prev, contentLibraries })); + //内容库选择 + const handleLibrariesChange = (data: { + contentGroups: string[]; + contentGroupsOptions: ContentItem[]; + }) => { + setFormData(prev => ({ ...prev, contentGroups: data.contentGroups })); + setContentGroupsOptions(data.contentGroupsOptions); }; const handleSave = async () => { @@ -230,12 +239,9 @@ const NewGroupPush: React.FC = () => { {currentStep === 3 && ( setCurrentStep(2)} - onNext={() => setCurrentStep(4)} - onSave={handleSave} - loading={loading} + onNext={handleLibrariesChange} /> )} {currentStep === 4 && ( diff --git a/nkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx b/nkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx index 8c6dea8e..32ac8209 100644 --- a/nkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx +++ b/nkebao/src/pages/mobile/workspace/moments-sync/new/index.tsx @@ -13,7 +13,7 @@ import { getMomentsSyncDetail, } from "./api"; import DeviceSelection from "@/components/DeviceSelection"; -import ContentLibrarySelection from "@/components/ContentLibrarySelection"; +import ContentLibrarySelection from "@/components/ContentSelection"; import NavCommon from "@/components/NavCommon"; const steps = [ From b06a0e16b030f9dc9a63c3ea6f4aa6adaec5ec7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 7 Aug 2025 18:27:37 +0800 Subject: [PATCH 06/31] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E9=80=89=E6=8B=A9=E6=9E=84=E5=BB=BA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/components/ContentSelector.tsx | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx index 1e3adff0..7b1e12fd 100644 --- a/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx +++ b/nkebao/src/pages/mobile/workspace/group-push/form/components/ContentSelector.tsx @@ -38,32 +38,13 @@ const ContentSelector = forwardRef( })); // 处理选择变化 - const handleLibrariesChange = (contentGroups: string[]) => { - const newSelectedLibraries = contentGroups.map(id => ({ - id, - name: `内容库 ${id}`, // 这里应该从API获取完整信息 - targets: [], // 这里应该从API获取完整信息 - })); + const handleLibrariesChange = (contentGroupsOptions: ContentItem[]) => { + const contentGroups = contentGroupsOptions.map(c => c.id.toString()); onNext({ - contentGroups: libraryIds, - contentGroupsOptions: newSelectedLibraries, + contentGroups, + contentGroupsOptions, }); - form.setFieldValue("contentLibraries", libraryIds); - }; - - // 处理选择详情变化 - const handleSelectDetail = (libraries: any[]) => { - // 将API返回的数据转换为ContentLibrary格式 - const convertedLibraries = libraries.map(lib => ({ - id: lib.id, - name: lib.name, - targets: [], // 这里需要根据实际情况获取targets数据 - })); - onNext(convertedLibraries); - form.setFieldValue( - "contentLibraries", - libraries.map(lib => lib.id), - ); + form.setFieldValue("contentGroups", contentGroups); }; return ( @@ -72,7 +53,7 @@ const ContentSelector = forwardRef(
c.id) }} >

@@ -84,7 +65,7 @@ const ContentSelector = forwardRef(

( ]} > From a339dd898b454c8f5059252e46f3781894e1e752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 8 Aug 2025 10:10:28 +0800 Subject: [PATCH 07/31] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E9=81=B8=E6=93=87=E6=B8=AC=E8=A9=A6=E7=B5=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=92=B0=E5=A2=83=E8=AE=8A=E6=95=B8=EF=BC=8C?= =?UTF-8?q?=E5=84=AA=E5=8C=96=E5=85=A7=E5=AE=B9=E8=A1=A8=E5=96=AE=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=A5=BD=E5=8F=8B=E5=92=8C=E7=BE=A4=E7=B5=84=E9=81=B8?= =?UTF-8?q?=E6=93=87=E9=82=8F=E8=BC=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/.env.development | 4 +- nkebao/src/components/FriendSelection/data.ts | 7 ++ nkebao/src/components/SelectionTest.tsx | 67 ------------------- .../src/pages/mobile/content/form/index.tsx | 36 ++++++++-- nkebao/src/pages/mobile/test/select.tsx | 42 +++++------- nkebao/src/router/module/other.tsx | 11 --- 6 files changed, 54 insertions(+), 113 deletions(-) create mode 100644 nkebao/src/components/FriendSelection/data.ts delete mode 100644 nkebao/src/components/SelectionTest.tsx delete mode 100644 nkebao/src/router/module/other.tsx diff --git a/nkebao/.env.development b/nkebao/.env.development index 7afdd84c..c008d630 100644 --- a/nkebao/.env.development +++ b/nkebao/.env.development @@ -1,4 +1,4 @@ # 基础环境变量示例 -# VITE_API_BASE_URL=http://www.yishi.com -VITE_API_BASE_URL=https://ckbapi.quwanzhi.com +VITE_API_BASE_URL=http://www.yishi.com +# VITE_API_BASE_URL=https://ckbapi.quwanzhi.com VITE_APP_TITLE=存客宝 diff --git a/nkebao/src/components/FriendSelection/data.ts b/nkebao/src/components/FriendSelection/data.ts new file mode 100644 index 00000000..5a7e2cbd --- /dev/null +++ b/nkebao/src/components/FriendSelection/data.ts @@ -0,0 +1,7 @@ +export interface FriendSelectionItem { + id: number; + wechatId: string; + nickname: string; + avatar: string; + [key: string]: any; +} diff --git a/nkebao/src/components/SelectionTest.tsx b/nkebao/src/components/SelectionTest.tsx deleted file mode 100644 index 08bc8be2..00000000 --- a/nkebao/src/components/SelectionTest.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState } from "react"; -import DeviceSelection from "./DeviceSelection"; -import FriendSelection from "./FriendSelection"; -import GroupSelection from "./GroupSelection"; -import { Button, Space } from "antd-mobile"; - -export default function SelectionTest() { - // 设备选择 - const [selectedDevices, setSelectedDevices] = useState([]); - const [deviceDialogOpen, setDeviceDialogOpen] = useState(false); - - // 好友选择 - const [selectedFriends, setSelectedFriends] = useState([]); - const [friendDialogOpen, setFriendDialogOpen] = useState(false); - - // 群组选择 - const [selectedGroups, setSelectedGroups] = useState([]); - const [groupDialogOpen, setGroupDialogOpen] = useState(false); - - return ( -
-

选择弹窗测试

- -
- DeviceSelection(内嵌输入框+弹窗) - -
-
- FriendSelection - - -
-
- GroupSelection - - -
-
-
-
已选设备ID: {selectedDevices.join(", ")}
-
已选好友ID: {selectedFriends.join(", ")}
-
已选群组ID: {selectedGroups.join(", ")}
-
-
- ); -} diff --git a/nkebao/src/pages/mobile/content/form/index.tsx b/nkebao/src/pages/mobile/content/form/index.tsx index 7df91566..1eb91a06 100644 --- a/nkebao/src/pages/mobile/content/form/index.tsx +++ b/nkebao/src/pages/mobile/content/form/index.tsx @@ -9,6 +9,8 @@ import Layout from "@/components/Layout/Layout"; import style from "./index.module.scss"; import request from "@/api/request"; import { getContentLibraryDetail, updateContentLibrary } from "./api"; +import { GroupSelectionItem } from "@/components/GroupSelection/data"; +import { FriendSelectionItem } from "@/components/FriendSelection/data"; const { TextArea } = AntdInput; @@ -27,8 +29,14 @@ export default function ContentForm() { const isEdit = !!id; const [sourceType, setSourceType] = useState<"friends" | "groups">("friends"); const [name, setName] = useState(""); - const [selectedFriends, setSelectedFriends] = useState([]); + const [friendsGroups, setSelectedFriends] = useState([]); + const [friendsGroupsOptions, setSelectedFriendsOptions] = useState< + FriendSelectionItem[] + >([]); const [selectedGroups, setSelectedGroups] = useState([]); + const [selectedGroupsOptions, setSelectedGroupsOptions] = useState< + GroupSelectionItem[] + >([]); const [useAI, setUseAI] = useState(false); const [aiPrompt, setAIPrompt] = useState(""); const [enabled, setEnabled] = useState(true); @@ -52,7 +60,11 @@ export default function ContentForm() { setName(data.name || ""); setSourceType(data.sourceType === 1 ? "friends" : "groups"); setSelectedFriends(data.sourceFriends || []); - setSelectedGroups(data.sourceGroups || []); + setSelectedGroups(data.selectedGroups || []); + setSelectedGroupsOptions(data.selectedGroupsOptions || []); + + setSelectedFriendsOptions(data.sourceFriendsOptions || []); + setKeywordsInclude((data.keywordInclude || []).join(",")); setKeywordsExclude((data.keywordExclude || []).join(",")); setAIPrompt(data.aiPrompt || ""); @@ -87,7 +99,7 @@ export default function ContentForm() { const payload = { name, sourceType: sourceType === "friends" ? 1 : 2, - friends: selectedFriends, + friends: friendsGroups, groups: selectedGroups, groupMembers: {}, keywordInclude: keywordsInclude @@ -122,6 +134,16 @@ export default function ContentForm() { } }; + const handleGroupsChange = (groups: GroupSelectionItem[]) => { + setSelectedGroups(groups.map(g => g.id.toString())); + setSelectedGroupsOptions(groups); + }; + + const handleFriendsChange = (friends: FriendSelectionItem[]) => { + setSelectedFriends(friends.map(f => f.id.toString())); + setSelectedFriendsOptions(friends); + }; + return ( } @@ -173,15 +195,15 @@ export default function ContentForm() { > diff --git a/nkebao/src/pages/mobile/test/select.tsx b/nkebao/src/pages/mobile/test/select.tsx index 1b6ae862..3665a917 100644 --- a/nkebao/src/pages/mobile/test/select.tsx +++ b/nkebao/src/pages/mobile/test/select.tsx @@ -10,6 +10,7 @@ import AccountSelection from "@/components/AccountSelection"; import { isDevelopment } from "@/utils/env"; import { GroupSelectionItem } from "@/components/GroupSelection/data"; import { ContentItem } from "@/components/ContentSelection/data"; +import { FriendSelectionItem } from "@/components/FriendSelection/data"; const ComponentTest: React.FC = () => { const [activeTab, setActiveTab] = useState("libraries"); @@ -28,7 +29,9 @@ const ComponentTest: React.FC = () => { const [selectedContent, setSelectedContent] = useState([]); const [selectedAccounts, setSelectedAccounts] = useState([]); - + const [selectedFriendsOptions, setSelectedFriendsOptions] = useState< + FriendSelectionItem[] + >([]); return ( }>
@@ -41,6 +44,18 @@ const ComponentTest: React.FC = () => { )} + +
+

FriendSelection 组件测试

+ +
+

ContentSelection 组件测试

@@ -118,31 +133,6 @@ const ComponentTest: React.FC = () => {
- -
-

FriendSelection 组件测试

- -
- 已选好友: {selectedFriends.length} 个 -
- 好友ID: {selectedFriends.join(", ") || "无"} -
-
-
-

AccountSelection 组件测试

diff --git a/nkebao/src/router/module/other.tsx b/nkebao/src/router/module/other.tsx deleted file mode 100644 index faedbee3..00000000 --- a/nkebao/src/router/module/other.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SelectionTest from "@/components/SelectionTest"; - -const otherRoutes = [ - { - path: "/selection-test", - element: , - auth: false, - }, -]; - -export default otherRoutes; From 08d851ec811846b6188a148983045592c16633c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 8 Aug 2025 10:37:43 +0800 Subject: [PATCH 08/31] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/components/DeviceSelection/data.ts | 2 +- nkebao/src/components/DeviceSelection/index.tsx | 14 +++++++------- .../components/DeviceSelection/selectionPopup.tsx | 14 +++++++------- .../plan/new/steps/FriendRequestSettings.tsx | 2 +- nkebao/src/pages/mobile/test/select.tsx | 2 +- .../mobile/workspace/auto-like/new/NewAutoLike.tsx | 2 +- .../pages/mobile/workspace/auto-like/new/index.tsx | 2 +- .../mobile/workspace/moments-sync/new/index.tsx | 8 ++++---- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/nkebao/src/components/DeviceSelection/data.ts b/nkebao/src/components/DeviceSelection/data.ts index 45b8a088..e8a09b5f 100644 --- a/nkebao/src/components/DeviceSelection/data.ts +++ b/nkebao/src/components/DeviceSelection/data.ts @@ -12,7 +12,7 @@ export interface DeviceSelectionItem { // 组件属性接口 export interface DeviceSelectionProps { - selectedDevices: string[]; + selectedOptions: string[]; onSelect: (devices: string[]) => void; placeholder?: string; className?: string; diff --git a/nkebao/src/components/DeviceSelection/index.tsx b/nkebao/src/components/DeviceSelection/index.tsx index 6612e9dc..c358e0ff 100644 --- a/nkebao/src/components/DeviceSelection/index.tsx +++ b/nkebao/src/components/DeviceSelection/index.tsx @@ -7,7 +7,7 @@ import SelectionPopup from "./selectionPopup"; import style from "./index.module.scss"; const DeviceSelection: React.FC = ({ - selectedDevices, + selectedOptions, onSelect, placeholder = "选择设备", className = "", @@ -36,14 +36,14 @@ const DeviceSelection: React.FC = ({ // 获取显示文本 const getDisplayText = () => { - if (selectedDevices.length === 0) return ""; - return `已选择 ${selectedDevices.length} 个设备`; + if (selectedOptions.length === 0) return ""; + return `已选择 ${selectedOptions.length} 个设备`; }; // 删除已选设备 const handleRemoveDevice = (id: string) => { if (readonly) return; - onSelect(selectedDevices.filter(d => d !== id)); + onSelect(selectedOptions.filter(d => d !== id)); }; return ( @@ -67,7 +67,7 @@ const DeviceSelection: React.FC = ({
)} {/* 已选设备列表窗口 */} - {mode === "input" && showSelectedList && selectedDevices.length > 0 && ( + {mode === "input" && showSelectedList && selectedOptions.length > 0 && (
= ({ background: "#fff", }} > - {selectedDevices.map(deviceId => ( + {selectedOptions.map(deviceId => (
= ({ setRealVisible(false)} - selectedDevices={selectedDevices} + selectedOptions={selectedOptions} onSelect={onSelect} /> diff --git a/nkebao/src/components/DeviceSelection/selectionPopup.tsx b/nkebao/src/components/DeviceSelection/selectionPopup.tsx index fc3ea3c2..49a933e1 100644 --- a/nkebao/src/components/DeviceSelection/selectionPopup.tsx +++ b/nkebao/src/components/DeviceSelection/selectionPopup.tsx @@ -20,7 +20,7 @@ interface DeviceSelectionItem { interface SelectionPopupProps { visible: boolean; onClose: () => void; - selectedDevices: string[]; + selectedOptions: string[]; onSelect: (devices: string[]) => void; } @@ -29,7 +29,7 @@ const PAGE_SIZE = 20; const SelectionPopup: React.FC = ({ visible, onClose, - selectedDevices, + selectedOptions, onSelect, }) => { // 设备数据 @@ -113,10 +113,10 @@ const SelectionPopup: React.FC = ({ // 处理设备选择 const handleDeviceToggle = (deviceId: string) => { - if (selectedDevices.includes(deviceId)) { - onSelect(selectedDevices.filter(id => id !== deviceId)); + if (selectedOptions.includes(deviceId)) { + onSelect(selectedOptions.filter(id => id !== deviceId)); } else { - onSelect([...selectedDevices, deviceId]); + onSelect([...selectedOptions, deviceId]); } }; @@ -155,7 +155,7 @@ const SelectionPopup: React.FC = ({ currentPage={currentPage} totalPages={totalPages} loading={loading} - selectedCount={selectedDevices.length} + selectedCount={selectedOptions.length} onPageChange={setCurrentPage} onCancel={onClose} onConfirm={onClose} @@ -172,7 +172,7 @@ const SelectionPopup: React.FC = ({ {filteredDevices.map(device => (