feat: refine screen dashboards

V2.0
M先生 1 month ago
parent 316ea8aeda
commit 1aed9d07db
  1. 0
      docs/assets/AlibabaPuHuiTi-14435b65-balabala-1782661552655.woff2
  2. 0
      docs/assets/SmartChartRenderer-8fd37aa6-balabala-1782661552655.css
  3. 4
      docs/assets/SmartChartRenderer-bbd6b2e2-1782661552655.js
  4. 0
      docs/assets/bg-3acb94e3-balabala-1782661552655.png
  5. 0
      docs/assets/bottom-menu-arrow-big-180b7071-balabala-1782661552655.svg
  6. 0
      docs/assets/bottom-menu-arrow-small-19304179-balabala-1782661552655.svg
  7. 0
      docs/assets/bottom-menu-bg-b465793d-balabala-1782661552655.png
  8. 0
      docs/assets/bottom-menu-btn-ee5c8064-balabala-1782661552655.png
  9. 0
      docs/assets/bottom-menu-btn-hover-df33e514-balabala-1782661552655.png
  10. 0
      docs/assets/chinaBlurLine-1a2cbc0f-balabala-1782661552655.png
  11. 0
      docs/assets/content-bottom-left-arrow-3a8a61f3-balabala-1782661552655.svg
  12. 0
      docs/assets/content-bottom-right-arrow-a3c5270d-balabala-1782661552655.svg
  13. 0
      docs/assets/content-middle-line-162bc0d5-balabala-1782661552655.svg
  14. 0
      docs/assets/focus_bg-b6803e2d-balabala-1782661552655.png
  15. 0
      docs/assets/focus_move_bg-cf41417a-balabala-1782661552655.png
  16. 0
      docs/assets/header-bg-65b38012-balabala-1782661552655.png
  17. 0
      docs/assets/huiguang-3c5dfda5-balabala-1782661552655.png
  18. 0
      docs/assets/icon1-6f7c222f-balabala-1782661552655.png
  19. 0
      docs/assets/icon2-4f77ccec-balabala-1782661552655.png
  20. 2
      docs/assets/index-1782661552655.js
  21. 1
      docs/assets/index-35304d5e-balabala-1782661552655.css
  22. 1
      docs/assets/index-539daacd-1782661552655.js
  23. 1
      docs/assets/index-92e8e8a8-1782659605222.js
  24. 1
      docs/assets/index-9849d193-balabala-1782659605222.css
  25. 2
      docs/assets/index-9d7c7b38-balabala-1782661552655.css
  26. 0
      docs/assets/index-b8a77371-balabala-1782661552655.css
  27. 4525
      docs/assets/index-c9e6302b-1782661552655.js
  28. 4527
      docs/assets/index-dd7cb1f5-1782659605222.js
  29. 0
      docs/assets/left-kuang-5d6bd1ae-balabala-1782661552655.svg
  30. 0
      docs/assets/menu-btn-91a961cf-balabala-1782661552655.png
  31. 0
      docs/assets/menu-btn-hover-62eba0bb-balabala-1782661552655.png
  32. 0
      docs/assets/ocean-bg-8c848b8f-balabala-1782661552655.png
  33. 0
      docs/assets/pathLine2-d4134d4b-balabala-1782661552655.png
  34. 0
      docs/assets/right-kuang-b1937caf-balabala-1782661552655.svg
  35. 0
      docs/assets/ring2-a1b91808-balabala-1782661552655.png
  36. 0
      docs/assets/ring3-fe0f60d7-balabala-1782661552655.png
  37. 0
      docs/assets/ring4-f2e324d6-balabala-1782661552655.png
  38. 0
      docs/assets/rotationBorder1-2ce8e0ef-balabala-1782661552655.png
  39. 0
      docs/assets/rotationBorder2-7496b968-balabala-1782661552655.png
  40. 0
      docs/assets/saoguang-ccf84a88-balabala-1782661552655.svg
  41. 0
      docs/assets/title-zs1-c15a5ee8-balabala-1782661552655.svg
  42. 4
      docs/index.html
  43. 2
      src/views/forestGrassWet/assets.js
  44. 59
      src/views/forestGrassWet/map.js
  45. 60
      src/views/forestGrassWet/map.vue
  46. 307
      src/views/grassLivestockBalance/index.vue
  47. 605
      src/views/home/index.vue
  48. 118
      src/views/yakManagement/index.vue

@ -1,2 +1,2 @@
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{L as b,H}from"./index-dd7cb1f5-1782659605222.js";import{e as h,h as F,q as R,o as g,c as p,a as f,t as u,g as L,b as V,u as q,n as P}from"./index-1782659605222.js";const j={class:"smart-chart-header"},G={key:0},E={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"],z=h(()=>{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=h(()=>{const t=i.chart.rows||[];return i.chart.type==="bar"&&x(t)}),y=h(()=>{const t=i.chart.source||{},e=[t.mode,t.table,t.period,t.generatedAt].filter(Boolean);return e.length?e.join(" / "):""});F(()=>{R(()=>{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 b(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 b(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} 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{L as b,H}from"./index-c9e6302b-1782661552655.js";import{e as h,l as F,v as R,o as g,c as p,a as f,t as u,g as L,b as V,u as P,n as j}from"./index-1782661552655.js";const q={class:"smart-chart-header"},G={key:0},E={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"],z=h(()=>{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=h(()=>{const t=i.chart.rows||[];return i.chart.type==="bar"&&x(t)}),y=h(()=>{const t=i.chart.source||{},e=[t.mode,t.table,t.period,t.generatedAt].filter(Boolean);return e.length?e.join(" / "):""});F(()=>{R(()=>{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 b(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 b(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 b(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",j,[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)):L("",!0)]),V(q(H),{class:"smart-chart",option:z.value,autoresize:""},null,8,["option"]),y.value?(g(),p("div",E,u(y.value),1)):L("",!0)],2))}};export{J as default}; {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 b(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:j(["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)):L("",!0)]),V(P(H),{class:"smart-chart",option:z.value,autoresize:""},null,8,["option"]),y.value?(g(),p("div",E,u(y.value),1)):L("",!0)],2))}};export{J as default};

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

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 @@
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,P as w,Q as h,e as R,o as _,c as f,a as e,J as S,G as g,H as y,t as b,g as V,d as p,R as k}from"./index-1782661552655.js";const B={class:"login-screen"},D={class:"login-shell"},H={class:"login-field"},M={class:"login-field"},P={key:0,class:"login-error"},U=["disabled"],q={__name:"index",setup(v){const d=w(),n=h(),i=p(""),a=p(""),u=p(!1),o=p(""),r=R(()=>{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",H,[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",P,b(o.value),1)):V("",!0),e("button",{class:"login-submit",disabled:u.value,type:"submit"},b(u.value?"登录中":"登录"),9,U)],32)])]))}},N=x(q,[["__scopeId","data-v-02c20620"]]);export{N as default};

@ -1 +0,0 @@
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,P as w,Q as h,e as B,o as _,c as f,a as e,D,A as g,B as y,t as b,g as R,d as p,R as S}from"./index-1782659605222.js";const V={class:"login-screen"},k={class:"login-shell"},M={class:"login-field"},P={class:"login-field"},U={key:0,class:"login-error"},q=["disabled"],A={__name:"index",setup(v){const d=w(),n=h(),i=p(""),a=p(""),u=p(!1),o=p(""),r=B(()=>{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 S(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",V,[e("div",k,[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:D(l,["prevent"])},[s[4]||(s[4]=e("div",{class:"login-panel-title"},"用户登录",-1)),e("label",M,[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",P,[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",U,b(o.value),1)):R("",!0),e("button",{class:"login-submit",disabled:u.value,type:"submit"},b(u.value?"登录中":"登录"),9,q)],32)])]))}},I=x(A,[["__scopeId","data-v-02c20620"]]);export{I as default};

File diff suppressed because one or more lines are too long

@ -1 +1 @@
.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-1782659605222.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%}} .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-1782661552655.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%}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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-1782659605222.js"></script> <script type="module" crossorigin src="./assets/index-1782661552655.js"></script>
<link rel="stylesheet" href="./assets/index-b8a77371-balabala-1782659605222.css"> <link rel="stylesheet" href="./assets/index-b8a77371-balabala-1782661552655.css">
</head> </head>
<body> <body>

@ -81,7 +81,7 @@ export class Assets {
assets.push({ assets.push({
type: "File", type: "File",
name: "chinaMap", name: "chinaMap",
path: base_url + "assets/json/echarts-china.json", path: base_url + "assets/json/中华人民共和国.json",
}) })
} }
// 资源加载 // 资源加载

@ -119,8 +119,8 @@ export class World extends Mini3d {
// 缩放比例 // 缩放比例
this.geoProjectionScale = 650 this.geoProjectionScale = 650
this.tradeGeoProjectionCenter = [104, 36] this.tradeGeoProjectionCenter = [104, 36]
this.tradeGeoProjectionScale = 10.2 this.tradeGeoProjectionScale = 11.4
this.tradeNationalDepth = 0.22 this.tradeNationalDepth = 0.24
// 地图拉伸高度 // 地图拉伸高度
this.depth = 0.5 this.depth = 0.5
this.mapFocusLabelInfo = { this.mapFocusLabelInfo = {
@ -666,17 +666,23 @@ export class World extends Mini3d {
const visibleNames = new Set(["四川", "青海", "甘肃", "陕西", "重庆", "云南", "西藏", "新疆"]) const visibleNames = new Set(["四川", "青海", "甘肃", "陕西", "重庆", "云南", "西藏", "新疆"])
;(chinaMapData.features || []).forEach((feature) => { ;(chinaMapData.features || []).forEach((feature) => {
const name = feature.properties?.name || "" const name = feature.properties?.name || ""
if (!visibleNames.has(name)) return const displayName = this.normalizeTradeProvinceName(name)
if (!visibleNames.has(displayName)) return
const center = this.getFeatureCenterLngLat(feature) const center = this.getFeatureCenterLngLat(feature)
if (!center) return if (!center) return
const label = this.label3d.create("", "trade-national-label", true) const label = this.label3d.create("", "trade-national-label", true)
label.init(`<div class="trade-national-name">${escapeHtml(name)}</div>`, this.getTradeLocalPoint(center.lng, center.lat, 0.82)) label.init(`<div class="trade-national-name">${escapeHtml(displayName)}</div>`, this.getTradeLocalPoint(center.lng, center.lat, 0.82))
this.label3d.setLabelStyle(label, 0.012, "x") this.label3d.setLabelStyle(label, 0.012, "x")
label.setParent(this.tradeNationalLabelGroup) label.setParent(this.tradeNationalLabelGroup)
label.visible = true label.visible = true
this.tradeNationalLabels.push(label) this.tradeNationalLabels.push(label)
}) })
} }
normalizeTradeProvinceName(name = "") {
return String(name)
.replace(/省|市|自治区|壮族|回族|维吾尔|特别行政区/g, "")
.trim()
}
getFeatureCenterLngLat(feature) { getFeatureCenterLngLat(feature) {
const center = feature?.properties?.cp || feature?.properties?.centroid || feature?.properties?.center const center = feature?.properties?.cp || feature?.properties?.centroid || feature?.properties?.center
if (Array.isArray(center) && Number.isFinite(Number(center[0])) && Number.isFinite(Number(center[1]))) { if (Array.isArray(center) && Number.isFinite(Number(center[0])) && Number.isFinite(Number(center[1]))) {
@ -1761,10 +1767,6 @@ export class World extends Mini3d {
name: row.name || row.destRegion || row.destination || `流向${index + 1}`, name: row.name || row.destRegion || row.destination || `流向${index + 1}`,
lng, lng,
lat, lat,
visualLng: toFiniteNumber(row.visualLng ?? row.visual_lng, lng),
visualLat: toFiniteNumber(row.visualLat ?? row.visual_lat, lat),
visualX: toFiniteNumber(row.visualX ?? row.visual_x, NaN),
visualY: toFiniteNumber(row.visualY ?? row.visual_y, NaN),
labelSide: row.labelSide || row.label_side || "", labelSide: row.labelSide || row.label_side || "",
value, value,
headCount: value, headCount: value,
@ -1785,28 +1787,34 @@ export class World extends Mini3d {
: rows.reduce((sum, row) => sum + toFiniteNumber(row.value), 0) : rows.reduce((sum, row) => sum + toFiniteNumber(row.value), 0)
return `<div class="trade-market-card"> return `<div class="trade-market-card">
<div class="market-title">${escapeHtml(source.name)}</div> <div class="market-title">${escapeHtml(source.name)}</div>
<div class="market-coord">交易源点</div> <div class="market-total"><span>${this.formatMapIntegerValue(total)}</span><em></em></div>
<div class="market-total"><span>${this.formatMapIntegerValue(total)}</span><em></em></div>
</div>` </div>`
} }
getTradeFlowDisplayName(name = "") {
const text = String(name || "")
.replace(/维吾尔自治区|壮族自治区|回族自治区|自治区|特别行政区|省|市|区|县|镇|乡|街道/g, "")
.replace(/\s+/g, "")
const parts = text.match(/[\u4e00-\u9fa5]{2,8}/g) || []
const last = parts.length ? parts[parts.length - 1] : text
return last.length > 7 ? `${last.slice(0, 7)}` : last || "目的地"
}
getTradeFlowLabelHtml(row) { getTradeFlowLabelHtml(row) {
const sideClass = row.labelSide ? ` is-${String(row.labelSide).replace(/[^a-z0-9-]/gi, "")}` : "" const sideClass = row.labelSide ? ` is-${String(row.labelSide).replace(/[^a-z0-9-]/gi, "")}` : ""
return `<div class="trade-flow-card${sideClass}"> return `<div class="trade-flow-card${sideClass}">
<div class="flow-head"><span>${String(row.rank).padStart(2, "0")}</span><strong>${escapeHtml(row.name)}</strong></div> <div class="flow-head"><span>${String(row.rank).padStart(2, "0")}</span><strong>${escapeHtml(this.getTradeFlowDisplayName(row.name))}</strong></div>
<div class="flow-body"><em>${this.formatMapIntegerValue(row.value)}</em><b>${escapeHtml(row.unit || "")}</b></div> <div class="flow-body"><em>${this.formatMapIntegerValue(row.value)}</em><b>${escapeHtml(row.unit || "")}</b></div>
<div class="flow-foot">出证 ${this.formatMapIntegerValue(row.recordCount)} </div>
</div>` </div>`
} }
getTradeFlowLabelOffset(row, index) { getTradeFlowLabelOffset(row, index) {
const map = { const map = {
left: new Vector3(-1.56, 0.18 + (index % 2) * 0.2, 0), left: new Vector3(-1.12, 0.18 + (index % 2) * 0.16, 0),
right: new Vector3(1.38, 0.16 - (index % 2) * 0.18, 0), right: new Vector3(1.04, 0.16 - (index % 2) * 0.14, 0),
top: new Vector3(-0.52 + (index % 2) * 1.0, 1.12, 0), top: new Vector3(-0.44 + (index % 2) * 0.78, 0.76, 0),
bottom: new Vector3(-0.5 + (index % 2) * 0.96, -1.1, 0), bottom: new Vector3(-0.4 + (index % 2) * 0.72, -0.76, 0),
"top-left": new Vector3(-1.52, 1.02, 0), "top-left": new Vector3(-1.04, 0.72, 0),
"top-right": new Vector3(1.28, 1.0, 0), "top-right": new Vector3(0.96, 0.72, 0),
"bottom-left": new Vector3(-1.52, -0.96, 0), "bottom-left": new Vector3(-1.04, -0.68, 0),
"bottom-right": new Vector3(1.28, -0.96, 0), "bottom-right": new Vector3(0.96, -0.68, 0),
} }
return map[row.labelSide] || [ return map[row.labelSide] || [
new Vector3(-1.24, 0.3, 0), new Vector3(-1.24, 0.3, 0),
@ -1820,12 +1828,7 @@ export class World extends Mini3d {
return new Vector3(x, -y, this.tradeNationalDepth + localZ) return new Vector3(x, -y, this.tradeNationalDepth + localZ)
} }
getTradeFlowPoint(row, localZ = 0) { getTradeFlowPoint(row, localZ = 0) {
const visualX = toFiniteNumber(row.visualX ?? row.visual_x, NaN) return this.getTradeLocalPoint(row.lng, row.lat, localZ)
const visualY = toFiniteNumber(row.visualY ?? row.visual_y, NaN)
if (Number.isFinite(visualX) && Number.isFinite(visualY)) {
return new Vector3(visualX, visualY, this.tradeNationalDepth + localZ)
}
return this.getTradeLocalPoint(row.visualLng ?? row.lng, row.visualLat ?? row.lat, localZ)
} }
tradeGeoProjection(args) { tradeGeoProjection(args) {
return geoMercator().center(this.tradeGeoProjectionCenter).scale(this.tradeGeoProjectionScale).translate([0, 0])(args) return geoMercator().center(this.tradeGeoProjectionCenter).scale(this.tradeGeoProjectionScale).translate([0, 0])(args)
@ -1849,7 +1852,7 @@ export class World extends Mini3d {
if (this.scatterGroup) this.scatterGroup.visible = !shouldShow && this.mapAnimationComplete if (this.scatterGroup) this.scatterGroup.visible = !shouldShow && this.mapAnimationComplete
if (this.topicLayerMaterial && shouldShow) this.topicLayerMaterial.opacity = 0 if (this.topicLayerMaterial && shouldShow) this.topicLayerMaterial.opacity = 0
if (shouldShow && this.mapAnimationComplete) { if (shouldShow && this.mapAnimationComplete) {
this.animateMapCamera(new Vector3(0, 12.0, 17.8), new Vector3(0, -0.08, 0), 0.65) this.animateMapCamera(new Vector3(0, 11.4, 16.2), new Vector3(0, -0.05, 0), 0.65)
} else if (!shouldShow && this.mapAnimationComplete) { } else if (!shouldShow && this.mapAnimationComplete) {
this.applyLayerMapView(this.currentTopicLayer, { force: true }) this.applyLayerMapView(this.currentTopicLayer, { force: true })
} }
@ -2026,7 +2029,7 @@ export class World extends Mini3d {
sourceLabel.element.className = "trade-market-label" sourceLabel.element.className = "trade-market-label"
sourceLabel.init( sourceLabel.init(
this.getTradeMarketHtml(source, rows), this.getTradeMarketHtml(source, rows),
this.getTradeLocalPoint(source.lng, source.lat, 1.46).add(new Vector3(0.92, -0.86, 0)) this.getTradeLocalPoint(source.lng, source.lat, 1.46).add(new Vector3(0.52, -0.98, 0))
) )
sourceLabel.visible = true sourceLabel.visible = true
sourceLabel.userData = { ...source, rows: rows.length } sourceLabel.userData = { ...source, rows: rows.length }

@ -481,14 +481,14 @@ defineExpose({
.trade-market-card { .trade-market-card {
position: relative; position: relative;
width: 152px; width: 128px;
padding: 7px 9px 7px; padding: 6px 8px 6px;
border: 1px solid rgba(255, 221, 117, 0.58); border: 1px solid rgba(255, 221, 117, 0.58);
border-radius: 4px; border-radius: 4px;
background: background:
linear-gradient(110deg, rgba(68, 44, 10, 0.82), rgba(4, 24, 34, 0.7)), linear-gradient(110deg, rgba(68, 44, 10, 0.72), rgba(4, 24, 34, 0.58)),
rgba(5, 20, 30, 0.72); rgba(5, 20, 30, 0.58);
box-shadow: 0 0 12px rgba(255, 207, 98, 0.18), inset 0 0 12px rgba(255, 207, 98, 0.08); box-shadow: 0 0 10px rgba(255, 207, 98, 0.14), inset 0 0 10px rgba(255, 207, 98, 0.06);
transform: translate(38%, 18%); transform: translate(38%, 18%);
&:before { &:before {
@ -516,20 +516,13 @@ defineExpose({
.market-title { .market-title {
overflow: hidden; overflow: hidden;
color: #ffffff; color: #ffffff;
font-size: 12px; font-size: 11px;
line-height: 16px; line-height: 14px;
font-weight: 900; font-weight: 900;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.market-coord {
margin-top: 2px;
color: rgba(255, 255, 255, 0.72);
font-size: 10px;
line-height: 13px;
}
.market-total { .market-total {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
@ -538,8 +531,8 @@ defineExpose({
span { span {
color: #ffffff; color: #ffffff;
font-size: 17px; font-size: 15px;
line-height: 20px; line-height: 18px;
font-weight: 900; font-weight: 900;
text-shadow: 0 0 10px rgba(255, 221, 117, 0.6); text-shadow: 0 0 10px rgba(255, 221, 117, 0.6);
} }
@ -555,14 +548,14 @@ defineExpose({
.trade-flow-card { .trade-flow-card {
position: relative; position: relative;
width: 128px; width: 96px;
padding: 6px 8px 5px; padding: 5px 7px 5px;
border: 1px solid rgba(85, 226, 255, 0.38); border: 1px solid rgba(85, 226, 255, 0.32);
border-radius: 4px; border-radius: 3px;
background: background:
linear-gradient(105deg, rgba(4, 37, 50, 0.76), rgba(3, 17, 27, 0.64)), linear-gradient(105deg, rgba(4, 37, 50, 0.58), rgba(3, 17, 27, 0.48)),
rgba(1, 17, 27, 0.64); rgba(1, 17, 27, 0.46);
box-shadow: 0 0 10px rgba(48, 220, 255, 0.14), inset 0 0 10px rgba(39, 240, 192, 0.06); box-shadow: 0 0 8px rgba(48, 220, 255, 0.12), inset 0 0 8px rgba(39, 240, 192, 0.05);
transform: translate(36%, -18%); transform: translate(36%, -18%);
&.is-left, &.is-left,
@ -641,16 +634,16 @@ defineExpose({
span { span {
flex: 0 0 auto; flex: 0 0 auto;
color: #ffe076; color: #ffe076;
font-size: 10px; font-size: 9px;
line-height: 13px; line-height: 12px;
font-weight: 900; font-weight: 900;
} }
strong { strong {
overflow: hidden; overflow: hidden;
color: #ffffff; color: #ffffff;
font-size: 11px; font-size: 10px;
line-height: 14px; line-height: 13px;
font-weight: 800; font-weight: 800;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@ -661,12 +654,12 @@ defineExpose({
display: flex; display: flex;
align-items: baseline; align-items: baseline;
gap: 3px; gap: 3px;
margin-top: 3px; margin-top: 2px;
em { em {
color: #ffffff; color: #ffffff;
font-size: 15px; font-size: 13px;
line-height: 17px; line-height: 15px;
font-style: normal; font-style: normal;
font-weight: 900; font-weight: 900;
text-shadow: 0 0 10px rgba(48, 220, 255, 0.55); text-shadow: 0 0 10px rgba(48, 220, 255, 0.55);
@ -679,13 +672,6 @@ defineExpose({
} }
} }
.flow-foot {
margin-top: 2px;
color: rgba(255, 255, 255, 0.62);
font-size: 9px;
line-height: 11px;
white-space: nowrap;
}
} }
.china-label { .china-label {

@ -233,7 +233,7 @@ import {
const emit = defineEmits(["loading-progress", "map-ready"]) const emit = defineEmits(["loading-progress", "map-ready"])
const selectorCardWidth = 326 const selectorCardWidth = 326
const selectorCardHeight = 620 const selectorCardHeight = 720
const sheetCardWidth = 1310 const sheetCardWidth = 1310
const mainCardHeight = 820 const mainCardHeight = 820
@ -2176,4 +2176,309 @@ function getProcessStepState(index) {
transform: translateX(18px); transform: translateX(18px);
} }
} }
.grass-livestock-balance {
background:
linear-gradient(rgba(31, 169, 190, 0.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(31, 169, 190, 0.022) 1px, transparent 1px),
radial-gradient(circle at 50% 35%, rgba(35, 128, 133, 0.1), transparent 34%),
linear-gradient(135deg, #020b13 0%, #061d2b 48%, #020b13 100%);
background-size: 44px 44px, 44px 44px, 100% 100%, 100% 100%;
&::before,
&::after {
opacity: 0.26;
}
}
.balance-backdrop {
top: 150px;
height: 800px;
background:
linear-gradient(rgba(48, 220, 255, 0.018) 1px, transparent 1px),
linear-gradient(90deg, rgba(48, 220, 255, 0.016) 1px, transparent 1px);
background-size: 44px 44px;
&::before,
&::after {
opacity: 0.32;
}
}
.balance-orbit,
.balance-energy-band,
.balance-field-lines,
.balance-wait-core::before,
.balance-core::before {
display: none;
}
.balance-screen {
top: 182px;
}
.balance-card {
filter: drop-shadow(0 16px 26px rgba(0, 0, 0, 0.28));
.m-card-hd {
background:
linear-gradient(90deg, rgba(24, 117, 135, 0.78), rgba(12, 65, 83, 0.52) 56%, rgba(6, 32, 46, 0.08));
&::before {
background: linear-gradient(90deg, #30dcff, rgba(126, 251, 246, 0.36), transparent 76%);
box-shadow: none;
}
}
.m-card-bd {
background:
linear-gradient(180deg, rgba(8, 49, 64, 0.92), rgba(3, 23, 36, 0.92));
&::before {
border-color: rgba(48, 220, 255, 0.2);
box-shadow: inset 0 0 0 1px rgba(126, 251, 246, 0.04);
}
&::after {
opacity: 0.22;
}
}
}
.balance-sheet-card {
filter: drop-shadow(0 18px 30px rgba(0, 0, 0, 0.24));
.m-card-bd {
background:
linear-gradient(180deg, rgba(7, 43, 58, 0.92), rgba(2, 19, 31, 0.94));
&::before {
border-color: rgba(48, 220, 255, 0.18);
border-left-color: rgba(48, 220, 255, 0.28);
border-bottom-color: rgba(48, 220, 255, 0.12);
box-shadow: inset 0 0 0 1px rgba(126, 251, 246, 0.035);
}
}
}
.balance-target-card {
filter: drop-shadow(0 16px 24px rgba(0, 0, 0, 0.26));
.m-card-bd {
background:
linear-gradient(180deg, rgba(7, 45, 58, 0.9), rgba(2, 20, 32, 0.92));
&::before {
border-color: rgba(48, 220, 255, 0.16);
border-left-color: rgba(48, 220, 255, 0.28);
}
}
}
.balance-selector {
grid-template-rows: 40px minmax(0, 1fr) 158px;
gap: 12px;
padding: 12px 12px 14px;
}
.balance-scope-tabs button {
border: 1px solid rgba(48, 220, 255, 0.12);
background: rgba(4, 31, 44, 0.58);
clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
}
.balance-scope-tabs button.active,
.balance-scope-tabs button:hover {
background: linear-gradient(180deg, rgba(19, 98, 112, 0.9), rgba(7, 56, 76, 0.86));
}
.balance-target-list button {
background: rgba(5, 39, 53, 0.72);
&::before {
border: 1px solid rgba(48, 220, 255, 0.1);
border-left-color: rgba(48, 220, 255, 0.2);
}
}
.balance-target-list button.active {
background: linear-gradient(90deg, rgba(119, 98, 38, 0.9), rgba(7, 70, 77, 0.86));
}
.balance-action-box {
background: rgba(3, 28, 40, 0.84);
&::before {
border: 1px solid rgba(48, 220, 255, 0.14);
}
}
.balance-sheet {
padding: 14px 22px 20px;
&::before,
&::after {
display: none;
}
}
.balance-waiting {
grid-template-rows: minmax(0, 1fr) 78px;
}
.balance-wait-core {
gap: 18px;
padding-top: 16px;
}
.balance-core-ring {
width: 164px;
height: 164px;
box-shadow:
0 0 18px rgba(36, 246, 196, 0.12),
inset 0 0 28px rgba(2, 16, 26, 0.74);
&::after {
opacity: 0.36;
}
}
.balance-core-inner {
width: 126px;
height: 126px;
strong {
font-size: 30px;
line-height: 34px;
text-shadow: none;
}
}
.balance-preflight span {
background: rgba(4, 32, 45, 0.78);
border: 1px solid rgba(48, 220, 255, 0.1);
clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
&::before {
display: none;
}
}
.balance-running-head {
padding: 30px 58px 18px;
}
.balance-live-process {
padding: 0 58px 26px;
}
.balance-live-step {
background: rgba(5, 36, 50, 0.76);
&::before {
border: 1px solid rgba(48, 220, 255, 0.08);
}
&.is-active {
background: linear-gradient(90deg, rgba(22, 108, 104, 0.82), rgba(7, 53, 66, 0.86));
}
}
.balance-analysis {
grid-template-rows: 188px 94px minmax(0, 1fr);
gap: 12px;
}
.balance-dashboard,
.balance-compare,
.balance-process,
.balance-decision {
background: linear-gradient(180deg, rgba(7, 48, 62, 0.9), rgba(3, 28, 42, 0.9));
clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%);
&::before {
border: 1px solid rgba(48, 220, 255, 0.12);
box-shadow: none;
}
}
.balance-dashboard {
grid-template-columns: minmax(0, 1fr) 230px minmax(0, 1fr);
padding: 14px 20px;
&::after {
display: none;
}
}
.balance-object-block,
.balance-verdict-card {
background: rgba(4, 31, 44, 0.82);
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
}
.balance-object-block strong,
.balance-verdict-card strong {
font-size: 34px;
line-height: 40px;
text-shadow: none;
}
.balance-verdict-card strong,
.balance-core-inner strong,
.balance-formula-row em,
.balance-bar-row i {
text-shadow: none;
box-shadow: none;
}
.balance-core-ring::before,
.balance-core-ring::after {
animation: none;
}
.balance-dashboard .balance-core-ring {
width: 156px;
height: 156px;
}
.balance-dashboard .balance-core-inner {
width: 120px;
height: 120px;
}
.balance-compare {
gap: 18px;
padding: 14px 20px;
}
.balance-detail {
gap: 12px;
}
.balance-process,
.balance-decision {
padding: 14px 16px;
}
.balance-formula-row {
background: rgba(4, 33, 46, 0.82);
&::before {
border: 1px solid rgba(48, 220, 255, 0.08);
}
}
.balance-conclusion {
background: rgba(70, 55, 22, 0.7);
}
.balance-bar-row div::after,
.balance-progress-line::after {
display: none;
}
.balance-core-caption {
color: rgba(223, 250, 255, 0.72);
}
</style> </style>

@ -29,7 +29,14 @@
:width="398" :width="398"
:height="panel.height" :height="panel.height"
> >
<business-panel-renderer :panel="panel" compact /> <v-chart
v-if="panel.option"
:ref="(el) => setHomeChartRef(panel.key, el)"
class="home-chart"
:option="panel.option"
:autoresize="true"
/>
<business-panel-renderer v-else :panel="panel" compact />
</m-card> </m-card>
</div> </div>
</div> </div>
@ -37,203 +44,334 @@
<script setup> <script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue" import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue"
import * as echarts from "echarts"
import VChart from "vue-echarts"
import mCard from "@/components/mCard/index.vue" import mCard from "@/components/mCard/index.vue"
import MapScene from "@/views/forestGrassWet/map.vue" import MapScene from "@/views/forestGrassWet/map.vue"
import BusinessPanelRenderer from "@/views/forestGrassWet/components/BusinessPanelRenderer.vue" import BusinessPanelRenderer from "@/views/forestGrassWet/components/BusinessPanelRenderer.vue"
import { Assets } from "@/views/forestGrassWet/assets" import { Assets } from "@/views/forestGrassWet/assets"
import { getLayerByKey } from "@/config/layers" import { getLayerByKey } from "@/config/layers"
import { getTopicBusinessStats } from "@/services/openApi"
import {
buildForecastSeries,
forecastPeriods,
getForecastPeriod,
structureRows,
yakForecastRows,
} from "@/views/yakManagement/data"
import { import {
businessMatrixRows,
dataFlowRows,
homeTopStats, homeTopStats,
resourceCompositionRows,
supportRows,
townSynergyRows,
tradeRows,
yakTownRows,
} from "./data" } from "./data"
const emit = defineEmits(["module-stats", "loading-progress", "map-ready"]) const emit = defineEmits(["module-stats", "loading-progress", "map-ready"])
const mapSceneRef = ref(null) const mapSceneRef = ref(null)
const homeMapLayer = getLayerByKey("grassland", "grassland-distribution") const homeMapLayer = getLayerByKey("grassland", "grassland-distribution")
const forestGrassWetStats = ref({})
const homeChartRefs = new Map()
let mapAssets = null let mapAssets = null
let destroyed = false let destroyed = false
let chartMotionTimer = null
const chartMotionTick = ref(0)
const palette = ["#30dcff", "#27f0c0", "#ffd76c", "#7aa7ff", "#ff8f6b", "#a6f06d"]
const sidePanelHeight = 186
const forecastTrendPeriod = getForecastPeriod("2028")
const trendSeries = buildForecastSeries(forecastTrendPeriod)
const activeStructureIndex = computed(() => getLoopIndex(structureRows.length))
const activeForecastYearIndex = computed(() => getLoopIndex(forecastPeriods.length))
const activeForecastTrendIndex = computed(() => getLoopIndex(trendSeries.labels.length))
const activeMonthlyIndex = computed(() => getLoopIndex(yakForecastRows.length))
const leftPanels = computed(() => [
pickTopicPanel("grassland", "grassland-town"),
pickTopicPanel("grassland", "grassland-type-proportion"),
pickTopicPanel("wetland", "wetland-yield"),
pickTopicPanel("woodland", "woodland-type"),
].filter(Boolean).map((panel, index) => normalizeHomePanel(panel, `forest-grass-wet-${index}`)))
const grassland = resourceCompositionRows.find((item) => item.key === "grassland") || {} const rightPanels = computed(() => [
const basicGrassland = businessMatrixRows.find((item) => item.key === "basic-grassland") || {}
const wetlandYield = businessMatrixRows.find((item) => item.key === "wetland-yield") || {}
const publicWoodland = businessMatrixRows.find((item) => item.key === "public-woodland") || {}
const totalResource = computed(() =>
resourceCompositionRows.reduce((sum, item) => sum + Number(item.value || 0), 0)
)
const resourceRows = computed(() =>
resourceCompositionRows.map((item) => ({
...item,
percent: resourcePercent(item),
}))
)
const yakRows = computed(() =>
yakTownRows.slice(0, 6).map((item) => ({
...item,
extra: `${item.percent}%`,
}))
)
const densityRows = computed(() =>
townSynergyRows.slice(0, 6).map((item) => ({
key: item.key,
name: item.name,
value: item.density,
unit: "头/万亩",
extra: `${formatNumber(item.yakCount)}`,
}))
)
const resourceBusinessRows = computed(() => [
{ {
key: "grassland-total", key: "yak-structure",
name: "草地资源", title: "畜群结构占比",
value: grassland.value, height: sidePanelHeight,
unit: grassland.unit, option: structureDonutOption.value,
extra: `${resourcePercent(grassland)}%`,
}, },
{ {
key: "basic-grassland", key: "yak-three-year-forecast",
name: "基本草原", title: "未来三年牦牛预测",
value: basicGrassland.value, height: sidePanelHeight,
unit: basicGrassland.unit, option: threeYearForecastOption.value,
extra: `${basicGrassland.percent || "--"}%`,
}, },
{ {
key: "public-woodland", key: "yak-forecast-trend",
name: "公益林面积", title: "未来存栏趋势",
value: publicWoodland.value, height: sidePanelHeight,
unit: publicWoodland.unit, option: forecastTrendOption.value,
extra: `${publicWoodland.percent || "--"}%`,
}, },
{ {
key: "wetland-yield", key: "yak-monthly-forecast",
name: "湿地生产力", title: "月度数量预测",
value: wetlandYield.value, height: sidePanelHeight,
unit: wetlandYield.unit, option: monthlyForecastOption.value,
extra: `${wetlandYield.percent || "--"}%`,
}, },
]) ])
const tradeStructureRows = computed(() => const structureDonutOption = computed(() => ({
tradeRows.map((item) => ({ animation: true,
...item, animationDuration: 900,
extra: `${item.yoy > 0 ? "+" : ""}${item.yoy}%`, animationDurationUpdate: 700,
})) animationEasing: "cubicOut",
) color: palette,
tooltip: chartTooltip((params) => `${params.name}<br/>占比:${params.percent}%<br/>估算数量:${formatNumber(params.value)}`),
const supportChartRows = computed(() => legend: {
supportRows.map((item) => ({ orient: "vertical",
...item, right: 0,
extra: item.capacityNote, top: "middle",
scaleValue: item.percent, itemWidth: 8,
})) itemHeight: 8,
) itemGap: 5,
formatter(name) {
const townResourceRows = computed(() => const row = structureRows.find((item) => item.name === name)
townSynergyRows.slice(0, 5).map((item) => ({ return row ? `${name} ${formatCompact(row.count)}` : name
key: item.key, },
name: item.name, textStyle: { color: "rgba(222, 250, 255, 0.78)", fontSize: 9 },
value: item.resourceTotal,
unit: "万亩",
extra: `${item.density}头/万亩`,
}))
)
const dataTrendRows = computed(() =>
dataFlowRows.slice(0, 6).map((item) => ({
key: item.key,
name: shortFlowName(item),
value: item.value,
unit: item.unit,
extra: item.trend,
}))
)
const leftPanels = computed(() => [
{
key: "resource-structure",
title: "林草湿资源结构",
height: 186,
type: "donut",
variant: "ring",
rows: resourceRows.value,
}, },
{ series: [
key: "yak-town-rank", {
title: "乡镇牦牛存栏", name: "畜群结构",
height: 186, type: "pie",
type: "bar", selectedMode: "single",
variant: "distribution", selectedOffset: 8,
rows: yakRows.value, radius: ["36%", "62%"],
center: ["30%", "54%"],
avoidLabelOverlap: true,
label: { color: "#fff", fontSize: 9, formatter: "{d}%" },
labelLine: { length: 7, length2: 4, lineStyle: { color: "rgba(223, 250, 255, 0.35)" } },
itemStyle: { borderColor: "rgba(3, 18, 31, 0.92)", borderWidth: 3 },
emphasis: {
scale: true,
scaleSize: 8,
itemStyle: { shadowBlur: 16, shadowColor: "rgba(48, 220, 255, 0.58)" },
},
data: structureRows.map((item, index) => ({
name: item.name,
value: item.count,
selected: index === activeStructureIndex.value,
})),
},
],
}))
const threeYearForecastOption = computed(() => ({
animation: true,
animationDuration: 1100,
animationDurationUpdate: 900,
animationEasing: "cubicOut",
color: ["#30dcff"],
tooltip: {
...chartTooltip(),
trigger: "axis",
axisPointer: { type: "shadow" },
formatter(params) {
const row = forecastPeriods[params[0].dataIndex]
return `${row.label}<br/>预测存栏:${formatNumber(row.stock)}`
},
}, },
{ grid: { left: 42, right: 18, top: 16, bottom: 24 },
key: "capacity-density", xAxis: {
title: "草畜承载密度", type: "category",
height: 186, data: forecastPeriods.map((item) => item.shortLabel),
type: "line", axisLabel: { color: "rgba(223, 250, 255, 0.76)", fontSize: 11 },
rows: densityRows.value, axisLine: { lineStyle: { color: "rgba(48, 220, 255, 0.26)" } },
axisTick: { show: false },
}, },
{ yAxis: {
key: "resource-business", type: "value",
title: "资源业务画像", min(value) {
height: 186, return Math.max(0, Math.floor(value.min / 10000) * 10000)
type: "structure", },
rows: resourceBusinessRows.value, axisLabel: { color: "rgba(223, 250, 255, 0.58)", fontSize: 10, formatter: formatCompact },
splitLine: { lineStyle: { color: "rgba(48, 220, 255, 0.1)", type: "dashed" } },
}, },
]) series: [
{
name: "预测存栏",
type: "bar",
barWidth: 24,
data: forecastPeriods.map((item, index) => ({
value: item.stock,
itemStyle: {
borderRadius: [9, 9, 1, 1],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: index === activeForecastYearIndex.value ? "#ffd76c" : "#30dcff" },
{ offset: 0.72, color: index === activeForecastYearIndex.value ? "rgba(255, 215, 108, 0.7)" : "rgba(48, 220, 255, 0.62)" },
{ offset: 1, color: index === activeForecastYearIndex.value ? "rgba(255, 215, 108, 0.1)" : "rgba(48, 220, 255, 0.08)" },
]),
shadowBlur: index === activeForecastYearIndex.value ? 16 : 8,
shadowColor: index === activeForecastYearIndex.value ? "rgba(255, 215, 108, 0.56)" : "rgba(48, 220, 255, 0.28)",
},
})),
itemStyle: {
borderRadius: [9, 9, 1, 1],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#30dcff" },
{ offset: 0.72, color: "rgba(48, 220, 255, 0.62)" },
{ offset: 1, color: "rgba(48, 220, 255, 0.08)" },
]),
},
label: {
show: true,
position: "top",
color: "#ffffff",
fontSize: 10,
formatter: ({ value }) => formatCompact(value),
},
},
],
}))
const forecastTrendOption = computed(() => {
return {
animation: true,
animationDuration: 1200,
animationDurationUpdate: 900,
animationEasing: "cubicOut",
color: ["#30dcff", "#ffd76c", "#ff8f6b"],
tooltip: { ...chartTooltip(), trigger: "axis" },
legend: {
top: 2,
right: 2,
itemWidth: 10,
itemHeight: 7,
itemGap: 7,
textStyle: { color: "rgba(222, 250, 255, 0.76)", fontSize: 9 },
},
grid: { left: 42, right: 32, top: 30, bottom: 24 },
xAxis: {
type: "category",
boundaryGap: false,
data: trendSeries.labels,
axisLabel: { color: "rgba(223, 250, 255, 0.62)", fontSize: 9, interval: 4 },
axisLine: { lineStyle: { color: "rgba(48, 220, 255, 0.28)" } },
axisTick: { show: false },
},
yAxis: [
{
type: "value",
axisLabel: { color: "rgba(223, 250, 255, 0.58)", fontSize: 9, formatter: formatCompact },
splitLine: { lineStyle: { color: "rgba(48, 220, 255, 0.1)", type: "dashed" } },
},
{
type: "value",
axisLabel: { color: "rgba(223, 250, 255, 0.48)", fontSize: 9, formatter: formatCompact },
splitLine: { show: false },
},
],
series: [
buildLineSeries("预测存栏", trendSeries.stock, "#30dcff", 0, true, activeForecastTrendIndex.value),
buildLineSeries("繁殖补充", trendSeries.births, "#ffd76c", 1, false, activeForecastTrendIndex.value),
buildLineSeries("出栏扣减", trendSeries.outflow, "#ff8f6b", 1, false, activeForecastTrendIndex.value),
],
}
})
const rightPanels = computed(() => [ const monthlyForecastOption = computed(() => ({
{ animation: true,
key: "trade-structure", animationDuration: 1100,
title: "牦牛交易结构", animationDurationUpdate: 800,
height: 186, animationEasing: "cubicOut",
type: "donut", color: ["#30dcff", "#ffd76c"],
rows: tradeStructureRows.value, tooltip: { ...chartTooltip(), trigger: "axis" },
}, legend: {
{ top: 2,
key: "support-industry", right: 8,
title: "产业支撑能力", itemWidth: 10,
height: 186, itemHeight: 7,
type: "column", itemGap: 8,
variant: "slim", textStyle: { color: "rgba(222, 250, 255, 0.76)", fontSize: 9 },
rows: supportChartRows.value,
}, },
{ grid: { left: 34, right: 34, top: 30, bottom: 22 },
key: "town-resource", xAxis: {
title: "乡镇资源协同", type: "category",
height: 186, data: yakForecastRows.map((item) => item.month || item.name),
type: "bar", axisLabel: { color: "rgba(223, 250, 255, 0.68)", fontSize: 9, interval: 1 },
variant: "distribution", axisLine: { lineStyle: { color: "rgba(48, 220, 255, 0.26)" } },
rows: townResourceRows.value, axisTick: { show: false },
},
{
key: "data-flow",
title: "业务数据动态",
height: 186,
type: "heat",
rows: dataTrendRows.value,
}, },
]) yAxis: [
{
type: "value",
axisLabel: { color: "rgba(223, 250, 255, 0.58)", fontSize: 9, formatter: formatCompact },
splitLine: { lineStyle: { color: "rgba(48, 220, 255, 0.1)", type: "dashed" } },
},
{
type: "value",
axisLabel: { color: "rgba(223, 250, 255, 0.48)", fontSize: 9, formatter: formatCompact },
splitLine: { show: false },
},
],
series: [
{
name: "数量",
type: "bar",
barWidth: 9,
data: yakForecastRows.map((item, index) => ({
value: item.count || item.value,
itemStyle: {
borderRadius: [7, 7, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: index === activeMonthlyIndex.value ? "#ffd76c" : "#30dcff" },
{ offset: 1, color: index === activeMonthlyIndex.value ? "rgba(255, 215, 108, 0.08)" : "rgba(48, 220, 255, 0.08)" },
]),
shadowBlur: index === activeMonthlyIndex.value ? 14 : 5,
shadowColor: index === activeMonthlyIndex.value ? "rgba(255, 215, 108, 0.48)" : "rgba(48, 220, 255, 0.22)",
},
})),
itemStyle: {
borderRadius: [7, 7, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#30dcff" },
{ offset: 1, color: "rgba(48, 220, 255, 0.08)" },
]),
},
},
{
name: "增速",
type: "line",
yAxisIndex: 1,
smooth: true,
symbolSize: 5,
data: yakForecastRows.map((item, index) => ({
value: item.speed || item.growthRate || 0,
symbolSize: index === activeMonthlyIndex.value ? 10 : 5,
itemStyle: { color: index === activeMonthlyIndex.value ? "#ffffff" : "#ffd76c" },
label: {
show: index === activeMonthlyIndex.value,
position: "top",
color: "#ffd76c",
fontSize: 9,
formatter: ({ value }) => formatCompact(value),
},
})),
lineStyle: { width: 2.4, color: "#ffd76c" },
itemStyle: { color: "#ffd76c" },
},
],
}))
onMounted(() => { onMounted(() => {
emit("module-stats", { emit("module-stats", {
moduleKey: "home", moduleKey: "home",
items: homeTopStats, items: homeTopStats,
}) })
loadForestGrassWetPanels()
initHomeMap() initHomeMap()
startChartMotion()
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
destroyed = true destroyed = true
stopChartMotion()
homeChartRefs.clear()
mapSceneRef.value?.destroyMap?.() mapSceneRef.value?.destroyMap?.()
}) })
@ -271,22 +409,139 @@ function initHomeMap() {
}) })
} }
function resourcePercent(item = {}) { function setHomeChartRef(key, chart) {
const total = totalResource.value || 1 if (!key) return
return Number(((Number(item.value || 0) / total) * 100).toFixed(1)) if (chart) {
homeChartRefs.set(key, chart)
} else {
homeChartRefs.delete(key)
}
}
function startChartMotion() {
stopChartMotion()
chartMotionTimer = window.setInterval(playChartMotion, 2600)
window.setTimeout(playChartMotion, 800)
}
function stopChartMotion() {
if (!chartMotionTimer) return
window.clearInterval(chartMotionTimer)
chartMotionTimer = null
}
function playChartMotion() {
if (destroyed) return
chartMotionTick.value += 1
playYakStructureMotion()
playThreeYearForecastMotion()
playForecastTrendMotion()
playMonthlyForecastMotion()
} }
function shortFlowName(item = {}) { function playYakStructureMotion() {
const name = String(item.name || "") const chart = homeChartRefs.get("yak-structure")
if (name.includes("草地面积")) return "草地归集" playChartAction(chart, "畜群结构", activeStructureIndex.value)
if (name.includes("湿地生产力")) return "湿地生产力" }
if (name.includes("公益林")) return "公益林同步"
if (name.includes("牦牛存栏")) return "牦牛存栏" function playThreeYearForecastMotion() {
if (name.includes("草畜密度")) return "草畜核算" const chart = homeChartRefs.get("yak-three-year-forecast")
if (name.includes("屠宰")) return "屠宰入库" playChartAction(chart, "预测存栏", activeForecastYearIndex.value)
if (name.includes("活畜")) return "活畜交易" }
if (name.includes("饲草")) return "产业画像"
return item.module || "业务动态" function playForecastTrendMotion() {
const chart = homeChartRefs.get("yak-forecast-trend")
playChartAction(chart, "预测存栏", activeForecastTrendIndex.value)
}
function playMonthlyForecastMotion() {
const chart = homeChartRefs.get("yak-monthly-forecast")
playChartAction(chart, "数量", activeMonthlyIndex.value)
}
function playChartAction(chart, seriesName, dataIndex) {
const instance = typeof chart?.dispatchAction === "function" ? chart : chart?.chart
if (!instance || dataIndex < 0) return
instance.dispatchAction({ type: "downplay", seriesName })
instance.dispatchAction({ type: "hideTip" })
instance.dispatchAction({ type: "highlight", seriesName, dataIndex })
instance.dispatchAction({ type: "showTip", seriesName, dataIndex })
}
function getLoopIndex(length) {
if (!length) return 0
return chartMotionTick.value % length
}
async function loadForestGrassWetPanels() {
const [grassland, wetland, woodland] = await Promise.all([
getTopicBusinessStats("grassland", "grassland-distribution"),
getTopicBusinessStats("wetland", "wetland-distribution"),
getTopicBusinessStats("woodland", "woodland-distribution"),
])
if (destroyed) return
forestGrassWetStats.value = { grassland, wetland, woodland }
}
function pickTopicPanel(topicKey, panelKey) {
const stats = forestGrassWetStats.value[topicKey]
const panels = [
...(stats?.leftPanels || []),
...(stats?.rightPanels || []),
]
return panels.find((panel) => panel?.key === panelKey)
}
function normalizeHomePanel(panel, prefix) {
return {
...panel,
key: `home-${prefix}-${panel.key}`,
height: sidePanelHeight,
}
}
function chartTooltip(formatter) {
return {
trigger: "item",
backgroundColor: "rgba(5, 31, 51, 0.96)",
borderColor: "rgba(0, 210, 255, 0.54)",
borderWidth: 1,
textStyle: { color: "#e7fbff", fontSize: 12 },
extraCssText: "box-shadow:0 0 18px rgba(0,210,255,.2);",
formatter,
}
}
function buildLineSeries(name, data, color, yAxisIndex = 0, area = false, activeIndex = -1) {
return {
name,
type: "line",
yAxisIndex,
smooth: true,
symbolSize: 5,
data: data.map((value, index) => ({
value,
symbolSize: index === activeIndex ? 10 : 5,
itemStyle: { color: index === activeIndex ? "#ffffff" : color },
label: {
show: index === activeIndex,
position: "top",
color,
fontSize: 9,
formatter: ({ value: labelValue }) => formatCompact(labelValue),
},
})),
lineStyle: { width: 2.4, color },
itemStyle: { color },
areaStyle: area
? {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: `${color}44` },
{ offset: 1, color: `${color}05` },
]),
}
: undefined,
}
} }
function formatNumber(value) { function formatNumber(value) {
@ -296,6 +551,13 @@ function formatNumber(value) {
maximumFractionDigits: Number.isInteger(number) ? 0 : 2, maximumFractionDigits: Number.isInteger(number) ? 0 : 2,
}) })
} }
function formatCompact(value) {
const number = Number(value)
if (!Number.isFinite(number)) return "--"
if (Math.abs(number) >= 10000) return `${(number / 10000).toFixed(Math.abs(number) >= 100000 ? 0 : 1)}`
return Math.round(number).toLocaleString("zh-CN")
}
</script> </script>
<style lang="scss"> <style lang="scss">
@ -380,4 +642,9 @@ function formatNumber(value) {
-webkit-text-fill-color: #ffffff !important; -webkit-text-fill-color: #ffffff !important;
text-shadow: none !important; text-shadow: none !important;
} }
.home-chart {
width: 100%;
height: 100%;
}
</style> </style>

@ -691,6 +691,7 @@ const tradeFlowProvinceCenters = {
北京: { lng: 116.407387, lat: 39.904179, level: "province" }, 北京: { lng: 116.407387, lat: 39.904179, level: "province" },
上海: { lng: 121.472644, lat: 31.231706, level: "province" }, 上海: { lng: 121.472644, lat: 31.231706, level: "province" },
广东: { lng: 113.26653, lat: 23.132191, level: "province" }, 广东: { lng: 113.26653, lat: 23.132191, level: "province" },
海南: { lng: 110.33119, lat: 20.031971, level: "province" },
} }
const tradeFlowPlaceCenters = { const tradeFlowPlaceCenters = {
邛溪: { lng: 102.633747, lat: 32.825764, level: "town" }, 邛溪: { lng: 102.633747, lat: 32.825764, level: "town" },
@ -702,6 +703,10 @@ const tradeFlowPlaceCenters = {
湟中: { lng: 101.571683, lat: 36.500879, level: "district" }, 湟中: { lng: 101.571683, lat: 36.500879, level: "district" },
崇州: { lng: 103.673025, lat: 30.630122, level: "county" }, 崇州: { lng: 103.673025, lat: 30.630122, level: "county" },
怀远: { lng: 103.524469, lat: 30.711891, level: "town" }, 怀远: { lng: 103.524469, lat: 30.711891, level: "town" },
绵阳: { lng: 104.679114, lat: 31.46745, level: "city" },
广元: { lng: 105.843357, lat: 32.435435, level: "city" },
南充: { lng: 106.110698, lat: 30.837793, level: "city" },
雅安: { lng: 103.013261, lat: 29.980537, level: "city" },
民乐: { lng: 100.812859, lat: 38.430318, level: "county" }, 民乐: { lng: 100.812859, lat: 38.430318, level: "county" },
湟源: { lng: 101.256464, lat: 36.682426, level: "county" }, 湟源: { lng: 101.256464, lat: 36.682426, level: "county" },
德阳: { lng: 104.397808, lat: 31.127494, level: "city" }, 德阳: { lng: 104.397808, lat: 31.127494, level: "city" },
@ -718,6 +723,9 @@ const tradeFlowPlaceCenters = {
北京: { lng: 116.407387, lat: 39.904179, level: "city" }, 北京: { lng: 116.407387, lat: 39.904179, level: "city" },
上海: { lng: 121.472644, lat: 31.231706, level: "city" }, 上海: { lng: 121.472644, lat: 31.231706, level: "city" },
广州: { lng: 113.264385, lat: 23.129112, level: "city" }, 广州: { lng: 113.264385, lat: 23.129112, level: "city" },
海口: { lng: 110.198293, lat: 20.044001, level: "city" },
三亚: { lng: 109.511909, lat: 18.252847, level: "city" },
汉中: { lng: 107.02377, lat: 33.06761, level: "city" },
} }
const tradeFlowFallbackSlots = [ const tradeFlowFallbackSlots = [
{ lng: 104.076452, lat: 30.651696 }, { lng: 104.076452, lat: 30.651696 },
@ -729,29 +737,6 @@ const tradeFlowFallbackSlots = [
{ lng: 87.616848, lat: 43.825592 }, { lng: 87.616848, lat: 43.825592 },
{ lng: 116.407387, lat: 39.904179 }, { lng: 116.407387, lat: 39.904179 },
] ]
const tradeFlowProvinceVisualCenters = {
四川: { visualLng: 106.4, visualLat: 28.5, visualX: 0.78, visualY: -2.15, labelSide: "bottom" },
青海: { visualLng: 95.2, visualLat: 37.6, visualX: -2.1, visualY: 0.62, labelSide: "left" },
甘肃: { visualLng: 100.4, visualLat: 42.0, visualX: -0.82, visualY: 2.18, labelSide: "top" },
重庆: { visualLng: 109.8, visualLat: 28.2, visualX: 1.72, visualY: -2.02, labelSide: "bottom-right" },
陕西: { visualLng: 110.2, visualLat: 36.2, visualX: 1.64, visualY: 0.48, labelSide: "right" },
云南: { visualLng: 99.6, visualLat: 24.0, visualX: -0.82, visualY: -2.82, labelSide: "bottom-left" },
西藏: { visualLng: 88.6, visualLat: 30.0, visualX: -3.45, visualY: -1.35, labelSide: "bottom-left" },
新疆: { visualLng: 85.8, visualLat: 43.6, visualX: -4.05, visualY: 2.38, labelSide: "top-left" },
北京: { visualLng: 118.2, visualLat: 41.0, visualX: 3.55, visualY: 1.72, labelSide: "top-right" },
上海: { visualLng: 122.2, visualLat: 31.5, visualX: 4.12, visualY: -0.86, labelSide: "right" },
广东: { visualLng: 116.0, visualLat: 22.4, visualX: 3.18, visualY: -2.92, labelSide: "bottom-right" },
}
const tradeFlowFallbackVisualCenters = [
{ visualLng: 91.8, visualLat: 42.2, visualX: -3.52, visualY: 2.18, labelSide: "top-left" },
{ visualLng: 114.0, visualLat: 40.6, visualX: 2.72, visualY: 1.86, labelSide: "top-right" },
{ visualLng: 122.0, visualLat: 31.4, visualX: 4.0, visualY: -0.82, labelSide: "right" },
{ visualLng: 116.0, visualLat: 22.4, visualX: 3.18, visualY: -2.9, labelSide: "bottom-right" },
{ visualLng: 98.4, visualLat: 24.6, visualX: -1.22, visualY: -2.68, labelSide: "bottom-left" },
{ visualLng: 89.0, visualLat: 30.2, visualX: -3.32, visualY: -1.28, labelSide: "bottom-left" },
{ visualLng: 104.4, visualLat: 43.0, visualX: 0.2, visualY: 2.78, labelSide: "top" },
{ visualLng: 106.4, visualLat: 28.5, visualX: 0.78, visualY: -2.15, labelSide: "bottom" },
]
const viewTopics = [ const viewTopics = [
{ key: "structure", name: "畜群结构", color: "#30dcff" }, { key: "structure", name: "畜群结构", color: "#30dcff" },
@ -975,17 +960,8 @@ const tradeCertFilterOptions = computed(() => {
})) }))
}) })
const tradeFlowRows = computed(() => { const tradeFlowRows = computed(() => {
const groupCounts = new Map()
tradeDestRows.value.forEach((item, index) => {
const groupKey = getTradeFlowGroupKey(item.name, index)
groupCounts.set(groupKey, Number(groupCounts.get(groupKey) || 0) + 1)
})
const groupIndexes = new Map()
return tradeDestRows.value.map((item, index) => { return tradeDestRows.value.map((item, index) => {
const groupKey = getTradeFlowGroupKey(item.name, index) const endpoint = getTradeFlowEndpoint(item, index)
const groupIndex = Number(groupIndexes.get(groupKey) || 0)
groupIndexes.set(groupKey, groupIndex + 1)
const endpoint = getTradeFlowEndpoint(item.name, index, groupIndex, Number(groupCounts.get(groupKey) || 1))
return { return {
key: item.key || `${item.name}-${index}`, key: item.key || `${item.name}-${index}`,
name: item.name, name: item.name,
@ -996,10 +972,6 @@ const tradeFlowRows = computed(() => {
unit: "头", unit: "头",
lng: endpoint.lng, lng: endpoint.lng,
lat: endpoint.lat, lat: endpoint.lat,
visualLng: endpoint.visualLng,
visualLat: endpoint.visualLat,
visualX: endpoint.visualX,
visualY: endpoint.visualY,
labelSide: endpoint.labelSide, labelSide: endpoint.labelSide,
province: endpoint.province, province: endpoint.province,
} }
@ -2340,6 +2312,8 @@ function normalizeTradeCountRows(rows = [], nameKey = "name") {
name: String(item[nameKey] || item.name || item.label || item.town || "未填写"), name: String(item[nameKey] || item.name || item.label || item.town || "未填写"),
recordCount: Number(item.recordCount ?? item.record_count ?? item.count ?? 0), recordCount: Number(item.recordCount ?? item.record_count ?? item.count ?? 0),
headCount: Number(item.headCount ?? item.head_count ?? item.amount ?? item.value ?? 0), headCount: Number(item.headCount ?? item.head_count ?? item.amount ?? item.value ?? 0),
lng: toFiniteNumber(item.lng ?? item.longitude ?? item.destLng ?? item.dest_lng ?? item.destLongitude ?? item.dest_longitude),
lat: toFiniteNumber(item.lat ?? item.latitude ?? item.destLat ?? item.dest_lat ?? item.destLatitude ?? item.dest_latitude),
})) }))
.filter((item) => item.name && Number.isFinite(item.headCount)) .filter((item) => item.name && Number.isFinite(item.headCount))
} }
@ -2377,67 +2351,57 @@ function getTradeFlowProvinceByPlace(placeKey) {
return tradeFlowProvinceCenters[placeKey] ? placeKey : "" return tradeFlowProvinceCenters[placeKey] ? placeKey : ""
} }
function getTradeFlowGroupKey(name, index = 0) { function getTradeFlowLabelSide(province, index = 0) {
const text = String(name || "") const sideMap = {
const placeKey = getTradeFlowMatchedPlace(text) 新疆: "right",
const province = placeKey ? getTradeFlowProvinceByPlace(placeKey) : getTradeFlowMatchedProvince(text) 西藏: "right",
return province || `fallback-${index % tradeFlowFallbackVisualCenters.length}` 青海: "top",
} 甘肃: "top",
四川: "bottom",
function getTradeFlowVisualCenter(province, index = 0) { 重庆: "bottom-right",
return tradeFlowProvinceVisualCenters[province] || tradeFlowFallbackVisualCenters[index % tradeFlowFallbackVisualCenters.length] 广东: "top-left",
} 上海: "left",
北京: "left",
function getTradeFlowVisualOffset(index = 0, groupIndex = 0, groupSize = 1) { }
if (groupSize <= 1) return { lng: 0, lat: 0, x: 0, y: 0 } return sideMap[province] || ["right", "top", "left", "bottom"][index % 4]
const radius = Math.min(0.52, 0.26 + groupSize * 0.05)
const angle = -0.9 + (Math.PI * 1.45 * groupIndex) / Math.max(groupSize - 1, 1)
const x = Math.cos(angle) * radius + (index % 2 === 0 ? -0.04 : 0.04)
const y = Math.sin(angle) * radius * 0.58
return { lng: x * 1.6, lat: y * 1.6, x, y }
} }
function getTradeFlowEndpoint(name, index = 0, groupIndex = 0, groupSize = 1) { function getTradeFlowEndpoint(row, index = 0) {
const explicitLng = toFiniteNumber(row?.lng)
const explicitLat = toFiniteNumber(row?.lat)
if (Number.isFinite(explicitLng) && Number.isFinite(explicitLat)) {
const province = getTradeFlowMatchedProvince(row?.name)
return {
lng: explicitLng,
lat: explicitLat,
labelSide: getTradeFlowLabelSide(province, index),
province: province || "接口坐标",
level: "api",
}
}
const name = typeof row === "string" ? row : row?.name
const text = String(name || "") const text = String(name || "")
const matchedPlaceKey = getTradeFlowMatchedPlace(text) const matchedPlaceKey = getTradeFlowMatchedPlace(text)
if (matchedPlaceKey) { if (matchedPlaceKey) {
const province = getTradeFlowProvinceByPlace(matchedPlaceKey) const province = getTradeFlowProvinceByPlace(matchedPlaceKey)
const visualCenter = getTradeFlowVisualCenter(province, index)
const offset = getTradeFlowVisualOffset(index, groupIndex, groupSize)
return { return {
...tradeFlowPlaceCenters[matchedPlaceKey], ...tradeFlowPlaceCenters[matchedPlaceKey],
visualLng: visualCenter.visualLng + offset.lng, labelSide: getTradeFlowLabelSide(province || matchedPlaceKey, index),
visualLat: visualCenter.visualLat + offset.lat,
visualX: visualCenter.visualX + offset.x,
visualY: visualCenter.visualY + offset.y,
labelSide: visualCenter.labelSide,
province: province || matchedPlaceKey, province: province || matchedPlaceKey,
} }
} }
const matchedKey = getTradeFlowMatchedProvince(text) const matchedKey = getTradeFlowMatchedProvince(text)
if (matchedKey) { if (matchedKey) {
const visualCenter = getTradeFlowVisualCenter(matchedKey, index)
const offset = getTradeFlowVisualOffset(index, groupIndex, groupSize)
return { return {
...tradeFlowProvinceCenters[matchedKey], ...tradeFlowProvinceCenters[matchedKey],
visualLng: visualCenter.visualLng + offset.lng, labelSide: getTradeFlowLabelSide(matchedKey, index),
visualLat: visualCenter.visualLat + offset.lat,
visualX: visualCenter.visualX + offset.x,
visualY: visualCenter.visualY + offset.y,
labelSide: visualCenter.labelSide,
province: matchedKey, province: matchedKey,
} }
} }
const fallback = tradeFlowFallbackSlots[index % tradeFlowFallbackSlots.length] const fallback = tradeFlowFallbackSlots[index % tradeFlowFallbackSlots.length]
const visualCenter = getTradeFlowVisualCenter("", index)
const offset = getTradeFlowVisualOffset(index, groupIndex, groupSize)
return { return {
...fallback, ...fallback,
visualLng: visualCenter.visualLng + offset.lng, labelSide: getTradeFlowLabelSide("", index),
visualLat: visualCenter.visualLat + offset.lat,
visualX: visualCenter.visualX + offset.x,
visualY: visualCenter.visualY + offset.y,
labelSide: visualCenter.labelSide,
province: "全国流向", province: "全国流向",
} }
} }

Loading…
Cancel
Save