import { getWorkbenchAccessToken } from "./request-context"; import { z } from "zod"; import type { WorkbenchAccess } from "./request-context"; import { env } from "@/shared/config/env"; import type { WaterNetworkSourceId } from "../map/sources"; import { createWorkbenchHeaders, findOwnedWorkbenchProject, getServerError } from "./project-client"; const gisAttributeGroupSchema = z.object({ dataset_key: z.string().min(1), dataset_title: z.string().min(1), source_date: z.string().date(), layer_key: z.string().min(1), layer_name: z.string().min(1), source_fid: z.number().int(), match_method: z.enum(["node_pair", "geometry", "source"]), match_distance_m: z.number().finite().nonnegative().nullable(), attributes: z.record(z.unknown()) }); const elementGisPropertiesSchema = z.object({ element_type: z.enum([ "pipes", "junctions", "valves", "reservoirs", "pumps", "tanks", "scada" ]), element_id: z.string().min(1), groups: z.array(gisAttributeGroupSchema) }); const sourceGisFeaturePropertiesSchema = z.object({ layer_key: z.string().min(1), source_fid: z.number().int().nonnegative(), group: gisAttributeGroupSchema }); export type GisAttributeGroup = z.infer; const SERVER_API_BASE_URL = env.TJWATER_SERVER_API_BASE_URL.replace(/\/$/, ""); export async function fetchElementGisProperties( elementType: WaterNetworkSourceId, elementId: string, projectId: string | null, getAccessToken?: WorkbenchAccess, signal?: AbortSignal ): Promise { const ownedProject = projectId ? null : await findOwnedWorkbenchProject(getAccessToken, signal); const accessToken = ownedProject?.accessToken ?? await getWorkbenchAccessToken(getAccessToken); const resolvedProjectId = projectId ?? ownedProject?.project.project_id; if (!resolvedProjectId) throw new Error("未找到 GIS 台账所属项目"); const path = [ "/api/v1/elements", encodeURIComponent(elementType), encodeURIComponent(elementId), "gis-properties" ].join("/"); const response = await fetch(`${SERVER_API_BASE_URL}${path}`, { headers: createWorkbenchHeaders(accessToken, resolvedProjectId), signal }); const payload: unknown = await response.json().catch(() => null); if (!response.ok) { throw new Error(getServerError(payload, response.status, "GIS 台账属性查询")); } const result = elementGisPropertiesSchema.parse(payload); if (result.element_type !== elementType || result.element_id !== elementId) { throw new Error("GIS 台账属性与所选元素不一致"); } return result.groups; } export async function fetchSourceGisFeatureProperties( layerKey: string, sourceFid: number, projectId: string | null, getAccessToken?: WorkbenchAccess, signal?: AbortSignal ): Promise { const ownedProject = projectId ? null : await findOwnedWorkbenchProject(getAccessToken, signal); const accessToken = ownedProject?.accessToken ?? await getWorkbenchAccessToken(getAccessToken); const resolvedProjectId = projectId ?? ownedProject?.project.project_id; if (!resolvedProjectId) throw new Error("未找到 GIS 台账所属项目"); const path = [ "/api/v1/source-gis/layers", encodeURIComponent(layerKey), "features", encodeURIComponent(String(sourceFid)), "properties" ].join("/"); const response = await fetch(`${SERVER_API_BASE_URL}${path}`, { headers: createWorkbenchHeaders(accessToken, resolvedProjectId), signal }); const payload: unknown = await response.json().catch(() => null); if (!response.ok) { throw new Error(getServerError(payload, response.status, "源 GIS 属性查询")); } const result = sourceGisFeaturePropertiesSchema.parse(payload); if (result.layer_key !== layerKey || result.source_fid !== sourceFid) { throw new Error("源 GIS 属性与所选要素不一致"); } return [result.group]; }