fix:sample/plate 之前的开发
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
import type { Cross, CrossParent, CrossingProject, PlannedCross } from "@/lib/api/types.gen";
|
||||
import {
|
||||
loadGermplasmOptions,
|
||||
loadObservationUnitOptions,
|
||||
loadProgramOptions,
|
||||
} from "@/services/dropdownCache";
|
||||
import { getAuthToken } from "@/utils/token";
|
||||
import {
|
||||
mapCross,
|
||||
mapCrossingProject,
|
||||
mapPlannedCross,
|
||||
} from "./mappers";
|
||||
import type {
|
||||
CrossParentRow,
|
||||
CrossRecord,
|
||||
CrossingProjectRecord,
|
||||
PlannedCrossRecord,
|
||||
SelectOption,
|
||||
} from "./types";
|
||||
|
||||
interface BrapiListResponse<T> {
|
||||
result: { data: T[] };
|
||||
}
|
||||
|
||||
export interface CrossPedigreeSnapshot {
|
||||
programs: SelectOption[];
|
||||
germplasm: SelectOption[];
|
||||
observationUnits: SelectOption[];
|
||||
crossingProjectOptions: SelectOption[];
|
||||
plannedCrossOptions: SelectOption[];
|
||||
crossOptions: SelectOption[];
|
||||
crossingProjects: CrossingProjectRecord[];
|
||||
plannedCrosses: PlannedCrossRecord[];
|
||||
actualCrosses: CrossRecord[];
|
||||
parentRows: CrossParentRow[];
|
||||
}
|
||||
|
||||
const apiBase = () => {
|
||||
if (typeof window !== "undefined") return "";
|
||||
return process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
|
||||
};
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const token = getAuthToken();
|
||||
const response = await fetch(`${apiBase()}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(init?.headers || {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(detail || `请求失败:${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
const flattenCrossParents = (
|
||||
crossId: string,
|
||||
crossName: string | null,
|
||||
planned: boolean,
|
||||
crossingProjectId: string | null,
|
||||
crossingProjectName: string | null,
|
||||
parent: CrossParent | null | undefined,
|
||||
slot: "parent1" | "parent2",
|
||||
): CrossParentRow | null => {
|
||||
if (!parent) return null;
|
||||
const hasData = parent.parentType || parent.germplasmDbId || parent.observationUnitDbId;
|
||||
if (!hasData) return null;
|
||||
return {
|
||||
id: `${crossId}:${slot}`,
|
||||
cross_id: crossId,
|
||||
cross_name: crossName,
|
||||
planned,
|
||||
parent_slot: slot,
|
||||
parent_type: parent.parentType ?? null,
|
||||
germplasm_id: parent.germplasmDbId ?? null,
|
||||
germplasm_name: parent.germplasmName ?? null,
|
||||
observation_unit_id: parent.observationUnitDbId ?? null,
|
||||
observation_unit_name: parent.observationUnitName ?? null,
|
||||
crossing_project_id: crossingProjectId,
|
||||
crossing_project_name: crossingProjectName,
|
||||
};
|
||||
};
|
||||
|
||||
function buildParentRows(planned: PlannedCrossRecord[], actual: CrossRecord[]): CrossParentRow[] {
|
||||
const rows: CrossParentRow[] = [];
|
||||
|
||||
for (const cross of planned) {
|
||||
const p1 = flattenCrossParents(
|
||||
cross.id, cross.name, true, cross.crossing_project_id, cross.crossing_project_name, cross.parent1, "parent1",
|
||||
);
|
||||
const p2 = flattenCrossParents(
|
||||
cross.id, cross.name, true, cross.crossing_project_id, cross.crossing_project_name, cross.parent2, "parent2",
|
||||
);
|
||||
if (p1) rows.push(p1);
|
||||
if (p2) rows.push(p2);
|
||||
}
|
||||
|
||||
for (const cross of actual) {
|
||||
const p1 = flattenCrossParents(
|
||||
cross.id, cross.name, false, cross.crossing_project_id, cross.crossing_project_name, cross.parent1, "parent1",
|
||||
);
|
||||
const p2 = flattenCrossParents(
|
||||
cross.id, cross.name, false, cross.crossing_project_id, cross.crossing_project_name, cross.parent2, "parent2",
|
||||
);
|
||||
if (p1) rows.push(p1);
|
||||
if (p2) rows.push(p2);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function fetchSnapshotFromNetwork(): Promise<CrossPedigreeSnapshot> {
|
||||
const [programs, germplasm, crossingProjects, plannedCrosses, actualCrosses, observationUnits] = await Promise.all([
|
||||
loadProgramOptions(),
|
||||
loadGermplasmOptions(),
|
||||
request<BrapiListResponse<CrossingProject>>("/brapi/v2/crossingprojects?page=0&pageSize=1000"),
|
||||
request<BrapiListResponse<PlannedCross>>("/brapi/v2/plannedcrosses?page=0&pageSize=1000"),
|
||||
request<BrapiListResponse<Cross>>("/brapi/v2/crosses?page=0&pageSize=1000"),
|
||||
loadObservationUnitOptions().catch(() => [] as SelectOption[]),
|
||||
]);
|
||||
|
||||
const crossingProjectRows = (crossingProjects.result?.data ?? []).map(mapCrossingProject);
|
||||
const plannedRows = (plannedCrosses.result?.data ?? []).map(mapPlannedCross);
|
||||
const actualRows = (actualCrosses.result?.data ?? []).map(mapCross);
|
||||
|
||||
return {
|
||||
programs,
|
||||
germplasm,
|
||||
observationUnits,
|
||||
crossingProjectOptions: crossingProjectRows.map((project) => ({
|
||||
value: project.id,
|
||||
label: project.name || project.id,
|
||||
})),
|
||||
plannedCrossOptions: plannedRows.map((cross) => ({
|
||||
value: cross.id,
|
||||
label: cross.name || cross.id,
|
||||
})),
|
||||
crossOptions: [...plannedRows, ...actualRows].map((cross) => ({
|
||||
value: cross.id,
|
||||
label: `${cross.planned ? "[计划] " : "[实际] "}${cross.name || cross.id}`,
|
||||
})),
|
||||
crossingProjects: crossingProjectRows,
|
||||
plannedCrosses: plannedRows,
|
||||
actualCrosses: actualRows,
|
||||
parentRows: buildParentRows(plannedRows, actualRows),
|
||||
};
|
||||
}
|
||||
|
||||
let cachedSnapshot: CrossPedigreeSnapshot | null = null;
|
||||
let inflightSnapshot: Promise<CrossPedigreeSnapshot> | null = null;
|
||||
|
||||
export function invalidateCrossPedigreeCache() {
|
||||
cachedSnapshot = null;
|
||||
inflightSnapshot = null;
|
||||
}
|
||||
|
||||
export async function loadCrossPedigreeSnapshot(force = false): Promise<CrossPedigreeSnapshot> {
|
||||
if (!force && cachedSnapshot) return cachedSnapshot;
|
||||
if (!force && inflightSnapshot) return inflightSnapshot;
|
||||
|
||||
inflightSnapshot = fetchSnapshotFromNetwork()
|
||||
.then((snapshot) => {
|
||||
cachedSnapshot = snapshot;
|
||||
inflightSnapshot = null;
|
||||
return snapshot;
|
||||
})
|
||||
.catch((error) => {
|
||||
inflightSnapshot = null;
|
||||
throw error;
|
||||
});
|
||||
|
||||
return inflightSnapshot;
|
||||
}
|
||||
|
||||
export function getCrossPedigreeSnapshot(): CrossPedigreeSnapshot | null {
|
||||
return cachedSnapshot;
|
||||
}
|
||||
Reference in New Issue
Block a user