ForcePilot/web/src/apis/base.js
Wenjie Zhang a005e0f3dc feat: 内置 Skills 按需安装机制
- Skill 模型新增 version, is_builtin, content_hash 字段
- 内置 skills 不再自动安装,改为按需安装
- 新增 GET/POST /system/skills/builtin 路由
- 安装时计算目录内容 hash,用于变更检测
- 更新时 hash 不一致返回 409 确认
- 前端显示内置 skills 未安装状态,支持安装/更新按钮
- 内置 skill 文件编辑入口已隐藏
- 修复 _compute_dir_hash 内存问题,改为 chunk 读取
- 19 个测试用例全部通过
2026-03-28 17:53:53 +08:00

247 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useUserStore, checkAdminPermission, checkSuperAdminPermission } from '@/stores/user'
import { message } from 'ant-design-vue'
/**
* 基础API请求封装
* 提供统一的请求方法,自动处理认证头和错误
*/
/**
* 发送API请求的基础函数
* @param {string} url - API端点
* @param {Object} options - 请求选项
* @param {boolean} requiresAuth - 是否需要认证头
* @param {string} responseType - 响应类型: 'json' | 'text' | 'blob'
* @returns {Promise} - 请求结果
*/
export async function apiRequest(url, options = {}, requiresAuth = true, responseType = 'json') {
try {
const isFormData = options?.body instanceof FormData
// 默认请求配置
const requestOptions = {
...options,
headers: {
...(!isFormData ? { 'Content-Type': 'application/json' } : {}),
...options.headers
}
}
// 如果需要认证,添加认证头
if (requiresAuth) {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('用户未登录')
}
Object.assign(requestOptions.headers, userStore.getAuthHeaders())
}
// 发送请求
const response = await fetch(url, requestOptions)
// 处理API返回的错误
if (!response.ok) {
// 尝试解析错误信息
let errorMessage = `请求失败: ${response.status}, ${response.statusText}`
let errorData = null
console.log('API请求失败:', {
url,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries())
})
try {
errorData = await response.json()
errorMessage = errorData.detail || errorData.message || errorMessage
console.log('API错误详情:', errorData)
// 如果是422错误打印更详细的信息
if (response.status === 422) {
console.error('422验证错误详情:', {
url,
requestMethod: requestOptions.method,
requestHeaders: requestOptions.headers,
requestBody: requestOptions.body,
responseData: errorData
})
}
} catch (e) {
// 如果无法解析JSON使用默认错误信息
console.log('无法解析错误响应JSON:', e)
}
// 特殊处理401和403错误
const error = new Error(errorMessage)
error.response = {
status: response.status,
statusText: response.statusText,
data: errorData
}
if (response.status === 401) {
// 如果是认证失败,可能需要重新登录
const userStore = useUserStore()
// 检查是否是token过期
const isTokenExpired =
errorData &&
(errorData.detail?.includes('令牌已过期') ||
errorData.detail?.includes('token expired') ||
errorMessage?.includes('令牌已过期') ||
errorMessage?.includes('token expired'))
message.error(isTokenExpired ? '登录已过期,请重新登录' : '认证失败,请重新登录')
// 如果用户当前认为自己已登录,则登出
if (userStore.isLoggedIn) {
userStore.logout()
}
// 使用setTimeout确保消息显示后再跳转
setTimeout(() => {
window.location.href = '/login'
}, 1500)
throw error
} else if (response.status === 403) {
error.message = '没有权限执行此操作'
throw error
} else if (response.status === 500) {
error.message = '服务器内部错误,请使用 docker logs api-dev 查看详细日志'
throw error
}
throw error
}
// 根据responseType处理响应
if (responseType === 'blob') {
return response
} else if (responseType === 'json') {
// 检查Content-Type以确定如何处理响应
const contentType = response.headers.get('Content-Type')
if (contentType && contentType.includes('application/json')) {
return await response.json()
}
return await response.text()
} else if (responseType === 'text') {
return await response.text()
} else {
return response
}
} catch (error) {
console.error('API请求错误:', error)
throw error
}
}
/**
* 发送GET请求
* @param {string} url - API端点
* @param {Object} options - 请求选项
* @param {boolean} requiresAuth - 是否需要认证
* @param {string} responseType - 响应类型: 'json' | 'text' | 'blob'
* @returns {Promise} - 请求结果
*/
export function apiGet(url, options = {}, requiresAuth = true, responseType = 'json') {
return apiRequest(url, { method: 'GET', ...options }, requiresAuth, responseType)
}
export function apiAdminGet(url, options = {}, responseType = 'json') {
checkAdminPermission()
return apiGet(url, options, true, responseType)
}
export function apiSuperAdminGet(url, options = {}, responseType = 'json') {
checkSuperAdminPermission()
return apiGet(url, options, true, responseType)
}
/**
* 发送POST请求
* @param {string} url - API端点
* @param {Object} data - 请求体数据
* @param {Object} options - 其他请求选项
* @param {boolean} requiresAuth - 是否需要认证
* @param {string} responseType - 响应类型: 'json' | 'text' | 'blob'
* @returns {Promise} - 请求结果
*/
export function apiPost(url, data = {}, options = {}, requiresAuth = true, responseType = 'json') {
return apiRequest(
url,
{
method: 'POST',
body: data instanceof FormData ? data : JSON.stringify(data),
...options
},
requiresAuth,
responseType
)
}
export function apiAdminPost(url, data = {}, options = {}, responseType = 'json') {
checkAdminPermission()
return apiPost(url, data, options, true, responseType)
}
export function apiSuperAdminPost(url, data = {}, options = {}, responseType = 'json') {
checkSuperAdminPermission()
return apiPost(url, data, options, true, responseType)
}
/**
* 发送PUT请求
* @param {string} url - API端点
* @param {Object} data - 请求体数据
* @param {Object} options - 其他请求选项
* @param {boolean} requiresAuth - 是否需要认证
* @param {string} responseType - 响应类型: 'json' | 'text' | 'blob'
* @returns {Promise} - 请求结果
*/
export function apiPut(url, data = {}, options = {}, requiresAuth = true, responseType = 'json') {
return apiRequest(
url,
{
method: 'PUT',
body: data instanceof FormData ? data : JSON.stringify(data),
...options
},
requiresAuth,
responseType
)
}
export function apiAdminPut(url, data = {}, options = {}, responseType = 'json') {
checkAdminPermission()
return apiPut(url, data, options, true, responseType)
}
export function apiSuperAdminPut(url, data = {}, options = {}, responseType = 'json') {
checkSuperAdminPermission()
return apiPut(url, data, options, true, responseType)
}
/**
* 发送DELETE请求
* @param {string} url - API端点
* @param {Object} options - 请求选项
* @param {boolean} requiresAuth - 是否需要认证
* @param {string} responseType - 响应类型: 'json' | 'text' | 'blob'
* @returns {Promise} - 请求结果
*/
export function apiDelete(url, options = {}, requiresAuth = true, responseType = 'json') {
return apiRequest(url, { method: 'DELETE', ...options }, requiresAuth, responseType)
}
export function apiAdminDelete(url, options = {}) {
checkAdminPermission()
return apiDelete(url, options, true)
}
export function apiSuperAdminDelete(url, options = {}) {
checkSuperAdminPermission()
return apiDelete(url, options, true)
}