From 2c299e3add0bd64292c30341ec691d4827fc853a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9C=AC=E9=87=8C=E7=9A=84=E6=B0=B8?= =?UTF-8?q?=E5=B9=B3?= Date: Tue, 22 Jul 2025 12:29:16 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=9C=AC=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E5=A6=82=E4=B8=8B?= =?UTF-8?q?=20=E9=80=9A=E7=94=A8=E8=AE=BE=E5=A4=87=E3=80=81=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E7=BB=84=E4=BB=B6=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/DeviceSelection/index.tsx | 208 +++++++++++ .../components/DeviceSelection/module.scss | 156 ++++++++ .../DeviceSelectionDialog/index.tsx | 216 ++++++++++++ .../DeviceSelectionDialog/module.scss | 170 +++++++++ .../src/components/FriendSelection/index.tsx | 332 ++++++++++++++++++ .../components/FriendSelection/module.scss | 223 ++++++++++++ .../src/components/GroupSelection/index.tsx | 317 +++++++++++++++++ .../src/components/GroupSelection/module.scss | 223 ++++++++++++ .../src/pages/workspace/auto-like/list/api.ts | 2 +- .../pages/workspace/auto-like/list/index.tsx | 2 +- .../src/pages/workspace/auto-like/new/api.ts | 2 +- .../pages/workspace/auto-like/new/index.tsx | 2 +- .../workspace/auto-like/record/api.ts} | 0 .../workspace/auto-like/record/data.ts} | 4 +- .../workspace/auto-like/record/index.tsx | 4 +- .../workspace/group-push/detail}/groupPush.ts | 2 +- .../workspace/group-push/detail/index.tsx | 5 +- .../pages/workspace/group-push/form/index.tsx | 2 +- .../pages/workspace/group-push/list/index.tsx | 2 +- 19 files changed, 1860 insertions(+), 12 deletions(-) create mode 100644 nkebao/src/components/DeviceSelection/index.tsx create mode 100644 nkebao/src/components/DeviceSelection/module.scss create mode 100644 nkebao/src/components/DeviceSelectionDialog/index.tsx create mode 100644 nkebao/src/components/DeviceSelectionDialog/module.scss create mode 100644 nkebao/src/components/FriendSelection/index.tsx create mode 100644 nkebao/src/components/FriendSelection/module.scss create mode 100644 nkebao/src/components/GroupSelection/index.tsx create mode 100644 nkebao/src/components/GroupSelection/module.scss rename nkebao/src/{types/auto-like.ts => pages/workspace/auto-like/record/api.ts} (100%) rename nkebao/src/{api/autoLike.ts => pages/workspace/auto-like/record/data.ts} (93%) rename nkebao/src/{api => pages/workspace/group-push/detail}/groupPush.ts (94%) diff --git a/nkebao/src/components/DeviceSelection/index.tsx b/nkebao/src/components/DeviceSelection/index.tsx new file mode 100644 index 000000000..67d5072cf --- /dev/null +++ b/nkebao/src/components/DeviceSelection/index.tsx @@ -0,0 +1,208 @@ +import React, { useState, useEffect } from "react"; +import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; +import { Input, Button, Checkbox, Popup, Toast } from "antd-mobile"; +import request from "@/api/request"; +import style from "./module.scss"; + +// 设备选择项接口 +interface DeviceSelectionItem { + id: string; + name: string; + imei: string; + wechatId: string; + status: "online" | "offline"; +} + +// 组件属性接口 +interface DeviceSelectionProps { + selectedDevices: string[]; + onSelect: (devices: string[]) => void; + placeholder?: string; + className?: string; +} + +export default function DeviceSelection({ + selectedDevices, + onSelect, + placeholder = "选择设备", + className = "", +}: DeviceSelectionProps) { + const [popupVisible, setPopupVisible] = useState(false); + const [devices, setDevices] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + const [loading, setLoading] = useState(false); + + // 获取设备列表,支持keyword + const fetchDevices = async (keyword: string = "") => { + setLoading(true); + try { + const res = await request( + "/v1/device/list", + { + page: 1, + limit: 100, + keyword: keyword.trim() || undefined, + }, + "GET" + ); + + if (res && Array.isArray(res.list)) { + setDevices( + res.list.map((d: any) => ({ + id: d.id?.toString() || "", + name: d.memo || d.imei || "", + imei: d.imei || "", + wechatId: d.wechatId || "", + status: d.alive === 1 ? "online" : "offline", + })) + ); + } + } catch (error) { + console.error("获取设备列表失败:", error); + Toast.show({ content: "获取设备列表失败", position: "top" }); + } finally { + setLoading(false); + } + }; + + // 打开弹窗时获取设备列表 + const openPopup = () => { + setSearchQuery(""); + setPopupVisible(true); + fetchDevices(""); + }; + + // 搜索防抖 + useEffect(() => { + if (!popupVisible) return; + const timer = setTimeout(() => { + fetchDevices(searchQuery); + }, 500); + return () => clearTimeout(timer); + }, [searchQuery, popupVisible]); + + // 过滤设备(只保留状态过滤) + const filteredDevices = devices.filter((device) => { + const matchesStatus = + statusFilter === "all" || + (statusFilter === "online" && device.status === "online") || + (statusFilter === "offline" && device.status === "offline"); + return matchesStatus; + }); + + // 处理设备选择 + const handleDeviceToggle = (deviceId: string) => { + if (selectedDevices.includes(deviceId)) { + onSelect(selectedDevices.filter((id) => id !== deviceId)); + } else { + onSelect([...selectedDevices, deviceId]); + } + }; + + // 获取显示文本 + const getDisplayText = () => { + if (selectedDevices.length === 0) return ""; + return `已选择 ${selectedDevices.length} 个设备`; + }; + + return ( + <> + {/* 输入框 */} +
+ + +
+ + {/* 设备选择弹窗 */} + setPopupVisible(false)} + position="bottom" + bodyStyle={{ height: "80vh" }} + > +
+
+
选择设备
+
+
+
+ + setSearchQuery(val)} + className={style.popupSearchInput} + /> +
+ +
+
+ {loading ? ( +
+
加载中...
+
+ ) : ( +
+ {filteredDevices.map((device) => ( + + ))} +
+ )} +
+
+
+ 已选择 {selectedDevices.length} 个设备 +
+
+ + +
+
+
+
+ + ); +} diff --git a/nkebao/src/components/DeviceSelection/module.scss b/nkebao/src/components/DeviceSelection/module.scss new file mode 100644 index 000000000..34510d81f --- /dev/null +++ b/nkebao/src/components/DeviceSelection/module.scss @@ -0,0 +1,156 @@ +.inputWrapper { + position: relative; +} +.inputIcon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #bdbdbd; + z-index: 10; + font-size: 18px; +} +.input { + padding-left: 38px !important; + height: 56px; + border-radius: 16px !important; + border: 1px solid #e5e6eb !important; + font-size: 16px; + background: #f8f9fa; +} + +.popupContainer { + display: flex; + flex-direction: column; + height: 100%; + background: #fff; +} +.popupHeader { + padding: 16px; + border-bottom: 1px solid #f0f0f0; +} +.popupTitle { + font-size: 20px; + font-weight: 600; + text-align: center; +} +.popupSearchRow { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; +} +.popupSearchInputWrap { + position: relative; + flex: 1; +} +.popupSearchInput { + padding-left: 36px !important; + border-radius: 12px !important; + height: 44px; + font-size: 15px; + border: 1px solid #e5e6eb !important; + background: #f8f9fa; +} +.statusSelect { + width: 120px; + height: 40px; + border-radius: 8px; + border: 1px solid #e5e6eb; + font-size: 15px; + padding: 0 10px; + background: #fff; +} +.deviceList { + flex: 1; + overflow-y: auto; +} +.deviceListInner { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; +} +.deviceItem { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + border-radius: 12px; + border: 1px solid #f0f0f0; + background: #fff; + cursor: pointer; + transition: background 0.2s; + &:hover { + background: #f5f6fa; + } +} +.deviceCheckbox { + margin-top: 4px; +} +.deviceInfo { + flex: 1; +} +.deviceInfoRow { + display: flex; + align-items: center; + justify-content: space-between; +} +.deviceName { + font-weight: 500; + font-size: 16px; + color: #222; +} +.statusOnline { + width: 56px; + height: 24px; + border-radius: 12px; + background: #52c41a; + color: #fff; + font-size: 13px; + display: flex; + align-items: center; + justify-content: center; +} +.statusOffline { + width: 56px; + height: 24px; + border-radius: 12px; + background: #e5e6eb; + color: #888; + font-size: 13px; + display: flex; + align-items: center; + justify-content: center; +} +.deviceInfoDetail { + font-size: 13px; + color: #888; + margin-top: 4px; +} +.loadingBox { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} +.loadingText { + color: #888; + font-size: 15px; +} +.popupFooter { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-top: 1px solid #f0f0f0; + background: #fff; +} +.selectedCount { + font-size: 14px; + color: #888; +} +.footerBtnGroup { + display: flex; + gap: 12px; +} diff --git a/nkebao/src/components/DeviceSelectionDialog/index.tsx b/nkebao/src/components/DeviceSelectionDialog/index.tsx new file mode 100644 index 000000000..4e741e157 --- /dev/null +++ b/nkebao/src/components/DeviceSelectionDialog/index.tsx @@ -0,0 +1,216 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; +import { Input, Button, Checkbox, Popup, Toast } from "antd-mobile"; +import request from "@/api/request"; +import style from "./module.scss"; + +interface Device { + id: string; + name: string; + imei: string; + wxid: string; + status: "online" | "offline"; + usedInPlans: number; + nickname: string; +} + +interface DeviceSelectionDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + selectedDevices: string[]; + onSelect: (devices: string[]) => void; +} + +export function DeviceSelectionDialog({ + open, + onOpenChange, + selectedDevices, + onSelect, +}: DeviceSelectionDialogProps) { + const [searchQuery, setSearchQuery] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + const [loading, setLoading] = useState(false); + const [devices, setDevices] = useState([]); + + // 获取设备列表,支持keyword + const fetchDevices = useCallback(async (keyword: string = "") => { + setLoading(true); + try { + const response = await request( + "/v1/device/list", + { + page: 1, + limit: 100, + keyword: keyword.trim() || undefined, + }, + "GET" + ); + + if (response && Array.isArray(response.list)) { + // 转换服务端数据格式为组件需要的格式 + const convertedDevices: Device[] = response.list.map( + (serverDevice: any) => ({ + id: serverDevice.id.toString(), + name: serverDevice.memo || `设备 ${serverDevice.id}`, + imei: serverDevice.imei, + wxid: serverDevice.wechatId || "", + status: serverDevice.alive === 1 ? "online" : "offline", + usedInPlans: 0, // 这个字段需要从其他API获取 + nickname: serverDevice.nickname || "", + }) + ); + setDevices(convertedDevices); + } + } catch (error) { + console.error("获取设备列表失败:", error); + Toast.show({ + content: "获取设备列表失败,请检查网络连接", + position: "top", + }); + } finally { + setLoading(false); + } + }, []); + + // 打开弹窗时获取设备列表 + useEffect(() => { + if (open) { + fetchDevices(""); + } + }, [open, fetchDevices]); + + // 搜索防抖 + useEffect(() => { + if (!open) return; + const timer = setTimeout(() => { + fetchDevices(searchQuery); + }, 500); + return () => clearTimeout(timer); + }, [searchQuery, open, fetchDevices]); + + // 过滤设备(只保留状态过滤) + const filteredDevices = devices.filter((device) => { + const matchesStatus = + statusFilter === "all" || + (statusFilter === "online" && device.status === "online") || + (statusFilter === "offline" && device.status === "offline"); + return matchesStatus; + }); + + const handleDeviceSelect = (deviceId: string) => { + if (selectedDevices.includes(deviceId)) { + onSelect(selectedDevices.filter((id) => id !== deviceId)); + } else { + onSelect([...selectedDevices, deviceId]); + } + }; + + return ( + onOpenChange(false)} + position="bottom" + bodyStyle={{ height: "80vh" }} + > +
+
+
选择设备
+
+
+
+ + setSearchQuery(val)} + className={style.popupSearchInput} + /> +
+ + +
+
+ {loading ? ( +
+
加载中...
+
+ ) : filteredDevices.length === 0 ? ( +
+
暂无数据
+
+ ) : ( +
+ {filteredDevices.map((device) => ( + + ))} +
+ )} +
+
+
+ 已选择 {selectedDevices.length} 个设备 +
+
+ + +
+
+
+
+ ); +} diff --git a/nkebao/src/components/DeviceSelectionDialog/module.scss b/nkebao/src/components/DeviceSelectionDialog/module.scss new file mode 100644 index 000000000..2a9cd2dea --- /dev/null +++ b/nkebao/src/components/DeviceSelectionDialog/module.scss @@ -0,0 +1,170 @@ +.popupContainer { + display: flex; + flex-direction: column; + height: 100%; + background: #fff; +} +.popupHeader { + padding: 16px; + border-bottom: 1px solid #f0f0f0; +} +.popupTitle { + font-size: 18px; + font-weight: 600; + text-align: center; +} +.popupSearchRow { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; +} +.popupSearchInputWrap { + position: relative; + flex: 1; +} +.inputIcon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #bdbdbd; + z-index: 10; + font-size: 16px; +} +.popupSearchInput { + padding-left: 36px !important; + border-radius: 12px !important; + height: 44px; + font-size: 15px; + border: 1px solid #e5e6eb !important; + background: #f8f9fa; +} +.statusSelect { + width: 128px; + height: 40px; + border-radius: 8px; + border: 1px solid #e5e6eb; + font-size: 14px; + padding: 0 12px; + background: #fff; +} +.refreshBtn { + min-width: 40px; + height: 40px; + border-radius: 8px; +} +.loadingIcon { + animation: spin 1s linear infinite; + font-size: 16px; +} +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} +.deviceList { + flex: 1; + overflow-y: auto; +} +.deviceListInner { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; +} +.deviceItem { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + border-radius: 12px; + border: 1px solid #f0f0f0; + background: #fff; + cursor: pointer; + transition: background 0.2s; + &:hover { + background: #f5f6fa; + } +} +.deviceCheckbox { + margin-top: 4px; +} +.deviceInfo { + flex: 1; +} +.deviceInfoRow { + display: flex; + align-items: center; + justify-content: space-between; +} +.deviceName { + font-weight: 500; + font-size: 16px; + color: #222; +} +.statusOnline { + padding: 4px 8px; + border-radius: 12px; + background: #52c41a; + color: #fff; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; +} +.statusOffline { + padding: 4px 8px; + border-radius: 12px; + background: #e5e6eb; + color: #888; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; +} +.deviceInfoDetail { + font-size: 13px; + color: #888; + margin-top: 4px; +} +.usedInPlans { + font-size: 13px; + color: #fa8c16; + margin-top: 4px; +} +.loadingBox { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} +.loadingText { + color: #888; + font-size: 15px; +} +.emptyBox { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} +.emptyText { + color: #888; + font-size: 15px; +} +.popupFooter { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-top: 1px solid #f0f0f0; + background: #fff; +} +.selectedCount { + font-size: 14px; + color: #888; +} +.footerBtnGroup { + display: flex; + gap: 12px; +} diff --git a/nkebao/src/components/FriendSelection/index.tsx b/nkebao/src/components/FriendSelection/index.tsx new file mode 100644 index 000000000..43f37ce5b --- /dev/null +++ b/nkebao/src/components/FriendSelection/index.tsx @@ -0,0 +1,332 @@ +import React, { useState, useEffect } from "react"; +import { + SearchOutlined, + CloseOutlined, + LeftOutlined, + RightOutlined, +} from "@ant-design/icons"; +import { Input, Button, Popup, Toast } from "antd-mobile"; +import request from "@/api/request"; +import style from "./module.scss"; + +// 微信好友接口类型 +interface WechatFriend { + id: string; + nickname: string; + wechatId: string; + avatar: string; + customer: string; +} + +// 组件属性接口 +interface FriendSelectionProps { + selectedFriends: string[]; + onSelect: (friends: string[]) => void; + onSelectDetail?: (friends: WechatFriend[]) => void; + deviceIds?: string[]; + enableDeviceFilter?: boolean; + placeholder?: string; + className?: string; +} + +export default function FriendSelection({ + selectedFriends, + onSelect, + onSelectDetail, + deviceIds = [], + enableDeviceFilter = true, + placeholder = "选择微信好友", + className = "", +}: FriendSelectionProps) { + const [popupVisible, setPopupVisible] = useState(false); + const [friends, setFriends] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalFriends, setTotalFriends] = useState(0); + const [loading, setLoading] = useState(false); + + // 打开弹窗并请求第一页好友 + const openPopup = () => { + setCurrentPage(1); + setSearchQuery(""); + setPopupVisible(true); + fetchFriends(1, ""); + }; + + // 当页码变化时,拉取对应页数据(弹窗已打开时) + useEffect(() => { + if (popupVisible && currentPage !== 1) { + fetchFriends(currentPage, searchQuery); + } + }, [currentPage, popupVisible, searchQuery]); + + // 搜索防抖 + useEffect(() => { + if (!popupVisible) return; + + const timer = setTimeout(() => { + setCurrentPage(1); + fetchFriends(1, searchQuery); + }, 500); + + return () => clearTimeout(timer); + }, [searchQuery, popupVisible]); + + // 获取好友列表API - 添加 keyword 参数 + const fetchFriends = async (page: number, keyword: string = "") => { + setLoading(true); + try { + let params: any = { + page, + limit: 20, + }; + + if (keyword.trim()) { + params.keyword = keyword.trim(); + } + + if (enableDeviceFilter) { + if (deviceIds.length === 0) { + setFriends([]); + setTotalFriends(0); + setTotalPages(1); + setLoading(false); + return; + } + params.deviceIds = deviceIds.join(","); + } + + const res = await request("/v1/friend", params, "GET"); + + if (res && Array.isArray(res.list)) { + setFriends( + res.list.map((friend: any) => ({ + id: friend.id?.toString() || "", + nickname: friend.nickname || "", + wechatId: friend.wechatId || "", + avatar: friend.avatar || "", + customer: friend.customer || "", + })) + ); + setTotalFriends(res.total || 0); + setTotalPages(Math.ceil((res.total || 0) / 20)); + } + } catch (error) { + console.error("获取好友列表失败:", error); + Toast.show({ content: "获取好友列表失败", position: "top" }); + } finally { + setLoading(false); + } + }; + + // 处理好友选择 + const handleFriendToggle = (friendId: string) => { + let newIds: string[]; + if (selectedFriends.includes(friendId)) { + newIds = selectedFriends.filter((id) => id !== friendId); + } else { + newIds = [...selectedFriends, friendId]; + } + onSelect(newIds); + if (onSelectDetail) { + const selectedObjs = friends.filter((f) => newIds.includes(f.id)); + onSelectDetail(selectedObjs); + } + }; + + // 获取显示文本 + const getDisplayText = () => { + if (selectedFriends.length === 0) return ""; + return `已选择 ${selectedFriends.length} 个好友`; + }; + + const handleConfirm = () => { + setPopupVisible(false); + }; + + // 清空搜索 + const handleClearSearch = () => { + setSearchQuery(""); + setCurrentPage(1); + fetchFriends(1, ""); + }; + + return ( + <> + {/* 输入框 */} +
+ + + + + + +
+ + {/* 微信好友选择弹窗 */} + setPopupVisible(false)} + position="bottom" + bodyStyle={{ height: "80vh" }} + > +
+
+
选择微信好友
+
+ setSearchQuery(val)} + className={style.searchInput} + /> + + {searchQuery && ( + + )} +
+
+ +
+ {loading ? ( +
+
加载中...
+
+ ) : friends.length > 0 ? ( +
+ {friends.map((friend) => ( + + ))} +
+ ) : ( +
+
+ {deviceIds.length === 0 + ? "请先选择设备" + : searchQuery + ? `没有找到包含"${searchQuery}"的好友` + : "没有找到好友"} +
+
+ )} +
+ +
+
总计 {totalFriends} 个好友
+
+ + + {currentPage} / {totalPages} + + +
+
+ +
+ + +
+
+
+ + ); +} diff --git a/nkebao/src/components/FriendSelection/module.scss b/nkebao/src/components/FriendSelection/module.scss new file mode 100644 index 000000000..865355010 --- /dev/null +++ b/nkebao/src/components/FriendSelection/module.scss @@ -0,0 +1,223 @@ +.inputWrapper { + position: relative; +} +.inputIcon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #bdbdbd; + font-size: 20px; +} +.input { + padding-left: 38px !important; + height: 48px; + border-radius: 16px !important; + border: 1px solid #e5e6eb !important; + font-size: 16px; + background: #f8f9fa; +} + +.popupContainer { + display: flex; + flex-direction: column; + height: 100%; + background: #fff; +} +.popupHeader { + padding: 24px; +} +.popupTitle { + text-align: center; + font-size: 20px; + font-weight: 600; + margin-bottom: 24px; +} +.searchWrapper { + position: relative; + margin-bottom: 16px; +} +.searchInput { + padding-left: 40px !important; + padding-top: 8px !important; + padding-bottom: 8px !important; + border-radius: 24px !important; + border: 1px solid #e5e6eb !important; + font-size: 15px; + background: #f8f9fa; +} +.searchIcon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #bdbdbd; + font-size: 16px; +} +.clearBtn { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + height: 24px; + width: 24px; + border-radius: 50%; + min-width: 24px; +} + +.friendList { + flex: 1; + overflow-y: auto; +} +.friendListInner { + border-top: 1px solid #f0f0f0; +} +.friendItem { + display: flex; + 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; +} +.friendInfo { + display: flex; + align-items: center; + gap: 12px; + flex: 1; +} +.friendAvatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 14px; + font-weight: 500; + overflow: hidden; +} +.avatarImg { + width: 100%; + height: 100%; + object-fit: cover; +} +.friendDetail { + flex: 1; +} +.friendName { + font-weight: 500; + font-size: 16px; + color: #222; + margin-bottom: 2px; +} +.friendId { + font-size: 13px; + color: #888; + margin-bottom: 2px; +} +.friendCustomer { + font-size: 13px; + color: #bdbdbd; +} + +.loadingBox { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} +.loadingText { + color: #888; + font-size: 15px; +} +.emptyBox { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} +.emptyText { + color: #888; + font-size: 15px; +} + +.paginationRow { + border-top: 1px solid #f0f0f0; + padding: 16px; + display: flex; + align-items: center; + justify-content: space-between; + background: #fff; +} +.totalCount { + font-size: 14px; + color: #888; +} +.paginationControls { + display: flex; + align-items: center; + gap: 8px; +} +.pageBtn { + padding: 0 8px; + height: 32px; + min-width: 32px; +} +.pageInfo { + font-size: 14px; + color: #222; +} + +.popupFooter { + border-top: 1px solid #f0f0f0; + padding: 16px; + display: flex; + align-items: center; + justify-content: space-between; + background: #fff; +} +.cancelBtn { + padding: 0 24px; + border-radius: 24px; + border: 1px solid #e5e6eb; +} +.confirmBtn { + padding: 0 24px; + border-radius: 24px; +} diff --git a/nkebao/src/components/GroupSelection/index.tsx b/nkebao/src/components/GroupSelection/index.tsx new file mode 100644 index 000000000..5e871e866 --- /dev/null +++ b/nkebao/src/components/GroupSelection/index.tsx @@ -0,0 +1,317 @@ +import React, { useState, useEffect } from "react"; +import { + SearchOutlined, + CloseOutlined, + LeftOutlined, + RightOutlined, +} from "@ant-design/icons"; +import { Input, Button, Popup, Toast } from "antd-mobile"; +import request from "@/api/request"; +import style from "./module.scss"; + +// 群组接口类型 +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; +} + +export default function GroupSelection({ + selectedGroups, + onSelect, + onSelectDetail, + placeholder = "选择群聊", + className = "", +}: 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 openPopup = () => { + setCurrentPage(1); + setSearchQuery(""); + setPopupVisible(true); + fetchGroups(1, ""); + }; + + // 当页码变化时,拉取对应页数据(弹窗已打开时) + useEffect(() => { + if (popupVisible && currentPage !== 1) { + fetchGroups(currentPage, searchQuery); + } + }, [currentPage, popupVisible, searchQuery]); + + // 搜索防抖 + useEffect(() => { + if (!popupVisible) return; + const timer = setTimeout(() => { + setCurrentPage(1); + fetchGroups(1, searchQuery); + }, 500); + return () => clearTimeout(timer); + }, [searchQuery, popupVisible]); + + // 获取群组列表API - 支持keyword + const fetchGroups = async (page: number, keyword: string = "") => { + setLoading(true); + try { + const params: any = { + page, + limit: 20, + }; + if (keyword.trim()) { + params.keyword = keyword.trim(); + } + + const res = await request("/v1/chatroom", params, "GET"); + + if (res && Array.isArray(res.list)) { + setGroups( + res.list.map((group: any) => ({ + id: group.id?.toString() || "", + chatroomId: group.chatroomId || "", + name: group.name || "", + avatar: group.avatar || "", + ownerWechatId: group.ownerWechatId || "", + ownerNickname: group.ownerNickname || "", + ownerAvatar: group.ownerAvatar || "", + })) + ); + setTotalGroups(res.total || 0); + setTotalPages(Math.ceil((res.total || 0) / 20)); + } + } catch (error) { + console.error("获取群组列表失败:", error); + Toast.show({ content: "获取群组列表失败", position: "top" }); + } finally { + setLoading(false); + } + }; + + // 处理群组选择 + const handleGroupToggle = (groupId: string) => { + let newIds: string[]; + if (selectedGroups.includes(groupId)) { + newIds = selectedGroups.filter((id) => id !== groupId); + } else { + newIds = [...selectedGroups, groupId]; + } + onSelect(newIds); + if (onSelectDetail) { + const selectedObjs = groups.filter((g) => newIds.includes(g.id)); + onSelectDetail(selectedObjs); + } + }; + + // 获取显示文本 + const getDisplayText = () => { + if (selectedGroups.length === 0) return ""; + return `已选择 ${selectedGroups.length} 个群聊`; + }; + + const handleConfirm = () => { + setPopupVisible(false); + }; + + // 清空搜索 + const handleClearSearch = () => { + setSearchQuery(""); + setCurrentPage(1); + fetchGroups(1, ""); + }; + + return ( + <> + {/* 输入框 */} +
+ + + + + + +
+ + {/* 群组选择弹窗 */} + setPopupVisible(false)} + position="bottom" + bodyStyle={{ height: "80vh" }} + > +
+
+
选择群聊
+
+ setSearchQuery(val)} + className={style.searchInput} + /> + + {searchQuery && ( + + )} +
+
+ +
+ {loading ? ( +
+
加载中...
+
+ ) : groups.length > 0 ? ( +
+ {groups.map((group) => ( + + ))} +
+ ) : ( +
+
+ {searchQuery + ? `没有找到包含"${searchQuery}"的群聊` + : "没有找到群聊"} +
+
+ )} +
+ +
+
+ 总计 {totalGroups} 个群聊 + {searchQuery && ` (搜索: "${searchQuery}")`} +
+
+ + + {currentPage} / {totalPages} + + +
+
+ +
+ + +
+
+
+ + ); +} diff --git a/nkebao/src/components/GroupSelection/module.scss b/nkebao/src/components/GroupSelection/module.scss new file mode 100644 index 000000000..75dbd174f --- /dev/null +++ b/nkebao/src/components/GroupSelection/module.scss @@ -0,0 +1,223 @@ +.inputWrapper { + position: relative; +} +.inputIcon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #bdbdbd; + font-size: 20px; +} +.input { + padding-left: 38px !important; + height: 48px; + border-radius: 16px !important; + border: 1px solid #e5e6eb !important; + font-size: 16px; + background: #f8f9fa; +} + +.popupContainer { + display: flex; + flex-direction: column; + height: 100%; + background: #fff; +} +.popupHeader { + padding: 24px; +} +.popupTitle { + text-align: center; + font-size: 20px; + font-weight: 600; + margin-bottom: 24px; +} +.searchWrapper { + position: relative; + margin-bottom: 16px; +} +.searchInput { + padding-left: 40px !important; + padding-top: 8px !important; + padding-bottom: 8px !important; + border-radius: 24px !important; + border: 1px solid #e5e6eb !important; + font-size: 15px; + background: #f8f9fa; +} +.searchIcon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #bdbdbd; + font-size: 16px; +} +.clearBtn { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + height: 24px; + width: 24px; + border-radius: 50%; + min-width: 24px; +} + +.groupList { + flex: 1; + overflow-y: auto; +} +.groupListInner { + border-top: 1px solid #f0f0f0; +} +.groupItem { + display: flex; + align-items: center; + padding: 16px 24px; + border-bottom: 1px solid #f0f0f0; + 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; + gap: 12px; + flex: 1; +} +.groupAvatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 14px; + font-weight: 500; + overflow: hidden; +} +.avatarImg { + width: 100%; + height: 100%; + object-fit: cover; +} +.groupDetail { + flex: 1; +} +.groupName { + font-weight: 500; + font-size: 16px; + color: #222; + margin-bottom: 2px; +} +.groupId { + font-size: 13px; + color: #888; + margin-bottom: 2px; +} +.groupOwner { + font-size: 13px; + color: #bdbdbd; +} + +.loadingBox { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} +.loadingText { + color: #888; + font-size: 15px; +} +.emptyBox { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} +.emptyText { + color: #888; + font-size: 15px; +} + +.paginationRow { + border-top: 1px solid #f0f0f0; + padding: 16px; + display: flex; + align-items: center; + justify-content: space-between; + background: #fff; +} +.totalCount { + font-size: 14px; + color: #888; +} +.paginationControls { + display: flex; + align-items: center; + gap: 8px; +} +.pageBtn { + padding: 0 8px; + height: 32px; + min-width: 32px; +} +.pageInfo { + font-size: 14px; + color: #222; +} + +.popupFooter { + border-top: 1px solid #f0f0f0; + padding: 16px; + display: flex; + align-items: center; + justify-content: space-between; + background: #fff; +} +.cancelBtn { + padding: 0 24px; + border-radius: 24px; + border: 1px solid #e5e6eb; +} +.confirmBtn { + padding: 0 24px; + border-radius: 24px; +} diff --git a/nkebao/src/pages/workspace/auto-like/list/api.ts b/nkebao/src/pages/workspace/auto-like/list/api.ts index 930902b45..34d4424af 100644 --- a/nkebao/src/pages/workspace/auto-like/list/api.ts +++ b/nkebao/src/pages/workspace/auto-like/list/api.ts @@ -5,7 +5,7 @@ import { UpdateLikeTaskData, LikeRecord, PaginatedResponse, -} from "@/types/auto-like"; +} from "@/pages/workspace/auto-like/record/api"; // 获取自动点赞任务列表 export function fetchAutoLikeTasks( diff --git a/nkebao/src/pages/workspace/auto-like/list/index.tsx b/nkebao/src/pages/workspace/auto-like/list/index.tsx index b3244fab9..f9a70a18c 100644 --- a/nkebao/src/pages/workspace/auto-like/list/index.tsx +++ b/nkebao/src/pages/workspace/auto-like/list/index.tsx @@ -22,7 +22,7 @@ import { toggleAutoLikeTask, copyAutoLikeTask, } from "./api"; -import { LikeTask } from "@/types/auto-like"; +import { LikeTask } from "@/pages/workspace/auto-like/record/api"; import style from "./index.module.scss"; // 卡片菜单组件 diff --git a/nkebao/src/pages/workspace/auto-like/new/api.ts b/nkebao/src/pages/workspace/auto-like/new/api.ts index 6ca2944c7..703d7bb60 100644 --- a/nkebao/src/pages/workspace/auto-like/new/api.ts +++ b/nkebao/src/pages/workspace/auto-like/new/api.ts @@ -3,7 +3,7 @@ import { CreateLikeTaskData, UpdateLikeTaskData, LikeTask, -} from "@/types/auto-like"; +} from "@/pages/workspace/auto-like/record/api"; // 获取自动点赞任务详情 export function fetchAutoLikeTaskDetail(id: string): Promise { diff --git a/nkebao/src/pages/workspace/auto-like/new/index.tsx b/nkebao/src/pages/workspace/auto-like/new/index.tsx index b0a13181f..1b8da3728 100644 --- a/nkebao/src/pages/workspace/auto-like/new/index.tsx +++ b/nkebao/src/pages/workspace/auto-like/new/index.tsx @@ -17,7 +17,7 @@ import { CreateLikeTaskData, UpdateLikeTaskData, ContentType, -} from "@/types/auto-like"; +} from "@/pages/workspace/auto-like/record/api"; import style from "./new.module.scss"; const contentTypeLabels: Record = { diff --git a/nkebao/src/types/auto-like.ts b/nkebao/src/pages/workspace/auto-like/record/api.ts similarity index 100% rename from nkebao/src/types/auto-like.ts rename to nkebao/src/pages/workspace/auto-like/record/api.ts diff --git a/nkebao/src/api/autoLike.ts b/nkebao/src/pages/workspace/auto-like/record/data.ts similarity index 93% rename from nkebao/src/api/autoLike.ts rename to nkebao/src/pages/workspace/auto-like/record/data.ts index 8ee3f4ba8..122a093d6 100644 --- a/nkebao/src/api/autoLike.ts +++ b/nkebao/src/pages/workspace/auto-like/record/data.ts @@ -1,4 +1,4 @@ -import { request } from "./request"; +import { request } from "../../../../api/request"; import { LikeTask, CreateLikeTaskData, @@ -6,7 +6,7 @@ import { LikeRecord, ApiResponse, PaginatedResponse, -} from "@/types/auto-like"; +} from "@/pages/workspace/auto-like/record/api"; // 获取自动点赞任务列表 export async function fetchAutoLikeTasks(): Promise { diff --git a/nkebao/src/pages/workspace/auto-like/record/index.tsx b/nkebao/src/pages/workspace/auto-like/record/index.tsx index 4d52913a7..6eb12b606 100644 --- a/nkebao/src/pages/workspace/auto-like/record/index.tsx +++ b/nkebao/src/pages/workspace/auto-like/record/index.tsx @@ -12,8 +12,8 @@ import { import Layout from "@/components/Layout/Layout"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; -import { fetchLikeRecords, fetchAutoLikeTaskDetail } from "@/api/autoLike"; -import { LikeRecord, LikeTask } from "@/types/auto-like"; +import { fetchLikeRecords, fetchAutoLikeTaskDetail } from "./data"; +import { LikeRecord, LikeTask } from "./api"; import style from "./record.module.scss"; // 格式化日期 diff --git a/nkebao/src/api/groupPush.ts b/nkebao/src/pages/workspace/group-push/detail/groupPush.ts similarity index 94% rename from nkebao/src/api/groupPush.ts rename to nkebao/src/pages/workspace/group-push/detail/groupPush.ts index 00ab36207..c1b068488 100644 --- a/nkebao/src/api/groupPush.ts +++ b/nkebao/src/pages/workspace/group-push/detail/groupPush.ts @@ -1,4 +1,4 @@ -import request from "./request"; +import request from "../../../../api/request"; export interface GroupPushTask { id: string; diff --git a/nkebao/src/pages/workspace/group-push/detail/index.tsx b/nkebao/src/pages/workspace/group-push/detail/index.tsx index 5e316ff7e..e78064763 100644 --- a/nkebao/src/pages/workspace/group-push/detail/index.tsx +++ b/nkebao/src/pages/workspace/group-push/detail/index.tsx @@ -10,7 +10,10 @@ import { } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; -import { getGroupPushTaskDetail, GroupPushTask } from "@/api/groupPush"; +import { + getGroupPushTaskDetail, + GroupPushTask, +} from "@/pages/workspace/group-push/detail/groupPush"; import styles from "./index.module.scss"; const Detail: React.FC = () => { diff --git a/nkebao/src/pages/workspace/group-push/form/index.tsx b/nkebao/src/pages/workspace/group-push/form/index.tsx index e6b4be352..8a57544bf 100644 --- a/nkebao/src/pages/workspace/group-push/form/index.tsx +++ b/nkebao/src/pages/workspace/group-push/form/index.tsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom"; import { Button } from "antd-mobile"; import { NavBar } from "antd-mobile"; import { LeftOutline } from "antd-mobile-icons"; -import { createGroupPushTask } from "@/api/groupPush"; +import { createGroupPushTask } from "@/pages/workspace/group-push/detail/groupPush"; import Layout from "@/components/Layout/Layout"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; import StepIndicator from "./components/StepIndicator"; diff --git a/nkebao/src/pages/workspace/group-push/list/index.tsx b/nkebao/src/pages/workspace/group-push/list/index.tsx index 2fe916342..6eaa35f17 100644 --- a/nkebao/src/pages/workspace/group-push/list/index.tsx +++ b/nkebao/src/pages/workspace/group-push/list/index.tsx @@ -38,7 +38,7 @@ import { toggleGroupPushTask, copyGroupPushTask, GroupPushTask, -} from "@/api/groupPush"; +} from "@/pages/workspace/group-push/detail/groupPush"; import styles from "./index.module.scss"; const { Search } = Input; From 06a37a541a526bafd7e23c8ac6ec0f42eca2ac17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9C=AC=E9=87=8C=E7=9A=84=E6=B0=B8?= =?UTF-8?q?=E5=B9=B3?= Date: Tue, 22 Jul 2025 13:47:05 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=9C=AC=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E5=A6=82=E4=B8=8B?= =?UTF-8?q?=20=E4=B8=AA=E4=BA=BA=E4=B8=AD=E5=BF=83=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=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/pages/mine/Profile.tsx | 258 ++++++++++++++++++ nkebao/src/pages/mine/index.module.scss | 35 ++- nkebao/src/pages/mine/index.tsx | 335 +++++++++++++++++------- 3 files changed, 521 insertions(+), 107 deletions(-) create mode 100644 nkebao/src/pages/mine/Profile.tsx diff --git a/nkebao/src/pages/mine/Profile.tsx b/nkebao/src/pages/mine/Profile.tsx new file mode 100644 index 000000000..bb302b3b9 --- /dev/null +++ b/nkebao/src/pages/mine/Profile.tsx @@ -0,0 +1,258 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ChevronRight, Settings, Bell, LogOut, Smartphone, MessageCircle, Database, FolderOpen } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { useAuth } from '@/contexts/AuthContext'; +import { useToast } from '@/components/ui/toast'; +import Layout from '@/components/Layout'; +import BottomNav from '@/components/BottomNav'; +import UnifiedHeader from '@/components/UnifiedHeader'; +import '@/components/Layout.css'; + +export default function Profile() { + const navigate = useNavigate(); + const { user, logout, isAuthenticated } = useAuth(); + const { toast } = useToast(); + const [showLogoutDialog, setShowLogoutDialog] = useState(false); + const [userInfo, setUserInfo] = useState(null); + const [stats, setStats] = useState({ + devices: 12, + wechat: 25, + traffic: 8, + content: 156, + }); + + // 从localStorage获取用户信息 + useEffect(() => { + const userInfoStr = localStorage.getItem('userInfo'); + if (userInfoStr) { + setUserInfo(JSON.parse(userInfoStr)); + } + }, []); + + // 用户信息 + const currentUserInfo = { + name: userInfo?.username || user?.username || "卡若", + email: userInfo?.email || "zhangsan@example.com", + role: "管理员", + joinDate: "2023-01-15", + lastLogin: "2024-01-20 14:30", + }; + + // 功能模块数据 + const functionModules = [ + { + id: "devices", + title: "设备管理", + description: "管理您的设备和微信账号", + icon: , + count: stats.devices, + path: "/devices", + bgColor: "bg-blue-50", + }, + { + id: "wechat", + title: "微信号管理", + description: "管理微信账号和好友", + icon: , + count: stats.wechat, + path: "/wechat-accounts", + bgColor: "bg-green-50", + }, + { + id: "traffic", + title: "流量池", + description: "管理用户流量池和分组", + icon: , + count: stats.traffic, + path: "/traffic-pool", + bgColor: "bg-purple-50", + }, + { + id: "content", + title: "内容库", + description: "管理营销内容和素材", + icon: , + count: stats.content, + path: "/content", + bgColor: "bg-orange-50", + }, + ]; + + // 加载统计数据 + const loadStats = async () => { + try { + // 这里可以调用实际的API + // const [deviceStats, wechatStats, trafficStats, contentStats] = await Promise.allSettled([ + // getDeviceStats(), + // getWechatStats(), + // getTrafficStats(), + // getContentStats(), + // ]); + + // 暂时使用模拟数据 + setStats({ + devices: 12, + wechat: 25, + traffic: 8, + content: 156, + }); + } catch (error) { + console.error("加载统计数据失败:", error); + } + }; + + useEffect(() => { + loadStats(); + }, []); + + const handleLogout = () => { + // 清除本地存储的用户信息 + localStorage.removeItem('token'); + localStorage.removeItem('token_expired'); + localStorage.removeItem('s2_accountId'); + localStorage.removeItem('userInfo'); + setShowLogoutDialog(false); + logout(); + navigate('/login'); + toast({ + title: '退出成功', + description: '您已安全退出系统', + }); + }; + + const handleFunctionClick = (path: string) => { + navigate(path); + }; + + if (!isAuthenticated) { + return ( +
+
请先登录
+
+ ); + } + + return ( + console.log('Notifications'), + }, + { + type: 'icon', + icon: Settings, + onClick: () => console.log('Settings'), + }, + ]} + /> + } + footer={} + > +
+
+ {/* 用户信息卡片 */} + + +
+ + + + {currentUserInfo.name.charAt(0)} + + +
+
+

{currentUserInfo.name}

+ + {currentUserInfo.role} + +
+

{currentUserInfo.email}

+
+
最近登录: {currentUserInfo.lastLogin}
+
+
+
+ + +
+
+
+
+ + {/* 我的功能 */} + + +
+ {functionModules.map((module) => ( +
handleFunctionClick(module.path)} + > +
{module.icon}
+
+
{module.title}
+
{module.description}
+
+
+ + {module.count} + + +
+
+ ))} +
+
+
+ + {/* 退出登录 */} + +
+
+ + {/* 退出登录确认对话框 */} + + + + 确认退出登录 + + 您确定要退出登录吗?退出后需要重新登录才能使用完整功能。 + + +
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/nkebao/src/pages/mine/index.module.scss b/nkebao/src/pages/mine/index.module.scss index 98aa403b9..fc1d92c7f 100644 --- a/nkebao/src/pages/mine/index.module.scss +++ b/nkebao/src/pages/mine/index.module.scss @@ -1,7 +1,5 @@ .mine-page { - padding: 16px; - background-color: #f5f5f5; - min-height: 100vh; + padding: 12px; } .user-card { @@ -59,22 +57,24 @@ .menu-card { margin-bottom: 16px; border-radius: 12px; - overflow: hidden; - - :global(.adm-card-body) { - padding: 0; + + :global(.adm-list-body) { + border: none; + } + + :global(.adm-card-body) { + padding: 14px 0 0 0; + } + :global(.adm-list-body-inner) { + margin-top: 0px; } - :global(.adm-list-item) { - padding: 16px; - border-bottom: 1px solid #f0f0f0; - - &:last-child { - border-bottom: none; - } - + padding: 0px; :global(.adm-list-item-content) { - padding: 0; + border: 1px solid #f0f0f0; + margin-bottom: 12px; + padding: 0 12px; + border-radius: 12px; } :global(.adm-list-item-content-prefix) { @@ -104,9 +104,6 @@ } } -.logout-section { - padding: 0 16px; -} .logout-btn { border-radius: 8px; diff --git a/nkebao/src/pages/mine/index.tsx b/nkebao/src/pages/mine/index.tsx index 12d1e55ca..b6298f529 100644 --- a/nkebao/src/pages/mine/index.tsx +++ b/nkebao/src/pages/mine/index.tsx @@ -1,78 +1,179 @@ -import React from "react"; -import { Card, NavBar, List, Button } from "antd-mobile"; +import React, { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { Card, NavBar, List, Button, Dialog, Toast } from "antd-mobile"; import { - UserOutline, - AppOutline, - BellOutline, - HeartOutline, - StarOutline, - MessageOutline, - SendOutline, - MailOutline, -} from "antd-mobile-icons"; + LogoutOutlined, + PhoneOutlined, + MessageOutlined, + DatabaseOutlined, + FolderOpenOutlined, + BellOutlined, + SettingOutlined, +} from "@ant-design/icons"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; import Layout from "@/components/Layout/Layout"; import style from "./index.module.scss"; const Mine: React.FC = () => { - const userInfo = { - name: "张三", - avatar: "https://via.placeholder.com/60", - level: "VIP会员", - points: 1280, + const navigate = useNavigate(); + const [userInfo, setUserInfo] = useState(null); + const [stats, setStats] = useState({ + devices: 12, + wechat: 25, + traffic: 8, + content: 156, + }); + const [showLogoutDialog, setShowLogoutDialog] = useState(false); + + // 从localStorage获取用户信息 + useEffect(() => { + const userInfoStr = localStorage.getItem("userInfo"); + if (userInfoStr) { + setUserInfo(JSON.parse(userInfoStr)); + } + }, []); + + // 用户信息 + const currentUserInfo = { + name: userInfo?.username || "售前", + email: userInfo?.email || "zhangsan@example.com", + role: "管理员", + lastLogin: "2024-01-20 14:30", + avatar: userInfo?.avatar || "", }; - const menuItems = [ + // 功能模块数据 + const functionModules = [ { - icon: , - title: "个人资料", - subtitle: "修改个人信息", - path: "/profile", + id: "devices", + title: "设备管理", + description: "管理您的设备和微信账号", + icon: , + count: stats.devices, + path: "/devices", + bgColor: "#e6f7ff", + iconColor: "#1890ff", }, { - icon: , - title: "系统设置", - subtitle: "应用设置与偏好", - path: "/settings", + id: "wechat", + title: "微信号管理", + description: "管理微信账号和好友", + icon: , + count: stats.wechat, + path: "/wechat-accounts", + bgColor: "#f6ffed", + iconColor: "#52c41a", }, { - icon: , - title: "消息通知", - subtitle: "通知设置", - path: "/notifications", + id: "traffic", + title: "流量池", + description: "管理用户流量池和分组", + icon: , + count: stats.traffic, + path: "/traffic-pool", + bgColor: "#f9f0ff", + iconColor: "#722ed1", }, { - icon: , - title: "我的收藏", - subtitle: "收藏的内容", - path: "/favorites", - }, - { - icon: , - title: "我的评价", - subtitle: "查看评价记录", - path: "/reviews", - }, - { - icon: , - title: "意见反馈", - subtitle: "问题反馈与建议", - path: "/feedback", - }, - { - icon: , - title: "联系客服", - subtitle: "在线客服", - path: "/customer-service", - }, - { - icon: , - title: "关于我们", - subtitle: "版本信息", - path: "/about", + id: "content", + title: "内容库", + description: "管理营销内容和素材", + icon: , + count: stats.content, + path: "/content", + bgColor: "#fff7e6", + iconColor: "#fa8c16", }, ]; + // 加载统计数据 + const loadStats = async () => { + try { + // 这里可以调用实际的API + // const [deviceStats, wechatStats, trafficStats, contentStats] = await Promise.allSettled([ + // getDeviceStats(), + // getWechatStats(), + // getTrafficStats(), + // getContentStats(), + // ]); + + // 暂时使用模拟数据 + setStats({ + devices: 12, + wechat: 25, + traffic: 8, + content: 156, + }); + } catch (error) { + console.error("加载统计数据失败:", error); + } + }; + + useEffect(() => { + loadStats(); + }, []); + + const handleLogout = () => { + // 清除本地存储的用户信息 + localStorage.removeItem("token"); + localStorage.removeItem("token_expired"); + localStorage.removeItem("s2_accountId"); + localStorage.removeItem("userInfo"); + setShowLogoutDialog(false); + navigate("/login"); + Toast.show({ + content: "退出成功", + position: "top", + }); + }; + + const handleFunctionClick = (path: string) => { + navigate(path); + }; + + // 渲染用户头像 + const renderUserAvatar = () => { + if (currentUserInfo.avatar) { + return 头像; + } + return ( +
+ 售 +
+ ); + }; + + // 渲染功能模块图标 + const renderModuleIcon = (module: any) => ( +
+ {module.icon} +
+ ); + return ( { {/* 用户信息卡片 */}
-
- 头像 -
+
{renderUserAvatar()}
-
{userInfo.name}
-
{userInfo.level}
-
- 积分: {userInfo.points} +
+
{currentUserInfo.name}
+ + {currentUserInfo.role} +
+
+ {currentUserInfo.email} +
+
+ 最近登录: {currentUserInfo.lastLogin} +
+
+
+ +
- {/* 菜单列表 */} + {/* 我的功能 */} - {menuItems.map((item, index) => ( + {functionModules.map((module) => ( + {module.count} + + } arrow - onClick={() => { - // 这里可以添加导航逻辑 - console.log(`点击了: ${item.title}`); - }} + onClick={() => handleFunctionClick(module.path)} /> ))} {/* 退出登录按钮 */} -
- -
+
+ + {/* 退出登录确认对话框 */} + setShowLogoutDialog(false)} + />
); };