feat(DeviceLoan): 添加设备搜索功能并优化借用流程

- 新增设备关键词搜索状态和输入框,支持按设备编码/名称搜索
- 优化借用设备选项过滤逻辑,排除已报废和借用中的设备
- 在打开借用弹窗时清空设备搜索关键词
- 更新借用设备选择下拉框提示文案

feat(DeviceManagement): 添加借用中设备状态

- 在设备状态列表中添加"借用中"状态选项
- 为"借用中"状态配置蓝色标签样式

feat(Messages): 实现群组邀请成员功能

- 添加邀请成员弹窗组件和相关状态管理
- 实现群主邀请成员加入群聊的功能接口调用
- 过滤可邀请用户列表,排除已在群内的成员
- 添加邀请成员按钮,仅群主可见
```
This commit is contained in:
mei
2026-08-15 09:42:45 +08:00
parent 1271627ef9
commit 7ebc4cc053
3 changed files with 97 additions and 7 deletions
+73 -1
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'
import { Card, Button, Input, List, Badge, Modal, Form, Select, Tabs, Avatar, Empty, message, Popconfirm, Space, Tag, Tooltip, Popover, Image } from 'antd'
import { PlusOutlined, SendOutlined, TeamOutlined, UserOutlined, ArrowLeftOutlined, DeleteOutlined, SettingOutlined, UserDeleteOutlined, CheckCircleFilled, ClockCircleOutlined, SmileOutlined, PictureOutlined, VideoCameraOutlined, PaperClipOutlined, FileOutlined, DownloadOutlined } from '@ant-design/icons'
import { PlusOutlined, SendOutlined, TeamOutlined, UserOutlined, ArrowLeftOutlined, DeleteOutlined, SettingOutlined, UserDeleteOutlined, UserAddOutlined, CheckCircleFilled, ClockCircleOutlined, SmileOutlined, PictureOutlined, VideoCameraOutlined, PaperClipOutlined, FileOutlined, DownloadOutlined } from '@ant-design/icons'
import axios from 'axios'
const { TextArea } = Input
@@ -45,6 +45,8 @@ function Messages({ user }) {
const [sending, setSending] = useState(false)
const [createVisible, setCreateVisible] = useState(false)
const [membersVisible, setMembersVisible] = useState(false)
const [inviteVisible, setInviteVisible] = useState(false)
const [inviteIds, setInviteIds] = useState([])
const [createForm] = Form.useForm()
const [creating, setCreating] = useState(false)
const [uploading, setUploading] = useState(false)
@@ -242,6 +244,38 @@ function Messages({ user }) {
}
}
// 群主邀请成员加入群聊(拉人)
const handleInviteMembers = async () => {
if (!inviteIds.length) {
message.warning('请选择要邀请的成员')
return
}
try {
const res = await axios.post(`/api/chat/conversations/${activeConv.id}/members`, { user_ids: inviteIds })
if (res.data.success) {
message.success(res.data.message)
setInviteVisible(false)
setInviteIds([])
const list = await axios.get('/api/chat/conversations')
if (list.data.success) {
setConversations(list.data.data)
const conv = list.data.data.find(c => c.id === activeConv.id)
if (conv) {
setActiveConv(conv)
activeConvRef.current = conv
}
}
}
} catch (err) {
message.error(err.response?.data?.message || '邀请失败')
}
}
// 可邀请的用户(尚未在群内)
const inviteOptions = allUsers
.filter(u => !(activeConv?.members || []).some(m => m.id === u.id))
.map(u => ({ value: u.id, label: `${u.display_name || u.username}${u.role === 'admin' ? '(管理员)' : ''}` }))
const otherOptions = allUsers.filter(u => u.id !== user?.id).map(u => ({
value: u.id,
label: (
@@ -507,6 +541,17 @@ function Messages({ user }) {
footer={null}
width={420}
>
{activeConv?.creator_id === user?.id && (
<Button
type="primary"
icon={<UserAddOutlined />}
block
style={{ marginBottom: 12 }}
onClick={() => { setInviteIds([]); setInviteVisible(true) }}
>
邀请成员(拉人入群)
</Button>
)}
<List
dataSource={activeConv?.members || []}
renderItem={(m) => (
@@ -543,6 +588,31 @@ function Messages({ user }) {
</Modal>
)
// ============ 邀请成员弹窗(仅群主,拉人) ============
const inviteModal = (
<Modal
title="邀请成员加入群聊"
open={inviteVisible}
onCancel={() => setInviteVisible(false)}
onOk={handleInviteMembers}
okText="邀请"
cancelText="取消"
width={420}
>
<Select
mode="multiple"
placeholder="选择要拉入群聊的成员"
value={inviteIds}
onChange={setInviteIds}
options={inviteOptions}
showSearch
optionFilterProp="label"
style={{ width: '100%' }}
/>
<p style={{ color: '#999', fontSize: 12, marginTop: 8 }}>被邀请成员将收到系统通知,并立即加入群聊。</p>
</Modal>
)
// ============ 布局 ============
if (isMobile) {
return (
@@ -553,6 +623,7 @@ function Messages({ user }) {
{createModal}
{membersModal}
{inviteModal}
</div>
)
}
@@ -568,6 +639,7 @@ function Messages({ user }) {
{createModal}
{membersModal}
{inviteModal}
</div>
)
}