Build lightweight AI agent admin
This commit is contained in:
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 },
|
||||
});
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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 } },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Schema(PostgreSQL)
|
||||
*/
|
||||
export async function createSchemaApi(
|
||||
dbName: string,
|
||||
data: CreateSchemaParams,
|
||||
) {
|
||||
return requestClient.post<SchemaOperationResponse>(
|
||||
`/api/core/database_manager/${dbName}/schemas`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Schema(PostgreSQL)
|
||||
*/
|
||||
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 } },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名 Schema(PostgreSQL)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@@ -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(',') },
|
||||
});
|
||||
}
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
@@ -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' },
|
||||
];
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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 },
|
||||
});
|
||||
}
|
||||
@@ -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 },
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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`,
|
||||
);
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './core';
|
||||
@@ -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 });
|
||||
@@ -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' },
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
Reference in New Issue
Block a user