feat: 本次提交更新内容如下

功能、和样式修复
This commit is contained in:
笔记本里的永平
2025-07-22 14:37:57 +08:00
parent 34df010769
commit f33bdf42e2
14 changed files with 205 additions and 74 deletions

View File

@@ -6,5 +6,5 @@ export function getDeviceList(params: {
limit: number;
keyword?: string;
}) {
return request("/v1/device/list", params, "GET");
return request("/v1/devices", params, "GET");
}

View File

@@ -22,7 +22,7 @@
.popupContainer {
display: flex;
flex-direction: column;
height: 100%;
height: 100vh;
background: #fff;
}
.popupHeader {

View File

@@ -1,8 +1,9 @@
import React, { useState, useEffect } from "react";
import { SearchOutlined, ReloadOutlined } from "@ant-design/icons";
import { Input, Button, Checkbox, Popup, Toast } from "antd-mobile";
import { Checkbox, Popup, Toast } from "antd-mobile";
import { Input, Button } from "antd";
import { getDeviceList } from "./api";
import style from "./module.scss";
import style from "./index.module.scss";
// 设备选择项接口
interface DeviceSelectionItem {
@@ -56,7 +57,6 @@ export default function DeviceSelection({
}
} catch (error) {
console.error("获取设备列表失败:", error);
Toast.show({ content: "获取设备列表失败", position: "top" });
} finally {
setLoading(false);
}
@@ -106,13 +106,13 @@ export default function DeviceSelection({
<>
{/* 输入框 */}
<div className={`${style.inputWrapper} ${className}`}>
<SearchOutlined className={style.inputIcon} />
<Input
placeholder={placeholder}
className={style.input}
readOnly
onClick={openPopup}
value={getDisplayText()}
onClick={openPopup}
prefix={<SearchOutlined />}
allowClear
size="large"
/>
</div>
@@ -121,7 +121,7 @@ export default function DeviceSelection({
visible={popupVisible}
onMaskClick={() => setPopupVisible(false)}
position="bottom"
bodyStyle={{ height: "80vh" }}
bodyStyle={{ height: "100vh" }}
>
<div className={style.popupContainer}>
<div className={style.popupHeader}>
@@ -129,12 +129,13 @@ export default function DeviceSelection({
</div>
<div className={style.popupSearchRow}>
<div className={style.popupSearchInputWrap}>
<SearchOutlined className={style.inputIcon} />
<Input
placeholder="搜索设备IMEI/备注/微信号"
value={searchQuery}
onChange={(val) => setSearchQuery(val)}
className={style.popupSearchInput}
onChange={(e) => setSearchQuery(e.target.value)}
prefix={<SearchOutlined />}
allowClear
size="large"
/>
</div>
<select
@@ -146,6 +147,19 @@ export default function DeviceSelection({
<option value="online">线</option>
<option value="offline">线</option>
</select>
<Button
type="primary"
size="large"
onClick={() => fetchDevices(searchQuery)}
disabled={loading}
className={style.refreshBtn}
>
{loading ? (
<div className={style.loadingIcon}></div>
) : (
<ReloadOutlined />
)}
</Button>
</div>
<div className={style.deviceList}>
{loading ? (

View File

@@ -6,5 +6,5 @@ export function getDeviceList(params: {
limit: number;
keyword?: string;
}) {
return request("/v1/device/list", params, "GET");
return request("/v1/devices", params, "GET");
}

View File

@@ -1,7 +1,7 @@
.popupContainer {
display: flex;
flex-direction: column;
height: 100%;
height: 100vh;
background: #fff;
}
.popupHeader {
@@ -49,11 +49,6 @@
padding: 0 12px;
background: #fff;
}
.refreshBtn {
min-width: 40px;
height: 40px;
border-radius: 8px;
}
.loadingIcon {
animation: spin 1s linear infinite;
font-size: 16px;

View File

@@ -1,8 +1,9 @@
import React, { useState, useEffect, useCallback } from "react";
import { SearchOutlined, ReloadOutlined } from "@ant-design/icons";
import { Input, Button, Checkbox, Popup, Toast } from "antd-mobile";
import { Checkbox, Popup, Toast } from "antd-mobile";
import { Input, Button } from "antd";
import { getDeviceList } from "./api";
import style from "./module.scss";
import style from "./index.module.scss";
interface Device {
id: string;
@@ -106,7 +107,7 @@ export function DeviceSelectionDialog({
visible={open}
onMaskClick={() => onOpenChange(false)}
position="bottom"
bodyStyle={{ height: "80vh" }}
bodyStyle={{ height: "100vh" }}
>
<div className={style.popupContainer}>
<div className={style.popupHeader}>
@@ -114,12 +115,13 @@ export function DeviceSelectionDialog({
</div>
<div className={style.popupSearchRow}>
<div className={style.popupSearchInputWrap}>
<SearchOutlined className={style.inputIcon} />
<Input
placeholder="搜索设备IMEI/备注"
placeholder="搜索设备IMEI/备注/微信号"
value={searchQuery}
onChange={(val) => setSearchQuery(val)}
className={style.popupSearchInput}
onChange={(e) => setSearchQuery(e.target.value)}
prefix={<SearchOutlined />}
allowClear
size="large"
/>
</div>
<select
@@ -132,8 +134,8 @@ export function DeviceSelectionDialog({
<option value="offline">线</option>
</select>
<Button
fill="outline"
size="mini"
type="primary"
size="large"
onClick={() => fetchDevices(searchQuery)}
disabled={loading}
className={style.refreshBtn}

View File

@@ -4,7 +4,7 @@ import request from "@/api/request";
export function getFriendList(params: {
page: number;
limit: number;
deviceIds?: string;
deviceIds?: string; // 逗号分隔
keyword?: string;
}) {
return request("/v1/friend", params, "GET");

View File

@@ -21,7 +21,7 @@
.popupContainer {
display: flex;
flex-direction: column;
height: 100%;
height: 100vh;
background: #fff;
}
.popupHeader {

View File

@@ -7,7 +7,7 @@ import {
} from "@ant-design/icons";
import { Input, Button, Popup, Toast } from "antd-mobile";
import { getFriendList } from "./api";
import style from "./module.scss";
import style from "./index.module.scss";
// 微信好友接口类型
interface WechatFriend {
@@ -27,6 +27,8 @@ interface FriendSelectionProps {
enableDeviceFilter?: boolean;
placeholder?: string;
className?: string;
visible?: boolean; // 新增
onVisibleChange?: (visible: boolean) => void; // 新增
}
export default function FriendSelection({
@@ -37,6 +39,8 @@ export default function FriendSelection({
enableDeviceFilter = true,
placeholder = "选择微信好友",
className = "",
visible,
onVisibleChange,
}: FriendSelectionProps) {
const [popupVisible, setPopupVisible] = useState(false);
const [friends, setFriends] = useState<WechatFriend[]>([]);
@@ -46,24 +50,31 @@ export default function FriendSelection({
const [totalFriends, setTotalFriends] = useState(0);
const [loading, setLoading] = useState(false);
// 受控弹窗逻辑
const realVisible = visible !== undefined ? visible : popupVisible;
const setRealVisible = (v: boolean) => {
if (onVisibleChange) onVisibleChange(v);
if (visible === undefined) setPopupVisible(v);
};
// 打开弹窗并请求第一页好友
const openPopup = () => {
setCurrentPage(1);
setSearchQuery("");
setPopupVisible(true);
setRealVisible(true);
fetchFriends(1, "");
};
// 当页码变化时,拉取对应页数据(弹窗已打开时)
useEffect(() => {
if (popupVisible && currentPage !== 1) {
if (realVisible && currentPage !== 1) {
fetchFriends(currentPage, searchQuery);
}
}, [currentPage, popupVisible, searchQuery]);
}, [currentPage, realVisible, searchQuery]);
// 搜索防抖
useEffect(() => {
if (!popupVisible) return;
if (!realVisible) return;
const timer = setTimeout(() => {
setCurrentPage(1);
@@ -71,7 +82,7 @@ export default function FriendSelection({
}, 500);
return () => clearTimeout(timer);
}, [searchQuery, popupVisible]);
}, [searchQuery, realVisible]);
// 获取好友列表API - 添加 keyword 参数
const fetchFriends = async (page: number, keyword: string = "") => {
@@ -183,10 +194,10 @@ export default function FriendSelection({
{/* 微信好友选择弹窗 */}
<Popup
visible={popupVisible}
onMaskClick={() => setPopupVisible(false)}
visible={realVisible}
onMaskClick={() => setRealVisible(false)}
position="bottom"
bodyStyle={{ height: "80vh" }}
bodyStyle={{ height: "100vh" }}
>
<div className={style.popupContainer}>
<div className={style.popupHeader}>
@@ -312,14 +323,17 @@ export default function FriendSelection({
<div className={style.popupFooter}>
<Button
fill="outline"
onClick={() => setPopupVisible(false)}
onClick={() => setRealVisible(false)}
className={style.cancelBtn}
>
</Button>
<Button
color="primary"
onClick={handleConfirm}
onClick={() => {
setRealVisible(false);
handleConfirm();
}}
className={style.confirmBtn}
>
({selectedFriends.length})

View File

@@ -21,7 +21,7 @@
.popupContainer {
display: flex;
flex-direction: column;
height: 100%;
height: 100vh;
background: #fff;
}
.popupHeader {

View File

@@ -7,7 +7,7 @@ import {
} from "@ant-design/icons";
import { Input, Button, Popup, Toast } from "antd-mobile";
import { getGroupList } from "./api";
import style from "./module.scss";
import style from "./index.module.scss";
// 群组接口类型
interface WechatGroup {
@@ -27,6 +27,8 @@ interface GroupSelectionProps {
onSelectDetail?: (groups: WechatGroup[]) => void;
placeholder?: string;
className?: string;
visible?: boolean; // 新增
onVisibleChange?: (visible: boolean) => void; // 新增
}
export default function GroupSelection({
@@ -35,6 +37,8 @@ export default function GroupSelection({
onSelectDetail,
placeholder = "选择群聊",
className = "",
visible,
onVisibleChange,
}: GroupSelectionProps) {
const [popupVisible, setPopupVisible] = useState(false);
const [groups, setGroups] = useState<WechatGroup[]>([]);
@@ -44,30 +48,37 @@ export default function GroupSelection({
const [totalGroups, setTotalGroups] = useState(0);
const [loading, setLoading] = useState(false);
// 受控弹窗逻辑
const realVisible = visible !== undefined ? visible : popupVisible;
const setRealVisible = (v: boolean) => {
if (onVisibleChange) onVisibleChange(v);
if (visible === undefined) setPopupVisible(v);
};
// 打开弹窗并请求第一页群组
const openPopup = () => {
setCurrentPage(1);
setSearchQuery("");
setPopupVisible(true);
setRealVisible(true);
fetchGroups(1, "");
};
// 当页码变化时,拉取对应页数据(弹窗已打开时)
useEffect(() => {
if (popupVisible && currentPage !== 1) {
if (realVisible && currentPage !== 1) {
fetchGroups(currentPage, searchQuery);
}
}, [currentPage, popupVisible, searchQuery]);
}, [currentPage, realVisible, searchQuery]);
// 搜索防抖
useEffect(() => {
if (!popupVisible) return;
if (!realVisible) return;
const timer = setTimeout(() => {
setCurrentPage(1);
fetchGroups(1, searchQuery);
}, 500);
return () => clearTimeout(timer);
}, [searchQuery, popupVisible]);
}, [searchQuery, realVisible]);
// 获取群组列表API - 支持keyword
const fetchGroups = async (page: number, keyword: string = "") => {
@@ -128,7 +139,7 @@ export default function GroupSelection({
};
const handleConfirm = () => {
setPopupVisible(false);
setRealVisible(false);
};
// 清空搜索
@@ -169,10 +180,10 @@ export default function GroupSelection({
{/* 群组选择弹窗 */}
<Popup
visible={popupVisible}
onMaskClick={() => setPopupVisible(false)}
visible={realVisible}
onMaskClick={() => setRealVisible(false)}
position="bottom"
bodyStyle={{ height: "80vh" }}
bodyStyle={{ height: "100vh" }}
>
<div className={style.popupContainer}>
<div className={style.popupHeader}>
@@ -297,14 +308,17 @@ export default function GroupSelection({
<div className={style.popupFooter}>
<Button
fill="outline"
onClick={() => setPopupVisible(false)}
onClick={() => setRealVisible(false)}
className={style.cancelBtn}
>
</Button>
<Button
color="primary"
onClick={handleConfirm}
onClick={() => {
setRealVisible(false);
handleConfirm();
}}
className={style.confirmBtn}
>
({selectedGroups.length})

View File

@@ -0,0 +1,80 @@
import React, { useState } from "react";
import DeviceSelection from "./DeviceSelection";
import { DeviceSelectionDialog } from "./DeviceSelectionDialog";
import FriendSelection from "./FriendSelection";
import GroupSelection from "./GroupSelection";
import { Button, Space } from "antd-mobile";
export default function SelectionTest() {
// 设备选择
const [selectedDevices, setSelectedDevices] = useState<string[]>([]);
const [deviceDialogOpen, setDeviceDialogOpen] = useState(false);
// 好友选择
const [selectedFriends, setSelectedFriends] = useState<string[]>([]);
const [friendDialogOpen, setFriendDialogOpen] = useState(false);
// 群组选择
const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
const [groupDialogOpen, setGroupDialogOpen] = useState(false);
return (
<div style={{ padding: 24 }}>
<h2></h2>
<Space direction="vertical" block>
<div>
<b>DeviceSelection+</b>
<DeviceSelection
selectedDevices={selectedDevices}
onSelect={setSelectedDevices}
/>
</div>
<div>
<b>DeviceSelectionDialog</b>
<Button color="primary" onClick={() => setDeviceDialogOpen(true)}>
</Button>
<DeviceSelectionDialog
open={deviceDialogOpen}
onOpenChange={setDeviceDialogOpen}
selectedDevices={selectedDevices}
onSelect={setSelectedDevices}
/>
</div>
<div>
<b>FriendSelection</b>
<Button color="primary" onClick={() => setFriendDialogOpen(true)}>
</Button>
<FriendSelection
selectedFriends={selectedFriends}
onSelect={setSelectedFriends}
placeholder="请选择微信好友"
className=""
visible={friendDialogOpen}
onVisibleChange={setFriendDialogOpen}
/>
</div>
<div>
<b>GroupSelection</b>
<Button color="primary" onClick={() => setGroupDialogOpen(true)}>
</Button>
<GroupSelection
selectedGroups={selectedGroups}
onSelect={setSelectedGroups}
placeholder="请选择群聊"
className=""
visible={groupDialogOpen}
onVisibleChange={setGroupDialogOpen}
/>
</div>
</Space>
<div style={{ marginTop: 32 }}>
<div>ID: {selectedDevices.join(", ")}</div>
<div>ID: {selectedFriends.join(", ")}</div>
<div>ID: {selectedGroups.join(", ")}</div>
</div>
</div>
);
}