update:1.后端新增API配置预设管理接口,支持API配置预设并保存到数据库 2.前端Settings页面重构为Tab布局,新增配置预设管理功能页面 3.优化角色/组织更新逻辑,修复组织字段同步问题 4.更新组织管理-组织成员UI显示,支持翻页显示和跳转

This commit is contained in:
xiamuceer
2025-12-15 15:58:57 +08:00
parent 247156d2c1
commit a753c75b9c
12 changed files with 2163 additions and 1041 deletions
+10 -2
View File
@@ -612,8 +612,12 @@ export default function Characters() {
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="主要成员" name="organization_members">
<Input placeholder="如:张三、李四、王五" />
<Form.Item
label="势力等级"
name="power_level"
tooltip="0-100的数值,表示组织的影响力"
>
<InputNumber min={0} max={100} style={{ width: '100%' }} />
</Form.Item>
</Col>
</Row>
@@ -626,6 +630,10 @@ export default function Characters() {
<TextArea rows={2} placeholder="描述组织的宗旨和目标..." />
</Form.Item>
<Form.Item label="主要成员" name="organization_members">
<Input placeholder="如:张三、李四、王五" />
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item label="所在地" name="location">
File diff suppressed because it is too large Load Diff
+73 -39
View File
@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom';
import { Card, Table, Tag, Button, Space, message, Modal, Form, Select, InputNumber, Input, Descriptions } from 'antd';
import { PlusOutlined, TeamOutlined, UserOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useStore } from '../store';
import { useCharacterSync } from '../store/hooks';
import axios from 'axios';
interface Organization {
@@ -41,6 +42,7 @@ interface Character {
export default function Organizations() {
const { projectId } = useParams<{ projectId: string }>();
const { currentProject } = useStore();
const { refreshCharacters } = useCharacterSync();
const [organizations, setOrganizations] = useState<Organization[]>([]);
const [selectedOrg, setSelectedOrg] = useState<Organization | null>(null);
const [members, setMembers] = useState<OrganizationMember[]>([]);
@@ -216,7 +218,7 @@ export default function Organizations() {
<span>{name}</span>
</Space>
),
width: isMobile ? 80 : undefined,
width: isMobile ? 100 : undefined,
},
{
title: '职位',
@@ -225,50 +227,53 @@ export default function Organizations() {
render: (position: string, record: OrganizationMember) => (
<Tag color="blue">{position} {!isMobile && `(级别 ${record.rank})`}</Tag>
),
width: isMobile ? 120 : undefined,
},
{
title: '忠诚度',
dataIndex: 'loyalty',
key: 'loyalty',
render: (loyalty: number) => (
<span style={{ color: loyalty >= 70 ? 'green' : loyalty >= 40 ? 'orange' : 'red' }}>
{loyalty}%
</span>
),
width: isMobile ? 80 : undefined,
},
...(!isMobile ? [
{
title: '忠诚度',
dataIndex: 'loyalty',
key: 'loyalty',
render: (loyalty: number) => (
<span style={{ color: loyalty >= 70 ? 'green' : loyalty >= 40 ? 'orange' : 'red' }}>
{loyalty}%
</span>
),
},
{
title: '贡献度',
dataIndex: 'contribution',
key: 'contribution',
render: (contribution: number) => `${contribution}%`,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={getStatusColor(status)}>{getStatusText(status)}</Tag>
),
},
{
title: '加入时间',
dataIndex: 'joined_at',
key: 'joined_at',
render: (time: string) => time || '-',
}
] : []),
{
title: '贡献度',
dataIndex: 'contribution',
key: 'contribution',
render: (contribution: number) => `${contribution}%`,
width: isMobile ? 80 : undefined,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={getStatusColor(status)}>{getStatusText(status)}</Tag>
),
width: isMobile ? 80 : undefined,
},
{
title: '加入时间',
dataIndex: 'joined_at',
key: 'joined_at',
render: (time: string) => time || '-',
width: isMobile ? 120 : undefined,
},
{
title: '操作',
key: 'action',
render: (_: unknown, record: OrganizationMember) => (
<Space>
<Space size={isMobile ? 0 : 'small'}>
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEditMember(record)}
style={isMobile ? { padding: '4px' } : undefined}
>
{isMobile ? '' : '编辑'}
</Button>
@@ -278,12 +283,13 @@ export default function Organizations() {
size="small"
icon={<DeleteOutlined />}
onClick={() => handleRemoveMember(record.id)}
style={isMobile ? { padding: '4px' } : undefined}
>
{isMobile ? '删除' : '移除'}
{isMobile ? '' : '移除'}
</Button>
</Space>
),
width: isMobile ? 60 : undefined,
width: isMobile ? 50 : undefined,
fixed: isMobile ? 'right' as const : undefined,
},
];
@@ -419,9 +425,24 @@ export default function Organizations() {
columns={memberColumns}
dataSource={members}
rowKey="id"
pagination={isMobile ? { simple: true, pageSize: 10 } : false}
pagination={
members.length > 5
? {
defaultPageSize: 5,
showSizeChanger: true,
showQuickJumper: !isMobile,
showTotal: (total) => `${total} 名成员`,
pageSizeOptions: [5, 10, 20],
simple: isMobile,
position: ['bottomCenter'],
}
: false
}
size="small"
scroll={isMobile ? { x: 'max-content', y: 400 } : undefined}
scroll={{
x: isMobile ? 'max-content' : undefined,
y: members.length > 10 ? 500 : undefined,
}}
/>
</Card>
</Space>
@@ -653,7 +674,20 @@ export default function Organizations() {
await axios.put(`/api/organizations/${selectedOrg.id}`, values);
message.success('组织信息更新成功');
setIsEditOrgModalOpen(false);
loadOrganizations();
editOrgForm.resetFields();
// 重新获取更新后的组织列表
const res = await axios.get(`/api/organizations/project/${projectId}`);
setOrganizations(res.data);
// 更新当前选中的组织详情
const updatedOrg = res.data.find((org: Organization) => org.id === selectedOrg.id);
if (updatedOrg) {
setSelectedOrg(updatedOrg);
}
// 刷新全局 store
await refreshCharacters();
} catch (error) {
message.error('更新失败');
console.error(error);
File diff suppressed because it is too large Load Diff
+39
View File
@@ -46,6 +46,10 @@ import type {
MCPTool,
MCPToolCallRequest,
MCPToolCallResponse,
APIKeyPreset,
PresetCreateRequest,
PresetUpdateRequest,
PresetListResponse,
} from '../types';
const api = axios.create({
@@ -197,6 +201,41 @@ export const settingsApi = {
error_type?: string;
suggestions?: string[];
}>('/settings/test', params),
// API配置预设管理
getPresets: () =>
api.get<unknown, PresetListResponse>('/settings/presets'),
createPreset: (data: PresetCreateRequest) =>
api.post<unknown, APIKeyPreset>('/settings/presets', data),
updatePreset: (presetId: string, data: PresetUpdateRequest) =>
api.put<unknown, APIKeyPreset>(`/settings/presets/${presetId}`, data),
deletePreset: (presetId: string) =>
api.delete<unknown, { message: string; preset_id: string }>(`/settings/presets/${presetId}`),
activatePreset: (presetId: string) =>
api.post<unknown, { message: string; preset_id: string; preset_name: string }>(`/settings/presets/${presetId}/activate`),
testPreset: (presetId: string) =>
api.post<unknown, {
success: boolean;
message: string;
response_time_ms?: number;
provider?: string;
model?: string;
response_preview?: string;
details?: Record<string, boolean>;
error?: string;
error_type?: string;
suggestions?: string[];
}>(`/settings/presets/${presetId}/test`),
createPresetFromCurrent: (name: string, description?: string) =>
api.post<unknown, APIKeyPreset>('/settings/presets/from-current', null, {
params: { name, description }
}),
};
export const projectApi = {
+41 -4
View File
@@ -36,6 +36,43 @@ export interface SettingsUpdate {
preferences?: string;
}
// API预设相关类型定义
export interface APIKeyPresetConfig {
api_provider: string;
api_key: string;
api_base_url?: string;
llm_model: string;
temperature: number;
max_tokens: number;
}
export interface APIKeyPreset {
id: string;
name: string;
description?: string;
is_active: boolean;
created_at: string;
config: APIKeyPresetConfig;
}
export interface PresetCreateRequest {
name: string;
description?: string;
config: APIKeyPresetConfig;
}
export interface PresetUpdateRequest {
name?: string;
description?: string;
config?: APIKeyPresetConfig;
}
export interface PresetListResponse {
presets: APIKeyPreset[];
total: number;
active_preset_id?: string;
}
// LinuxDO 授权 URL 响应
export interface AuthUrlResponse {
auth_url: string;
@@ -622,22 +659,22 @@ export interface MCPPlugin {
description?: string;
plugin_type: 'http' | 'stdio';
category: string;
// HTTP类型字段
server_url?: string;
headers?: Record<string, string>;
// Stdio类型字段
command?: string;
args?: string[];
env?: Record<string, string>;
// 状态字段
enabled: boolean;
status: 'active' | 'inactive' | 'error';
last_error?: string;
last_test_at?: string;
// 时间戳
created_at: string;
}