R54 A1-A6:Cutover 控制台与特性开关增强
- Feature Flag 变更历史查看与影响范围预览 - Feature Flag 批量操作(启用/禁用/继承) - Cutover 控制台页面增强(摘要卡片、时间线、延迟生效) - 单测覆盖 changelog、batch、权限边界
这个提交包含在:
+103
-1
@@ -1,8 +1,32 @@
|
||||
import { apiRequestData } from "./http";
|
||||
|
||||
export type FeatureFlagKey = "erp_sync" | "invoice_verification" | "leave_holiday_calendar" | "budget_precheck_v1" | "payment_batch_v1" | "portal_home_v1" | "global_search_v1" | "announcements_v1" | "workflow_definition_admin_v1" | "cutover_console_v1" | "presence_board_v1" | "weekly_report_v1" | "employee_lifecycle_v1" | "todo_board_v1" | "project_domain_v1" | "project_linking_v1" | "project_communication_log_v1" | "project_closure_checklist_v1" | "confidential_doc_refs_v1" | "public_security_hardening_v1";
|
||||
export type FeatureFlagKey =
|
||||
| "ai_assist"
|
||||
| "erp_sync"
|
||||
| "invoice_verification"
|
||||
| "leave_holiday_calendar"
|
||||
| "budget_precheck_v1"
|
||||
| "payment_batch_v1"
|
||||
| "portal_home_v1"
|
||||
| "global_search_v1"
|
||||
| "announcements_v1"
|
||||
| "workflow_definition_admin_v1"
|
||||
| "cutover_console_v1"
|
||||
| "presence_board_v1"
|
||||
| "weekly_report_v1"
|
||||
| "employee_lifecycle_v1"
|
||||
| "todo_board_v1"
|
||||
| "wecom_entry_v1"
|
||||
| "project_domain_v1"
|
||||
| "project_linking_v1"
|
||||
| "project_communication_log_v1"
|
||||
| "project_closure_checklist_v1"
|
||||
| "confidential_doc_refs_v1"
|
||||
| "public_security_hardening_v1";
|
||||
|
||||
export type FeatureFlagOverrideMode = "inherit" | "force_on" | "force_off";
|
||||
export type FeatureFlagRolloutType = "global" | "department_allowlist";
|
||||
export type FeatureFlagSection = "main" | "admin" | "backup";
|
||||
|
||||
export type FeatureFlagItem = {
|
||||
id: string | null;
|
||||
@@ -23,10 +47,51 @@ export type FeatureFlagItem = {
|
||||
requiresDepartment: boolean;
|
||||
};
|
||||
|
||||
export type FeatureFlagSnapshot = {
|
||||
overrideMode: FeatureFlagOverrideMode;
|
||||
rolloutType: FeatureFlagRolloutType;
|
||||
departmentIds: string[];
|
||||
note: string;
|
||||
version: number;
|
||||
};
|
||||
|
||||
export type FeatureFlagChangelogItem = {
|
||||
id: string;
|
||||
actorId: string;
|
||||
actorName: string;
|
||||
createdAt: string | null;
|
||||
from: FeatureFlagSnapshot | null;
|
||||
to: FeatureFlagSnapshot | null;
|
||||
};
|
||||
|
||||
export type FeatureFlagImpactNavigationItem = {
|
||||
label: string;
|
||||
path: string;
|
||||
section: FeatureFlagSection;
|
||||
};
|
||||
|
||||
export type FeatureFlagImpactBackendModule = {
|
||||
label: string;
|
||||
routes: string[];
|
||||
};
|
||||
|
||||
export type FeatureFlagListResponse = {
|
||||
items: FeatureFlagItem[];
|
||||
};
|
||||
|
||||
export type FeatureFlagChangelogResponse = {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
items: FeatureFlagChangelogItem[];
|
||||
};
|
||||
|
||||
export type FeatureFlagImpactResponse = {
|
||||
flagKey: FeatureFlagKey;
|
||||
navigationItems: FeatureFlagImpactNavigationItem[];
|
||||
backendModules: FeatureFlagImpactBackendModule[];
|
||||
};
|
||||
|
||||
export type FeatureFlagUpdatePayload = {
|
||||
overrideMode: FeatureFlagOverrideMode;
|
||||
rolloutType: FeatureFlagRolloutType;
|
||||
@@ -35,13 +100,50 @@ export type FeatureFlagUpdatePayload = {
|
||||
expectedVersion: number;
|
||||
};
|
||||
|
||||
export type FeatureFlagBatchUpdatePayload = {
|
||||
items: Array<{
|
||||
flagKey: FeatureFlagKey;
|
||||
overrideMode: FeatureFlagOverrideMode;
|
||||
expectedVersion: number;
|
||||
}>;
|
||||
note: string;
|
||||
};
|
||||
|
||||
export async function fetchFeatureFlags() {
|
||||
return apiRequestData<FeatureFlagListResponse>("/api/system/feature-flags");
|
||||
}
|
||||
|
||||
export async function fetchFeatureFlagChangelog(
|
||||
flagKey: FeatureFlagKey,
|
||||
params?: { page?: number; pageSize?: number },
|
||||
) {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) {
|
||||
searchParams.set("page", String(params.page));
|
||||
}
|
||||
if (params?.pageSize) {
|
||||
searchParams.set("pageSize", String(params.pageSize));
|
||||
}
|
||||
const suffix = searchParams.toString() ? `?${searchParams.toString()}` : "";
|
||||
return apiRequestData<FeatureFlagChangelogResponse>(
|
||||
`/api/system/feature-flags/${flagKey}/changelog${suffix}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchFeatureFlagImpact(flagKey: FeatureFlagKey) {
|
||||
return apiRequestData<FeatureFlagImpactResponse>(`/api/system/feature-flags/${flagKey}/impact`);
|
||||
}
|
||||
|
||||
export async function updateFeatureFlag(flagKey: FeatureFlagKey, payload: FeatureFlagUpdatePayload) {
|
||||
return apiRequestData<{ item: FeatureFlagItem }>(`/api/system/feature-flags/${flagKey}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function batchUpdateFeatureFlags(payload: FeatureFlagBatchUpdatePayload) {
|
||||
return apiRequestData<{ count: number }>("/api/system/feature-flags/batch", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户