|
|
|
|
@ -1,18 +1,12 @@ |
|
|
|
|
import { hongyuanTownshipDisplayOrder } from "@/config/townshipBoundaries" |
|
|
|
|
import { NATIONAL_PARK_ZONING_COLORS } from "@/config/layers" |
|
|
|
|
import { ecologicalProtectionMenu } from "@/config/ecologicalProtection" |
|
|
|
|
|
|
|
|
|
export { ecologicalProtectionMenu } |
|
|
|
|
|
|
|
|
|
const toWanMu = (mu) => Number((Number(mu || 0) / 10000).toFixed(2)) |
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
|
// The legacy scope builder is retained solely for the natural-protected-area view.
|
|
|
|
|
// Ecological redline data is an API-owned ready-to-render model below.
|
|
|
|
|
export const ecologicalScopeViewKeys = ["natural-protected-area"] |
|
|
|
|
|
|
|
|
|
const ecologicalRedlinePanelDefinitions = { |
|
|
|
|
const ecologicalDirectPanelDefinitions = { |
|
|
|
|
left: [ |
|
|
|
|
{ key: "grasslandDistribution", title: "草地分布", type: "column", variant: "plain", labelMode: "horizontal", scrollable: false, span: 1 }, |
|
|
|
|
{ key: "wetlandDistribution", title: "湿地面积", type: "rank", labelMode: "horizontal", scrollable: false, span: 1 }, |
|
|
|
|
@ -27,9 +21,9 @@ const ecologicalRedlinePanelDefinitions = { |
|
|
|
|
], |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function buildEcologicalRedlinePanels(panels = {}) { |
|
|
|
|
function buildEcologicalDirectPanels(panels = {}) { |
|
|
|
|
return Object.fromEntries( |
|
|
|
|
Object.entries(ecologicalRedlinePanelDefinitions).map(([side, definitions]) => [ |
|
|
|
|
Object.entries(ecologicalDirectPanelDefinitions).map(([side, definitions]) => [ |
|
|
|
|
side, |
|
|
|
|
definitions.map((definition) => ({ |
|
|
|
|
...definition, |
|
|
|
|
@ -40,180 +34,8 @@ export function buildEcologicalRedlinePanels(panels = {}) { |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function normalizeScopeRows(value = []) { |
|
|
|
|
let rows = value |
|
|
|
|
if (rows && typeof rows === "object" && !Array.isArray(rows) && "value" in rows) { |
|
|
|
|
rows = rows.value |
|
|
|
|
} |
|
|
|
|
if (typeof rows === "string") { |
|
|
|
|
try { |
|
|
|
|
rows = JSON.parse(rows) |
|
|
|
|
} catch { |
|
|
|
|
rows = [] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return (Array.isArray(rows) ? rows : []).map((item, index) => ({ |
|
|
|
|
key: item.key || `${item.name || "统计项"}-${index}`, |
|
|
|
|
name: item.name || `统计项${index + 1}`, |
|
|
|
|
value: round(item.value, 2), |
|
|
|
|
unit: item.unit || "万亩", |
|
|
|
|
color: item.color, |
|
|
|
|
extra: item.extra || "", |
|
|
|
|
icon: item.icon || "", |
|
|
|
|
})) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function scopeMetric(key, name, value, unit, extra = "") { |
|
|
|
|
return { key, name, value: round(value, unit === "%" ? 1 : 2), unit, extra } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function scopePercentExtra(rows = [], total = sumBy(rows, "value")) { |
|
|
|
|
return rows.map((item) => ({ |
|
|
|
|
...item, |
|
|
|
|
extra: item.extra || `占比 ${round((Number(item.value || 0) / Math.max(total, 1)) * 100, 1)}%`, |
|
|
|
|
})) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function sortScopeTownRows(rows = [], sortByValue = false) { |
|
|
|
|
const order = new Map(hongyuanTownshipDisplayOrder.map((name, index) => [name, index])) |
|
|
|
|
return [...rows].sort((a, b) => { |
|
|
|
|
if (sortByValue) return Number(b.value || 0) - Number(a.value || 0) |
|
|
|
|
const left = order.has(a.name) ? order.get(a.name) : 999 |
|
|
|
|
const right = order.has(b.name) ? order.get(b.name) : 999 |
|
|
|
|
return left - right || Number(b.value || 0) - Number(a.value || 0) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function buildScopeTownRows(stats = {}, key, fallbackMetricKey, unit = "万亩", options = {}) { |
|
|
|
|
const rows = sortScopeTownRows(normalizeScopeRows(stats[key]), options.sortByValue === true) |
|
|
|
|
.filter((item) => Number(item.value) > 0) |
|
|
|
|
.map((item) => ({ ...item, unit: item.unit || unit })) |
|
|
|
|
if (rows.length) return options.withPercent === false ? rows : scopePercentExtra(rows) |
|
|
|
|
const fallbackValue = Number(stats[fallbackMetricKey] || 0) |
|
|
|
|
return fallbackValue > 0 ? [scopeMetric(`${key}-total`, "全县合计", fallbackValue, unit)] : [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function buildScopeValueRows(stats = {}, key, fallbackMetricKey, fallbackName, unit = "万亩", limit = 6) { |
|
|
|
|
const rows = normalizeScopeRows(stats[key]) |
|
|
|
|
.filter((item) => Number(item.value) > 0) |
|
|
|
|
.slice(0, limit) |
|
|
|
|
.map((item) => ({ ...item, unit: item.unit || unit })) |
|
|
|
|
if (rows.length) return scopePercentExtra(rows) |
|
|
|
|
const fallbackValue = Number(stats[fallbackMetricKey] || 0) |
|
|
|
|
return fallbackValue > 0 ? [scopeMetric(`${key}-total`, fallbackName, fallbackValue, unit)] : [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function hasMeaningfulRows(panel) { |
|
|
|
|
return (panel?.rows || []).some((item) => Number(item.value) > 0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function resolveEcologicalScopeStats(stats = {}) { |
|
|
|
|
return stats && typeof stats === "object" ? stats : {} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function buildEcologicalScopePanels(stats = {}, viewKey = "natural-protected-area") { |
|
|
|
|
stats = resolveEcologicalScopeStats(stats, viewKey) |
|
|
|
|
const grasslandDistributionRows = buildScopeTownRows(stats, "grasslandDistribution", "grasslandAreaWanMu", "万亩") |
|
|
|
|
const wetlandDistributionRows = buildScopeTownRows(stats, "wetlandDistribution", "wetlandAreaWanMu", "万亩") |
|
|
|
|
const woodlandDistributionRows = buildScopeTownRows(stats, "woodlandDistribution", "woodlandAreaWanMu", "万亩", { |
|
|
|
|
sortByValue: true, |
|
|
|
|
}) |
|
|
|
|
const grasslandProductivityRows = buildScopeTownRows(stats, "grasslandProductivity", "grasslandMuYieldJin", "斤/亩", { |
|
|
|
|
withPercent: false, |
|
|
|
|
}) |
|
|
|
|
const grasslandTypeRows = buildScopeValueRows(stats, "grasslandTypes", "grasslandAreaWanMu", "草地面积") |
|
|
|
|
const grasslandLevelRows = buildScopeValueRows(stats, "grasslandLevels", "grasslandAreaWanMu", "草地面积") |
|
|
|
|
const woodlandQualityRows = buildScopeValueRows(stats, "woodlandQuality", "woodlandAreaWanMu", "林地面积") |
|
|
|
|
const resourceStructureRows = scopePercentExtra([ |
|
|
|
|
{ key: `${viewKey}-scope-grassland`, name: "草地资源", value: Number(stats.grasslandAreaWanMu || 0), unit: "万亩" }, |
|
|
|
|
{ key: `${viewKey}-scope-wetland`, name: "湿地资源", value: Number(stats.wetlandAreaWanMu || 0), unit: "万亩" }, |
|
|
|
|
{ key: `${viewKey}-scope-woodland`, name: "林地资源", value: Number(stats.woodlandAreaWanMu || 0), unit: "万亩" }, |
|
|
|
|
].filter((item) => Number(item.value) > 0)) |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
left: [ |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-grassland-distribution`, |
|
|
|
|
title: "草地分布", |
|
|
|
|
type: "column", |
|
|
|
|
variant: "plain", |
|
|
|
|
labelMode: "horizontal", |
|
|
|
|
scrollable: false, |
|
|
|
|
span: 1, |
|
|
|
|
rows: grasslandDistributionRows, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-wetland-distribution`, |
|
|
|
|
title: "湿地面积", |
|
|
|
|
type: "rank", |
|
|
|
|
labelMode: "horizontal", |
|
|
|
|
scrollable: false, |
|
|
|
|
span: 1, |
|
|
|
|
rows: wetlandDistributionRows, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-woodland-distribution`, |
|
|
|
|
title: "林地分布", |
|
|
|
|
type: "split", |
|
|
|
|
variant: "compact", |
|
|
|
|
span: 1, |
|
|
|
|
rows: woodlandDistributionRows, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-grassland-productivity`, |
|
|
|
|
title: "草地生产力", |
|
|
|
|
type: "line", |
|
|
|
|
fixedType: true, |
|
|
|
|
labelMode: "horizontal", |
|
|
|
|
span: 1, |
|
|
|
|
rows: grasslandProductivityRows, |
|
|
|
|
}, |
|
|
|
|
].filter(hasMeaningfulRows), |
|
|
|
|
right: [ |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-grassland-type`, |
|
|
|
|
title: "草原类型", |
|
|
|
|
type: "donut", |
|
|
|
|
variant: "ring", |
|
|
|
|
span: 1, |
|
|
|
|
rows: grasslandTypeRows, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-grassland-level`, |
|
|
|
|
title: "草原级占比", |
|
|
|
|
type: "rose", |
|
|
|
|
span: 1, |
|
|
|
|
rows: grasslandLevelRows, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-woodland-quality`, |
|
|
|
|
title: "林地质量", |
|
|
|
|
type: "heat", |
|
|
|
|
limit: 6, |
|
|
|
|
span: 1, |
|
|
|
|
rows: woodlandQualityRows, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
key: `${viewKey}-resource-structure`, |
|
|
|
|
title: "林草湿面积构成", |
|
|
|
|
type: "structure", |
|
|
|
|
span: 1, |
|
|
|
|
rows: resourceStructureRows, |
|
|
|
|
}, |
|
|
|
|
].filter(hasMeaningfulRows), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function getEcologicalScopeTopStats(stats = {}, viewKey = "natural-protected-area") { |
|
|
|
|
stats = resolveEcologicalScopeStats(stats, viewKey) |
|
|
|
|
const scopeName = "保护地" |
|
|
|
|
return [ |
|
|
|
|
{ zh: `${scopeName}面积`, value: round(stats.scopeAreaWanMu, 2), unit: "万亩", decimals: 2, icon: "park" }, |
|
|
|
|
{ zh: "草地面积", value: round(stats.grasslandAreaWanMu, 2), unit: "万亩", decimals: 2, icon: "grass-basic" }, |
|
|
|
|
{ zh: "湿地面积", value: round(stats.wetlandAreaWanMu, 2), unit: "万亩", decimals: 2, icon: "wetland" }, |
|
|
|
|
{ zh: "林地面积", value: round(stats.woodlandAreaWanMu, 2), unit: "万亩", decimals: 2, icon: "forest" }, |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
export const buildEcologicalRedlinePanels = buildEcologicalDirectPanels |
|
|
|
|
export const buildNaturalProtectedAreaPanels = buildEcologicalDirectPanels |
|
|
|
|
|
|
|
|
|
function normalizeEcologicalRows(value = []) { |
|
|
|
|
let rows = value |
|
|
|
|
@ -557,7 +379,7 @@ export const centerMapNotes = { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function getEcologicalMapRanking(viewKey, stats = {}) { |
|
|
|
|
if (viewKey === "ecological-redline" || ecologicalScopeViewKeys.includes(viewKey)) { |
|
|
|
|
if (["ecological-redline", "natural-protected-area"].includes(viewKey)) { |
|
|
|
|
return { |
|
|
|
|
status: "success", |
|
|
|
|
title: viewKey === "natural-protected-area" ? "自然保护地" : "生态保护红线", |
|
|
|
|
|