fix: refine screen2 maps and monitoring

V2.0
M先生 3 weeks ago
parent cd0aa26c98
commit 1d47359c40
  1. 0
      docs/assets/AlibabaPuHuiTi-14435b65-balabala-1783995319167.woff2
  2. 0
      docs/assets/AlimamaShuHeiTi-Bold-6401fd0d-balabala-1783995319167.ttf
  3. 0
      docs/assets/AlimamaShuHeiTi-Bold-a95023f2-balabala-1783995319167.woff
  4. 0
      docs/assets/AlimamaShuHeiTi-Bold-fa4a258d-balabala-1783995319167.woff2
  5. 2
      docs/assets/SmartChartRenderer-4a1f392b-1783995319167.js
  6. 0
      docs/assets/SmartChartRenderer-7a3a96b9-balabala-1783995319167.css
  7. 0
      docs/assets/area-b57940eb-balabala-1783995319167.png
  8. 0
      docs/assets/bg-3acb94e3-balabala-1783995319167.png
  9. 0
      docs/assets/bottom-menu-arrow-big-180b7071-balabala-1783995319167.svg
  10. 0
      docs/assets/bottom-menu-arrow-small-19304179-balabala-1783995319167.svg
  11. 0
      docs/assets/bottom-menu-bg-b465793d-balabala-1783995319167.png
  12. 0
      docs/assets/bottom-menu-btn-ee5c8064-balabala-1783995319167.png
  13. 0
      docs/assets/bottom-menu-btn-hover-df33e514-balabala-1783995319167.png
  14. 6
      docs/assets/chartTheme-0e0a1707-1783995319167.js
  15. 0
      docs/assets/chartTheme-73f6d8d0-balabala-1783995319167.css
  16. 0
      docs/assets/chinaBlurLine-1a2cbc0f-balabala-1783995319167.png
  17. 0
      docs/assets/content-bottom-left-arrow-3a8a61f3-balabala-1783995319167.svg
  18. 0
      docs/assets/content-bottom-right-arrow-a3c5270d-balabala-1783995319167.svg
  19. 0
      docs/assets/content-middle-line-162bc0d5-balabala-1783995319167.svg
  20. 0
      docs/assets/focus_bg-b6803e2d-balabala-1783995319167.png
  21. 0
      docs/assets/focus_move_bg-cf41417a-balabala-1783995319167.png
  22. 0
      docs/assets/header-bg-65b38012-balabala-1783995319167.png
  23. 0
      docs/assets/huiguang-3c5dfda5-balabala-1783995319167.png
  24. 0
      docs/assets/icon1-6f7c222f-balabala-1783995319167.png
  25. 0
      docs/assets/icon2-4f77ccec-balabala-1783995319167.png
  26. 2
      docs/assets/index-0cc2c5ef-1783995319167.js
  27. 2
      docs/assets/index-1783995319167.js
  28. 18158
      docs/assets/index-5e598b65-1783957353180.js
  29. 2
      docs/assets/index-695ff00c-balabala-1783995319167.css
  30. 2
      docs/assets/index-6fef048d-balabala-1783995319167.css
  31. 17858
      docs/assets/index-89c6439f-1783995319167.js
  32. 0
      docs/assets/index-8d752fbe-balabala-1783995319167.css
  33. 2
      docs/assets/index-d2259c0a-1783995319167.js
  34. 1
      docs/assets/index-f35293d5-balabala-1783957353180.css
  35. 1
      docs/assets/index-f5d1da98-balabala-1783995319167.css
  36. 1
      docs/assets/json/hongyuan-county-outline.json
  37. 0
      docs/assets/leaf-7be9efef-balabala-1783995319167.png
  38. 0
      docs/assets/left-kuang-5d6bd1ae-balabala-1783995319167.svg
  39. 0
      docs/assets/live-scene-1-786cdd0a-balabala-1783995319167.png
  40. 0
      docs/assets/menu-btn-91a961cf-balabala-1783995319167.png
  41. 0
      docs/assets/menu-btn-hover-62eba0bb-balabala-1783995319167.png
  42. 0
      docs/assets/ocean-bg-8c848b8f-balabala-1783995319167.png
  43. 0
      docs/assets/park-ee0533e0-balabala-1783995319167.png
  44. 0
      docs/assets/pathLine2-d4134d4b-balabala-1783995319167.png
  45. 0
      docs/assets/right-kuang-b1937caf-balabala-1783995319167.svg
  46. 0
      docs/assets/ring2-a1b91808-balabala-1783995319167.png
  47. 0
      docs/assets/ring3-fe0f60d7-balabala-1783995319167.png
  48. 0
      docs/assets/ring4-f2e324d6-balabala-1783995319167.png
  49. 0
      docs/assets/rotationBorder1-2ce8e0ef-balabala-1783995319167.png
  50. 0
      docs/assets/rotationBorder2-7496b968-balabala-1783995319167.png
  51. 0
      docs/assets/saoguang-ccf84a88-balabala-1783995319167.svg
  52. 0
      docs/assets/stack-7a426057-balabala-1783995319167.png
  53. 0
      docs/assets/title-zs1-c15a5ee8-balabala-1783995319167.svg
  54. 0
      docs/assets/town-f8c0c9cc-balabala-1783995319167.png
  55. 0
      docs/assets/xiangfa-breeding-base-03c7f2cb-balabala-1783995319167.png
  56. 0
      docs/assets/yield-66e9af79-balabala-1783995319167.png
  57. 4
      docs/index.html
  58. 1
      public/assets/json/hongyuan-county-outline.json
  59. 12
      scripts/export-screen2-datasets.mjs
  60. 116
      src/components/CesiumMap/index.vue
  61. 23
      src/config/layers.js
  62. 2
      src/config/mapResources.js
  63. 5
      src/views/ecologicalProtection/data.js
  64. 125
      src/views/ecologicalProtection/index.vue
  65. 67
      src/views/forestGrassWet/components/FeatureInfoPanel.vue
  66. 266
      src/views/home/index.vue
  67. 20
      src/views/pastureManagement/index.vue
  68. 13
      src/views/yakManagement/index.vue
  69. 9
      vite.config.js

@ -1,2 +1,2 @@
var D=Object.defineProperty,R=Object.defineProperties;var W=Object.getOwnPropertyDescriptors;var z=Object.getOwnPropertySymbols;var H=Object.prototype.hasOwnProperty,P=Object.prototype.propertyIsEnumerable;var L=(a,r,o)=>r in a?D(a,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):a[r]=o,m=(a,r)=>{for(var o in r||(r={}))H.call(r,o)&&L(a,o,r[o]);if(z)for(var o of z(r))P.call(r,o)&&L(a,o,r[o]);return a},d=(a,r)=>R(a,W(r));import{e as p,h as Z,j as F,L as b,o as h,c as g,a as y,t as x,g as _,b as V,u as E,n as j}from"./index-1783957353180.js";import{aK as q}from"./chartTheme-1b75dfa1-1783957353180.js";const G={class:"smart-chart-header"},I={key:0},K={key:0},A="口径",Q={__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"],k=p(()=>{const t=(i.chart.rows||[]).filter(e=>Number.isFinite(Number(e.value)));return i.chart.type==="pie"?T(t):i.chart.type==="line"?$(t):C(t)}),w=p(()=>{const t=i.chart.rows||[];return i.chart.type==="bar"&&v(t)}),S=p(()=>B(i.chart.subtitle));Z(()=>{F(()=>{window.requestAnimationFrame(()=>o("ready"))})});function C(t){return v(t)?N(t):{animationDuration:700,grid:{left:52,right:26,top:28,bottom:54},tooltip:c(),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})=>u(e)}}]}}function N(t){return{animationDuration:700,grid:{left:78,right:72,top:22,bottom:20},tooltip:d(m({},c()),{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})=>u(e)}}]}}function T(t){return{animationDuration:700,tooltip:c(),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 D=Object.defineProperty,R=Object.defineProperties;var W=Object.getOwnPropertyDescriptors;var z=Object.getOwnPropertySymbols;var H=Object.prototype.hasOwnProperty,P=Object.prototype.propertyIsEnumerable;var L=(a,r,o)=>r in a?D(a,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):a[r]=o,m=(a,r)=>{for(var o in r||(r={}))H.call(r,o)&&L(a,o,r[o]);if(z)for(var o of z(r))P.call(r,o)&&L(a,o,r[o]);return a},d=(a,r)=>R(a,W(r));import{e as p,h as Z,j as F,L as b,o as h,c as g,a as y,t as x,g as _,b as V,u as E,n as j}from"./index-1783995319167.js";import{aK as q}from"./chartTheme-0e0a1707-1783995319167.js";const G={class:"smart-chart-header"},I={key:0},K={key:0},A="口径",Q={__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"],k=p(()=>{const t=(i.chart.rows||[]).filter(e=>Number.isFinite(Number(e.value)));return i.chart.type==="pie"?T(t):i.chart.type==="line"?$(t):C(t)}),w=p(()=>{const t=i.chart.rows||[];return i.chart.type==="bar"&&v(t)}),S=p(()=>B(i.chart.subtitle));Z(()=>{F(()=>{window.requestAnimationFrame(()=>o("ready"))})});function C(t){return v(t)?N(t):{animationDuration:700,grid:{left:52,right:26,top:28,bottom:54},tooltip:c(),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})=>u(e)}}]}}function N(t){return{animationDuration:700,grid:{left:78,right:72,top:22,bottom:20},tooltip:d(m({},c()),{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})=>u(e)}}]}}function T(t){return{animationDuration:700,tooltip:c(),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 $(t){return{animationDuration:760,grid:{left:52,right:28,top:30,bottom:48},tooltip:d(m({},c()),{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})=>u(e)},data:t.map(e=>({value:e.value,name:e.name}))}]}}function c(){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(s=>s.name===t.name),n=(e==null?void 0:e.unit)||i.chart.unit||"",f=e!=null&&e.extra?`<br/><span style="color:rgba(223,250,255,.62)">${e.extra}</span>`:"";return`${t.marker}${t.name}: ${u(t.value)}${n}${f}`}}}function v(t=[]){return t.length>6||t.some(e=>String(e.name||"").length>4)}function B(t){const e=String(t||"").trim();if(!e||/^(数据)?来源[:]/.test(e)||e.includes("固定展示"))return"";const n=new RegExp(A,"g"),f=new RegExp(`统计${A}`,"g"),s=e.replace(f,"统计数据").replace(n,"数据").replace(/来源[::]\s*openApi\s*统计库\s*[a-zA-Z0-9_./:-]*[,,、]?\s*/gi,"").replace(/openApi\s*统计库\s*/gi,"").replace(/\b[a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+){2,}\b/g,"").replace(/[,,、]\s*[,,、]+/g,",").replace(/^[\s,,、::]+|[\s,,、]+$/g,"");return!s||/^来源[::]?$/.test(s)||O(s)?"":s}function O(t){return/^[a-zA-Z0-9_./:-]+$/.test(t)&&(t.includes("_")||t.length>24)}function u(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)=>(h(),g("div",{class:j(["smart-chart-card",{"is-horizontal":w.value}])},[y("div",G,[y("div",null,[y("strong",null,x(a.chart.title),1),S.value?(h(),g("span",I,x(S.value),1)):_("",!0)]),a.chart.unit?(h(),g("em",K,x(a.chart.unit),1)):_("",!0)]),V(E(q),{class:"smart-chart",option:k.value,autoresize:""},null,8,["option"])],2))}};export{Q as default};

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

@ -1 +1 @@
var m=(v,d,n)=>new Promise((i,a)=>{var u=l=>{try{r(n.next(l))}catch(t){a(t)}},o=l=>{try{r(n.throw(l))}catch(t){a(t)}},r=l=>l.done?i(l.value):Promise.resolve(l.value).then(u,o);r((n=n.apply(v,d)).next())});import{_ as x,J as w,K as h,e as S,o as _,c as f,a as e,x as V,y as g,z as y,t as b,g as k,d as p,N as B}from"./index-1783957353180.js";const D={class:"login-screen"},M={class:"login-shell"},N={class:"login-field"},R={class:"login-field"},U={key:0,class:"login-error"},q=["disabled"],C={__name:"index",setup(v){const d=w(),n=h(),i=p(""),a=p(""),u=p(!1),o=p(""),r=S(()=>{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 B(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",D,[e("div",M,[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:V(l,["prevent"])},[s[4]||(s[4]=e("div",{class:"login-panel-title"},"用户登录",-1)),e("label",N,[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",R,[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)):k("",!0),e("button",{class:"login-submit",disabled:u.value,type:"submit"},b(u.value?"登录中":"登录"),9,q)],32)])]))}},P=x(C,[["__scopeId","data-v-8ebd0d6e"]]);export{P as default};
var m=(v,d,n)=>new Promise((i,a)=>{var u=l=>{try{r(n.next(l))}catch(t){a(t)}},o=l=>{try{r(n.throw(l))}catch(t){a(t)}},r=l=>l.done?i(l.value):Promise.resolve(l.value).then(u,o);r((n=n.apply(v,d)).next())});import{_ as x,J as w,K as h,e as S,o as _,c as f,a as e,x as V,y as g,z as y,t as b,g as k,d as p,N as B}from"./index-1783995319167.js";const D={class:"login-screen"},M={class:"login-shell"},N={class:"login-field"},R={class:"login-field"},U={key:0,class:"login-error"},q=["disabled"],C={__name:"index",setup(v){const d=w(),n=h(),i=p(""),a=p(""),u=p(!1),o=p(""),r=S(()=>{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 B(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",D,[e("div",M,[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:V(l,["prevent"])},[s[4]||(s[4]=e("div",{class:"login-panel-title"},"用户登录",-1)),e("label",N,[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",R,[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)):k("",!0),e("button",{class:"login-submit",disabled:u.value,type:"submit"},b(u.value?"登录中":"登录"),9,q)],32)])]))}},P=x(C,[["__scopeId","data-v-8ebd0d6e"]]);export{P as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -11,8 +11,8 @@
window.CESIUM_BASE_URL = "./cdn/Cesium/";
</script>
<script src="./cdn/Cesium/Cesium.js"></script>
<script type="module" crossorigin src="./assets/index-1783957353180.js"></script>
<link rel="stylesheet" href="./assets/index-4ccdd147-balabala-1783957353180.css">
<script type="module" crossorigin src="./assets/index-1783995319167.js"></script>
<link rel="stylesheet" href="./assets/index-6fef048d-balabala-1783995319167.css">
</head>
<body>

File diff suppressed because one or more lines are too long

@ -33,6 +33,16 @@ const rawPlugin = {
},
}
const assetPlugin = {
name: "screen2-asset-loader",
setup(build) {
build.onLoad({ filter: /\.(png|jpe?g|webp|gif|svg)$/i }, async (args) => ({
contents: `export default ${JSON.stringify(path.relative(rootDir, args.path))};`,
loader: "js",
}))
},
}
const aliasPlugin = {
name: "screen2-alias",
setup(build) {
@ -83,7 +93,7 @@ const buildResult = await esbuild.build({
platform: "node",
format: "cjs",
target: "node18",
plugins: [rawPlugin, aliasPlugin],
plugins: [rawPlugin, assetPlugin, aliasPlugin],
logLevel: "silent",
})

@ -8,7 +8,7 @@
import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue"
import polygonClipping from "polygon-clipping"
import { HONGYUAN_BOUNDS } from "@/config/layers"
import { CHINA_GEOJSON_PATH, getPublicMapResourcePath } from "@/config/mapResources"
import { CHINA_GEOJSON_PATH, HONGYUAN_COUNTY_OUTLINE_PATH, getPublicMapResourcePath } from "@/config/mapResources"
import { hongyuanTownshipLabelPoints, hongyuanTownshipsGeoJson } from "@/config/townshipBoundaries"
const NATIONAL_BACKDROP_BOUNDS = {
@ -30,7 +30,8 @@ const COUNTY_WALL_MAX_HEIGHT = 7600
const COUNTY_STAGE_HEIGHT = 900
const COUNTY_BOUNDARY_RENDER_HEIGHT = 4700
const TOWNSHIP_BOUNDARY_RENDER_HEIGHT = 4620
const HONGYUAN_CLIP_MULTIPOLYGON = createTownshipClipMultiPolygon(hongyuanTownshipsGeoJson)
let hongyuanCountyClipMultiPolygon = []
let hongyuanCountyBoundaryPromise = null
const props = defineProps({
active: {
@ -73,6 +74,10 @@ const props = defineProps({
type: Number,
default: 9,
},
minimumZoomDistance: {
type: Number,
default: 350,
},
countyViewTargetOffset: {
type: Object,
default: null,
@ -270,13 +275,25 @@ function initializeCesium() {
viewer.scene.fxaa = false
viewer.scene.postProcessStages.fxaa.enabled = false
viewer.scene.screenSpaceCameraController.enableCollisionDetection = true
viewer.scene.screenSpaceCameraController.minimumZoomDistance = 350
viewer.scene.screenSpaceCameraController.minimumZoomDistance = Math.max(
1,
Number(props.minimumZoomDistance) || 1,
)
viewer.scene.screenSpaceCameraController.maximumZoomDistance = 560000
if (viewer.cesiumWidget?.creditContainer) viewer.cesiumWidget.creditContainer.style.display = "none"
if (viewer._cesiumWidget?._creditContainer) viewer._cesiumWidget._creditContainer.style.display = "none"
bindTerrainFallback(terrainProvider, Cesium)
loadCountyBoundary()
.then(() => {
if (!viewer || viewer.isDestroyed?.()) return
addCountyFrame()
updateBoundaryVisualScale()
})
.catch((error) => {
console.warn("[cesium-map] county outline load failed", error)
emit("layer-status", { type: "error", text: "红原县边界加载失败" })
})
addTownshipBoundaries()
bindMapClick()
connectResizeObserver()
@ -575,24 +592,41 @@ function forEachPolygon(geometry, callback) {
}
}
function createTownshipClipMultiPolygon(data) {
const townshipPolygons = []
function createFeatureClipMultiPolygon(data, label = "feature") {
const featurePolygons = []
;(data?.features || []).forEach((feature) => {
const geometry = normalizeGeometry(feature?.geometry)
getGeometryPolygons(geometry).forEach((polygon) => {
const cleanPolygon = sanitizeClipPolygon(polygon)
if (cleanPolygon) townshipPolygons.push(cleanPolygon)
if (cleanPolygon) featurePolygons.push(cleanPolygon)
})
})
if (!townshipPolygons.length) return []
if (!featurePolygons.length) return []
try {
return polygonClipping.union(...townshipPolygons)
return polygonClipping.union(...featurePolygons)
} catch (error) {
console.warn("[cesium-map] township union boundary failed", error)
return townshipPolygons.map((polygon) => polygon)
console.warn(`[cesium-map] ${label} union boundary failed`, error)
return featurePolygons.map((polygon) => polygon)
}
}
async function loadCountyBoundary() {
if (hongyuanCountyClipMultiPolygon.length) return hongyuanCountyClipMultiPolygon
if (!hongyuanCountyBoundaryPromise) {
hongyuanCountyBoundaryPromise = fetchJson(HONGYUAN_COUNTY_OUTLINE_PATH)
.then((data) => {
const polygons = createFeatureClipMultiPolygon(data, "county outline")
hongyuanCountyClipMultiPolygon = polygons
return polygons
})
.catch((error) => {
hongyuanCountyBoundaryPromise = null
throw error
})
}
return hongyuanCountyBoundaryPromise
}
function sanitizeClipPolygon(polygon) {
if (!Array.isArray(polygon)) return null
const rings = polygon
@ -777,6 +811,8 @@ async function refreshTopicLayers() {
}
emit("layer-status", { type: "loading", text: "正在加载三维专题图层" })
try {
await loadCountyBoundary()
if (revision !== layerRevision || !viewer) return
for (const layer of layers) {
if (revision !== layerRevision) return
await addTopicLayer(layer, revision)
@ -828,8 +864,9 @@ function addWmtsLayer(layer) {
const imageryLayer = viewer.imageryLayers.addImageryProvider(provider)
const isYakRecognition = String(layer.layerName || "").includes("daping_yak_marks")
imageryLayer.alpha = isYakRecognition
? clampOpacity(layer.opacity, 0.92)
? clampOpacity(layer.opacity, 0.94)
: clampOpacity(layer.opacity, 0.86)
if (isYakRecognition) applyYakRecognitionImageryStyle(imageryLayer, layer)
imageryLayer.__hyLayer = layer
topicImageryLayers.push(imageryLayer)
}
@ -862,12 +899,20 @@ function addWmsLayer(layer) {
const imageryLayer = viewer.imageryLayers.addImageryProvider(provider)
const isYakRecognition = String(layer.layerName || "").includes("daping_yak_marks")
imageryLayer.alpha = isYakRecognition
? clampOpacity(layer.opacity, 0.92)
? clampOpacity(layer.opacity, 0.94)
: clampOpacity(layer.opacity, 0.86)
if (isYakRecognition) applyYakRecognitionImageryStyle(imageryLayer, layer)
imageryLayer.__hyLayer = layer
topicImageryLayers.push(imageryLayer)
}
function applyYakRecognitionImageryStyle(imageryLayer, layer = {}) {
imageryLayer.brightness = Number.isFinite(Number(layer.brightness)) ? Number(layer.brightness) : 1.08
imageryLayer.contrast = Number.isFinite(Number(layer.contrast)) ? Number(layer.contrast) : 1.18
imageryLayer.saturation = Number.isFinite(Number(layer.saturation)) ? Number(layer.saturation) : 1.42
imageryLayer.gamma = Number.isFinite(Number(layer.gamma)) ? Number(layer.gamma) : 0.95
}
async function loadVectorLayerData(layer) {
if (layer.sourceType === "wkt-api") {
try {
@ -888,7 +933,7 @@ async function loadVectorLayerData(layer) {
async function fetchJson(path) {
if (!path) return null
const url = /^(https?:)?\/\//.test(path) || path.startsWith("/") ? path : `/${path}`
const url = /^(https?:)?\/\//.test(path) || path.startsWith("/") ? path : getPublicMapResourcePath(path)
const response = await fetch(url, { credentials: "same-origin" })
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
@ -987,8 +1032,8 @@ function clipFeatureForLayer(feature, layer) {
let polygons = geometry.type === "Polygon" ? [geometry.coordinates] : geometry.coordinates
try {
if (shouldClipToCounty && HONGYUAN_CLIP_MULTIPOLYGON.length) {
polygons = polygonClipping.intersection(polygons, HONGYUAN_CLIP_MULTIPOLYGON)
if (shouldClipToCounty && hongyuanCountyClipMultiPolygon.length) {
polygons = polygonClipping.intersection(polygons, hongyuanCountyClipMultiPolygon)
}
if (clipBounds && polygons.length) {
polygons = polygonClipping.intersection(polygons, [createClipBoundsPolygon(clipBounds)])
@ -1164,10 +1209,13 @@ function resolveFeatureStyle(layer, properties, overrides = {}) {
const opacity = clampOpacity(layer.opacity, 0.84)
const fillText = overrides.fillColor || legend?.fillColor || layer.fillColor || layer.color || "#24F6C4"
const strokeText = overrides.strokeColor || legend?.strokeColor || layer.strokeColor || layer.color || "#B7FFF2"
const fillAlpha = overrides.fillAlpha ?? legend?.fillAlpha ?? layer.fillAlpha ?? Math.min(0.34, opacity * 0.34)
const strokeAlpha = overrides.strokeAlpha ?? legend?.strokeAlpha ?? layer.strokeAlpha ?? Math.max(0.72, opacity)
const lineWidth = overrides.lineWidth ?? legend?.lineWidth ?? layer.lineWidth ?? Math.max(1.6, 1.8 * Number(props.adminBoundaryLineScale || 1))
return {
fill: colorWithAlpha(Cesium, fillText, overrides.fillAlpha ?? Math.min(0.34, opacity * 0.34)),
stroke: colorWithAlpha(Cesium, strokeText, overrides.strokeAlpha ?? Math.max(0.72, opacity)),
lineWidth: overrides.lineWidth || Math.max(1.6, 1.8 * Number(props.adminBoundaryLineScale || 1)),
fill: colorWithAlpha(Cesium, fillText, fillAlpha),
stroke: colorWithAlpha(Cesium, strokeText, strokeAlpha),
lineWidth,
pointSize: overrides.pointSize || 12,
}
}
@ -1443,11 +1491,11 @@ function extendBoundaryPath(path, endpointMap, segments, used, atStart) {
}
function addCountyFrame() {
if (!viewer || !HONGYUAN_CLIP_MULTIPOLYGON.length) return
if (!viewer || !hongyuanCountyClipMultiPolygon.length) return
removeEntities(countyFrameEntities)
const Cesium = window.Cesium
addCountyOutsideMask(Cesium)
HONGYUAN_CLIP_MULTIPOLYGON.forEach((polygon) => {
hongyuanCountyClipMultiPolygon.forEach((polygon) => {
const outerRing = Array.isArray(polygon?.[0]) ? polygon[0] : []
const ring = outerRing.map(normalizeLngLat).filter(Boolean)
if (ring.length < 3) return
@ -1497,7 +1545,7 @@ function addCountyOutsideMask(Cesium) {
const outerMask = createCountyOutsideMaskPolygon()
let maskPolygons = []
try {
maskPolygons = polygonClipping.difference([outerMask], HONGYUAN_CLIP_MULTIPOLYGON)
maskPolygons = polygonClipping.difference([outerMask], hongyuanCountyClipMultiPolygon)
} catch (error) {
console.warn("[cesium-map] county outside mask failed", error)
maskPolygons = [outerMask]
@ -1987,10 +2035,10 @@ function addSelectedFeatureOverlay(feature, options = {}) {
const strokeColor = isTown ? "#F2FFFF" : isWhite ? "#FFFFFF" : "#FF2D2D"
const style = {
fill: Cesium.Color.fromCssColorString(fillColor)
.withAlpha(options.fillAlpha ?? (isTown ? 0.16 : isWhite ? 0.12 : 0.16)),
.withAlpha(options.fillAlpha ?? (isTown ? 0.16 : isWhite ? 0.035 : 0.16)),
stroke: Cesium.Color.fromCssColorString(strokeColor)
.withAlpha(options.strokeAlpha ?? (isWhite ? 0.92 : 0.98)),
lineWidth: options.lineWidth ?? (isTown ? 4.2 : isWhite ? 5.8 : 5.2),
.withAlpha(options.strokeAlpha ?? (isWhite ? 0.9 : 0.98)),
lineWidth: options.lineWidth ?? (isTown ? 4.2 : isWhite ? 3.8 : 5.2),
pointSize: isTown ? 0 : options.pointSize ?? 14,
}
const entities = addSelectedOverlayGeometryEntities(feature.geometry, feature, dataSource.entities, style)
@ -2341,13 +2389,7 @@ function focusPasture(payload = {}) {
const grasslands = normalizePastureGrasslands(payload.grasslands || payload)
const whiteTone = payload.highlightTone === "white"
const glowStyle = whiteTone
? {
fillColor: "#FFFFFF",
strokeColor: "#FFFFFF",
fillAlpha: 0.04,
strokeAlpha: 0.82,
lineWidth: 11,
}
? null
: {
fillColor: "#FF3B30",
strokeColor: "#FF3B30",
@ -2357,11 +2399,11 @@ function focusPasture(payload = {}) {
}
const focusStyle = whiteTone
? {
fillColor: "#FFFFFF",
strokeColor: "#FFFFFF",
fillAlpha: 0.16,
strokeAlpha: 1,
lineWidth: 5.6,
fillColor: "#F7FFFF",
strokeColor: "#F7FFFF",
fillAlpha: 0.025,
strokeAlpha: 0.9,
lineWidth: 3.8,
}
: {
fillColor: "#FF2D2D",
@ -2372,7 +2414,7 @@ function focusPasture(payload = {}) {
}
if (!payload.locateOnly) {
grasslands.forEach((feature) => {
addGeoFeature(feature, { key: "pasture-focus-glow", name: payload.name || "牧场高亮" }, focusEntities, glowStyle)
if (glowStyle) addGeoFeature(feature, { key: "pasture-focus-glow", name: payload.name || "牧场高亮" }, focusEntities, glowStyle)
addGeoFeature(feature, { key: "pasture-focus", name: payload.name || "牧场高亮" }, focusEntities, focusStyle)
})
;(Array.isArray(payload.yakMarks) ? payload.yakMarks : []).forEach((row, index) => {

@ -353,9 +353,12 @@ export const ecologicalProtectionLayers = {
layerName: "ne:t_ecological_redline_hy",
sourceType: "wfs",
styles: "ecological_redline_hy",
opacity: 0.72,
fillColor: "rgba(48, 220, 255, 0.12)",
strokeColor: "#8BF7FF",
opacity: 0.58,
fillColor: "#5EDFEA",
strokeColor: "#BFFFFF",
fillAlpha: 0.08,
strokeAlpha: 0.56,
lineWidth: 1.42,
mapView: {
targetLngLat: [102.58, 32.72],
distanceScale: 0.78,
@ -366,8 +369,8 @@ export const ecologicalProtectionLayers = {
{
key: "ecological-redline-all",
name: "生态保护红线",
fillColor: "rgba(48, 220, 255, 0.12)",
strokeColor: "#8BF7FF",
fillColor: "rgba(94, 223, 234, 0.28)",
strokeColor: "#BFFFFF",
selectable: false,
},
],
@ -562,9 +565,9 @@ export const yakIndustryChainLayers = {
url: "/geoserver/gwc/service/wmts",
opacity: 0.96,
queryBuffer: 10,
fillColor: "rgba(255, 203, 69, 0)",
strokeColor: "#FFCB45",
glowColor: "rgba(255, 203, 69, 0.28)",
fillColor: "rgba(255, 230, 0, 0)",
strokeColor: "#FFE600",
glowColor: "rgba(255, 230, 0, 0.42)",
lineWidth: 1.4,
mapView: {
targetLngLat: [102.58, 32.72],
@ -576,8 +579,8 @@ export const yakIndustryChainLayers = {
{
key: "yak-recognition-all",
name: "牦牛识别",
fillColor: "#FFCB45",
strokeColor: "#FFF4B8",
fillColor: "#FFE600",
strokeColor: "#FFF58A",
selectable: false,
},
],

@ -1,6 +1,6 @@
export const HONGYUAN_TOWNSHIP_GEOJSON_PATH = "datas/geojsons/hongyuan-townships.geojson"
export const HONGYUAN_TOWNSHIP_SCREEN_GEOJSON_PATH = "datas/geojsons/hongyuan-townships-screen.geojson"
export const HONGYUAN_COUNTY_OUTLINE_PATH = "assets/json/红原县-轮廓.json"
export const HONGYUAN_COUNTY_OUTLINE_PATH = "assets/json/hongyuan-county-outline.json"
export const CHINA_GEOJSON_PATH = "assets/json/中华人民共和国.json"
export function getPublicMapResourcePath(path) {

@ -1288,6 +1288,11 @@ export const restorationPanels = {
title: "重点项目库",
type: "projectTable",
span: 2,
columns: [
{ key: "name", label: "项目名称", field: "name", width: "58%", className: "business-project-col-name" },
{ key: "year", label: "年份", field: "year", width: "16%", fallback: "未知", align: "right", className: "business-project-col-year" },
{ key: "category", label: "类型", field: "category", width: "26%", className: "business-project-col-category" },
],
rows: restorationProjectRows,
},
],

@ -134,7 +134,7 @@ import {
getLayersByTopic,
} from "@/config/layers"
import { getEcologicalScopeStats } from "@/services/ecologicalProtectionApi"
import { getProjectClickInfoByWkt, getProjectList } from "@/services/pastureApi"
import { getProjectClickInfoByWkt } from "@/services/pastureApi"
import { Assets } from "@/views/forestGrassWet/assets"
import { SCREEN2_LAYOUT, getScreen2SideCardHeight, getScreen2SpanHeight } from "@/utils/screen2Standard"
import MapScene from "@/components/CesiumMap/index.vue"
@ -195,9 +195,7 @@ let mapLoadStarted = false
let mapLoadTimer = null
let scopeStatsRequestId = 0
let featureQueryRequestId = 0
let projectListRequestId = 0
const scopeStatsCache = new Map()
const projectListRows = ref(null)
const nonBusinessFeatureKeys = new Set([
"code",
@ -262,7 +260,7 @@ const activeLayer = computed(() => {
})
const currentPanels = computed(() => {
if (isEcologicalScopeView.value) return scopePanels.value
if (currentView.value === "restoration") return buildRestorationPanels(projectListRows.value)
if (currentView.value === "restoration") return restorationPanels
if (currentView.value === "engineering-analysis") return engineeringAnalysisPanels
return parkPanels
})
@ -296,18 +294,6 @@ const featurePanelStyle = computed(() => {
}
})
function buildRestorationPanels(rows) {
if (!Array.isArray(rows) || !rows.length) return restorationPanels
return {
...restorationPanels,
right: (restorationPanels.right || []).map((panel) =>
panel?.key === "restoration-projects"
? { ...panel, rows }
: panel
),
}
}
onMounted(() => {
updateScreenViewportHeight()
window.addEventListener("resize", updateScreenViewportHeight)
@ -363,9 +349,6 @@ function syncView(viewKey) {
scopePanels.value = buildEcologicalScopePanels({}, viewKey)
loadEcologicalScopeStats(viewKey)
}
if (viewKey === "restoration") {
loadRestorationProjectList()
}
emit("module-stats", {
moduleKey: "ecological-protection",
viewKey,
@ -375,110 +358,6 @@ function syncView(viewKey) {
})
}
async function loadRestorationProjectList() {
const requestId = ++projectListRequestId
try {
const payload = await getProjectList("", 1, 200)
if (requestId !== projectListRequestId || currentView.value !== "restoration" || destroyed) return
const rows = normalizeProjectListRows(payload)
projectListRows.value = rows.length ? rows : null
} catch (error) {
if (requestId !== projectListRequestId || currentView.value !== "restoration" || destroyed) return
projectListRows.value = null
console.warn("Failed to load restoration project list", error)
}
}
function normalizeProjectListRows(payload) {
return normalizeProjectListSource(payload)
.map((item, index) => normalizeProjectRow(item, index))
.filter((item) => item.name)
}
function normalizeProjectListSource(value) {
if (Array.isArray(value)) return value
if (!value || typeof value !== "object") return []
const candidates = [
value.rows,
value.list,
value.records,
value.items,
value.data?.rows,
value.data?.list,
value.data?.records,
value.result?.rows,
value.result?.list,
value.result?.records,
]
for (const candidate of candidates) {
if (Array.isArray(candidate)) return candidate
}
return []
}
function normalizeProjectRow(item = {}, index = 0) {
const name = pickProjectValue(item, ["projectName", "project_name", "name", "project", "title"])
const category = pickProjectValue(item, ["projectCategory", "project_category", "category", "type", "project_type"])
const year = pickProjectValue(item, ["year", "projectYear", "project_year", "buildYear", "build_year"]) || "未知"
const areaWanMu = toProjectNumber(pickProjectValue(item, ["areaWanMu", "area_wan_mu", "area_mu", "acreage", "area", "mj"]))
const lengthKm = normalizeProjectLength(pickProjectValue(item, ["lengthKm", "length_km", "length_meter", "length", "project_length"]))
const measure = normalizeProjectTextList(pickProjectValue(item, ["measure", "measureTypes", "measure_types", "measure_type", "method"]))
const townships = normalizeProjectTextList(pickProjectValue(item, ["townships", "township", "town", "xzqmc", "full_name"]))
const subjectFeatureCount = toProjectNumber(pickProjectValue(item, ["subjectFeatureCount", "subject_feature_count", "featureCount", "feature_count", "count"]))
const hasArea = areaWanMu > 0
const hasLength = lengthKm > 0
const value = hasArea ? roundProjectNumber(areaWanMu, 2) : hasLength ? roundProjectNumber(lengthKm, 2) : subjectFeatureCount || 0
const unit = hasArea ? "万亩" : hasLength ? "公里" : "个"
const metric = hasArea ? "治理面积" : hasLength ? "工程长度" : "主体要素"
return {
key: item.id || item.project_id || name || `project-${index + 1}`,
index: index + 1,
name,
projectName: name,
category,
year,
areaWanMu: roundProjectNumber(areaWanMu, 4),
lengthKm: roundProjectNumber(lengthKm, 2),
measure,
townships,
subjectFeatureCount,
value,
unit,
metric,
extra: [year, measure].filter(Boolean).join(" / "),
searchText: [name, category, year, measure, townships].filter(Boolean).join(" "),
}
}
function pickProjectValue(item, keys) {
for (const key of keys) {
const value = item?.[key]
if (value !== undefined && value !== null && value !== "") return value
}
return undefined
}
function normalizeProjectTextList(value) {
if (Array.isArray(value)) return value.filter(Boolean).join("、")
return String(value || "").trim()
}
function toProjectNumber(value) {
if (value === undefined || value === null || value === "") return 0
const number = Number(String(value).replace(/[^\d.-]/g, ""))
return Number.isFinite(number) ? number : 0
}
function normalizeProjectLength(value) {
const number = toProjectNumber(value)
if (!number) return 0
return number > 10000 ? number / 1000 : number
}
function roundProjectNumber(value, digits = 2) {
return Number(Number(value || 0).toFixed(digits))
}
function handleViewSelect(viewKey) {
const nextKey = ecologicalProtectionTopics.some((item) => item.key === viewKey) ? viewKey : "ecological-redline"
if (nextKey === currentView.value) return

@ -68,14 +68,15 @@ const tailBusinessFields = [
const grasslandCoreBusinessFields = [
[["pastureName", "pasture_name"], "所属牧场"],
[["yakCount", "yak_count"], "牦牛数量"],
[["recognizedYakCount", "recognized_yak_count", "yakCount", "yak_count"], "牦牛数量"],
[["acreage", "area_mu", "mj", "xbmj", "tbdlmj", "tbmj", "geomj", "shape_area", "areaWanMu", "area_wan_mu"], "草场面积"],
[["full_name", "townName", "town_name", "xiangName", "xiang_name", "xiang", "xzqmc", "xzqdm", "town_code", "areaCode", "area_code"], "所在区域"],
[["grassTypeName", "grass_type_name", "grassland_type_name", "grassType", "grass_type"], "草原类"],
[["grasslandLevelName", "grassland_level_name", "grasslandLevel", "grassland_level", "level_name", "level"], "草原级别"],
[["vegetationCover", "vegetation_cover"], "植被盖度"],
[["xcMuYield", "xc_mu_yield", "xbXcMuYield", "xb_xc_mu_yield", "mu_yield"], "鲜草亩产"],
[["gcMuYield", "gc_mu_yield", "xbGcMuYield", "xb_gc_mu_yield"], "干草亩产"],
[["grasslandAreaMu", "grassland_area_mu"], "草地面积"],
[["wetlandAreaMu", "wetland_area_mu"], "湿地面积"],
[["otherAreaMu", "other_area_mu"], "其他面积"],
[["resourceFreshMuYield", "resource_fresh_mu_yield"], "综合鲜草亩产"],
[["resourceDryMuYield", "resource_dry_mu_yield"], "综合干草亩产"],
[["grasslandVegetationCover", "grassland_vegetation_cover"], "草地植被盖度"],
[["fullName", "full_name", "townName", "town_name", "xiangName", "xiang_name", "xiang", "xzqmc", "xzqdm", "town_code", "areaCode", "area_code"], "所在区域"],
]
const artificialGrasslandBusinessFields = [
@ -332,6 +333,30 @@ const fieldLabels = {
related_pasture_names: "关联牧场",
yakCount: "牦牛数量",
yak_count: "牦牛数量",
recognizedYakCount: "牦牛数量",
recognized_yak_count: "牦牛数量",
grasslandAreaMu: "草地面积",
grassland_area_mu: "草地面积",
wetlandAreaMu: "湿地面积",
wetland_area_mu: "湿地面积",
otherAreaMu: "其他面积",
other_area_mu: "其他面积",
grasslandAreaPercent: "草地占比",
grassland_area_percent: "草地占比",
wetlandAreaPercent: "湿地占比",
wetland_area_percent: "湿地占比",
otherAreaPercent: "其他占比",
other_area_percent: "其他占比",
resourceFreshMuYield: "综合鲜草亩产",
resource_fresh_mu_yield: "综合鲜草亩产",
resourceDryMuYield: "综合干草亩产",
resource_dry_mu_yield: "综合干草亩产",
resourceFreshYieldJin: "鲜草产量",
resource_fresh_yield_jin: "鲜草产量",
resourceDryYieldJin: "干草产量",
resource_dry_yield_jin: "干草产量",
grasslandVegetationCover: "草地植被盖度",
grassland_vegetation_cover: "草地植被盖度",
grass_min_type: "草原型",
grassMinType: "草原型",
grass_type: "草原类",
@ -426,6 +451,7 @@ const fieldLabels = {
grass_yield: "草地产量",
grassYield: "草地产量",
full_name: "所在区域",
fullName: "所在区域",
gyl_bhlx: "公益林保护类型",
ling_zu: "龄组",
yu_bi_du: "郁闭度",
@ -562,7 +588,7 @@ const displayRows = computed(() => {
const rows = []
const used = new Set()
const isGrassland = activeTopicKey.value === "grassland"
const useCompactGrassland = isGrassland && props.compactGrassland
const useCompactGrassland = (isGrassland || activeTopicKey.value === "wetland") && props.compactGrassland
const businessFields = useCompactGrassland ? grasslandCoreBusinessFields : topicBusinessFields[activeTopicKey.value] || topicBusinessFields.woodland
businessFields.forEach(([keys, label]) => {
const row = buildRow(propsData.value, keys, label, used)
@ -608,6 +634,13 @@ function getRowPriority(row, index) {
"所属牧场",
"牦牛数量",
"草场面积",
"草地面积",
"湿地面积",
"其他面积",
"综合鲜草亩产",
"综合干草亩产",
"草地植被盖度",
"所在区域",
"牧场面积",
"关系面积",
"重叠面积",
@ -729,16 +762,26 @@ function formatValue(key, value) {
if (number === 0 && zeroHiddenKeys.has(key)) return ""
const formatted = Number.isInteger(number) ? `${number}` : `${Number(number.toFixed(2))}`
if (["xbmj", "tbdlmj", "geomj", "area_square_meter"].includes(key)) return `${formatNumber(number / 666.6667)}`
if (["grasslandAreaMu", "grassland_area_mu", "wetlandAreaMu", "wetland_area_mu", "otherAreaMu", "other_area_mu"].includes(key)) {
const percentKeys = key.startsWith("grassland")
? ["grasslandAreaPercent", "grassland_area_percent"]
: key.startsWith("wetland")
? ["wetlandAreaPercent", "wetland_area_percent"]
: ["otherAreaPercent", "other_area_percent"]
const percent = percentKeys.map((percentKey) => propsData.value?.[percentKey]).find(hasValue)
const percentText = hasValue(percent) && Number.isFinite(Number(percent)) ? `${Number(Number(percent).toFixed(2))}%)` : ""
return `${formatted}${percentText}`
}
if (["acreage", "mj", "area_mu", "pastureAcreage", "pasture_acreage", "relationAcreage", "relation_acreage", "pastureOverlapAcreage", "pasture_overlap_acreage"].includes(key)) return `${formatted}`
if (["yakCount", "yak_count"].includes(key)) return `${formatted}`
if (["recognizedYakCount", "recognized_yak_count", "yakCount", "yak_count"].includes(key)) return `${formatted}`
if (["relatedPastureCount", "related_pasture_count"].includes(key)) return `${formatted}`
if (["length_meter"].includes(key)) return `${formatted}`
if (["totalAcreage", "cdAcreage", "sdAcreage"].includes(key)) return `${formatted} 万亩`
if (["cdScl", "sdScl"].includes(key)) return `${formatted} 万吨`
if (["areaWanMu", "area_wan_mu"].includes(key)) return `${formatted} 万亩`
if (["xc_yield", "xcYield", "xb_xc_yield", "xbXcYield", "gc_yield", "gcYield", "xb_gc_yield", "xbGcYield", "edible_xc_yield", "grass_yield", "grassYield"].includes(key)) return `${formatted}`
if (["mu_yield", "xc_mu_yield", "xcMuYield", "gc_mu_yield", "gcMuYield", "xb_xc_mu_yield", "xbXcMuYield", "xb_gc_mu_yield", "xbGcMuYield", "edible_xc_mu_yield"].includes(key)) return `${formatted}`
if (["vegetation_cover", "vegetationCover", "lbmjbl", "yu_bi_du"].includes(key)) return `${formatted}%`
if (["xc_yield", "xcYield", "xb_xc_yield", "xbXcYield", "gc_yield", "gcYield", "xb_gc_yield", "xbGcYield", "edible_xc_yield", "grass_yield", "grassYield", "resourceFreshYieldJin", "resource_fresh_yield_jin", "resourceDryYieldJin", "resource_dry_yield_jin"].includes(key)) return `${formatted}`
if (["mu_yield", "xc_mu_yield", "xcMuYield", "gc_mu_yield", "gcMuYield", "xb_xc_mu_yield", "xbXcMuYield", "xb_gc_mu_yield", "xbGcMuYield", "edible_xc_mu_yield", "resourceFreshMuYield", "resource_fresh_mu_yield", "resourceDryMuYield", "resource_dry_mu_yield"].includes(key)) return `${formatted}/亩`
if (["vegetation_cover", "vegetationCover", "grasslandVegetationCover", "grassland_vegetation_cover", "lbmjbl", "yu_bi_du"].includes(key)) return `${formatted}%`
if (["elevation", "tu_ceng_hd", "soilDepth", "soil_depth", "pingjun_sg"].includes(key)) return `${formatted}`
if (["po_du", "slope"].includes(key)) return `${formatted}°`
return formatted

@ -7,6 +7,7 @@
:active-layer="homeMapLayer"
:admin-boundary-line-scale="2.4"
:initial-zoom="homeMapInitialZoom"
:minimum-zoom-distance="1"
:auto-apply-layer-map-view="false"
:enable-imagery-feature-pick="false"
hide-town-label-stats
@ -112,6 +113,7 @@
v-for="item in visibleHomeVideos"
:key="item.key"
:ref="(el) => setHomeVideoItemRef(el, item)"
:data-video-index="item.index"
class="home-video-screen"
:class="{
'is-active': item.isActive,
@ -119,19 +121,20 @@
'is-loading': isHomeVideoLoading(item.index),
}"
:style="videoScreenStyle(item.live, item.isActive, item.index)"
@click="playHomeVideoAt(item.index)"
role="button"
tabindex="0"
@click="handleHomeVideoScreenClick(item.index, $event)"
@keydown.enter.prevent="playHomeVideoAt(item.index)"
@keydown.space.prevent="playHomeVideoAt(item.index)"
>
<div v-if="item.isActive && !homeVideoModalVisible" :id="homeVideoElementId(item.index)" class="home-video-player"></div>
<div v-if="!isHomeVideoPlaying(item.index) && !isHomeVideoLoading(item.index) && item.live.hdPlayUrl" class="home-video-play"></div>
<div class="home-video-player-host"></div>
<div v-if="isHomeVideoLoading(item.index)" class="home-video-loading">加载中</div>
<div v-if="homeVideoErrorFor(item.index)" class="home-video-error">{{ homeVideoErrorFor(item.index) }}</div>
<button
v-if="item.live.hdPlayUrl && (!item.isActive || homeVideoErrorFor(item.index))"
v-if="item.live.hdPlayUrl && (!isHomeVideoMounted(item.index) || homeVideoErrorFor(item.index))"
class="home-video-hit"
type="button"
aria-label="播放监控"
@pointerdown.stop.prevent="playHomeVideoAt(item.index)"
@mousedown.stop.prevent="playHomeVideoAt(item.index)"
@click.stop="playHomeVideoAt(item.index)"
></button>
<div class="home-video-point-name">{{ item.live.title }}</div>
@ -221,9 +224,6 @@
<div
v-if="homeVideoModalRows.length"
class="home-video-modal-grid"
@pointerdown.capture="handleHomeVideoModalGridSelect"
@mousedown.capture="handleHomeVideoModalGridSelect"
@click.capture="handleHomeVideoModalGridSelect"
>
<div
v-for="item in homeVideoModalRows"
@ -240,14 +240,11 @@
:style="videoScreenStyle(item.live, item.index === activeVideoIndex, item.index)"
role="button"
tabindex="0"
@pointerdown.capture="handleHomeVideoModalPointerDown(item.index, $event)"
@mousedown.capture="handleHomeVideoModalPointerDown(item.index, $event)"
@click="playHomeVideoAt(item.index)"
@click="handleHomeVideoScreenClick(item.index, $event)"
@keydown.enter.prevent="playHomeVideoAt(item.index)"
@keydown.space.prevent="playHomeVideoAt(item.index)"
>
<div v-if="isHomeVideoMounted(item.index)" :id="homeVideoElementId(item.index)" class="home-video-player"></div>
<div v-if="item.live.status === '直播中' && !isHomeVideoPlaying(item.index) && !isHomeVideoLoading(item.index)" class="home-video-play"></div>
<div class="home-video-player-host"></div>
<div v-if="isHomeVideoLoading(item.index)" class="home-video-loading">加载中</div>
<div v-if="homeVideoErrorFor(item.index)" class="home-video-error">{{ homeVideoErrorFor(item.index) }}</div>
<button
@ -255,8 +252,6 @@
class="home-video-hit"
type="button"
aria-label="播放监控"
@pointerdown.stop.prevent="playHomeVideoAt(item.index)"
@mousedown.stop.prevent="playHomeVideoAt(item.index)"
@click.stop="playHomeVideoAt(item.index)"
></button>
<span class="home-video-point-name">{{ item.live.title }}</span>
@ -271,6 +266,20 @@
</section>
</div>
<Teleport to="body">
<div
v-if="homeVideoExpandedIndex !== null"
class="home-video-web-overlay"
role="dialog"
aria-modal="true"
aria-label="基地监控网页全屏"
>
<div ref="homeVideoWebStageRef" class="home-video-screen home-video-web-stage is-active is-playing">
<div class="home-video-web-player-slot"></div>
</div>
</div>
</Teleport>
<div
v-if="homeBalanceModalVisible"
class="home-more-modal"
@ -438,6 +447,7 @@ const props = defineProps({
const emit = defineEmits(["module-stats", "loading-progress", "map-ready"])
const mapSceneRef = ref(null)
const homeVideoScreenRef = ref(null)
const homeVideoWebStageRef = ref(null)
const homeVideoScreenRefs = new Map()
const forestGrassWetStats = ref({})
const homeChartRefs = new Map()
@ -447,6 +457,7 @@ let chartMotionTimer = null
let chartMotionKickTimer = null
const videoPlayers = new Map()
const videoPlayRuns = new Map()
const videoPlayStarts = new Set()
const videoLoadTimeoutTimers = new Map()
let homeIdleWorkHandle = null
let homeIdleWorkType = ""
@ -461,6 +472,8 @@ const activeVideoManualIndex = ref(0)
const HOME_VIDEO_AUTO_TICK_SPAN = 4
const homeVideoModalVisible = ref(false)
const homeVideoModalPage = ref(1)
const homeVideoRotationPaused = ref(false)
const homeVideoExpandedIndex = ref(null)
const homeBalanceModalVisible = ref(false)
const activeForecastGranularity = ref("month")
const videoPlaying = ref(false)
@ -679,7 +692,9 @@ const emptyHomeVideo = {
const activeVideoIndex = computed(() => {
const length = homeVideoRows.value.length
if (!length) return 0
if (videoPlaying.value || homeVideoModalVisible.value) return activeVideoManualIndex.value % length
if (homeVideoRotationPaused.value || videoPlaying.value || homeVideoModalVisible.value) {
return activeVideoManualIndex.value % length
}
return (Math.floor(chartMotionTick.value / HOME_VIDEO_AUTO_TICK_SPAN) + activeVideoManualIndex.value) % length
})
const currentHomeVideo = computed(() => homeVideoRows.value[activeVideoIndex.value] || emptyHomeVideo)
@ -978,6 +993,7 @@ onMounted(() => {
window.addEventListener("resize", updateHomeViewportHeight)
window.addEventListener("keydown", handleHomeKeyDown)
document.addEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
document.addEventListener("click", handleHomeVideoDocumentClickCapture, true)
emitHomeTopStats()
startHomeToolbarRotation()
initHomeMap()
@ -988,6 +1004,7 @@ onBeforeUnmount(() => {
window.removeEventListener("resize", updateHomeViewportHeight)
window.removeEventListener("keydown", handleHomeKeyDown)
document.removeEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
document.removeEventListener("click", handleHomeVideoDocumentClickCapture, true)
clearHomeVideoFitFrames()
stopVideoPlayer()
cancelHomeIdleWork()
@ -1227,6 +1244,7 @@ async function loadHomeMonitorings() {
homeVideoRows.value = rows
activeVideoManualIndex.value = 0
homeVideoModalPage.value = 1
homeVideoRotationPaused.value = false
videoError.value = rows.length ? "" : "暂无在线可播放摄像头"
} catch (error) {
if (destroyed) return
@ -1316,6 +1334,7 @@ function openHomeVideoModal() {
const currentIndex = activeVideoIndex.value
stopVideoPlayer({ resetError: true })
activeVideoManualIndex.value = currentIndex
homeVideoRotationPaused.value = true
chartMotionTick.value = 0
homeVideoModalPage.value = Math.floor(currentIndex / homeVideoModalPageSize) + 1
homeVideoModalVisible.value = true
@ -1351,6 +1370,7 @@ function stepVideo(step) {
stopVideoPlayer({ resetError: true })
const current = activeVideoIndex.value
activeVideoManualIndex.value = (current + step + length) % length
homeVideoRotationPaused.value = true
chartMotionTick.value = 0
}
@ -1361,43 +1381,80 @@ function playHomeVideoAt(index) {
return
}
const targetIndex = normalizeHomeVideoIndex(index)
const key = homeVideoStateKey(targetIndex)
const state = getHomeVideoState(targetIndex)
if ((state.playing || state.loading) && !state.error) {
activeVideoManualIndex.value = targetIndex
homeVideoRotationPaused.value = true
chartMotionTick.value = 0
if (videoPlayStarts.has(key) || videoPlayers.has(key) || ((state.playing || state.loading) && !state.error)) {
fitVideoPlayerToScreen(targetIndex)
return
}
activeVideoManualIndex.value = targetIndex
chartMotionTick.value = 0
void nextTick(() => playHomeVideoByIndex(targetIndex, { stopOthers: !homeVideoModalVisible.value }))
videoPlayStarts.add(key)
void nextTick(async () => {
try {
await playHomeVideoByIndex(targetIndex, { stopOthers: !homeVideoModalVisible.value })
} finally {
videoPlayStarts.delete(key)
}
})
}
function handleHomeVideoModalPointerDown(index, event) {
function handleHomeVideoScreenClick(index, event) {
if (isHomeVideoSdkControlTarget(event?.target)) return
const length = homeVideoRows.value.length
if (!length) return
const targetIndex = ((Number(index) || 0) + length) % length
if (targetIndex === activeVideoIndex.value) return
event?.preventDefault?.()
event?.stopPropagation?.()
playHomeVideoAt(targetIndex)
}
function handleHomeVideoModalGridSelect(event) {
const target = event?.target
if (!target?.closest) return
if (isHomeVideoSdkControlTarget(target)) return
const card = target.closest(".home-video-modal-item")
if (!card) return
const index = Number(card.dataset.videoIndex)
if (!Number.isFinite(index)) return
const length = homeVideoRows.value.length
if (!length) return
const targetIndex = ((index || 0) + length) % length
const isActive = targetIndex === activeVideoIndex.value
if (isActive && !homeVideoErrorFor(targetIndex)) return
event?.preventDefault?.()
event?.stopPropagation?.()
playHomeVideoAt(targetIndex)
playHomeVideoAt(index)
}
function handleHomeVideoDocumentClickCapture(event) {
const webFullscreenControl = event?.target?.closest?.('[id$="-webExpend-content"]')
|| event?.composedPath?.().find((node) => String(node?.id || "").endsWith("-webExpend-content"))
if (!webFullscreenControl) return
const screenIndex = Number(webFullscreenControl.closest?.("[data-video-index]")?.dataset?.videoIndex)
const targetIndex = homeVideoExpandedIndex.value ?? (Number.isFinite(screenIndex) ? screenIndex : activeVideoIndex.value)
event.preventDefault?.()
event.stopPropagation?.()
event.stopImmediatePropagation?.()
void toggleHomeVideoWebExpanded(targetIndex)
}
async function toggleHomeVideoWebExpanded(index) {
const targetIndex = normalizeHomeVideoIndex(index)
if (homeVideoExpandedIndex.value === targetIndex) {
closeHomeVideoWebExpanded()
return
}
if (homeVideoExpandedIndex.value !== null) closeHomeVideoWebExpanded()
const wrap = document.getElementById(`${homeVideoElementId(targetIndex)}-wrap`)
if (!wrap) return
homeVideoExpandedIndex.value = targetIndex
await nextTick()
const slot = homeVideoWebStageRef.value?.querySelector?.(".home-video-web-player-slot")
if (!slot) {
homeVideoExpandedIndex.value = null
return
}
slot.appendChild(wrap)
fitVideoPlayerToScreen(targetIndex)
scheduleVideoFit(targetIndex)
}
function closeHomeVideoWebExpanded(options = {}) {
const { restore = true } = options
const targetIndex = homeVideoExpandedIndex.value
if (targetIndex === null) return
const wrap = document.getElementById(`${homeVideoElementId(targetIndex)}-wrap`)
if (restore && wrap) {
const screen = resolveHomeVideoScreen(targetIndex) || homeVideoScreenRef.value
const host = screen?.querySelector?.(".home-video-player-host")
if (host) host.appendChild(wrap)
}
homeVideoExpandedIndex.value = null
if (restore) {
void nextTick(() => {
fitVideoPlayerToScreen(targetIndex)
scheduleVideoFit(targetIndex)
})
}
}
function isHomeVideoSdkControlTarget(target) {
@ -1438,7 +1495,7 @@ async function playHomeVideoByIndex(index, options = {}) {
const targetIndex = normalizeHomeVideoIndex(index)
const live = homeVideoRows.value[targetIndex] || emptyHomeVideo
const key = homeVideoStateKey(targetIndex)
const state = ensureHomeVideoState(targetIndex)
let state = ensureHomeVideoState(targetIndex)
if (live?.status !== "直播中" || !live?.hdPlayUrl || !live?.token) {
state.error = live?.status === "离线" ? "当前监控离线" : "暂无在线可播放摄像头"
state.loading = false
@ -1448,7 +1505,7 @@ async function playHomeVideoByIndex(index, options = {}) {
syncVideoPlaybackSummary()
return
}
if ((state.playing || state.loading) && !state.error) return
if (videoPlayers.has(key) || ((state.playing || state.loading) && !state.error)) return
activeVideoManualIndex.value = targetIndex
chartMotionTick.value = 0
@ -1457,6 +1514,7 @@ async function playHomeVideoByIndex(index, options = {}) {
} else if (videoPlayers.has(key) || state.loading) {
stopVideoPlayerAt(targetIndex, { resetError: true })
}
state = ensureHomeVideoState(targetIndex)
state.error = ""
state.loading = true
state.playing = false
@ -1470,9 +1528,10 @@ async function playHomeVideoByIndex(index, options = {}) {
let player = null
startVideoLoadTimeout(targetIndex, playRun)
try {
const screen = homeVideoScreenRefs.get(targetIndex) || homeVideoScreenRef.value
const { width: playerWidth, height: playerHeight } = getHomeVideoScreenSize(screen, 340, 92)
const screen = resolveHomeVideoScreen(targetIndex) || homeVideoScreenRef.value
const elementId = homeVideoElementId(targetIndex)
if (!mountHomeVideoElement(screen, elementId)) throw new Error("视频容器未就绪")
const { width: playerWidth, height: playerHeight } = getHomeVideoScreenSize(screen, 340, 92)
player = new EZUIKit.EZUIKitPlayer({
id: elementId,
accessToken: live.token,
@ -1613,12 +1672,36 @@ function getHomeVideoScreenSize(screen, fallbackWidth = 1, fallbackHeight = 1) {
}
}
function resolveHomeVideoScreen(index = activeVideoIndex.value) {
if (typeof document === "undefined") return null
const targetIndex = normalizeHomeVideoIndex(index)
const scope = homeVideoModalVisible.value ? ".home-video-modal-item" : ".home-video-card .home-video-screen"
const scopedScreen = document.querySelector(`${scope}[data-video-index="${targetIndex}"]`)
if (scopedScreen) return scopedScreen
const mappedScreen = homeVideoScreenRefs.get(targetIndex)
if (mappedScreen?.isConnected) return mappedScreen
return null
}
function mountHomeVideoElement(screen, elementId) {
const host = screen?.querySelector?.(".home-video-player-host")
if (!host) return null
host.replaceChildren()
const container = document.createElement("div")
container.id = elementId
container.className = "home-video-player"
host.appendChild(container)
return container
}
function fitVideoPlayerToScreen(index = activeVideoIndex.value) {
if (typeof document === "undefined") return
const targetIndex = normalizeHomeVideoIndex(index)
const key = homeVideoStateKey(targetIndex)
const elementId = homeVideoElementId(targetIndex)
const screen = homeVideoScreenRefs.get(targetIndex) || homeVideoScreenRef.value
const screen = homeVideoExpandedIndex.value === targetIndex && homeVideoWebStageRef.value
? homeVideoWebStageRef.value
: resolveHomeVideoScreen(targetIndex) || homeVideoScreenRef.value
const wrap = document.getElementById(`${elementId}-wrap`)
const player = document.getElementById(elementId)
const root = wrap || player
@ -1627,9 +1710,9 @@ function fitVideoPlayerToScreen(index = activeVideoIndex.value) {
const { width, height } = getHomeVideoScreenSize(screen)
const ezvizPlayer = videoPlayers.get(key)
try {
if (ezvizPlayer && typeof ezvizPlayer.reSize === "function") {
if (wrap && player && ezvizPlayer && typeof ezvizPlayer.reSize === "function") {
ezvizPlayer.reSize(width, height)
} else if (ezvizPlayer && typeof ezvizPlayer.autoResize === "function") {
} else if (wrap && player && ezvizPlayer && typeof ezvizPlayer.autoResize === "function") {
ezvizPlayer.autoResize()
}
} catch (error) {
@ -1783,19 +1866,16 @@ function stopEzvizPlayer(player) {
function clearVideoElement(index = activeVideoIndex.value) {
if (typeof document === "undefined") return
const elementId = homeVideoElementId(index)
const targetIndex = normalizeHomeVideoIndex(index)
const elementId = homeVideoElementId(targetIndex)
const wrap = document.getElementById(`${elementId}-wrap`)
if (wrap) {
const container = document.createElement("div")
container.id = elementId
container.className = "home-video-player"
wrap.replaceWith(container)
return
}
const container = document.getElementById(elementId)
if (container) {
container.replaceChildren()
}
const screen = resolveHomeVideoScreen(targetIndex) || homeVideoScreenRef.value
const host = screen?.querySelector?.(".home-video-player-host")
if (homeVideoExpandedIndex.value === targetIndex) closeHomeVideoWebExpanded({ restore: false })
if (host) host.replaceChildren()
if (wrap?.isConnected) wrap.remove()
if (container?.isConnected) container.remove()
}
function buildEzvizThemeData() {
@ -1824,7 +1904,7 @@ function buildEzvizThemeData() {
{ iconId: "recordvideo", part: "left", defaultActive: 0, memo: "录制按钮", isrender: 0 },
{ iconId: "zoom", part: "left", defaultActive: 0, memo: "电子放大", isrender: 0 },
{ iconId: "hd", part: "right", defaultActive: 0, memo: "清晰度切换按钮", isrender: 1 },
{ iconId: "expend", part: "right", defaultActive: 0, memo: "全局全屏按钮", isrender: 1 },
{ iconId: "webExpend", part: "right", defaultActive: 0, memo: "网页全屏按钮", isrender: 1 },
],
},
}
@ -2795,6 +2875,10 @@ function stopHomeToolbarRotation() {
function handleHomeKeyDown(event) {
if (event.key !== "Escape") return
if (homeVideoExpandedIndex.value !== null) {
closeHomeVideoWebExpanded()
return
}
closeHomeVideoModal()
closeHomeBalanceModal()
}
@ -4087,6 +4171,28 @@ function handleOutsideFeaturePointerDown(event) {
background-image: none !important;
}
.home-video-web-overlay {
position: fixed;
inset: 0;
z-index: 100000;
overflow: hidden;
background: #000000;
}
.home-video-web-stage {
width: 100vw;
height: 100vh;
border: 0;
cursor: default;
}
.home-video-web-player-slot {
position: absolute;
inset: 0;
overflow: hidden;
}
.home-video-screen > .home-video-player-host,
.home-video-screen > .home-video-player,
.home-video-screen [id$="-wrap"],
.home-video-screen [id$="-player"],
@ -4115,6 +4221,7 @@ function handleOutsideFeaturePointerDown(event) {
object-fit: fill !important;
}
.home-video-screen > .home-video-player-host,
.home-video-screen > .home-video-player,
.home-video-screen [id$="-wrap"] {
z-index: 3;
@ -4219,32 +4326,6 @@ function handleOutsideFeaturePointerDown(event) {
background: linear-gradient(180deg, rgba(0, 0, 0, 0.14), transparent 28%, rgba(0, 0, 0, 0.18));
}
.home-video-play {
position: absolute;
left: 50%;
top: 46%;
z-index: 4;
width: 30px;
height: 30px;
border: 1px solid rgba(255, 255, 255, 0.62);
border-radius: 50%;
background: rgba(0, 0, 0, 0.36);
transform: translate(-50%, -50%);
box-shadow: none;
}
.home-video-play::after {
position: absolute;
left: 12px;
top: 8px;
width: 0;
height: 0;
content: "";
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-left: 10px solid #ffffff;
}
.home-video-hit {
position: absolute;
inset: 0;
@ -4255,7 +4336,6 @@ function handleOutsideFeaturePointerDown(event) {
background: transparent;
}
.home-video-play,
.home-video-point-name,
.home-video-loading,
.home-video-error {

@ -8,6 +8,7 @@
:tile-url-template="pastureMapTileUrlTemplate"
:center="pastureOlCenter"
:initial-zoom="9"
:minimum-zoom-distance="1"
:auto-apply-layer-map-view="false"
:max-zoom="21"
:overlay-layer="pastureMapLayer"
@ -213,6 +214,7 @@ const nonBusinessGrasslandFeatureKeys = new Set([
const pastureMapTileUrlTemplate = "https://qkl-map.oss-cn-chengdu.aliyuncs.com/hy-result/{z}/{x}/{y}.png"
const pastureOlCenter = [102.58, 32.72]
const pastureBoundaryColor = "#30DCFF"
const yakRecognitionYellow = "#FFE600"
const pastureBoundarySldBody = createPastureBoundarySld("ne:t_pasture_grassland", pastureBoundaryColor)
const pastureYakRecognitionLayer = {
key: pastureYakLayerKey,
@ -225,19 +227,19 @@ const pastureYakRecognitionLayer = {
maximumLevel: 21,
url: "/geoserver/gwc/service/wmts",
name: "牦牛识别标记",
color: "#FFCB45",
fillColor: "rgba(255, 203, 69, 0)",
strokeColor: "#FFCB45",
glowColor: "rgba(255, 203, 69, 0.28)",
opacity: 0.22,
color: yakRecognitionYellow,
fillColor: "rgba(255, 230, 0, 0)",
strokeColor: yakRecognitionYellow,
glowColor: "rgba(255, 230, 0, 0.42)",
opacity: 0.92,
lineWidth: 1.4,
zIndex: 46,
legends: [
{
key: "pasture-yak-marks",
name: "牦牛识别",
fillColor: "#FFCB45",
strokeColor: "#FFF4B8",
fillColor: yakRecognitionYellow,
strokeColor: "#FFF58A",
selectable: false,
},
],
@ -1264,6 +1266,7 @@ function normalizePastureClickProperties(payload = {}) {
const balance = pickFirstObject(data?.pasture_balance || data?.pastureBalance || data?.data?.pasture_balance)
const source = grassland || pickFirstObject(data?.data) || pickFirstObject(data?.result) || pickFirstObject(data)
if (!source && !pasture && !balance) return null
const recognizedYakCount = pickValue(source || {}, ["recognizedYakCount", "recognized_yak_count"])
return {
...(source || {}),
...(balance || {}),
@ -1271,7 +1274,8 @@ function normalizePastureClickProperties(payload = {}) {
...flattenDynamicColumns(pasture),
...flattenDynamicColumns(balance),
pastureName: pickValue(pasture || {}, ["name", "pasturesName", "pastures_name", "pastureName", "pasture_name"]) || pickValue(source || {}, ["pastureName", "pasture_name"]),
yakCount: pickValue(pasture || {}, ["yakCount", "yak_count", "yakNumber", "yak_number"]) || pickValue(balance || {}, ["yakCount", "yak_count", "yakNumber", "yak_number"]),
recognizedYakCount,
yakCount: recognizedYakCount ?? pickValue(pasture || {}, ["yakCount", "yak_count", "yakNumber", "yak_number"]) ?? pickValue(balance || {}, ["yakCount", "yak_count", "yakNumber", "yak_number"]),
}
}

@ -1231,6 +1231,7 @@ const activeMapValueKey = computed(() => {
return "count"
})
const centerMapRows = computed(() => buildCenterMapRows(activeMapRows.value, activeMapValueKey.value))
const yakRecognitionYellow = "#FFE600"
const yakRecognitionLayer = computed(() => ({
key: "yak-marks-layer",
sourceType: "wmts",
@ -1243,12 +1244,12 @@ const yakRecognitionLayer = computed(() => ({
url: "/geoserver/gwc/service/wmts",
cqlFilter: "gov_show = true",
name: "牦牛识别标记",
opacity: 0.28,
opacity: 0.92,
queryBuffer: 10,
geometryField: "geom",
fillColor: "rgba(255, 203, 69, 0)",
strokeColor: "#FFCB45",
glowColor: "rgba(255, 203, 69, 0.28)",
fillColor: "rgba(255, 230, 0, 0)",
strokeColor: yakRecognitionYellow,
glowColor: "rgba(255, 230, 0, 0.42)",
lineWidth: 1.4,
mapView: {
targetLngLat: yakDefaultMapCenter,
@ -1260,8 +1261,8 @@ const yakRecognitionLayer = computed(() => ({
{
key: "yak-marks",
name: "牦牛",
fillColor: "#FFCB45",
strokeColor: "#FFF4B8",
fillColor: yakRecognitionYellow,
strokeColor: "#FFF58A",
selectable: false,
},
],

@ -117,8 +117,15 @@ export default ({ mode }) => {
changeOrigin: true,
},
'/terrain': {
target: 'http://data.mars3d.cn',
target: 'https://data.mars3d.cn',
changeOrigin: true,
configure: (proxy) => {
proxy.on('proxyReq', (proxyReq) => {
proxyReq.setHeader('User-Agent', 'Mozilla/5.0')
proxyReq.setHeader('Referer', 'https://mars3d.cn/')
proxyReq.setHeader('Origin', 'https://mars3d.cn')
})
},
},
'/hy-result': {
target: hyResultTileTarget,

Loading…
Cancel
Save