diff --git a/src/config/layers.js b/src/config/layers.js index 1e2a8ba..37a2b08 100644 --- a/src/config/layers.js +++ b/src/config/layers.js @@ -475,10 +475,8 @@ export const ecologicalProtectionLayers = { { key: "national-park-zoning", name: "国家公园分区", - sourceType: "wkt-api", - apiUrl: "/openApi/gjgy/gjgy_area", - clipToHongyuanBoundary: true, - clipToLayerBounds: false, + layerName: "ne:resource_national_park_boundary_render", + sourceType: "wfs", opacity: 0.9, mapView: { targetLngLat: [102.88, 33.15], @@ -492,7 +490,7 @@ export const ecologicalProtectionLayers = { name: "核心保护区", fillColor: NATIONAL_PARK_ZONING_COLORS.core.fillColor, strokeColor: NATIONAL_PARK_ZONING_COLORS.core.strokeColor, - match: { field: "type", value: "核心保护区" }, + match: { field: "zone_type", value: "核心保护区" }, selectable: false, }, { @@ -500,7 +498,7 @@ export const ecologicalProtectionLayers = { name: "一般控制区", fillColor: NATIONAL_PARK_ZONING_COLORS.general.fillColor, strokeColor: NATIONAL_PARK_ZONING_COLORS.general.strokeColor, - match: { field: "type", value: "一般控制区" }, + match: { field: "zone_type", value: "一般控制区" }, selectable: false, }, ], diff --git a/src/services/ecologicalProtectionApi.js b/src/services/ecologicalProtectionApi.js index e7f816f..31ece36 100644 --- a/src/services/ecologicalProtectionApi.js +++ b/src/services/ecologicalProtectionApi.js @@ -42,31 +42,49 @@ async function getEcologicalDirectStats(path, featureName) { return stats } -const NATIONAL_PARK_JSON_FIELDS = [ +const NATIONAL_PARK_ROW_SECTIONS = [ "topRows", "coreRows", "townRows", - "townResourceRows", - "townProductivityRows", - "townYakRows", - "townHouseholdRows", - "townJobRows", - "zoningRows", - "balanceGaugeRows", "villageRows", ] +const NATIONAL_PARK_PANEL_KEYS = [ + "townResource", + "townProductivity", + "townYak", + "townHousehold", + "townJob", + "zoning", + "balance", +] + export async function getNationalParkStats() { const response = await requestJson( - `${OPEN_API_BASE}/screen2/ecologicalProtection/nationalPark`, + `${OPEN_API_BASE}/screen2/ecology/national-park`, { method: "GET", timeout: 30000 }, ) - const data = normalizeResponseData(response) - const stats = Array.isArray(data) ? data[0] || {} : data || {} - return NATIONAL_PARK_JSON_FIELDS.reduce((result, key) => { - result[key] = parseJsonbRows(stats[key]) - return result - }, { ...stats }) + const payload = normalizeResponseData(response) + const stats = payload || {} + + if (Array.isArray(stats) || !stats || typeof stats !== "object" || !["ready", "empty"].includes(stats.dataState)) { + throw new Error("国家公园接口返回的数据状态无效。") + } + NATIONAL_PARK_ROW_SECTIONS.forEach((key) => assertDirectRows(stats[key], `国家公园 ${key}`)) + if (!stats.panels || typeof stats.panels !== "object") { + throw new Error("国家公园接口未返回完整展示面板。") + } + NATIONAL_PARK_PANEL_KEYS.forEach((key) => assertDirectRows(stats.panels[key], `国家公园 ${key} 面板`)) + return stats +} + +function assertDirectRows(section, label) { + if (!section || typeof section !== "object" || !Array.isArray(section.rows) || !Number.isInteger(section.count)) { + throw new Error(`${label}未返回合法的 rows/count 展示模型。`) + } + if (section.count !== section.rows.length) { + throw new Error(`${label}的 count 与 rows 数量不一致。`) + } } const RESTORATION_JSON_FIELDS = [ diff --git a/src/views/ecologicalProtection/data.js b/src/views/ecologicalProtection/data.js index 921e69f..1889810 100644 --- a/src/views/ecologicalProtection/data.js +++ b/src/views/ecologicalProtection/data.js @@ -1,9 +1,7 @@ -import { NATIONAL_PARK_ZONING_COLORS } from "@/config/layers" import { ecologicalProtectionMenu } from "@/config/ecologicalProtection" export { ecologicalProtectionMenu } -const sumBy = (rows, field) => rows.reduce((sum, item) => sum + Number(item[field] || 0), 0) const round = (value, digits = 2) => Number(Number(value || 0).toFixed(digits)) const ecologicalDirectPanelDefinitions = { @@ -57,60 +55,27 @@ function normalizeEcologicalRows(value = []) { })) } +function getNationalParkRows(stats, key) { + return stats?.[key]?.rows || [] +} + +function getNationalParkPanelRows(stats, key) { + return stats?.panels?.[key]?.rows || [] +} + export function getNationalParkTopStats(stats = {}) { - return normalizeEcologicalRows(stats.topRows).map((item, index) => ({ - icon: item.icon || (index % 2 === 0 ? "xiaoshoujine" : "zongxiaoliang"), - en: "", - ...item, - })) + return getNationalParkRows(stats, "topRows") } export function getNationalParkCoreRows(stats = {}) { - return normalizeEcologicalRows(stats.coreRows) + return getNationalParkRows(stats, "coreRows") } export function getNationalParkTownRows(stats = {}) { - return normalizeEcologicalRows(stats.townRows) -} - -function withNationalParkPercentExtra(rows = []) { - const total = sumBy(rows, "value") - return rows.map((item) => ({ - ...item, - extra: item.extra || `占比 ${round((Number(item.value || 0) / Math.max(total, 1)) * 100, 1)}%`, - })) + return getNationalParkRows(stats, "townRows") } export function buildNationalParkPanels(stats = {}) { - const townRows = getNationalParkTownRows(stats) - const townByName = new Map(townRows.map((item) => [item.name, item])) - const areaRows = normalizeEcologicalRows(stats.townResourceRows) - const productivityRows = normalizeEcologicalRows(stats.townProductivityRows) - const yakRows = withNationalParkPercentExtra(normalizeEcologicalRows(stats.townYakRows)) - const zoningRows = withNationalParkPercentExtra( - normalizeEcologicalRows(stats.zoningRows).map((item) => ({ - ...item, - color: item.name === "核心保护区" - ? NATIONAL_PARK_ZONING_COLORS.core.fillColor - : item.name === "一般控制区" - ? NATIONAL_PARK_ZONING_COLORS.general.fillColor - : item.color, - })), - ) - const householdRows = normalizeEcologicalRows(stats.townHouseholdRows).map((item) => ({ - ...item, - extra: `公益岗位 ${Number(item.ecologyJobs || 0)}户`, - })) - const jobColors = { - 麦洼乡: "#24f6c4", - 瓦切镇: "#32dcff", - 色地镇: "#8bf7ff", - } - const jobRows = normalizeEcologicalRows(stats.townJobRows).map((item) => ({ - ...item, - color: jobColors[item.name], - extra: `牧户 ${Number(townByName.get(item.name)?.householdCount || 0)}户`, - })) return { left: [ { @@ -125,7 +90,7 @@ export function buildNationalParkPanels(stats = {}) { { key: "grassland", name: "草地面积", color: "#24f6c4" }, { key: "wetland", name: "湿地面积", color: "#8bf7ff" }, ], - rows: areaRows, + rows: getNationalParkPanelRows(stats, "townResource"), }, { key: "park-town-productivity", @@ -135,21 +100,21 @@ export function buildNationalParkPanels(stats = {}) { labelMode: "horizontal", showValues: true, unit: "万吨", - rows: productivityRows, + rows: getNationalParkPanelRows(stats, "townProductivity"), }, { key: "park-town-yak", title: "乡镇牦牛数量", type: "bar", variant: "parkBar", - rows: yakRows, + rows: getNationalParkPanelRows(stats, "townYak"), }, { key: "park-town-balance", title: "草畜平衡超载率", type: "balanceGaugeGrid", limit: 1, - rows: normalizeEcologicalRows(stats.balanceGaugeRows), + rows: getNationalParkPanelRows(stats, "balance"), }, ], right: [ @@ -157,20 +122,20 @@ export function buildNationalParkPanels(stats = {}) { key: "park-zoning-structure", title: "红原管控区结构", type: "donut", - rows: zoningRows, + rows: getNationalParkPanelRows(stats, "zoning"), }, { key: "park-town-household", title: "牧户分布", type: "split", - rows: householdRows, + rows: getNationalParkPanelRows(stats, "townHousehold"), }, { key: "park-town-ecology-job", title: "公益岗位分布", type: "column", variant: "plain", - rows: jobRows, + rows: getNationalParkPanelRows(stats, "townJob"), }, ], } @@ -420,7 +385,7 @@ export function getEcologicalMapRanking(viewKey, stats = {}) { unit: "万亩", rows: getNationalParkTownRows(stats).map((item) => ({ name: item.name, - value: round(item.protectedAreaWanMu, 2), + value: item.value, unit: "万亩", metricLabel: "划定面积", })), diff --git a/src/views/ecologicalProtection/index.vue b/src/views/ecologicalProtection/index.vue index 4a32d85..7b3c0bd 100644 --- a/src/views/ecologicalProtection/index.vue +++ b/src/views/ecologicalProtection/index.vue @@ -15,13 +15,13 @@
- {{ ecologicalDirectDataStateMessage }} + {{ ecologicalDataStateMessage }}
@@ -229,7 +229,8 @@ const redlinePanels = ref(buildEcologicalRedlinePanels()) const redlineDataState = ref("loading") const naturalProtectedAreaPanels = ref(buildNaturalProtectedAreaPanels()) const naturalProtectedAreaDataState = ref("loading") -const nationalParkPanels = ref(buildNationalParkPanels({})) +const nationalParkPanels = ref(buildNationalParkPanels()) +const nationalParkDataState = ref("loading") const restorationPanels = ref(buildEcologicalRestorationPanels({})) const selectedFeature = ref(null) const featureEmpty = ref(false) @@ -297,15 +298,24 @@ const isEngineeringAnalysis = computed(() => currentView.value === "engineering- const isProjectDetailView = computed(() => ["restoration", "engineering-analysis"].includes(currentView.value)) const isEcologicalRedlineView = computed(() => currentView.value === "ecological-redline") const isNaturalProtectedAreaView = computed(() => currentView.value === "natural-protected-area") -const isEcologicalDirectView = computed(() => isEcologicalRedlineView.value || isNaturalProtectedAreaView.value) -const ecologicalDirectDataState = computed(() => ( - isEcologicalRedlineView.value ? redlineDataState.value : naturalProtectedAreaDataState.value +const isNationalParkView = computed(() => currentView.value === "national-park") +const isEcologicalDataView = computed(() => ( + isEcologicalRedlineView.value || isNaturalProtectedAreaView.value || isNationalParkView.value )) -const ecologicalDirectDataStateMessage = computed(() => { - const featureName = isEcologicalRedlineView.value ? "生态保护红线" : "自然保护地" - if (ecologicalDirectDataState.value === "loading") return `${featureName}数据加载中` - if (ecologicalDirectDataState.value === "empty") return `${featureName}暂无数据` - if (ecologicalDirectDataState.value === "error") return `${featureName}数据加载失败,请稍后重试` +const ecologicalDataState = computed(() => { + if (isEcologicalRedlineView.value) return redlineDataState.value + if (isNaturalProtectedAreaView.value) return naturalProtectedAreaDataState.value + return nationalParkDataState.value +}) +const ecologicalDataStateMessage = computed(() => { + const featureName = isEcologicalRedlineView.value + ? "生态保护红线" + : isNaturalProtectedAreaView.value + ? "自然保护地" + : "国家公园" + if (ecologicalDataState.value === "loading") return `${featureName}数据加载中` + if (ecologicalDataState.value === "empty") return `${featureName}暂无数据` + if (ecologicalDataState.value === "error") return `${featureName}数据加载失败,请稍后重试` return "" }) const baseActiveLayer = computed(() => activeLayerKey.value ? getLayerByKey(currentView.value, activeLayerKey.value) : null) @@ -440,7 +450,8 @@ function syncView(viewKey) { naturalProtectedAreaPanels.value = buildNaturalProtectedAreaPanels() } if (isNationalPark) { - nationalParkPanels.value = buildNationalParkPanels({}) + nationalParkDataState.value = "loading" + nationalParkPanels.value = buildNationalParkPanels() } if (isRestoration) { restorationPanels.value = buildEcologicalRestorationPanels({}) @@ -455,7 +466,7 @@ function syncView(viewKey) { items: isRedline || isNaturalProtectedArea ? [] : isNationalPark - ? getNationalParkTopStats({}) + ? [] : isRestoration ? getEcologicalRestorationTopStats({}) : [], @@ -527,10 +538,13 @@ async function loadEcologicalRestorationStats(viewKey) { async function loadNationalParkStats(viewKey) { const requestId = ++nationalParkStatsRequestId + nationalParkDataState.value = "loading" + nationalParkPanels.value = buildNationalParkPanels() try { const stats = nationalParkStatsCache || await getNationalParkStats() if (requestId !== nationalParkStatsRequestId || currentView.value !== viewKey) return nationalParkStatsCache = stats + nationalParkDataState.value = stats.dataState nationalParkPanels.value = buildNationalParkPanels(stats) mapRanking.value = getEcologicalMapRanking(viewKey, stats) emit("module-stats", { @@ -540,12 +554,13 @@ async function loadNationalParkStats(viewKey) { }) } catch (error) { if (requestId !== nationalParkStatsRequestId || currentView.value !== viewKey) return - nationalParkPanels.value = buildNationalParkPanels({}) + nationalParkDataState.value = "error" + nationalParkPanels.value = buildNationalParkPanels() mapRanking.value = getEcologicalMapRanking(viewKey) emit("module-stats", { moduleKey: "ecological-protection", viewKey, - items: getNationalParkTopStats({}), + items: [], }) layerStatus.value = { type: "error", diff --git a/src/views/forestGrassWet/components/FeatureInfoPanel.vue b/src/views/forestGrassWet/components/FeatureInfoPanel.vue index 4a8f7a0..f0b6151 100644 --- a/src/views/forestGrassWet/components/FeatureInfoPanel.vue +++ b/src/views/forestGrassWet/components/FeatureInfoPanel.vue @@ -734,6 +734,8 @@ const isEcologicalScopeFeature = computed(() => { || signal.includes("ecological_redline") || signal.includes("natural-protected-area") || signal.includes("natural_protected_area") + || signal.includes("national-park") + || signal.includes("resource_national_park_boundary") }) const isEcologicalRedlineFeature = computed(() => { @@ -748,8 +750,14 @@ const isNaturalProtectedAreaFeature = computed(() => { return layerKey.includes("natural-protected-area") || layerName.includes("resource_natural_protected_area_boundary") }) +const isNationalParkFeature = computed(() => { + const layerKey = String(props.feature?.layer?.key || "").toLowerCase() + const layerName = String(props.feature?.layer?.layerName || "").toLowerCase() + return layerKey.includes("national-park") || layerName.includes("resource_national_park_boundary") +}) + const isServerOwnedEcologicalAreaFeature = computed(() => ( - isEcologicalRedlineFeature.value || isNaturalProtectedAreaFeature.value + isEcologicalRedlineFeature.value || isNaturalProtectedAreaFeature.value || isNationalParkFeature.value )) const isEngineeringSuitabilityFeature = computed(() => { @@ -779,7 +787,11 @@ const displayRows = computed(() => { const businessFields = useCompactGrassland ? grasslandCoreBusinessFields : topicBusinessFields[activeTopicKey.value] || topicBusinessFields.woodland businessFields.forEach(([keys, label]) => { const ecologicalArea = isServerOwnedEcologicalAreaFeature.value && String(label) === "图斑面积" - const areaKeys = isEcologicalRedlineFeature.value ? ["area_mu"] : ["area_source_mj"] + const areaKeys = isEcologicalRedlineFeature.value + ? ["area_mu"] + : isNationalParkFeature.value + ? ["total_acreage"] + : ["area_source_mj"] const row = buildRow( propsData.value, ecologicalArea ? areaKeys : keys, @@ -792,7 +804,11 @@ const displayRows = computed(() => { } }) if (isServerOwnedEcologicalAreaFeature.value && !rows.some((row) => row.label === "图斑面积")) { - const sourceKey = isEcologicalRedlineFeature.value ? "area_mu" : "area_source_mj" + const sourceKey = isEcologicalRedlineFeature.value + ? "area_mu" + : isNationalParkFeature.value + ? "total_acreage" + : "area_source_mj" addStaticRow(rows, used, "图斑面积", "数据缺失", `${sourceKey}-missing`) } const areaLabel = getTopicAreaLabel(activeTopicKey.value) @@ -996,6 +1012,7 @@ function formatValue(key, value) { if (number === 0 && zeroHiddenKeys.has(key)) return "" const formatted = Number.isInteger(number) ? `${number}` : `${Number(number.toFixed(2))}` if (key === "area_source_mj") return formatted + if (key === "total_acreage") return `${formatAreaWanMu(number)} 万亩` if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && ["area_mu", "acreage", "mj"].includes(key)) return `${formatAreaWanMu(number)} 万亩` if (activeTopicKey.value === "ecological" && isEcologicalScopeFeature.value && ["area_m2", "original_area_m2", "adjusted_area_m2"].includes(key)) return `${formatAreaWanMu(number / 666.6667)} 万亩` if (["xbmj", "tbdlmj", "geomj", "area_square_meter"].includes(key)) return `${formatAreaMu(number / 666.6667)} 亩`