diff --git a/.DS_Store b/.DS_Store
index 53fdf9b..3f5d312 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/dist.zip b/dist.zip
index d5163d7..87e8622 100644
Binary files a/dist.zip and b/dist.zip differ
diff --git a/public/.DS_Store b/public/.DS_Store
index c176d00..63e21d2 100644
Binary files a/public/.DS_Store and b/public/.DS_Store differ
diff --git a/src/config/api.js b/src/config/api.js
index becdf26..71c83e7 100644
--- a/src/config/api.js
+++ b/src/config/api.js
@@ -41,7 +41,14 @@ const API_CONFIG = {
USER_STATS: '/api/admin/user-stats',
KICK_USER: (userId) => `/api/admin/kick-user/${userId}`,
TASKS_MONITOR: '/api/admin/tasks/monitor',
- SYSTEM_RESOURCES: '/api/admin/system/resources'
+ SYSTEM_RESOURCES: '/api/admin/system/resources',
+ HOT_WORDS: {
+ LIST: '/api/admin/hot-words',
+ CREATE: '/api/admin/hot-words',
+ UPDATE: (id) => `/api/admin/hot-words/${id}`,
+ DELETE: (id) => `/api/admin/hot-words/${id}`,
+ SYNC: '/api/admin/hot-words/sync'
+ }
},
TAGS: {
LIST: '/api/tags'
diff --git a/src/pages/AdminManagement.jsx b/src/pages/AdminManagement.jsx
index 44465d2..3de1130 100644
--- a/src/pages/AdminManagement.jsx
+++ b/src/pages/AdminManagement.jsx
@@ -1,55 +1,56 @@
import React from 'react';
-import { Settings, Users, Smartphone, Shield, BookText } from 'lucide-react';
+import { Settings, Users, Smartphone, Shield, BookText, Type } from 'lucide-react';
import { Tabs } from 'antd';
import UserManagement from './admin/UserManagement';
-import SystemConfiguration from './admin/SystemConfiguration';
import ClientManagement from './ClientManagement';
import PermissionManagement from './admin/PermissionManagement';
import DictManagement from './admin/DictManagement';
+import HotWordManagement from './admin/HotWordManagement';
import Breadcrumb from '../components/Breadcrumb';
import './AdminManagement.css';
const { TabPane } = Tabs;
const AdminManagement = () => {
+ const items = [
+ {
+ key: 'userManagement',
+ label: 用户管理,
+ children: ,
+ },
+ {
+ key: 'permissionManagement',
+ label: 权限管理,
+ children: ,
+ },
+ {
+ key: 'dictManagement',
+ label: 字典管理,
+ children: ,
+ },
+ {
+ key: 'hotWordManagement',
+ label: 热词管理,
+ children: ,
+ },
+ {
+ key: 'clientManagement',
+ label: 客户端管理,
+ children: ,
+ },
+ ];
+
return (
-
- 用户管理}
- key="userManagement"
- >
-
-
- 权限管理}
- key="permissionManagement"
- >
-
-
- 字典管理}
- key="dictManagement"
- >
-
-
- 系统配置}
- key="systemConfiguration"
- >
-
-
- 客户端管理}
- key="clientManagement"
- >
-
-
-
+
diff --git a/src/pages/CreateMeeting.jsx b/src/pages/CreateMeeting.jsx
index 4247ea4..5f5232c 100644
--- a/src/pages/CreateMeeting.jsx
+++ b/src/pages/CreateMeeting.jsx
@@ -40,8 +40,8 @@ const CreateMeeting = ({ user }) => {
const fetchUsers = async () => {
try {
- // 获取所有用户,设置较大的size参数
- const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.USERS.LIST}?page=1&size=1000`));
+ // 获取所有普通用户(role_id=2),设置较大的size参数
+ const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.USERS.LIST}?page=1&size=1000&role_id=2`));
setAvailableUsers(response.data.users.filter(u => u.user_id !== user.user_id));
} catch (err) {
console.error('Error fetching users:', err);
diff --git a/src/pages/EditMeeting.jsx b/src/pages/EditMeeting.jsx
index 88cb928..618c7d9 100644
--- a/src/pages/EditMeeting.jsx
+++ b/src/pages/EditMeeting.jsx
@@ -73,8 +73,8 @@ const EditMeeting = ({ user }) => {
const fetchUsers = async () => {
try {
- // 获取所有用户,设置较大的size参数
- const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.USERS.LIST}?page=1&size=1000`));
+ // 获取所有普通用户(role_id=2),设置较大的size参数
+ const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.USERS.LIST}?page=1&size=1000&role_id=2`));
setAvailableUsers(response.data.users.filter(u => u.user_id !== user.user_id));
} catch (err) {
console.error('Error fetching users:', err);
diff --git a/src/pages/admin/DictManagement.jsx b/src/pages/admin/DictManagement.jsx
index 66f11e2..d6bd4af 100644
--- a/src/pages/admin/DictManagement.jsx
+++ b/src/pages/admin/DictManagement.jsx
@@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react';
-import { Tree, Button, Form, Input, InputNumber, Select, Switch, Space, message, Card, Empty, Popconfirm } from 'antd';
+import { Tree, Button, Form, Input, InputNumber, Select, Switch, Space, Card, Empty, Popconfirm } from 'antd';
import { BookText, Plus, Save, Trash2, FolderTree, FileText, X } from 'lucide-react';
import apiClient from '../../utils/apiClient';
import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
+import Toast from '../../components/Toast';
import './DictManagement.css';
const { Option } = Select;
@@ -16,8 +17,19 @@ const DictManagement = () => {
const [treeData, setTreeData] = useState([]); // 树形结构数据
const [selectedNode, setSelectedNode] = useState(null); // 当前选中的节点
const [isEditing, setIsEditing] = useState(false); // 是否处于编辑状态
+ const [toasts, setToasts] = useState([]);
const [form] = Form.useForm();
+ // Toast helper functions
+ const showToast = (message, type = 'info') => {
+ const id = Date.now();
+ setToasts(prev => [...prev, { id, message, type }]);
+ };
+
+ const removeToast = (id) => {
+ setToasts(prev => prev.filter(toast => toast.id !== id));
+ };
+
// 获取所有字典类型
const fetchDictTypes = async () => {
try {
@@ -26,7 +38,7 @@ const DictManagement = () => {
setDictTypes(response.data.types);
}
} catch (error) {
- message.error('获取字典类型失败');
+ showToast('获取字典类型失败', 'error');
}
};
@@ -48,7 +60,7 @@ const DictManagement = () => {
form.resetFields();
}
} catch (error) {
- message.error('获取字典数据失败');
+ showToast('获取字典数据失败', 'error');
} finally {
setLoading(false);
}
@@ -126,7 +138,7 @@ const DictManagement = () => {
try {
values.extension_attr = JSON.parse(values.extension_attr);
} catch (e) {
- message.error('扩展属性 JSON 格式错误');
+ showToast('扩展属性 JSON 格式错误:无法解析JSON', 'error');
return;
}
}
@@ -136,25 +148,39 @@ const DictManagement = () => {
if (selectedNode) {
// 更新
- await apiClient.put(
+ const response = await apiClient.put(
buildApiUrl(API_ENDPOINTS.DICT_DATA.UPDATE(selectedNode.id)),
values
);
- message.success('更新成功');
+ if (response.code === '200') {
+ showToast('更新成功', 'success');
+ // 重新加载数据
+ fetchDictData(selectedDictType);
+ } else {
+ showToast(response.message || '更新失败', 'error');
+ }
} else {
// 新增
- await apiClient.post(buildApiUrl(API_ENDPOINTS.DICT_DATA.CREATE), values);
- message.success('创建成功');
+ const response = await apiClient.post(
+ buildApiUrl(API_ENDPOINTS.DICT_DATA.CREATE),
+ values
+ );
+ if (response.code === '200') {
+ showToast('创建成功', 'success');
+ // 重新加载数据
+ fetchDictData(selectedDictType);
+ } else {
+ showToast(response.message || '创建失败', 'error');
+ }
}
-
- // 重新加载数据
- fetchDictData(selectedDictType);
} catch (error) {
if (error.errorFields) {
// 表单验证错误
return;
}
- message.error(selectedNode ? '更新失败' : '创建失败');
+ // 显示后端返回的具体错误信息
+ const errorMsg = error.response?.data?.message || error.message || '操作失败';
+ showToast(errorMsg, 'error');
}
};
@@ -164,7 +190,7 @@ const DictManagement = () => {
try {
await apiClient.delete(buildApiUrl(API_ENDPOINTS.DICT_DATA.DELETE(selectedNode.id)));
- message.success('删除成功');
+ showToast('删除成功', 'success');
// 重新加载数据
setSelectedNode(null);
@@ -172,7 +198,7 @@ const DictManagement = () => {
form.resetFields();
fetchDictData(selectedDictType);
} catch (error) {
- message.error('删除失败:' + (error.message || '未知错误'));
+ showToast('删除失败:' + (error.message || '未知错误'), 'error');
}
};
@@ -400,6 +426,16 @@ const DictManagement = () => {
+
+ {/* Toast notifications */}
+ {toasts.map(toast => (
+ removeToast(toast.id)}
+ />
+ ))}
);
};
diff --git a/src/pages/admin/HotWordManagement.css b/src/pages/admin/HotWordManagement.css
new file mode 100644
index 0000000..8c91eda
--- /dev/null
+++ b/src/pages/admin/HotWordManagement.css
@@ -0,0 +1,38 @@
+.hot-word-management {
+ padding: 16px 0;
+}
+
+.hot-word-management .status-bar {
+ background-color: #e6f7ff;
+ border: 1px solid #91d5ff;
+ padding: 10px 16px;
+ border-radius: 4px;
+ margin-bottom: 16px;
+ font-size: 14px;
+ color: #003a8c;
+}
+
+.hot-word-management .table-header {
+ margin-bottom: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.hot-word-management .helper-text {
+ font-size: 13px;
+ color: #8c8c8c;
+}
+
+.animate-spin {
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/src/pages/admin/HotWordManagement.jsx b/src/pages/admin/HotWordManagement.jsx
new file mode 100644
index 0000000..06092ad
--- /dev/null
+++ b/src/pages/admin/HotWordManagement.jsx
@@ -0,0 +1,328 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Button, Modal, Form, Input, InputNumber, Switch, Space, Popconfirm, Tag } from 'antd';
+import { Plus, RefreshCw, Trash2, Edit } from 'lucide-react';
+import apiClient from '../../utils/apiClient';
+import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
+import Toast from '../../components/Toast';
+import './HotWordManagement.css';
+
+const HotWordManagement = () => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [syncLoading, setSyncLoading] = useState(false);
+ const [modalVisible, setModalVisible] = useState(false);
+ const [editingItem, setEditingItem] = useState(null);
+ const [form] = Form.useForm();
+ const [vocabInfo, setVocabInfo] = useState(null);
+ const [toasts, setToasts] = useState([]);
+
+ // Toast helper functions
+ const showToast = (message, type = 'info') => {
+ const id = Date.now();
+ setToasts(prev => [...prev, { id, message, type }]);
+ };
+
+ const removeToast = (id) => {
+ setToasts(prev => prev.filter(toast => toast.id !== id));
+ };
+
+ const fetchHotWords = async () => {
+ setLoading(true);
+ try {
+ const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.HOT_WORDS.LIST));
+ console.log('Fetch hot words response:', response);
+ // apiClient unwrap the code 200 responses, so response IS {code, message, data}
+ if (response.code === "200") {
+ setData(response.data);
+ } else {
+ showToast(response.message, 'error');
+ }
+ } catch (error) {
+ console.error('Fetch hot words error:', error);
+ showToast(error.message || '获取热词列表失败', 'error');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const fetchVocabInfo = async () => {
+ try {
+ const response = await apiClient.get(
+ buildApiUrl(API_ENDPOINTS.DICT_DATA.BY_CODE('system_config', 'asr_vocabulary_id'))
+ );
+ if (response.code === "200") {
+ const data = response.data;
+ // extension_attr is already parsed by backend
+ const vocabId = data.extension_attr?.value || '';
+ setVocabInfo({ ...data, vocabId });
+ }
+ } catch (error) {
+ // Ignore error if config not found
+ }
+ };
+
+ useEffect(() => {
+ fetchHotWords();
+ fetchVocabInfo();
+ }, []);
+
+ useEffect(() => {
+ if (modalVisible) {
+ form.resetFields();
+ if (editingItem) {
+ form.setFieldsValue(editingItem);
+ } else {
+ form.setFieldsValue({ weight: 4, lang: 'zh', status: 1 });
+ }
+ }
+ }, [modalVisible, editingItem, form]);
+
+ const handleAdd = () => {
+ setEditingItem(null);
+ setModalVisible(true);
+ };
+
+ const handleEdit = (record) => {
+ setEditingItem(record);
+ setModalVisible(true);
+ };
+
+ const handleDelete = async (id) => {
+ try {
+ const response = await apiClient.delete(buildApiUrl(API_ENDPOINTS.ADMIN.HOT_WORDS.DELETE(id)));
+ if (response.code === "200") {
+ showToast('删除成功', 'success');
+ fetchHotWords();
+ } else {
+ showToast(response.message, 'error');
+ }
+ } catch (error) {
+ showToast('删除失败', 'error');
+ }
+ };
+
+ const handleSync = async () => {
+ console.log('Starting sync...');
+ setSyncLoading(true);
+ try {
+ const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.ADMIN.HOT_WORDS.SYNC));
+ console.log('Sync response:', response);
+ if (response.code === "200") {
+ showToast('同步到阿里云成功', 'success');
+ fetchVocabInfo();
+ } else {
+ showToast(response.message, 'error');
+ }
+ } catch (error) {
+ console.error('Sync error:', error);
+ showToast('同步失败', 'error');
+ } finally {
+ setSyncLoading(false);
+ }
+ };
+
+ const handleModalOk = async () => {
+ try {
+ const values = await form.validateFields();
+ if (editingItem) {
+ const response = await apiClient.put(
+ buildApiUrl(API_ENDPOINTS.ADMIN.HOT_WORDS.UPDATE(editingItem.id)),
+ values
+ );
+ if (response.code === "200") {
+ showToast('更新成功', 'success');
+ setModalVisible(false);
+ fetchHotWords();
+ } else {
+ showToast(response.message, 'error');
+ }
+ } else {
+ const response = await apiClient.post(
+ buildApiUrl(API_ENDPOINTS.ADMIN.HOT_WORDS.CREATE),
+ values
+ );
+ if (response.code === "200") {
+ showToast('创建成功', 'success');
+ setModalVisible(false);
+ fetchHotWords();
+ } else {
+ showToast(response.message, 'error');
+ }
+ }
+ } catch (error) {
+ console.error('Submit hot word error:', error);
+ }
+ };
+
+ const columns = [
+ {
+ title: '热词内容',
+ dataIndex: 'text',
+ key: 'text',
+ },
+ {
+ title: '权重',
+ dataIndex: 'weight',
+ key: 'weight',
+ render: (weight) => {weight}
+ },
+ {
+ title: '语言',
+ dataIndex: 'lang',
+ key: 'lang',
+ render: (lang) => {lang === 'zh' ? '中文' : '英文'}
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ key: 'status',
+ render: (status, record) => (
+ {
+ try {
+ await apiClient.put(
+ buildApiUrl(API_ENDPOINTS.ADMIN.HOT_WORDS.UPDATE(record.id)),
+ { status: checked ? 1 : 0 }
+ );
+ fetchHotWords();
+ } catch (error) {
+ showToast('更新状态失败', 'error');
+ }
+ }}
+ />
+ )
+ },
+ {
+ title: '更新时间',
+ dataIndex: 'update_time',
+ key: 'update_time',
+ render: (time) => new Date(time).toLocaleString()
+ },
+ {
+ title: '操作',
+ key: 'action',
+ render: (_, record) => (
+
+ }
+ onClick={() => handleEdit(record)}
+ />
+ handleDelete(record.id)}
+ okText="确定"
+ cancelText="取消"
+ >
+ }
+ />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+ {vocabInfo && (
+
+
+ 当前生效热词表 ID: {vocabInfo.vocabId}
+ 上次同步时间: {new Date(vocabInfo.update_time).toLocaleString()}
+
+
+ )}
+
+
+
+ }
+ onClick={handleAdd}
+ >
+ 添加热词
+
+ }
+ onClick={handleSync}
+ loading={syncLoading}
+ >
+ 同步到阿里云
+
+
+
+ 提示:修改热词后需点击“同步到阿里云”才能在转录中生效。权重越高识别概率越大。
+
+
+
+
+
+
setModalVisible(false)}
+ forceRender
+ >
+
+
+
+
+
+
+
+
+
+
+ ({ checked: value === 1 })}
+ getValueFromEvent={(checked) => (checked ? 1 : 0)}
+ >
+
+
+
+
+
+
+ {/* Toast notifications */}
+ {toasts.map(toast => (
+
removeToast(toast.id)}
+ />
+ ))}
+
+ );
+ };
+ export default HotWordManagement;
diff --git a/src/pages/admin/PermissionManagement.jsx b/src/pages/admin/PermissionManagement.jsx
index 916066a..6f67a61 100644
--- a/src/pages/admin/PermissionManagement.jsx
+++ b/src/pages/admin/PermissionManagement.jsx
@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react';
-import { Table, Button, Checkbox, message, Spin, Card } from 'antd';
+import { Table, Button, Checkbox, Spin, Card } from 'antd';
import { Shield, Save } from 'lucide-react';
import axios from 'axios';
+import Toast from '../../components/Toast';
import './PermissionManagement.css';
const PermissionManagement = () => {
@@ -10,6 +11,17 @@ const PermissionManagement = () => {
const [permissions, setPermissions] = useState({}); // {roleId: [menuId1, menuId2, ...]}
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
+ const [toasts, setToasts] = useState([]);
+
+ // Toast helper functions
+ const showToast = (message, type = 'info') => {
+ const id = Date.now();
+ setToasts(prev => [...prev, { id, message, type }]);
+ };
+
+ const removeToast = (id) => {
+ setToasts(prev => prev.filter(toast => toast.id !== id));
+ };
// 获取所有数据
const fetchData = async () => {
@@ -18,7 +30,7 @@ const PermissionManagement = () => {
// 获取token - 与apiClient保持一致
const savedUser = localStorage.getItem('iMeetingUser');
if (!savedUser) {
- message.error('未找到登录信息,请重新登录');
+ showToast('未找到登录信息,请重新登录', 'error');
return;
}
const user = JSON.parse(savedUser);
@@ -56,10 +68,10 @@ const PermissionManagement = () => {
setPermissions(permsMap);
} else {
- message.error('获取数据失败: ' + (rolesRes.data.message || menusRes.data.message));
+ showToast('获取数据失败: ' + (rolesRes.data.message || menusRes.data.message), 'error');
}
} catch (error) {
- message.error('获取数据失败: ' + (error.response?.data?.message || error.message));
+ showToast('获取数据失败: ' + (error.response?.data?.message || error.message), 'error');
} finally {
setLoading(false);
}
@@ -87,7 +99,7 @@ const PermissionManagement = () => {
// 获取token - 与apiClient保持一致
const savedUser = localStorage.getItem('iMeetingUser');
if (!savedUser) {
- message.error('未找到登录信息,请重新登录');
+ showToast('未找到登录信息,请重新登录', 'error');
return;
}
const user = JSON.parse(savedUser);
@@ -102,11 +114,11 @@ const PermissionManagement = () => {
);
}
- message.success('权限保存成功');
+ showToast('权限保存成功', 'success');
fetchData(); // 重新加载数据
} catch (error) {
console.error('Error saving permissions:', error);
- message.error('保存权限失败');
+ showToast('保存权限失败', 'error');
} finally {
setSaving(false);
}
@@ -193,6 +205,16 @@ const PermissionManagement = () => {
/>
+
+ {/* Toast notifications */}
+ {toasts.map(toast => (
+ removeToast(toast.id)}
+ />
+ ))}
);
};
diff --git a/src/pages/admin/SystemConfiguration.jsx b/src/pages/admin/SystemConfiguration.jsx
deleted file mode 100644
index b85f89a..0000000
--- a/src/pages/admin/SystemConfiguration.jsx
+++ /dev/null
@@ -1,299 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Settings, Brain, Shield, Save, RotateCcw, CheckCircle, AlertCircle, Loader } from 'lucide-react';
-import apiClient from '../../utils/apiClient';
-import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
-import PageLoading from '../../components/PageLoading';
-import './SystemConfiguration.css';
-
-const SystemConfiguration = () => {
- const [configs, setConfigs] = useState({
- model_name: '',
- template_text: '',
- DEFAULT_RESET_PASSWORD: '',
- MAX_FILE_SIZE: 0,
- TIMELINE_PAGESIZE: 0
- });
-
- // 用于存储输入框的显示值(MB单位)
- const [displayValues, setDisplayValues] = useState({
- MAX_FILE_SIZE: 0,
- TIMELINE_PAGESIZE: 0
- });
-
- const [originalConfigs, setOriginalConfigs] = useState({});
- const [loading, setLoading] = useState(true);
- const [saving, setSaving] = useState(false);
- const [message, setMessage] = useState({ type: '', text: '' });
-
- // 工具函数:字节转MB
- const bytesToMB = (bytes) => {
- return Math.round(bytes / (1024 * 1024));
- };
-
- // 工具函数:MB转字节
- const mbToBytes = (mb) => {
- return mb * 1024 * 1024;
- };
-
- // 加载配置数据
- useEffect(() => {
- fetchConfigs();
- }, []);
-
- const fetchConfigs = async () => {
- try {
- setLoading(true);
- const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.SYSTEM_CONFIG));
- const configData = response.data;
- setConfigs(configData);
- setOriginalConfigs(configData);
- // 初始化显示值
- setDisplayValues({
- MAX_FILE_SIZE: bytesToMB(configData.MAX_FILE_SIZE),
- TIMELINE_PAGESIZE: configData.TIMELINE_PAGESIZE
- });
- setMessage({ type: '', text: '' });
- } catch (err) {
- console.error('Failed to fetch configurations:', err);
- setMessage({
- type: 'error',
- text: '加载配置失败:' + (err.response?.data?.message || err.message)
- });
- } finally {
- setLoading(false);
- }
- };
-
- const handleInputChange = (key, value) => {
- if (key === 'MAX_FILE_SIZE') {
- // 直接更新显示值,不进行转换
- setDisplayValues(prev => ({
- ...prev,
- MAX_FILE_SIZE: value
- }));
- // 转换为字节存储
- const numValue = parseInt(value) || 0;
- setConfigs(prev => ({
- ...prev,
- [key]: mbToBytes(numValue)
- }));
- } else if (key === 'TIMELINE_PAGESIZE') {
- // 直接更新显示值
- setDisplayValues(prev => ({
- ...prev,
- TIMELINE_PAGESIZE: value
- }));
- // 存储数字
- const numValue = parseInt(value) || 0;
- setConfigs(prev => ({
- ...prev,
- [key]: numValue
- }));
- } else {
- setConfigs(prev => ({
- ...prev,
- [key]: value
- }));
- }
- };
-
- const handleSave = async () => {
- try {
- setSaving(true);
- setMessage({ type: 'loading', text: '保存配置中...' });
-
- const saveResponse = await apiClient.put(buildApiUrl(API_ENDPOINTS.ADMIN.SYSTEM_CONFIG), configs);
-
- setOriginalConfigs(configs);
- setMessage({ type: 'success', text: saveResponse.message });
-
- // 3秒后清除成功消息
- setTimeout(() => {
- setMessage({ type: '', text: '' });
- }, 3000);
- } catch (err) {
- console.error('Failed to save configurations:', err);
- setMessage({
- type: 'error',
- text: '保存配置失败:' + (err.response?.data?.message || err.message)
- });
- } finally {
- setSaving(false);
- }
- };
-
- const handleReset = () => {
- setConfigs(originalConfigs);
- setDisplayValues({
- MAX_FILE_SIZE: bytesToMB(originalConfigs.MAX_FILE_SIZE),
- TIMELINE_PAGESIZE: originalConfigs.TIMELINE_PAGESIZE
- });
- setMessage({ type: '', text: '' });
- };
-
- const hasChanges = JSON.stringify(configs) !== JSON.stringify(originalConfigs);
-
- if (loading) {
- return ;
- }
-
- return (
-
-
-
-
- 系统配置
-
-
-
- {message.text && (
-
- {message.type === 'success' &&
}
- {message.type === 'error' &&
}
- {message.type === 'loading' &&
}
- {message.text}
-
- )}
-
-
- {/* 模型配置块 */}
-
-
-
-
- 模型配置
-
-
配置AI模型相关参数
-
-
-
-
-
handleInputChange('model_name', e.target.value)}
- placeholder="请输入模型名称"
- />
-
- 指定要使用的AI模型名称,例如:gpt-4, claude-3-sonnet等
-
-
-
-
-
-
-
-
-
- {/* 管理配置块 */}
-
-
-
-
- 管理配置
-
-
系统管理相关设置
-
-
-
-
-
handleInputChange('DEFAULT_RESET_PASSWORD', e.target.value)}
- placeholder="请输入默认密码"
- />
-
- 管理员重置用户密码时使用的默认密码,建议设置为安全的临时密码
-
-
-
-
-
-
handleInputChange('MAX_FILE_SIZE', e.target.value)}
- placeholder="请输入文件大小限制(MB)"
- min="1"
- max="1000"
- />
-
- 用户上传音频文件的大小限制,单位为MB,建议设置为50-200MB
-
-
-
-
-
-
handleInputChange('TIMELINE_PAGESIZE', e.target.value)}
- placeholder="请输入分页大小"
- min="5"
- max="100"
- />
-
- 会议时间轴每页显示的会议数量,建议设置为10-50
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default SystemConfiguration;