feat: update screen2 dashboard release

V2.0
M先生 1 month ago
parent f20c9bce54
commit 321bf4a139
  1. 0
      docs/assets/AlibabaPuHuiTi-14435b65-balabala-1783244194656.woff2
  2. 0
      docs/assets/AlimamaShuHeiTi-Bold-6401fd0d-balabala-1783244194656.ttf
  3. 0
      docs/assets/AlimamaShuHeiTi-Bold-a95023f2-balabala-1783244194656.woff
  4. 0
      docs/assets/AlimamaShuHeiTi-Bold-fa4a258d-balabala-1783244194656.woff2
  5. 2
      docs/assets/SmartChartRenderer-431d3b86-1783073104768.js
  6. 0
      docs/assets/SmartChartRenderer-8fd37aa6-balabala-1783244194656.css
  7. 2
      docs/assets/SmartChartRenderer-912fade1-1783244194656.js
  8. BIN
      docs/assets/area-1144cf4a-balabala-1783244194656.png
  9. 0
      docs/assets/bg-3acb94e3-balabala-1783244194656.png
  10. 0
      docs/assets/bottom-menu-arrow-big-180b7071-balabala-1783244194656.svg
  11. 0
      docs/assets/bottom-menu-arrow-small-19304179-balabala-1783244194656.svg
  12. 0
      docs/assets/bottom-menu-bg-b465793d-balabala-1783244194656.png
  13. 0
      docs/assets/bottom-menu-btn-ee5c8064-balabala-1783244194656.png
  14. 0
      docs/assets/bottom-menu-btn-hover-df33e514-balabala-1783244194656.png
  15. BIN
      docs/assets/certificate-355c8c0e-balabala-1783244194656.png
  16. 1
      docs/assets/chartTheme-812dab15-balabala-1783244194656.css
  17. 3881
      docs/assets/chartTheme-fbd5f176-1783244194656.js
  18. 0
      docs/assets/chinaBlurLine-1a2cbc0f-balabala-1783244194656.png
  19. 0
      docs/assets/content-bottom-left-arrow-3a8a61f3-balabala-1783244194656.svg
  20. 0
      docs/assets/content-bottom-right-arrow-a3c5270d-balabala-1783244194656.svg
  21. 0
      docs/assets/content-middle-line-162bc0d5-balabala-1783244194656.svg
  22. BIN
      docs/assets/device-74309daf-balabala-1783244194656.png
  23. 0
      docs/assets/focus_bg-b6803e2d-balabala-1783244194656.png
  24. 0
      docs/assets/focus_move_bg-cf41417a-balabala-1783244194656.png
  25. BIN
      docs/assets/forest-a956b24c-balabala-1783244194656.png
  26. BIN
      docs/assets/grass-basic-b26948b5-balabala-1783244194656.png
  27. BIN
      docs/assets/grass-general-9f6c02e6-balabala-1783244194656.png
  28. 0
      docs/assets/header-bg-65b38012-balabala-1783244194656.png
  29. 0
      docs/assets/huiguang-3c5dfda5-balabala-1783244194656.png
  30. 0
      docs/assets/icon1-6f7c222f-balabala-1783244194656.png
  31. 0
      docs/assets/icon2-4f77ccec-balabala-1783244194656.png
  32. 54
      docs/assets/index-1783244194656.js
  33. 1
      docs/assets/index-24f2e349-balabala-1783073104768.css
  34. 1
      docs/assets/index-387cb5ab-balabala-1783073104768.css
  35. 0
      docs/assets/index-47d6f37a-balabala-1783244194656.css
  36. 2
      docs/assets/index-59342d7f-1783244194656.js
  37. 4264
      docs/assets/index-5a21d3d3-1783244194656.js
  38. 1
      docs/assets/index-7323850c-balabala-1783244194656.css
  39. 2
      docs/assets/index-81c84a41-1783244194656.js
  40. 1
      docs/assets/index-8d752fbe-balabala-1783244194656.css
  41. 1
      docs/assets/index-e914e8dc-balabala-1783244194656.css
  42. BIN
      docs/assets/leaf-61e84605-balabala-1783244194656.png
  43. 0
      docs/assets/left-kuang-5d6bd1ae-balabala-1783244194656.svg
  44. BIN
      docs/assets/length-32ee20b8-balabala-1783244194656.png
  45. 0
      docs/assets/live-scene-1-786cdd0a-balabala-1783244194656.png
  46. 0
      docs/assets/menu-btn-91a961cf-balabala-1783244194656.png
  47. 0
      docs/assets/menu-btn-hover-62eba0bb-balabala-1783244194656.png
  48. 0
      docs/assets/ocean-bg-8c848b8f-balabala-1783244194656.png
  49. BIN
      docs/assets/park-b1db3900-balabala-1783244194656.png
  50. 0
      docs/assets/pathLine2-d4134d4b-balabala-1783244194656.png
  51. BIN
      docs/assets/project-da9e3256-balabala-1783244194656.png
  52. BIN
      docs/assets/rate-bfe355f6-balabala-1783244194656.png
  53. 0
      docs/assets/right-kuang-b1937caf-balabala-1783244194656.svg
  54. 0
      docs/assets/ring2-a1b91808-balabala-1783244194656.png
  55. 0
      docs/assets/ring3-fe0f60d7-balabala-1783244194656.png
  56. 0
      docs/assets/ring4-f2e324d6-balabala-1783244194656.png
  57. BIN
      docs/assets/risk-2e5b3a69-balabala-1783244194656.png
  58. 0
      docs/assets/rotationBorder1-2ce8e0ef-balabala-1783244194656.png
  59. 0
      docs/assets/rotationBorder2-7496b968-balabala-1783244194656.png
  60. 0
      docs/assets/saoguang-ccf84a88-balabala-1783244194656.svg
  61. BIN
      docs/assets/stack-8cf8bdb6-balabala-1783244194656.png
  62. 0
      docs/assets/title-zs1-c15a5ee8-balabala-1783244194656.svg
  63. BIN
      docs/assets/town-0200c4bb-balabala-1783244194656.png
  64. BIN
      docs/assets/wetland-d67b919e-balabala-1783244194656.png
  65. BIN
      docs/assets/yield-b201b10e-balabala-1783244194656.png
  66. 4
      docs/index.html
  67. BIN
      src/assets/images/top-stat-icons/area.png
  68. BIN
      src/assets/images/top-stat-icons/certificate.png
  69. BIN
      src/assets/images/top-stat-icons/device.png
  70. BIN
      src/assets/images/top-stat-icons/forest.png
  71. BIN
      src/assets/images/top-stat-icons/grass-basic.png
  72. BIN
      src/assets/images/top-stat-icons/grass-general.png
  73. BIN
      src/assets/images/top-stat-icons/leaf.png
  74. BIN
      src/assets/images/top-stat-icons/length.png
  75. BIN
      src/assets/images/top-stat-icons/park.png
  76. BIN
      src/assets/images/top-stat-icons/project.png
  77. BIN
      src/assets/images/top-stat-icons/rate.png
  78. BIN
      src/assets/images/top-stat-icons/risk.png
  79. BIN
      src/assets/images/top-stat-icons/stack.png
  80. BIN
      src/assets/images/top-stat-icons/town.png
  81. BIN
      src/assets/images/top-stat-icons/wetland.png
  82. BIN
      src/assets/images/top-stat-icons/yield.png
  83. 19
      src/components/mCard/index.vue
  84. 149
      src/components/mCountCard/index.vue
  85. 57
      src/config/layers.js
  86. 7
      src/services/balanceApi.js
  87. 52
      src/services/openApi.js
  88. 6
      src/views/chartStyleGuide/index.vue
  89. 153
      src/views/ecologicalProtection/components/EngineeringAnalysisPanel.vue
  90. 66
      src/views/ecologicalProtection/data.js
  91. 28
      src/views/ecologicalProtection/index.vue
  92. 1272
      src/views/forestGrassWet/components/BusinessPanelRenderer.vue
  93. 34
      src/views/forestGrassWet/index.vue
  94. 58
      src/views/forestGrassWet/map.js
  95. 18
      src/views/grassLivestockBalance/data.js
  96. 47
      src/views/grassLivestockBalance/index.vue
  97. 310
      src/views/home/index.vue
  98. 2
      src/views/login/index.vue
  99. 2
      src/views/pastureManagement/data.js
  100. 331
      src/views/pastureManagement/index.vue
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,2 +0,0 @@
var B=Object.defineProperty,O=Object.defineProperties;var D=Object.getOwnPropertyDescriptors;var S=Object.getOwnPropertySymbols;var W=Object.prototype.hasOwnProperty,$=Object.prototype.propertyIsEnumerable;var v=(a,r,o)=>r in a?B(a,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):a[r]=o,m=(a,r)=>{for(var o in r||(r={}))W.call(r,o)&&v(a,o,r[o]);if(S)for(var o of S(r))$.call(r,o)&&v(a,o,r[o]);return a},d=(a,r)=>O(a,D(r));import{e as b,l as H,x as F,M as h,o as g,c as p,a as f,t as u,g as z,b as R,u as V,n as P}from"./index-1783073104768.js";import{H as j}from"./index-8dceb547-1783073104768.js";const q={class:"smart-chart-header"},G={key:0},M={key:0,class:"smart-chart-source"},J={__name:"SmartChartRenderer",props:{chart:{type:Object,required:!0}},emits:["ready"],setup(a,{emit:r}){const o=r,i=a,l=["#30dcff","#27f0c0","#ffd76c","#7aa7ff","#ff8f6b","#a6f06d"],L=b(()=>{const t=(i.chart.rows||[]).filter(e=>Number.isFinite(Number(e.value)));return i.chart.type==="pie"?N(t):i.chart.type==="line"?w(t):_(t)}),k=b(()=>{const t=i.chart.rows||[];return i.chart.type==="bar"&&x(t)}),y=b(()=>{const t=i.chart.source||{},e=[t.mode,t.table,t.period,t.generatedAt].filter(Boolean);return e.length?e.join(" / "):""});H(()=>{F(()=>{window.requestAnimationFrame(()=>o("ready"))})});function _(t){return x(t)?C(t):{animationDuration:700,grid:{left:52,right:26,top:28,bottom:54},tooltip:s(),xAxis:{type:"category",data:t.map(e=>e.name),axisTick:{show:!1},axisLine:{lineStyle:{color:"rgba(48, 220, 255, 0.28)"}},axisLabel:{color:"rgba(223, 250, 255, 0.76)",fontSize:11,interval:0,rotate:t.length>6?28:0}},yAxis:{type:"value",axisLine:{show:!1},axisTick:{show:!1},splitLine:{lineStyle:{color:"rgba(48, 220, 255, 0.1)",type:"dashed"}},axisLabel:{color:"rgba(223, 250, 255, 0.58)",fontSize:11}},series:[{name:i.chart.title,type:"bar",barWidth:t.length>7?16:22,data:t.map((e,n)=>({value:e.value,name:e.name,itemStyle:{borderRadius:[8,8,0,0],color:new h(0,0,0,1,[{offset:0,color:l[n%l.length]},{offset:1,color:"rgba(48, 220, 255, 0.08)"}])}})),label:{show:!0,position:"top",color:"#ffffff",fontSize:11,formatter:({value:e})=>c(e)}}]}}function C(t){return{animationDuration:700,grid:{left:78,right:72,top:22,bottom:20},tooltip:d(m({},s()),{trigger:"axis",axisPointer:{type:"shadow"}}),xAxis:{type:"value",axisLine:{show:!1},axisTick:{show:!1},splitLine:{lineStyle:{color:"rgba(48, 220, 255, 0.1)",type:"dashed"}},axisLabel:{color:"rgba(223, 250, 255, 0.58)",fontSize:11}},yAxis:{type:"category",inverse:!0,data:t.map(e=>e.name),axisTick:{show:!1},axisLine:{lineStyle:{color:"rgba(48, 220, 255, 0.22)"}},axisLabel:{color:"rgba(223, 250, 255, 0.82)",fontSize:12}},series:[{name:i.chart.title,type:"bar",barWidth:13,data:t.map((e,n)=>({value:e.value,name:e.name,itemStyle:{borderRadius:[0,8,8,0],color:new h(0,0,1,0,[{offset:0,color:"rgba(48, 220, 255, 0.16)"},{offset:1,color:l[n%l.length]}])}})),label:{show:!0,position:"right",color:"#ffffff",fontSize:11,formatter:({value:e})=>c(e)}}]}}function N(t){return{animationDuration:700,tooltip:s(),legend:{orient:"vertical",right:10,top:"middle",itemWidth:10,itemHeight:10,textStyle:{color:"rgba(223, 250, 255, 0.78)",fontSize:11}},series:[{name:i.chart.title,type:"pie",radius:["48%","68%"],center:["35%","52%"],avoidLabelOverlap:!0,label:{color:"#fff",fontSize:11,formatter:`{b}
{d}%`},labelLine:{lineStyle:{color:"rgba(223, 250, 255, 0.42)"}},itemStyle:{borderColor:"rgba(3, 18, 31, 0.92)",borderWidth:3},data:t.map((e,n)=>({name:e.name,value:e.value,itemStyle:{color:l[n%l.length]}}))}]}}function w(t){return{animationDuration:760,grid:{left:52,right:28,top:30,bottom:48},tooltip:d(m({},s()),{trigger:"axis",axisPointer:{type:"line",lineStyle:{color:"rgba(255, 215, 108, 0.52)"}}}),xAxis:{type:"category",boundaryGap:!1,data:t.map(e=>e.name),axisTick:{show:!1},axisLine:{lineStyle:{color:"rgba(48, 220, 255, 0.28)"}},axisLabel:{color:"rgba(223, 250, 255, 0.76)",fontSize:11,interval:0}},yAxis:{type:"value",axisLine:{show:!1},axisTick:{show:!1},splitLine:{lineStyle:{color:"rgba(48, 220, 255, 0.1)",type:"dashed"}},axisLabel:{color:"rgba(223, 250, 255, 0.58)",fontSize:11}},series:[{name:i.chart.title,type:"line",smooth:!0,symbol:"circle",symbolSize:8,lineStyle:{width:3,color:"#30dcff"},itemStyle:{color:"#ffd76c",borderColor:"#041522",borderWidth:2},areaStyle:{color:new h(0,0,0,1,[{offset:0,color:"rgba(48, 220, 255, 0.24)"},{offset:1,color:"rgba(48, 220, 255, 0.02)"}])},label:{show:!0,position:"top",color:"#ffffff",fontSize:11,formatter:({value:e})=>c(e)},data:t.map(e=>({value:e.value,name:e.name}))}]}}function s(){return{trigger:"item",backgroundColor:"rgba(2, 13, 22, 0.94)",borderColor:"rgba(48, 220, 255, 0.34)",borderWidth:1,textStyle:{color:"#fff",fontSize:12},formatter:t=>{const e=(i.chart.rows||[]).find(A=>A.name===t.name),n=(e==null?void 0:e.unit)||i.chart.unit||"",T=e!=null&&e.extra?`<br/><span style="color:rgba(223,250,255,.62)">${e.extra}</span>`:"";return`${t.marker}${t.name}: ${c(t.value)}${n}${T}`}}}function x(t=[]){return t.length>6||t.some(e=>String(e.name||"").length>4)}function c(t){const e=Number(t);return Number.isFinite(e)?Number.isInteger(e)?e.toLocaleString("zh-CN"):e.toLocaleString("zh-CN",{maximumFractionDigits:2}):"--"}return(t,e)=>(g(),p("div",{class:P(["smart-chart-card",{"is-horizontal":k.value}])},[f("div",q,[f("div",null,[f("strong",null,u(a.chart.title),1),f("span",null,u(a.chart.subtitle),1)]),a.chart.unit?(g(),p("em",G,u(a.chart.unit),1)):z("",!0)]),R(V(j),{class:"smart-chart",option:L.value,autoresize:""},null,8,["option"]),y.value?(g(),p("div",M,u(y.value),1)):z("",!0)],2))}};export{J as default};

@ -0,0 +1,2 @@
var D=Object.defineProperty,W=Object.defineProperties;var H=Object.getOwnPropertyDescriptors;var z=Object.getOwnPropertySymbols;var F=Object.prototype.hasOwnProperty,P=Object.prototype.propertyIsEnumerable;var k=(r,o,n)=>o in r?D(r,o,{enumerable:!0,configurable:!0,writable:!0,value:n}):r[o]=n,h=(r,o)=>{for(var n in o||(o={}))F.call(o,n)&&k(r,n,o[n]);if(z)for(var n of z(o))P.call(o,n)&&k(r,n,o[n]);return r},p=(r,o)=>W(r,H(o));import{e as g,j as R,k as V,L as y,o as x,c as S,a as f,t as d,g as _,b as j,u as q,n as G}from"./index-1783244194656.js";import{aS as M}from"./chartTheme-fbd5f176-1783244194656.js";const E={class:"smart-chart-header"},I={key:0},U={key:0,class:"smart-chart-source"},Q={__name:"SmartChartRenderer",props:{chart:{type:Object,required:!0}},emits:["ready"],setup(r,{emit:o}){const n=o,i=r,l=["#30dcff","#27f0c0","#ffd76c","#7aa7ff","#ff8f6b","#a6f06d"],N=g(()=>{const e=(i.chart.rows||[]).filter(t=>Number.isFinite(Number(t.value)));return i.chart.type==="pie"?w(e):i.chart.type==="line"?B(e):A(e)}),T=g(()=>{const e=i.chart.rows||[];return i.chart.type==="bar"&&L(e)}),v=g(()=>{const e=i.chart.source||{},t=$(e),a=m(e.period||e.dataPeriod||i.chart.period),b=m(e.generatedAt||e.updateTime||e.updatedAt,!0),u=[t,a,b].filter(Boolean);return u.length?`统计口径:${u.join(" / ")}`:"统计口径:业务统计库"});R(()=>{V(()=>{window.requestAnimationFrame(()=>n("ready"))})});function A(e){return L(e)?C(e):{animationDuration:700,grid:{left:52,right:26,top:28,bottom:54},tooltip:s(),xAxis:{type:"category",data:e.map(t=>t.name),axisTick:{show:!1},axisLine:{lineStyle:{color:"rgba(48, 220, 255, 0.28)"}},axisLabel:{color:"rgba(223, 250, 255, 0.76)",fontSize:11,interval:0,rotate:e.length>6?28:0}},yAxis:{type:"value",axisLine:{show:!1},axisTick:{show:!1},splitLine:{lineStyle:{color:"rgba(48, 220, 255, 0.1)",type:"dashed"}},axisLabel:{color:"rgba(223, 250, 255, 0.58)",fontSize:11}},series:[{name:i.chart.title,type:"bar",barWidth:e.length>7?16:22,data:e.map((t,a)=>({value:t.value,name:t.name,itemStyle:{borderRadius:[8,8,0,0],color:new y(0,0,0,1,[{offset:0,color:l[a%l.length]},{offset:1,color:"rgba(48, 220, 255, 0.08)"}])}})),label:{show:!0,position:"top",color:"#ffffff",fontSize:11,formatter:({value:t})=>c(t)}}]}}function C(e){return{animationDuration:700,grid:{left:78,right:72,top:22,bottom:20},tooltip:p(h({},s()),{trigger:"axis",axisPointer:{type:"shadow"}}),xAxis:{type:"value",axisLine:{show:!1},axisTick:{show:!1},splitLine:{lineStyle:{color:"rgba(48, 220, 255, 0.1)",type:"dashed"}},axisLabel:{color:"rgba(223, 250, 255, 0.58)",fontSize:11}},yAxis:{type:"category",inverse:!0,data:e.map(t=>t.name),axisTick:{show:!1},axisLine:{lineStyle:{color:"rgba(48, 220, 255, 0.22)"}},axisLabel:{color:"rgba(223, 250, 255, 0.82)",fontSize:12}},series:[{name:i.chart.title,type:"bar",barWidth:13,data:e.map((t,a)=>({value:t.value,name:t.name,itemStyle:{borderRadius:[0,8,8,0],color:new y(0,0,1,0,[{offset:0,color:"rgba(48, 220, 255, 0.16)"},{offset:1,color:l[a%l.length]}])}})),label:{show:!0,position:"right",color:"#ffffff",fontSize:11,formatter:({value:t})=>c(t)}}]}}function w(e){return{animationDuration:700,tooltip:s(),legend:{orient:"vertical",right:10,top:"middle",itemWidth:10,itemHeight:10,textStyle:{color:"rgba(223, 250, 255, 0.78)",fontSize:11}},series:[{name:i.chart.title,type:"pie",radius:["48%","68%"],center:["35%","52%"],avoidLabelOverlap:!0,label:{color:"#fff",fontSize:11,formatter:`{b}
{d}%`},labelLine:{lineStyle:{color:"rgba(223, 250, 255, 0.42)"}},itemStyle:{borderColor:"rgba(3, 18, 31, 0.92)",borderWidth:3},data:e.map((t,a)=>({name:t.name,value:t.value,itemStyle:{color:l[a%l.length]}}))}]}}function B(e){return{animationDuration:760,grid:{left:52,right:28,top:30,bottom:48},tooltip:p(h({},s()),{trigger:"axis",axisPointer:{type:"line",lineStyle:{color:"rgba(255, 215, 108, 0.52)"}}}),xAxis:{type:"category",boundaryGap:!1,data:e.map(t=>t.name),axisTick:{show:!1},axisLine:{lineStyle:{color:"rgba(48, 220, 255, 0.28)"}},axisLabel:{color:"rgba(223, 250, 255, 0.76)",fontSize:11,interval:0}},yAxis:{type:"value",axisLine:{show:!1},axisTick:{show:!1},splitLine:{lineStyle:{color:"rgba(48, 220, 255, 0.1)",type:"dashed"}},axisLabel:{color:"rgba(223, 250, 255, 0.58)",fontSize:11}},series:[{name:i.chart.title,type:"line",smooth:!0,symbol:"circle",symbolSize:8,lineStyle:{width:3,color:"#30dcff"},itemStyle:{color:"#ffd76c",borderColor:"#041522",borderWidth:2},areaStyle:{color:new y(0,0,0,1,[{offset:0,color:"rgba(48, 220, 255, 0.24)"},{offset:1,color:"rgba(48, 220, 255, 0.02)"}])},label:{show:!0,position:"top",color:"#ffffff",fontSize:11,formatter:({value:t})=>c(t)},data:e.map(t=>({value:t.value,name:t.name}))}]}}function s(){return{trigger:"item",backgroundColor:"rgba(2, 13, 22, 0.94)",borderColor:"rgba(48, 220, 255, 0.34)",borderWidth:1,textStyle:{color:"#fff",fontSize:12},formatter:e=>{const t=(i.chart.rows||[]).find(u=>u.name===e.name),a=(t==null?void 0:t.unit)||i.chart.unit||"",b=t!=null&&t.extra?`<br/><span style="color:rgba(223,250,255,.62)">${t.extra}</span>`:"";return`${e.marker}${e.name}: ${c(e.value)}${a}${b}`}}}function L(e=[]){return e.length>6||e.some(t=>String(t.name||"").length>4)}function $(e={}){const t=m(e.sourceName||e.name||e.label);if(t)return t;const a=String(e.mode||"").trim();return/openapi|api|stat/i.test(a)?"":/local|mock|fallback/i.test(a)?"":""}function m(e,t=!1){const a=String(e||"").trim();return a?t&&/^\d{4}-\d{2}-\d{2}/.test(a)?a.slice(0,10):O(a)?"":a:""}function O(e){return/^[a-zA-Z0-9_./:-]+$/.test(e)&&(e.includes("_")||e.length>24)}function c(e){const t=Number(e);return Number.isFinite(t)?Number.isInteger(t)?t.toLocaleString("zh-CN"):t.toLocaleString("zh-CN",{maximumFractionDigits:2}):"--"}return(e,t)=>(x(),S("div",{class:G(["smart-chart-card",{"is-horizontal":T.value}])},[f("div",E,[f("div",null,[f("strong",null,d(r.chart.title),1),f("span",null,d(r.chart.subtitle),1)]),r.chart.unit?(x(),S("em",I,d(r.chart.unit),1)):_("",!0)]),j(q(M),{class:"smart-chart",option:N.value,autoresize:""},null,8,["option"]),v.value?(x(),S("div",U,d(v.value),1)):_("",!0)],2))}};export{Q as default};

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

@ -0,0 +1 @@
.m-card{position:relative;overflow:visible}.m-card-hd{position:absolute;left:0;top:0;width:100%;height:36px;z-index:2;pointer-events:none}.m-card-hd-bg{position:absolute;left:0;top:0;width:100%;height:36px}.m-card-hd-zs1{position:absolute;right:12px;top:10px;width:120px;height:11px}.m-card-hd-title{position:absolute;left:22px;right:128px;top:0;color:#fff;font-size:16px;letter-spacing:1.6px;height:36px;line-height:32px;font-family:PingFangSc;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:none;-webkit-background-clip:initial;-webkit-text-fill-color:#ffffff;opacity:1;mix-blend-mode:normal;filter:brightness(1.2) contrast(1.08);text-shadow:0 0 1px rgba(255,255,255,.96),0 0 8px rgba(255,255,255,.22)}.m-card-hd .saoguang{position:absolute;left:0;top:0;width:100%;height:36px;overflow:hidden;pointer-events:none}.m-card-hd .saoguang img{width:89px;height:36px;animation:saoguangMove 6s linear infinite}.m-card-bd{position:absolute;left:0;top:0;z-index:1}.m-card-bd-bg{position:absolute;left:0;top:0}.m-card-bd-bottom-left-arrow{position:absolute;left:4px;bottom:4px}.m-card-bd-bottom-right-arrow{position:absolute;right:4px;bottom:4px}.m-card-bd-middle-left-line{position:absolute;left:0;top:50%;margin-top:-35px}.m-card-bd-middle-right-line{position:absolute;right:0;top:50%;margin-top:-35px}.m-card-bd-content{position:absolute;left:0;top:36px;right:0;bottom:0;pointer-events:all;overflow:hidden}@keyframes saoguangMove{0%{transform:translate(-160px)}to{transform:translate(2000px)}}.three-pie-wrap{position:relative;width:100%;height:100%;z-index:2}.three-pie{width:100%;height:100%}.three-pie-slot{position:absolute;left:0;top:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;pointer-events:none}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
.login-screen[data-v-02c20620]{position:fixed;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;overflow:hidden;background:linear-gradient(180deg,rgba(4,16,38,.1),rgba(2,8,22,.62)),url(./bg-3acb94e3-balabala-1783073104768.png) center/cover no-repeat,#05142f;color:#fff}.login-screen[data-v-02c20620]:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(90deg,rgba(0,0,0,.46),transparent 42%,rgba(0,0,0,.35)),radial-gradient(circle at 52% 46%,rgba(48,220,255,.16),transparent 32%)}.login-shell[data-v-02c20620]{position:relative;z-index:1;width:min(980px,100vw - 48px);display:grid;grid-template-columns:minmax(0,1fr) 360px;gap:56px;align-items:center}.login-brand[data-v-02c20620]{display:flex;flex-direction:column;gap:14px;min-width:0;text-shadow:0 0 20px rgba(48,220,255,.36)}.login-brand span[data-v-02c20620]{font-size:42px;line-height:1.2;font-weight:700}.login-brand small[data-v-02c20620]{color:#c4f3feb8;font-size:13px;letter-spacing:0}.login-panel[data-v-02c20620]{width:360px;padding:30px 28px 28px;border:1px solid rgba(48,220,255,.34);background:rgba(3,14,32,.82);box-shadow:0 24px 70px #00000057,inset 0 0 28px #30dcff14;box-sizing:border-box}.login-panel-title[data-v-02c20620]{margin-bottom:26px;color:#c4f3fe;font-size:22px;font-weight:700;text-align:center}.login-field[data-v-02c20620]{display:block;margin-bottom:18px}.login-field span[data-v-02c20620]{display:block;margin-bottom:8px;color:#c4f3fec7;font-size:13px}.login-field input[data-v-02c20620]{width:100%;height:42px;padding:0 12px;border:1px solid rgba(48,220,255,.26);outline:none;background:rgba(0,10,24,.62);color:#fff;font-size:15px;box-sizing:border-box}.login-field input[data-v-02c20620]:focus{border-color:#30dcffb8;box-shadow:0 0 0 2px #30dcff1f}.login-error[data-v-02c20620]{min-height:20px;margin:-4px 0 12px;color:#ffcf8a;font-size:13px;line-height:20px}.login-submit[data-v-02c20620]{width:100%;height:44px;border:1px solid rgba(48,220,255,.52);background:linear-gradient(90deg,rgba(24,140,196,.92),rgba(37,204,218,.92));color:#fff;cursor:pointer;font-size:16px;font-weight:700}.login-submit[data-v-02c20620]:disabled{cursor:not-allowed;opacity:.68}@media (max-width: 820px){.login-shell[data-v-02c20620]{grid-template-columns:1fr;gap:28px}.login-brand span[data-v-02c20620]{font-size:30px}.login-panel[data-v-02c20620]{width:100%}}

@ -1 +1 @@
var m=(v,d,n)=>new Promise((i,a)=>{var u=l=>{try{r(n.next(l))}catch(t){a(t)}},o=l=>{try{r(n.throw(l))}catch(t){a(t)}},r=l=>l.done?i(l.value):Promise.resolve(l.value).then(u,o);r((n=n.apply(v,d)).next())});import{_ as x,S as w,T as S,e as h,o as _,c as f,a as e,L as U,I as g,J as y,t as b,g as V,d as p,U as k}from"./index-1783073104768.js";const B={class:"login-screen"},D={class:"login-shell"},I={class:"login-field"},M={class:"login-field"},R={key:0,class:"login-error"},q=["disabled"],C={__name:"index",setup(v){const d=w(),n=S(),i=p(""),a=p(""),u=p(!1),o=p(""),r=h(()=>{const t=typeof d.query.redirect=="string"?decodeURIComponent(d.query.redirect):"/platform";return t.startsWith("/login")?"/platform":t||"/platform"});function l(){return m(this,null,function*(){if(o.value="",!i.value||!a.value){o.value="请输入账号和密码。";return}u.value=!0;try{yield k(i.value,a.value),n.replace(r.value)}catch(t){o.value=(t==null?void 0:t.message)||"登录失败,请稍后再试。"}finally{u.value=!1}})}return(t,s)=>(_(),f("div",B,[e("div",D,[s[5]||(s[5]=e("div",{class:"login-brand"},[e("span",null,"红原县数字畜牧业监管平台"),e("small",null,"Hongyuan Digital Animal Husbandry Supervision Platform")],-1)),e("form",{class:"login-panel",onSubmit:U(l,["prevent"])},[s[4]||(s[4]=e("div",{class:"login-panel-title"},"用户登录",-1)),e("label",I,[s[2]||(s[2]=e("span",null,"账号",-1)),g(e("input",{"onUpdate:modelValue":s[0]||(s[0]=c=>i.value=c),autocomplete:"username",autofocus:"",placeholder:"请输入账号"},null,512),[[y,i.value,void 0,{trim:!0}]])]),e("label",M,[s[3]||(s[3]=e("span",null,"密码",-1)),g(e("input",{"onUpdate:modelValue":s[1]||(s[1]=c=>a.value=c),autocomplete:"current-password",placeholder:"请输入密码",type:"password"},null,512),[[y,a.value]])]),o.value?(_(),f("div",R,b(o.value),1)):V("",!0),e("button",{class:"login-submit",disabled:u.value,type:"submit"},b(u.value?"登录中":"登录"),9,q)],32)])]))}},P=x(C,[["__scopeId","data-v-02c20620"]]);export{P as default}; var m=(v,d,n)=>new Promise((i,a)=>{var u=l=>{try{r(n.next(l))}catch(t){a(t)}},o=l=>{try{r(n.throw(l))}catch(t){a(t)}},r=l=>l.done?i(l.value):Promise.resolve(l.value).then(u,o);r((n=n.apply(v,d)).next())});import{_ as x,J as w,K as h,e as M,o as _,c as f,a as e,y as S,z as g,A as y,t as b,g as V,d as p,M as k}from"./index-1783244194656.js";const B={class:"login-screen"},D={class:"login-shell"},R={class:"login-field"},U={class:"login-field"},q={key:0,class:"login-error"},A=["disabled"],C={__name:"index",setup(v){const d=w(),n=h(),i=p(""),a=p(""),u=p(!1),o=p(""),r=M(()=>{const t=typeof d.query.redirect=="string"?decodeURIComponent(d.query.redirect):"/platform";return t.startsWith("/login")?"/platform":t||"/platform"});function l(){return m(this,null,function*(){if(o.value="",!i.value||!a.value){o.value="请输入账号和密码。";return}u.value=!0;try{yield k(i.value,a.value),n.replace(r.value)}catch(t){o.value=(t==null?void 0:t.message)||"登录失败,请稍后再试。"}finally{u.value=!1}})}return(t,s)=>(_(),f("div",B,[e("div",D,[s[5]||(s[5]=e("div",{class:"login-brand"},[e("span",null,"红原县数字畜牧业监管平台"),e("small",null,"Hongyuan Digital Animal Husbandry Supervision Platform")],-1)),e("form",{class:"login-panel",onSubmit:S(l,["prevent"])},[s[4]||(s[4]=e("div",{class:"login-panel-title"},"用户登录",-1)),e("label",R,[s[2]||(s[2]=e("span",null,"账号",-1)),g(e("input",{"onUpdate:modelValue":s[0]||(s[0]=c=>i.value=c),autocomplete:"username",autofocus:"",placeholder:"请输入账号"},null,512),[[y,i.value,void 0,{trim:!0}]])]),e("label",U,[s[3]||(s[3]=e("span",null,"密码",-1)),g(e("input",{"onUpdate:modelValue":s[1]||(s[1]=c=>a.value=c),autocomplete:"current-password",placeholder:"请输入密码",type:"password"},null,512),[[y,a.value]])]),o.value?(_(),f("div",q,b(o.value),1)):V("",!0),e("button",{class:"login-submit",disabled:u.value,type:"submit"},b(u.value?"登录中":"登录"),9,A)],32)])]))}},N=x(C,[["__scopeId","data-v-8ebd0d6e"]]);export{N as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
.login-screen[data-v-8ebd0d6e]{position:fixed;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;overflow:hidden;background:linear-gradient(180deg,rgba(4,16,38,.1),rgba(2,8,22,.62)),url(./bg-3acb94e3-balabala-1783244194656.png) center/cover no-repeat,#05142f;color:#fff}.login-screen[data-v-8ebd0d6e]:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(90deg,rgba(0,0,0,.46),transparent 42%,rgba(0,0,0,.35)),radial-gradient(circle at 52% 46%,rgba(48,220,255,.16),transparent 32%)}.login-shell[data-v-8ebd0d6e]{position:relative;z-index:1;width:min(980px,100vw - 48px);display:grid;grid-template-columns:minmax(0,1fr) 360px;gap:56px;align-items:center}.login-brand[data-v-8ebd0d6e]{display:flex;flex-direction:column;gap:14px;min-width:0;text-shadow:0 0 20px rgba(48,220,255,.36)}.login-brand span[data-v-8ebd0d6e]{font-size:42px;line-height:1.2;font-weight:700}.login-brand small[data-v-8ebd0d6e]{color:#c4f3feb8;font-size:13px;letter-spacing:0}.login-panel[data-v-8ebd0d6e]{width:360px;padding:30px 28px 28px;border:1px solid rgba(48,220,255,.34);background:rgba(3,14,32,.82);box-shadow:0 24px 70px #00000057,inset 0 0 28px #30dcff14;box-sizing:border-box}.login-panel-title[data-v-8ebd0d6e]{margin-bottom:26px;color:#fff;font-size:22px;font-weight:700;text-align:center}.login-field[data-v-8ebd0d6e]{display:block;margin-bottom:18px}.login-field span[data-v-8ebd0d6e]{display:block;margin-bottom:8px;color:#c4f3fec7;font-size:13px}.login-field input[data-v-8ebd0d6e]{width:100%;height:42px;padding:0 12px;border:1px solid rgba(48,220,255,.26);outline:none;background:rgba(0,10,24,.62);color:#fff;font-size:15px;box-sizing:border-box}.login-field input[data-v-8ebd0d6e]:focus{border-color:#30dcffb8;box-shadow:0 0 0 2px #30dcff1f}.login-error[data-v-8ebd0d6e]{min-height:20px;margin:-4px 0 12px;color:#ffcf8a;font-size:13px;line-height:20px}.login-submit[data-v-8ebd0d6e]{width:100%;height:44px;border:1px solid rgba(48,220,255,.52);background:linear-gradient(90deg,rgba(24,140,196,.92),rgba(37,204,218,.92));color:#fff;cursor:pointer;font-size:16px;font-weight:700}.login-submit[data-v-8ebd0d6e]:disabled{cursor:not-allowed;opacity:.68}@media (max-width: 820px){.login-shell[data-v-8ebd0d6e]{grid-template-columns:1fr;gap:28px}.login-brand span[data-v-8ebd0d6e]{font-size:30px}.login-panel[data-v-8ebd0d6e]{width:100%}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

@ -6,8 +6,8 @@
<link rel="icon" type="image/svg+xml" href="./vite.svg" /> <link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>红原县数字畜牧业监管平台</title> <title>红原县数字畜牧业监管平台</title>
<script type="module" crossorigin src="./assets/index-1783073104768.js"></script> <script type="module" crossorigin src="./assets/index-1783244194656.js"></script>
<link rel="stylesheet" href="./assets/index-47d6f37a-balabala-1783073104768.css"> <link rel="stylesheet" href="./assets/index-47d6f37a-balabala-1783244194656.css">
</head> </head>
<body> <body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

@ -445,21 +445,26 @@ onMounted(() => {
left: 22px; left: 22px;
right: 128px; right: 128px;
top: 0; top: 0;
color: #fff; color: #ffffff;
font-size: 16px; font-size: 16px;
font-weight: 400;
letter-spacing: 1.6px; letter-spacing: 1.6px;
height: 36px; height: 36px;
line-height: 32px; line-height: 32px;
font-family: "PingFangSc"; font-family: "PingFangSc";
font-weight: 600; font-weight: 800;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
background: -webkit-linear-gradient(rgba(219, 249, 255, 1), rgba(169, 240, 255, 1)); background: none;
-webkit-background-clip: text; -webkit-background-clip: initial;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: #ffffff;
} opacity: 1;
mix-blend-mode: normal;
filter: brightness(1.2) contrast(1.08);
text-shadow:
0 0 1px rgba(255, 255, 255, 0.96),
0 0 8px rgba(255, 255, 255, 0.22);
}
.saoguang { .saoguang {
position: absolute; position: absolute;
left: 0; left: 0;

@ -264,129 +264,68 @@ onBeforeUnmount(() => {
background-image: url("~@/assets/images/icon2.png"); background-image: url("~@/assets/images/icon2.png");
} }
&.icon-area, &.icon-stack {
&.icon-town, background-image: url("~@/assets/images/top-stat-icons/stack.png");
&.icon-yield, }
&.icon-rate,
&.icon-stack,
&.icon-leaf,
&.icon-risk {
border: 1px solid rgba(48, 220, 255, 0.26);
border-radius: 50%;
background:
radial-gradient(circle at 50% 50%, rgba(36, 246, 196, 0.2), rgba(48, 220, 255, 0.08) 54%, rgba(48, 220, 255, 0.02) 56%),
linear-gradient(135deg, rgba(48, 220, 255, 0.16), rgba(36, 246, 196, 0.08));
box-shadow:
inset 0 0 0 1px rgba(48, 220, 255, 0.08),
0 0 12px rgba(48, 220, 255, 0.24);
&::before,
&::after {
content: "";
position: absolute;
pointer-events: none;
}
&::before { &.icon-area {
left: 11px; background-image: url("~@/assets/images/top-stat-icons/area.png");
top: 11px; }
width: 20px;
height: 20px;
background: #30dcff;
box-shadow: 0 0 10px rgba(48, 220, 255, 0.42);
}
&::after { &.icon-grass-basic {
left: 7px; background-image: url("~@/assets/images/top-stat-icons/grass-basic.png");
top: 7px; }
width: 26px;
height: 26px; &.icon-grass-general {
border: 1px solid rgba(36, 246, 196, 0.28); background-image: url("~@/assets/images/top-stat-icons/grass-general.png");
border-radius: 50%;
}
} }
&.icon-area::before { &.icon-wetland {
border: 2px solid #24f6c4; background-image: url("~@/assets/images/top-stat-icons/wetland.png");
border-radius: 4px;
background: transparent;
clip-path: polygon(0 16%, 100% 0, 84% 100%, 0 82%);
} }
&.icon-town::before { &.icon-forest {
left: 10px; background-image: url("~@/assets/images/top-stat-icons/forest.png");
top: 12px;
width: 22px;
height: 18px;
background: #24f6c4;
clip-path: polygon(0 100%, 0 45%, 22% 45%, 22% 22%, 48% 22%, 48% 0, 86% 0, 86% 100%);
} }
&.icon-yield::before, &.icon-town {
&.icon-leaf::before { background-image: url("~@/assets/images/top-stat-icons/town.png");
left: 13px;
top: 8px;
width: 17px;
height: 24px;
border-radius: 16px 2px 16px 2px;
background: #24f6c4;
transform: rotate(-28deg);
} }
&.icon-yield::after, &.icon-park {
&.icon-leaf::after { background-image: url("~@/assets/images/top-stat-icons/park.png");
left: 20px;
top: 19px;
width: 2px;
height: 13px;
border: 0;
border-radius: 0;
background: rgba(255, 224, 107, 0.88);
transform: rotate(20deg);
} }
&.icon-rate::before { &.icon-yield {
left: 10px; background-image: url("~@/assets/images/top-stat-icons/yield.png");
top: 20px;
width: 22px;
height: 12px;
background: transparent;
border-left: 3px solid #24f6c4;
border-bottom: 3px solid #24f6c4;
transform: skew(-18deg) rotate(-45deg);
} }
&.icon-stack::before { &.icon-project {
left: 11px; background-image: url("~@/assets/images/top-stat-icons/project.png");
top: 10px;
width: 20px;
height: 22px;
background:
linear-gradient(#24f6c4 0 0) 0 0 / 100% 4px no-repeat,
linear-gradient(#30dcff 0 0) 0 8px / 100% 4px no-repeat,
linear-gradient(#ffe06b 0 0) 0 16px / 100% 4px no-repeat;
box-shadow: none;
} }
&.icon-risk::before { &.icon-length {
left: 11px; background-image: url("~@/assets/images/top-stat-icons/length.png");
top: 9px;
width: 20px;
height: 22px;
background: #ffe06b;
clip-path: polygon(50% 0, 100% 88%, 0 88%);
box-shadow: 0 0 12px rgba(255, 224, 107, 0.38);
} }
&.icon-risk::after { &.icon-device {
left: 20px; background-image: url("~@/assets/images/top-stat-icons/device.png");
top: 17px; }
width: 2px;
height: 8px; &.icon-certificate {
border: 0; background-image: url("~@/assets/images/top-stat-icons/certificate.png");
border-radius: 0; }
background: rgba(2, 16, 29, 0.88);
box-shadow: 0 10px 0 rgba(2, 16, 29, 0.88); &.icon-rate {
background-image: url("~@/assets/images/top-stat-icons/rate.png");
}
&.icon-leaf {
background-image: url("~@/assets/images/top-stat-icons/leaf.png");
}
&.icon-risk {
background-image: url("~@/assets/images/top-stat-icons/risk.png");
} }
} }
&-title { &-title {

@ -14,6 +14,17 @@ export const WMS_IMAGE_SIZE = {
export const SATELLITE_TEXTURE_ZOOM = 9 export const SATELLITE_TEXTURE_ZOOM = 9
export const NATIONAL_PARK_ZONING_COLORS = {
core: {
fillColor: "#24F6C4",
strokeColor: "#B7FFF2",
},
general: {
fillColor: "#FFE06B",
strokeColor: "#FFF2A0",
},
}
function escapeCqlLiteral(value) { function escapeCqlLiteral(value) {
return String(value).replace(/'/g, "''") return String(value).replace(/'/g, "''")
} }
@ -274,7 +285,7 @@ export const forestGrassWetLayers = {
key: "grassland-health", key: "grassland-health",
name: "草地健康/退化", name: "草地健康/退化",
layerName: "ne:2023_08_ndvi", layerName: "ne:2023_08_ndvi",
opacity: 0.72, opacity: 0.78,
legends: [ legends: [
{ name: "非常差", fillColor: "#FF000533", strokeColor: "#FF0005", selectable: false }, { name: "非常差", fillColor: "#FF000533", strokeColor: "#FF0005", selectable: false },
{ name: "差", fillColor: "#D8EE8C33", strokeColor: "#D8EE8C", selectable: false }, { name: "差", fillColor: "#D8EE8C33", strokeColor: "#D8EE8C", selectable: false },
@ -341,16 +352,16 @@ export const ecologicalProtectionLayers = {
{ {
key: "national-park-core", key: "national-park-core",
name: "核心保护区", name: "核心保护区",
fillColor: "#2EEA78", fillColor: NATIONAL_PARK_ZONING_COLORS.core.fillColor,
strokeColor: "#18F8FF", strokeColor: NATIONAL_PARK_ZONING_COLORS.core.strokeColor,
match: { field: "type", value: "核心保护区" }, match: { field: "type", value: "核心保护区" },
selectable: false, selectable: false,
}, },
{ {
key: "national-park-general", key: "national-park-general",
name: "一般控制区", name: "一般控制区",
fillColor: "#FFD15A", fillColor: NATIONAL_PARK_ZONING_COLORS.general.fillColor,
strokeColor: "#FFF2A0", strokeColor: NATIONAL_PARK_ZONING_COLORS.general.strokeColor,
match: { field: "type", value: "一般控制区" }, match: { field: "type", value: "一般控制区" },
selectable: false, selectable: false,
}, },
@ -363,37 +374,37 @@ export const ecologicalProtectionLayers = {
name: "生态修复工程", name: "生态修复工程",
sourceType: "geojson", sourceType: "geojson",
dataPath: "datas/ecological-protection/ecological_restoration_projects.geojson", dataPath: "datas/ecological-protection/ecological_restoration_projects.geojson",
opacity: 0.9, opacity: 0.72,
legends: [ legends: [
{ {
key: "restoration-grassland", key: "restoration-grassland",
name: "草地修复", name: "草地修复",
fillColor: "#70D44B", fillColor: "#75AD49",
strokeColor: "#B7FF6D", strokeColor: "#D2EA78",
cqlFilter: createEqualityCql("project_category", "草地修复"), cqlFilter: createEqualityCql("project_category", "草地修复"),
match: { field: "project_category", value: "草地修复" }, match: { field: "project_category", value: "草地修复" },
}, },
{ {
key: "restoration-wetland", key: "restoration-wetland",
name: "湿地修复", name: "湿地修复",
fillColor: "#42C6DF", fillColor: "#4FB4C3",
strokeColor: "#9CF7FF", strokeColor: "#A9EEF2",
cqlFilter: createEqualityCql("project_category", "湿地修复"), cqlFilter: createEqualityCql("project_category", "湿地修复"),
match: { field: "project_category", value: "湿地修复" }, match: { field: "project_category", value: "湿地修复" },
}, },
{ {
key: "restoration-woodland", key: "restoration-woodland",
name: "林地修复", name: "林地修复",
fillColor: "#1FB579", fillColor: "#43A97A",
strokeColor: "#57F2A6", strokeColor: "#95E2B7",
cqlFilter: createEqualityCql("project_category", "林地修复"), cqlFilter: createEqualityCql("project_category", "林地修复"),
match: { field: "project_category", value: "林地修复" }, match: { field: "project_category", value: "林地修复" },
}, },
{ {
key: "restoration-linear", key: "restoration-linear",
name: "线性设施", name: "线性设施",
fillColor: "#F5C84B", fillColor: "#D2B24D",
strokeColor: "#FFE289", strokeColor: "#FFE28A",
cqlFilter: createEqualityCql("feature_role", "线性设施"), cqlFilter: createEqualityCql("feature_role", "线性设施"),
match: { field: "feature_role", value: "线性设施" }, match: { field: "feature_role", value: "线性设施" },
}, },
@ -406,7 +417,7 @@ export const ecologicalProtectionLayers = {
name: "工程分析项目库", name: "工程分析项目库",
sourceType: "geojson", sourceType: "geojson",
dataPath: "datas/ecological-protection/ecological_restoration_projects.geojson", dataPath: "datas/ecological-protection/ecological_restoration_projects.geojson",
opacity: 0.82, opacity: 0.76,
mapView: { mapView: {
targetLngLat: [102.58, 32.72], targetLngLat: [102.58, 32.72],
distanceScale: 0.78, distanceScale: 0.78,
@ -417,32 +428,32 @@ export const ecologicalProtectionLayers = {
{ {
key: "analysis-project-grassland", key: "analysis-project-grassland",
name: "草地修复", name: "草地修复",
fillColor: "#70D44B", fillColor: "#75AD49",
strokeColor: "#B7FF6D", strokeColor: "#D2EA78",
match: { field: "project_category", value: "草地修复" }, match: { field: "project_category", value: "草地修复" },
selectable: false, selectable: false,
}, },
{ {
key: "analysis-project-wetland", key: "analysis-project-wetland",
name: "湿地修复", name: "湿地修复",
fillColor: "#42C6DF", fillColor: "#4FB4C3",
strokeColor: "#9CF7FF", strokeColor: "#A9EEF2",
match: { field: "project_category", value: "湿地修复" }, match: { field: "project_category", value: "湿地修复" },
selectable: false, selectable: false,
}, },
{ {
key: "analysis-project-woodland", key: "analysis-project-woodland",
name: "林地修复", name: "林地修复",
fillColor: "#1FB579", fillColor: "#43A97A",
strokeColor: "#57F2A6", strokeColor: "#95E2B7",
match: { field: "project_category", value: "林地修复" }, match: { field: "project_category", value: "林地修复" },
selectable: false, selectable: false,
}, },
{ {
key: "analysis-project-linear", key: "analysis-project-linear",
name: "线性设施", name: "线性设施",
fillColor: "#F5C84B", fillColor: "#D2B24D",
strokeColor: "#FFE289", strokeColor: "#FFE28A",
match: { field: "feature_role", value: "线性设施" }, match: { field: "feature_role", value: "线性设施" },
selectable: false, selectable: false,
}, },

@ -52,9 +52,11 @@ function hasMeaningfulBalanceResult(data) {
"acreage", "acreage",
"grasslandArea", "grasslandArea",
"grassland_area", "grassland_area",
"arearange",
"area", "area",
"yakNumber", "yakNumber",
"yak_number", "yak_number",
"yaknumber",
"number", "number",
"yakCount", "yakCount",
"yak_count", "yak_count",
@ -62,14 +64,17 @@ function hasMeaningfulBalanceResult(data) {
"livestock_count", "livestock_count",
"bearingCapacity", "bearingCapacity",
"bearing_capacity", "bearing_capacity",
"bearingcapacity",
"capacity", "capacity",
"theoryCapacity", "theoryCapacity",
"livestockCapacity", "livestockCapacity",
"livestock_capacity", "livestock_capacity",
"livestockcapacity",
"carryingCapacity", "carryingCapacity",
"actualCapacity", "actualCapacity",
"overloadRate", "overloadRate",
"overload_rate", "overload_rate",
"overloadrate",
"rate", "rate",
"level", "level",
"conclusion", "conclusion",
@ -86,8 +91,8 @@ export async function getBalanceAdvice({ areaCode } = {}) {
areaCode, areaCode,
} }
const requests = [ const requests = [
() => get(JSY_API_BASE, "/ai/livestockBalance", params, { timeout: 18000 }),
() => get(OPEN_API_BASE, "/ai/balance/advice", params, { timeout: 18000 }), () => get(OPEN_API_BASE, "/ai/balance/advice", params, { timeout: 18000 }),
() => get(JSY_API_BASE, "/ai/livestockBalance", params, { timeout: 18000 }),
] ]
let lastError let lastError

@ -754,6 +754,10 @@ function diversifyPanelColumn(panels = []) {
return panel return panel
} }
const visualType = panel.type === "donut" && panel.variant === "ring" ? "donut-ring" : panel.type const visualType = panel.type === "donut" && panel.variant === "ring" ? "donut-ring" : panel.type
if (panel.fixedType) {
used.add(visualType)
return panel
}
if (!used.has(visualType)) { if (!used.has(visualType)) {
used.add(visualType) used.add(visualType)
return panel return panel
@ -912,35 +916,20 @@ function buildWetlandYieldCapacityRows(yieldSummary = {}, yieldRows = []) {
const total = toNumber(yieldSummary?.xc) ?? sumRowValues(yieldRows) const total = toNumber(yieldSummary?.xc) ?? sumRowValues(yieldRows)
const edible = toNumber(yieldSummary?.edibleXc) const edible = toNumber(yieldSummary?.edibleXc)
const unavailable = total !== undefined && edible !== undefined ? Math.max(total - edible, 0) : undefined const unavailable = total !== undefined && edible !== undefined ? Math.max(total - edible, 0) : undefined
const topTown = yieldRows[0]
return [ return [
{
key: "wetland-yield-total",
name: "鲜草总产",
value: formatNumber(total),
unit,
scaleValue: 100,
},
{ {
key: "wetland-yield-edible", key: "wetland-yield-edible",
name: "可食鲜草", name: "可食鲜草",
value: formatNumber(edible), value: formatNumber(edible),
unit, unit,
scaleValue: formatNumber(percentOf(edible, total)), extra: `${formatNumber(percentOf(edible, total))}%`,
}, },
{ {
key: "wetland-yield-unavailable", key: "wetland-yield-unavailable",
name: "不可食鲜草", name: "不可食鲜草",
value: formatNumber(unavailable), value: formatNumber(unavailable),
unit, unit,
scaleValue: formatNumber(percentOf(unavailable, total)), extra: `${formatNumber(percentOf(unavailable, total))}%`,
},
{
key: "wetland-yield-top-town",
name: topTown?.name ? `${topTown.name}产量` : "最高乡镇产量",
value: topTown?.value,
unit: topTown?.unit || unit,
scaleValue: formatNumber(percentOf(topTown?.value, total)),
}, },
].filter((row) => row.value !== undefined) ].filter((row) => row.value !== undefined)
} }
@ -962,15 +951,15 @@ function buildWetlandDefaultPanels({
const townAreaPanel = buildPanel("wetland-town-area", "湿地面积分布", colorRowsByPalette(sortRowsByTownDisplayOrder(townAreaRows), WETLAND_CHART_PALETTE), "column", { span: 1, variant: "plain" }) const townAreaPanel = buildPanel("wetland-town-area", "湿地面积分布", colorRowsByPalette(sortRowsByTownDisplayOrder(townAreaRows), WETLAND_CHART_PALETTE), "column", { span: 1, variant: "plain" })
const typePanel = buildPanel("wetland-type", "湿地类型结构", colorRowsByPalette(typeRows, WETLAND_CHART_PALETTE), "donut", { span: 1 }) const typePanel = buildPanel("wetland-type", "湿地类型结构", colorRowsByPalette(typeRows, WETLAND_CHART_PALETTE), "donut", { span: 1 })
const levelStructurePanel = buildPanel("wetland-level-proportion", "湿地级占比", colorRowsByPalette(withPercentExtra(levelRows), WETLAND_CHART_PALETTE), "donut", { span: 1, variant: "ring" }) const levelStructurePanel = buildPanel("wetland-level-proportion", "湿地级占比", colorRowsByPalette(withPercentExtra(levelRows), WETLAND_CHART_PALETTE), "donut", { span: 1, variant: "ring" })
const minTypePanel = buildPanel("wetland-min-type", "湿地优势小类", colorRowsByPalette(topRows(minTypeRows, 6), WETLAND_CHART_PALETTE), "bar", { span: 1, variant: "raceBar" }) const minTypePanel = buildPanel("wetland-min-type", "湿地优势小类", colorRowsByPalette(topRows(minTypeRows, 6), WETLAND_CHART_PALETTE), "bar", { span: 1, variant: "raceBarNoWrap" })
const yieldCapacityPanel = buildPanel("wetland-yield-capacity", "湿地产能结构", buildWetlandYieldCapacityRows(yieldSummary, yieldRows), "bar", { span: 1, variant: "capacity" }) const yieldCapacityPanel = buildPanel("wetland-yield-capacity", "湿地产能结构", buildWetlandYieldCapacityRows(yieldSummary, yieldRows), "structure", { span: 1 })
const yieldPanel = buildPanel("wetland-yield", "湿地生产力分布", sortRowsByTownDisplayOrder(yieldRows), "line", { span: 1 }) const yieldPanel = buildPanel("wetland-yield", "湿地生产力分布", sortRowsByTownDisplayOrder(yieldRows), "line", { span: 1, fixedType: true })
const edibleYieldPanel = buildPanel("wetland-edible-yield", "可食鲜草产量", topRows(edibleYieldRows, 6), "column", { span: 1, variant: "slim" }) const edibleYieldPanel = buildPanel("wetland-edible-yield", "可食鲜草产量", topRows(edibleYieldRows, hongyuanTownshipDisplayOrder.length), "column", { span: 1, variant: "slim" })
const muYieldPanel = buildPanel("wetland-mu-yield", "湿地亩产效率", topRows(muYieldRows, 6), "bar", { span: 1 }) 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 levelPanel = buildPanel("wetland-level", "湿地级别面积", levelRows, "column", { span: 1 })
const levelTownPanel = buildPanel("wetland-level-town", "1级湿地分布", topRows(levelTownRows, 6), "bar", { span: 1, variant: "distribution" }) 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 typeTownPanel = buildPanel("wetland-type-town", "湿地类型乡镇分布", topRows(typeTownRows, 6), "bar", { span: 1, variant: "distribution" })
const edibleMuPanel = buildPanel("wetland-edible-mu", "可食鲜草亩产", topRows(edibleMuRows, 6), "bar", { span: 1 }) 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, 6), "bar", { span: 1, variant: "distribution" })
const leftPanels = diversifyPanelColumn([ const leftPanels = diversifyPanelColumn([
townAreaPanel, townAreaPanel,
@ -1083,12 +1072,12 @@ function buildWoodlandDefaultPanels({
}) { }) {
const townPanel = buildPanel("woodland-town", "林地面积分布", colorRowsByPalette(sortRowsByTownDisplayOrder(townRows), WOODLAND_CHART_PALETTE), "column", { span: 1, variant: "plain" }) const townPanel = buildPanel("woodland-town", "林地面积分布", colorRowsByPalette(sortRowsByTownDisplayOrder(townRows), WOODLAND_CHART_PALETTE), "column", { span: 1, variant: "plain" })
const typePanel = buildPanel("woodland-type", "林地类型结构", colorRowsByPalette(typeRows, WOODLAND_CHART_PALETTE), "donut", { span: 1, variant: "ring" }) const typePanel = buildPanel("woodland-type", "林地类型结构", colorRowsByPalette(typeRows, WOODLAND_CHART_PALETTE), "donut", { span: 1, variant: "ring" })
const protectionPanel = buildPanel("woodland-protection", "公益林保护等级", colorRowsByPalette(withPercentExtra(protectionRows), WOODLAND_CHART_PALETTE), "bar", { span: 1, variant: "raceBar" }) const protectionPanel = buildPanel("woodland-protection", "公益林保护等级", colorRowsByPalette(withPercentExtra(protectionRows), WOODLAND_CHART_PALETTE), "bar", { span: 1, variant: "raceBarRelaxed" })
const ownershipPanel = buildPanel("woodland-ownership", "林地权属结构", colorRowsByPalette(withPercentExtra(ownershipRows), WOODLAND_CHART_PALETTE), "structure", { span: 1 }) const ownershipPanel = buildPanel("woodland-ownership", "林地权属结构", colorRowsByPalette(withPercentExtra(ownershipRows), WOODLAND_CHART_PALETTE), "structure", { span: 1 })
const qualityPanel = buildPanel("woodland-quality", "优势树种面积", topRows(qualityRows, 6), "bar", { span: 1 }) const qualityPanel = buildPanel("woodland-quality", "优势树种面积", topRows(qualityRows, 6), "bar", { span: 1 })
const originPanel = buildPanel("woodland-origin", "林地起源结构", withPercentExtra(originRows), "structure", { span: 1 }) const originPanel = buildPanel("woodland-origin", "林地起源结构", withPercentExtra(originRows), "structure", { span: 1 })
const agePanel = buildPanel("woodland-age", "林龄组结构", topRows(ageRows, 6), "column", { span: 1 }) const agePanel = buildPanel("woodland-age", "林龄组结构", topRows(ageRows, 6), "column", { span: 1 })
const canopyPanel = buildPanel("woodland-canopy", "郁闭度结构", topRows(canopyRows, 6), "column", { span: 1 }) const canopyPanel = buildPanel("woodland-canopy", "郁闭度结构", colorRowsByPalette(topRows(canopyRows, 6), WOODLAND_CHART_PALETTE), "donut", { span: 1, fixedType: true })
const scalePanel = buildPanel("woodland-area-scale", "林地图斑规模", scaleRows, "column", { span: 1 }) const scalePanel = buildPanel("woodland-area-scale", "林地图斑规模", scaleRows, "column", { span: 1 })
const forestTypePanel = buildPanel("woodland-forest-type", "森林类别结构", topRows(forestTypeRows, 6), "bar", { span: 1 }) const forestTypePanel = buildPanel("woodland-forest-type", "森林类别结构", topRows(forestTypeRows, 6), "bar", { span: 1 })
const manageLevelPanel = buildPanel("woodland-manage-level", "管护等级分布", topRows(manageLevelRows, 6), "bar", { span: 1 }) const manageLevelPanel = buildPanel("woodland-manage-level", "管护等级分布", topRows(manageLevelRows, 6), "bar", { span: 1 })
@ -1590,6 +1579,11 @@ function buildFallbackTopicStats(topicKey, layerKey) {
{ name: "麦洼乡", value: 911.14, unit: "斤" }, { name: "麦洼乡", value: 911.14, unit: "斤" },
{ name: "查尔玛乡", value: 873.31, unit: "斤" }, { name: "查尔玛乡", value: 873.31, unit: "斤" },
{ name: "阿木乡", value: 872, unit: "斤" }, { name: "阿木乡", value: 872, unit: "斤" },
{ name: "邛溪镇", value: 870.48, unit: "斤" },
{ name: "色地镇", value: 865.92, unit: "斤" },
{ name: "刷经寺镇", value: 863.48, unit: "斤" },
{ name: "江茸乡", value: 827.92, unit: "斤" },
{ name: "龙日镇", value: 825.59, unit: "斤" },
] ]
const townAreaRows = [ const townAreaRows = [
{ name: "瓦切镇", value: 36.38, unit: "万亩" }, { name: "瓦切镇", value: 36.38, unit: "万亩" },
@ -1784,10 +1778,10 @@ function buildGrasslandDefaultPanels({
degradationRows, degradationRows,
}) { }) {
const areaPanel = buildPanel("grassland-town", "草地面积分布", sortRowsByTownDisplayOrder(townRows), "column", { span: 1, variant: "plain" }) const areaPanel = buildPanel("grassland-town", "草地面积分布", sortRowsByTownDisplayOrder(townRows), "column", { span: 1, variant: "plain" })
const productivityPanel = buildPanel("grassland-productivity-town", "草地生产力分布", sortRowsByTownDisplayOrder(productivityRows), "line", { span: 1 }) const productivityPanel = buildPanel("grassland-productivity-town", "草地生产力分布", sortRowsByTownDisplayOrder(productivityRows), "line", { span: 1, fixedType: true })
const typePanel = buildPanel("grassland-type-proportion", "草原类占比", grassTypeRows, "donut", { span: 1 }) const typePanel = buildPanel("grassland-type-proportion", "草原类占比", grassTypeRows, "donut", { span: 1 })
const edibleYieldPanel = buildPanel("grassland-edible-yield", "可食鲜草产量分布", topRows(edibleYieldRows, 10), "column", { span: 1, variant: "slim" }) const edibleYieldPanel = buildPanel("grassland-edible-yield", "可食鲜草产量分布", topRows(edibleYieldRows, 10), "column", { span: 1, variant: "slim" })
const degradationPanel = buildPanel("grassland-degradation", "草地退化统计", buildGrasslandDegradationDistributionRows(degradationRows), "bar", { span: 1, variant: "raceBar" }) const degradationPanel = buildPanel("grassland-degradation", "草地退化统计", buildGrasslandDegradationDistributionRows(degradationRows), "bar", { span: 1, variant: "degradation" })
const healthPanel = buildPanel("grassland-health-proportion", "草地健康占比", healthRows, "rose", { span: 1 }) const healthPanel = buildPanel("grassland-health-proportion", "草地健康占比", healthRows, "rose", { span: 1 })
const levelPanel = buildPanel("grassland-level-proportion", "草原级占比", grassLevelRows, "donut", { span: 1, variant: "ring" }) const levelPanel = buildPanel("grassland-level-proportion", "草原级占比", grassLevelRows, "donut", { span: 1, variant: "ring" })
const foragePanel = buildPanel("grassland-forage-capacity", "饲草供给", forageRows, "bar", { span: 1, variant: "capacity" }) const foragePanel = buildPanel("grassland-forage-capacity", "饲草供给", forageRows, "bar", { span: 1, variant: "capacity" })
@ -1838,7 +1832,7 @@ function normalizeWetlandBusiness(items, stats = {}) {
nameKeys: ["areaName", "name"], nameKeys: ["areaName", "name"],
valueKeys: ["yield", "value"], valueKeys: ["yield", "value"],
unit: "斤", unit: "斤",
max: 5, max: hongyuanTownshipDisplayOrder.length,
}) })
const wetlandLevelRows = normalizeMetricRows(levelRows, { const wetlandLevelRows = normalizeMetricRows(levelRows, {
nameKeys: ["name"], nameKeys: ["name"],
@ -1851,7 +1845,7 @@ function normalizeWetlandBusiness(items, stats = {}) {
nameKeys: ["areaName", "name"], nameKeys: ["areaName", "name"],
valueKeys: ["yield", "value"], valueKeys: ["yield", "value"],
unit: pickValue(edibleMuRows?.[0], ["unit"]) || "斤", unit: pickValue(edibleMuRows?.[0], ["unit"]) || "斤",
max: 5, max: hongyuanTownshipDisplayOrder.length,
}) })
const wetlandTownAreaRows = aggregateAreaWanMuRows(typeDistributionRows, { const wetlandTownAreaRows = aggregateAreaWanMuRows(typeDistributionRows, {
nameKeys: ["areaName", "name"], nameKeys: ["areaName", "name"],

@ -732,7 +732,11 @@ function createProgressRingOption() {
color: #ffffff !important; color: #ffffff !important;
background: none !important; background: none !important;
-webkit-text-fill-color: #ffffff !important; -webkit-text-fill-color: #ffffff !important;
text-shadow: none !important; font-weight: 800 !important;
filter: brightness(1.2) contrast(1.08) !important;
text-shadow:
0 0 1px rgba(255, 255, 255, 0.96),
0 0 8px rgba(255, 255, 255, 0.22) !important;
} }
.guide-chart-card :deep(.m-card-bd-content) { .guide-chart-card :deep(.m-card-bd-content) {

@ -38,43 +38,40 @@
</div> </div>
</div> </div>
<div class="analysis-card analysis-result"> <div v-if="boundaryFeature" class="analysis-card analysis-result">
<div class="analysis-card-head"> <div class="analysis-card-head">
<span>建设记录核查</span> <span>建设记录核查</span>
<strong v-if="boundaryFeature">{{ resultTitle }}</strong> <strong>{{ resultTitle }}</strong>
</div> </div>
<div v-if="!boundaryFeature" class="analysis-empty"></div> <div class="analysis-conclusion" :class="analysisConclusion.levelClass">
<template v-else> <div class="analysis-conclusion-head">
<div class="analysis-conclusion" :class="analysisConclusion.levelClass"> <span>分析结论</span>
<div class="analysis-conclusion-head"> <strong>{{ analysisConclusion.verdict }}</strong>
<span>分析结论</span>
<strong>{{ analysisConclusion.verdict }}</strong>
</div>
<p>{{ analysisConclusion.summary }}</p>
<div class="analysis-conclusion-tags">
<span v-for="tag in analysisConclusion.tags" :key="tag">{{ tag }}</span>
</div>
<small>{{ analysisConclusion.advice }}</small>
</div> </div>
<div v-if="!resultRows.length" class="analysis-empty is-success">该边界范围内暂未匹配到已建设项目</div> <p>{{ analysisConclusion.summary }}</p>
<div v-else class="analysis-project-list"> <div class="analysis-conclusion-tags">
<button <span v-for="tag in analysisConclusion.tags" :key="tag">{{ tag }}</span>
v-for="item in resultRows"
:key="item.key"
type="button"
class="analysis-project-row"
@pointerup.stop.prevent="$emit('focus-project', item)"
>
<span class="analysis-project-name">{{ item.projectName }}</span>
<div class="analysis-project-meta">
<strong>{{ item.year }}</strong>
<em>{{ item.category }}</em>
<small>叠加 {{ formatMu(item.overlapAreaWanMu) }} </small>
</div>
<b>{{ item.featureCount }} 个要素</b>
</button>
</div> </div>
</template> <small>{{ analysisConclusion.advice }}</small>
</div>
<div v-if="!resultRows.length" class="analysis-empty is-success">该边界范围内暂未匹配到已建设项目</div>
<div v-else class="analysis-project-list">
<button
v-for="item in resultRows"
:key="item.key"
type="button"
class="analysis-project-row"
@pointerup.stop.prevent="$emit('focus-project', item)"
>
<span class="analysis-project-name">{{ item.projectName }}</span>
<div class="analysis-project-meta">
<strong>{{ item.year }}</strong>
<em>{{ item.category }}</em>
<small>叠加 {{ formatMu(item.overlapAreaWanMu) }} </small>
</div>
<b>{{ item.featureCount }} 个要素</b>
</button>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -637,11 +634,11 @@ defineExpose({
z-index: 8; z-index: 8;
left: 32px; left: 32px;
top: 188px; top: 188px;
bottom: 132px;
width: 430px; width: 430px;
display: grid; display: grid;
grid-template-rows: auto minmax(0, 1fr); grid-template-rows: auto auto;
gap: 14px; gap: 12px;
max-height: calc(100vh - 190px);
pointer-events: auto; pointer-events: auto;
} }
@ -649,11 +646,12 @@ defineExpose({
position: relative; position: relative;
overflow: hidden; overflow: hidden;
min-height: 0; min-height: 0;
padding: 14px 16px; padding: 14px 16px 16px;
border: 1px solid rgba(48, 220, 255, 0.32); border: 1px solid rgba(55, 228, 255, 0.28);
background: background:
linear-gradient(135deg, rgba(7, 34, 50, 0.9), rgba(4, 20, 31, 0.76)), linear-gradient(135deg, rgba(7, 36, 52, 0.88), rgba(4, 18, 29, 0.74)),
radial-gradient(circle at 20% 0, rgba(36, 246, 196, 0.16), transparent 42%); radial-gradient(circle at 16% 4%, rgba(36, 246, 196, 0.18), transparent 38%),
radial-gradient(circle at 90% 0, rgba(48, 220, 255, 0.12), transparent 34%);
box-shadow: box-shadow:
inset 0 1px 0 rgba(180, 252, 255, 0.16), inset 0 1px 0 rgba(180, 252, 255, 0.16),
inset 0 0 26px rgba(48, 220, 255, 0.12), inset 0 0 26px rgba(48, 220, 255, 0.12),
@ -670,6 +668,19 @@ defineExpose({
background: linear-gradient(90deg, #24f6c4, rgba(48, 220, 255, 0.18), transparent); background: linear-gradient(90deg, #24f6c4, rgba(48, 220, 255, 0.18), transparent);
opacity: 0.8; opacity: 0.8;
} }
&::after {
content: "";
position: absolute;
right: 12px;
top: 10px;
width: 78px;
height: 18px;
border-top: 1px solid rgba(48, 220, 255, 0.22);
background: repeating-linear-gradient(90deg, rgba(48, 220, 255, 0.18) 0 7px, transparent 7px 14px);
opacity: 0.42;
pointer-events: none;
}
} }
.analysis-card-head { .analysis-card-head {
@ -709,8 +720,8 @@ defineExpose({
.analysis-operation { .analysis-operation {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px; gap: 12px;
margin-top: 12px; margin-top: 14px;
&.has-boundary { &.has-boundary {
margin-top: 10px; margin-top: 10px;
@ -728,12 +739,13 @@ defineExpose({
.analysis-entry { .analysis-entry {
position: relative; position: relative;
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 36px minmax(0, 1fr);
column-gap: 12px;
align-content: center; align-content: center;
align-items: center; align-items: center;
min-width: 0; min-width: 0;
height: 58px; height: 64px;
padding: 0 18px; padding: 0 16px;
border: 1px solid rgba(48, 220, 255, 0.34); border: 1px solid rgba(48, 220, 255, 0.34);
background: background:
linear-gradient(90deg, rgba(48, 220, 255, 0.18), rgba(48, 220, 255, 0.04)), linear-gradient(90deg, rgba(48, 220, 255, 0.18), rgba(48, 220, 255, 0.04)),
@ -744,6 +756,39 @@ defineExpose({
box-sizing: border-box; box-sizing: border-box;
box-shadow: inset 0 0 18px rgba(48, 220, 255, 0.08); box-shadow: inset 0 0 18px rgba(48, 220, 255, 0.08);
&::before {
content: "";
grid-row: 1 / 3;
width: 34px;
height: 34px;
border: 1px solid rgba(36, 246, 196, 0.42);
background:
linear-gradient(135deg, rgba(36, 246, 196, 0.22), rgba(48, 220, 255, 0.08)),
rgba(4, 23, 34, 0.7);
box-shadow:
inset 0 0 12px rgba(48, 220, 255, 0.12),
0 0 12px rgba(36, 246, 196, 0.16);
}
&.is-upload::before {
background:
linear-gradient(#24f6c4 0 0) 50% 9px / 2px 15px no-repeat,
linear-gradient(45deg, transparent 42%, #24f6c4 43% 56%, transparent 57%) 10px 8px / 8px 8px no-repeat,
linear-gradient(-45deg, transparent 42%, #24f6c4 43% 56%, transparent 57%) 16px 8px / 8px 8px no-repeat,
linear-gradient(#24f6c4 0 0) 50% 25px / 16px 2px no-repeat,
linear-gradient(135deg, rgba(36, 246, 196, 0.22), rgba(48, 220, 255, 0.08)),
rgba(4, 23, 34, 0.7);
}
&.is-draw::before {
background:
radial-gradient(circle, #24f6c4 0 2px, transparent 2.5px) 21px 7px / 8px 8px no-repeat,
linear-gradient(135deg, transparent 42%, #24f6c4 43% 56%, transparent 57%) 9px 11px / 18px 18px no-repeat,
linear-gradient(#24f6c4 0 0) 9px 25px / 14px 2px no-repeat,
linear-gradient(135deg, rgba(36, 246, 196, 0.22), rgba(48, 220, 255, 0.08)),
rgba(4, 23, 34, 0.7);
}
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
@ -756,17 +801,25 @@ defineExpose({
} }
span { span {
min-width: 0;
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
line-height: 20px; line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
strong { strong {
min-width: 0;
margin-top: 2px; margin-top: 2px;
color: rgba(232, 252, 255, 0.72); color: rgba(232, 252, 255, 0.72);
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
line-height: 14px; line-height: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
input { input {
@ -918,10 +971,12 @@ defineExpose({
} }
.analysis-result { .analysis-result {
display: grid; display: flex;
grid-template-rows: 26px auto minmax(0, 1fr); flex-direction: column;
gap: 12px; gap: 10px;
height: clamp(500px, 56vh, 620px);
min-height: 0; min-height: 0;
max-height: calc(100vh - 350px);
} }
.analysis-empty { .analysis-empty {
@ -934,6 +989,9 @@ defineExpose({
text-align: center; text-align: center;
&.is-success { &.is-success {
min-height: 64px;
border: 1px solid rgba(36, 246, 196, 0.18);
background: rgba(36, 246, 196, 0.06);
color: #24f6c4; color: #24f6c4;
} }
} }
@ -1017,6 +1075,7 @@ defineExpose({
} }
.analysis-project-list { .analysis-project-list {
flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
padding-right: 6px; padding-right: 6px;

@ -1,4 +1,6 @@
import { hongyuanTownshipDisplayOrder } from "@/config/townshipBoundaries" import { hongyuanTownshipDisplayOrder } from "@/config/townshipBoundaries"
import { getRiskLevel } from "@/views/grassLivestockBalance/data"
import { NATIONAL_PARK_ZONING_COLORS } from "@/config/layers"
const toWanMu = (mu) => Number((Number(mu || 0) / 10000).toFixed(2)) 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 sumBy = (rows, field) => rows.reduce((sum, item) => sum + Number(item[field] || 0), 0)
@ -313,8 +315,8 @@ parkSummary.otherProtectedAreaWanMu = round(
) )
export const parkCoreRows = [ export const parkCoreRows = [
{ key: "park-national-area", name: "国家公园总面积", value: parkSummary.nationalParkTotalAreaWanMu, unit: "万亩", icon: "area" }, { key: "park-national-area", name: "国家公园总面积", value: parkSummary.nationalParkTotalAreaWanMu, unit: "万亩", icon: "park" },
{ key: "park-hongyuan-area", name: "红原县划定面积", value: parkSummary.hongyuanDelimitedAreaWanMu, unit: "万亩", icon: "area" }, { key: "park-hongyuan-area", name: "红原县划定面积", value: parkSummary.hongyuanDelimitedAreaWanMu, unit: "万亩", icon: "park" },
{ key: "park-yak-count", name: "牦牛数量", value: parkSummary.yakCount, unit: "头", icon: "stack" }, { key: "park-yak-count", name: "牦牛数量", value: parkSummary.yakCount, unit: "头", icon: "stack" },
] ]
@ -341,13 +343,18 @@ const parkTownListRows = parkTownRows.map((item) => ({
})) }))
const parkZoningRows = [ const parkZoningRows = [
{ key: "core-protection", name: "核心保护区", value: 67.39, unit: "万亩" }, { key: "core-protection", name: "核心保护区", value: 67.39, unit: "万亩", color: NATIONAL_PARK_ZONING_COLORS.core.fillColor },
{ key: "general-control", name: "一般控制区", value: 93.92, unit: "万亩" }, { key: "general-control", name: "一般控制区", value: 93.92, unit: "万亩", color: NATIONAL_PARK_ZONING_COLORS.general.fillColor },
] ]
const parkGrasslandProductivityWanTon = 43.66 const parkGrasslandProductivityWanTon = 43.66
const parkEdibleGrasslandWanTon = round(parkGrasslandProductivityWanTon * (192.26 / 297.29), 2) const parkEdibleGrasslandWanTon = round(parkGrasslandProductivityWanTon * (192.26 / 297.29), 2)
const parkGrasslandCapacityPerMu = 0.06229833103624831 const parkGrasslandCapacityPerMu = 0.06229833103624831
const parkBalanceTownRowMap = new Map([
["瓦切镇", { areaRange: 747837.6, yakNumber: 121654, bearingCapacity: 63347.29, livestockCapacity: 66751, overloadRate: 2.05, level: "轻度超载", year: "2026" }],
["麦洼乡", { areaRange: 458338.35, yakNumber: 78465, bearingCapacity: 37634.86, livestockCapacity: 55974, overloadRate: 1.18, level: "轻度超载", year: "2026" }],
["色地镇", { areaRange: 991265.25, yakNumber: 131956, bearingCapacity: 83382.49, livestockCapacity: 70715, overloadRate: 2.14, level: "轻度超载", year: "2026" }],
])
const parkVillageWeights = { const parkVillageWeights = {
麦洼乡: [ 麦洼乡: [
{ name: "洞拉村", grassland: 0.29, wetland: 0.34, household: 0.3, yak: 0.32, edible: 0.31, woodland: 0.3 }, { name: "洞拉村", grassland: 0.29, wetland: 0.34, household: 0.3, yak: 0.32, edible: 0.31, woodland: 0.3 },
@ -384,11 +391,19 @@ if (parkHouseholdDelta && parkTownBusinessRows[0]) {
parkTownBusinessRows[0].householdCount += parkHouseholdDelta parkTownBusinessRows[0].householdCount += parkHouseholdDelta
} }
parkTownBusinessRows.forEach((item) => { parkTownBusinessRows.forEach((item) => {
const bearingCapacity = round(Number(item.grasslandAreaWanMu || 0) * 10000 * parkGrasslandCapacityPerMu, 0) const balanceRow = parkBalanceTownRowMap.get(item.name)
const livestockCapacity = Number(item.livestockCount || item.yakCount || 0) const bearingCapacity = Number(balanceRow?.bearingCapacity) || round(Number(item.grasslandAreaWanMu || 0) * 10000 * parkGrasslandCapacityPerMu, 0)
const livestockCapacity = Number(balanceRow?.livestockCapacity) || Number(item.livestockCount || item.yakCount || 0)
const overloadRate = Number.isFinite(Number(balanceRow?.overloadRate))
? Number(balanceRow.overloadRate)
: bearingCapacity
? round(((livestockCapacity - bearingCapacity) / bearingCapacity) * 100, 1)
: 0
const risk = balanceRow?.level ? { label: balanceRow.level } : getRiskLevel(overloadRate)
item.bearingCapacity = bearingCapacity item.bearingCapacity = bearingCapacity
item.livestockCapacity = livestockCapacity item.livestockCapacity = livestockCapacity
item.overloadRate = bearingCapacity ? round(((livestockCapacity - bearingCapacity) / bearingCapacity) * 100, 1) : 0 item.overloadRate = round(overloadRate, 2)
item.balanceLevel = risk.label
item.searchText = [ item.searchText = [
item.name, item.name,
`划定面积${item.protectedAreaWanMu}万亩`, `划定面积${item.protectedAreaWanMu}万亩`,
@ -399,7 +414,12 @@ parkTownBusinessRows.forEach((item) => {
}) })
const parkBalanceBearingCapacity = sumBy(parkTownBusinessRows, "bearingCapacity") const parkBalanceBearingCapacity = sumBy(parkTownBusinessRows, "bearingCapacity")
const parkBalanceLivestockCapacity = sumBy(parkTownBusinessRows, "livestockCapacity") const parkBalanceLivestockCapacity = sumBy(parkTownBusinessRows, "livestockCapacity")
parkSummary.balanceOverloadRate = parkBalanceBearingCapacity const parkBalanceDirectRates = parkTownBusinessRows
.map((item) => Number(item.overloadRate))
.filter((value) => Number.isFinite(value))
parkSummary.balanceOverloadRate = parkBalanceDirectRates.length
? round(parkBalanceDirectRates.reduce((sum, value) => sum + value, 0) / parkBalanceDirectRates.length, 1)
: parkBalanceBearingCapacity
? round(((parkBalanceLivestockCapacity - parkBalanceBearingCapacity) / parkBalanceBearingCapacity) * 100, 1) ? round(((parkBalanceLivestockCapacity - parkBalanceBearingCapacity) / parkBalanceBearingCapacity) * 100, 1)
: 0 : 0
@ -503,6 +523,7 @@ const parkTownBalanceRows = sortParkTownRows(parkTownBusinessRows).map((item) =>
name: item.name, name: item.name,
value: item.overloadRate, value: item.overloadRate,
unit: "%", unit: "%",
status: item.balanceLevel,
extra: `载畜 ${item.livestockCapacity}`, extra: `载畜 ${item.livestockCapacity}`,
})) }))
@ -576,8 +597,8 @@ export const parkPanels = {
title: "乡镇管控区构成", title: "乡镇管控区构成",
type: "stackColumn", type: "stackColumn",
segments: [ segments: [
{ key: "core", name: "核心保护区" }, { key: "core", name: "核心保护区", color: NATIONAL_PARK_ZONING_COLORS.core.fillColor },
{ key: "general", name: "一般控制区" }, { key: "general", name: "一般控制区", color: NATIONAL_PARK_ZONING_COLORS.general.fillColor },
], ],
rows: parkTownZoningRows, rows: parkTownZoningRows,
}, },
@ -591,7 +612,8 @@ export const parkPanels = {
{ {
key: "park-town-balance", key: "park-town-balance",
title: "草畜平衡超载率", title: "草畜平衡超载率",
type: "heat", type: "gauge",
max: 100,
rows: parkTownBalanceRows, rows: parkTownBalanceRows,
}, },
], ],
@ -602,28 +624,6 @@ export const parkPanels = {
type: "donut", type: "donut",
rows: withParkPercentExtra(parkZoningRows), rows: withParkPercentExtra(parkZoningRows),
}, },
{
key: "park-town-ecology-jobs",
title: "生态管护岗位",
type: "peak",
rows: parkTownEcologyJobRows,
},
{
key: "park-town-ledger",
title: "乡镇保护管护清单",
type: "projectTable",
span: 2,
searchPlaceholder: "搜索乡镇",
searchKeys: ["name", "searchText"],
columns: [
{ key: "name", label: "乡镇", field: "name", width: "18%", className: "business-project-col-name" },
{ key: "area", label: "面积", field: "protectedAreaWanMu", format: "number", unit: "万亩", width: "22%", align: "right", strong: true, className: "business-project-col-scale" },
{ key: "yak", label: "牦牛", field: "yakCount", format: "number", unit: "头", width: "21%", align: "right" },
{ key: "household", label: "牧户", field: "householdCount", format: "number", unit: "户", width: "18%", align: "right" },
{ key: "rate", label: "超载率", field: "overloadRate", format: "number", unit: "%", width: "21%", align: "right", strong: true },
],
rows: parkTownLedgerRows,
},
], ],
} }

@ -196,9 +196,18 @@ const currentPanels = computed(() => {
}) })
const leftPanels = computed(() => currentPanels.value.left || []) const leftPanels = computed(() => currentPanels.value.left || [])
const rightPanels = computed(() => currentPanels.value.right || []) const rightPanels = computed(() => currentPanels.value.right || [])
const panelHeight = 190 const screenViewportHeight = ref(typeof window === "undefined" ? 1080 : window.innerHeight)
const sidePanelTop = 128
const sidePanelGap = 12
const sidePanelBottomGap = 8
const sidePanelMinHeight = 227
const panelHeight = computed(() => {
const viewportHeight = Math.max(1080, Number(screenViewportHeight.value) || 1080)
const fittedHeight = Math.floor((viewportHeight - sidePanelTop - sidePanelBottomGap - sidePanelGap * 3) / 4)
return Math.max(sidePanelMinHeight, fittedHeight)
})
const panelGap = 12 const panelGap = 12
const doublePanelHeight = panelHeight * 2 + panelGap const doublePanelHeight = computed(() => panelHeight.value * 2 + panelGap)
const layerPanelWidth = computed(() => isNationalPark.value ? 178 : 248) const layerPanelWidth = computed(() => isNationalPark.value ? 178 : 248)
const layerPanelHeight = computed(() => isNationalPark.value ? 118 : 430) const layerPanelHeight = computed(() => isNationalPark.value ? 118 : 430)
const featurePanelVisible = computed(() => Boolean(featurePanelPoint.value)) const featurePanelVisible = computed(() => Boolean(featurePanelPoint.value))
@ -225,6 +234,8 @@ const featurePanelStyle = computed(() => {
}) })
onMounted(() => { onMounted(() => {
updateScreenViewportHeight()
window.addEventListener("resize", updateScreenViewportHeight)
document.addEventListener("pointerdown", handleOutsideFeaturePointerDown, true) document.addEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
initAssets() initAssets()
syncView(currentView.value) syncView(currentView.value)
@ -233,6 +244,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
destroyed = true destroyed = true
if (mapLoadTimer) window.clearTimeout(mapLoadTimer) if (mapLoadTimer) window.clearTimeout(mapLoadTimer)
window.removeEventListener("resize", updateScreenViewportHeight)
document.removeEventListener("pointerdown", handleOutsideFeaturePointerDown, true) document.removeEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
}) })
@ -357,6 +369,10 @@ function toScreenLocalPoint(point) {
} }
} }
function updateScreenViewportHeight() {
screenViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight
}
function getPanelRenderKey(panel, side) { function getPanelRenderKey(panel, side) {
return `${currentView.value}-${side}-${panel?.key || panel?.title || "panel"}` return `${currentView.value}-${side}-${panel?.key || panel?.title || "panel"}`
} }
@ -366,7 +382,7 @@ function isDoublePanel(panel) {
} }
function getPanelHeight(panel) { function getPanelHeight(panel) {
return isDoublePanel(panel) ? doublePanelHeight : panelHeight return isDoublePanel(panel) ? doublePanelHeight.value : panelHeight.value
} }
function estimateFeatureRowCount(feature) { function estimateFeatureRowCount(feature) {
@ -518,6 +534,12 @@ function hideLoading() {
.layer-panel-legend-row { .layer-panel-legend-row {
height: 28px; height: 28px;
} }
.m-card-hd-title {
right: 42px !important;
font-size: 15px !important;
letter-spacing: 1px !important;
}
} }
} }

@ -21,7 +21,7 @@
:width="398" :width="398"
:height="getPanelHeight(panel)" :height="getPanelHeight(panel)"
> >
<business-panel-renderer :panel="panel" compact /> <business-panel-renderer :panel="panel" compact column-label-standard />
</m-card> </m-card>
</div> </div>
</div> </div>
@ -37,7 +37,7 @@
:width="398" :width="398"
:height="getPanelHeight(panel)" :height="getPanelHeight(panel)"
> >
<business-panel-renderer :panel="panel" compact /> <business-panel-renderer :panel="panel" compact column-label-standard />
</m-card> </m-card>
</div> </div>
</div> </div>
@ -199,7 +199,16 @@ const activeLayer = computed(() => {
timelineItem, timelineItem,
} }
}) })
const panelHeight = computed(() => 190) const screenViewportHeight = ref(typeof window === "undefined" ? 1080 : window.innerHeight)
const sidePanelTop = 128
const sidePanelGap = 12
const sidePanelBottomGap = 8
const sidePanelMinHeight = 227
const panelHeight = computed(() => {
const viewportHeight = Math.max(1080, Number(screenViewportHeight.value) || 1080)
const fittedHeight = Math.floor((viewportHeight - sidePanelTop - sidePanelBottomGap - sidePanelGap * 3) / 4)
return Math.max(sidePanelMinHeight, fittedHeight)
})
const panelGap = computed(() => 12) const panelGap = computed(() => 12)
const doublePanelHeight = computed(() => panelHeight.value * 2 + panelGap.value) const doublePanelHeight = computed(() => panelHeight.value * 2 + panelGap.value)
const featurePanelVisible = computed(() => Boolean(featurePanelPoint.value)) const featurePanelVisible = computed(() => Boolean(featurePanelPoint.value))
@ -235,7 +244,13 @@ function toScreenLocalPoint(point) {
} }
} }
function updateScreenViewportHeight() {
screenViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight
}
onMounted(() => { onMounted(() => {
updateScreenViewportHeight()
window.addEventListener("resize", updateScreenViewportHeight)
document.addEventListener("pointerdown", handleOutsideFeaturePointerDown, true) document.addEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
initAssets() initAssets()
loadBusinessStats(activeTopicKey.value, "") loadBusinessStats(activeTopicKey.value, "")
@ -245,6 +260,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
destroyed = true destroyed = true
if (mapLoadTimer) window.clearTimeout(mapLoadTimer) if (mapLoadTimer) window.clearTimeout(mapLoadTimer)
window.removeEventListener("resize", updateScreenViewportHeight)
document.removeEventListener("pointerdown", handleOutsideFeaturePointerDown, true) document.removeEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
}) })
@ -556,7 +572,7 @@ function hideLoading() {
.forest-grass-wet { .forest-grass-wet {
position: absolute; position: absolute;
inset: 0; inset: 0;
height: 1080px; height: 100%;
overflow: clip; overflow: clip;
overscroll-behavior: none; overscroll-behavior: none;
@ -586,8 +602,8 @@ function hideLoading() {
.fgw-left, .fgw-left,
.fgw-right { .fgw-right {
position: absolute; position: absolute;
top: 156px; top: 128px;
bottom: 112px; bottom: 8px;
z-index: 6; z-index: 6;
width: 398px; width: 398px;
perspective: 700px; perspective: 700px;
@ -655,7 +671,11 @@ function hideLoading() {
color: #ffffff !important; color: #ffffff !important;
background: none !important; background: none !important;
-webkit-text-fill-color: #ffffff !important; -webkit-text-fill-color: #ffffff !important;
text-shadow: none !important; font-weight: 800 !important;
filter: brightness(1.2) contrast(1.08) !important;
text-shadow:
0 0 1px rgba(255, 255, 255, 0.96),
0 0 8px rgba(255, 255, 255, 0.22) !important;
} }
.fgw-map-layer-tools, .fgw-map-layer-tools,

@ -1539,22 +1539,23 @@ export class World extends Mini3d {
const boundary = this.analysisBoundaryFeature const boundary = this.analysisBoundaryFeature
const drawPoints = this.boundaryDrawPoints || [] const drawPoints = this.boundaryDrawPoints || []
if (!features.length && !analysisFeatures.length && !boundary && !drawPoints.length) return if (!features.length && !analysisFeatures.length && !boundary && !drawPoints.length) return
const quietProjectLayer = this.isEcologicalProjectLayer(this.currentTopicLayer)
const glowStyle = { const glowStyle = {
fill: "#FFE15A", fill: "#FFE15A",
stroke: "#24F6C4", stroke: "#24F6C4",
polygonAlpha: 0.24, polygonAlpha: quietProjectLayer ? 0.14 : 0.24,
lineAlpha: 0.98, lineAlpha: quietProjectLayer ? 0.72 : 0.98,
lineWidth: 12, lineWidth: quietProjectLayer ? 6 : 12,
pointRadius: 11, pointRadius: 11,
glow: true, glow: !quietProjectLayer,
halo: true, halo: !quietProjectLayer,
} }
const focusStyle = { const focusStyle = {
fill: "#FFF06A", fill: "#FFF06A",
stroke: "#FFFDF2", stroke: "#FFFDF2",
polygonAlpha: 0.46, polygonAlpha: quietProjectLayer ? 0.28 : 0.46,
lineAlpha: 1, lineAlpha: quietProjectLayer ? 0.94 : 1,
lineWidth: 5, lineWidth: quietProjectLayer ? 3.2 : 5,
pointRadius: 6.5, pointRadius: 6.5,
glow: false, glow: false,
halo: false, halo: false,
@ -1570,22 +1571,22 @@ export class World extends Mini3d {
this.drawVectorFeature(context, geometry, { this.drawVectorFeature(context, geometry, {
fill: "#FF8A3D", fill: "#FF8A3D",
stroke: "#FFE15A", stroke: "#FFE15A",
polygonAlpha: 0.28, polygonAlpha: quietProjectLayer ? 0.22 : 0.28,
lineAlpha: 0.98, lineAlpha: quietProjectLayer ? 0.88 : 0.98,
lineWidth: 5, lineWidth: quietProjectLayer ? 2.8 : 5,
pointRadius: 6, pointRadius: 6,
glow: true, glow: !quietProjectLayer,
}) })
}) })
if (boundary?.geometry) { if (boundary?.geometry) {
this.drawVectorFeature(context, boundary.geometry, { this.drawVectorFeature(context, boundary.geometry, {
fill: "#24F6C4", fill: "#24F6C4",
stroke: "#FFFFFF", stroke: "#FFFFFF",
polygonAlpha: 0.18, polygonAlpha: quietProjectLayer ? 0.12 : 0.18,
lineAlpha: 1, lineAlpha: quietProjectLayer ? 0.92 : 1,
lineWidth: 4, lineWidth: quietProjectLayer ? 3 : 4,
pointRadius: 6, pointRadius: 6,
glow: true, glow: !quietProjectLayer,
}) })
} }
if (drawPoints.length) { if (drawPoints.length) {
@ -1597,11 +1598,11 @@ export class World extends Mini3d {
this.drawVectorFeature(context, lineGeometry, { this.drawVectorFeature(context, lineGeometry, {
fill: "#24F6C4", fill: "#24F6C4",
stroke: "#24F6C4", stroke: "#24F6C4",
polygonAlpha: 0.12, polygonAlpha: quietProjectLayer ? 0.08 : 0.12,
lineAlpha: 1, lineAlpha: 1,
lineWidth: 4, lineWidth: quietProjectLayer ? 3 : 4,
pointRadius: 6, pointRadius: 6,
glow: true, glow: !quietProjectLayer,
}) })
coordinates.forEach((coordinate) => { coordinates.forEach((coordinate) => {
this.drawVectorFeature(context, { type: "Point", coordinates: coordinate }, { this.drawVectorFeature(context, { type: "Point", coordinates: coordinate }, {
@ -1609,9 +1610,9 @@ export class World extends Mini3d {
stroke: "#24F6C4", stroke: "#24F6C4",
polygonAlpha: 0.2, polygonAlpha: 0.2,
lineAlpha: 1, lineAlpha: 1,
lineWidth: 3, lineWidth: quietProjectLayer ? 2.4 : 3,
pointRadius: 5.5, pointRadius: 5.5,
glow: true, glow: !quietProjectLayer,
}) })
}) })
} }
@ -2008,6 +2009,9 @@ export class World extends Mini3d {
if (String(type).includes("LineString")) return 2 if (String(type).includes("LineString")) return 2
return 3 return 3
} }
isEcologicalProjectLayer(layer = this.currentTopicLayer) {
return ["restoration-projects", "engineering-analysis-projects"].includes(layer?.key)
}
getVectorFeatureStyle(layer, feature) { getVectorFeatureStyle(layer, feature) {
const properties = feature.properties || {} const properties = feature.properties || {}
const matchedLegend = (layer.legends || []).find((legend) => { const matchedLegend = (layer.legends || []).find((legend) => {
@ -2018,6 +2022,18 @@ export class World extends Mini3d {
const legend = matchedLegend || layer.activeLegend || layer.legends?.[0] || {} const legend = matchedLegend || layer.activeLegend || layer.legends?.[0] || {}
const isLine = String(feature.geometry?.type || "").includes("LineString") const isLine = String(feature.geometry?.type || "").includes("LineString")
const isPoint = feature.geometry?.type === "Point" const isPoint = feature.geometry?.type === "Point"
if (this.isEcologicalProjectLayer(layer)) {
const lineFeature = isLine || String(properties.feature_role || "") === "线性设施"
return {
fill: lineFeature || isPoint ? legend.strokeColor || legend.fillColor || "#D2EA78" : legend.fillColor || "#75AD49",
stroke: legend.strokeColor || legend.fillColor || "#D2EA78",
polygonAlpha: lineFeature ? 0.08 : 0.22,
lineAlpha: lineFeature ? 0.9 : 0.82,
lineWidth: lineFeature ? 3 : 1.85,
pointRadius: isPoint ? 3.8 : 4,
glow: false,
}
}
if (layer?.key === "yak-epidemic-village-boundary") { if (layer?.key === "yak-epidemic-village-boundary") {
return { return {
fill: legend.fillColor || "#24F6C4", fill: legend.fillColor || "#24F6C4",

@ -58,15 +58,15 @@ export const RISK_LEVELS = [
] ]
export const fieldMap = { export const fieldMap = {
areaRange: ["areaRange", "area_range", "acreage", "grasslandArea", "area"], areaRange: ["areaRange", "area_range", "arearange", "acreage", "grasslandArea", "grasslandarea", "area"],
yakNumber: ["yakNumber", "yak_number", "number", "yakCount", "livestockCount"], yakNumber: ["yakNumber", "yak_number", "yaknumber", "number", "yakCount", "yakcount", "livestockCount", "livestockcount"],
bearingCapacity: ["bearingCapacity", "bearing_capacity", "capacity", "theoryCapacity"], bearingCapacity: ["bearingCapacity", "bearing_capacity", "bearingcapacity", "capacity", "theoryCapacity", "theorycapacity"],
livestockCapacity: ["livestockCapacity", "livestock_capacity", "carryingCapacity", "actualCapacity"], livestockCapacity: ["livestockCapacity", "livestock_capacity", "livestockcapacity", "carryingCapacity", "carryingcapacity", "actualCapacity", "actualcapacity"],
overloadRate: ["overloadRate", "overload_rate", "rate"], overloadRate: ["overloadRate", "overload_rate", "overloadrate", "rate"],
level: ["level", "conclusion"], level: ["level", "conclusion"],
advice: ["advice", "balanceAdvice", "balance_advice", "balanceAdivce", "balance_adivce"], advice: ["advice", "balanceAdvice", "balance_advice", "balanceadvice", "balanceAdivce", "balance_adivce", "balanceadivce"],
year: ["year"], year: ["year"],
areaName: ["areaName", "area_name", "name"], areaName: ["areaName", "area_name", "areaname", "name"],
} }
const round = (value, decimals = 2) => { const round = (value, decimals = 2) => {
@ -259,7 +259,7 @@ export function normalizeBalanceResult(source = {}, fallback = {}) {
key: fallback.key || source.key || source.id || source.areaCode || fallback.name, key: fallback.key || source.key || source.id || source.areaCode || fallback.name,
type: fallback.type || source.type || "town", type: fallback.type || source.type || "town",
name: pickValue(source, fieldMap.areaName, fallback.name || "核算对象"), name: pickValue(source, fieldMap.areaName, fallback.name || "核算对象"),
areaCode: source.areaCode || source.area_code || fallback.areaCode, areaCode: source.areaCode || source.area_code || source.areacode || fallback.areaCode,
areaRange, areaRange,
yakNumber, yakNumber,
bearingCapacity, bearingCapacity,
@ -276,7 +276,7 @@ function getLegacyLivestockCapacity(source = {}, rawLivestockCapacity, fallbackY
if (!source || typeof source !== "object") return null if (!source || typeof source !== "object") return null
if (rawLivestockCapacity !== undefined && rawLivestockCapacity !== null && rawLivestockCapacity !== "") return null if (rawLivestockCapacity !== undefined && rawLivestockCapacity !== null && rawLivestockCapacity !== "") return null
const hasLegacyAlias = Object.prototype.hasOwnProperty.call(source, "yak_number") const hasLegacyAlias = Object.prototype.hasOwnProperty.call(source, "yak_number")
const hasBalanceMarker = ["overload_rate", "overloadRate", "advice", "balanceAdvice", "level", "conclusion"] const hasBalanceMarker = ["overload_rate", "overloadRate", "overloadrate", "advice", "balanceAdvice", "balanceadvice", "level", "conclusion"]
.some((key) => source[key] !== undefined && source[key] !== null && source[key] !== "") .some((key) => source[key] !== undefined && source[key] !== null && source[key] !== "")
const aliasValue = toNumber(source.yak_number) const aliasValue = toNumber(source.yak_number)
if (!hasLegacyAlias || !hasBalanceMarker || aliasValue === null) return null if (!hasLegacyAlias || !hasBalanceMarker || aliasValue === null) return null

@ -238,9 +238,15 @@ const props = defineProps({
const emit = defineEmits(["loading-progress", "map-ready"]) const emit = defineEmits(["loading-progress", "map-ready"])
const selectorCardWidth = 326 const selectorCardWidth = 326
const selectorCardHeight = 720
const sheetCardWidth = 1310 const sheetCardWidth = 1310
const mainCardHeight = 820 const screenViewportHeight = ref(typeof window === "undefined" ? 1080 : window.innerHeight)
const balanceScreenTop = 128
const balanceScreenBottomGap = 8
const mainCardHeight = computed(() => {
const viewportHeight = Math.max(1080, Number(screenViewportHeight.value) || 1080)
return Math.max(944, viewportHeight - balanceScreenTop - balanceScreenBottomGap)
})
const selectorCardHeight = computed(() => Math.max(844, mainCardHeight.value - 100))
const MIN_ANALYSIS_DURATION = 6200 const MIN_ANALYSIS_DURATION = 6200
const activeScope = ref("county") const activeScope = ref("county")
@ -318,10 +324,14 @@ const conclusionSummary = computed(() => {
return `${deltaName} ${formatNumber(Math.abs(derived.delta))},承载利用率 ${formatPercent(derived.usageRate)}` return `${deltaName} ${formatNumber(Math.abs(derived.delta))},承载利用率 ${formatPercent(derived.usageRate)}`
}) })
const verdictHint = computed(() => { const verdictHint = computed(() => {
if (["healthy", "balanced"].includes(activeDerived.value.risk.key)) return "保持监测"
if (activeDerived.value.risk.key === "watch") return "关注调控"
if (activeDerived.value.delta === null) return "按结果表判定" if (activeDerived.value.delta === null) return "按结果表判定"
return activeDerived.value.delta > 0 ? "需调控载畜量" : "承载能力可覆盖" return activeDerived.value.delta > 0 ? "需调控载畜量" : "承载能力可覆盖"
}) })
const decisionHint = computed(() => { const decisionHint = computed(() => {
if (["healthy", "balanced"].includes(activeDerived.value.risk.key)) return "保持监测"
if (activeDerived.value.risk.key === "watch") return "关注调控"
if (activeDerived.value.delta === null) return "结果表口径" if (activeDerived.value.delta === null) return "结果表口径"
return activeDerived.value.delta > 0 ? "需要调控" : "保持监测" return activeDerived.value.delta > 0 ? "需要调控" : "保持监测"
}) })
@ -390,7 +400,7 @@ const analysisProcessRows = computed(() => {
name: "平衡判定", name: "平衡判定",
desc: "生成结论与调控方向", desc: "生成结论与调控方向",
pending: "等待平衡判定", pending: "等待平衡判定",
plan: derived.delta > 0 ? "判定超载风险并生成调控方案" : "判定平衡状态并生成监测方案", plan: ["healthy", "balanced"].includes(derived.risk.key) ? "判定平衡状态并生成监测方案" : "判定超载风险并生成调控方案",
}, },
] ]
}) })
@ -429,7 +439,13 @@ const workflowRows = computed(() => [
const activeAdviceRows = computed(() => analysisDone.value ? splitAdvice(activeBalance.value, activeDerived.value.risk) : []) const activeAdviceRows = computed(() => analysisDone.value ? splitAdvice(activeBalance.value, activeDerived.value.risk) : [])
function updateScreenViewportHeight() {
screenViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight
}
onMounted(() => { onMounted(() => {
updateScreenViewportHeight()
window.addEventListener("resize", updateScreenViewportHeight)
emit("loading-progress", { progress: 100 }) emit("loading-progress", { progress: 100 })
emit("map-ready") emit("map-ready")
loadVillageTargets() loadVillageTargets()
@ -437,6 +453,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
destroyed = true destroyed = true
window.removeEventListener("resize", updateScreenViewportHeight)
requestSerial += 1 requestSerial += 1
villageLoadSerial += 1 villageLoadSerial += 1
clearAnalysisTimer() clearAnalysisTimer()
@ -629,6 +646,7 @@ function pickAreaCode(row = {}) {
return String(pickValue(row, [ return String(pickValue(row, [
"areaCode", "areaCode",
"area_code", "area_code",
"areacode",
"code", "code",
"value", "value",
"id", "id",
@ -641,6 +659,7 @@ function pickAreaName(row = {}) {
return String(pickValue(row, [ return String(pickValue(row, [
"areaName", "areaName",
"area_name", "area_name",
"areaname",
"name", "name",
"label", "label",
"zoneName", "zoneName",
@ -727,10 +746,10 @@ function getProcessStepState(index) {
.balance-backdrop { .balance-backdrop {
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 118px; top: 104px;
z-index: 1; z-index: 1;
width: 1740px; width: 1740px;
height: 878px; height: 944px;
transform: translateX(-50%); transform: translateX(-50%);
pointer-events: none; pointer-events: none;
background: background:
@ -765,7 +784,7 @@ function getProcessStepState(index) {
position: absolute; position: absolute;
left: 96px; left: 96px;
right: 96px; right: 96px;
top: 122px; top: 108px;
height: 460px; height: 460px;
background: background:
radial-gradient(ellipse at 50% 50%, rgba(36, 246, 196, 0.16), transparent 56%), radial-gradient(ellipse at 50% 50%, rgba(36, 246, 196, 0.16), transparent 56%),
@ -779,7 +798,7 @@ function getProcessStepState(index) {
.balance-orbit { .balance-orbit {
position: absolute; position: absolute;
top: 116px; top: 108px;
width: 282px; width: 282px;
height: 282px; height: 282px;
border: 1px solid rgba(126, 251, 246, 0.08); border: 1px solid rgba(126, 251, 246, 0.08);
@ -818,7 +837,7 @@ function getProcessStepState(index) {
.balance-screen { .balance-screen {
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 186px; top: 128px;
z-index: 4; z-index: 4;
display: grid; display: grid;
grid-template-columns: 326px 1310px; grid-template-columns: 326px 1310px;
@ -1005,7 +1024,11 @@ function getProcessStepState(index) {
letter-spacing: 1px !important; letter-spacing: 1px !important;
background: none !important; background: none !important;
-webkit-text-fill-color: #fff !important; -webkit-text-fill-color: #fff !important;
text-shadow: 0 0 12px rgba(126, 251, 246, 0.36) !important; font-weight: 800 !important;
filter: brightness(1.2) contrast(1.08) !important;
text-shadow:
0 0 1px rgba(255, 255, 255, 0.96),
0 0 8px rgba(255, 255, 255, 0.22) !important;
} }
.balance-selector { .balance-selector {
@ -2319,8 +2342,8 @@ function getProcessStepState(index) {
} }
.balance-backdrop { .balance-backdrop {
top: 150px; top: 104px;
height: 800px; height: 944px;
background: background:
linear-gradient(rgba(48, 220, 255, 0.018) 1px, transparent 1px), linear-gradient(rgba(48, 220, 255, 0.018) 1px, transparent 1px),
linear-gradient(90deg, rgba(48, 220, 255, 0.016) 1px, transparent 1px); linear-gradient(90deg, rgba(48, 220, 255, 0.016) 1px, transparent 1px);
@ -2341,7 +2364,7 @@ function getProcessStepState(index) {
} }
.balance-screen { .balance-screen {
top: 182px; top: 128px;
} }
.balance-card { .balance-card {

@ -80,28 +80,38 @@
</m-card> </m-card>
</div> </div>
<div class="home-left home-side-stack"> <div class="home-left">
<m-card <div class="home-side-stack is-left">
v-for="panel in leftPanels" <m-card
:key="panel.key" v-for="panel in leftPanels"
class="home-card left-card" :key="panel.key"
:title="panel.title" class="home-card left-card"
:width="398" :title="panel.title"
:height="panel.height" :width="sidePanelWidth"
> :height="panel.height"
<business-panel-renderer :panel="panel" compact /> >
</m-card> <v-chart
v-if="panel.option"
:ref="(el) => setHomeChartRef(panel.key, el)"
class="home-chart home-town-column-chart"
:option="panel.option"
:autoresize="true"
/>
<business-panel-renderer v-else :panel="panel" compact />
</m-card>
</div>
</div> </div>
<div class="home-right home-side-stack"> <div class="home-right">
<m-card <div class="home-side-stack is-right">
v-for="panel in rightPanels" <m-card
:key="panel.key" v-for="panel in rightPanels"
:class="['home-card', 'right-card', { 'home-video-card': panel.type === 'video' }]" :key="panel.key"
:title="panel.title" :class="['home-card', 'right-card', { 'home-video-card': panel.type === 'video' }]"
:width="398" :title="panel.title"
:height="panel.height" :width="sidePanelWidth"
> :height="panel.height"
>
<div v-if="panel.type === 'video'" class="home-video-monitor"> <div v-if="panel.type === 'video'" class="home-video-monitor">
<button <button
class="home-video-nav home-video-prev" class="home-video-nav home-video-prev"
@ -160,7 +170,8 @@
:autoresize="true" :autoresize="true"
/> />
<business-panel-renderer v-else :panel="panel" compact /> <business-panel-renderer v-else :panel="panel" compact />
</m-card> </m-card>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -221,8 +232,18 @@ const videoLoading = ref(false)
const videoError = ref("") const videoError = ref("")
const videoElementId = `home-video-player-${Math.random().toString(36).slice(2)}` const videoElementId = `home-video-player-${Math.random().toString(36).slice(2)}`
const palette = ["#30dcff", "#27f0c0", "#ffd76c", "#7aa7ff", "#ff8f6b", "#a6f06d"] const palette = ["#30dcff", "#27f0c0", "#ffd76c", "#7aa7ff", "#ff8f6b", "#a6f06d"]
const sidePanelHeight = 208 const sidePanelWidth = 398
const rightChartPanelHeight = sidePanelHeight const homeViewportHeight = ref(typeof window === "undefined" ? 1080 : window.innerHeight)
const sidePanelTop = 128
const sidePanelGap = 12
const sidePanelBottomGap = 8
const sidePanelMinHeight = 227
const sidePanelHeight = computed(() => {
const viewportHeight = Math.max(1080, Number(homeViewportHeight.value) || 1080)
const fittedHeight = Math.floor((viewportHeight - sidePanelTop - sidePanelBottomGap - sidePanelGap * 3) / 4)
return Math.max(sidePanelMinHeight, fittedHeight)
})
const rightChartPanelHeight = computed(() => sidePanelHeight.value)
const videoPanelHeight = rightChartPanelHeight const videoPanelHeight = rightChartPanelHeight
const videoLoadTimeoutMs = 12000 const videoLoadTimeoutMs = 12000
const forecastTrendPeriod = getForecastPeriod("2028") const forecastTrendPeriod = getForecastPeriod("2028")
@ -350,25 +371,25 @@ const rightPanels = computed(() => [
key: "home-video-monitor", key: "home-video-monitor",
title: "牧场实时监控", title: "牧场实时监控",
type: "video", type: "video",
height: videoPanelHeight, height: videoPanelHeight.value,
live: currentHomeVideo.value, live: currentHomeVideo.value,
}, },
{ {
key: "yak-structure", key: "yak-structure",
title: "畜群结构占比", title: "畜群结构占比",
height: rightChartPanelHeight, height: rightChartPanelHeight.value,
option: structureDonutOption.value, option: structureDonutOption.value,
}, },
{ {
key: "yak-three-year-forecast", key: "yak-three-year-forecast",
title: "未来三年牦牛预测", title: "未来三年牦牛预测",
height: rightChartPanelHeight, height: rightChartPanelHeight.value,
option: threeYearForecastOption.value, option: threeYearForecastOption.value,
}, },
{ {
key: "yak-forecast-trend", key: "yak-forecast-trend",
title: "未来存栏趋势", title: "未来存栏趋势",
height: rightChartPanelHeight, height: rightChartPanelHeight.value,
option: forecastTrendOption.value, option: forecastTrendOption.value,
}, },
]) ])
@ -434,7 +455,7 @@ const threeYearForecastOption = computed(() => withChartDefaults({
return `${row.label}<br/>预测存栏:${formatNumber(row.stock)}` return `${row.label}<br/>预测存栏:${formatNumber(row.stock)}`
}, },
}, },
grid: { left: 42, right: 18, top: 16, bottom: 24 }, grid: { left: 50, right: 18, top: 16, bottom: 24 },
xAxis: { xAxis: {
type: "category", type: "category",
data: forecastPeriods.map((item) => item.shortLabel), data: forecastPeriods.map((item) => item.shortLabel),
@ -447,7 +468,7 @@ const threeYearForecastOption = computed(() => withChartDefaults({
min(value) { min(value) {
return Math.max(0, Math.floor(value.min / 10000) * 10000) return Math.max(0, Math.floor(value.min / 10000) * 10000)
}, },
axisLabel: { color: "rgba(223, 250, 255, 0.58)", fontSize: 10, formatter: formatCompact }, axisLabel: { color: "rgba(223, 250, 255, 0.58)", fontSize: 10, margin: 4, formatter: (value) => formatHeadWan(value, 1) },
splitLine: { lineStyle: { color: "rgba(48, 220, 255, 0.1)", type: "dashed" } }, splitLine: { lineStyle: { color: "rgba(48, 220, 255, 0.1)", type: "dashed" } },
}, },
series: [ series: [
@ -481,7 +502,7 @@ const threeYearForecastOption = computed(() => withChartDefaults({
position: "top", position: "top",
color: "#ffffff", color: "#ffffff",
fontSize: 10, fontSize: 10,
formatter: ({ value }) => formatCompact(value), formatter: ({ value }) => formatHeadWan(value),
}, },
}, },
], ],
@ -594,6 +615,8 @@ const forecastTrendOption = computed(() => {
}) })
onMounted(() => { onMounted(() => {
updateHomeViewportHeight()
window.addEventListener("resize", updateHomeViewportHeight)
emit("module-stats", { emit("module-stats", {
moduleKey: "home", moduleKey: "home",
items: homeTopStats, items: homeTopStats,
@ -603,6 +626,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
destroyed = true destroyed = true
window.removeEventListener("resize", updateHomeViewportHeight)
stopVideoPlayer() stopVideoPlayer()
cancelHomeIdleWork() cancelHomeIdleWork()
stopChartMotion() stopChartMotion()
@ -983,8 +1007,19 @@ function pickTopicPanel(topicKey, panelKey) {
} }
function normalizeHomePanel(panel, prefix) { function normalizeHomePanel(panel, prefix) {
const homeTownColumnMetrics = {
"grassland-town": "草地面积",
"wetland-yield": "湿地生产力",
}
if (homeTownColumnMetrics[panel.key]) {
return {
...panel,
key: `home-${prefix}-${panel.key}`,
height: sidePanelHeight.value,
option: buildHomeTownColumnOption(panel, homeTownColumnMetrics[panel.key]),
}
}
const displayOverrides = { const displayOverrides = {
"wetland-yield": { type: "column", variant: "slim" },
"woodland-type": { type: "structure", variant: undefined }, "woodland-type": { type: "structure", variant: undefined },
} }
const override = displayOverrides[panel.key] || {} const override = displayOverrides[panel.key] || {}
@ -992,8 +1027,155 @@ function normalizeHomePanel(panel, prefix) {
...panel, ...panel,
...override, ...override,
key: `home-${prefix}-${panel.key}`, key: `home-${prefix}-${panel.key}`,
height: sidePanelHeight, height: sidePanelHeight.value,
}
}
function buildHomeTownColumnOption(panel, metricName) {
const rows = (panel.rows || []).filter((item) => Number.isFinite(Number(item.value))).slice(0, 10)
const unit = rows.find((item) => item.unit)?.unit || "万亩"
const values = rows.map((item) => Number(item.value) || 0)
const maxValue = Math.max(...values, 1)
const topValue = Math.ceil(maxValue * 1.08)
const colors = getHomeTownColumnPalette(panel.key)
return withChartDefaults({
animation: true,
animationDuration: 1000,
animationDurationUpdate: 800,
animationEasing: "cubicOut",
tooltip: {
...chartTooltip(),
trigger: "axis",
axisPointer: {
type: "shadow",
shadowStyle: { color: "rgba(48, 220, 255, 0.08)" },
},
formatter(params) {
const items = Array.isArray(params) ? params : [params]
const point = items.find((item) => item.seriesName === metricName) || items[0]
const row = rows[point?.dataIndex ?? 0]
if (!row) return ""
return `${row.name}<br/>${metricName}${formatNumber(row.value)}${row.unit || unit}`
},
},
grid: { left: 24, right: 14, top: 26, bottom: 58, containLabel: false },
graphic: [
{
type: "text",
right: 8,
top: 3,
style: {
text: `单位:${unit}`,
fill: "rgba(224, 246, 255, 0.72)",
fontSize: 11,
fontWeight: 600,
},
},
],
xAxis: {
type: "category",
data: rows.map((item) => item.name),
axisLabel: {
interval: 0,
hideOverlap: false,
color: "rgba(226, 250, 255, 0.82)",
fontSize: 11,
lineHeight: 13,
margin: 8,
formatter: formatShortVerticalTownName,
},
axisLine: { show: false },
axisTick: { show: false },
},
yAxis: {
type: "value",
min: 0,
max: topValue,
axisLabel: { show: false },
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "rgba(48, 220, 255, 0.1)" } },
},
series: [
{
name: "上限",
type: "bar",
silent: true,
barWidth: 13,
barGap: "-100%",
data: rows.map(() => topValue),
itemStyle: {
borderRadius: [8, 8, 3, 3],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(48, 220, 255, 0.18)" },
{ offset: 1, color: "rgba(48, 220, 255, 0.03)" },
]),
},
tooltip: { show: false },
z: 1,
},
{
name: metricName,
type: "bar",
barWidth: 13,
data: rows.map((item, index) => ({
value: Number(item.value) || 0,
itemStyle: {
borderRadius: [8, 8, 3, 3],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: colors[index % colors.length].top },
{ offset: 0.72, color: colors[index % colors.length].bottom },
{ offset: 1, color: "rgba(24, 114, 185, 0.22)" },
]),
shadowBlur: 10,
shadowColor: colors[index % colors.length].shadow,
},
})),
label: { show: false },
emphasis: {
itemStyle: { shadowBlur: 18 },
},
z: 3,
},
{
name: "柱顶",
type: "pictorialBar",
symbol: "circle",
symbolSize: [15, 6],
symbolOffset: [0, -2],
symbolPosition: "end",
data: rows.map((item, index) => ({
value: Number(item.value) || 0,
itemStyle: {
color: colors[index % colors.length].cap,
opacity: 0.9,
shadowBlur: 8,
shadowColor: colors[index % colors.length].capShadow,
},
})),
tooltip: { show: false },
z: 4,
},
],
})
}
function getHomeTownColumnPalette(panelKey) {
if (panelKey === "grassland-town") {
return [
{ top: "#9BEA55", bottom: "#29DFA7", shadow: "rgba(41, 223, 167, 0.4)", cap: "#9BEA55", capShadow: "rgba(155, 234, 85, 0.52)" },
{ top: "#7FEA6A", bottom: "#24D5C0", shadow: "rgba(36, 213, 192, 0.4)", cap: "#7FEA6A", capShadow: "rgba(127, 234, 106, 0.52)" },
{ top: "#B6E84C", bottom: "#47D982", shadow: "rgba(71, 217, 130, 0.4)", cap: "#B6E84C", capShadow: "rgba(182, 232, 76, 0.52)" },
]
} }
return ["#24f6c4", "#32dcff", "#ffe06b", "#7ee269", "#ff9a62", "#9ea7ff"].map((color) => ({
top: color,
bottom: "rgba(50, 220, 255, 0.12)",
shadow: `${color}66`,
cap: color,
capShadow: `${color}88`,
}))
} }
function buildLineSeries(name, data, color, yAxisIndex = 0, area = false, activeIndex = -1) { function buildLineSeries(name, data, color, yAxisIndex = 0, area = false, activeIndex = -1) {
@ -1110,6 +1292,24 @@ function formatCompact(value) {
return Math.round(number).toLocaleString("zh-CN") return Math.round(number).toLocaleString("zh-CN")
} }
function formatHeadWan(value, digits = 2) {
const number = Number(value)
if (!Number.isFinite(number)) return "--"
if (Math.abs(number) >= 10000) return `${(number / 10000).toFixed(digits)}`
return Math.round(number).toLocaleString("zh-CN")
}
function formatVerticalTownName(name) {
return String(name || "").split("").join("\n")
}
function formatShortVerticalTownName(name) {
const shortName = String(name || "")
.replace(/\s+/g, "")
.slice(0, 3)
return formatVerticalTownName(shortName)
}
function selectHomeLayerTopic(topicKey) { function selectHomeLayerTopic(topicKey) {
activeHomeTopicKey.value = topicKey activeHomeTopicKey.value = topicKey
activeHomeLayerKey.value = "" activeHomeLayerKey.value = ""
@ -1150,6 +1350,11 @@ function isHomeSelectableLegend(item) {
function isHomeLegendItemSelectable(item) { function isHomeLegendItemSelectable(item) {
return homeLegendSelectable.value && isHomeSelectableLegend(item) return homeLegendSelectable.value && isHomeSelectableLegend(item)
} }
function updateHomeViewportHeight() {
homeViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight
}
</script> </script>
<style lang="scss"> <style lang="scss">
@ -1417,26 +1622,42 @@ function isHomeLegendItemSelectable(item) {
text-align: center; text-align: center;
} }
.home-side-stack { .home-left,
.home-right {
position: absolute; position: absolute;
top: 176px; top: 128px;
bottom: 8px;
z-index: 5; z-index: 5;
display: flex;
flex-direction: column;
gap: 12px;
width: 398px; width: 398px;
pointer-events: none; perspective: 700px;
perspective-origin: 50% 50%;
pointer-events: all;
} }
.home-left { .home-left {
left: 32px; left: 32px;
transform: perspective(700px) rotateY(6deg);
transform-origin: left center;
} }
.home-right { .home-right {
right: 32px; right: 32px;
transform: perspective(700px) rotateY(-6deg); }
.home-side-stack {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
gap: 12px;
transform-style: preserve-3d;
}
.home-side-stack.is-left {
transform: translate3d(0, 0, 0) rotateY(6deg);
transform-origin: left center;
}
.home-side-stack.is-right {
transform: translate3d(0, 0, 0) rotateY(-6deg);
transform-origin: right center; transform-origin: right center;
} }
@ -1473,7 +1694,11 @@ function isHomeLegendItemSelectable(item) {
color: #ffffff !important; color: #ffffff !important;
background: none !important; background: none !important;
-webkit-text-fill-color: #ffffff !important; -webkit-text-fill-color: #ffffff !important;
text-shadow: none !important; font-weight: 800 !important;
filter: brightness(1.2) contrast(1.08) !important;
text-shadow:
0 0 1px rgba(255, 255, 255, 0.96),
0 0 8px rgba(255, 255, 255, 0.22) !important;
} }
.home-chart { .home-chart {
@ -1706,4 +1931,5 @@ function isHomeLegendItemSelectable(item) {
transform: translateX(100%); transform: translateX(100%);
} }
} }
</style> </style>

@ -124,7 +124,7 @@ async function handleSubmit() {
.login-panel-title { .login-panel-title {
margin-bottom: 26px; margin-bottom: 26px;
color: #c4f3fe; color: #ffffff;
font-size: 22px; font-size: 22px;
font-weight: 700; font-weight: 700;
text-align: center; text-align: center;

@ -7,7 +7,7 @@ const toWanMu = (value) => round((Number(value) || 0) / 10000, 2)
const cxptCoreRows = [ const cxptCoreRows = [
{ key: "pasture-total", name: "牧户数量", value: 9110, unit: "户", icon: "town" }, { key: "pasture-total", name: "牧户数量", value: 9110, unit: "户", icon: "town" },
{ key: "avg-yak-per-household", name: "户均牦牛头数", value: 52, unit: "头", icon: "yield" }, { key: "avg-yak-per-household", name: "户均牦牛头数", value: 52, unit: "头", icon: "stack" },
{ key: "avg-grassland-area", name: "户均草地面积", value: 1131, unit: "亩", icon: "area" }, { key: "avg-grassland-area", name: "户均草地面积", value: 1131, unit: "亩", icon: "area" },
{ key: "joint-household-count", name: "联户数量", value: 237, unit: "户", icon: "town" }, { key: "joint-household-count", name: "联户数量", value: 237, unit: "户", icon: "town" },
{ key: "poverty-monitor-count", name: "防反贫监测户", value: 122, unit: "户", icon: "town" }, { key: "poverty-monitor-count", name: "防反贫监测户", value: 122, unit: "户", icon: "town" },

@ -14,11 +14,11 @@
<div class="pasture-left"> <div class="pasture-left">
<div class="pasture-side-stack is-left"> <div class="pasture-side-stack is-left">
<m-card class="pasture-side-card left-card" title="牧户区域统计" :width="sideCardWidth" :height="sideCardHeight"> <m-card class="pasture-side-card left-card" title="牧户区域统计" :width="sideCardWidth" :height="sideCardHeight">
<business-panel-renderer :panel="townPanel" compact /> <business-panel-renderer :panel="householdColumnPanel" compact />
</m-card> </m-card>
<m-card class="pasture-side-card left-card" title="草地面积分布" :width="sideCardWidth" :height="sideCardHeight"> <m-card class="pasture-side-card left-card" title="草地面积分布" :width="sideCardWidth" :height="sideCardHeight">
<business-panel-renderer :panel="grasslandPanel" compact /> <business-panel-renderer :panel="grasslandColumnPanel" compact />
</m-card> </m-card>
<m-card class="pasture-side-card left-card pasture-video-card" title="牧场实时监控" :width="sideCardWidth" :height="sideCardHeight"> <m-card class="pasture-side-card left-card pasture-video-card" title="牧场实时监控" :width="sideCardWidth" :height="sideCardHeight">
@ -130,9 +130,17 @@ const props = defineProps({
}) })
const emit = defineEmits(["module-stats", "loading-progress", "map-ready"]) const emit = defineEmits(["module-stats", "loading-progress", "map-ready"])
const sideCardWidth = 398 const sideCardWidth = 398
const sideCardHeight = 190
const cardGap = 12 const cardGap = 12
const doubleCardHeight = sideCardHeight * 2 + cardGap const screenViewportHeight = ref(typeof window === "undefined" ? 1080 : window.innerHeight)
const sidePanelTop = 128
const sidePanelBottomGap = 8
const sideCardMinHeight = 227
const sideCardHeight = computed(() => {
const viewportHeight = Math.max(1080, Number(screenViewportHeight.value) || 1080)
const fittedHeight = Math.floor((viewportHeight - sidePanelTop - sidePanelBottomGap - cardGap * 3) / 4)
return Math.max(sideCardMinHeight, fittedHeight)
})
const doubleCardHeight = computed(() => sideCardHeight.value * 2 + cardGap)
const mapSceneRef = ref(null) const mapSceneRef = ref(null)
const mapReady = ref(false) const mapReady = ref(false)
const apiProfile = ref(pastureFallbackProfile) const apiProfile = ref(pastureFallbackProfile)
@ -140,6 +148,7 @@ const pastureRows = ref(pastureListRows)
const activeLayerKey = ref("") const activeLayerKey = ref("")
const selectedLegendKey = ref("") const selectedLegendKey = ref("")
const pastureTopStatNames = ["牧户数量", "户均牦牛头数", "户均草地面积", "联户数量"] const pastureTopStatNames = ["牧户数量", "户均牦牛头数", "户均草地面积", "联户数量"]
const pasturePalette = ["#23f6c8", "#35d8ff", "#f0d96a", "#76e36b", "#ff9a5c", "#a59cff", "#20e5c4"]
const PASTURE_VIDEO_ACCESS_TOKEN = String(import.meta.env.VITE_EZVIZ_ACCESS_TOKEN || "").trim() const PASTURE_VIDEO_ACCESS_TOKEN = String(import.meta.env.VITE_EZVIZ_ACCESS_TOKEN || "").trim()
const pastureVideoElementId = `pasture-video-player-${Math.random().toString(36).slice(2)}` const pastureVideoElementId = `pasture-video-player-${Math.random().toString(36).slice(2)}`
const pastureVideoLoadTimeoutMs = 12000 const pastureVideoLoadTimeoutMs = 12000
@ -298,7 +307,10 @@ const onlineRows = computed(() => {
}) })
const ageRows = computed(() => normalizeAgeRows(apiProfile.value.age).length ? normalizeAgeRows(apiProfile.value.age) : pastureAgeRows) const ageRows = computed(() => normalizeAgeRows(apiProfile.value.age).length ? normalizeAgeRows(apiProfile.value.age) : pastureAgeRows)
const genderRows = computed(() => normalizeGenderRows(apiProfile.value.age).length ? normalizeGenderRows(apiProfile.value.age) : pastureGenderRows) const genderRows = computed(() => normalizeGenderRows(apiProfile.value.age).length ? normalizeGenderRows(apiProfile.value.age) : pastureGenderRows)
const grasslandRows = computed(() => aggregatePastureAreaRows(pastureRows.value).length ? aggregatePastureAreaRows(pastureRows.value) : pastureGrasslandRows) const grasslandRows = computed(() => {
const aggregatedRows = aggregatePastureAreaRows(pastureRows.value)
return completePastureGrasslandRows(aggregatedRows.length ? aggregatedRows : pastureGrasslandRows)
})
const mapRanking = computed(() => ({ const mapRanking = computed(() => ({
title: "牧户分布排行", title: "牧户分布排行",
metricLabel: "牧户", metricLabel: "牧户",
@ -325,6 +337,26 @@ const grasslandPanel = computed(() => ({
type: "heat", type: "heat",
rows: grasslandRows.value, rows: grasslandRows.value,
})) }))
const householdColumnPanel = computed(() => ({
key: "pasture-household-column",
type: "column",
scrollable: false,
rows: normalizeVisualRows(townRows.value).slice(0, 10).map((item, index) => ({
...item,
unit: item.unit || "户",
color: pasturePalette[index % pasturePalette.length],
})),
}))
const grasslandColumnPanel = computed(() => ({
key: "pasture-grassland-column",
type: "bar",
variant: "raceBar",
rows: grasslandRows.value.map((item, index) => ({
...item,
unit: item.unit || "万亩",
color: pasturePalette[(index + 1) % pasturePalette.length],
})),
}))
const onlinePanel = computed(() => ({ const onlinePanel = computed(() => ({
key: "pasture-online", key: "pasture-online",
type: "line", type: "line",
@ -333,7 +365,7 @@ const onlinePanel = computed(() => ({
const agePanel = computed(() => ({ const agePanel = computed(() => ({
key: "pasture-age", key: "pasture-age",
type: "column", type: "column",
variant: "slim", variant: "age",
rows: ageRows.value, rows: ageRows.value,
})) }))
const genderPanel = computed(() => ({ const genderPanel = computed(() => ({
@ -362,7 +394,25 @@ const activePastureVideoIndex = computed(() => {
}) })
const currentPastureVideo = computed(() => pastureVideoRows.value[activePastureVideoIndex.value] || fallbackPastureVideoRows[0]) const currentPastureVideo = computed(() => pastureVideoRows.value[activePastureVideoIndex.value] || fallbackPastureVideoRows[0])
function normalizeVisualRows(rows = []) {
return (Array.isArray(rows) ? rows : [])
.map((item, index) => ({
...item,
key: item.key || item.code || item.name || `${index}`,
name: item.name || item.areaName || item.area_name || item.townName || "未标注",
value: Number(item.value ?? item.number ?? item.count ?? item.num) || 0,
unit: item.unit || "",
}))
.filter((item) => item.name && Number.isFinite(item.value) && item.value >= 0)
}
function updateScreenViewportHeight() {
screenViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight
}
onMounted(() => { onMounted(() => {
updateScreenViewportHeight()
window.addEventListener("resize", updateScreenViewportHeight)
emitPastureTopStats() emitPastureTopStats()
initPastureMap() initPastureMap()
loadPastureProfile() loadPastureProfile()
@ -371,6 +421,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
destroyed = true destroyed = true
window.removeEventListener("resize", updateScreenViewportHeight)
stopPastureVideoPlayer() stopPastureVideoPlayer()
if (mapLoadTimer) window.clearTimeout(mapLoadTimer) if (mapLoadTimer) window.clearTimeout(mapLoadTimer)
if (pastureEnrichTimer) window.clearTimeout(pastureEnrichTimer) if (pastureEnrichTimer) window.clearTimeout(pastureEnrichTimer)
@ -1002,6 +1053,26 @@ function completePastureOnlineRows(rows = []) {
}) })
} }
function completePastureGrasslandRows(rows = []) {
const rowMap = new Map()
;(Array.isArray(rows) ? rows : []).forEach((row) => {
const codeKey = String(row?.key || row?.code || row?.areaCode || row?.area_code || "").trim()
const nameKey = normalizePastureTownName(row?.name || row?.areaName || row?.area_name || row?.townName || row?.zoneName || row?.township)
;[codeKey, nameKey].filter(Boolean).forEach((key) => rowMap.set(key, row))
})
return pastureTownRows
.map((town) => {
const row = rowMap.get(String(town.key || "")) || rowMap.get(town.name)
return {
key: town.key,
name: town.name,
value: Number(row?.value) || 0,
unit: row?.unit || "万亩",
}
})
.sort((a, b) => b.value - a.value)
}
function normalizeAgeRows(rows = []) { function normalizeAgeRows(rows = []) {
const groups = new Map() const groups = new Map()
;(Array.isArray(rows) ? rows : []).forEach((item) => { ;(Array.isArray(rows) ? rows : []).forEach((item) => {
@ -1486,8 +1557,8 @@ function isLngLatInPolygon(point, polygon) {
.pasture-left, .pasture-left,
.pasture-right { .pasture-right {
position: absolute; position: absolute;
top: 176px; top: 128px;
bottom: 112px; bottom: 8px;
z-index: 6; z-index: 6;
width: 398px; width: 398px;
perspective: 700px; perspective: 700px;
@ -1550,6 +1621,244 @@ function isLngLatInPolygon(point, polygon) {
} }
} }
.pasture-household-rank {
display: flex;
flex-direction: column;
gap: 4px;
height: 100%;
padding: 1px 12px 3px;
box-sizing: border-box;
}
.pasture-household-rank-row {
position: relative;
flex: 1 1 0;
min-height: 24px;
}
.pasture-household-rank-row::before {
position: absolute;
left: 0;
top: 8px;
width: 5px;
height: 5px;
border: 1px solid var(--rank-color);
border-radius: 50%;
content: "";
background: rgba(6, 28, 40, 0.92);
box-shadow: 0 0 9px var(--rank-color);
}
.pasture-household-rank-row.is-leading .pasture-rank-value {
color: #ffffff;
text-shadow: 0 0 12px rgba(35, 246, 200, 0.58);
}
.pasture-rank-copy {
display: grid;
grid-template-columns: 32px minmax(0, 1fr) auto;
gap: 7px;
align-items: center;
height: 18px;
padding-left: 12px;
}
.pasture-rank-index {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 15px;
border: 1px solid rgba(48, 220, 255, 0.24);
color: var(--rank-color);
font-size: 10px;
line-height: 1;
font-weight: 800;
background: linear-gradient(90deg, rgba(48, 220, 255, 0.12), rgba(48, 220, 255, 0.02));
box-shadow: inset 0 0 10px rgba(48, 220, 255, 0.1);
}
.pasture-rank-name {
overflow: hidden;
color: rgba(226, 250, 255, 0.9);
font-size: 14px;
line-height: 17px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.pasture-rank-value {
color: rgba(240, 253, 255, 0.94);
font-size: 14px;
line-height: 17px;
font-weight: 900;
}
.pasture-rank-value em {
margin-left: 4px;
color: rgba(210, 242, 248, 0.72);
font-size: 10px;
font-style: normal;
font-weight: 700;
}
.pasture-rank-track {
position: relative;
height: 6px;
margin: 1px 0 0 45px;
overflow: hidden;
border: 1px solid rgba(48, 220, 255, 0.12);
background:
linear-gradient(90deg, rgba(48, 220, 255, 0.1), rgba(48, 220, 255, 0.02)),
rgba(3, 22, 33, 0.72);
box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.24);
}
.pasture-rank-track i {
display: block;
width: var(--rank-rate);
height: 100%;
background: linear-gradient(90deg, rgba(35, 246, 200, 0.18), var(--rank-color));
box-shadow: 0 0 12px var(--rank-color);
}
.pasture-grassland-profile {
display: grid;
grid-template-rows: 54px minmax(0, 1fr);
gap: 5px;
height: 100%;
padding: 2px 11px 3px;
box-sizing: border-box;
}
.pasture-grassland-featured {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 9px;
min-height: 0;
}
.pasture-grassland-feature-card {
position: relative;
overflow: hidden;
padding: 5px 9px 6px;
border: 1px solid rgba(48, 220, 255, 0.18);
background:
radial-gradient(circle at 15% 20%, color-mix(in srgb, var(--area-color) 36%, transparent), transparent 42%),
linear-gradient(135deg, rgba(19, 90, 112, 0.24), rgba(3, 28, 42, 0.5));
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.03),
inset 0 -18px 28px rgba(0, 0, 0, 0.18),
0 0 14px rgba(48, 220, 255, 0.08);
}
.pasture-grassland-feature-card::after {
position: absolute;
right: -18px;
top: -18px;
width: 54px;
height: 54px;
border: 1px solid color-mix(in srgb, var(--area-color) 55%, transparent);
border-radius: 50%;
content: "";
opacity: 0.5;
box-shadow: 0 0 20px color-mix(in srgb, var(--area-color) 42%, transparent);
}
.pasture-grassland-feature-card span {
display: block;
color: rgba(220, 250, 255, 0.82);
font-size: 12px;
line-height: 14px;
font-weight: 800;
}
.pasture-grassland-feature-card strong {
display: block;
margin-top: 1px;
color: #ffffff;
font-size: 19px;
line-height: 22px;
font-weight: 900;
text-shadow: 0 0 12px color-mix(in srgb, var(--area-color) 50%, transparent);
}
.pasture-grassland-feature-card em,
.pasture-grassland-mini-row em {
margin-left: 4px;
color: rgba(216, 244, 250, 0.72);
font-size: 10px;
font-style: normal;
font-weight: 800;
}
.pasture-feature-track {
position: absolute;
right: 10px;
bottom: 6px;
left: 10px;
height: 3px;
overflow: hidden;
background: rgba(14, 57, 72, 0.72);
}
.pasture-feature-track i {
display: block;
width: var(--area-rate);
height: 100%;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.18), var(--area-color));
box-shadow: 0 0 12px var(--area-color);
}
.pasture-grassland-mini-list {
display: flex;
flex-direction: column;
gap: 3px;
min-height: 0;
}
.pasture-grassland-mini-row {
display: grid;
grid-template-columns: 64px minmax(0, 1fr) 70px;
gap: 6px;
align-items: center;
min-height: 14px;
}
.pasture-grassland-mini-row span {
overflow: hidden;
color: rgba(222, 250, 255, 0.84);
font-size: 12px;
line-height: 14px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.pasture-grassland-mini-row strong {
justify-self: end;
color: rgba(245, 253, 255, 0.94);
font-size: 12px;
line-height: 14px;
font-weight: 900;
}
.pasture-mini-track {
height: 5px;
overflow: hidden;
border: 1px solid rgba(48, 220, 255, 0.12);
background: rgba(8, 39, 54, 0.66);
}
.pasture-mini-track i {
display: block;
width: var(--area-rate);
height: 100%;
background: linear-gradient(90deg, rgba(48, 220, 255, 0.08), var(--area-color));
box-shadow: 0 0 10px var(--area-color);
}
.pasture-video-card { .pasture-video-card {
.m-card-bd-content { .m-card-bd-content {
bottom: 12px; bottom: 12px;
@ -1762,7 +2071,11 @@ function isLngLatInPolygon(point, polygon) {
color: #ffffff !important; color: #ffffff !important;
background: none !important; background: none !important;
-webkit-text-fill-color: #ffffff !important; -webkit-text-fill-color: #ffffff !important;
text-shadow: none !important; font-weight: 800 !important;
filter: brightness(1.2) contrast(1.08) !important;
text-shadow:
0 0 1px rgba(255, 255, 255, 0.96),
0 0 8px rgba(255, 255, 255, 0.22) !important;
} }
.pasture-management .business-bars.is-distribution { .pasture-management .business-bars.is-distribution {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save