update
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "device-management-client",
|
"name": "device-management-client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+1
-1
@@ -127,7 +127,7 @@ function App() {
|
|||||||
{ key: 'personnel', icon: <TeamOutlined />, label: '人员管理' },
|
{ key: 'personnel', icon: <TeamOutlined />, label: '人员管理' },
|
||||||
...(user?.role === 'admin' ? [{ key: 'accounts', icon: <UserSwitchOutlined />, label: '账户管理' }] : []),
|
...(user?.role === 'admin' ? [{ key: 'accounts', icon: <UserSwitchOutlined />, label: '账户管理' }] : []),
|
||||||
{ key: 'notifications', icon: <BellOutlined />, label: '通知管理' },
|
{ key: 'notifications', icon: <BellOutlined />, label: '通知管理' },
|
||||||
{ key: 'download', icon: <InfoCircleOutlined />, label: '软件下载' },
|
{ key: 'download', icon: <InfoCircleOutlined />, label: '软件下载' }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 移动端底部导航(5个核心入口)
|
// 移动端底部导航(5个核心入口)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ function About() {
|
|||||||
return (
|
return (
|
||||||
<div className="about-page">
|
<div className="about-page">
|
||||||
<Card>
|
<Card>
|
||||||
<h1>影视星河 · 设备管理系统 v2.1</h1>
|
<h1>影视星河 · 设备管理系统 v2.2</h1>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<h2>软件概述</h2>
|
<h2>软件概述</h2>
|
||||||
@@ -62,12 +62,17 @@ function About() {
|
|||||||
|
|
||||||
<h2>版本信息</h2>
|
<h2>版本信息</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>当前版本:v2.1</li>
|
<li>当前版本:v2.2</li>
|
||||||
<li>发布日期:2026年8月</li>
|
<li>发布日期:2026年8月</li>
|
||||||
<li>许可证:MIT License</li>
|
<li>许可证:MIT License</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>更新日志</h2>
|
<h2>更新日志</h2>
|
||||||
|
<h3>v2.2(2026年8月)</h3>
|
||||||
|
<ol>
|
||||||
|
<li>添加群聊删除人员的功能</li>
|
||||||
|
<li>修复创建群聊显示框闪退的bug</li>
|
||||||
|
</ol>
|
||||||
<h3>v2.1(2026年8月)</h3>
|
<h3>v2.1(2026年8月)</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li>添加用户修改密码功能</li>
|
<li>添加用户修改密码功能</li>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ function Dashboard({ onNavigate }) {
|
|||||||
<h3>系统信息</h3>
|
<h3>系统信息</h3>
|
||||||
<Row gutter={16} style={{ marginTop: 12 }}>
|
<Row gutter={16} style={{ marginTop: 12 }}>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<p><strong>系统版本:</strong>v2.1</p>
|
<p><strong>系统版本:</strong>v2.2</p>
|
||||||
<p><strong>系统名称:</strong>设备管理系统</p>
|
<p><strong>系统名称:</strong>设备管理系统</p>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
|
|||||||
+133
-55
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
import { Card, Button, Input, List, Badge, Modal, Form, Select, Tabs, Avatar, Empty, message, Popconfirm, Space, Tag, Tooltip } from 'antd'
|
import { Card, Button, Input, List, Badge, Modal, Form, Select, Tabs, Avatar, Empty, message, Popconfirm, Space, Tag, Tooltip } from 'antd'
|
||||||
import { PlusOutlined, SendOutlined, TeamOutlined, UserOutlined, ArrowLeftOutlined, DeleteOutlined } from '@ant-design/icons'
|
import { PlusOutlined, SendOutlined, TeamOutlined, UserOutlined, ArrowLeftOutlined, DeleteOutlined, SettingOutlined, UserDeleteOutlined } from '@ant-design/icons'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const { TextArea } = Input
|
const { TextArea } = Input
|
||||||
@@ -25,6 +25,7 @@ function Messages({ user }) {
|
|||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState('')
|
||||||
const [sending, setSending] = useState(false)
|
const [sending, setSending] = useState(false)
|
||||||
const [createVisible, setCreateVisible] = useState(false)
|
const [createVisible, setCreateVisible] = useState(false)
|
||||||
|
const [membersVisible, setMembersVisible] = useState(false)
|
||||||
const [createForm] = Form.useForm()
|
const [createForm] = Form.useForm()
|
||||||
const [creating, setCreating] = useState(false)
|
const [creating, setCreating] = useState(false)
|
||||||
const bottomRef = useRef(null)
|
const bottomRef = useRef(null)
|
||||||
@@ -148,6 +149,31 @@ function Messages({ user }) {
|
|||||||
|
|
||||||
const canDelete = (conv) => conv.type === 'direct' || conv.creator_id === user?.id
|
const canDelete = (conv) => conv.type === 'direct' || conv.creator_id === user?.id
|
||||||
|
|
||||||
|
// 群主移除群成员
|
||||||
|
const handleRemoveMember = async (memberId) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.delete(`/api/chat/conversations/${activeConv.id}/members/${memberId}`)
|
||||||
|
if (res.data.success) {
|
||||||
|
message.success(res.data.message)
|
||||||
|
setMembersVisible(false)
|
||||||
|
// 刷新会话列表与当前会话
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
closeConversation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
message.error(err.response?.data?.message || '操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const otherOptions = allUsers.filter(u => u.id !== user?.id).map(u => ({
|
const otherOptions = allUsers.filter(u => u.id !== user?.id).map(u => ({
|
||||||
value: u.id,
|
value: u.id,
|
||||||
label: (
|
label: (
|
||||||
@@ -234,6 +260,11 @@ function Messages({ user }) {
|
|||||||
<Tag style={{ marginLeft: 8 }}>{activeConv.member_count}人</Tag>
|
<Tag style={{ marginLeft: 8 }}>{activeConv.member_count}人</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{activeConv.type === 'group' && activeConv.creator_id === user?.id && (
|
||||||
|
<Button size="small" type="text" icon={<SettingOutlined />} onClick={() => setMembersVisible(true)} style={{ marginLeft: 'auto' }}>
|
||||||
|
成员管理
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-messages">
|
<div className="chat-messages">
|
||||||
{messages.length === 0 && <Empty description="还没有消息,发送第一条吧" image={Empty.PRESENTED_IMAGE_SIMPLE} style={{ marginTop: 40 }} />}
|
{messages.length === 0 && <Empty description="还没有消息,发送第一条吧" image={Empty.PRESENTED_IMAGE_SIMPLE} style={{ marginTop: 40 }} />}
|
||||||
@@ -267,6 +298,103 @@ function Messages({ user }) {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ============ 发起对话 / 建群弹窗 ============
|
||||||
|
// 注意:这里必须用普通 JSX 变量而非内部函数组件,
|
||||||
|
// 否则轮询重新渲染时组件类型变化会导致弹窗被卸载重建(闪退)
|
||||||
|
const createModal = (
|
||||||
|
<Modal
|
||||||
|
title="发起对话"
|
||||||
|
open={createVisible}
|
||||||
|
onCancel={() => setCreateVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
width={480}
|
||||||
|
>
|
||||||
|
<Form form={createForm} layout="vertical" onFinish={handleCreate} initialValues={{ tab: 'direct' }}>
|
||||||
|
<Form.Item name="tab" noStyle>
|
||||||
|
<Tabs
|
||||||
|
centered
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'direct',
|
||||||
|
label: '单聊',
|
||||||
|
children: (
|
||||||
|
<Form.Item name="userId" label="选择对话对象" rules={[{ required: true, message: '请选择对话对象' }]}>
|
||||||
|
<Select placeholder="选择用户(绿点为在线)" options={otherOptions} showSearch optionFilterProp="label" />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'group',
|
||||||
|
label: '创建群聊',
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Form.Item name="name" label="群名称" rules={[{ required: true, message: '请输入群名称' }]}>
|
||||||
|
<Input placeholder="例如:拍摄组工作群" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="memberIds" label="群成员(除自己外至少选1人)" rules={[{ required: true, message: '请选择群成员' }]}>
|
||||||
|
<Select mode="multiple" placeholder="选择群成员" options={otherOptions} showSearch optionFilterProp="label" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<div style={{ textAlign: 'right' }}>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => setCreateVisible(false)}>取消</Button>
|
||||||
|
<Button type="primary" htmlType="submit" loading={creating}>确定</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
// ============ 群成员管理弹窗(仅群主可见) ============
|
||||||
|
const membersModal = (
|
||||||
|
<Modal
|
||||||
|
title={`群成员管理(${activeConv?.member_count || 0}人)`}
|
||||||
|
open={membersVisible}
|
||||||
|
onCancel={() => setMembersVisible(false)}
|
||||||
|
footer={null}
|
||||||
|
width={420}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
dataSource={activeConv?.members || []}
|
||||||
|
renderItem={(m) => (
|
||||||
|
<List.Item
|
||||||
|
actions={
|
||||||
|
m.id !== activeConv?.creator_id
|
||||||
|
? [
|
||||||
|
<Popconfirm
|
||||||
|
key="del"
|
||||||
|
title={`确定将 ${m.display_name || m.username} 移出群聊吗?`}
|
||||||
|
onConfirm={() => handleRemoveMember(m.id)}
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<Button type="text" size="small" danger icon={<UserDeleteOutlined />} />
|
||||||
|
</Popconfirm>
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={<Avatar icon={<UserOutlined />} />}
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
{m.display_name || m.username}
|
||||||
|
{m.id === activeConv?.creator_id && <Tag color="gold" style={{ marginLeft: 8 }}>群主</Tag>}
|
||||||
|
{m.id === user?.id && <Tag style={{ marginLeft: 8 }}>我</Tag>}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
// ============ 布局 ============
|
// ============ 布局 ============
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
@@ -275,7 +403,8 @@ function Messages({ user }) {
|
|||||||
{activeConv ? chatWindow : conversationList}
|
{activeConv ? chatWindow : conversationList}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<CreateConvModal />
|
{createModal}
|
||||||
|
{membersModal}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -289,61 +418,10 @@ function Messages({ user }) {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<CreateConvModal />
|
{createModal}
|
||||||
|
{membersModal}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============ 发起对话 / 建群弹窗 ============
|
|
||||||
function CreateConvModal() {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title="发起对话"
|
|
||||||
open={createVisible}
|
|
||||||
onCancel={() => setCreateVisible(false)}
|
|
||||||
footer={null}
|
|
||||||
width={480}
|
|
||||||
>
|
|
||||||
<Form form={createForm} layout="vertical" onFinish={handleCreate} initialValues={{ tab: 'direct' }}>
|
|
||||||
<Form.Item name="tab" noStyle>
|
|
||||||
<Tabs
|
|
||||||
centered
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: 'direct',
|
|
||||||
label: '单聊',
|
|
||||||
children: (
|
|
||||||
<Form.Item name="userId" label="选择对话对象" rules={[{ required: true, message: '请选择对话对象' }]}>
|
|
||||||
<Select placeholder="选择用户(绿点为在线)" options={otherOptions} showSearch optionFilterProp="label" />
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'group',
|
|
||||||
label: '创建群聊',
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<Form.Item name="name" label="群名称" rules={[{ required: true, message: '请输入群名称' }]}>
|
|
||||||
<Input placeholder="例如:拍摄组工作群" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="memberIds" label="群成员(除自己外至少选1人)" rules={[{ required: true, message: '请选择群成员' }]}>
|
|
||||||
<Select mode="multiple" placeholder="选择群成员" options={otherOptions} showSearch optionFilterProp="label" />
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<div style={{ textAlign: 'right' }}>
|
|
||||||
<Space>
|
|
||||||
<Button onClick={() => setCreateVisible(false)}>取消</Button>
|
|
||||||
<Button type="primary" htmlType="submit" loading={creating}>确定</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Messages
|
export default Messages
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "device-management-system",
|
"name": "device-management-system",
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"description": "影视星河设备管理系统 - Device Management System",
|
"description": "影视星河设备管理系统 - Device Management System",
|
||||||
"main": "electron/main.js",
|
"main": "electron/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -867,6 +867,29 @@ app.delete('/api/chat/conversations/:id', (req, res) => {
|
|||||||
res.json({ success: true, message: conv.type === 'group' ? '群组已解散' : '会话已删除' });
|
res.json({ success: true, message: conv.type === 'group' ? '群组已解散' : '会话已删除' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 移除群成员(群主可移除任意成员,群主本人不可被移除)
|
||||||
|
app.delete('/api/chat/conversations/:id/members/:userId', (req, res) => {
|
||||||
|
const conv = db.prepare('SELECT * FROM conversations WHERE id = ?').get(req.params.id);
|
||||||
|
if (!conv || conv.type !== 'group') {
|
||||||
|
return res.status(404).json({ success: false, message: '群聊不存在' });
|
||||||
|
}
|
||||||
|
if (!isConversationMember(conv.id, req.user.id)) {
|
||||||
|
return res.status(403).json({ success: false, message: '您不是该群聊的成员' });
|
||||||
|
}
|
||||||
|
const targetId = req.params.userId;
|
||||||
|
if (targetId === conv.creator_id) {
|
||||||
|
return res.status(400).json({ success: false, message: '群主不能被移除' });
|
||||||
|
}
|
||||||
|
if (!isConversationMember(conv.id, targetId)) {
|
||||||
|
return res.status(404).json({ success: false, message: '该用户不在群聊中' });
|
||||||
|
}
|
||||||
|
if (req.user.id !== conv.creator_id) {
|
||||||
|
return res.status(403).json({ success: false, message: '只有群主可以移除群成员' });
|
||||||
|
}
|
||||||
|
db.prepare('DELETE FROM conversation_members WHERE conversation_id = ? AND user_id = ?').run(conv.id, targetId);
|
||||||
|
res.json({ success: true, message: '已将该成员移出群聊' });
|
||||||
|
});
|
||||||
|
|
||||||
// ==================== 认证 API ====================
|
// ==================== 认证 API ====================
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
|
|||||||
Reference in New Issue
Block a user