diff --git a/src/services/openApi.js b/src/services/openApi.js
index 412bac1..b69ee63 100644
--- a/src/services/openApi.js
+++ b/src/services/openApi.js
@@ -5,7 +5,15 @@ import { getHongyuanTownshipNameByCode, hongyuanTownshipDisplayOrder } from "@/c
const OPEN_API_BASE = import.meta.env.VITE_OPEN_API_BASE || "/openApi"
const CXPT_GRASSLAND_AREA_WAN_MU = 756.37
+const CXPT_GRASSLAND_YIELD_WAN_TON = 297.29
const CXPT_WETLAND_AREA_WAN_MU = 291.82
+const CXPT_WETLAND_EDIBLE_YIELD_WAN_TON = 16.47
+const GRASSLAND_DEGRADATION_SOURCE_ROWS = [
+ { key: "none", name: "未退化", value: 481.8, unit: "万亩" },
+ { key: "light", name: "轻度退化", value: 231.25, unit: "万亩" },
+ { key: "middle", name: "中度退化", value: 23.7, unit: "万亩" },
+ { key: "heavy", name: "重度退化", value: 20.21, unit: "万亩" },
+]
const HONGYUAN_TOWN_DISPLAY_ORDER = hongyuanTownshipDisplayOrder.map(normalizeTownName)
const WETLAND_CHART_PALETTE = ["#32DCFF", "#24F6C4", "#6EA8FF", "#FFE06B", "#7EE269", "#9EA7FF"]
const WOODLAND_CHART_PALETTE = ["#2BD17E", "#8AE611", "#24F6C4", "#FFE06B", "#32DCFF", "#9EA7FF"]
@@ -101,6 +109,20 @@ function normalizeDataItems(data) {
return rows.map(normalizeMetricItem).filter((item) => item.name)
}
+function normalizeGrasslandDataItems(data) {
+ return normalizeDataItems(data).map((item) => {
+ const signal = `${item?.key || ""}${item?.name || ""}`
+ if (/gransLand_produce|grassland_grass_yield|草地生产力|草地鲜草产量|鲜草产量/.test(signal)) {
+ return {
+ ...item,
+ value: CXPT_GRASSLAND_YIELD_WAN_TON,
+ unit: "万吨",
+ }
+ }
+ return item
+ })
+}
+
function normalizeWoodlandDistribution(data) {
const core = data?.core || data?.summary || data || {}
const mainType = (data?.typeDistribution || data?.typeStats || [])[0] || {}
@@ -720,6 +742,48 @@ function withPercentExtra(rows = [], total = sumRowValues(rows), label = "") {
})
}
+function appendRemainderRowToTotal(rows = [], total, options = {}) {
+ const {
+ key = "unclassified",
+ name = "未分类",
+ unit = "万亩",
+ sort = true,
+ } = options
+ const target = toNumber(total)
+ if (target === undefined) return rows
+ const currentTotal = sumRowValues(rows)
+ const remainder = formatNumber(target - currentTotal)
+ if (remainder === undefined || remainder <= 0.01) return rows
+ const result = [
+ ...rows,
+ {
+ key,
+ name,
+ value: remainder,
+ unit,
+ },
+ ]
+ return sort ? result.sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0)) : result
+}
+
+function scaleRowsToTotal(rows = [], total) {
+ const target = toNumber(total)
+ const sourceTotal = sumRowValues(rows)
+ if (target === undefined || !sourceTotal) return rows
+ let assignedTotal = 0
+ return rows.map((row, index) => {
+ const isLast = index === rows.length - 1
+ const value = isLast
+ ? formatNumber(Math.max(target - assignedTotal, 0))
+ : formatNumber(((Number(row.value) || 0) / sourceTotal) * target)
+ assignedTotal += Number(value) || 0
+ return {
+ ...row,
+ value,
+ }
+ })
+}
+
function colorRowsByPalette(rows = [], palette = []) {
if (!palette.length) return rows
return rows.map((row, index) => ({
@@ -953,6 +1017,22 @@ function buildWetlandYieldCapacityRows(yieldSummary = {}, yieldRows = []) {
].filter((row) => row.value !== undefined)
}
+function normalizeWetlandYieldSummary(yieldSummary = {}, edibleYieldRows = [], yieldRows = []) {
+ const unit = yieldSummary?.unit || pickValue(edibleYieldRows?.[0], ["unit"]) || pickValue(yieldRows?.[0], ["unit"]) || "万吨"
+ const total = toNumber(yieldSummary?.xc) ?? sumRowValues(yieldRows)
+ const summaryEdible = toNumber(yieldSummary?.edibleXc)
+ const distributionEdible = sumRowValues(edibleYieldRows)
+ const edible = summaryEdible !== undefined || distributionEdible > 0
+ ? Math.max(summaryEdible || 0, distributionEdible || 0)
+ : undefined
+ return {
+ ...yieldSummary,
+ xc: formatNumber(total),
+ edibleXc: formatNumber(edible),
+ unit,
+ }
+}
+
function buildWetlandDefaultPanels({
yieldSummary,
townAreaRows,
@@ -976,10 +1056,10 @@ function buildWetlandDefaultPanels({
const edibleYieldPanel = buildPanel("wetland-edible-yield", "可食鲜草产量", topRows(edibleYieldRows, hongyuanTownshipDisplayOrder.length), "column", { span: 1, variant: "slim" })
const muYieldPanel = buildPanel("wetland-mu-yield", "湿地亩产效率", topRows(muYieldRows, hongyuanTownshipDisplayOrder.length), "heat", { span: 1, limit: hongyuanTownshipDisplayOrder.length })
const levelPanel = buildPanel("wetland-level", "湿地级别面积", levelRows, "column", { span: 1 })
- const levelTownPanel = buildPanel("wetland-level-town", "1级湿地分布", topRows(levelTownRows, 6), "bar", { span: 1, variant: "distribution" })
- const typeTownPanel = buildPanel("wetland-type-town", "湿地类型乡镇分布", topRows(typeTownRows, 6), "bar", { span: 1, variant: "distribution" })
+ const levelTownPanel = buildPanel("wetland-level-town", "1级湿地分布", topRows(levelTownRows, hongyuanTownshipDisplayOrder.length), "bar", { span: 1, variant: "distribution" })
+ const typeTownPanel = buildPanel("wetland-type-town", "湿地类型乡镇分布", topRows(typeTownRows, hongyuanTownshipDisplayOrder.length), "bar", { span: 1, variant: "distribution" })
const edibleMuPanel = buildPanel("wetland-edible-mu", "可食鲜草亩产", topRows(edibleMuRows, hongyuanTownshipDisplayOrder.length), "bar", { span: 1 })
- const minTownPanel = buildPanel("wetland-min-town", "优势小类乡镇分布", topRows(minTownRows, 6), "bar", { span: 1, variant: "distribution" })
+ const minTownPanel = buildPanel("wetland-min-town", "优势小类乡镇分布", topRows(minTownRows, hongyuanTownshipDisplayOrder.length), "bar", { span: 1, variant: "distribution" })
const leftPanels = diversifyPanelColumn([
townAreaPanel,
typePanel,
@@ -1198,57 +1278,27 @@ async function getGrasslandDegradationRows() {
return (await getGrasslandDegradationStats()).levelRows
}
-async function getGrasslandDegradationStats() {
- const data = await getWfsFeatureProperties("ne:t_grassland_result_caodi", {
- propertyName: ["area_code", "degradation_level", "acreage"],
- maxFeatures: 200000,
- timeout: 25000,
- })
- const levelGrouped = new Map()
- const townGrouped = new Map()
- ;(data?.features || []).forEach((feature) => {
- const properties = feature.properties || {}
- const name = normalizeGrasslandDegradationName(properties.degradation_level)
- const acreage = toNumber(properties.acreage)
- if (!name || acreage === undefined) return
- const areaWanMu = acreage / 10000
- levelGrouped.set(name, (levelGrouped.get(name) || 0) + areaWanMu)
- if (name === "未退化") return
- const townCode = String(properties.area_code || "").slice(0, 9)
- const townName = getHongyuanTownshipNameByCode(townCode)
- if (!townName) return
- townGrouped.set(townName, (townGrouped.get(townName) || 0) + areaWanMu)
- })
- const levelOrder = ["未退化", "轻度退化", "中度退化", "重度退化", "未分级"]
- const levelRows = Array.from(levelGrouped.entries())
- .map(([name, value]) => ({
- key: name,
- name,
- value: formatNumber(value),
- unit: "万亩",
- }))
- .filter((row) => Number(row.value) > 0)
- .sort((a, b) => {
- const orderA = levelOrder.indexOf(a.name)
- const orderB = levelOrder.indexOf(b.name)
- if (orderA !== -1 || orderB !== -1) {
- return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB)
- }
- return (Number(b.value) || 0) - (Number(a.value) || 0)
- })
- const townRows = Array.from(townGrouped.entries())
- .map(([name, value]) => ({
- key: name,
- name,
- value: formatNumber(value),
- unit: "万亩",
- }))
- .filter((row) => Number(row.value) > 0)
- .sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0))
- return {
- levelRows: withPercentExtra(levelRows),
- townRows,
+function buildGrasslandDegradationLevelRows(rows = GRASSLAND_DEGRADATION_SOURCE_ROWS) {
+ const levelRows = rows.map((row) => ({
+ ...row,
+ value: formatNumber(row.value),
+ unit: row.unit || "万亩",
+ }))
+ const totalDiff = formatNumber(CXPT_GRASSLAND_AREA_WAN_MU - sumRowValues(levelRows))
+ const noneRow = levelRows.find((row) => row.name === "未退化")
+ if (noneRow && totalDiff !== undefined && Math.abs(totalDiff) > 0.01) {
+ noneRow.value = formatNumber(Math.max((Number(noneRow.value) || 0) + totalDiff, 0))
}
+ const levelOrder = ["未退化", "轻度退化", "中度退化", "重度退化"]
+ return withPercentExtra(levelRows.sort((a, b) => {
+ const orderA = levelOrder.indexOf(a.name)
+ const orderB = levelOrder.indexOf(b.name)
+ return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB)
+ }), CXPT_GRASSLAND_AREA_WAN_MU)
+}
+
+async function getGrasslandDegradationStats() {
+ return fallbackGrasslandDegradationStats()
}
function findGrasslandCoreRow(rows = [], keywords = []) {
@@ -1325,10 +1375,18 @@ function buildGrasslandTopRows(core = [], yieldSummary = {}, yieldRows = []) {
].filter((row) => row?.value !== undefined)
}
+function normalizeGrasslandYieldSummary(yieldSummary = {}, yieldRows = []) {
+ return {
+ ...yieldSummary,
+ xc: CXPT_GRASSLAND_YIELD_WAN_TON,
+ unit: yieldSummary?.unit || pickValue(yieldRows?.[0], ["unit"]) || "万吨",
+ }
+}
+
function buildGrasslandDegradationDistributionRows(rows = []) {
const total = sumRowValues(rows)
return rows
- .filter((row) => row.name && row.name !== "未退化" && Number(row.value) > 0)
+ .filter((row) => row.name && Number(row.value) > 0)
.map((row) => ({
...row,
color: getGrasslandDegradationColor(row.name),
@@ -1339,8 +1397,9 @@ function buildGrasslandDegradationDistributionRows(rows = []) {
function getGrasslandDegradationColor(name = "") {
const text = String(name)
+ if (text.includes("未退化")) return "#32DCFF"
if (text.includes("重度")) return "#FFE06B"
- if (text.includes("中度")) return "#32DCFF"
+ if (text.includes("中度")) return "#6EA8FF"
return "#24F6C4"
}
@@ -1412,12 +1471,7 @@ function fallbackGrasslandDegradationRows() {
}
function fallbackGrasslandDegradationStats() {
- const levelRows = withPercentExtra([
- { key: "none", name: "未退化", value: 642.42, unit: "万亩" },
- { key: "light", name: "轻度退化", value: 83.95, unit: "万亩" },
- { key: "middle", name: "中度退化", value: 17.49, unit: "万亩" },
- { key: "heavy", name: "重度退化", value: 13.09, unit: "万亩" },
- ])
+ const levelRows = buildGrasslandDegradationLevelRows()
const townRows = [
{ key: "qiongxi", name: "邛溪镇", value: 14.72, unit: "万亩" },
{ key: "sedi", name: "色地镇", value: 13.86, unit: "万亩" },
@@ -1528,7 +1582,7 @@ function buildFallbackTopicStats(topicKey, layerKey) {
{ key: "unhealthy", name: "不健康", value: 20.27, unit: "万亩" },
])
const forageRows = buildGrasslandForageCapacityRows({
- xc: 297.29,
+ xc: CXPT_GRASSLAND_YIELD_WAN_TON,
edibleXc: 192.26,
unit: "万吨",
}, yieldRows)
@@ -1558,7 +1612,7 @@ function buildFallbackTopicStats(topicKey, layerKey) {
const result = {
status: "fallback",
...grasslandPanels,
- top: buildGrasslandTopRows(core, { xc: 297.29, unit: "万吨" }, yieldRows),
+ top: buildGrasslandTopRows(core, { xc: CXPT_GRASSLAND_YIELD_WAN_TON, unit: "万吨" }, yieldRows),
}
return applyLayerPanelSelection(result, topicKey, layerKey)
}
@@ -1569,18 +1623,24 @@ function buildFallbackTopicStats(topicKey, layerKey) {
{ key: "wetland-yield", name: "湿地生产力(鲜草)", value: 108.84, unit: "万吨" },
{ key: "wetland-level-1-area", name: "1级湿地面积", value: 213.22, unit: "万亩" },
], topicKey)
- const typeRows = aggregateTailRows([
+ const typeRows = appendRemainderRowToTotal(aggregateTailRows([
{ name: "高寒草甸类", value: 190.32, unit: "万亩" },
{ name: "山地草甸类", value: 49.48, unit: "万亩" },
{ name: "低地草甸类", value: 3.21, unit: "万亩" },
- ], { limit: 4, minPercent: 1, otherName: "其他湿地类型" })
- const levelRows = [
+ ], { limit: 4, minPercent: 1, otherName: "其他湿地类型" }), CXPT_WETLAND_AREA_WAN_MU, {
+ key: "wetland-type-unclassified",
+ name: "未分类湿地",
+ })
+ const levelRows = appendRemainderRowToTotal([
{ name: "1级湿地", value: 213.22, unit: "万亩" },
{ name: "2级湿地", value: 22.99, unit: "万亩" },
{ name: "3级湿地", value: 6.42, unit: "万亩" },
{ name: "4级湿地", value: 0.36, unit: "万亩" },
{ name: "5级湿地", value: 0.02, unit: "万亩" },
- ]
+ ], CXPT_WETLAND_AREA_WAN_MU, {
+ key: "wetland-level-unclassified",
+ name: "未分级湿地",
+ })
const yieldRows = normalizeRankRows([
{ areaName: "色地镇", yield: 21.32 },
{ areaName: "瓦切镇", yield: 17.49 },
@@ -1605,20 +1665,23 @@ function buildFallbackTopicStats(topicKey, layerKey) {
{ name: "江茸乡", value: 827.92, unit: "斤" },
{ name: "龙日镇", value: 825.59, unit: "斤" },
]
- const townAreaRows = [
+ const townAreaRows = scaleRowsToTotal([
{ name: "瓦切镇", value: 36.38, unit: "万亩" },
{ name: "邛溪镇", value: 29.29, unit: "万亩" },
{ name: "阿木乡", value: 25.26, unit: "万亩" },
{ name: "龙日镇", value: 24.59, unit: "万亩" },
{ name: "色地镇", value: 24.62, unit: "万亩" },
- ].sort((a, b) => b.value - a.value)
- const minTypeRows = [
+ ].sort((a, b) => b.value - a.value), CXPT_WETLAND_AREA_WAN_MU)
+ const minTypeRows = appendRemainderRowToTotal([
{ name: "矮生嵩草、杂类草型", value: 108.35, unit: "万亩" },
{ name: "高山嵩草、矮生嵩草型", value: 34.36, unit: "万亩" },
{ name: "西藏嵩草、杂类草型", value: 32.37, unit: "万亩" },
{ name: "早熟禾、杂类草型", value: 20.99, unit: "万亩" },
{ name: "苔草、杂类草型", value: 14.63, unit: "万亩" },
- ]
+ ], CXPT_WETLAND_AREA_WAN_MU, {
+ key: "wetland-min-type-unclassified",
+ name: "未分类湿地",
+ })
const edibleYieldRows = normalizeRankRows([
{ areaName: "色地镇", yield: 2.82 },
{ areaName: "邛溪镇", yield: 2.71 },
@@ -1635,7 +1698,7 @@ function buildFallbackTopicStats(topicKey, layerKey) {
], { nameKeys: ["areaName"], valueKeys: ["yield"], unit: "斤" })
const wetlandPanels = buildWetlandDefaultPanels({
core,
- yieldSummary: { xc: 108.84, edibleXc: 1.65, unit: "万吨" },
+ yieldSummary: { xc: 108.84, edibleXc: CXPT_WETLAND_EDIBLE_YIELD_WAN_TON, unit: "万吨" },
townAreaRows,
typeRows,
levelRows,
@@ -1842,11 +1905,14 @@ function normalizeWetlandBusiness(items, stats = {}) {
const levelDistributionRows = stats.levelDistributionRows || []
const minTypeDistributionRows = stats.minTypeDistributionRows || []
const yieldSummary = stats.yieldSummary || {}
- const wetlandTypeRows = aggregateTailRows(normalizeMetricRows(typeRows, {
+ const wetlandTypeRows = appendRemainderRowToTotal(aggregateTailRows(normalizeMetricRows(typeRows, {
nameKeys: ["name"],
valueKeys: ["value"],
unit: "万亩",
- }), { limit: 4, minPercent: 1, otherName: "其他湿地类型" })
+ }), { limit: 4, minPercent: 1, otherName: "其他湿地类型" }), CXPT_WETLAND_AREA_WAN_MU, {
+ key: "wetland-type-unclassified",
+ name: "未分类湿地",
+ })
const wetlandYieldRows = normalizeYieldRows(yieldRows, stats.muYieldRows || [], { showMuYieldExtra: false })
const wetlandMuYieldRows = normalizeMetricRows(stats.muYieldRows || [], {
nameKeys: ["areaName", "name"],
@@ -1854,12 +1920,18 @@ function normalizeWetlandBusiness(items, stats = {}) {
unit: "斤",
max: hongyuanTownshipDisplayOrder.length,
})
- const wetlandLevelRows = normalizeMetricRows(levelRows, {
+ const wetlandLevelRows = appendRemainderRowToTotal(normalizeMetricRows(levelRows, {
nameKeys: ["name"],
valueKeys: ["value"],
unit: "万亩",
- }).map((row) => ({ ...row, name: normalizeWetlandLevelName(row.name) }))
- const wetlandMinTypeRows = normalizeMuRows(minTypeRows)
+ }).map((row) => ({ ...row, name: normalizeWetlandLevelName(row.name) })), CXPT_WETLAND_AREA_WAN_MU, {
+ key: "wetland-level-unclassified",
+ name: "未分级湿地",
+ })
+ const wetlandMinTypeRows = aggregateTailRows(appendRemainderRowToTotal(normalizeMuRows(minTypeRows), CXPT_WETLAND_AREA_WAN_MU, {
+ key: "wetland-min-type-unclassified",
+ name: "未分类湿地",
+ }), { limit: 6, minPercent: 1, otherName: "其他小类" })
const wetlandEdibleRows = normalizeYieldRows(edibleRows, edibleMuRows, { showMuYieldExtra: false })
const wetlandEdibleMuRows = normalizeMetricRows(edibleMuRows, {
nameKeys: ["areaName", "name"],
@@ -1867,33 +1939,34 @@ function normalizeWetlandBusiness(items, stats = {}) {
unit: pickValue(edibleMuRows?.[0], ["unit"]) || "斤",
max: hongyuanTownshipDisplayOrder.length,
})
- const wetlandTownAreaRows = aggregateAreaWanMuRows(typeDistributionRows, {
+ const wetlandTownAreaRows = scaleRowsToTotal(aggregateAreaWanMuRows(typeDistributionRows, {
nameKeys: ["areaName", "name"],
valueKeys: ["acreage", "areaWanMu", "value"],
- })
- const wetlandTypeTownRows = aggregateAreaWanMuRows(typeDistributionRows, {
+ }), CXPT_WETLAND_AREA_WAN_MU)
+ const wetlandTypeTownRows = scaleRowsToTotal(aggregateAreaWanMuRows(typeDistributionRows, {
nameKeys: ["areaName", "name"],
valueKeys: ["acreage", "areaWanMu", "value"],
extra: (row) => `${row.source?.length || 0}类`,
- })
- const wetlandLevelTownRows = aggregateAreaWanMuRows(levelDistributionRows, {
+ }), CXPT_WETLAND_AREA_WAN_MU)
+ const wetlandLevelTownRows = scaleRowsToTotal(aggregateAreaWanMuRows(levelDistributionRows, {
nameKeys: ["areaName", "name"],
valueKeys: ["acreage", "areaWanMu", "value"],
- })
+ }), CXPT_WETLAND_AREA_WAN_MU)
const wetlandLevelOneTownRows = aggregateAreaWanMuRows(levelDistributionRows, {
nameKeys: ["areaName", "name"],
valueKeys: ["acreage", "areaWanMu", "value"],
filter: (row) => normalizeWetlandLevelName(pickValue(row, ["level", "name"])).startsWith("1级"),
})
- const wetlandMinTypeTownRows = aggregateAreaWanMuRows(minTypeDistributionRows, {
+ const wetlandMinTypeTownRows = scaleRowsToTotal(aggregateAreaWanMuRows(minTypeDistributionRows, {
nameKeys: ["areaName", "name"],
valueKeys: ["acreage", "areaWanMu", "value"],
extra: (row) => `${row.source?.filter((item) => normalizeAreaValueToWanMu(item, ["acreage", "areaWanMu", "value"]) > 0).length || 0}型`,
- })
+ }), CXPT_WETLAND_AREA_WAN_MU)
+ const wetlandYieldSummary = normalizeWetlandYieldSummary(yieldSummary, wetlandEdibleRows, wetlandYieldRows)
const wetlandCore = ensureCoreShape(normalized, "wetland")
const levelOneArea = wetlandLevelRows.find((row) => /^1级/.test(row.name)) || wetlandLevelRows[0]
const wetlandPanels = buildWetlandDefaultPanels({
- yieldSummary,
+ yieldSummary: wetlandYieldSummary,
townAreaRows: wetlandTownAreaRows,
typeRows: wetlandTypeRows,
levelRows: wetlandLevelRows,
@@ -2086,6 +2159,7 @@ export async function getTopicBusinessStats(topicKey, layerKey) {
const degradationRows = degradationStats.levelRows || []
const core = ensureCoreShape(normalizeBasicGrassland(data), topicKey)
const grassYieldRows = normalizeYieldRows(yieldRows || [], muYieldRows || [], { showMuYieldExtra: false })
+ const grassYieldSummary = normalizeGrasslandYieldSummary(yieldSummary, grassYieldRows)
const muYieldRankRows = normalizeRankRows(muYieldRows || [], {
nameKeys: ["areaName", "name"],
valueKeys: ["yield", "value"],
@@ -2122,13 +2196,13 @@ export async function getTopicBusinessStats(topicKey, layerKey) {
healthRows: normalizedHealthRows,
productivityRows: muYieldRankRows,
edibleYieldRows,
- forageRows: buildGrasslandForageCapacityRows(yieldSummary, grassYieldRows),
+ forageRows: buildGrasslandForageCapacityRows(grassYieldSummary, grassYieldRows),
degradationRows,
})
const result = {
status: "success",
...grasslandPanels,
- top: buildGrasslandTopRows(core, yieldSummary, grassYieldRows),
+ top: buildGrasslandTopRows(core, grassYieldSummary, grassYieldRows),
}
return applyLayerPanelSelection(result, topicKey, layerKey)
}
@@ -2212,7 +2286,7 @@ export async function getTopicCoreStats(topicKey) {
async () => normalizeResponseData(await get("/index/dataItem", { group: "grassLand" })),
async () => normalizeResponseData(await get("/index/dataItem", { group: "grassland" })),
])
- const items = Array.isArray(data) ? normalizeDataItems(data) : normalizeBasicGrassland(data)
+ const items = Array.isArray(data) ? normalizeGrasslandDataItems(data) : normalizeBasicGrassland(data)
return {
status: "success",
items: ensureCoreShape(items, topicKey),
diff --git a/src/services/pastureApi.js b/src/services/pastureApi.js
index 20c0688..f037eac 100644
--- a/src/services/pastureApi.js
+++ b/src/services/pastureApi.js
@@ -74,6 +74,12 @@ export function getGrasslandClickInfo(params = {}) {
return get("/api/home/grasslandClickInfo", params, { timeout: 20000 }).then((data) => data?.data || data || null)
}
+export function getYakMarksByGrasslandIds(ids = []) {
+ const grasslandIds = Array.from(new Set((Array.isArray(ids) ? ids : [ids]).map((id) => String(id || "").trim()).filter(Boolean)))
+ if (!grasslandIds.length) return Promise.resolve([])
+ return get("/api/home/yakMarksByGrasslandIds", { ids: grasslandIds.join(",") }, { timeout: 20000 }).then(normalizeRows)
+}
+
export function getPastureDetailsById(id) {
return get("/api/pastureDetailsById", { id }).then((data) => data?.data || data || {})
}
@@ -93,7 +99,7 @@ export async function getPastureProfile() {
getPastureAge(),
getPastureOnline(),
getPastureList(),
- getMuhuList("", 1, 10000),
+ getMuhuList("", 1, 20),
])
const pastureRows = pastureList.status === "fulfilled" ? pastureList.value : []
const muhuRows = muhuList.status === "fulfilled" ? muhuList.value : []
diff --git a/src/services/smartApi.js b/src/services/smartApi.js
index 4c28f91..80c11eb 100644
--- a/src/services/smartApi.js
+++ b/src/services/smartApi.js
@@ -62,7 +62,7 @@ export function askStatQuestion(question, pagePath = window.location.hash || win
pagePath,
pageName,
},
- { timeout: 45000 },
+ { timeout: 90000 },
)
}
diff --git a/src/services/smartChartApi.js b/src/services/smartChartApi.js
index 08f73b3..9d7446f 100644
--- a/src/services/smartChartApi.js
+++ b/src/services/smartChartApi.js
@@ -43,7 +43,7 @@ function normalizeChart(chart, index) {
id: chart?.id || `smart-chart-${index}`,
type: normalizeChartType(chart?.type),
title: chart?.title || `统计图表${index + 1}`,
- subtitle: chart?.subtitle || buildSourceSubtitle(chart?.source),
+ subtitle: cleanChartSubtitle(chart?.subtitle),
unit: chart?.unit || "",
source: chart?.source || {},
rows: rows
@@ -65,10 +65,12 @@ function normalizeChartType(type) {
return "bar"
}
-function buildSourceSubtitle(source = {}) {
- const table = source.table ? `来源:${source.table}` : "来源:openApi 统计库"
- const period = source.period ? `,${source.period}` : ""
- return `${table}${period}`
+function cleanChartSubtitle(value) {
+ const text = String(value || "").trim()
+ if (!text) return ""
+ if (/^(数据)?来源[::]/.test(text)) return ""
+ if (text.includes("固定展示")) return ""
+ return text
}
function toNumber(value) {
diff --git a/src/views/forestGrassWet/components/LayerPanel.vue b/src/views/forestGrassWet/components/LayerPanel.vue
index eac5484..608ab26 100644
--- a/src/views/forestGrassWet/components/LayerPanel.vue
+++ b/src/views/forestGrassWet/components/LayerPanel.vue
@@ -7,8 +7,8 @@
:key="layer.key"
class="layer-panel-item"
:class="{
- 'is-active': layer.key === modelValue,
- 'is-checked': layer.key === modelValue,
+ 'is-active': isLayerActive(layer.key),
+ 'is-checked': isLayerActive(layer.key),
}"
type="button"
@mousedown.prevent
@@ -70,7 +70,7 @@ const props = defineProps({
required: true,
},
modelValue: {
- type: String,
+ type: [String, Array],
default: "",
},
legends: {
@@ -97,12 +97,20 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ multiple: {
+ type: Boolean,
+ default: false,
+ },
})
const emit = defineEmits(["update:modelValue", "update:selectedLegendKey"])
const panelLayers = computed(() => Array.isArray(props.layers) ? props.layers : getLayersByTopic(props.topicKey))
const panelLegends = computed(() => Array.isArray(props.legends) ? props.legends : [])
+const selectedLayerKeys = computed(() => {
+ if (Array.isArray(props.modelValue)) return props.modelValue.map(String).filter(Boolean)
+ return props.modelValue ? [String(props.modelValue)] : []
+})
const legendSelectable = computed(() => panelLegends.value.filter(isSelectableLegend).length > 1)
const legendSingle = computed(() => panelLegends.value.length === 1)
const panelHeight = computed(() => {
@@ -112,9 +120,20 @@ const panelHeight = computed(() => {
function handleLayerClick(event, layerKey) {
event.currentTarget?.blur?.()
+ if (props.multiple) {
+ const nextKeys = new Set(selectedLayerKeys.value)
+ if (nextKeys.has(layerKey)) nextKeys.delete(layerKey)
+ else nextKeys.add(layerKey)
+ emit("update:modelValue", Array.from(nextKeys))
+ return
+ }
emit("update:modelValue", props.modelValue === layerKey ? "" : layerKey)
}
+function isLayerActive(layerKey) {
+ return selectedLayerKeys.value.includes(String(layerKey))
+}
+
function handleLegendClick(event, item) {
event.currentTarget?.blur?.()
if (!isLegendItemSelectable(item)) return
diff --git a/src/views/gdMap/components/ProportionPopulationConsumption.vue b/src/views/gdMap/components/ProportionPopulationConsumption.vue
index 5d606ad..936ebe6 100644
--- a/src/views/gdMap/components/ProportionPopulationConsumption.vue
+++ b/src/views/gdMap/components/ProportionPopulationConsumption.vue
@@ -24,8 +24,17 @@ import mCard from "@/components/mCard/index.vue"
import VChart from "vue-echarts"
import { herdStructureRows } from "../livestockData"
+const pieGradients = [
+ ["rgba(3,65,128,1)", "rgba(115,208,255,1)"],
+ ["rgba(11, 77, 44, 1)", "rgba(77, 255, 181, 1)"],
+ ["rgba(117, 117, 117, 1)", "rgba(230, 230, 230, 1)"],
+ ["rgba(153, 105, 38, 1)", "rgba(255, 200, 89, 1)"],
+ ["rgba(114, 54, 36, 1)", "rgba(255, 122, 69, 1)"],
+ ["rgba(41, 55, 130, 1)", "rgba(126, 158, 255, 1)"],
+]
+
const state = reactive({
- pieDataColor: ["#17E6C3", "#40CFFF", "#1979FF", "#FFC472", "#FF7A45"],
+ pieDataColor: ["#17E6C3", "#40CFFF", "#1979FF", "#FFC472", "#FF7A45", "#8BA8FF"],
pieData: herdStructureRows.map((item) => ({
name: item.name,
value: item.ratio,
@@ -59,65 +68,22 @@ const option = ref({
radius: ["55%", "70%"],
color: ["#c487ee", "#deb140", "#49dff0", "#034079", "#6f81da", "#00ffb4"],
- data: [
- {
- value: herdStructureRows[0].ratio,
- name: herdStructureRows[0].name,
- itemStyle: {
- //颜色渐变
- color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
- { offset: 0, color: "rgba(3,65,128,1)" },
- { offset: 1, color: "rgba(115,208,255,1)" },
- ]),
- },
- },
- {
- value: herdStructureRows[1].ratio,
- name: herdStructureRows[1].name,
- itemStyle: {
- //颜色渐变
- color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
- { offset: 0, color: "rgba(11, 77, 44, 1)" },
- { offset: 1, color: "rgba(77, 255, 181, 1)" },
- ]),
- },
- },
- {
- value: herdStructureRows[2].ratio,
- name: herdStructureRows[2].name,
- itemStyle: {
- //颜色渐变
- color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
- { offset: 0, color: "rgba(117, 117, 117, 1)" },
- { offset: 1, color: "rgba(230, 230, 230, 1)" },
- ]),
- },
- },
- {
- value: herdStructureRows[3].ratio,
- name: herdStructureRows[3].name,
- itemStyle: {
- //颜色渐变
- color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
- { offset: 0, color: "rgba(153, 105, 38, 1)" },
- { offset: 1, color: "rgba(255, 200, 89, 1)" },
- ]),
- },
- },
- {
- value: herdStructureRows[4].ratio,
- name: herdStructureRows[4].name,
+ data: herdStructureRows.map((item, index) => {
+ const colors = pieGradients[index % pieGradients.length]
+ return {
+ value: item.ratio,
+ name: item.name,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
- { offset: 0, color: "rgba(114, 54, 36, 1)" },
- { offset: 1, color: "rgba(255, 122, 69, 1)" },
+ { offset: 0, color: colors[0] },
+ { offset: 1, color: colors[1] },
]),
},
- },
- ],
+ }
+ }),
},
],
-})
+})
diff --git a/src/views/wisdomManagement/index.vue b/src/views/wisdomManagement/index.vue
index 9f9c165..6bc4df1 100644
--- a/src/views/wisdomManagement/index.vue
+++ b/src/views/wisdomManagement/index.vue
@@ -81,7 +81,7 @@
AI
-
智慧管理问答
+ 小牧智慧问答
{{ welcomeText }}