fetch(add): 新增
Some checks failed
构建并部署前端到测试环境 / build-and-deploy (push) Failing after 6s

This commit is contained in:
sexygoat
2026-01-27 09:18:45 +08:00
parent 0eed8244e5
commit 5c6312c407
33 changed files with 4897 additions and 374 deletions

View File

@@ -0,0 +1,46 @@
/**
* 授权记录相关 API
*/
import { BaseService } from '../BaseService'
import type { BaseResponse, PaginationResponse } from '@/types/api'
import type {
AuthorizationItem,
AuthorizationListParams,
UpdateAuthorizationRemarkRequest
} from '@/types/api/authorization'
export class AuthorizationService extends BaseService {
/**
* 获取授权记录列表
* @param params 查询参数
*/
static getAuthorizations(
params?: AuthorizationListParams
): Promise<PaginationResponse<AuthorizationItem>> {
return this.getPage<AuthorizationItem>('/api/admin/authorizations', params)
}
/**
* 获取授权记录详情
* @param id 授权记录ID
*/
static getAuthorizationDetail(id: number): Promise<BaseResponse<AuthorizationItem>> {
return this.getOne<AuthorizationItem>(`/api/admin/authorizations/${id}`)
}
/**
* 修改授权备注
* @param id 授权记录ID
* @param data 备注数据
*/
static updateAuthorizationRemark(
id: number,
data: UpdateAuthorizationRemarkRequest
): Promise<BaseResponse<AuthorizationItem>> {
return this.put<BaseResponse<AuthorizationItem>>(
`/api/admin/authorizations/${id}/remark`,
data
)
}
}

View File

@@ -273,23 +273,18 @@ export class CardService extends BaseService {
// ========== ICCID批量导入相关 ==========
/**
* 批量导入ICCID
* @param file Excel文件
* @param carrier_id 运营商ID
* @param batch_no 批次号(可选)
* 批量导入ICCID(新版:使用 JSON 格式)
* @param data 导入请求参数
*/
static importIotCards(
file: File,
carrier_id: number,
static importIotCards(data: {
carrier_id: number
file_key: string
batch_no?: string
): Promise<BaseResponse> {
const formData = new FormData()
formData.append('file', file)
formData.append('carrier_id', carrier_id.toString())
if (batch_no) {
formData.append('batch_no', batch_no)
}
return this.upload('/api/admin/iot-cards/import', file, { carrier_id, batch_no })
}): Promise<BaseResponse<{ task_id: number; task_no: string; message: string }>> {
return this.post<BaseResponse<{ task_id: number; task_no: string; message: string }>>(
'/api/admin/iot-cards/import',
data
)
}
/**

149
src/api/modules/device.ts Normal file
View File

@@ -0,0 +1,149 @@
/**
* 设备管理相关 API
*/
import { BaseService } from '../BaseService'
import type {
Device,
DeviceQueryParams,
DeviceListResponse,
DeviceCardsResponse,
BindCardToDeviceRequest,
BindCardToDeviceResponse,
UnbindCardFromDeviceResponse,
AllocateDevicesRequest,
AllocateDevicesResponse,
RecallDevicesRequest,
RecallDevicesResponse,
ImportDeviceRequest,
ImportDeviceResponse,
DeviceImportTaskQueryParams,
DeviceImportTaskListResponse,
DeviceImportTaskDetail,
BaseResponse
} from '@/types/api'
export class DeviceService extends BaseService {
// ========== 设备基础管理 ==========
/**
* 获取设备列表
* @param params 查询参数
*/
static getDevices(params?: DeviceQueryParams): Promise<BaseResponse<DeviceListResponse>> {
return this.get<BaseResponse<DeviceListResponse>>('/api/admin/devices', params)
}
/**
* 获取设备详情
* @param id 设备ID
*/
static getDeviceById(id: number): Promise<BaseResponse<Device>> {
return this.getOne<Device>(`/api/admin/devices/${id}`)
}
/**
* 删除设备
* @param id 设备ID
*/
static deleteDevice(id: number): Promise<BaseResponse> {
return this.remove(`/api/admin/devices/${id}`)
}
// ========== 设备卡绑定管理 ==========
/**
* 获取设备绑定的卡列表
* @param id 设备ID
*/
static getDeviceCards(id: number): Promise<BaseResponse<DeviceCardsResponse>> {
return this.getOne<DeviceCardsResponse>(`/api/admin/devices/${id}/cards`)
}
/**
* 绑定卡到设备
* @param id 设备ID
* @param data 绑定参数
*/
static bindCard(
id: number,
data: BindCardToDeviceRequest
): Promise<BaseResponse<BindCardToDeviceResponse>> {
return this.post<BaseResponse<BindCardToDeviceResponse>>(
`/api/admin/devices/${id}/cards`,
data
)
}
/**
* 解绑设备上的卡
* @param deviceId 设备ID
* @param cardId IoT卡ID
*/
static unbindCard(
deviceId: number,
cardId: number
): Promise<BaseResponse<UnbindCardFromDeviceResponse>> {
return this.delete<BaseResponse<UnbindCardFromDeviceResponse>>(
`/api/admin/devices/${deviceId}/cards/${cardId}`
)
}
// ========== 批量分配和回收 ==========
/**
* 批量分配设备
* @param data 分配参数
*/
static allocateDevices(
data: AllocateDevicesRequest
): Promise<BaseResponse<AllocateDevicesResponse>> {
return this.post<BaseResponse<AllocateDevicesResponse>>(
'/api/admin/devices/allocate',
data
)
}
/**
* 批量回收设备
* @param data 回收参数
*/
static recallDevices(
data: RecallDevicesRequest
): Promise<BaseResponse<RecallDevicesResponse>> {
return this.post<BaseResponse<RecallDevicesResponse>>('/api/admin/devices/recall', data)
}
// ========== 设备导入 ==========
/**
* 批量导入设备
* @param data 导入参数
*/
static importDevices(
data: ImportDeviceRequest
): Promise<BaseResponse<ImportDeviceResponse>> {
return this.post<BaseResponse<ImportDeviceResponse>>('/api/admin/devices/import', data)
}
/**
* 获取导入任务列表
* @param params 查询参数
*/
static getImportTasks(
params?: DeviceImportTaskQueryParams
): Promise<BaseResponse<DeviceImportTaskListResponse>> {
return this.get<BaseResponse<DeviceImportTaskListResponse>>(
'/api/admin/devices/import/tasks',
params
)
}
/**
* 获取导入任务详情
* @param id 任务ID
*/
static getImportTaskDetail(id: number): Promise<BaseResponse<DeviceImportTaskDetail>> {
return this.getOne<DeviceImportTaskDetail>(`/api/admin/devices/import/tasks/${id}`)
}
}

View File

@@ -14,6 +14,16 @@ import type {
UpdateEnterpriseStatusParams,
CreateEnterpriseResponse
} from '@/types/api/enterprise'
import type {
AllocateCardsRequest,
AllocateCardsResponse,
AllocateCardsPreviewRequest,
AllocateCardsPreviewResponse,
EnterpriseCardListParams,
EnterpriseCardPageResult,
RecallCardsRequest,
RecallCardsResponse
} from '@/types/api/enterpriseCard'
export class EnterpriseService extends BaseService {
/**
@@ -63,4 +73,88 @@ export class EnterpriseService extends BaseService {
): Promise<BaseResponse> {
return this.put<BaseResponse>(`/api/admin/enterprises/${id}/status`, data)
}
// ========== 企业卡授权相关 ==========
/**
* 授权卡给企业
* @param enterpriseId 企业ID
* @param data 授权请求数据
*/
static allocateCards(
enterpriseId: number,
data: AllocateCardsRequest
): Promise<BaseResponse<AllocateCardsResponse>> {
return this.post<BaseResponse<AllocateCardsResponse>>(
`/api/admin/enterprises/${enterpriseId}/allocate-cards`,
data
)
}
/**
* 卡授权预检
* @param enterpriseId 企业ID
* @param data 预检请求数据
*/
static previewAllocateCards(
enterpriseId: number,
data: AllocateCardsPreviewRequest
): Promise<BaseResponse<AllocateCardsPreviewResponse>> {
return this.post<BaseResponse<AllocateCardsPreviewResponse>>(
`/api/admin/enterprises/${enterpriseId}/allocate-cards/preview`,
data
)
}
/**
* 获取企业卡列表
* @param enterpriseId 企业ID
* @param params 查询参数
*/
static getEnterpriseCards(
enterpriseId: number,
params?: EnterpriseCardListParams
): Promise<BaseResponse<EnterpriseCardPageResult>> {
return this.get<BaseResponse<EnterpriseCardPageResult>>(
`/api/admin/enterprises/${enterpriseId}/cards`,
params
)
}
/**
* 复机卡
* @param enterpriseId 企业ID
* @param cardId 卡ID
*/
static resumeCard(enterpriseId: number, cardId: number): Promise<BaseResponse> {
return this.post<BaseResponse>(
`/api/admin/enterprises/${enterpriseId}/cards/${cardId}/resume`
)
}
/**
* 停机卡
* @param enterpriseId 企业ID
* @param cardId 卡ID
*/
static suspendCard(enterpriseId: number, cardId: number): Promise<BaseResponse> {
return this.post<BaseResponse>(
`/api/admin/enterprises/${enterpriseId}/cards/${cardId}/suspend`
)
}
/**
* 回收卡授权
* @param enterpriseId 企业ID
* @param data 回收请求数据
*/
static recallCards(
enterpriseId: number,
data: RecallCardsRequest
): Promise<BaseResponse<RecallCardsResponse>> {
return this.post<BaseResponse<RecallCardsResponse>>(
`/api/admin/enterprises/${enterpriseId}/recall-cards`,
data
)
}
}

View File

@@ -17,8 +17,10 @@ export { CardService } from './card'
export { CommissionService } from './commission'
export { EnterpriseService } from './enterprise'
export { CustomerAccountService } from './customerAccount'
export { StorageService } from './storage'
export { AuthorizationService } from './authorization'
export { DeviceService } from './device'
// TODO: 按需添加其他业务模块
// export { PackageService } from './package'
// export { DeviceService } from './device'
// export { SettingService } from './setting'

104
src/api/modules/storage.ts Normal file
View File

@@ -0,0 +1,104 @@
/**
* 对象存储相关 API
*/
import { BaseService } from '../BaseService'
import type { BaseResponse } from '@/types/api'
/**
* 文件用途枚举
*/
export type FilePurpose = 'iot_import' | 'export' | 'attachment'
/**
* 获取上传 URL 请求参数
*/
export interface GetUploadUrlRequest {
/** 文件名cards.csv */
file_name: string
/** 文件 MIME 类型text/csv留空则自动推断 */
content_type?: string
/** 文件用途 (iot_import:ICCID导入, export:数据导出, attachment:附件) */
purpose: FilePurpose
}
/**
* 获取上传 URL 响应
*/
export interface GetUploadUrlResponse {
/** 预签名上传 URL使用 PUT 方法上传文件 */
upload_url: string
/** 文件路径标识,上传成功后用于调用业务接口 */
file_key: string
/** URL 有效期(秒) */
expires_in: number
}
export class StorageService extends BaseService {
/**
* 获取文件上传预签名 URL
*
* ## 完整上传流程
* 1. 调用本接口获取预签名 URL 和 file_key
* 2. 使用预签名 URL 上传文件(发起 PUT 请求直接上传到对象存储)
* 3. 使用 file_key 调用相关业务接口
*
* @param data 请求参数
*/
static getUploadUrl(data: GetUploadUrlRequest): Promise<BaseResponse<GetUploadUrlResponse>> {
return this.post<BaseResponse<GetUploadUrlResponse>>('/api/admin/storage/upload-url', data)
}
/**
* 使用预签名 URL 上传文件到对象存储
*
* 注意事项:
* - 预签名 URL 有效期 15 分钟,请及时使用
* - 上传时 Content-Type 需与请求时一致
* - file_key 在上传成功后永久有效
*
* @param uploadUrl 预签名 URL
* @param file 文件
* @param contentType 文件类型(需与 getUploadUrl 请求时保持一致)
*/
static async uploadFile(uploadUrl: string, file: File, contentType?: string): Promise<void> {
try {
// 在开发环境下,使用代理路径避免 CORS 问题
let finalUrl = uploadUrl
if (import.meta.env.DEV) {
// 将对象存储的域名替换为代理路径
// 例如http://obs-helf.cucloud.cn/cmp/... -> /obs-proxy/cmp/...
finalUrl = uploadUrl.replace(/^https?:\/\/obs-helf\.cucloud\.cn/, '/obs-proxy')
}
const headers: Record<string, string> = {}
// 只有在明确指定 contentType 时才设置,否则让浏览器自动处理
if (contentType) {
headers['Content-Type'] = contentType
}
const response = await fetch(finalUrl, {
method: 'PUT',
body: file,
headers,
mode: 'cors' // 明确指定 CORS 模式
})
if (!response.ok) {
const errorText = await response.text().catch(() => response.statusText)
throw new Error(`上传文件失败 (${response.status}): ${errorText}`)
}
} catch (error: any) {
// 增强错误信息
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
throw new Error(
'CORS 错误: 无法上传文件到对象存储。' +
'这通常是因为对象存储服务器未正确配置 CORS 策略。' +
'请联系后端开发人员检查对象存储的 CORS 配置。'
)
}
throw error
}
}
}