diff --git a/client/src/pages/DeviceLoan.jsx b/client/src/pages/DeviceLoan.jsx index 9a4cb24..1b5032f 100644 --- a/client/src/pages/DeviceLoan.jsx +++ b/client/src/pages/DeviceLoan.jsx @@ -23,6 +23,7 @@ function DeviceLoan({ user }) { const [loading, setLoading] = useState(false) const [statusFilter, setStatusFilter] = useState('') const [keyword, setKeyword] = useState('') + const [deviceKw, setDeviceKw] = useState('') // 借用弹窗 const [loanOpen, setLoanOpen] = useState(false) @@ -74,6 +75,7 @@ function DeviceLoan({ user }) { // ============ 借用 ============ const openLoanModal = () => { loanForm.resetFields() + setDeviceKw('') setLoanPhoto('') setLoanPhotoPreview('') setLoanOpen(true) @@ -216,9 +218,16 @@ function DeviceLoan({ user }) { } ] - const deviceOptions = devices + // 借用选项:排除已报废与借用中(已借用设备不能二次借用),支持按设备编码/名称搜索 + const kw = deviceKw.trim().toLowerCase() + const loanDeviceOptions = devices + .filter(d => d.status !== '已报废' && d.status !== '借用中') + .filter(d => !kw || (d.serial_number || '').toLowerCase().includes(kw) || (d.name || '').toLowerCase().includes(kw)) + .map(d => ({ value: d.id, label: `${d.name}(编号 ${d.serial_number || '-'} / ${d.status})` })) + + const returnDeviceOptions = devices .filter(d => d.status !== '已报废') - .map(d => ({ value: d.id, label: `${d.name}(${d.type || '未分类'} / ${d.status})` })) + .map(d => ({ value: d.id, label: `${d.name}(编号 ${d.serial_number || '-'} / ${d.status})` })) const personnelOptions = personnel.map(p => ({ value: p.name, @@ -299,8 +308,16 @@ function DeviceLoan({ user }) { width={520} >
+ + setDeviceKw(e.target.value)} + /> + - + diff --git a/client/src/pages/DeviceManagement.jsx b/client/src/pages/DeviceManagement.jsx index f41a135..4c4bceb 100644 --- a/client/src/pages/DeviceManagement.jsx +++ b/client/src/pages/DeviceManagement.jsx @@ -9,7 +9,7 @@ const { Option } = Select const { TextArea } = Input const deviceTypes = ['笔记本电脑', '台式机', '显示器', '打印机', '网络设备', '服务器', '投影仪', '其他'] -const deviceStatuses = ['正常', '维修中', '已报废', '闲置'] +const deviceStatuses = ['正常', '维修中', '已报废', '闲置', '借用中'] function DeviceManagement({ user }) { const isAdminUser = can(user, 'device:edit') @@ -229,7 +229,8 @@ function DeviceManagement({ user }) { '正常': 'green', '维修中': 'orange', '已报废': 'red', - '闲置': 'default' + '闲置': 'default', + '借用中': 'blue' } return {status} } diff --git a/client/src/pages/Messages.jsx b/client/src/pages/Messages.jsx index 0b62d8b..d868aae 100644 --- a/client/src/pages/Messages.jsx +++ b/client/src/pages/Messages.jsx @@ -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 && ( + + )} ( @@ -543,6 +588,31 @@ function Messages({ user }) { ) + // ============ 邀请成员弹窗(仅群主,拉人) ============ + const inviteModal = ( + setInviteVisible(false)} + onOk={handleInviteMembers} + okText="邀请" + cancelText="取消" + width={420} + > +