From bb72038e2b7e5f3272768d47de1167644df83398 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: Tue, 11 Nov 2025 11:56:38 +0800 Subject: [PATCH] Implement contact filtering functionality in CreatePushTask component. Add modal for filtering contacts by tags, cities, and nickname/remark. Update styles for filter modal and enhance state management for filter values. --- .../components/StepSelectContacts/index.tsx | 357 +++++++++++++++++- .../create-push-task/index.module.scss | 83 ++++ .../create-push-task/types.ts | 6 + 3 files changed, 437 insertions(+), 9 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx index b804cafcd..8de9fce18 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSelectContacts/index.tsx @@ -10,6 +10,8 @@ import { Pagination, Spin, message, + Modal, + Select, } from "antd"; import { CloseOutlined, @@ -23,6 +25,37 @@ import { getContactList, getGroupList } from "@/pages/pc/ckbox/weChat/api"; import styles from "../../index.module.scss"; import { ContactItem, PushType } from "../../types"; +interface ContactFilterValues { + includeTags: string[]; + excludeTags: string[]; + includeCities: string[]; + excludeCities: string[]; + nicknameRemark: string; + groupIds: string[]; +} + +const createDefaultFilterValues = (): ContactFilterValues => ({ + includeTags: [], + excludeTags: [], + includeCities: [], + excludeCities: [], + nicknameRemark: "", + groupIds: [], +}); + +const cloneFilterValues = ( + values: ContactFilterValues, +): ContactFilterValues => ({ + includeTags: [...values.includeTags], + excludeTags: [...values.excludeTags], + includeCities: [...values.includeCities], + excludeCities: [...values.excludeCities], + nicknameRemark: values.nicknameRemark, + groupIds: [...values.groupIds], +}); + +const DISABLED_TAG_LABELS = new Set(["请选择标签"]); + interface StepSelectContactsProps { pushType: PushType; selectedAccounts: any[]; @@ -41,6 +74,12 @@ const StepSelectContacts: React.FC = ({ const [page, setPage] = useState(1); const [searchValue, setSearchValue] = useState(""); const [total, setTotal] = useState(0); + const [filterModalVisible, setFilterModalVisible] = useState(false); + const [filterValues, setFilterValues] = useState( + createDefaultFilterValues, + ); + const [draftFilterValues, setDraftFilterValues] = + useState(createDefaultFilterValues); const pageSize = 20; @@ -140,12 +179,159 @@ const StepSelectContacts: React.FC = ({ } }; - const filteredContacts = useMemo(() => { - if (searchValue.trim()) { - return contactsData; + const tagOptions = useMemo(() => { + const tagSet = new Set(); + contactsData.forEach(contact => { + (contact.labels || []).forEach(tag => { + const normalizedTag = (tag || "").trim(); + if (normalizedTag && !DISABLED_TAG_LABELS.has(normalizedTag)) { + tagSet.add(normalizedTag); + } + }); + }); + return Array.from(tagSet).map(tag => ({ label: tag, value: tag })); + }, [contactsData]); + + const cityOptions = useMemo(() => { + const citySet = new Set(); + contactsData.forEach(contact => { + const city = (contact.city || contact.region || "").trim(); + if (city) { + citySet.add(city); + } + }); + return Array.from(citySet).map(city => ({ label: city, value: city })); + }, [contactsData]); + + const groupOptions = useMemo(() => { + const groupMap = new Map(); + contactsData.forEach(contact => { + const key = + contact.groupName || + contact.groupLabel || + (contact.groupId !== undefined ? contact.groupId.toString() : ""); + if (key) { + const display = + contact.groupName || contact.groupLabel || `分组 ${key}`; + groupMap.set(key, display); + } + }); + return Array.from(groupMap.entries()).map(([value, label]) => ({ + value, + label, + })); + }, [contactsData]); + + const hasActiveFilter = useMemo(() => { + const { + includeTags, + excludeTags, + includeCities, + excludeCities, + nicknameRemark, + groupIds, + } = filterValues; + + if ( + includeTags.length || + excludeTags.length || + includeCities.length || + excludeCities.length || + groupIds.length || + nicknameRemark.trim() + ) { + return true; } - return contactsData; - }, [contactsData, searchValue]); + return false; + }, [filterValues]); + + const filteredContacts = useMemo(() => { + const keyword = searchValue.trim().toLowerCase(); + const nicknameKeyword = filterValues.nicknameRemark.trim().toLowerCase(); + + return contactsData.filter(contact => { + const labels = contact.labels || []; + const city = (contact.city || contact.region || "").toLowerCase(); + const groupValue = + contact.groupName || + contact.groupLabel || + (contact.groupId !== undefined ? contact.groupId.toString() : ""); + + if (keyword) { + const combined = `${contact.nickname || ""} ${ + contact.conRemark || "" + }`.toLowerCase(); + if (!combined.includes(keyword)) { + return false; + } + } + + if (filterValues.includeTags.length > 0) { + const hasAllIncludes = filterValues.includeTags.every(tag => + labels.includes(tag), + ); + if (!hasAllIncludes) { + return false; + } + } + + if (filterValues.excludeTags.length > 0) { + const hasExcluded = filterValues.excludeTags.some(tag => + labels.includes(tag), + ); + if (hasExcluded) { + return false; + } + } + + if (filterValues.includeCities.length > 0) { + const matchCity = filterValues.includeCities.some(value => + city.includes(value.toLowerCase()), + ); + if (!matchCity) { + return false; + } + } + + if (filterValues.excludeCities.length > 0) { + const matchExcludedCity = filterValues.excludeCities.some(value => + city.includes(value.toLowerCase()), + ); + if (matchExcludedCity) { + return false; + } + } + + if (nicknameKeyword) { + const combined = `${contact.nickname || ""} ${ + contact.conRemark || "" + }`.toLowerCase(); + if (!combined.includes(nicknameKeyword)) { + return false; + } + } + + if (filterValues.groupIds.length > 0) { + if (!groupValue) { + return false; + } + if ( + !filterValues.groupIds.some(value => value === groupValue.toString()) + ) { + return false; + } + } + + return true; + }); + }, [contactsData, filterValues, searchValue]); + + const displayTotal = useMemo(() => { + if (hasActiveFilter) { + return filteredContacts.length; + } + return total; + }, [filteredContacts, hasActiveFilter, total]); const handleContactToggle = (contact: ContactItem) => { const isSelected = selectedContacts.some(c => c.id === contact.id); @@ -176,6 +362,39 @@ const StepSelectContacts: React.FC = ({ onChange([...selectedContacts, ...toAdd]); }; + const openFilterModal = () => { + setDraftFilterValues(cloneFilterValues(filterValues)); + setFilterModalVisible(true); + }; + + const closeFilterModal = () => { + setFilterModalVisible(false); + }; + + const handleFilterConfirm = () => { + setFilterValues(cloneFilterValues(draftFilterValues)); + setPage(1); + setFilterModalVisible(false); + }; + + const handleFilterReset = () => { + const nextValues = createDefaultFilterValues(); + setDraftFilterValues(nextValues); + setFilterValues(nextValues); + setPage(1); + setFilterModalVisible(false); + }; + + const updateDraftFilter = ( + key: K, + value: ContactFilterValues[K], + ) => { + setDraftFilterValues(prev => ({ + ...prev, + [key]: value, + })); + }; + const handlePageChange = (p: number) => { setPage(p); }; @@ -195,14 +414,22 @@ const StepSelectContacts: React.FC = ({ onChange={e => handleSearchChange(e.target.value)} allowClear /> -
- {stepTitle}列表(共{total}个) + {stepTitle}列表(共{displayTotal}个) +
+ + +
{loadingContacts ? ( @@ -260,13 +487,13 @@ const StepSelectContacts: React.FC = ({ /> )}
- {total > 0 && ( + {displayTotal > 0 && (
@@ -332,6 +559,118 @@ const StepSelectContacts: React.FC = ({
+ + + 重置 + , + , + , + ]} + > +
+
标签
+
+
+ + + updateDraftFilter("excludeTags", value) + } + /> +
+
+
+ +
+
城市
+
+
+ + + updateDraftFilter("excludeCities", value) + } + /> +
+
+
+ +
+
昵称/备注
+
+ + updateDraftFilter("nicknameRemark", e.target.value) + } + /> +
+
+ +
+
分组
+
+