Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,186 @@
/**
* 公告管理 API
*/
import { requestClient } from '#/api/request';
const BASE_URL = '/api/core/announcement';
// ============ 类型定义 ============
export interface Announcement {
id: string;
title: string;
content: string;
summary: string;
status: 'draft' | 'expired' | 'published';
priority: 0 | 1 | 2;
is_top: boolean;
target_type: 'all' | 'dept' | 'role' | 'user';
target_ids: string[];
publish_time?: string;
expire_time?: string;
publisher_id?: string;
publisher_name: string;
read_count: number;
created_at: string;
}
export interface AnnouncementListItem {
id: string;
title: string;
summary: string;
status: string;
priority: number;
is_top: boolean;
target_type: string;
publisher_name: string;
read_count: number;
publish_time?: string;
created_at: string;
}
export interface UserAnnouncement {
id: string;
title: string;
summary: string;
content: string;
priority: number;
is_top: boolean;
is_read: boolean;
publisher_name: string;
publish_time?: string;
}
export interface AnnouncementCreate {
title: string;
content: string;
summary?: string;
status?: string;
priority?: number;
is_top?: boolean;
target_type?: string;
target_ids?: string[];
publish_time?: string;
expire_time?: string;
}
export interface AnnouncementUpdate {
title?: string;
content?: string;
summary?: string;
status?: string;
priority?: number;
is_top?: boolean;
target_type?: string;
target_ids?: string[];
publish_time?: string;
expire_time?: string;
}
export interface ReadStats {
total_read: number;
readers: Array<{
read_at: string;
user_id: string;
user_name: string;
}>;
}
// ============ 管理端 API ============
/**
* 获取公告列表(管理端)
*/
export async function getAnnouncementListApi(params?: {
keyword?: string;
page?: number;
pageSize?: number;
status?: string;
}) {
return requestClient.get<{
items: AnnouncementListItem[];
total: number;
}>(`${BASE_URL}/admin/list`, { params });
}
/**
* 获取公告详情(管理端)
*/
export async function getAnnouncementDetailApi(id: string) {
return requestClient.get<Announcement>(`${BASE_URL}/admin/${id}`);
}
/**
* 创建公告
*/
export async function createAnnouncementApi(data: AnnouncementCreate) {
return requestClient.post<Announcement>(`${BASE_URL}/admin`, data);
}
/**
* 更新公告
*/
export async function updateAnnouncementApi(
id: string,
data: AnnouncementUpdate,
) {
return requestClient.put<Announcement>(`${BASE_URL}/admin/${id}`, data);
}
/**
* 删除公告
*/
export async function deleteAnnouncementApi(id: string) {
return requestClient.delete(`${BASE_URL}/admin/${id}`);
}
/**
* 发布公告
*/
export async function publishAnnouncementApi(id: string) {
return requestClient.post<Announcement>(`${BASE_URL}/admin/${id}/publish`);
}
/**
* 获取阅读统计
*/
export async function getReadStatsApi(id: string) {
return requestClient.get<ReadStats>(`${BASE_URL}/admin/${id}/stats`);
}
// ============ 用户端 API ============
/**
* 获取我的公告列表
*/
export async function getUserAnnouncementListApi(params?: {
page?: number;
pageSize?: number;
unread_only?: boolean;
}) {
return requestClient.get<{
items: UserAnnouncement[];
total: number;
}>(`${BASE_URL}/user/list`, { params });
}
/**
* 获取未读公告数量
*/
export async function getUnreadAnnouncementCountApi() {
return requestClient.get<{ count: number }>(`${BASE_URL}/user/unread-count`);
}
/**
* 获取公告详情(用户端,会自动标记已读)
*/
export async function getUserAnnouncementDetailApi(id: string) {
return requestClient.get<UserAnnouncement>(`${BASE_URL}/user/${id}`);
}
/**
* 标记公告已读
*/
export async function markAnnouncementReadApi(id: string) {
return requestClient.post(`${BASE_URL}/user/${id}/read`);
}
@@ -0,0 +1,48 @@
/**
* API Token (Personal Access Token) 管理
*/
import { requestClient } from '#/api/request';
export namespace ApiTokenApi {
export interface TokenItem {
id: string;
name: string;
token_prefix: string;
expires_at?: null | string;
last_used_at?: null | string;
description?: null | string;
is_active: boolean;
sys_create_datetime?: null | string;
}
export interface CreateTokenRequest {
name: string;
expires_at?: null | string;
description?: string;
}
export interface CreateTokenResponse {
id: string;
name: string;
token: string;
token_prefix: string;
expires_at?: null | string;
description?: null | string;
sys_create_datetime?: null | string;
}
}
export async function getApiTokenListApi() {
return requestClient.get<ApiTokenApi.TokenItem[]>('/api/core/api-tokens');
}
export async function createApiTokenApi(data: ApiTokenApi.CreateTokenRequest) {
return requestClient.post<ApiTokenApi.CreateTokenResponse>(
'/api/core/api-tokens',
data,
);
}
export async function revokeApiTokenApi(tokenId: string) {
return requestClient.delete(`/api/core/api-tokens/${tokenId}`);
}
@@ -0,0 +1,204 @@
import { requestClient } from '#/api/request';
/**
* 应用管理 API
* 低代码平台的顶层容器,管理表单、页面、工作流等资源
*/
// ============ 类型定义 ============
/** 应用类型 */
export type AppType =
| 'ai'
| 'dashboard'
| 'form'
| 'mixed'
| 'screen'
| 'workflow';
/** 应用状态 */
export type AppStatus = 'disabled' | 'draft' | 'published';
/** 应用信息 */
export interface Application {
id: string;
name: string;
code: string;
description: string;
icon: string;
cover: string;
app_type: AppType;
status: AppStatus;
home_path: null | string;
version: number;
config: Record<string, any>;
owner_id: null | string;
team_ids: string[];
system_menu_ids: string[];
sort: number;
is_deleted: boolean;
sys_create_datetime: string;
sys_update_datetime: string;
}
/** 应用列表项 */
export interface ApplicationListItem {
id: string;
name: string;
code: string;
description: string;
icon: string;
cover: string;
app_type: AppType;
status: AppStatus;
home_path: null | string;
version: number;
owner_id: null | string;
system_menu_ids: string[];
sys_create_datetime: string;
}
/** 创建应用请求 */
export interface ApplicationCreateInput {
name: string;
code: string;
description?: string;
icon?: string;
cover?: string;
app_type?: AppType;
home_path?: string;
config?: Record<string, any>;
system_menu_ids?: string[];
}
/** 更新应用请求 */
export interface ApplicationUpdateInput {
name?: string;
code?: string;
description?: string;
icon?: string;
cover?: string;
app_type?: AppType;
home_path?: string;
status?: AppStatus;
config?: Record<string, any>;
team_ids?: string[];
system_menu_ids?: string[];
}
/** 列表查询参数 */
export interface ApplicationListParams {
page?: number;
pageSize?: number;
keyword?: string;
appType?: AppType;
status?: AppStatus;
}
/** 分页响应 */
interface PaginatedResponse<T> {
items: T[];
total: number;
}
/** 统计信息 */
export interface ApplicationStats {
total: number;
by_status: Record<string, number>;
by_type: Record<string, number>;
}
// ============ 应用管理 API ============
/**
* 获取应用列表(分页)
*/
export async function getApplicationListApi(params?: ApplicationListParams) {
return requestClient.get<PaginatedResponse<ApplicationListItem>>(
'/api/core/applications/',
{ params },
);
}
/**
* 获取应用统计信息
*/
export async function getApplicationStatsApi() {
return requestClient.get<{ data: ApplicationStats }>(
'/api/core/applications/stats',
);
}
/**
* 检查字段唯一性
*/
export async function checkApplicationUniqueApi(
field: 'code' | 'name',
value: string,
excludeId?: string,
) {
return requestClient.get<{ data: { unique: boolean } }>(
'/api/core/applications/check/unique',
{
params: { field, value, excludeId },
},
);
}
/**
* 根据编码获取应用
*/
export async function getApplicationByCodeApi(code: string) {
return requestClient.get<Application>(`/api/core/applications/code/${code}`);
}
/**
* 获取应用详情
*/
export async function getApplicationDetailApi(id: string) {
return requestClient.get<Application>(`/api/core/applications/${id}`);
}
/**
* 创建应用
*/
export async function createApplicationApi(data: ApplicationCreateInput) {
return requestClient.post<Application>('/api/core/applications/', data);
}
/**
* 更新应用
*/
export async function updateApplicationApi(
id: string,
data: ApplicationUpdateInput,
) {
return requestClient.put<Application>(`/api/core/applications/${id}`, data);
}
/**
* 发布应用
*/
export async function publishApplicationApi(id: string) {
return requestClient.post<Application>(
`/api/core/applications/${id}/publish`,
);
}
/**
* 停用应用
*/
export async function disableApplicationApi(id: string) {
return requestClient.post<Application>(
`/api/core/applications/${id}/disable`,
);
}
/**
* 删除应用
*/
export async function deleteApplicationApi(id: string, hard = false) {
return requestClient.delete(`/api/core/applications/${id}`, {
params: { hard },
});
}
+53
View File
@@ -0,0 +1,53 @@
import { baseRequestClient, requestClient } from '#/api/request';
export namespace AuthApi {
/** 登录接口参数 */
export interface LoginParams {
password?: string;
username?: string;
}
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
refreshToken: string;
}
export interface RefreshTokenResult {
data: LoginResult;
status: number;
}
}
/**
* 登录
*/
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/api/core/login', data);
}
/**
* 刷新accessToken
*/
export async function refreshTokenApi(refreshToken: string) {
return baseRequestClient.post<AuthApi.RefreshTokenResult>(
'/api/core/refresh_token',
{
refresh_token: refreshToken,
},
);
}
/**
* 退出登录
*/
export async function logoutApi() {
return requestClient.get('/api/core/logout');
}
/**
* 获取用户权限码
*/
export async function getAccessCodesApi() {
return requestClient.get<string[]>('/api/core/userinfo');
}
+211
View File
@@ -0,0 +1,211 @@
import { requestClient } from '#/api/request';
const BASE_URL = '/api/core/chat';
// ============ 类型定义 ============
export interface Conversation {
id: string;
type: 'group' | 'private';
name?: string;
avatar?: string;
owner_id?: string;
last_message_time?: string;
last_message_preview?: string;
member_count: number;
sys_create_datetime?: string;
unread_count: number;
is_muted: boolean;
is_pinned: boolean;
peer_user_id?: string;
peer_user_name?: string;
peer_user_avatar?: string;
}
export interface ConversationMember {
id: string;
user_id: string;
role: string;
nickname?: string;
is_muted: boolean;
is_pinned: boolean;
unread_count: number;
joined_at?: string;
user_name?: string;
user_avatar?: string;
}
export interface ChatMessage {
id: string;
conversation_id: string;
sender_id: string;
msg_type: string;
content?: string;
file_id?: string;
reply_to_id?: string;
is_recalled: boolean;
recalled_at?: string;
extra?: Record<string, any>;
sys_create_datetime?: string;
sender_name?: string;
sender_avatar?: string;
reply_to_preview?: string;
reply_to_sender_name?: string;
file_name?: string;
file_url?: string;
file_size?: number;
file_ext?: string;
_sending?: boolean;
_tempId?: string;
_localUrl?: string;
}
// ============ 会话 API ============
/** 获取会话列表 */
export async function getConversationsApi() {
return requestClient.get<{ items: Conversation[]; total: number }>(
`${BASE_URL}/conversations`,
);
}
/** 创建/获取单聊 */
export async function createPrivateConversationApi(userId: string) {
return requestClient.post<Conversation>(`${BASE_URL}/conversations/private`, {
user_id: userId,
});
}
/** 创建群聊 */
export async function createGroupConversationApi(data: {
avatar?: string;
member_ids: string[];
name: string;
}) {
return requestClient.post<Conversation>(
`${BASE_URL}/conversations/group`,
data,
);
}
/** 获取会话详情 */
export async function getConversationApi(conversationId: string) {
return requestClient.get<Conversation>(
`${BASE_URL}/conversations/${conversationId}`,
);
}
/** 更新群聊信息 */
export async function updateConversationApi(
conversationId: string,
data: { avatar?: string; name?: string },
) {
return requestClient.put<Conversation>(
`${BASE_URL}/conversations/${conversationId}`,
data,
);
}
/** 解散群聊 */
export async function deleteConversationApi(conversationId: string) {
return requestClient.delete(`${BASE_URL}/conversations/${conversationId}`);
}
// ============ 成员 API ============
/** 获取成员列表 */
export async function getMembersApi(conversationId: string) {
return requestClient.get<ConversationMember[]>(
`${BASE_URL}/conversations/${conversationId}/members`,
);
}
/** 添加成员 */
export async function addMembersApi(conversationId: string, userIds: string[]) {
return requestClient.post(
`${BASE_URL}/conversations/${conversationId}/members`,
{ user_ids: userIds },
);
}
/** 移除成员 */
export async function removeMemberApi(conversationId: string, userId: string) {
return requestClient.delete(
`${BASE_URL}/conversations/${conversationId}/members/${userId}`,
);
}
// ============ 会话设置 API ============
/** 置顶/取消置顶 */
export async function togglePinApi(conversationId: string, value: boolean) {
return requestClient.put(`${BASE_URL}/conversations/${conversationId}/pin`, {
value,
});
}
/** 免打扰设置 */
export async function toggleMuteApi(conversationId: string, value: boolean) {
return requestClient.put(`${BASE_URL}/conversations/${conversationId}/mute`, {
value,
});
}
// ============ 消息 API ============
/** 获取消息列表(游标分页) */
export async function getMessagesApi(
conversationId: string,
params?: { beforeId?: string; limit?: number },
) {
return requestClient.get<{ has_more: boolean; items: ChatMessage[] }>(
`${BASE_URL}/conversations/${conversationId}/messages`,
{ params },
);
}
/** 发送消息(REST备用) */
export async function sendChatMessageApi(
conversationId: string,
data: {
content?: string;
extra?: Record<string, any>;
file_id?: string;
msg_type?: string;
reply_to_id?: string;
},
) {
return requestClient.post<ChatMessage>(
`${BASE_URL}/conversations/${conversationId}/messages`,
data,
);
}
/** 撤回消息 */
export async function recallMessageApi(messageId: string) {
return requestClient.post(`${BASE_URL}/messages/${messageId}/recall`);
}
/** 获取所有未读聊天消息 */
export async function getUnreadChatMessagesApi(limit = 50) {
return requestClient.get<{ items: ChatMessage[]; total: number }>(
`${BASE_URL}/messages/unread`,
{ params: { limit } },
);
}
/** 获取在线用户ID列表 */
export async function getOnlineUsersApi() {
return requestClient.get<{ user_ids: string[] }>(`${BASE_URL}/users/online`);
}
/** 标记已读 */
export async function markConversationReadApi(
conversationId: string,
messageId: string,
) {
return requestClient.post(
`${BASE_URL}/conversations/${conversationId}/read`,
{ message_id: messageId },
);
}
@@ -0,0 +1,526 @@
import { requestClient } from '#/api/request';
/**
* 数据源类型定义
*/
export type DataSourceType = 'api' | 'sql' | 'static';
export type ResultType =
| 'chart-axis'
| 'chart-gauge'
| 'chart-heatmap'
| 'chart-pie'
| 'chart-radar'
| 'chart-scatter'
| 'list'
| 'object'
| 'tree'
| 'value';
export type HttpMethod = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
export type ApiAuthType = 'api_key' | 'basic_auth' | 'bearer_token' | 'none';
export type ApiBodyType = 'form-data' | 'json' | 'none' | 'raw' | 'x-www-form-urlencoded';
/**
* Query参数定义
*/
export interface QueryParamItem {
key: string;
value: string;
description?: string;
enabled?: boolean;
}
/**
* 认证配置
*/
export interface AuthConfig {
// bearer_token
token?: string;
// basic_auth
username?: string;
password?: string;
// api_key
key_name?: string;
key_value?: string;
key_position?: 'header' | 'query';
}
/**
* 成功条件配置
*/
export interface SuccessCondition {
status_codes?: number[];
field_path?: string;
field_value?: string;
}
/**
* 参数定义
*/
export interface ParamDefinition {
name: string;
label?: string;
type?: 'boolean' | 'date' | 'datetime' | 'float' | 'integer' | 'string';
required?: boolean;
default?: any;
}
/**
* 树形配置
*/
export interface TreeConfig {
id_field?: string;
parent_field?: string;
children_field?: string;
root_value?: any;
}
/**
* 图表配置
*/
export interface ChartConfig {
// chart-axis(轴向图表)配置
x_field?: string; // X轴字段
series_fields?: string[]; // 系列字段(多个)
series_names?: string[]; // 系列名称(可选)
// chart-pie(饼图)配置
name_field?: string; // 名称字段
value_field?: string; // 数值字段
// chart-gauge(仪表盘)配置
max_field?: string; // 最大值字段
// chart-radar(雷达图)配置
indicator_field?: string; // 指标名称字段
value_fields?: string[]; // 数值字段(多个系列)
// chart-scatter(散点图)配置
y_field?: string; // Y坐标字段
size_field?: string; // 大小字段(气泡图)
}
/**
* 数据源完整类型
*/
export interface DataSource {
id: string;
application_id?: string;
application_name?: string;
name: string;
code: string;
source_type: DataSourceType;
description?: string;
status: boolean;
sort?: number;
// API 配置
api_url?: string;
api_method?: HttpMethod;
api_headers?: Record<string, string>;
api_query_params?: QueryParamItem[];
api_body_type?: ApiBodyType;
api_body?: Record<string, any>;
api_content_type?: string;
api_timeout?: number;
api_data_path?: string;
// API 认证
api_auth_type?: ApiAuthType;
api_auth_config?: AuthConfig;
// API 高级
api_retry_count?: number;
api_retry_interval?: number;
api_success_condition?: SuccessCondition;
api_proxy?: string;
api_follow_redirects?: boolean;
api_verify_ssl?: boolean;
// SQL 配置
sql_content?: string;
db_connection?: string;
// 静态数据
static_data?: any[];
// 参数定义
params?: ParamDefinition[];
// 结果处理
result_type?: ResultType;
tree_config?: TreeConfig;
field_mapping?: Record<string, string>;
chart_config?: ChartConfig;
// 缓存配置
cache_enabled?: boolean;
cache_ttl?: number;
// 系统字段
sys_create_datetime?: string;
sys_update_datetime?: string;
}
/**
* 数据源简单类型(用于下拉选择)
*/
export interface DataSourceSimple {
id: string;
application_id?: string;
name: string;
code: string;
source_type: DataSourceType;
result_type?: ResultType;
description?: string;
}
/**
* 创建/更新数据源输入
*/
export interface DataSourceInput {
application_id?: string;
name: string;
code: string;
source_type: DataSourceType;
description?: string;
status?: boolean;
sort?: number;
// API 配置
api_url?: string;
api_method?: HttpMethod;
api_headers?: Record<string, string>;
api_query_params?: QueryParamItem[];
api_body_type?: ApiBodyType;
api_body?: Record<string, any>;
api_content_type?: string;
api_timeout?: number;
api_data_path?: string;
// API 认证
api_auth_type?: ApiAuthType;
api_auth_config?: AuthConfig;
// API 高级
api_retry_count?: number;
api_retry_interval?: number;
api_success_condition?: SuccessCondition;
api_proxy?: string;
api_follow_redirects?: boolean;
api_verify_ssl?: boolean;
// SQL 配置
sql_content?: string;
db_connection?: string;
// 静态数据
static_data?: any[];
// 参数定义
params?: ParamDefinition[];
// 结果处理
result_type?: ResultType;
tree_config?: TreeConfig;
field_mapping?: Record<string, string>;
chart_config?: ChartConfig;
// 缓存配置
cache_enabled?: boolean;
cache_ttl?: number;
}
/**
* 列表查询参数
*/
export interface DataSourceListParams {
page?: number;
pageSize?: number;
applicationId?: string;
name?: string;
code?: string;
source_type?: DataSourceType;
status?: boolean;
}
/**
* 分页响应
*/
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
/**
* 预览请求
*/
export interface PreviewRequest {
params?: Record<string, any>;
limit?: number;
}
/**
* 测试请求
*/
export interface TestRequest {
source_type: DataSourceType;
// API 配置
api_url?: string;
api_method?: HttpMethod;
api_headers?: Record<string, string>;
api_query_params?: QueryParamItem[];
api_body_type?: ApiBodyType;
api_body?: Record<string, any>;
api_content_type?: string;
api_timeout?: number;
api_data_path?: string;
// API 认证
api_auth_type?: ApiAuthType;
api_auth_config?: AuthConfig;
// API 高级
api_retry_count?: number;
api_retry_interval?: number;
api_success_condition?: SuccessCondition;
api_proxy?: string;
api_follow_redirects?: boolean;
api_verify_ssl?: boolean;
// SQL 配置
sql_content?: string;
db_connection?: string;
// 静态数据
static_data?: any[];
// 参数
params_def?: ParamDefinition[];
params?: Record<string, any>;
// 结果处理
result_type?: ResultType;
tree_config?: TreeConfig;
field_mapping?: Record<string, string>;
chart_config?: ChartConfig;
}
/**
* 复制请求
*/
export interface CopyRequest {
new_code: string;
new_name?: string;
}
/** 数据源导入请求 */
export type DataSourceImportInput = DataSourceInput;
/** 数据源导入预检查请求 */
export interface DataSourceImportCheckInput {
code: string;
}
/** 数据源导入预检查结果 */
export interface DataSourceImportCheckResult {
code_exists: boolean;
can_import: boolean;
}
// ============ CRUD API ============
/**
* 创建数据源
*/
export async function createDataSourceApi(data: DataSourceInput) {
return requestClient.post<DataSource>('/api/core/data-source', data);
}
/**
* 获取数据源列表(分页)
*/
export async function getDataSourceListApi(params?: DataSourceListParams) {
return requestClient.get<PaginatedResponse<DataSource>>(
'/api/core/data-source',
{
params,
},
);
}
/**
* 获取所有数据源(不分页)
*/
export async function getAllDataSourceApi(applicationId?: string) {
return requestClient.get<DataSourceSimple[]>(
'/api/core/data-source/get/all',
{
params: { applicationId },
},
);
}
/**
* 获取数据源详情
*/
export async function getDataSourceDetailApi(id: string) {
return requestClient.get<DataSource>(`/api/core/data-source/${id}`);
}
/**
* 根据编码获取数据源详情(包含参数定义)
*/
export async function getDataSourceByCodeApi(code: string) {
return requestClient.get<DataSource>(`/api/core/data-source/code/${code}`);
}
/**
* 更新数据源
*/
export async function updateDataSourceApi(id: string, data: DataSourceInput) {
return requestClient.put<DataSource>(`/api/core/data-source/${id}`, data);
}
/**
* 删除数据源
*/
export async function deleteDataSourceApi(id: string) {
return requestClient.delete<DataSource>(`/api/core/data-source/${id}`);
}
// ============ 执行 API ============
/**
* 执行数据源(GET 方式)
*/
export async function executeDataSourceGetApi(
code: string,
params?: Record<string, any>,
) {
return requestClient.get<any>(`/api/core/data-source/execute/${code}`, {
params,
});
}
/**
* 执行数据源(POST 方式)
*/
export async function executeDataSourcePostApi(
code: string,
params?: Record<string, any>,
) {
return requestClient.post<any>(`/api/core/data-source/execute/${code}`, {
params: params || {},
});
}
/**
* 预览数据源数据
*/
export async function previewDataSourceApi(id: string, data?: PreviewRequest) {
return requestClient.post<{ data: any[]; limited: number; total: number }>(
`/api/core/data-source/${id}/preview`,
data || { params: {}, limit: 100 },
);
}
/**
* 测试数据源配置
*/
export async function testDataSourceApi(data: TestRequest) {
return requestClient.post<{
data: any[];
limited: number;
success: boolean;
total: number;
}>('/api/core/data-source/test', data);
}
// ============ 其他 API ============
/**
* 复制数据源
*/
export async function copyDataSourceApi(id: string, data: CopyRequest) {
return requestClient.post<DataSource>(
`/api/core/data-source/${id}/copy`,
data,
);
}
/**
* 导出数据源配置(返回 JSON 文件)
*/
export async function exportDataSourceConfigApi(id: string) {
return requestClient.get<Blob>(`/api/core/data-source/${id}/export`, {
responseType: 'blob',
});
}
/**
* 导入预检查
*/
export async function checkImportDataSourceApi(data: DataSourceImportCheckInput) {
return requestClient.post<DataSourceImportCheckResult>(
'/api/core/data-source/import/check',
data,
);
}
/**
* 导入数据源配置
*/
export async function importDataSourceConfigApi(data: DataSourceImportInput) {
return requestClient.post<DataSource>('/api/core/data-source/import', data);
}
/**
* 清除数据源缓存
*/
export async function clearDataSourceCacheApi(id: string) {
return requestClient.post<{ msg: string }>(
`/api/core/data-source/${id}/clear-cache`,
);
}
/**
* 检查编码是否可用
*/
export async function checkCodeAvailableApi(code: string) {
return requestClient.get<{ available: boolean }>(
`/api/core/data-source/check-code/${code}`,
);
}
// ============ AI SQL 生成 API ============
/**
* 表关系定义
*/
export interface TableRelation {
id: string;
sourceTable: string;
sourceField: string;
targetTable: string;
targetField: string;
relationType: 'many-to-many' | 'many-to-one' | 'one-to-many' | 'one-to-one';
}
/**
* 表字段信息
*/
export interface TableFieldInfo {
name: string;
type: string;
comment?: string;
isPrimaryKey?: boolean;
}
/**
* AI 生成 SQL 请求
*/
export interface AIGenerateSqlRequest {
user_question: string;
db_connection: string;
database?: string;
schema_name?: string;
selected_tables?: string[];
table_fields?: Record<string, TableFieldInfo[]>;
table_relations?: TableRelation[];
include_table_relations?: boolean;
model_id: string;
}
/**
* AI 生成 SQL 响应
*/
export interface AIGenerateSqlResponse {
sql: string;
thought: string;
params: ParamDefinition[];
}
/**
* AI 生成 SQL
*/
export async function aiGenerateSqlApi(data: AIGenerateSqlRequest) {
return requestClient.post<AIGenerateSqlResponse>(
'/api/core/data-source/ai/generate-sql',
data,
);
}
@@ -0,0 +1,176 @@
import { requestClient } from '#/api/request';
export type DatabaseConnectionType =
| 'mysql'
| 'oracle'
| 'postgresql'
| 'sqlserver';
export interface DatabaseConnection {
id: string;
code: string;
name: string;
db_type: DatabaseConnectionType;
host: string;
port: number;
user: string;
default_database: string;
description: string;
status: boolean;
is_system: boolean;
has_password: boolean;
application_id?: string;
application_name?: string;
extra_options?: Record<string, unknown>;
sort?: number;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface DatabaseConnectionInput {
code: string;
name: string;
dbType: DatabaseConnectionType;
host: string;
port: number;
user?: string;
password?: string;
defaultDatabase?: string;
description?: string;
status?: boolean;
applicationId?: string;
}
export interface DatabaseConnectionUpdateInput {
name?: string;
dbType?: DatabaseConnectionType;
host?: string;
port?: number;
user?: string;
password?: string;
defaultDatabase?: string;
description?: string;
status?: boolean;
}
export interface DatabaseConnectionTestInput {
dbType: DatabaseConnectionType;
host: string;
port: number;
user?: string;
password?: string;
defaultDatabase?: string;
}
export interface DatabaseConnectionTestResult {
success: boolean;
message: string;
db_name?: string;
db_type?: string;
}
export interface DatabaseConnectionSimple {
code: string;
name: string;
db_type: string;
host: string;
port: number;
database: string;
is_system: boolean;
}
export async function getDatabaseConnectionListApi(params?: {
page?: number;
pageSize?: number;
applicationId?: string;
name?: string;
code?: string;
status?: boolean;
}) {
return requestClient.get<{ items: DatabaseConnection[]; total: number }>(
'/api/core/database-connection',
{ params },
);
}
export async function getAllDatabaseConnectionApi(applicationId?: string) {
return requestClient.get<DatabaseConnectionSimple[]>(
'/api/core/database-connection/get/all',
{ params: { applicationId } },
);
}
export async function getDatabaseConnectionDetailApi(id: string) {
return requestClient.get<DatabaseConnection>(
`/api/core/database-connection/${id}`,
);
}
export async function createDatabaseConnectionApi(data: DatabaseConnectionInput) {
return requestClient.post<DatabaseConnection>(
'/api/core/database-connection',
{
code: data.code,
name: data.name,
db_type: data.dbType,
host: data.host,
port: data.port,
user: data.user ?? '',
password: data.password ?? '',
default_database: data.defaultDatabase ?? '',
description: data.description ?? '',
status: data.status ?? true,
application_id: data.applicationId,
},
);
}
export async function updateDatabaseConnectionApi(
id: string,
data: DatabaseConnectionUpdateInput,
) {
return requestClient.put<DatabaseConnection>(
`/api/core/database-connection/${id}`,
{
name: data.name,
db_type: data.dbType,
host: data.host,
port: data.port,
user: data.user,
password: data.password,
default_database: data.defaultDatabase,
description: data.description,
status: data.status,
},
);
}
export async function deleteDatabaseConnectionApi(id: string) {
return requestClient.delete(`/api/core/database-connection/${id}`);
}
export async function testDatabaseConnectionApi(data: DatabaseConnectionTestInput) {
return requestClient.post<DatabaseConnectionTestResult>(
'/api/core/database-connection/test',
{
db_type: data.dbType,
host: data.host,
port: data.port,
user: data.user ?? '',
password: data.password ?? '',
default_database: data.defaultDatabase ?? '',
},
);
}
export async function testSavedDatabaseConnectionApi(id: string) {
return requestClient.post<DatabaseConnectionTestResult>(
`/api/core/database-connection/${id}/test`,
);
}
export async function checkDatabaseConnectionCodeApi(code: string) {
return requestClient.get<{ available: boolean; message?: string }>(
`/api/core/database-connection/check-code/${code}`,
);
}
@@ -0,0 +1,515 @@
/**
* 数据库管理API
*/
import { requestClient } from '#/api/request';
// ==================== 类型定义 ====================
/**
* 数据库配置
*/
export interface DatabaseConfig {
db_name: string;
name: string;
db_type: string;
host: string;
port: number;
database: string;
user: string;
has_password: boolean;
is_system?: boolean;
display_name?: string;
}
/**
* 数据库信息
*/
export interface DatabaseInfo {
name: string;
owner?: string;
encoding?: string;
collation?: string;
size?: string;
size_bytes?: number;
tables_count?: number;
description?: string;
}
/**
* Schema信息
*/
export interface SchemaInfo {
name: string;
owner?: string;
tables_count?: number;
}
/**
* 表信息
*/
export interface TableInfo {
schema_name?: string;
table_name: string;
table_type?: string;
row_count?: number;
total_size?: string;
total_size_bytes?: number;
description?: string;
}
/**
* 字段信息
*/
export interface ColumnInfo {
column_name: string;
data_type: string;
is_nullable: boolean;
column_default?: string;
character_maximum_length?: number;
numeric_precision?: number;
numeric_scale?: number;
ordinal_position?: number;
is_primary_key: boolean;
is_unique: boolean;
description?: string;
}
/**
* 索引信息
*/
export interface IndexInfo {
index_name: string;
index_type?: string;
columns: string;
is_unique: boolean;
is_primary: boolean;
definition?: string;
}
/**
* 约束信息
*/
export interface ConstraintInfo {
constraint_name: string;
constraint_type: string;
columns?: string;
definition?: string;
referenced_table?: string;
referenced_columns?: string;
}
/**
* 表结构
*/
export interface TableStructure {
table_info: TableInfo;
columns: ColumnInfo[];
indexes: IndexInfo[];
constraints: ConstraintInfo[];
}
/**
* 查询数据参数
*/
export interface QueryDataParams {
table_name: string;
schema_name?: string;
database?: string;
page: number;
page_size: number;
where?: string;
order_by?: string;
}
/**
* 查询数据响应
*/
export interface QueryDataResponse {
columns: string[];
rows: Record<string, any>[];
total: number;
page: number;
page_size: number;
}
/**
* SQL执行参数
*/
export interface ExecuteSQLParams {
sql: string;
is_query: boolean;
}
/**
* SQL执行响应
*/
export interface ExecuteSQLResponse {
success: boolean;
message: string;
columns?: string[];
rows?: Record<string, any>[];
affected_rows?: number;
execution_time: number;
}
/**
* 数据库操作响应
*/
export interface DatabaseOperationResponse {
message: string;
success: boolean;
}
/**
* Schema 操作响应
*/
export interface SchemaOperationResponse {
message: string;
success: boolean;
}
/**
* 创建数据库参数
*/
export interface CreateDatabaseParams {
charset?: string;
collation?: string;
encoding?: string;
name: string;
owner?: string;
template?: string;
}
/**
* 创建 Schema 参数
*/
export interface CreateSchemaParams {
database?: string;
name: string;
owner?: string;
}
/**
* 重命名 Schema 参数
*/
export interface RenameDatabaseParams {
new_name: string;
}
export interface RenameSchemaParams {
database?: string;
new_name: string;
}
// ==================== API接口 ====================
/**
* 获取数据库配置列表
*/
export async function getDatabaseConfigsApi() {
return requestClient.get<DatabaseConfig[]>(
'/api/core/database_manager/configs',
);
}
/**
* 测试数据库连接
*/
export async function testDatabaseConnectionApi(dbName: string) {
return requestClient.post<ConnectionTestResponse>(
`/api/core/database_manager/${dbName}/test`,
);
}
/**
* 获取数据库列表
*/
export async function getDatabasesApi(dbName: string) {
return requestClient.get<DatabaseInfo[]>(
`/api/core/database_manager/${dbName}/databases`,
);
}
/**
* 创建数据库
*/
export async function createDatabaseApi(
dbName: string,
data: CreateDatabaseParams,
) {
return requestClient.post<DatabaseOperationResponse>(
`/api/core/database_manager/${dbName}/databases`,
data,
);
}
/**
* 删除数据库
*/
export async function dropDatabaseApi(dbName: string, databaseName: string) {
return requestClient.delete<DatabaseOperationResponse>(
`/api/core/database_manager/${dbName}/databases/${encodeURIComponent(databaseName)}`,
);
}
/**
* 重命名数据库(PostgreSQL
*/
export async function renameDatabaseApi(
dbName: string,
databaseName: string,
data: RenameDatabaseParams,
) {
return requestClient.patch<DatabaseOperationResponse>(
`/api/core/database_manager/${dbName}/databases/${encodeURIComponent(databaseName)}`,
data,
);
}
/**
* 获取Schema列表(PostgreSQL
*/
export async function getSchemasApi(dbName: string, database?: string) {
return requestClient.get<SchemaInfo[]>(
`/api/core/database_manager/${dbName}/schemas`,
{ params: { database } },
);
}
/**
* 创建 SchemaPostgreSQL
*/
export async function createSchemaApi(
dbName: string,
data: CreateSchemaParams,
) {
return requestClient.post<SchemaOperationResponse>(
`/api/core/database_manager/${dbName}/schemas`,
data,
);
}
/**
* 删除 SchemaPostgreSQL
*/
export async function dropSchemaApi(
dbName: string,
schemaName: string,
database?: string,
cascade = true,
) {
return requestClient.delete<SchemaOperationResponse>(
`/api/core/database_manager/${dbName}/schemas/${encodeURIComponent(schemaName)}`,
{ params: { cascade, database } },
);
}
/**
* 重命名 SchemaPostgreSQL
*/
export async function renameSchemaApi(
dbName: string,
schemaName: string,
data: RenameSchemaParams,
) {
return requestClient.patch<SchemaOperationResponse>(
`/api/core/database_manager/${dbName}/schemas/${encodeURIComponent(schemaName)}`,
data,
);
}
/**
* 获取表列表
*/
export async function getTablesApi(
dbName: string,
database?: string,
schemaName?: string,
) {
return requestClient.get<TableInfo[]>(
`/api/core/database_manager/${dbName}/tables`,
{ params: { database, schema_name: schemaName } },
);
}
/**
* 获取表结构
*/
export async function getTableStructureApi(
dbName: string,
tableName: string,
database?: string,
schemaName?: string,
) {
return requestClient.get<TableStructure>(
`/api/core/database_manager/${dbName}/tables/${tableName}/structure`,
{ params: { database, schema_name: schemaName } },
);
}
/**
* 获取表字段
*/
export async function getTableColumnsApi(
dbName: string,
tableName: string,
database?: string,
schemaName?: string,
) {
return requestClient.get<ColumnInfo[]>(
`/api/core/database_manager/${dbName}/tables/${tableName}/columns`,
{ params: { database, schema_name: schemaName } },
);
}
/**
* 获取表索引
*/
export async function getTableIndexesApi(
dbName: string,
tableName: string,
database?: string,
schemaName?: string,
) {
return requestClient.get<IndexInfo[]>(
`/api/core/database_manager/${dbName}/tables/${tableName}/indexes`,
{ params: { database, schema_name: schemaName } },
);
}
/**
* 获取表约束
*/
export async function getTableConstraintsApi(
dbName: string,
tableName: string,
database?: string,
schemaName?: string,
) {
return requestClient.get<ConstraintInfo[]>(
`/api/core/database_manager/${dbName}/tables/${tableName}/constraints`,
{ params: { database, schema_name: schemaName } },
);
}
/**
* 查询表数据
*/
export async function queryTableDataApi(
dbName: string,
params: QueryDataParams,
) {
return requestClient.post<QueryDataResponse>(
`/api/core/database_manager/${dbName}/query`,
params,
);
}
/**
* 执行SQL
*/
export async function executeSQLApi(
dbName: string,
data: ExecuteSQLParams,
): Promise<ExecuteSQLResponse> {
return requestClient.post<ExecuteSQLResponse>(
`/api/core/database_manager/${dbName}/execute`,
data,
);
}
/**
* 获取表DDL语句
*/
export async function getTableDDLApi(
dbName: string,
tableName: string,
database?: string,
schemaName?: string,
): Promise<{ ddl: string }> {
return requestClient.get(
`/api/core/database_manager/${dbName}/tables/${tableName}/ddl`,
{ params: { database, schema_name: schemaName } },
);
}
// ============ 视图管理 ============
/**
* 获取视图列表
*/
export async function getViewsApi(
dbName: string,
database?: string,
schemaName?: string,
): Promise<any[]> {
return requestClient.get(`/api/core/database_manager/${dbName}/views`, {
params: { database, schema_name: schemaName },
});
}
/**
* 获取视图结构
*/
export async function getViewStructureApi(
dbName: string,
viewName: string,
schemaName?: string,
): Promise<any> {
return requestClient.get(
`/api/core/database_manager/${dbName}/views/${viewName}/structure`,
{
params: { schema_name: schemaName },
},
);
}
/**
* 获取视图定义SQL
*/
export async function getViewDefinitionApi(
dbName: string,
viewName: string,
schemaName?: string,
): Promise<{ definition: string }> {
return requestClient.get(
`/api/core/database_manager/${dbName}/views/${viewName}/definition`,
{
params: { schema_name: schemaName },
},
);
}
/**
* 获取视图依赖的表
*/
export async function getViewDependenciesApi(
dbName: string,
viewName: string,
schemaName?: string,
): Promise<string[]> {
return requestClient.get(
`/api/core/database_manager/${dbName}/views/${viewName}/dependencies`,
{
params: { schema_name: schemaName },
},
);
}
/**
* 执行DDL语句
*/
export async function executeDDLApi(
dbName: string,
data: {
database?: string;
schema_name?: string;
sql: string;
},
) {
return requestClient.post<{
affected_rows: number;
message: string;
success: boolean;
}>(`/api/core/database_manager/${dbName}/execute/ddl`, data);
}
+240
View File
@@ -0,0 +1,240 @@
import { requestClient } from '#/api/request';
/**
* 部门相关类型定义
*/
export interface Dept {
id: string;
parent_id?: string;
name: string;
dept_type: string; // 'company' | 'department' | 'team' | 'other'
code?: string;
phone?: string;
email?: string;
status: boolean;
description?: string;
lead_id?: string;
level: number;
path: string;
dept_type_display?: string;
lead_name?: string;
user_count?: number;
child_count?: number;
can_delete?: boolean;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface DeptTreeNode extends Dept {
children?: DeptTreeNode[];
}
export interface DeptCreateInput {
parent_id?: string;
name: string;
dept_type?: string; // 'company' | 'department' | 'team' | 'other'
code?: string;
phone?: string;
email?: string;
status?: boolean;
description?: string;
lead_id?: string;
}
export interface DeptUpdateInput extends Partial<DeptCreateInput> {}
export interface DeptBatchDeleteInput {
ids: string[];
}
export interface DeptMoveInput {
target_parent_id?: string;
position?: number;
}
export interface DeptListParams {
page?: number;
pageSize?: number;
name?: string;
dept_type?: string;
status?: boolean;
parent_id?: string;
}
export interface DeptUser {
id: string;
username: string;
name?: string;
email?: string;
mobile?: string;
user_status?: number;
}
export interface DeptStats {
total_count: number;
active_count: number;
inactive_count: number;
type_counts: Record<string, number>;
max_level: number;
total_users: number;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
/**
* 创建部门
*/
export async function createDeptApi(data: DeptCreateInput) {
return requestClient.post<Dept>('/api/core/dept', data);
}
/**
* 获取部门列表(分页)
*/
export async function getDeptListApi(params?: DeptListParams) {
return requestClient.get<Dept[]>('/api/core/dept', { params });
}
/**
* 获取部门树形结构
*/
export async function getDeptTreeApi() {
return requestClient.get<DeptTreeNode[]>('/api/core/dept/tree');
}
/**
* 获取部门详情
*/
export async function getDeptDetailApi(deptId: string) {
return requestClient.get<Dept>(`/api/core/dept/${deptId}`);
}
/**
* 更新部门
*/
export async function updateDeptApi(deptId: string, data: DeptUpdateInput) {
return requestClient.put<Dept>(`/api/core/dept/${deptId}`, data);
}
/**
* 删除部门
*/
export async function deleteDeptApi(deptId: string) {
return requestClient.delete<Dept>(`/api/core/dept/${deptId}`);
}
/**
* 批量删除部门
*/
export async function batchDeleteDeptApi(data: DeptBatchDeleteInput) {
return requestClient.post<{ count: number }>(
'/api/core/dept/batch_delete',
data,
);
}
/**
* 根据父部门ID获取子部门
*/
export async function getDeptByParentApi(parentId?: string) {
const url = parentId
? `/api/core/dept/by/parent/${parentId}`
: '/api/core/dept/by/parent/null';
return requestClient.get<Dept[]>(url);
}
/**
* 搜索部门
*/
export async function searchDeptApi(keyword: string) {
return requestClient.get<Dept[]>('/api/core/dept/search', {
params: { keyword },
});
}
/**
* 移动部门
*/
export async function moveDeptApi(deptId: string, data: DeptMoveInput) {
return requestClient.post<Dept>(`/api/core/dept/${deptId}/move`, data);
}
/**
* 获取部门路径
*/
export async function getDeptPathApi(deptId: string) {
return requestClient.get<Dept[]>(`/api/core/dept/${deptId}/path`);
}
/**
* 获取部门用户列表
*/
export async function getDeptUsersApi(
deptId: string,
params?: {
include_children?: boolean;
page?: number;
pageSize?: number;
username?: string;
},
) {
return requestClient.get<PaginatedResponse<DeptUser>>(
`/api/core/dept/users/${deptId}`,
{ params },
);
}
/**
* 为部门添加用户
*/
export async function addDeptUsersApi(
deptId: string,
data: { user_ids: string[] },
) {
return requestClient.post<{ count: number }>(
`/api/core/dept/users/${deptId}`,
data,
);
}
/**
* 从部门移除用户
*/
export async function removeDeptUsersApi(
deptId: string,
data: { user_ids: string[] },
) {
return requestClient.delete<{ count: number }>(
`/api/core/dept/users/${deptId}`,
{ data },
);
}
/**
* 获取部门统计信息
*/
export async function getDeptStatsApi() {
return requestClient.get<DeptStats>('/api/core/dept/stats');
}
/**
* 获取简单部门列表(用于选择器)
*/
export async function getSimpleDeptListApi() {
return requestClient.get<Dept[]>('/api/core/dept/simple');
}
/**
* 根据ID列表获取部门
*/
export async function getDeptsByIds(ids: string[]) {
if (!ids || ids.length === 0) return [];
return requestClient.get<DeptTreeNode[]>('/api/core/dept/by/ids', {
params: { ids: ids.join(',') },
});
}
+77
View File
@@ -0,0 +1,77 @@
/**
* 设备管理 API
*/
import { requestClient } from '#/api/request';
export namespace DeviceApi {
/** 设备信息 */
export interface DeviceInfo {
device_id: string;
device_name?: string;
device_type?: string;
browser_type?: string;
os_type?: string;
ip_address?: string;
last_active_time?: string;
is_current: boolean;
is_online: boolean;
}
/** 设备列表响应 */
export interface DeviceListResponse {
current_device?: DeviceInfo;
online_devices: DeviceInfo[];
total_count: number;
}
/** 设备重命名请求 */
export interface DeviceRenameRequest {
device_name: string;
}
/** 统计信息响应 */
export interface StatisticsResponse {
online_count: number;
total_count: number;
}
}
/**
* 获取设备列表
*/
export async function getDeviceListApi() {
return requestClient.get<DeviceApi.DeviceListResponse>('/api/core/devices');
}
/**
* 强制登出指定设备
*/
export async function logoutDeviceApi(deviceId: string) {
return requestClient.delete(`/api/core/devices/${deviceId}`);
}
/**
* 登出其他所有设备
*/
export async function logoutOtherDevicesApi() {
return requestClient.delete('/api/core/devices/logout-others');
}
/**
* 重命名设备
*/
export async function renameDeviceApi(
deviceId: string,
data: DeviceApi.DeviceRenameRequest,
) {
return requestClient.post(`/api/core/devices/${deviceId}/rename`, data);
}
/**
* 获取设备统计信息
*/
export async function getDeviceStatisticsApi() {
return requestClient.get<DeviceApi.StatisticsResponse>(
'/api/core/devices/statistics',
);
}
+187
View File
@@ -0,0 +1,187 @@
import { requestClient } from '#/api/request';
/**
* 字典相关类型定义
*/
export interface Dict {
id: string;
application_id?: string;
application_name?: string;
is_global?: boolean;
name: string;
code: string;
status: boolean;
remark?: string;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface DictCreateInput {
application_id?: string;
is_global?: boolean;
name: string;
code: string;
status?: boolean;
remark?: string;
}
export interface DictUpdateInput extends Partial<DictCreateInput> {}
export interface DictListParams {
page?: number;
pageSize?: number;
applicationId?: string;
name?: string;
code?: string;
status?: boolean;
}
/**
* 字典项相关类型定义
*/
export interface DictItem {
id: string;
dict_id: string;
label?: string;
value?: string;
icon?: string;
sort?: number;
status: boolean;
remark?: string;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface DictItemCreateInput {
dict_id: string;
label?: string;
value?: string;
icon?: string;
sort?: number;
status?: boolean;
remark?: string;
}
export interface DictItemUpdateInput extends Partial<DictItemCreateInput> {}
export interface DictItemListParams {
page?: number;
pageSize?: number;
dict_id?: string;
label?: string;
value?: string;
status?: boolean;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
// ============ 字典API ============
/**
* 创建字典
*/
export async function createDictApi(data: DictCreateInput) {
return requestClient.post<Dict>('/api/core/dict', data);
}
/**
* 获取字典列表(分页)
*/
export async function getDictListApi(params?: DictListParams) {
return requestClient.get<PaginatedResponse<Dict>>('/api/core/dict', {
params,
});
}
/**
* 获取所有字典(不分页)
*/
export async function getAllDictApi(applicationId?: string) {
return requestClient.get<Dict[]>('/api/core/dict/all', {
params: { applicationId },
});
}
/**
* 获取字典详情
*/
export async function getDictDetailApi(dictId: string) {
return requestClient.get<Dict>(`/api/core/dict/${dictId}`);
}
/**
* 更新字典
*/
export async function updateDictApi(dictId: string, data: DictUpdateInput) {
return requestClient.put<Dict>(`/api/core/dict/${dictId}`, data);
}
/**
* 删除字典
*/
export async function deleteDictApi(dictId: string) {
return requestClient.delete<Dict>(`/api/core/dict/${dictId}`);
}
// ============ 字典项API ============
/**
* 创建字典项
*/
export async function createDictItemApi(data: DictItemCreateInput) {
return requestClient.post<DictItem>('/api/core/dict_item', data);
}
/**
* 获取字典项列表(分页)
*/
export async function getDictItemListApi(params?: DictItemListParams) {
return requestClient.get<PaginatedResponse<DictItem>>('/api/core/dict_item', {
params,
});
}
/**
* 获取所有字典项(不分页)
*/
export async function getAllDictItemApi() {
return requestClient.get<DictItem[]>('/api/core/dict_item/get/all');
}
/**
* 根据字典编码获取字典项
*/
export async function getDictItemByCodeApi(code: string) {
return requestClient.get<DictItem[]>(
`/api/core/dict_item/by/dict_code/${code}`,
);
}
/**
* 获取字典项详情
*/
export async function getDictItemDetailApi(dictItemId: string) {
return requestClient.get<DictItem>(`/api/core/dict_item/${dictItemId}`);
}
/**
* 更新字典项
*/
export async function updateDictItemApi(
dictItemId: string,
data: DictItemUpdateInput,
) {
return requestClient.put<DictItem>(`/api/core/dict_item/${dictItemId}`, data);
}
/**
* 删除字典项
*/
export async function deleteDictItemApi(dictItemId: string) {
return requestClient.delete<DictItem>(`/api/core/dict_item/${dictItemId}`);
}
@@ -0,0 +1,118 @@
import { requestClient } from '#/api/request';
export interface FieldPermission {
field_name: string;
permission_type: 'hidden' | 'masked' | 'read' | 'write';
mask_rule?: 'default' | 'email' | 'id_card' | 'name' | 'phone';
}
export interface FieldPermissionConfig extends FieldPermission {
id: string;
role_id: string;
resource_type: string;
sort: number;
is_deleted: boolean;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface FieldPermissionBatchUpdate {
role_id: string;
resource_type: string;
configs: FieldPermission[];
}
export interface ResourceFieldMetadata {
field_name: string;
label: string;
field_type: string;
required?: boolean;
sensitive: boolean;
maskable: boolean;
default_permission: string;
}
export interface ResourceFieldsMetadata {
resource_type: string;
display_name: string;
fields: ResourceFieldMetadata[];
}
/**
* 获取所有资源的字段元数据
* @param applicationId 应用ID,子应用访问时只显示该应用的资源
*/
export async function getAllResourceFieldsApi(applicationId?: string) {
return requestClient.get<ResourceFieldsMetadata[]>(
'/api/core/field-permissions/resource-fields',
{
params: applicationId ? { applicationId } : undefined,
},
);
}
/**
* 获取指定资源的字段元数据
*/
export async function getResourceFieldsApi(resourceType: string) {
return requestClient.get<ResourceFieldsMetadata>(
`/api/core/field-permissions/resource-fields/${resourceType}`,
);
}
/**
* 获取角色的字段权限配置
*/
export async function getRoleFieldPermissionsApi(
roleId: string,
resourceType: string,
) {
return requestClient.get<FieldPermissionConfig[]>(
`/api/core/field-permissions/${roleId}`,
{
params: { resource_type: resourceType },
},
);
}
/**
* 批量更新字段权限配置
*/
export async function batchUpdateFieldPermissionsApi(
data: FieldPermissionBatchUpdate,
) {
return requestClient.post('/api/core/field-permissions/batch', data);
}
/**
* 删除角色的字段权限配置
*/
export async function deleteRoleFieldPermissionsApi(
roleId: string,
resourceType: string,
) {
return requestClient.delete(`/api/core/field-permissions/${roleId}`, {
params: { resource_type: resourceType },
});
}
/**
* 权限类型选项
*/
export const PERMISSION_TYPE_OPTIONS = [
{ label: '可读', value: 'read' },
{ label: '可写', value: 'write' },
{ label: '隐藏', value: 'hidden' },
{ label: '脱敏', value: 'masked' },
];
/**
* 脱敏规则选项
*/
export const MASK_RULE_OPTIONS = [
{ label: '手机号', value: 'phone' },
{ label: '邮箱', value: 'email' },
{ label: '身份证', value: 'id_card' },
{ label: '姓名', value: 'name' },
{ label: '默认', value: 'default' },
];
+507
View File
@@ -0,0 +1,507 @@
/**
* -*- coding: utf-8 -*-
* time: 2024/12/19
* author: 臧成龙
* QQ: 939589097
*/
import { requestClient } from '#/api/request';
export namespace SystemFileManagerApi {
export interface FileItem {
[key: string]: any;
id?: string;
name: string;
path: string;
file_type: 'file' | 'folder'; // 后端返回 file_type
file_size?: number; // 后端返回 file_size
file_ext?: string;
parent_id?: string;
updated_time?: string; // 后端返回 updated_time
has_children?: boolean;
}
export interface FolderTree {
id: string;
name: string;
path: string;
parent_id?: string;
children?: FolderTree[];
}
export interface FileListParams {
path?: string;
parent_id?: null | string;
type?: 'file' | 'folder';
page?: number;
pageSize?: number;
}
export interface FileListResult {
items: FileItem[];
total: number;
}
export interface StorageConfig {
[key: string]: any;
storage_type?: string;
max_file_size?: number;
allowed_extensions?: string[];
}
export interface MoveParams {
source_ids: string[];
target_parent_id: string;
}
export interface RenameParams {
name: string;
}
export interface BatchDeleteParams {
ids: string[];
}
export interface FolderCreateParams {
name: string;
parent_id?: string;
path?: string;
}
export interface CreateAccessTokenParams {
fileId: string;
expiresIn?: number;
}
export interface AccessTokenResponse {
token: string;
expiresAt: string;
fileId: string;
}
export interface AccessTokenUrlResponse {
url: string;
token: string;
expiresAt: string;
}
}
/**
* 获取文件列表
* @param params 查询参数
*/
async function getFileList(params?: SystemFileManagerApi.FileListParams) {
return requestClient.get<SystemFileManagerApi.FileListResult>(
'/api/core/file_manager',
{ params },
);
}
/**
* 获取最近上传的图片
* @param limit 数量,默认20
*/
async function getRecentImages(limit: number = 20) {
return requestClient.get<SystemFileManagerApi.FileItem[]>(
'/api/core/file_manager/recent/images',
{ params: { limit } },
);
}
/**
* 获取最近上传的文件(所有类型)
* @param limit 数量,默认20
*/
async function getRecentFiles(limit: number = 20) {
return requestClient.get<SystemFileManagerApi.FileItem[]>(
'/api/core/file_manager/recent/files',
{ params: { limit } },
);
}
/**
* 上传文件
* @param file 文件对象
* @param options 上传选项
* @param options.parentId 父文件夹ID
* @param options.isPublic 是否公开(公开文件无需认证即可访问)
* @param options.onProgress 上传进度回调
*/
async function uploadFile(
file: File,
options?: {
isPublic?: boolean;
onProgress?: (progressEvent: {
loaded: number;
percentage: number;
total: number;
}) => void;
parentId?: string;
source?: string;
},
) {
const { parentId, isPublic = false, source, onProgress } = options || {};
return requestClient.upload(
'/api/core/file_manager/upload',
{
file,
parent_id: parentId || undefined,
is_public: isPublic,
source: source || undefined,
},
{
onUploadProgress: (progressEvent: any) => {
if (onProgress && progressEvent.total) {
const percentage = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
onProgress({
loaded: progressEvent.loaded,
total: progressEvent.total,
percentage,
});
}
},
},
);
}
/**
* 创建文件夹
* @param data 文件夹数据
*/
async function createFolder(data: SystemFileManagerApi.FolderCreateParams) {
return requestClient.post('/api/core/file_manager/folder', data);
}
/**
* 获取文件夹树
*/
async function getFolderTree() {
return requestClient.get<Array<SystemFileManagerApi.FolderTree>>(
'/api/core/file_manager/tree',
);
}
/**
* 重命名文件/文件夹
* @param id 文件/文件夹ID
* @param data 重命名数据
*/
async function renameItem(id: string, data: SystemFileManagerApi.RenameParams) {
return requestClient.put(`/api/core/file_manager/${id}/rename`, data);
}
/**
* 移动文件/文件夹
* @param data 移动参数
*/
async function moveItems(data: SystemFileManagerApi.MoveParams) {
return requestClient.put('/api/core/file_manager/move', data);
}
/**
* 删除文件/文件夹
* @param id 文件/文件夹ID
*/
async function deleteItem(id: string) {
return requestClient.delete(`/api/core/file_manager/${id}`);
}
/**
* 批量删除文件/文件夹
* @param data 批量删除参数
*/
async function batchDelete(data: SystemFileManagerApi.BatchDeleteParams) {
return requestClient.post('/api/core/file_manager/batch/delete', data);
}
/**
* 获取下载链接
* @param path 文件路径
*/
function getDownloadUrl(path: string): string {
return `/basic-api/api/core/file_manager/file/download?path=${encodeURIComponent(path)}`;
}
/**
* 获取存储配置
*/
async function getStorageConfig() {
return requestClient.get<SystemFileManagerApi.StorageConfig>(
'/api/core/file_manager/storage_config',
);
}
/**
* 更新存储配置
* @param data 存储配置数据
*/
async function updateStorageConfig(data: SystemFileManagerApi.StorageConfig) {
return requestClient.put('/api/core/file_manager/storage_config', data);
}
/**
* 通过文件ID获取文件访问URL
* @param fileId 文件ID
*/
async function getFileUrlById(fileId: string) {
return requestClient.get<{ url: string }>(
`/api/core/file_manager/url/${fileId}`,
);
}
/**
* 批量获取文件访问URL
* @param fileIds 文件ID数组
*/
async function getBatchFileUrls(fileIds: string[]) {
const ids = fileIds.join(',');
return requestClient.get<{ data: Record<string, string> }>(
`/api/core/file_manager/batch/urls`,
{
params: { ids },
},
);
}
/**
* 创建临时访问令牌
* @param params 创建参数
*/
async function createAccessToken(
params: SystemFileManagerApi.CreateAccessTokenParams,
) {
return requestClient.post<SystemFileManagerApi.AccessTokenResponse>(
'/api/core/file_manager/access-token',
params,
);
}
/**
* 获取带临时令牌的文件URL
* @param fileId 文件ID
* @param expiresIn 过期时间(秒),默认1小时
*/
async function getFileUrlWithToken(fileId: string, expiresIn: number = 3600) {
return requestClient.get<SystemFileManagerApi.AccessTokenUrlResponse>(
`/api/core/file_manager/access-token/url/${fileId}`,
{
params: { expiresIn },
},
);
}
/**
* 撤销临时访问令牌
* @param token 令牌
*/
async function revokeAccessToken(token: string) {
return requestClient.delete(`/api/core/file_manager/access-token/${token}`);
}
/**
* 获取文件流式传输URL(用于img src等直接访问)
* 注意:此方法已废弃,建议使用 getFileUrlWithToken 获取带临时令牌的URL
* @param fileId 文件ID
* @deprecated 使用 getFileUrlWithToken 替代
*/
function getFileStreamUrl(fileId: string): string {
return `/basic-api/api/core/file_manager/stream/${fileId}`;
}
/**
* 获取文件流式传输URL(带临时令牌)
* @param fileId 文件ID
* @param token 临时访问令牌
*/
function getFileStreamUrlWithToken(fileId: string, token: string): string {
return `/basic-api/api/core/file_manager/stream/${fileId}?token=${token}`;
}
/**
* 获取文件代理访问URL(用于img src等直接访问)
* @param fileId 文件ID
* @param download 是否下载
*/
function getFileProxyUrl(fileId: string, download = false): string {
const params = download ? '?download=true' : '';
return `/api/core/file_manager/proxy/${fileId}${params}`;
}
/**
* 通过API客户端获取文件流(返回blob数据)
* @param fileId 文件ID
*/
async function getFileStream(fileId: string) {
return requestClient.get(`/api/core/file_manager/stream/${fileId}`, {
responseType: 'blob',
});
}
/**
* 通过id获取文件信息
* @param fileId 文件ID
*/
async function getFileInfo(fileId: string) {
return requestClient.get(`/api/core/file_manager/file_info/${fileId}`);
}
/**
* 通过多个文件ID批量获取文件信息
* @param fileIds 文件ID数组
* @returns 所有文件信息的数组
*/
async function getFilesInfo(fileIds: string[]) {
if (!fileIds || fileIds.length === 0) {
return [];
}
// 使用Promise.all并行请求所有文件信息
const promises = fileIds.map((fileId) => getFileInfo(fileId));
try {
return await Promise.all(promises);
} catch (error) {
console.error('批量获取文件信息失败:', error);
throw error; // 可以根据需要处理错误,这里选择抛出以便上层处理
}
}
/**
* 通过API客户端获取文件代理数据(返回blob数据)
* @param fileId 文件ID
* @param download 是否下载
*/
async function getFileProxy(fileId: string, download = false) {
return requestClient.get(`/api/core/file_manager/proxy/${fileId}`, {
params: download ? { download: true } : undefined,
responseType: 'blob',
});
}
/**
* 获取公开文件的URL(无需认证)
* 公开文件可以直接通过URL访问,不需要临时令牌
* @param fileId 文件ID
*/
function getPublicFileUrl(fileId: string): string {
return `/basic-api/api/core/file_manager/stream/${fileId}`;
}
/**
* 设置文件的公开状态
* @param fileId 文件ID
* @param isPublic 是否公开
*/
async function setFilePublic(fileId: string, isPublic: boolean): Promise<any> {
return requestClient.put(`/api/core/file_manager/${fileId}/public`, null, {
params: { isPublic },
});
}
// ==================== 签名令牌相关 API ====================
export interface SignatureTokenResponse {
token: string;
callback_key: string;
expired_at: string;
}
export interface SignatureStatusResponse {
status: 'completed' | 'expired' | 'not_found' | 'pending';
message: string;
file_id?: string;
}
/**
* 创建签名令牌(用于手机扫码签名)
*/
export async function createSignatureToken(
source: string = 'form',
expireMinutes: number = 30,
): Promise<SignatureTokenResponse> {
const formData = new FormData();
formData.append('source', source);
formData.append('expire_minutes', String(expireMinutes));
return requestClient.post('/api/core/file_manager/signature/token', formData);
}
/**
* 检查签名状态(用于前端轮询)
*/
export async function checkSignatureStatus(
callbackKey: string,
): Promise<SignatureStatusResponse> {
return requestClient.get(
`/api/core/file_manager/signature/status/${callbackKey}`,
);
}
/**
* 获取签名令牌信息(移动端使用,无需登录)
*/
export async function getSignatureTokenInfo(token: string): Promise<{
expired_at: string;
source: string;
token: string;
}> {
return requestClient.get(`/api/core/file_manager/signature/info/${token}`);
}
/**
* 上传签名图片并完成签名(移动端使用,无需登录)
*/
export async function uploadSignatureImage(
token: string,
file: File,
): Promise<{ file_id: string; message: string; success: boolean }> {
return requestClient.upload(
`/api/core/file_manager/signature/upload/${token}`,
{ file },
);
}
/**
* 完成签名(移动端使用)
*/
export async function completeSignature(
token: string,
signatureFileId: string,
): Promise<{ file_id: string; message: string; success: boolean }> {
const formData = new FormData();
formData.append('signature_file_id', signatureFileId);
return requestClient.post(
`/api/core/file_manager/signature/complete/${token}`,
formData,
);
}
export {
batchDelete,
createAccessToken,
createFolder,
deleteItem,
getBatchFileUrls,
getDownloadUrl,
getFileInfo,
getFileList,
getFileProxy,
getFileProxyUrl,
getFilesInfo,
getFileStream,
getFileStreamUrl,
getFileStreamUrlWithToken,
getFileUrlById,
getFileUrlWithToken,
getFolderTree,
getPublicFileUrl,
getRecentFiles,
getRecentImages,
getStorageConfig,
moveItems,
renameItem,
revokeAccessToken,
setFilePublic,
updateStorageConfig,
uploadFile,
};
+23
View File
@@ -0,0 +1,23 @@
export * from './announcement';
export * from './api-token';
export * from './application';
export * from './auth';
export * from './data-source';
export * from './database-connection';
export * from './database-manager';
export * from './dept';
export * from './dict';
export * from './file';
export * from './field-permission';
export * from './menu';
export * from './message';
export * from './org-chart';
export * from './oauth';
export * from './permission';
export * from './post';
export * from './role';
export * from './resource-scope';
export * from './system-config';
export * from './ui-config';
export * from './user';
export * from './websocket';
+307
View File
@@ -0,0 +1,307 @@
import type { PaginatedResponse } from './user';
import { requestClient } from '#/api/request';
/**
* 登录日志相关类型定义
*/
export interface LoginLog {
id: string;
user_id?: string;
username: string;
status: number;
failure_reason?: number;
failure_message?: string;
login_ip: string;
ip_location?: string;
user_agent?: string;
browser_type?: string;
os_type?: string;
device_type?: string;
duration?: number;
session_id?: string;
remark?: string;
status_display?: string;
failure_reason_display?: string;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface LoginLogListParams {
page?: number;
pageSize?: number;
username?: string;
user_id?: string;
status?: number;
failure_reason?: number;
login_ip?: string;
device_type?: string;
browser_type?: string;
os_type?: string;
start_datetime?: string;
end_datetime?: string;
}
export interface LoginLogStats {
total_logins: number;
success_logins: number;
failed_logins: number;
success_rate: number;
unique_users: number;
unique_ips: number;
}
export interface LoginLogIpStats {
login_ip: string;
ip_location?: string;
login_count: number;
failed_count: number;
last_login_time?: string;
}
export interface LoginLogDeviceStats {
device_type?: string;
browser_type?: string;
os_type?: string;
login_count: number;
last_login_time?: string;
}
export interface LoginLogUserStats {
user_id?: string;
username: string;
total_logins: number;
failed_logins: number;
last_login_time?: string;
last_login_ip?: string;
}
export interface LoginLogDailyStats {
date: string;
total_logins: number;
success_logins: number;
failed_logins: number;
unique_users: number;
}
export interface LoginLogBatchDeleteInput {
ids: string[];
}
/**
* 获取登录日志列表(分页)
*/
export async function getLoginLogListApi(params?: LoginLogListParams) {
return requestClient.get<PaginatedResponse<LoginLog>>('/api/core/login-log', {
params,
});
}
/**
* 获取登录日志详情
*/
export async function getLoginLogDetailApi(logId: string) {
return requestClient.get<LoginLog>(`/api/core/login-log/${logId}`);
}
/**
* 删除登录日志
*/
export async function deleteLoginLogApi(logId: string) {
return requestClient.delete(`/api/core/login-log/${logId}`);
}
/**
* 批量删除登录日志
*/
export async function batchDeleteLoginLogApi(ids: string[]) {
return requestClient.delete('/api/core/login-log/batch/delete', {
params: { ids },
});
}
/**
* 获取登录统计概览
*/
export async function getLoginStatsApi(days: number = 30) {
return requestClient.get<LoginLogStats>(
'/api/core/login-log/stats/overview',
{
params: { days },
},
);
}
/**
* 获取IP登录统计
*/
export async function getIpStatsApi(days: number = 30, limit: number = 10) {
return requestClient.get<LoginLogIpStats[]>('/api/core/login-log/stats/ip', {
params: { days, limit },
});
}
/**
* 获取设备登录统计
*/
export async function getDeviceStatsApi(days: number = 30) {
return requestClient.get<LoginLogDeviceStats[]>(
'/api/core/login-log/stats/device',
{
params: { days },
},
);
}
/**
* 获取用户登录统计
*/
export async function getUserStatsApi(days: number = 30, limit: number = 10) {
return requestClient.get<LoginLogUserStats[]>(
'/api/core/login-log/stats/user',
{
params: { days, limit },
},
);
}
/**
* 获取每日登录统计
*/
export async function getDailyStatsApi(days: number = 30) {
return requestClient.get<LoginLogDailyStats[]>(
'/api/core/login-log/stats/daily',
{
params: { days },
},
);
}
/**
* 获取用户的登录日志
*/
export async function getUserLoginLogsApi(
userId: string,
days: number = 30,
page: number = 1,
pageSize: number = 10,
) {
return requestClient.get<PaginatedResponse<LoginLog>>(
`/api/core/login-log/user/${userId}`,
{
params: { days, page, pageSize },
},
);
}
/**
* 获取用户登录次数
*/
export async function getUserLoginCountApi(userId: string, days: number = 30) {
return requestClient.get<{
failed_logins: number;
success_logins: number;
total_logins: number;
user_id: string;
}>(`/api/core/login-log/user/${userId}/count`, {
params: { days },
});
}
/**
* 获取用户最后一次登录
*/
export async function getUserLastLoginApi(userId: string) {
return requestClient.get<LoginLog>(`/api/core/login-log/user/${userId}/last`);
}
/**
* 获取用户登录过的IP地址
*/
export async function getUserLoginIpsApi(userId: string, days: number = 30) {
return requestClient.get<{
ip_count: number;
ips: string[];
user_id: string;
}>(`/api/core/login-log/user/${userId}/ips`, {
params: { days },
});
}
/**
* 获取可疑登录记录
*/
export async function getSuspiciousLoginsApi(
failedThreshold: number = 5,
hours: number = 1,
) {
return requestClient.get<{
records: any[];
suspicious_count: number;
}>('/api/core/login-log/suspicious', {
params: { failed_threshold: failedThreshold, hours },
});
}
/**
* 清理旧的登录日志
*/
export async function cleanOldLogsApi(days: number = 90) {
return requestClient.post<{ deleted_count: number }>(
'/api/core/login-log/clean',
{},
{
params: { days },
},
);
}
/**
* 根据用户名获取登录日志
*/
export async function getLogsByUsernameApi(
username: string,
days: number = 30,
page: number = 1,
pageSize: number = 10,
) {
return requestClient.get<PaginatedResponse<LoginLog>>(
`/api/core/login-log/username/${username}`,
{
params: { days, page, pageSize },
},
);
}
/**
* 根据IP地址获取登录日志
*/
export async function getLogsByIpApi(
loginIp: string,
days: number = 30,
page: number = 1,
pageSize: number = 10,
) {
return requestClient.get<PaginatedResponse<LoginLog>>(
`/api/core/login-log/ip/${loginIp}`,
{
params: { days, page, pageSize },
},
);
}
/**
* 获取用户登录失败次数
*/
export async function getFailedAttemptsApi(
username: string,
hours: number = 1,
) {
return requestClient.get<{
failed_attempts: number;
should_lock: boolean;
username: string;
}>(`/api/core/login-log/failed-attempts/${username}`, {
params: { hours },
});
}
+260
View File
@@ -0,0 +1,260 @@
import type { RouteRecordStringComponent } from '@vben/types';
import { requestClient } from '#/api/request';
/**
* 菜单相关类型定义
*/
export interface Menu {
id: string;
application_id?: string;
is_system?: boolean;
parent_id?: string;
name: string;
title?: string;
authCode?: string;
path: string;
type: string;
component?: string;
redirect?: string;
activePath?: string;
query?: Record<string, any>;
noBasicLayout?: boolean;
icon?: string;
activeIcon?: string;
order: number;
hideInMenu?: boolean;
hideChildrenInMenu?: boolean;
hideInBreadcrumb?: boolean;
hideInTab?: boolean;
affixTab?: boolean;
affixTabOrder?: number;
keepAlive?: boolean;
maxNumOfOpenTab?: number;
fullPathKey?: boolean;
link?: string;
iframeSrc?: string;
openInNewWindow?: boolean;
badge?: string;
badgeType?: string;
badgeVariants?: string;
level?: number;
child_count?: number;
full_path?: string;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface MenuTreeNode extends Menu {
children?: MenuTreeNode[];
}
export interface MenuCreateInput {
application_id?: string;
is_system?: boolean;
parent_id?: string;
name: string;
title?: string;
authCode?: string;
path: string;
type: string;
component?: string;
redirect?: string;
activePath?: string;
query?: Record<string, any>;
noBasicLayout?: boolean;
icon?: string;
activeIcon?: string;
order?: number;
hideInMenu?: boolean;
hideChildrenInMenu?: boolean;
hideInBreadcrumb?: boolean;
hideInTab?: boolean;
affixTab?: boolean;
affixTabOrder?: number;
keepAlive?: boolean;
maxNumOfOpenTab?: number;
fullPathKey?: boolean;
link?: string;
iframeSrc?: string;
openInNewWindow?: boolean;
badge?: string;
badgeType?: string;
badgeVariants?: string;
}
export interface MenuUpdateInput extends Partial<MenuCreateInput> {}
export interface MenuMoveInput {
target_parent_id?: string;
position?: number;
}
export interface MenuListParams {
page?: number;
pageSize?: number;
name?: string;
title?: string;
type?: string;
parent_id?: string;
applicationId?: string;
}
export interface MenuStats {
total_count: number;
type_counts: Record<string, number>;
max_level: number;
type_choices: Array<[string, string]>;
}
/**
* 获取用户所有菜单(路由树)
* @param applicationCode 应用编码,用于过滤应用专属菜单
* @param devMode 开发模式:true只返回系统菜单,false只返回应用菜单
*/
export async function getAllMenusApi(
applicationCode?: string,
devMode?: boolean,
) {
const params: Record<string, any> = {};
if (applicationCode) {
params.application_code = applicationCode;
}
if (devMode !== undefined) {
params.devMode = devMode;
}
return requestClient.get<RouteRecordStringComponent[]>(
'/api/core/menu/route/tree',
{ params },
);
}
/**
* 获取用户路由树(Core 版本)
*/
export async function getUserRouteTreeApi() {
return requestClient.get<MenuTreeNode[]>('/api/core/menu/user_route_tree');
}
/**
* 创建菜单
*/
export async function createMenuApi(data: MenuCreateInput) {
return requestClient.post<Menu>('/api/core/menu', data);
}
/**
* 获取菜单列表(分页)
*/
export async function getMenuListApi(params?: MenuListParams) {
return requestClient.get<Menu[]>('/api/core/menu', { params });
}
/**
* 获取所有菜单(树形结构)
*/
export async function getAllMenuTreeApi(
applicationId?: string,
useCache: boolean = true,
includeSystem: boolean = true,
) {
const params: any = { use_cache: useCache, includeSystem };
if (applicationId) {
params.applicationId = applicationId;
}
return requestClient.get<MenuTreeNode[]>('/api/core/menu/get/tree', {
params,
});
}
/**
* 获取菜单详情
*/
export async function getMenuDetailApi(menuId: string) {
return requestClient.get<Menu>(`/api/core/menu/${menuId}`);
}
/**
* 更新菜单
*/
export async function updateMenuApi(menuId: string, data: MenuUpdateInput) {
return requestClient.put<Menu>(`/api/core/menu/${menuId}`, data);
}
/**
* 删除菜单
*/
export async function deleteMenuApi(menuId: string) {
return requestClient.delete<Menu>(`/api/core/menu/${menuId}`);
}
/**
* 根据父菜单ID获取子菜单
*/
export async function getMenuByParentApi(parentId?: string) {
// 后端使用 "null" 字符串表示根菜单
const id = parentId || 'null';
return requestClient.get<Menu[]>(`/api/core/menu/by/parent/${id}`);
}
/**
* 搜索菜单
*/
export async function searchMenuApi(keyword: string) {
return requestClient.get<Menu[]>('/api/core/menu/search', {
params: { keyword },
});
}
/**
* 移动菜单
*/
export async function moveMenuApi(menuId: string, data: MenuMoveInput) {
return requestClient.post<Menu>(`/api/core/menu/${menuId}/move`, data);
}
/**
* 获取菜单路径
*/
export async function getMenuPathApi(menuId: string) {
return requestClient.get<Menu[]>(`/api/core/menu/${menuId}/path`);
}
/**
* 获取菜单统计信息
*/
export async function getMenuStatsApi() {
return requestClient.get<MenuStats>('/api/core/menu/stats');
}
/**
* 检查菜单名称是否存在
*/
export async function checkMenuNameApi(
name: string,
excludeId?: string,
applicationId?: string,
) {
const params = applicationId ? { applicationId } : {};
return requestClient.post<{ exists: boolean; message?: string }>(
'/api/core/menu/check/name',
{ name, exclude_id: excludeId },
{ params },
);
}
/**
* 检查路由路径是否存在
*/
export async function checkMenuPathApi(
path: string,
excludeId?: string,
applicationId?: string,
) {
const params = applicationId ? { applicationId } : {};
return requestClient.post<{ exists: boolean; message?: string }>(
'/api/core/menu/check/path',
{ path, exclude_id: excludeId },
{ params },
);
}
+102
View File
@@ -0,0 +1,102 @@
/**
* 消息中心 API
*/
import { requestClient } from '#/api/request';
const BASE_URL = '/api/core/message';
// ============ 类型定义 ============
export interface Message {
id: string;
title: string;
content: string;
msg_type: 'announcement' | 'system' | 'todo' | 'workflow';
status: 'read' | 'unread';
link_type: string;
link_id: string;
sender_id?: string;
sender_name?: string;
read_at?: string;
created_at: string;
}
export interface UnreadCount {
total: number;
by_type: Record<string, number>;
}
// ============ API 函数 ============
/**
* 获取消息列表
*/
export async function getMessageListApi(params?: {
msg_type?: string;
page?: number;
pageSize?: number;
status?: string;
}) {
return requestClient.get<{
items: Message[];
total: number;
}>(`${BASE_URL}/list`, { params });
}
/**
* 获取未读消息数量
*/
export async function getUnreadCountApi() {
return requestClient.get<UnreadCount>(`${BASE_URL}/unread-count`);
}
/**
* 获取消息详情
*/
export async function getMessageDetailApi(messageId: string) {
return requestClient.get<Message>(`${BASE_URL}/${messageId}`);
}
/**
* 标记单条消息为已读
*/
export async function markAsReadApi(messageId: string) {
return requestClient.post(`${BASE_URL}/${messageId}/read`);
}
/**
* 标记所有消息为已读
*/
export async function markAllAsReadApi(msgType?: string) {
return requestClient.post(`${BASE_URL}/read-all`, { msg_type: msgType });
}
/**
* 删除消息
*/
export async function deleteMessageApi(messageId: string) {
return requestClient.delete(`${BASE_URL}/${messageId}`);
}
/**
* 清空已读消息
*/
export async function clearReadMessagesApi() {
return requestClient.delete(`${BASE_URL}/clear-read`);
}
/**
* 发送消息
*/
export async function sendMessageApi(data: {
channels?: string[];
content: string;
msg_type?: string;
recipient_ids: string[];
title: string;
}) {
return requestClient.post<{
data: { recipient_count: number; results: Record<string, boolean> };
message: string;
}>(`${BASE_URL}/send`, data);
}
+218
View File
@@ -0,0 +1,218 @@
import { requestClient } from '#/api/request';
export namespace OAuthApi {
/** OAuth 提供商类型 */
export type OAuthProvider =
| 'dingtalk'
| 'feishu'
| 'gitee'
| 'github'
| 'google'
| 'microsoft'
| 'qq'
| 'wechat'
| 'wecom';
/** OAuth 回调参数 */
export interface OAuthCallbackParams {
code: string;
state?: string;
}
/** OAuth 登录返回值 */
export interface OAuthLoginResult {
access_token: string;
refresh_token: string;
expire: number;
user_info: {
avatar?: string;
email?: string;
id: string;
is_superuser: boolean;
name: string;
user_type: number;
username: string;
};
}
/** 授权 URL 返回值 */
export interface AuthorizeUrlResult {
authorize_url: string;
}
// 兼容旧接口
export type GiteeCallbackParams = OAuthCallbackParams;
}
/**
* 获取 OAuth 授权 URL (通用接口)
* @param provider OAuth 提供商 (gitee/github)
* @param state 状态参数
*/
export async function getOAuthAuthorizeUrlApi(
provider: OAuthApi.OAuthProvider,
state?: string,
) {
const params = state ? { state } : {};
return requestClient.get<OAuthApi.AuthorizeUrlResult>(
`/api/core/oauth/${provider}/authorize`,
{ params },
);
}
/**
* OAuth 回调处理 (通用接口)
* @param provider OAuth 提供商 (gitee/github)
* @param data 回调数据
*/
export async function oauthCallbackApi(
provider: OAuthApi.OAuthProvider,
data: OAuthApi.OAuthCallbackParams,
) {
return requestClient.post<OAuthApi.OAuthLoginResult>(
`/api/core/oauth/${provider}/callback`,
data,
);
}
/**
* 获取 Gitee 授权 URL (兼容旧接口)
*/
export async function getGiteeAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('gitee', state);
}
/**
* Gitee OAuth 回调处理 (兼容旧接口)
*/
export async function giteeOAuthCallbackApi(
data: OAuthApi.GiteeCallbackParams,
) {
return oauthCallbackApi('gitee', data);
}
/**
* 获取 GitHub 授权 URL
*/
export async function getGitHubAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('github', state);
}
/**
* GitHub OAuth 回调处理
*/
export async function gitHubOAuthCallbackApi(
data: OAuthApi.OAuthCallbackParams,
) {
return oauthCallbackApi('github', data);
}
/**
* 获取 QQ 授权 URL
*/
export async function getQQAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('qq', state);
}
/**
* QQ OAuth 回调处理
*/
export async function qqOAuthCallbackApi(data: OAuthApi.OAuthCallbackParams) {
return oauthCallbackApi('qq', data);
}
/**
* 获取 Google 授权 URL
*/
export async function getGoogleAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('google', state);
}
/**
* Google OAuth 回调处理
*/
export async function googleOAuthCallbackApi(
data: OAuthApi.OAuthCallbackParams,
) {
return oauthCallbackApi('google', data);
}
/**
* 获取微信授权 URL
*/
export async function getWeChatAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('wechat', state);
}
/**
* 微信 OAuth 回调处理
*/
export async function wechatOAuthCallbackApi(
data: OAuthApi.OAuthCallbackParams,
) {
return oauthCallbackApi('wechat', data);
}
/**
* 获取微软授权 URL
*/
export async function getMicrosoftAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('microsoft', state);
}
/**
* 微软 OAuth 回调处理
*/
export async function microsoftOAuthCallbackApi(
data: OAuthApi.OAuthCallbackParams,
) {
return oauthCallbackApi('microsoft', data);
}
/**
* 获取钉钉授权 URL
*/
export async function getDingTalkAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('dingtalk', state);
}
/**
* 钉钉 OAuth 回调处理
*/
export async function dingtalkOAuthCallbackApi(
data: OAuthApi.OAuthCallbackParams,
) {
return oauthCallbackApi('dingtalk', data);
}
/**
* 获取飞书授权 URL
*/
export async function getFeishuAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('feishu', state);
}
/**
* 飞书 OAuth 回调处理
*/
export async function feishuOAuthCallbackApi(
data: OAuthApi.OAuthCallbackParams,
) {
return oauthCallbackApi('feishu', data);
}
/**
* 获取企业微信授权 URL
*/
export async function getWeComAuthorizeUrlApi(state?: string) {
return getOAuthAuthorizeUrlApi('wecom', state);
}
/**
* 企业微信 OAuth 回调处理
*/
export async function wecomOAuthCallbackApi(
data: OAuthApi.OAuthCallbackParams,
) {
return oauthCallbackApi('wecom', data);
}
@@ -0,0 +1,53 @@
import { requestClient } from '#/api/request';
/**
* 组织架构节点类型
*/
export interface OrgChartNode {
id: string;
name?: string;
username: string;
avatar?: string;
dept_name?: string;
post_name?: string;
subordinate_count: number;
}
/**
* 获取组织架构顶层节点
*/
export async function getOrgChartTopApi() {
return requestClient.get<OrgChartNode[]>('/api/core/user/org-chart/top');
}
/**
* 获取指定用户的组织架构节点
*/
export async function getOrgChartNodeApi(userId: string) {
return requestClient.get<OrgChartNode>(`/api/core/user/org-chart/${userId}`);
}
/**
* 汇报链节点(嵌套结构)
*/
export interface OrgChartChainNode extends OrgChartNode {
children: OrgChartChainNode[];
}
/**
* 获取用户汇报链(从顶层到当前用户的嵌套树)
*/
export async function getOrgChartChainApi(userId: string) {
return requestClient.get<OrgChartChainNode>(
`/api/core/user/org-chart/${userId}/chain`,
);
}
/**
* 获取组织架构子节点
*/
export async function getOrgChartChildrenApi(userId: string) {
return requestClient.get<OrgChartNode[]>(
`/api/core/user/org-chart/${userId}/children`,
);
}
+199
View File
@@ -0,0 +1,199 @@
import { requestClient } from '#/api/request';
/**
* 权限相关类型定义
*/
export interface Permission {
id: string;
menu_id: string;
name: string;
code: string;
permission_type: number;
api_path?: string;
http_method?: number;
description?: string;
is_active: boolean;
permission_type_display?: string;
http_method_display?: string;
menu_name?: string;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface PermissionCreateInput {
menu_id: string;
name: string;
code: string;
permission_type?: number;
api_path?: string;
http_method?: number;
description?: string;
is_active?: boolean;
}
export interface PermissionUpdateInput extends Partial<PermissionCreateInput> {}
export interface PermissionBatchDeleteInput {
ids: string[];
}
export interface PermissionBatchUpdateStatusInput {
ids: string[];
is_active: boolean;
}
export interface PermissionListParams {
page?: number;
pageSize?: number;
name?: string;
code?: string;
permission_type?: number;
is_active?: boolean;
menu_id?: string;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
export interface PermissionStats {
total_count: number;
active_count: number;
inactive_count: number;
type_counts: Record<string, number>;
}
/**
* 创建权限
*/
export async function createPermissionApi(data: PermissionCreateInput) {
return requestClient.post<Permission>('/api/core/permission', data);
}
/**
* 获取权限列表(分页)
*/
export async function getPermissionListApi(params?: PermissionListParams) {
return requestClient.get<PaginatedResponse<Permission>>(
'/api/core/permission',
{ params },
);
}
/**
* 获取权限详情
*/
export async function getPermissionDetailApi(permissionId: string) {
return requestClient.get<Permission>(`/api/core/permission/${permissionId}`);
}
/**
* 更新权限
*/
export async function updatePermissionApi(
permissionId: string,
data: PermissionUpdateInput,
) {
return requestClient.put<Permission>(
`/api/core/permission/${permissionId}`,
data,
);
}
/**
* 删除权限
*/
export async function deletePermissionApi(permissionId: string) {
return requestClient.delete<Permission>(
`/api/core/permission/${permissionId}`,
);
}
/**
* 批量删除权限
*/
export async function batchDeletePermissionApi(
data: PermissionBatchDeleteInput,
) {
return requestClient.post<{ count: number }>(
'/api/core/permission/batch/delete',
data,
);
}
/**
* 批量更新权限状态
*/
export async function batchUpdatePermissionStatusApi(
data: PermissionBatchUpdateStatusInput,
) {
return requestClient.post<{ count: number }>(
'/api/core/permission/batch_update_status',
data,
);
}
/**
* 根据菜单ID获取权限列表
*/
export async function getPermissionsByMenuApi(menuId: string) {
return requestClient.get<Permission[]>(
`/api/core/permission/by_menu/${menuId}`,
);
}
/**
* 获取权限统计信息
*/
export async function getPermissionStatsApi() {
return requestClient.get<PermissionStats>('/api/core/permission/stats');
}
/**
* 检查权限编码是否可用
*/
export async function checkPermissionCodeApi(code: string, menuId: string) {
return requestClient.get<{ available: boolean }>(
'/api/core/permission/check_code',
{
params: { code, menu_id: menuId },
},
);
}
/**
* 获取所有可用的 API 路由
*/
export async function getAllRoutesApi() {
return requestClient.get<any[]>('/api/core/permission/all/routes');
}
/**
* 从路由批量创建权限
*/
export async function batchCreatePermissionsFromRoutesApi(data: {
menu_id: string;
routes: any[];
}) {
return requestClient.post<{
created: number;
errors: string[];
failed: number;
skipped: number;
}>('/api/core/permission/batch/create-from-routes', data);
}
/**
* 从 Router 自动扫描并生成权限
*/
export async function autoScanPermissionsApi(dryRun: boolean = false) {
return requestClient.post<{
created: number;
failed: number;
permissions: any[];
skipped: number;
}>('/api/core/permission/auto/scan', { dry_run: dryRun });
}
+235
View File
@@ -0,0 +1,235 @@
import { requestClient } from '#/api/request';
/**
* 岗位相关类型定义
*/
export interface Post {
id: string;
name: string;
code: string;
post_type: number;
post_level: number;
status: boolean;
description?: string;
dept_id?: string;
post_type_display?: string;
post_level_display?: string;
dept_name?: string;
user_count?: number;
can_delete?: boolean;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface PostCreateInput {
name: string;
code: string;
post_type?: number;
post_level?: number;
status?: boolean;
description?: string;
dept_id?: string;
}
export interface PostUpdateInput extends Partial<PostCreateInput> {}
export interface PostBatchDeleteInput {
ids: string[];
}
export interface PostBatchUpdateStatusInput {
ids: string[];
status: boolean;
}
export interface PostListParams {
page?: number;
pageSize?: number;
name?: string;
code?: string;
post_type?: number;
post_level?: number;
status?: boolean;
dept_id?: string;
}
export interface PostUser {
id: string;
username: string;
name?: string;
email?: string;
mobile?: string;
dept_name?: string;
}
export interface PostStats {
total_count: number;
active_count: number;
inactive_count: number;
type_counts: Record<string, number>;
level_counts: Record<string, number>;
total_users: number;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
/**
* 创建岗位
*/
export async function createPostApi(data: PostCreateInput) {
return requestClient.post<Post>('/api/core/post', data);
}
/**
* 获取岗位列表(分页)
*/
export async function getPostListApi(params?: PostListParams) {
return requestClient.get<PaginatedResponse<Post>>('/api/core/post', {
params,
});
}
/**
* 获取岗位详情
*/
export async function getPostDetailApi(postId: string) {
return requestClient.get<Post>(`/api/core/post/${postId}`);
}
/**
* 更新岗位
*/
export async function updatePostApi(postId: string, data: PostUpdateInput) {
return requestClient.put<Post>(`/api/core/post/${postId}`, data);
}
/**
* 删除岗位
*/
export async function deletePostApi(postId: string) {
return requestClient.delete<Post>(`/api/core/post/${postId}`);
}
/**
* 批量删除岗位
*/
export async function batchDeletePostApi(data: PostBatchDeleteInput) {
return requestClient.post<{ count: number }>(
'/api/core/post/batch_delete',
data,
);
}
/**
* 批量更新岗位状态
*/
export async function batchUpdatePostStatusApi(
data: PostBatchUpdateStatusInput,
) {
return requestClient.post<{ count: number }>(
'/api/core/post/batch_update_status',
data,
);
}
/**
* 根据部门ID获取岗位列表
*/
export async function getPostsByDeptApi(deptId: string) {
return requestClient.get<Post[]>(`/api/core/post/by_dept/${deptId}`);
}
/**
* 获取岗位用户列表
*/
export async function getPostUsersApi(
postId: string,
params?: { page?: number; pageSize?: number; username?: string },
) {
return requestClient.get<PaginatedResponse<PostUser>>(
'/api/core/post/users/by/post_id',
{ params: { ...params, post_id: postId } },
);
}
/**
* 为岗位添加用户
*/
export async function addPostUsersApi(
postId: string,
data: { user_ids: string[] },
) {
return requestClient.post<{ count: number }>(
'/api/core/post/users/by/post_id',
{ ...data, post_id: postId },
);
}
/**
* 从岗位移除用户
*/
export async function removePostUsersApi(
postId: string,
data: { user_ids: string[] },
) {
return requestClient.delete<{ count: number }>(
'/api/core/post/users/by/post_id',
{ data: { ...data, post_id: postId } },
);
}
/**
* 获取岗位统计信息
*/
export async function getPostStatsApi() {
return requestClient.get<PostStats>('/api/core/post/stats');
}
/**
* 导出岗位数据
*/
export async function exportPostApi(params?: PostListParams) {
return requestClient.get<Blob>('/api/core/post/export', {
params,
responseType: 'blob',
});
}
/**
* 导入岗位数据
*/
export async function importPostApi(file: File) {
const formData = new FormData();
formData.append('file', file);
return requestClient.post<{ error_count: number; success_count: number }>(
'/api/core/post/import',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
);
}
/**
* 获取简单岗位列表(用于选择器)
*/
export async function getSimplePostListApi() {
return requestClient.get<Post[]>('/api/core/post/simple');
}
/**
* 根据ID列表获取岗位
*/
export async function getPostsByIds(ids: string[]) {
if (!ids || ids.length === 0) return [];
return requestClient.get<Post[]>('/api/core/post/by/ids', {
params: { ids: ids.join(',') },
});
}
@@ -0,0 +1,129 @@
import { requestClient } from '#/api/request';
/**
* 资源数据权限配置相关类型定义
*/
// 资源类型信息
export interface ResourceType {
resource_type: string;
display_name: string;
model_name?: string;
table_name?: string;
}
// 资源权限配置
export interface ResourceScopeConfig {
id?: string;
role_id: string;
resource_type: string;
data_scope: number;
dept_ids?: null | string[];
sys_create_datetime?: string;
sys_update_datetime?: string;
}
// 角色资源权限配置(用于批量更新)
export interface RoleResourceScopeConfig {
resource_type: string;
data_scope: number;
dept_ids?: null | string[];
}
export interface RoleResourceScopeBatchUpdate {
role_id: string;
configs: RoleResourceScopeConfig[];
}
// 数据权限范围选项
export const DATA_SCOPE_OPTIONS = [
{ label: '全部数据', value: 0 },
{ label: '仅本人数据', value: 1 },
{ label: '本部门数据', value: 2 },
{ label: '本部门及下级部门数据', value: 3 },
{ label: '自定义数据', value: 4 },
];
/**
* 获取所有资源类型
* @param applicationId 应用ID,子应用访问时只显示该应用的资源
*/
export async function getResourceTypesApi(applicationId?: string) {
return requestClient.get<ResourceType[]>('/api/core/resource-scope/types', {
params: applicationId ? { applicationId } : undefined,
});
}
/**
* 获取资源类型列表(简单)
*/
export async function getResourceTypeListApi() {
return requestClient.get<string[]>('/api/core/resource-scope/types/list');
}
/**
* 获取注册表信息
*/
export async function getRegistryInfoApi() {
return requestClient.get<{
data: {
resource_types: string[];
resources: ResourceType[];
total_count: number;
};
message: string;
}>('/api/core/resource-scope/registry/info');
}
/**
* 获取角色的资源权限配置
*/
export async function getRoleResourceScopesApi(roleId: string) {
return requestClient.get<ResourceScopeConfig[]>(
`/api/core/resource-scope/role/${roleId}`,
);
}
/**
* 批量更新角色的资源权限配置
*/
export async function batchUpdateRoleResourceScopesApi(
data: RoleResourceScopeBatchUpdate,
) {
return requestClient.put<ResourceScopeConfig[]>(
'/api/core/resource-scope/role/batch',
data,
);
}
/**
* 创建资源权限配置
*/
export async function createResourceScopeConfigApi(
data: Omit<ResourceScopeConfig, 'id'>,
) {
return requestClient.post<ResourceScopeConfig>(
'/api/core/resource-scope',
data,
);
}
/**
* 更新资源权限配置
*/
export async function updateResourceScopeConfigApi(
configId: string,
data: Partial<ResourceScopeConfig>,
) {
return requestClient.put<ResourceScopeConfig>(
`/api/core/resource-scope/${configId}`,
data,
);
}
/**
* 删除资源权限配置
*/
export async function deleteResourceScopeConfigApi(configId: string) {
return requestClient.delete(`/api/core/resource-scope/${configId}`);
}
+258
View File
@@ -0,0 +1,258 @@
import { requestClient } from '#/api/request';
/**
* 角色相关类型定义
*/
export interface Role {
id: string;
name: string;
code: string;
role_type: number;
status: boolean;
priority: number;
description?: string;
remark?: string;
role_type_display?: string;
user_count?: number;
menu_count?: number;
permission_count?: number;
can_delete?: boolean;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface RoleCreateInput {
name: string;
code: string;
role_type?: number;
status?: boolean;
priority?: number;
description?: string;
remark?: string;
menu?: string[];
permission?: string[];
group?: string[];
}
export interface RoleUpdateInput extends Partial<RoleCreateInput> {}
export interface RoleBatchDeleteInput {
ids: string[];
}
export interface RoleBatchUpdateStatusInput {
ids: string[];
status: boolean;
}
export interface RoleUserInput {
user_ids: string[];
}
export interface RoleListParams {
page?: number;
pageSize?: number;
name?: string;
code?: string;
status?: boolean;
role_type?: number;
id?: string[];
}
export interface RoleUser {
id: string;
username: string;
name?: string;
email?: string;
mobile?: string;
dept_name?: string;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
export interface MenuPermissionTree {
menu_tree: any[];
permission_tree: any[];
selected_menu_ids: string[];
selected_permission_ids: string[];
}
/**
* 创建角色
*/
export async function createRoleApi(data: RoleCreateInput) {
return requestClient.post<Role>('/api/core/role', data);
}
/**
* 获取角色列表(分页)
*/
export async function getRoleListApi(params?: RoleListParams) {
return requestClient.get<PaginatedResponse<Role>>('/api/core/role', {
params,
});
}
/**
* 获取角色详情
*/
export async function getRoleDetailApi(roleId: string) {
return requestClient.get<Role>(`/api/core/role/${roleId}`);
}
/**
* 更新角色
*/
export async function updateRoleApi(roleId: string, data: RoleUpdateInput) {
return requestClient.put<Role>(`/api/core/role/${roleId}`, data);
}
/**
* 删除角色
*/
export async function deleteRoleApi(roleId: string) {
return requestClient.delete<Role>(`/api/core/role/${roleId}`);
}
/**
* 批量删除角色
*/
export async function batchDeleteRoleApi(data: RoleBatchDeleteInput) {
return requestClient.post<{ count: number }>(
'/api/core/role/batch_delete',
data,
);
}
/**
* 批量更新角色状态
*/
export async function batchUpdateRoleStatusApi(
data: RoleBatchUpdateStatusInput,
) {
return requestClient.post<{ count: number }>(
'/api/core/role/batch_update_status',
data,
);
}
/**
* 获取角色用户列表
*/
export async function getRoleUsersApi(
roleId: string,
params?: { page?: number; pageSize?: number; username?: string },
) {
return requestClient.get<PaginatedResponse<RoleUser>>(
'/api/core/role/users/by/role_id',
{ params: { ...params, role_id: roleId } },
);
}
/**
* 为角色添加用户
*/
export async function addRoleUsersApi(roleId: string, data: RoleUserInput) {
return requestClient.post<{ count: number }>(
'/api/core/role/users/by/role_id',
{ ...data, role_id: roleId },
);
}
/**
* 从角色移除用户
*/
export async function removeRoleUsersApi(roleId: string, data: RoleUserInput) {
return requestClient.delete<{ count: number }>(
'/api/core/role/users/by/role_id',
{ data: { ...data, role_id: roleId } },
);
}
/**
* 获取角色的菜单权限树
*/
export async function getRoleMenuPermissionTreeApi(roleId: string) {
return requestClient.get<MenuPermissionTree>(
`/api/core/role/menu-permission-tree/${roleId}`,
);
}
/**
* 获取简单角色列表(用于选择器)
*/
export async function getSimpleRoleListApi() {
return requestClient.get<Role[]>('/api/core/role/all');
}
/**
* 获取角色权限列表(按菜单分组)
*/
export async function getRolePermissionTreeApi(roleId: string) {
return requestClient.get<MenuPermissionTree>(
`/api/core/role/menu-permission-tree/${roleId}`,
);
}
/**
* 更新角色权限
*/
export async function updateRolePermissionsApi(
roleId: string,
data: { permission_ids: string[] },
) {
return requestClient.put(`/api/core/role/${roleId}/permissions`, data);
}
/**
* 更新角色菜单和权限
*/
export async function updateRoleMenusPermissionsApi(
roleId: string,
data: {
menu_ids: string[];
permission_ids: string[];
loaded_menu_ids: string[];
},
) {
return requestClient.put(`/api/core/role/${roleId}/menus-permissions`, data);
}
/**
* 获取角色菜单列表(不包含权限)
* @param roleId 角色ID
* @param applicationId 应用ID,子应用访问时只显示该应用的菜单
*/
export async function getRoleMenusApi(roleId: string, applicationId?: string) {
return requestClient.get<{
menu_tree: any[];
selected_menu_ids: string[];
}>(`/api/core/role/${roleId}/menus`, {
params: applicationId ? { applicationId } : undefined,
});
}
/**
* 获取菜单的权限列表
*/
export async function getMenuPermissionsApi(roleId: string, menuId: string) {
return requestClient.get<{
menu_id: string;
permissions: any[];
}>(`/api/core/role/${roleId}/menu/${menuId}/permissions`);
}
/**
* 根据ID列表获取角色
*/
export async function getRolesByIds(ids: string[]) {
if (!ids || ids.length === 0) return [];
return requestClient.get<Role[]>('/api/core/role/by/ids', {
params: { ids: ids.join(',') },
});
}
@@ -0,0 +1,72 @@
import { requestClient } from '#/api/request';
/**
* 配置分组定义
*/
export interface ConfigGroupDef {
key: string;
fields: string[];
}
/**
* 分组配置更新参数
*/
export interface GroupConfigUpdateInput {
configs: Record<string, null | string>;
}
/**
* 获取所有配置分组定义
*/
export async function getConfigGroupsApi() {
return requestClient.get<ConfigGroupDef[]>('/api/core/system-config/groups');
}
/**
* 获取所有分组配置(敏感字段脱敏)
*/
export async function getAllConfigsApi() {
return requestClient.get<Record<string, Record<string, any>>>(
'/api/core/system-config/all',
);
}
/**
* 获取指定分组配置
*/
export async function getGroupConfigApi(group: string) {
return requestClient.get<Record<string, any>>(
`/api/core/system-config/group/${group}`,
);
}
/**
* 更新指定分组配置
*/
export async function updateGroupConfigApi(
group: string,
data: GroupConfigUpdateInput,
) {
return requestClient.put(`/api/core/system-config/group/${group}`, data);
}
/**
* 删除指定分组配置(恢复默认)
*/
export async function deleteGroupConfigApi(group: string) {
return requestClient.delete(`/api/core/system-config/group/${group}`);
}
/**
* 预热配置缓存
*/
export async function warmupCacheApi() {
return requestClient.post('/api/core/system-config/cache/warmup');
}
/**
* 清除配置缓存
*/
export async function clearCacheApi() {
return requestClient.delete('/api/core/system-config/cache');
}
+208
View File
@@ -0,0 +1,208 @@
import { merge } from '@vben/utils';
import { requestClient } from '#/api/request';
/**
* UI配置相关类型定义
*/
export interface UIConfig {
id: string;
config_key: string;
config_value?: string;
config_type: string;
description?: string;
status: boolean;
sort: number;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface UIConfigCreateInput {
config_key: string;
config_value?: string;
config_type?: string;
description?: string;
status?: boolean;
sort?: number;
}
export interface UIConfigUpdateInput extends Partial<UIConfigCreateInput> {}
export interface UIConfigListParams {
page?: number;
pageSize?: number;
configKey?: string;
configType?: string;
status?: boolean;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
}
export interface LoginConfig {
enableThirdPartyLogin?: boolean;
enabledProviders?: string[];
}
export interface PreferencesConfig {
app?: Record<string, any>;
theme?: Record<string, any>;
logo?: Record<string, any>;
copyright?: Record<string, any>;
sidebar?: Record<string, any>;
header?: Record<string, any>;
footer?: Record<string, any>;
tabbar?: Record<string, any>;
breadcrumb?: Record<string, any>;
navigation?: Record<string, any>;
shortcutKeys?: Record<string, any>;
transition?: Record<string, any>;
widget?: Record<string, any>;
loginConfig?: LoginConfig;
}
/**
* 深度合并偏好配置:updates 中的字段覆盖 current,未涉及的字段保留
* 避免各配置面板分别保存时用浅合并覆盖掉 app.name 等字段
*/
export function mergePreferencesConfig(
current: PreferencesConfig,
updates: PreferencesConfig,
): PreferencesConfig {
return merge({}, updates, current) as PreferencesConfig;
}
// ============ 前端偏好配置API ============
/**
* 获取前端偏好配置(无需认证)
* @param applicationId 应用ID,不传则获取主应用配置
*/
export async function getPreferencesConfigApi(applicationId?: string) {
return requestClient.get<PreferencesConfig>(
'/api/core/ui_config/preferences',
{
params: applicationId ? { applicationId } : undefined,
},
);
}
/**
* 更新前端偏好配置
* @param data 配置数据
* @param applicationId 应用ID,不传则更新主应用配置
*/
export async function updatePreferencesConfigApi(
data: PreferencesConfig,
applicationId?: string,
) {
return requestClient.put<{ id: string }>(
'/api/core/ui_config/preferences',
data,
{
params: applicationId ? { applicationId } : undefined,
},
);
}
// ============ UI配置CRUD API ============
/**
* 创建UI配置
*/
export async function createUIConfigApi(data: UIConfigCreateInput) {
return requestClient.post<UIConfig>('/api/core/ui_config', data);
}
/**
* 获取UI配置列表(分页)
*/
export async function getUIConfigListApi(params?: UIConfigListParams) {
return requestClient.get<PaginatedResponse<UIConfig>>('/api/core/ui_config', {
params,
});
}
/**
* 获取所有UI配置(不分页)
*/
export async function getAllUIConfigApi() {
return requestClient.get<UIConfig[]>('/api/core/ui_config/all');
}
/**
* 获取UI配置详情
*/
export async function getUIConfigDetailApi(configId: string) {
return requestClient.get<UIConfig>(`/api/core/ui_config/${configId}`);
}
/**
* 根据配置键获取UI配置
*/
export async function getUIConfigByKeyApi(configKey: string) {
return requestClient.get<UIConfig>(`/api/core/ui_config/by/key/${configKey}`);
}
/**
* 根据配置类型获取UI配置列表
*/
export async function getUIConfigByTypeApi(configType: string) {
return requestClient.get<UIConfig[]>(
`/api/core/ui_config/by/type/${configType}`,
);
}
/**
* 获取配置值
*/
export async function getUIConfigValueApi(configKey: string) {
return requestClient.get<any>(`/api/core/ui_config/value/${configKey}`);
}
/**
* 更新配置值
*/
export async function updateUIConfigValueApi(
configKey: string,
configValue: string,
) {
return requestClient.put(`/api/core/ui_config/value/${configKey}`, {
config_value: configValue,
});
}
/**
* 更新UI配置
*/
export async function updateUIConfigApi(
configId: string,
data: UIConfigUpdateInput,
) {
return requestClient.put<UIConfig>(`/api/core/ui_config/${configId}`, data);
}
/**
* 删除UI配置
*/
export async function deleteUIConfigApi(configId: string) {
return requestClient.delete(`/api/core/ui_config/${configId}`);
}
/**
* 检查配置键唯一性
*/
export async function checkUIConfigUniqueApi(
field: string,
value: string,
excludeId?: string,
) {
return requestClient.get<{ unique: boolean }>(
'/api/core/ui_config/check/unique',
{
params: { field, value, excludeId },
},
);
}
+247
View File
@@ -0,0 +1,247 @@
import type { UserInfo } from '@vben/types';
import { requestClient } from '#/api/request';
/**
* 用户相关类型定义
*/
export interface User {
id: string;
username: string;
email?: string;
mobile?: string;
avatar?: string; // UUID string
name?: string;
gender?: number;
user_type?: number;
user_status?: number;
birthday?: string;
city?: string;
address?: string;
bio?: string;
dept_id?: string; // UUID string
dept_name?: string;
post_id?: string; // UUID string
post_name?: string;
manager_id?: string; // UUID string
manager_name?: string;
user_type_display?: string;
user_status_display?: string;
gender_display?: string;
role_ids?: string[];
role_names?: string[];
sys_create_datetime?: string;
sys_update_datetime?: string;
is_active: number;
}
export interface UserCreateInput {
username: string;
email?: string;
mobile?: string;
avatar?: string;
name?: string;
gender?: number;
user_type?: number;
user_status?: number;
birthday?: string;
city?: string;
address?: string;
bio?: string;
dept_id?: string;
manager_id?: string;
post?: string[];
core_roles?: string[];
}
export interface UserUpdateInput extends Partial<UserCreateInput> {}
export interface UserPasswordResetInput {
new_password: string;
confirm_password: string;
}
export interface UserChangePasswordInput {
old_password: string;
new_password: string;
confirm_password: string;
}
export interface UserProfileUpdateInput {
name?: string;
email?: string;
mobile?: string;
avatar?: string;
gender?: number;
birthday?: string;
city?: string;
address?: string;
bio?: string;
}
export interface UserBatchDeleteInput {
ids: string[];
}
export interface UserBatchUpdateStatusInput {
ids: string[];
user_status: number;
}
export interface UserPermissionCheckInput {
permission_code: string;
}
export interface UserListParams {
page?: number;
pageSize?: number;
name?: string;
username?: string;
user_status?: number;
user_type?: number;
dept_ids?: string[];
mobile?: string;
email?: string;
last_login_type?: string;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
/**
* 获取用户信息
*/
export async function getUserInfoApi() {
return requestClient.get<UserInfo>('/api/core/userinfo');
}
/**
* 创建用户
*/
export async function createUserApi(data: UserCreateInput) {
return requestClient.post<User>('/api/core/user', data);
}
/**
* 获取用户列表(分页)
*/
export async function getUserListApi(params?: UserListParams) {
return requestClient.get<PaginatedResponse<User>>('/api/core/user', {
params,
});
}
/**
* 获取用户详情
*/
export async function getUserDetailApi(userId: string) {
return requestClient.get<User>(`/api/core/user/${userId}`);
}
/**
* 更新用户
*/
export async function updateUserApi(userId: string, data: UserUpdateInput) {
return requestClient.put<User>(`/api/core/user/${userId}`, data);
}
/**
* 删除用户
*/
export async function deleteUserApi(userId: string) {
return requestClient.delete<User>(`/api/core/user/${userId}`);
}
/**
* 批量删除用户
*/
export async function batchDeleteUserApi(data: UserBatchDeleteInput) {
return requestClient.post<{ count: number }>(
'/api/core/user/batch_delete',
data,
);
}
/**
* 批量更新用户状态
*/
export async function batchUpdateUserStatusApi(
data: UserBatchUpdateStatusInput,
) {
return requestClient.post<{ count: number }>(
'/api/core/user/batch_update_status',
data,
);
}
/**
* 重置用户密码
*/
export async function resetUserPasswordApi(
userId: string,
data: UserPasswordResetInput,
) {
return requestClient.post<User>(
`/api/core/user/${userId}/reset-password`,
data,
);
}
/**
* 更新用户个人信息
*/
export async function updateUserProfileApi(data: UserProfileUpdateInput) {
return requestClient.put<User>('/api/core/user/profile', data);
}
/**
* 检查用户权限
*/
export async function checkUserPermissionApi(data: UserPermissionCheckInput) {
return requestClient.post<{ has_permission: boolean }>(
'/api/core/user/check_permission',
data,
);
}
/**
* 获取用户下属列表
*/
export async function getUserSubordinatesApi(userId: string) {
return requestClient.get<User[]>(`/api/core/user/${userId}/subordinates`);
}
/**
* 获取简单用户列表(用于选择器)
*/
export async function getSimpleUserListApi() {
return requestClient.get<User[]>('/api/core/user/simple');
}
/**
* 获取当前用户个人信息
*/
export async function getCurrentUserProfileApi() {
return requestClient.get<User>('/api/core/userinfo');
}
/**
* 部分更新用户个人信息
*/
export async function patchUserProfileApi(data: UserProfileUpdateInput) {
return requestClient.put<User>('/api/core/user/profile', data);
}
/**
* 修改密码
*/
export async function changePasswordApi(data: UserChangePasswordInput) {
return requestClient.post<{ message: string }>(
'/api/core/user/change-password',
data,
);
}
+644
View File
@@ -0,0 +1,644 @@
/**
* WebSocket API
*/
import { useAccessStore } from '@vben/stores';
import { refreshTokenApi } from './auth';
export namespace WebSocketApi {
/** WebSocket消息类型 */
export interface WebSocketMessage {
type: string;
content?: string;
timestamp?: string;
data?: any;
interval?: number;
[key: string]: any;
}
/** WebSocket回调函数 */
export interface WebSocketCallbacks {
onOpen?: (event: Event) => void;
onMessage?: (message: WebSocketMessage) => void;
onClose?: (event: CloseEvent) => void;
onError?: (event: Event) => void;
onReconnect?: (attempt: number) => void;
}
/** WebSocket连接状态 */
export type ConnectionStatus = 'CLOSED' | 'CLOSING' | 'CONNECTING' | 'OPEN';
/** WebSocket连接配置 */
export interface WebSocketConfig {
url: string;
protocols?: string | string[];
reconnect?: boolean;
maxReconnectAttempts?: number;
reconnectInterval?: number;
heartbeat?: boolean;
heartbeatInterval?: number;
}
/** 监控数据类型 */
export interface MonitorMessage {
type:
| 'get_overview'
| 'get_realtime'
| 'set_interval'
| 'start_monitor'
| 'stop_monitor'
| 'test_connection';
interval?: number;
timestamp?: string;
}
/** 服务器监控数据 */
export interface ServerMonitorData {
basic_info?: any;
cpu_info?: any;
memory_info?: any;
disk_info?: any;
network_info?: any;
process_info?: any;
system_load?: any;
boot_time?: any;
users_info?: any;
timestamp?: string;
}
/** Redis监控数据 */
export interface RedisMonitorData {
connection_id?: string;
connection_name?: string;
status?: string;
info?: any;
memory?: any;
stats?: any;
keyspace?: any[];
clients?: any[];
slow_log?: any[];
timestamp?: string;
}
}
/**
* WebSocket管理类
*/
export class WebSocketManager {
/**
* 是否已连接
*/
get isConnected(): boolean {
return this.status === 'OPEN';
}
/**
* 获取连接状态
*/
get status(): WebSocketApi.ConnectionStatus {
if (!this.ws) return 'CLOSED';
switch (this.ws.readyState) {
case WebSocket.CLOSING: {
return 'CLOSING';
}
case WebSocket.CONNECTING: {
return 'CONNECTING';
}
case WebSocket.OPEN: {
return 'OPEN';
}
default: {
return 'CLOSED';
}
}
}
private callbacks: WebSocketApi.WebSocketCallbacks;
private config: WebSocketApi.WebSocketConfig;
private heartbeatTimer: null | number = null;
private isManualClose = false;
private isTokenExpiredClose = false;
private reconnectAttempts = 0;
private reconnectTimer: null | number = null;
private ws: null | WebSocket = null;
constructor(
config: WebSocketApi.WebSocketConfig,
callbacks: WebSocketApi.WebSocketCallbacks = {},
) {
this.config = {
reconnect: true,
maxReconnectAttempts: 5,
reconnectInterval: 3000,
heartbeat: true,
heartbeatInterval: 30_000,
...config,
};
this.callbacks = callbacks;
}
/**
* 关闭连接
*/
close(code?: number, reason?: string): void {
this.isManualClose = true;
this.stopHeartbeat();
this.clearReconnectTimer();
if (this.ws) {
this.ws.close(code, reason);
this.ws = null;
}
}
/**
* 连接WebSocket
*/
async connect(): Promise<void> {
return new Promise((resolve, reject) => {
try {
this.isManualClose = false;
// 获取访问令牌
const accessStore = useAccessStore();
const token = accessStore.accessToken;
if (!token) {
reject(new Error('未找到访问令牌'));
return;
}
// 构建带token的URL
const separator = this.config.url.includes('?') ? '&' : '?';
const wsUrl = `${this.config.url}${separator}token=${encodeURIComponent(token)}`;
console.log(
'Connecting to WebSocket:',
wsUrl.replace(/token=[^&]+/, 'token=***'),
);
console.log('WebSocket URL详情:', {
originalUrl: this.config.url,
finalUrl: wsUrl.replace(/token=[^&]+/, 'token=***'),
isDev: import.meta.env.DEV,
});
this.ws = new WebSocket(wsUrl, this.config.protocols);
this.ws.addEventListener('open', (event) => {
console.log('WebSocket连接已建立');
this.reconnectAttempts = 0;
this.startHeartbeat();
this.callbacks.onOpen?.(event);
resolve();
});
this.ws.onmessage = (event) => {
try {
const message: WebSocketApi.WebSocketMessage = JSON.parse(
event.data,
);
// 处理心跳响应
if (message.type === 'pong') {
return;
}
// 处理token过期通知:刷新token后自动重连
if (message.type === 'token_expired') {
console.warn(
'[WebSocket] 收到token_expired,准备刷新token并重连',
);
this.isTokenExpiredClose = true;
return;
}
this.callbacks.onMessage?.(message);
} catch (error) {
console.error('解析WebSocket消息失败:', error);
}
};
this.ws.addEventListener('close', (event) => {
console.log('WebSocket连接已关闭', event.code, event.reason);
this.stopHeartbeat();
this.callbacks.onClose?.(event);
// token过期关闭:刷新token后立即重连(不计入重连次数)
if (this.isTokenExpiredClose || event.code === 4002) {
this.isTokenExpiredClose = false;
this.handleTokenExpiredReconnect();
return;
}
// 如果不是手动关闭且启用了重连,则尝试重连
if (!this.isManualClose && this.config.reconnect) {
this.handleReconnect();
}
});
this.ws.onerror = (event) => {
console.error('WebSocket连接错误', event);
this.callbacks.onError?.(event);
reject(new Error('WebSocket连接失败'));
};
} catch (error) {
reject(error);
}
});
}
/**
* 发送消息
*/
send(message: WebSocketApi.WebSocketMessage): boolean {
if (!this.isConnected) {
console.warn('WebSocket未连接,无法发送消息');
return false;
}
try {
this.ws!.send(JSON.stringify(message));
return true;
} catch (error) {
console.error('发送WebSocket消息失败:', error);
return false;
}
}
/**
* 清除重连定时器
*/
private clearReconnectTimer(): void {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
}
/**
* 确保accessToken有效,如果无效则用refreshToken刷新
* 用于普通重连场景
*/
private async ensureFreshToken(): Promise<void> {
const accessStore = useAccessStore();
// 如果没有refreshToken,无法刷新
if (!accessStore.refreshToken) {
throw new Error('No refresh token available');
}
// 如果已有accessToken,先尝试使用(可能还没过期)
if (accessStore.accessToken) {
return;
}
// accessToken为空,需要刷新
await this.forceRefreshToken();
}
/**
* 强制刷新token,用于后端明确通知token_expired的场景
*/
private async forceRefreshToken(): Promise<void> {
const accessStore = useAccessStore();
if (!accessStore.refreshToken) {
throw new Error('No refresh token available');
}
console.log('[WebSocket] 使用refreshToken刷新accessToken...');
const resp = await refreshTokenApi(accessStore.refreshToken);
const newToken = resp.data?.accessToken || '';
if (!newToken) {
throw new Error('Token refresh returned empty token');
}
accessStore.setAccessToken(newToken);
if (typeof resp.data === 'object' && resp.data?.refreshToken) {
accessStore.setRefreshToken(resp.data.refreshToken);
}
}
/**
* 处理重连
*/
private handleReconnect(): void {
if (
this.reconnectAttempts >= (this.config.maxReconnectAttempts || 5) ||
this.isManualClose
) {
console.log('WebSocket重连次数已达上限或手动关闭');
return;
}
this.reconnectAttempts++;
console.log(
`WebSocket重连中... (${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`,
);
this.callbacks.onReconnect?.(this.reconnectAttempts);
this.reconnectTimer = window.setTimeout(async () => {
try {
// 重连前确保token有效
await this.ensureFreshToken();
this.ws = null;
await this.connect();
} catch (error) {
console.error('WebSocket重连失败:', error);
}
}, this.config.reconnectInterval || 3000);
}
/**
* 处理token过期后的重连:先刷新token再重连,不计入重连次数
*/
private handleTokenExpiredReconnect(): void {
if (this.isManualClose) return;
console.log('[WebSocket] Token过期,尝试刷新token后重连...');
this.forceRefreshToken()
.then(() => {
console.log('[WebSocket] Token刷新成功,重新连接...');
this.ws = null;
return this.connect();
})
.catch((error) => {
console.error('[WebSocket] Token刷新失败,无法重连:', error);
});
}
/**
* 开始心跳检测
*/
private startHeartbeat(): void {
if (!this.config.heartbeat) return;
this.heartbeatTimer = window.setInterval(() => {
if (this.isConnected) {
this.send({
type: 'ping',
timestamp: new Date().toISOString(),
});
}
}, this.config.heartbeatInterval || 30_000);
}
/**
* 停止心跳检测
*/
private stopHeartbeat(): void {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
}
/**
* 创建WebSocket连接
*/
export function createWebSocket(
endpoint: string,
callbacks?: WebSocketApi.WebSocketCallbacks,
): WebSocketManager {
// 构建WebSocket URL
let wsUrl: string;
if (import.meta.env.DEV) {
// 开发环境:直接连接到后端服务器
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
wsUrl = `${wsProtocol}//localhost:8000${endpoint}`;
} else {
// 生产环境:使用当前域名
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsHost = location.host;
wsUrl = `${wsProtocol}//${wsHost}${endpoint}`;
}
const config: WebSocketApi.WebSocketConfig = {
url: wsUrl,
reconnect: true,
maxReconnectAttempts: 5,
reconnectInterval: 3000,
heartbeat: true,
heartbeatInterval: 30_000,
};
return new WebSocketManager(config, callbacks);
}
/**
* 创建测试WebSocket连接
*/
export function createTestWebSocket(
callbacks?: WebSocketApi.WebSocketCallbacks,
): WebSocketManager {
return createWebSocket('/ws/test/', callbacks);
}
/**
* 创建通知WebSocket连接
*/
export function createNotificationWebSocket(
callbacks?: WebSocketApi.WebSocketCallbacks,
): WebSocketManager {
return createWebSocket('/ws/notifications/', callbacks);
}
/**
* 创建服务器监控WebSocket连接
*/
export function createServerMonitorWebSocket(
callbacks?: WebSocketApi.WebSocketCallbacks,
): WebSocketManager {
return createWebSocket('/ws/server-monitor/', callbacks);
}
/**
* 创建Redis监控WebSocket连接
*/
export function createRedisMonitorWebSocket(
callbacks?: WebSocketApi.WebSocketCallbacks,
): WebSocketManager {
return createWebSocket('/ws/redis-monitor/', callbacks);
}
/**
* 创建数据库监控WebSocket连接
*/
export function createDatabaseMonitorWebSocket(
callbacks?: WebSocketApi.WebSocketCallbacks,
): WebSocketManager {
return createWebSocket('/ws/database-monitor/', callbacks);
}
/**
* 监控WebSocket管理器类
*/
export class MonitorWebSocketManager extends WebSocketManager {
/**
* 获取概览信息
*/
getOverview(): boolean {
return this.send({
type: 'get_overview',
timestamp: new Date().toISOString(),
});
}
/**
* 获取实时统计
*/
getRealtime(): boolean {
return this.send({
type: 'get_realtime',
timestamp: new Date().toISOString(),
});
}
/**
* 开始监控
*/
startMonitoring(): boolean {
return this.send({
type: 'start_monitor',
timestamp: new Date().toISOString(),
});
}
/**
* 停止监控
*/
stopMonitoring(): boolean {
return this.send({
type: 'stop_monitor',
timestamp: new Date().toISOString(),
});
}
/**
* 测试连接(适用于Redis监控)
*/
testConnection(): boolean {
return this.send({
type: 'test_connection',
timestamp: new Date().toISOString(),
});
}
}
/**
* 创建服务器监控WebSocket管理器
*/
export function createServerMonitorManager(
callbacks?: WebSocketApi.WebSocketCallbacks,
): MonitorWebSocketManager {
const wsManager = createServerMonitorWebSocket(callbacks);
// 创建增强版管理器
return Object.setPrototypeOf(wsManager, MonitorWebSocketManager.prototype);
}
/**
* 创建Redis监控WebSocket管理器
*/
export function createRedisMonitorManager(
callbacks?: WebSocketApi.WebSocketCallbacks,
): MonitorWebSocketManager {
const wsManager = createRedisMonitorWebSocket(callbacks);
// 创建增强版管理器
return Object.setPrototypeOf(wsManager, MonitorWebSocketManager.prototype);
}
/**
* 数据库监控WebSocket管理器类
*/
export class DatabaseMonitorWebSocketManager extends WebSocketManager {
/**
* 获取数据库配置列表
*/
getConfigs(): boolean {
return this.send({
type: 'get_configs',
timestamp: new Date().toISOString(),
});
}
/**
* 获取概览信息
*/
getOverview(dbName: string): boolean {
return this.send({
type: 'get_overview',
db_name: dbName,
timestamp: new Date().toISOString(),
});
}
/**
* 获取实时统计
*/
getRealtime(dbName: string): boolean {
return this.send({
type: 'get_realtime',
db_name: dbName,
timestamp: new Date().toISOString(),
});
}
/**
* 开始监控
*/
startMonitoring(dbName: string): boolean {
return this.send({
type: 'start_monitor',
db_name: dbName,
timestamp: new Date().toISOString(),
});
}
/**
* 停止监控
*/
stopMonitoring(): boolean {
return this.send({
type: 'stop_monitor',
timestamp: new Date().toISOString(),
});
}
/**
* 测试连接
*/
testConnection(dbName: string): boolean {
return this.send({
type: 'test_connection',
db_name: dbName,
timestamp: new Date().toISOString(),
});
}
}
/**
* 创建聊天WebSocket连接
*/
export function createChatWebSocket(
callbacks?: WebSocketApi.WebSocketCallbacks,
): WebSocketManager {
return createWebSocket('/ws/chat/', callbacks);
}
/**
* 创建数据库监控WebSocket管理器
*/
export function createDatabaseMonitorManager(
callbacks?: WebSocketApi.WebSocketCallbacks,
): DatabaseMonitorWebSocketManager {
const wsManager = createDatabaseMonitorWebSocket(callbacks);
// 创建增强版管理器
return Object.setPrototypeOf(
wsManager,
DatabaseMonitorWebSocketManager.prototype,
);
}
+1
View File
@@ -0,0 +1 @@
export * from './core';
+257
View File
@@ -0,0 +1,257 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
defaultResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { ElMessage } from 'element-plus';
import { useAuthStore } from '#/store';
import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
/** 解析 API 错误体中的用户可读文案(兼容 FastAPI 字符串 detail 与结构化 detail */
function resolveApiErrorMessage(
responseData: Record<string, unknown>,
fallback: string,
): string {
const detail = responseData?.detail;
if (typeof detail === 'string' && detail.trim()) {
return detail;
}
if (detail && typeof detail === 'object' && !Array.isArray(detail)) {
const nested = (detail as Record<string, unknown>).message;
if (typeof nested === 'string' && nested.trim()) {
return nested;
}
}
for (const key of ['message', 'error', 'msg'] as const) {
const val = responseData[key];
if (typeof val === 'string' && val.trim()) {
return val;
}
}
return fallback;
}
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({
...options,
baseURL,
paramsSerializer: (params) => {
const searchParams = new URLSearchParams();
for (const key in params) {
const value = params[key];
if (value === undefined || value === null) {
continue;
}
if (Array.isArray(value)) {
value.forEach((v) => searchParams.append(key, v));
} else {
searchParams.append(key, value);
}
}
return searchParams.toString();
},
});
/**
* 重新认证逻辑
*/
async function doReAuthenticate() {
console.warn(
'[认证] Access token or refresh token is invalid or expired. ',
);
const accessStore = useAccessStore();
const authStore = useAuthStore();
// 检查是否有 token,如果没有说明是登录接口返回的 401(账号密码错误)
// 这种情况不需要显示"登录已失效"的消息,也不需要跳转
const hasToken = accessStore.accessToken || accessStore.refreshToken;
accessStore.setAccessToken(null);
accessStore.setRefreshToken(null);
// 只有在有 token 的情况下才显示提示消息(说明是 token 失效)
if (hasToken) {
ElMessage.warning('登录已失效,请重新登录');
if (
preferences.app.loginExpiredMode === 'modal' &&
accessStore.isAccessChecked
) {
accessStore.setLoginExpired(true);
} else {
// 被强制登出时,不需要再调用后端 logout 接口(会 401
await authStore.logout(true, false);
}
}
}
/**
* 刷新token逻辑
*/
// 刷新Token的Promise缓存,用于请求去重
let refreshPromise: null | Promise<string> = null;
async function doRefreshToken() {
// 如果已经有正在进行的刷新请求,直接返回该Promise
// 这样多个并发请求会共享同一个刷新操作,避免重复刷新
if (refreshPromise) {
console.log('[Token刷新] 已有刷新请求进行中,等待...');
return refreshPromise;
}
console.log('[Token刷新] 开始刷新token...');
// 创建新的刷新Promise
refreshPromise = (async () => {
try {
const accessStore = useAccessStore();
console.log(
'[Token刷新] 当前refreshToken:',
accessStore.refreshToken ? '存在' : '不存在',
);
// 检查 refreshToken 是否存在
if (!accessStore.refreshToken) {
console.error('[Token刷新] Refresh token is missing');
await doReAuthenticate();
throw new Error($t('ui.fallback.http.unauthorized'));
}
// 传递 refreshToken 给 API
const resp = await refreshTokenApi(accessStore.refreshToken);
// 处理响应中的新 token
// 后端支持两种格式:直接返回 token 字符串 或 { token, accessToken } 对象
const newToken = resp.data?.accessToken || '';
// 更新 access token
accessStore.setAccessToken(newToken);
// 如果响应中有新的 refresh token,也保存
if (typeof resp.data === 'object' && resp.data?.refreshToken) {
accessStore.setRefreshToken(resp.data.refreshToken);
}
return newToken;
} finally {
// 无论成功或失败,都清空Promise缓存,允许下次刷新
refreshPromise = null;
}
})();
return refreshPromise;
}
function formatToken(token: null | string) {
return token ? `Bearer ${token}` : null;
}
// 请求头处理
client.addRequestInterceptor({
fulfilled: async (config) => {
const accessStore = useAccessStore();
config.headers.Authorization = formatToken(accessStore.accessToken);
config.headers['Accept-Language'] = preferences.app.locale;
return config;
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({
codeField: 'code',
dataField: 'data',
successCode: 0,
}),
);
// SSE 模块 401 时的 token 刷新
client.refreshToken = async () => {
try {
return await doRefreshToken();
} catch {
await doReAuthenticate();
throw new Error('Token refresh failed');
}
};
// token过期的处理
// 注意:enableRefreshToken 需要动态获取,因为 preferences 可能在运行时更新
client.addResponseInterceptor(
authenticateResponseInterceptor({
client,
doReAuthenticate,
doRefreshToken,
enableRefreshToken: true, // 始终启用 token 刷新
formatToken,
}),
);
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string, error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
const responseData = error?.response?.data ?? {};
const status = error?.response?.status;
// 401 错误由 authenticateResponseInterceptor 处理,这里跳过
// 避免在 token 刷新过程中显示错误消息
if (status === 401) {
return;
}
// 优先级顺序:后端自定义消息 > 传入的消息 > 状态码默认消息
let errorMessage = resolveApiErrorMessage(
responseData as Record<string, unknown>,
msg,
);
// 特殊处理429限流
if (status === 429) {
errorMessage =
errorMessage || '请求过于频繁,请稍后再试(5分钟内不能重试)';
}
// 特殊处理403禁止访问
if (status === 403) {
errorMessage = errorMessage || '您没有权限访问此资源';
}
// 打印完整错误信息便于调试
console.error('[API Error]', {
status,
message: errorMessage,
data: responseData,
});
// 显示错误消息
if (errorMessage) {
ElMessage.error(errorMessage || msg);
}
}),
);
return client;
}
export const requestClient = createRequestClient(apiURL, {
responseReturn: 'body',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
+675
View File
@@ -0,0 +1,675 @@
import { requestClient } from '#/api/request';
const BASE = '/api/smart-table';
// ==================== Types ====================
export interface SmartTableItem {
id: string;
name: string;
icon: string;
type?: string;
parent_id?: string | null;
sort?: number;
description?: string;
active_view_id?: string;
sys_create_datetime?: string;
sys_update_datetime?: string;
}
export interface SmartFieldItem {
id: string;
table_id: string;
name: string;
type: string;
width: number;
visible: boolean;
required: boolean;
description?: string;
config: Record<string, any>;
sort: number;
sys_create_datetime?: string;
}
export interface SmartRecordItem {
id: string;
table_id: string;
values: Record<string, any>;
sys_create_datetime?: string;
sys_update_datetime?: string;
sys_creator_id?: string;
sys_modifier_id?: string;
}
export interface SmartViewItem {
id: string;
table_id: string;
name: string;
type: string;
config: Record<string, any>;
sort: number;
sys_create_datetime?: string;
}
export interface SmartTableFull extends SmartTableItem {
type?: string;
content?: any;
fields: SmartFieldItem[];
records: SmartRecordItem[];
views: SmartViewItem[];
record_total: number;
next_cursor: string | null;
has_more: boolean;
sys_creator_id?: string;
creator_name?: string;
creator_avatar?: string;
}
export interface CursorPaginatedRecords {
items: SmartRecordItem[];
total: number;
next_cursor: string | null;
has_more: boolean;
}
export interface RecordFilterParam {
field_id: string;
operator: string;
value?: any;
}
export interface RecordSortParam {
field_id: string;
direction: 'asc' | 'desc';
}
export interface RecordQueryParam {
filters?: RecordFilterParam[];
filter_logic?: 'and' | 'or';
sorts?: RecordSortParam[];
search?: string;
search_field_ids?: string[];
group_field_id?: string;
cursor?: string | null;
limit?: number;
}
export interface RecordGroupItem {
key: string;
label: string;
records: SmartRecordItem[];
}
export interface GroupedRecordsResponse {
groups: RecordGroupItem[];
total: number;
}
// ==================== Table API ====================
export function getTableListApi(wikiSpaceId?: string | null) {
const params: Record<string, any> = {};
if (wikiSpaceId) params.wiki_space_id = wikiSpaceId;
return requestClient.get<SmartTableItem[]>(`${BASE}/tables`, { params });
}
export function getTableFullApi(
tableId: string,
opts?: { filters?: RecordFilterParam[]; sorts?: RecordSortParam[]; search?: string; filter_logic?: string },
) {
const params: Record<string, any> = {};
if (opts?.filters?.length) params.filters = JSON.stringify(opts.filters);
if (opts?.sorts?.length) params.sorts = JSON.stringify(opts.sorts);
if (opts?.search) params.search = opts.search;
if (opts?.filter_logic) params.filter_logic = opts.filter_logic;
return requestClient.get<SmartTableFull>(`${BASE}/tables/${tableId}/full`, { params });
}
export function createTableApi(data: { name: string; icon?: string; description?: string; type?: string; content?: any; parent_id?: string | null; wiki_space_id?: string | null }) {
return requestClient.post<SmartTableItem>(`${BASE}/tables`, data);
}
export function updateTableApi(tableId: string, data: Partial<SmartTableItem>) {
return requestClient.put<SmartTableItem>(`${BASE}/tables/${tableId}`, data);
}
export function updateDocumentContentApi(tableId: string, content: any) {
return requestClient.patch(`${BASE}/tables/${tableId}/content`, { content });
}
export function exportDocumentPdfApi(tableId: string, html: string, title: string) {
return requestClient.post<Blob>(`${BASE}/tables/${tableId}/export-pdf`, { html, title }, {
responseType: 'blob',
});
}
export function deleteTableApi(tableId: string) {
return requestClient.delete(`${BASE}/tables/${tableId}`);
}
export function moveTableApi(tableId: string, parentId: string | null, afterId?: string | null) {
return requestClient.put<SmartTableItem>(`${BASE}/tables/${tableId}/move`, {
parent_id: parentId,
after_id: afterId ?? null,
});
}
// ==================== Field API ====================
export function getFieldListApi(tableId: string) {
return requestClient.get<SmartFieldItem[]>(`${BASE}/tables/${tableId}/fields`);
}
export function createFieldApi(tableId: string, data: Omit<SmartFieldItem, 'id' | 'table_id' | 'sys_create_datetime'>) {
return requestClient.post<SmartFieldItem>(`${BASE}/tables/${tableId}/fields`, data);
}
export function updateFieldApi(fieldId: string, data: Partial<SmartFieldItem>) {
return requestClient.put<SmartFieldItem>(`${BASE}/fields/${fieldId}`, data);
}
export function deleteFieldApi(fieldId: string) {
return requestClient.delete(`${BASE}/fields/${fieldId}`);
}
export function reorderFieldsApi(tableId: string, fieldIds: string[]) {
return requestClient.put(`${BASE}/tables/${tableId}/fields/reorder`, { field_ids: fieldIds });
}
// ==================== Record API ====================
export function getRecordListApi(
tableId: string,
cursor?: string | null,
limit = 200,
opts?: { filters?: RecordFilterParam[]; sorts?: RecordSortParam[]; search?: string; filter_logic?: string },
) {
const params: Record<string, any> = { cursor: cursor ?? undefined, limit };
if (opts?.filters?.length) params.filters = JSON.stringify(opts.filters);
if (opts?.sorts?.length) params.sorts = JSON.stringify(opts.sorts);
if (opts?.search) params.search = opts.search;
if (opts?.filter_logic) params.filter_logic = opts.filter_logic;
return requestClient.get<CursorPaginatedRecords>(
`${BASE}/tables/${tableId}/records`,
{ params },
);
}
export function queryRecordsApi(tableId: string, query: RecordQueryParam) {
return requestClient.post<CursorPaginatedRecords | GroupedRecordsResponse>(
`${BASE}/tables/${tableId}/records/query`,
query,
);
}
export function reorderRecordsApi(tableId: string, recordIds: string[]) {
return requestClient.put(`${BASE}/tables/${tableId}/records/reorder`, { record_ids: recordIds });
}
export function createRecordApi(tableId: string, values: Record<string, any> = {}) {
return requestClient.post<SmartRecordItem>(`${BASE}/tables/${tableId}/records`, { table_id: tableId, values });
}
export function updateRecordApi(recordId: string, values: Record<string, any>) {
return requestClient.put<SmartRecordItem>(`${BASE}/records/${recordId}`, { values });
}
export function updateCellApi(recordId: string, fieldId: string, value: any) {
return requestClient.patch<SmartRecordItem>(`${BASE}/records/${recordId}/cells`, {
field_id: fieldId,
value,
});
}
export function batchUpdateCellsApi(recordId: string, cells: Record<string, any>) {
return requestClient.patch<SmartRecordItem>(`${BASE}/records/${recordId}/cells/batch`, { cells });
}
export function batchUpdateMultiRecordCellsApi(
tableId: string,
updates: Array<{ record_id: string; cells: Record<string, any> }>,
) {
return requestClient.patch(`${BASE}/tables/${tableId}/records/batch-cells`, { updates });
}
export function deleteRecordApi(recordId: string) {
return requestClient.delete(`${BASE}/records/${recordId}`);
}
export function batchDeleteRecordsApi(tableId: string, ids: string[]) {
return requestClient.post(`${BASE}/tables/${tableId}/records/batch-delete`, { ids });
}
// ==================== Trash / Recycle Bin ====================
export interface TrashRecordItem {
id: string;
table_id: string;
values: Record<string, any>;
sys_create_datetime?: string;
sys_update_datetime?: string;
sys_creator_id?: string;
}
export function getTrashRecordsApi(tableId: string, page = 1, pageSize = 50) {
return requestClient.get<{ items: TrashRecordItem[]; total: number }>(
`${BASE}/tables/${tableId}/trash`,
{ params: { page, page_size: pageSize } },
);
}
export function restoreTrashRecordsApi(tableId: string, ids: string[]) {
return requestClient.post(`${BASE}/tables/${tableId}/trash/restore`, { ids });
}
export function permanentDeleteTrashRecordApi(tableId: string, recordId: string) {
return requestClient.delete(`${BASE}/tables/${tableId}/trash/${recordId}`);
}
export function emptyTrashApi(tableId: string) {
return requestClient.delete(`${BASE}/tables/${tableId}/trash`);
}
export function exportTableApi(tableId: string, format: 'csv' | 'xlsx' = 'csv') {
return requestClient.get(`${BASE}/tables/${tableId}/export`, {
params: { format },
responseType: 'blob',
});
}
export function importTableApi(tableId: string, file: File) {
const formData = new FormData();
formData.append('file', file);
return requestClient.post(`${BASE}/tables/${tableId}/import`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
}
export interface RecordSearchResultItem {
id: string;
title: string;
}
export function searchRecordsApi(tableId: string, keyword: string = '', limit: number = 20) {
return requestClient.post<RecordSearchResultItem[]>(
`${BASE}/tables/${tableId}/records/search`,
{ keyword, limit },
);
}
// ==================== Summary API ====================
export interface SummaryResult {
summaries: Record<string, any>;
total_count: number;
}
export function getSummaryApi(
tableId: string,
data: {
aggregations: Record<string, string>;
filters?: RecordFilterParam[];
filter_logic?: string;
search?: string;
},
) {
return requestClient.post<SummaryResult>(`${BASE}/tables/${tableId}/summary`, data);
}
// ==================== Comment API ====================
export interface CommentItem {
id: string;
record_id: string;
user_id: string;
content: string;
mentions: string[];
parent_id: string | null;
sys_create_datetime?: string;
sys_update_datetime?: string;
user_name?: string;
user_avatar?: string;
replies: CommentItem[];
}
export function getCommentsApi(recordId: string) {
return requestClient.get<CommentItem[]>(`${BASE}/records/${recordId}/comments`);
}
export function createCommentApi(recordId: string, data: { content: string; mentions?: string[]; parent_id?: string }) {
return requestClient.post<CommentItem>(`${BASE}/records/${recordId}/comments`, data);
}
export function updateCommentApi(commentId: string, data: { content: string; mentions?: string[] }) {
return requestClient.put<CommentItem>(`${BASE}/comments/${commentId}`, data);
}
export function deleteCommentApi(commentId: string) {
return requestClient.delete(`${BASE}/comments/${commentId}`);
}
// ==================== View API ====================
export function getViewListApi(tableId: string) {
return requestClient.get<SmartViewItem[]>(`${BASE}/tables/${tableId}/views`);
}
export function createViewApi(tableId: string, data: { name: string; type: string; config?: Record<string, any> }) {
return requestClient.post<SmartViewItem>(`${BASE}/tables/${tableId}/views`, { ...data, table_id: tableId });
}
export function updateViewApi(viewId: string, data: Partial<SmartViewItem>) {
return requestClient.put<SmartViewItem>(`${BASE}/views/${viewId}`, data);
}
export function deleteViewApi(viewId: string) {
return requestClient.delete(`${BASE}/views/${viewId}`);
}
// ==================== Permission API ====================
export interface MyPermission {
role_type: string;
role_name: string;
capabilities: Record<string, boolean>;
field_permissions: Record<string, string>;
row_view_mode: string;
row_edit_mode: string;
}
export interface TableRole {
id: string;
table_id: string | null;
name: string;
role_type: string;
capabilities: Record<string, boolean>;
is_system: boolean;
sys_create_datetime?: string;
}
export interface Collaborator {
id: string;
table_id: string;
subject_type: string;
subject_id: string;
role_id: string;
role_name?: string;
role_type?: string;
subject_name?: string;
subject_avatar?: string;
sys_create_datetime?: string;
}
export interface FieldPermItem {
field_id: string;
access: string;
}
export interface FieldPermMatrix {
role_id: string;
role_name: string;
role_type: string;
fields: FieldPermItem[];
}
export interface RowRule {
id: string;
table_id: string;
role_id: string;
rule_type: string;
mode: string;
conditions: Record<string, any>[];
}
export function getMyPermissionApi(tableId: string) {
return requestClient.get<MyPermission>(`${BASE}/tables/${tableId}/my-permission`);
}
export function getRolesApi(tableId: string) {
return requestClient.get<TableRole[]>(`${BASE}/tables/${tableId}/roles`);
}
export function createRoleApi(tableId: string, data: { name: string; capabilities: Record<string, boolean> }) {
return requestClient.post<TableRole>(`${BASE}/tables/${tableId}/roles`, data);
}
export function updateRoleApi(tableId: string, roleId: string, data: { name?: string; capabilities?: Record<string, boolean> }) {
return requestClient.put<TableRole>(`${BASE}/tables/${tableId}/roles/${roleId}`, data);
}
export function deleteRoleApi(tableId: string, roleId: string) {
return requestClient.delete(`${BASE}/tables/${tableId}/roles/${roleId}`);
}
export function getCollaboratorsApi(tableId: string) {
return requestClient.get<Collaborator[]>(`${BASE}/tables/${tableId}/collaborators`);
}
export function addCollaboratorApi(tableId: string, data: { subject_type: string; subject_id: string; role_id: string }) {
return requestClient.post<Collaborator>(`${BASE}/tables/${tableId}/collaborators`, data);
}
export function updateCollaboratorApi(tableId: string, collabId: string, data: { role_id: string }) {
return requestClient.put<Collaborator>(`${BASE}/tables/${tableId}/collaborators/${collabId}`, data);
}
export function removeCollaboratorApi(tableId: string, collabId: string) {
return requestClient.delete(`${BASE}/tables/${tableId}/collaborators/${collabId}`);
}
export function getFieldPermissionsApi(tableId: string) {
return requestClient.get<FieldPermMatrix[]>(`${BASE}/tables/${tableId}/field-permissions`);
}
export function updateFieldPermissionsApi(tableId: string, data: { role_id: string; permissions: FieldPermItem[] }) {
return requestClient.put(`${BASE}/tables/${tableId}/field-permissions`, data);
}
export function getRowRulesApi(tableId: string) {
return requestClient.get<RowRule[]>(`${BASE}/tables/${tableId}/row-rules`);
}
export function updateRowRuleApi(tableId: string, data: { role_id: string; rule_type: string; mode: string; conditions: Record<string, any>[] }) {
return requestClient.put<RowRule>(`${BASE}/tables/${tableId}/row-rules`, data);
}
// ==================== Document Version API ====================
export interface DocumentVersionItem {
id: string;
document_id: string;
version: number;
title?: string;
change_summary?: string;
content_size: number;
sys_create_datetime?: string;
sys_creator_id?: string;
creator_name?: string;
creator_avatar?: string;
}
export interface DocumentVersionDetail extends DocumentVersionItem {
content: Record<string, any>;
}
export interface DocumentVersionCompare {
version_from: DocumentVersionDetail;
version_to: DocumentVersionDetail;
}
export function getDocumentVersionsApi(tableId: string, page = 1, pageSize = 20) {
return requestClient.get<{ items: DocumentVersionItem[]; total: number }>(
`${BASE}/tables/${tableId}/versions`,
{ params: { page, pageSize } },
);
}
export function getDocumentVersionDetailApi(versionId: string) {
return requestClient.get<DocumentVersionDetail>(`${BASE}/versions/${versionId}`);
}
export function createDocumentVersionApi(tableId: string, changeSummary?: string) {
return requestClient.post<DocumentVersionItem>(
`${BASE}/tables/${tableId}/versions`,
{ change_summary: changeSummary },
);
}
export function restoreDocumentVersionApi(tableId: string, versionId: string) {
return requestClient.post(`${BASE}/tables/${tableId}/versions/${versionId}/restore`);
}
export function compareDocumentVersionsApi(tableId: string, fromId: string, toId: string) {
return requestClient.get<DocumentVersionCompare>(
`${BASE}/tables/${tableId}/versions/compare`,
{ params: { from: fromId, to: toId } },
);
}
export function deleteDocumentVersionApi(versionId: string) {
return requestClient.delete(`${BASE}/versions/${versionId}`);
}
// ==================== Document Template API ====================
export interface DocumentTemplateItem {
id: string;
name: string;
description?: string;
icon: string;
category: string;
preview_image?: string;
is_system: boolean;
use_count: number;
sys_create_datetime?: string;
sys_update_datetime?: string;
sys_creator_id?: string;
creator_name?: string;
}
export interface DocumentTemplateDetail extends DocumentTemplateItem {
content: Record<string, any>;
}
export function getDocumentTemplatesApi(params?: { category?: string; keyword?: string; page?: number; pageSize?: number }) {
return requestClient.get<{ items: DocumentTemplateItem[]; total: number }>(
`${BASE}/document-templates`,
{ params },
);
}
export function getDocumentTemplateCategoriesApi() {
return requestClient.get<string[]>(`${BASE}/document-templates/categories`);
}
export function getDocumentTemplateDetailApi(templateId: string) {
return requestClient.get<DocumentTemplateDetail>(`${BASE}/document-templates/${templateId}`);
}
export function createDocumentTemplateApi(data: {
name: string;
description?: string;
icon?: string;
category?: string;
content: Record<string, any>;
preview_image?: string;
}) {
return requestClient.post<DocumentTemplateItem>(`${BASE}/document-templates`, data);
}
export function createTemplateFromDocumentApi(documentId: string, data: {
name: string;
description?: string;
category?: string;
content: Record<string, any>;
}) {
return requestClient.post<DocumentTemplateItem>(
`${BASE}/document-templates/from-document/${documentId}`,
data,
);
}
export function updateDocumentTemplateApi(templateId: string, data: {
name?: string;
description?: string;
icon?: string;
category?: string;
content?: Record<string, any>;
preview_image?: string;
}) {
return requestClient.put<DocumentTemplateItem>(`${BASE}/document-templates/${templateId}`, data);
}
export function deleteDocumentTemplateApi(templateId: string) {
return requestClient.delete(`${BASE}/document-templates/${templateId}`);
}
export function useDocumentTemplateApi(templateId: string) {
return requestClient.post(`${BASE}/document-templates/${templateId}/use`);
}
// ==================== Wiki Space API ====================
export interface WikiSpaceItem {
id: string;
name: string;
icon: string;
avatar?: string | null;
description?: string;
cover?: string;
category: string;
visibility: string;
sort: number;
document_count?: number;
sys_create_datetime?: string;
sys_update_datetime?: string;
sys_creator_id?: string;
creator_name?: string;
}
export interface WikiSpaceDetail extends WikiSpaceItem {
documents: SmartTableItem[];
}
export function getWikiSpacesApi() {
return requestClient.get<WikiSpaceItem[]>(`${BASE}/wiki-spaces`);
}
export function getWikiSpaceDetailApi(spaceId: string) {
return requestClient.get<WikiSpaceDetail>(`${BASE}/wiki-spaces/${spaceId}`);
}
export function createWikiSpaceApi(data: {
name: string;
icon?: string;
avatar?: string | null;
description?: string;
cover?: string;
category?: string;
visibility?: string;
}) {
return requestClient.post<WikiSpaceItem>(`${BASE}/wiki-spaces`, data);
}
export function updateWikiSpaceApi(spaceId: string, data: Partial<WikiSpaceItem>) {
return requestClient.put<WikiSpaceItem>(`${BASE}/wiki-spaces/${spaceId}`, data);
}
export function deleteWikiSpaceApi(spaceId: string) {
return requestClient.delete(`${BASE}/wiki-spaces/${spaceId}`);
}
export function getWikiSpaceDocumentsApi(spaceId: string) {
return requestClient.get<SmartTableItem[]>(`${BASE}/wiki-spaces/${spaceId}/documents`);
}
export function createWikiDocumentApi(spaceId: string, data: {
name: string;
parent_id?: string | null;
content?: any;
}) {
return requestClient.post<SmartTableItem>(
`${BASE}/wiki-spaces/${spaceId}/documents`,
{ ...data, type: 'document', icon: 'FileText' },
);
}
+403
View File
@@ -0,0 +1,403 @@
import { preferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores';
import { ElMessage } from 'element-plus';
import { useAuthStore } from '#/store';
import { refreshTokenApi } from './core';
/**
* 流式请求配置
*/
interface StreamRequestOptions {
/** 请求方法 */
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
/** 请求头 */
headers?: Record<string, string>;
/** 请求体 */
body?: any;
/** 超时时间(毫秒) */
timeout?: number;
/** 是否自动处理JSON */
json?: boolean;
}
/**
* 流式请求事件处理器
*/
export interface StreamEventHandler<T> {
/** 处理数据事件 */
onData?: (data: T) => void;
/** 处理错误事件 */
onError?: (error: Error) => void;
/** 处理完成事件 */
onComplete?: () => void;
}
// 是否正在刷新 token
let isRefreshing = false;
// 等待刷新 token 的请求队列
let refreshSubscribers: Array<(token: string) => void> = [];
/**
* 添加到刷新队列
*/
function subscribeTokenRefresh(callback: (token: string) => void) {
refreshSubscribers.push(callback);
}
/**
* 通知所有等待的请求
*/
function onTokenRefreshed(token: string) {
refreshSubscribers.forEach((callback) => callback(token));
refreshSubscribers = [];
}
/**
* 重新认证逻辑
*/
async function doReAuthenticate() {
console.warn('Access token or refresh token is invalid or expired.');
const accessStore = useAccessStore();
const authStore = useAuthStore();
accessStore.setAccessToken(null);
if (
preferences.app.loginExpiredMode === 'modal' &&
accessStore.isAccessChecked
) {
accessStore.setLoginExpired(true);
} else {
await authStore.logout();
}
}
/**
* 刷新 token 逻辑
*/
async function doRefreshToken(): Promise<string> {
const accessStore = useAccessStore();
// 检查 refreshToken 是否存在
if (!accessStore.refreshToken) {
console.error('Refresh token is missing');
await doReAuthenticate();
throw new Error('Refresh token is missing');
}
// 传递 refreshToken 给 API
const resp = await refreshTokenApi(accessStore.refreshToken);
// 处理响应中的新 token
const newToken = resp.data?.accessToken || '';
// 更新 access token
accessStore.setAccessToken(newToken);
// 如果响应中有新的 refresh token,也保存
if (typeof resp.data === 'object' && resp.data?.refreshToken) {
accessStore.setRefreshToken(resp.data.refreshToken);
}
return newToken;
}
/**
* 执行流式请求(内部实现)
*/
function doStreamRequest<T = any>(
url: string,
options: StreamRequestOptions = {},
eventHandler: StreamEventHandler<T> = {},
token?: string,
): () => void {
const {
method = 'GET',
headers = {},
body,
timeout = 30_000,
json = true,
} = options;
const controller = new AbortController();
const signal = controller.signal;
let isAborted = false;
// 设置请求头
const requestHeaders = new Headers({
'Content-Type': 'application/json',
...headers,
});
// 添加认证头
const accessStore = useAccessStore();
const accessToken = token || accessStore.accessToken;
if (accessToken) {
requestHeaders.set('Authorization', `Bearer ${accessToken}`);
}
// 添加语言头
requestHeaders.set('Accept-Language', preferences.app.locale);
// 准备请求体
let requestBody: BodyInit | null = null;
if (body) {
requestBody = json ? JSON.stringify(body) : body;
}
// 发起请求
const requestPromise = fetch(url, {
method,
headers: requestHeaders,
body: requestBody,
signal,
});
// 设置超时
const timeoutId = setTimeout(() => {
if (!isAborted) {
controller.abort();
const error = new Error('请求超时');
eventHandler.onError?.(error);
}
}, timeout);
// 处理响应
requestPromise
.then(async (response) => {
clearTimeout(timeoutId);
// 处理 401 错误 - 尝试刷新 token
if (response.status === 401 && preferences.app.enableRefreshToken) {
// 如果正在刷新 token,等待刷新完成后重试
if (isRefreshing) {
return new Promise<void>((resolve) => {
subscribeTokenRefresh((newToken) => {
// 使用新 token 重试请求
const cancel = doStreamRequest(
url,
options,
eventHandler,
newToken,
);
// 如果原请求被取消,也取消重试的请求
if (isAborted) {
cancel();
}
resolve();
});
});
}
// 开始刷新 token
isRefreshing = true;
try {
const newToken = await doRefreshToken();
isRefreshing = false;
onTokenRefreshed(newToken);
// 使用新 token 重试请求
const cancel = doStreamRequest(url, options, eventHandler, newToken);
// 如果原请求被取消,也取消重试的请求
if (isAborted) {
cancel();
}
return;
} catch {
isRefreshing = false;
refreshSubscribers = [];
await doReAuthenticate();
const error = new Error('认证失败,请重新登录');
(error as any).status = 401;
eventHandler.onError?.(error);
return;
}
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const error = new Error(
errorData.message ||
errorData.detail ||
`请求失败: ${response.statusText}`,
);
(error as any).status = response.status;
(error as any).data = errorData;
throw error;
}
if (!response.body) {
throw new Error('响应体为空');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
const processStream = async () => {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
eventHandler.onComplete?.();
return;
}
buffer += decoder.decode(value, { stream: true });
// 处理可能的多条消息
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
const line = buffer.slice(0, newlineIndex).trim();
buffer = buffer.slice(newlineIndex + 1);
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
eventHandler.onComplete?.();
return;
}
try {
const parsed = JSON.parse(data);
eventHandler.onData?.(parsed);
} catch (error) {
console.error('解析流数据失败:', error, '原始数据:', data);
}
}
}
}
} catch (error) {
if (!isAborted) {
console.error('处理流数据时出错:', error);
eventHandler.onError?.(error as Error);
}
}
};
await processStream();
})
.catch((error) => {
clearTimeout(timeoutId);
if (!isAborted) {
console.error('请求失败:', error);
// 处理取消的请求
if (error.name === 'AbortError') {
return;
}
// 处理HTTP错误
if (error.status) {
let errorMessage = error.message;
// 根据状态码显示更友好的错误信息
switch (error.status) {
case 401: {
errorMessage = '认证失败,请重新登录';
break;
}
case 403: {
errorMessage = '没有权限访问此资源';
break;
}
case 429: {
errorMessage = '请求过于频繁,请稍后再试';
break;
}
default: {
if (error.status >= 500) {
errorMessage = '服务器内部错误,请稍后再试';
}
}
}
ElMessage.error(errorMessage);
}
eventHandler.onError?.(error);
}
});
// 返回取消函数
return () => {
isAborted = true;
controller.abort();
clearTimeout(timeoutId);
};
}
/**
* 执行流式请求
* @param url 请求URL
* @param options 请求选项
* @param eventHandler 事件处理器
* @returns 取消函数
*/
export function streamRequest<T = any>(
url: string,
options: StreamRequestOptions = {},
eventHandler: StreamEventHandler<T> = {},
): () => void {
return doStreamRequest(url, options, eventHandler);
}
/**
* 创建流式请求的取消令牌
*/
export class StreamRequestToken {
/**
* 检查是否已取消
*/
get isCancelled() {
return this.controller?.signal.aborted ?? false;
}
private controller: AbortController | null = null;
/**
* 取消请求
*/
cancel() {
if (this.controller) {
this.controller.abort();
this.controller = null;
}
}
/**
* 创建一个新的取消令牌
*/
create() {
this.controller = new AbortController();
return this.controller.signal;
}
}
/**
* 使用流式请求配置创建请求函数
*/
export function createStreamRequest(
baseURL: string = '',
options: StreamRequestOptions = {},
) {
return <T>(url: string, data?: any, eventHandler?: StreamEventHandler<T>) => {
const fullUrl = `${baseURL}${url}`;
return streamRequest<T>(
fullUrl,
{
...options,
method: data ? 'POST' : 'GET',
body: data,
},
eventHandler,
);
};
}
// 创建默认的流式请求客户端
export const streamRequestClient = createStreamRequest(
import.meta.env.VITE_GLOB_API_URL,
);