From b89860bc93eea407299daded34743553c39d0367 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, 28 Aug 2025 17:38:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ContentSelection):=20=E5=B0=86?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E7=BB=84=E4=BB=B6=E6=8A=BD=E7=A6=BB=E4=B8=BA?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E6=96=87=E4=BB=B6=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将内容选择弹窗组件抽离为独立的selectionPopup.tsx文件,优化代码结构 添加加载状态处理,改进搜索和分页逻辑,增强组件复用性 --- Cunkebao/dist/.vite/manifest.json | 2 +- Cunkebao/dist/index.html | 2 +- .../src/components/ContentSelection/index.tsx | 216 ++-------------- .../ContentSelection/selectionPopup.tsx | 233 ++++++++++++++++++ 4 files changed, 260 insertions(+), 193 deletions(-) create mode 100644 Cunkebao/src/components/ContentSelection/selectionPopup.tsx diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index 24c694d0..801dd2fa 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -33,7 +33,7 @@ "name": "vendor" }, "index.html": { - "file": "assets/index-C4WYi_u0.js", + "file": "assets/index-BQZfedpY.js", "name": "index", "src": "index.html", "isEntry": true, diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index b1a6a34d..5f7e88a3 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -11,7 +11,7 @@ - + diff --git a/Cunkebao/src/components/ContentSelection/index.tsx b/Cunkebao/src/components/ContentSelection/index.tsx index 457dc923..eadb64bc 100644 --- a/Cunkebao/src/components/ContentSelection/index.tsx +++ b/Cunkebao/src/components/ContentSelection/index.tsx @@ -1,39 +1,11 @@ -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, Checkbox } 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"; -import { getContentLibraryList } from "./api"; import { ContentItem, ContentSelectionProps } from "./data"; +import SelectionPopup from "./selectionPopup"; -// 类型标签文本 -const getTypeText = (type?: number) => { - if (type === 1) return "文本"; - if (type === 2) return "图片"; - if (type === 3) return "视频"; - return "未知"; -}; - -// 时间格式化 -const formatDate = (dateStr?: string) => { - if (!dateStr) return "-"; - const d = new Date(dateStr); - if (isNaN(d.getTime())) return "-"; - return `${d.getFullYear()}/${(d.getMonth() + 1) - .toString() - .padStart(2, "0")}/${d.getDate().toString().padStart(2, "0")} ${d - .getHours() - .toString() - .padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d - .getSeconds() - .toString() - .padStart(2, "0")}`; -}; - -export default function ContentSelection({ +const ContentSelection: React.FC = ({ selectedOptions, onSelect, placeholder = "选择内容库", @@ -45,25 +17,9 @@ export default function ContentSelection({ showSelectedList = true, readonly = false, onConfirm, -}: ContentSelectionProps) { +}) => { + // 弹窗控制 const [popupVisible, setPopupVisible] = useState(false); - 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 [tempSelectedOptions, setTempSelectedOptions] = useState( - [], - ); - - // 删除已选内容库 - const handleRemoveLibrary = (id: number) => { - if (readonly) return; - onSelect(selectedOptions.filter(c => c.id !== id)); - }; - - // 受控弹窗逻辑 const realVisible = visible !== undefined ? visible : popupVisible; const setRealVisible = (v: boolean) => { if (onVisibleChange) onVisibleChange(v); @@ -73,62 +29,7 @@ export default function ContentSelection({ // 打开弹窗 const openPopup = () => { if (readonly) return; - setCurrentPage(1); - setSearchQuery(""); - // 复制一份selectedOptions到临时变量 - setTempSelectedOptions([...selectedOptions]); setRealVisible(true); - fetchLibraries(1, ""); - }; - - // 当页码变化时,拉取对应页数据(弹窗已打开时) - useEffect(() => { - if (realVisible && currentPage !== 1) { - fetchLibraries(currentPage, searchQuery); - } - }, [currentPage, realVisible, searchQuery]); - - // 搜索防抖 - useEffect(() => { - if (!realVisible) return; - const timer = setTimeout(() => { - setCurrentPage(1); - fetchLibraries(1, searchQuery); - }, 500); - return () => clearTimeout(timer); - }, [searchQuery, realVisible]); - - // 获取内容库列表API - const fetchLibraries = async (page: number, keyword: string = "") => { - setLoading(true); - try { - const params: any = { - page, - limit: 20, - }; - if (keyword.trim()) { - params.keyword = keyword.trim(); - } - const response = await getContentLibraryList(params); - if (response && response.list) { - setLibraries(response.list); - setTotalLibraries(response.total || 0); - setTotalPages(Math.ceil((response.total || 0) / 20)); - } - } catch (error) { - console.error("获取内容库列表失败:", error); - } finally { - setLoading(false); - } - }; - - // 处理内容库选择 - const handleLibraryToggle = (library: ContentItem) => { - if (readonly) return; - const newSelected = tempSelectedOptions.some(c => c.id === library.id) - ? tempSelectedOptions.filter(c => c.id !== library.id) - : [...tempSelectedOptions, library]; - setTempSelectedOptions(newSelected); }; // 获取显示文本 @@ -137,14 +38,16 @@ export default function ContentSelection({ return `已选择 ${selectedOptions.length} 个内容库`; }; - // 确认选择 - const handleConfirm = () => { - // 用户点击确认时,才更新实际的selectedOptions - onSelect(tempSelectedOptions); - if (onConfirm) { - onConfirm(tempSelectedOptions); - } - setRealVisible(false); + // 删除已选内容库 + const handleRemoveLibrary = (id: number) => { + if (readonly) return; + onSelect(selectedOptions.filter(c => c.id !== id)); + }; + + // 清除所有已选内容库 + const handleClearAll = () => { + if (readonly) return; + onSelect([]); }; return ( @@ -158,6 +61,7 @@ export default function ContentSelection({ onClick={openPopup} prefix={} allowClear={!readonly} + onClear={handleClearAll} size="large" readOnly={readonly} disabled={readonly} @@ -227,85 +131,15 @@ export default function ContentSelection({ )} {/* 弹窗 */} - setRealVisible(false)} - position="bottom" - bodyStyle={{ height: "100vh" }} - > - fetchLibraries(currentPage, searchQuery)} - /> - } - footer={ - setRealVisible(false)} - onConfirm={handleConfirm} - /> - } - > -
- {loading ? ( -
-
加载中...
-
- ) : libraries.length > 0 ? ( -
- {libraries.map(item => ( - - ))} -
- ) : ( -
-
- {searchQuery - ? `没有找到包含"${searchQuery}"的内容库` - : "没有找到内容库"} -
-
- )} -
-
-
+ onClose={() => setRealVisible(false)} + selectedOptions={selectedOptions} + onSelect={onSelect} + onConfirm={onConfirm} + /> ); -} +}; + +export default ContentSelection; diff --git a/Cunkebao/src/components/ContentSelection/selectionPopup.tsx b/Cunkebao/src/components/ContentSelection/selectionPopup.tsx new file mode 100644 index 00000000..fc87881d --- /dev/null +++ b/Cunkebao/src/components/ContentSelection/selectionPopup.tsx @@ -0,0 +1,233 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { Checkbox, Popup } from "antd-mobile"; +import { getContentLibraryList } 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 { ContentItem } from "./data"; + +interface SelectionPopupProps { + visible: boolean; + onClose: () => void; + selectedOptions: ContentItem[]; + onSelect: (libraries: ContentItem[]) => void; + onConfirm?: (libraries: ContentItem[]) => void; +} + +const PAGE_SIZE = 20; + +// 类型标签文本 +const getTypeText = (type?: number) => { + if (type === 1) return "文本"; + if (type === 2) return "图片"; + if (type === 3) return "视频"; + return "未知"; +}; + +// 时间格式化 +const formatDate = (dateStr?: string) => { + if (!dateStr) return "-"; + const d = new Date(dateStr); + if (isNaN(d.getTime())) return "-"; + return `${d.getFullYear()}/${(d.getMonth() + 1) + .toString() + .padStart(2, "0")}/${d.getDate().toString().padStart(2, "0")} ${d + .getHours() + .toString() + .padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d + .getSeconds() + .toString() + .padStart(2, "0")}`; +}; + +const SelectionPopup: React.FC = ({ + visible, + onClose, + selectedOptions, + onSelect, + onConfirm, +}) => { + // 内容库数据 + const [libraries, setLibraries] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [loading, setLoading] = useState(true); // 默认设置为加载中状态 + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalLibraries, setTotalLibraries] = useState(0); + const [tempSelectedOptions, setTempSelectedOptions] = useState( + [], + ); + + // 获取内容库列表,支持keyword和分页 + const fetchLibraries = useCallback( + async (page: number, keyword: string = "") => { + setLoading(true); + try { + const params: any = { + page, + limit: PAGE_SIZE, + }; + if (keyword.trim()) { + params.keyword = keyword.trim(); + } + const response = await getContentLibraryList(params); + if (response && response.list) { + setLibraries(response.list); + setTotalLibraries(response.total || 0); + setTotalPages(Math.ceil((response.total || 0) / PAGE_SIZE)); + } else { + // 如果没有返回列表数据,设置为空数组 + setLibraries([]); + setTotalLibraries(0); + setTotalPages(1); + } + } catch (error) { + console.error("获取内容库列表失败:", error); + // 请求失败时,设置为空数组 + setLibraries([]); + setTotalLibraries(0); + setTotalPages(1); + } finally { + setLoading(false); + } + }, + [], + ); + + // 打开弹窗时获取第一页 + useEffect(() => { + if (visible) { + setSearchQuery(""); + setCurrentPage(1); + // 复制一份selectedOptions到临时变量 + setTempSelectedOptions([...selectedOptions]); + // 设置loading状态,避免显示空内容 + setLoading(true); + fetchLibraries(1, ""); + } else { + // 关闭弹窗时重置加载状态,确保下次打开时显示加载中 + setLoading(true); + } + }, [visible, fetchLibraries, selectedOptions]); + + // 搜索防抖 + useEffect(() => { + if (!visible) return; + const timer = setTimeout(() => { + setCurrentPage(1); + fetchLibraries(1, searchQuery); + }, 500); + return () => clearTimeout(timer); + }, [searchQuery, visible, fetchLibraries]); + + // 翻页时重新请求 + useEffect(() => { + if (!visible || currentPage === 1) return; + fetchLibraries(currentPage, searchQuery); + }, [currentPage, visible, fetchLibraries, searchQuery]); + + // 处理内容库选择 + const handleLibraryToggle = (library: ContentItem) => { + const newSelected = tempSelectedOptions.some(c => c.id === library.id) + ? tempSelectedOptions.filter(c => c.id !== library.id) + : [...tempSelectedOptions, library]; + setTempSelectedOptions(newSelected); + }; + + // 确认选择 + const handleConfirm = () => { + // 用户点击确认时,才更新实际的selectedOptions + onSelect(tempSelectedOptions); + if (onConfirm) { + onConfirm(tempSelectedOptions); + } + onClose(); + }; + // 渲染内容库列表或空状态提示 + const OptionsList = () => { + return libraries.length > 0 ? ( +
+ {libraries.map(item => ( + + ))} +
+ ) : ( +
+
+ {searchQuery + ? `没有找到包含"${searchQuery}"的内容库` + : "没有找到内容库"} +
+
+ ); + }; + + return ( + + fetchLibraries(currentPage, searchQuery)} + /> + } + footer={ + + } + > +
+ {loading ? ( +
+
加载中...
+
+ ) : ( + OptionsList() + )} +
+
+
+ ); +}; + +export default SelectionPopup;