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. 575
      src/views/home/index.vue
  48. 114
      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}
{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};
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: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" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>红原县数字畜牧业监管平台</title>
<script type="module" crossorigin src="./assets/index-1782659605222.js"></script>
<link rel="stylesheet" href="./assets/index-b8a77371-balabala-1782659605222.css">
<script type="module" crossorigin src="./assets/index-1782661552655.js"></script>
<link rel="stylesheet" href="./assets/index-b8a77371-balabala-1782661552655.css">
</head>
<body>

@ -81,7 +81,7 @@ export class Assets {
assets.push({
type: "File",
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.tradeGeoProjectionCenter = [104, 36]
this.tradeGeoProjectionScale = 10.2
this.tradeNationalDepth = 0.22
this.tradeGeoProjectionScale = 11.4
this.tradeNationalDepth = 0.24
// 地图拉伸高度
this.depth = 0.5
this.mapFocusLabelInfo = {
@ -666,17 +666,23 @@ export class World extends Mini3d {
const visibleNames = new Set(["四川", "青海", "甘肃", "陕西", "重庆", "云南", "西藏", "新疆"])
;(chinaMapData.features || []).forEach((feature) => {
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)
if (!center) return
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")
label.setParent(this.tradeNationalLabelGroup)
label.visible = true
this.tradeNationalLabels.push(label)
})
}
normalizeTradeProvinceName(name = "") {
return String(name)
.replace(/省|市|自治区|壮族|回族|维吾尔|特别行政区/g, "")
.trim()
}
getFeatureCenterLngLat(feature) {
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]))) {
@ -1761,10 +1767,6 @@ export class World extends Mini3d {
name: row.name || row.destRegion || row.destination || `流向${index + 1}`,
lng,
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 || "",
value,
headCount: value,
@ -1785,28 +1787,34 @@ export class World extends Mini3d {
: rows.reduce((sum, row) => sum + toFiniteNumber(row.value), 0)
return `<div class="trade-market-card">
<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>`
}
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) {
const sideClass = row.labelSide ? ` is-${String(row.labelSide).replace(/[^a-z0-9-]/gi, "")}` : ""
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-foot">出证 ${this.formatMapIntegerValue(row.recordCount)} </div>
</div>`
}
getTradeFlowLabelOffset(row, index) {
const map = {
left: new Vector3(-1.56, 0.18 + (index % 2) * 0.2, 0),
right: new Vector3(1.38, 0.16 - (index % 2) * 0.18, 0),
top: new Vector3(-0.52 + (index % 2) * 1.0, 1.12, 0),
bottom: new Vector3(-0.5 + (index % 2) * 0.96, -1.1, 0),
"top-left": new Vector3(-1.52, 1.02, 0),
"top-right": new Vector3(1.28, 1.0, 0),
"bottom-left": new Vector3(-1.52, -0.96, 0),
"bottom-right": new Vector3(1.28, -0.96, 0),
left: new Vector3(-1.12, 0.18 + (index % 2) * 0.16, 0),
right: new Vector3(1.04, 0.16 - (index % 2) * 0.14, 0),
top: new Vector3(-0.44 + (index % 2) * 0.78, 0.76, 0),
bottom: new Vector3(-0.4 + (index % 2) * 0.72, -0.76, 0),
"top-left": new Vector3(-1.04, 0.72, 0),
"top-right": new Vector3(0.96, 0.72, 0),
"bottom-left": new Vector3(-1.04, -0.68, 0),
"bottom-right": new Vector3(0.96, -0.68, 0),
}
return map[row.labelSide] || [
new Vector3(-1.24, 0.3, 0),
@ -1820,12 +1828,7 @@ export class World extends Mini3d {
return new Vector3(x, -y, this.tradeNationalDepth + localZ)
}
getTradeFlowPoint(row, localZ = 0) {
const visualX = toFiniteNumber(row.visualX ?? row.visual_x, NaN)
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)
return this.getTradeLocalPoint(row.lng, row.lat, localZ)
}
tradeGeoProjection(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.topicLayerMaterial && shouldShow) this.topicLayerMaterial.opacity = 0
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) {
this.applyLayerMapView(this.currentTopicLayer, { force: true })
}
@ -2026,7 +2029,7 @@ export class World extends Mini3d {
sourceLabel.element.className = "trade-market-label"
sourceLabel.init(
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.userData = { ...source, rows: rows.length }

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

@ -233,7 +233,7 @@ import {
const emit = defineEmits(["loading-progress", "map-ready"])
const selectorCardWidth = 326
const selectorCardHeight = 620
const selectorCardHeight = 720
const sheetCardWidth = 1310
const mainCardHeight = 820
@ -2176,4 +2176,309 @@ function getProcessStepState(index) {
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>

@ -29,7 +29,14 @@
:width="398"
: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>
</div>
</div>
@ -37,203 +44,334 @@
<script setup>
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 MapScene from "@/views/forestGrassWet/map.vue"
import BusinessPanelRenderer from "@/views/forestGrassWet/components/BusinessPanelRenderer.vue"
import { Assets } from "@/views/forestGrassWet/assets"
import { getLayerByKey } from "@/config/layers"
import { getTopicBusinessStats } from "@/services/openApi"
import {
buildForecastSeries,
forecastPeriods,
getForecastPeriod,
structureRows,
yakForecastRows,
} from "@/views/yakManagement/data"
import {
businessMatrixRows,
dataFlowRows,
homeTopStats,
resourceCompositionRows,
supportRows,
townSynergyRows,
tradeRows,
yakTownRows,
} from "./data"
const emit = defineEmits(["module-stats", "loading-progress", "map-ready"])
const mapSceneRef = ref(null)
const homeMapLayer = getLayerByKey("grassland", "grassland-distribution")
const forestGrassWetStats = ref({})
const homeChartRefs = new Map()
let mapAssets = null
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 grassland = resourceCompositionRows.find((item) => item.key === "grassland") || {}
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 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 resourceBusinessRows = computed(() => [
const rightPanels = computed(() => [
{
key: "grassland-total",
name: "草地资源",
value: grassland.value,
unit: grassland.unit,
extra: `${resourcePercent(grassland)}%`,
key: "yak-structure",
title: "畜群结构占比",
height: sidePanelHeight,
option: structureDonutOption.value,
},
{
key: "basic-grassland",
name: "基本草原",
value: basicGrassland.value,
unit: basicGrassland.unit,
extra: `${basicGrassland.percent || "--"}%`,
key: "yak-three-year-forecast",
title: "未来三年牦牛预测",
height: sidePanelHeight,
option: threeYearForecastOption.value,
},
{
key: "public-woodland",
name: "公益林面积",
value: publicWoodland.value,
unit: publicWoodland.unit,
extra: `${publicWoodland.percent || "--"}%`,
key: "yak-forecast-trend",
title: "未来存栏趋势",
height: sidePanelHeight,
option: forecastTrendOption.value,
},
{
key: "wetland-yield",
name: "湿地生产力",
value: wetlandYield.value,
unit: wetlandYield.unit,
extra: `${wetlandYield.percent || "--"}%`,
key: "yak-monthly-forecast",
title: "月度数量预测",
height: sidePanelHeight,
option: monthlyForecastOption.value,
},
])
const tradeStructureRows = computed(() =>
tradeRows.map((item) => ({
...item,
extra: `${item.yoy > 0 ? "+" : ""}${item.yoy}%`,
}))
)
const supportChartRows = computed(() =>
supportRows.map((item) => ({
...item,
extra: item.capacityNote,
scaleValue: item.percent,
}))
)
const townResourceRows = computed(() =>
townSynergyRows.slice(0, 5).map((item) => ({
key: item.key,
const structureDonutOption = computed(() => ({
animation: true,
animationDuration: 900,
animationDurationUpdate: 700,
animationEasing: "cubicOut",
color: palette,
tooltip: chartTooltip((params) => `${params.name}<br/>占比:${params.percent}%<br/>估算数量:${formatNumber(params.value)}`),
legend: {
orient: "vertical",
right: 0,
top: "middle",
itemWidth: 8,
itemHeight: 8,
itemGap: 5,
formatter(name) {
const row = structureRows.find((item) => item.name === name)
return row ? `${name} ${formatCompact(row.count)}` : name
},
textStyle: { color: "rgba(222, 250, 255, 0.78)", fontSize: 9 },
},
series: [
{
name: "畜群结构",
type: "pie",
selectedMode: "single",
selectedOffset: 8,
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.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,
value: item.count,
selected: index === activeStructureIndex.value,
})),
},
],
}))
)
const leftPanels = computed(() => [
{
key: "resource-structure",
title: "林草湿资源结构",
height: 186,
type: "donut",
variant: "ring",
rows: resourceRows.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 },
xAxis: {
type: "category",
data: forecastPeriods.map((item) => item.shortLabel),
axisLabel: { color: "rgba(223, 250, 255, 0.76)", fontSize: 11 },
axisLine: { lineStyle: { color: "rgba(48, 220, 255, 0.26)" } },
axisTick: { show: false },
},
yAxis: {
type: "value",
min(value) {
return Math.max(0, Math.floor(value.min / 10000) * 10000)
},
axisLabel: { color: "rgba(223, 250, 255, 0.58)", fontSize: 10, formatter: formatCompact },
splitLine: { lineStyle: { color: "rgba(48, 220, 255, 0.1)", type: "dashed" } },
},
series: [
{
key: "yak-town-rank",
title: "乡镇牦牛存栏",
height: 186,
name: "预测存栏",
type: "bar",
variant: "distribution",
rows: yakRows.value,
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: [
{
key: "capacity-density",
title: "草畜承载密度",
height: 186,
type: "line",
rows: densityRows.value,
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" } },
},
{
key: "resource-business",
title: "资源业务画像",
height: 186,
type: "structure",
rows: resourceBusinessRows.value,
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,
animationDuration: 1100,
animationDurationUpdate: 800,
animationEasing: "cubicOut",
color: ["#30dcff", "#ffd76c"],
tooltip: { ...chartTooltip(), trigger: "axis" },
legend: {
top: 2,
right: 8,
itemWidth: 10,
itemHeight: 7,
itemGap: 8,
textStyle: { color: "rgba(222, 250, 255, 0.76)", fontSize: 9 },
},
grid: { left: 34, right: 34, top: 30, bottom: 22 },
xAxis: {
type: "category",
data: yakForecastRows.map((item) => item.month || item.name),
axisLabel: { color: "rgba(223, 250, 255, 0.68)", fontSize: 9, interval: 1 },
axisLine: { lineStyle: { color: "rgba(48, 220, 255, 0.26)" } },
axisTick: { show: false },
},
yAxis: [
{
key: "trade-structure",
title: "牦牛交易结构",
height: 186,
type: "donut",
rows: tradeStructureRows.value,
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" } },
},
{
key: "support-industry",
title: "产业支撑能力",
height: 186,
type: "column",
variant: "slim",
rows: supportChartRows.value,
type: "value",
axisLabel: { color: "rgba(223, 250, 255, 0.48)", fontSize: 9, formatter: formatCompact },
splitLine: { show: false },
},
],
series: [
{
key: "town-resource",
title: "乡镇资源协同",
height: 186,
name: "数量",
type: "bar",
variant: "distribution",
rows: townResourceRows.value,
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)" },
]),
},
},
{
key: "data-flow",
title: "业务数据动态",
height: 186,
type: "heat",
rows: dataTrendRows.value,
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(() => {
emit("module-stats", {
moduleKey: "home",
items: homeTopStats,
})
loadForestGrassWetPanels()
initHomeMap()
startChartMotion()
})
onBeforeUnmount(() => {
destroyed = true
stopChartMotion()
homeChartRefs.clear()
mapSceneRef.value?.destroyMap?.()
})
@ -271,22 +409,139 @@ function initHomeMap() {
})
}
function resourcePercent(item = {}) {
const total = totalResource.value || 1
return Number(((Number(item.value || 0) / total) * 100).toFixed(1))
function setHomeChartRef(key, chart) {
if (!key) return
if (chart) {
homeChartRefs.set(key, chart)
} else {
homeChartRefs.delete(key)
}
}
function startChartMotion() {
stopChartMotion()
chartMotionTimer = window.setInterval(playChartMotion, 2600)
window.setTimeout(playChartMotion, 800)
}
function shortFlowName(item = {}) {
const name = String(item.name || "")
if (name.includes("草地面积")) return "草地归集"
if (name.includes("湿地生产力")) return "湿地生产力"
if (name.includes("公益林")) return "公益林同步"
if (name.includes("牦牛存栏")) return "牦牛存栏"
if (name.includes("草畜密度")) return "草畜核算"
if (name.includes("屠宰")) return "屠宰入库"
if (name.includes("活畜")) return "活畜交易"
if (name.includes("饲草")) return "产业画像"
return item.module || "业务动态"
function stopChartMotion() {
if (!chartMotionTimer) return
window.clearInterval(chartMotionTimer)
chartMotionTimer = null
}
function playChartMotion() {
if (destroyed) return
chartMotionTick.value += 1
playYakStructureMotion()
playThreeYearForecastMotion()
playForecastTrendMotion()
playMonthlyForecastMotion()
}
function playYakStructureMotion() {
const chart = homeChartRefs.get("yak-structure")
playChartAction(chart, "畜群结构", activeStructureIndex.value)
}
function playThreeYearForecastMotion() {
const chart = homeChartRefs.get("yak-three-year-forecast")
playChartAction(chart, "预测存栏", activeForecastYearIndex.value)
}
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) {
@ -296,6 +551,13 @@ function formatNumber(value) {
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>
<style lang="scss">
@ -380,4 +642,9 @@ function formatNumber(value) {
-webkit-text-fill-color: #ffffff !important;
text-shadow: none !important;
}
.home-chart {
width: 100%;
height: 100%;
}
</style>

@ -691,6 +691,7 @@ const tradeFlowProvinceCenters = {
北京: { lng: 116.407387, lat: 39.904179, level: "province" },
上海: { lng: 121.472644, lat: 31.231706, level: "province" },
广东: { lng: 113.26653, lat: 23.132191, level: "province" },
海南: { lng: 110.33119, lat: 20.031971, level: "province" },
}
const tradeFlowPlaceCenters = {
邛溪: { lng: 102.633747, lat: 32.825764, level: "town" },
@ -702,6 +703,10 @@ const tradeFlowPlaceCenters = {
湟中: { lng: 101.571683, lat: 36.500879, level: "district" },
崇州: { lng: 103.673025, lat: 30.630122, level: "county" },
怀远: { 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: 101.256464, lat: 36.682426, level: "county" },
德阳: { lng: 104.397808, lat: 31.127494, level: "city" },
@ -718,6 +723,9 @@ const tradeFlowPlaceCenters = {
北京: { lng: 116.407387, lat: 39.904179, level: "city" },
上海: { lng: 121.472644, lat: 31.231706, 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 = [
{ lng: 104.076452, lat: 30.651696 },
@ -729,29 +737,6 @@ const tradeFlowFallbackSlots = [
{ lng: 87.616848, lat: 43.825592 },
{ 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 = [
{ key: "structure", name: "畜群结构", color: "#30dcff" },
@ -975,17 +960,8 @@ const tradeCertFilterOptions = 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) => {
const groupKey = getTradeFlowGroupKey(item.name, 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))
const endpoint = getTradeFlowEndpoint(item, index)
return {
key: item.key || `${item.name}-${index}`,
name: item.name,
@ -996,10 +972,6 @@ const tradeFlowRows = computed(() => {
unit: "头",
lng: endpoint.lng,
lat: endpoint.lat,
visualLng: endpoint.visualLng,
visualLat: endpoint.visualLat,
visualX: endpoint.visualX,
visualY: endpoint.visualY,
labelSide: endpoint.labelSide,
province: endpoint.province,
}
@ -2340,6 +2312,8 @@ function normalizeTradeCountRows(rows = [], nameKey = "name") {
name: String(item[nameKey] || item.name || item.label || item.town || "未填写"),
recordCount: Number(item.recordCount ?? item.record_count ?? item.count ?? 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))
}
@ -2377,67 +2351,57 @@ function getTradeFlowProvinceByPlace(placeKey) {
return tradeFlowProvinceCenters[placeKey] ? placeKey : ""
}
function getTradeFlowGroupKey(name, index = 0) {
const text = String(name || "")
const placeKey = getTradeFlowMatchedPlace(text)
const province = placeKey ? getTradeFlowProvinceByPlace(placeKey) : getTradeFlowMatchedProvince(text)
return province || `fallback-${index % tradeFlowFallbackVisualCenters.length}`
function getTradeFlowLabelSide(province, index = 0) {
const sideMap = {
新疆: "right",
西藏: "right",
青海: "top",
甘肃: "top",
四川: "bottom",
重庆: "bottom-right",
广东: "top-left",
上海: "left",
北京: "left",
}
function getTradeFlowVisualCenter(province, index = 0) {
return tradeFlowProvinceVisualCenters[province] || tradeFlowFallbackVisualCenters[index % tradeFlowFallbackVisualCenters.length]
return sideMap[province] || ["right", "top", "left", "bottom"][index % 4]
}
function getTradeFlowVisualOffset(index = 0, groupIndex = 0, groupSize = 1) {
if (groupSize <= 1) return { lng: 0, lat: 0, x: 0, y: 0 }
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(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",
}
function getTradeFlowEndpoint(name, index = 0, groupIndex = 0, groupSize = 1) {
}
const name = typeof row === "string" ? row : row?.name
const text = String(name || "")
const matchedPlaceKey = getTradeFlowMatchedPlace(text)
if (matchedPlaceKey) {
const province = getTradeFlowProvinceByPlace(matchedPlaceKey)
const visualCenter = getTradeFlowVisualCenter(province, index)
const offset = getTradeFlowVisualOffset(index, groupIndex, groupSize)
return {
...tradeFlowPlaceCenters[matchedPlaceKey],
visualLng: visualCenter.visualLng + offset.lng,
visualLat: visualCenter.visualLat + offset.lat,
visualX: visualCenter.visualX + offset.x,
visualY: visualCenter.visualY + offset.y,
labelSide: visualCenter.labelSide,
labelSide: getTradeFlowLabelSide(province || matchedPlaceKey, index),
province: province || matchedPlaceKey,
}
}
const matchedKey = getTradeFlowMatchedProvince(text)
if (matchedKey) {
const visualCenter = getTradeFlowVisualCenter(matchedKey, index)
const offset = getTradeFlowVisualOffset(index, groupIndex, groupSize)
return {
...tradeFlowProvinceCenters[matchedKey],
visualLng: visualCenter.visualLng + offset.lng,
visualLat: visualCenter.visualLat + offset.lat,
visualX: visualCenter.visualX + offset.x,
visualY: visualCenter.visualY + offset.y,
labelSide: visualCenter.labelSide,
labelSide: getTradeFlowLabelSide(matchedKey, index),
province: matchedKey,
}
}
const fallback = tradeFlowFallbackSlots[index % tradeFlowFallbackSlots.length]
const visualCenter = getTradeFlowVisualCenter("", index)
const offset = getTradeFlowVisualOffset(index, groupIndex, groupSize)
return {
...fallback,
visualLng: visualCenter.visualLng + offset.lng,
visualLat: visualCenter.visualLat + offset.lat,
visualX: visualCenter.visualX + offset.x,
visualY: visualCenter.visualY + offset.y,
labelSide: visualCenter.labelSide,
labelSide: getTradeFlowLabelSide("", index),
province: "全国流向",
}
}

Loading…
Cancel
Save