feat: 优化发布单冲突提示

- src/services/http.ts: 保留后端 envelope 错误码、状态和 details
- src/stores/platform.ts: 增加发布单列表轻量刷新入口
- src/views: 发布中心和运行记录接入 CONFLICT 刷新提示
- src/types/devops.ts: 同步 ReleaseRun version 字段
This commit is contained in:
湛兮
2026-06-12 05:22:28 +08:00
parent 0302660351
commit a2eb83051c
6 changed files with 167 additions and 14 deletions
+2
View File
@@ -98,6 +98,7 @@ interface BackendDeployRunStep {
interface BackendDeployRun {
id?: string
version?: number
projectKey?: string
environment?: EnvironmentName
ref?: string
@@ -993,6 +994,7 @@ function toReleaseRun(run: BackendDeployRun, projectList: ProjectConfig[]): Rele
return {
id: run.id || `${projectKey}-${ref}-${run.createdAt ?? Date.now()}`,
version: typeof run.version === 'number' && Number.isFinite(run.version) ? run.version : 0,
projectKey,
projectName: project?.name ?? projectKey,
environment: toEnvironmentName(run.environment),
+85 -8
View File
@@ -1,5 +1,29 @@
import axios from 'axios'
type DevopsApiErrorOptions = {
code?: string
message?: string
details?: unknown
status?: number
}
/**
* 后端 envelope 错误在浏览器侧的统一形态,保留 code/details 供页面做精确交互。
*/
export class DevopsApiError extends Error {
readonly code: string
readonly details: unknown
readonly status: number | undefined
constructor(options: DevopsApiErrorOptions) {
super(options.message || '请求失败,请稍后重试')
this.name = 'DevopsApiError'
this.code = options.code || 'HTTP_ERROR'
this.details = options.details
this.status = options.status
}
}
/**
* DevOps 平台统一 HTTP 实例,只识别后端 envelope,不承接任何第三方密钥。
*/
@@ -27,13 +51,7 @@ http.interceptors.response.use(
return response
},
(error) => {
const message =
error.response?.data?.error?.message ||
error.response?.data?.message ||
error.message ||
'请求失败,请稍后重试'
return Promise.reject(new Error(message))
return Promise.reject(toDevopsApiError(error))
},
)
@@ -59,7 +77,11 @@ function unwrapApiPayload(payload: unknown): unknown {
return envelope.data
}
throw new Error(envelope.error?.message || '请求失败,请稍后重试')
throw new DevopsApiError({
code: envelope.error?.code,
message: envelope.error?.message,
details: envelope.error?.details,
})
}
if (Object.keys(payload).length === 1 && 'data' in payload) {
@@ -76,3 +98,58 @@ function isRecord(value: unknown): value is Record<string, unknown> {
function isApiEnvelope(value: unknown): value is ApiEnvelope<unknown> {
return isRecord(value) && typeof value.success === 'boolean'
}
/**
* 类型守卫供页面区分业务错误与普通网络错误,避免靠中文文案判断状态。
*/
export function isDevopsApiError(error: unknown): error is DevopsApiError {
return error instanceof DevopsApiError
}
function toDevopsApiError(error: unknown): DevopsApiError {
if (isDevopsApiError(error)) {
return error
}
if (isRecord(error)) {
const response = isRecord(error.response) ? error.response : undefined
const status = typeof response?.status === 'number' ? response.status : undefined
const payload = response?.data
if (isApiEnvelope(payload)) {
return new DevopsApiError({
code: payload.error?.code,
message: payload.error?.message,
details: payload.error?.details,
status,
})
}
if (isRecord(payload)) {
const backendError = isRecord(payload.error) ? payload.error : undefined
const code = textValue(backendError?.code) || textValue(payload.code)
const message =
textValue(backendError?.message) ||
textValue(payload.message) ||
textValue(error.message)
return new DevopsApiError({
code,
message,
details: backendError?.details ?? payload.details,
status,
})
}
return new DevopsApiError({
message: textValue(error.message),
status,
})
}
return new DevopsApiError({})
}
function textValue(value: unknown): string | undefined {
return typeof value === 'string' && value.trim() ? value : undefined
}