Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
basic-uniapp-v3
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
1
合并请求
1
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Basic
basic-uniapp-v3
Commits
8b340651
提交
8b340651
authored
4月 22, 2026
作者:
王定
浏览文件
操作
浏览文件
下载
差异文件
合并分支
上级
2c9e0a0e
13efb5af
隐藏空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
872 行增加
和
18 行删除
+872
-18
weather.ts
src/api/model/weather.ts
+23
-1
echarts.module.js
src/components/Echarts/src/echarts.module.js
+14
-6
hook.ts
src/components/Echarts/src/hook.ts
+2
-2
index.vue
src/components/Echarts/src/index.vue
+6
-1
WeatherChartPopup.vue
src/pages/nongchang/detail/components/WeatherChartPopup.vue
+617
-0
index.vue
src/pages/nongchang/detail/index.vue
+210
-8
没有找到文件。
src/api/model/weather.ts
浏览文件 @
8b340651
import
{
weatherHttp
}
from
'/@/utils/http/axios'
import
{
otherHttp
,
weatherHttp
}
from
'/@/utils/http/axios'
const
API_KEY
=
'4fb5c9e814994516b6522419651f2e9e'
...
...
@@ -22,3 +22,25 @@ export function alarm(location: string) {
url
:
`/weatheralert/v1/current/
${
location
.
replace
(
','
,
'/'
)}
?key=
${
API_KEY
}
`
,
})
}
// 格点天气预报-日值(降雨、大风用)
export
function
gridForecastDaily
(
location
:
string
,
day
=
3
)
{
return
weatherHttp
.
get
({
url
:
`/v7/weather/
${
day
}
d?key=
${
API_KEY
}
&location=
${
location
}
`
,
})
}
// 格点天气预报-小时值(温度用)
export
function
gridForecastHourly
(
location
:
string
)
{
return
weatherHttp
.
get
({
url
:
`/v7/weather/24h?key=
${
API_KEY
}
&location=
${
location
}
`
,
})
}
// 强对流预报(自建接口)
export
function
severeForecast
(
lon
:
string
,
lat
:
string
)
{
return
otherHttp
.
get
({
url
:
'/weather/forecast'
,
params
:
{
lon
,
lat
},
},
{
isTransformResponse
:
false
})
}
src/components/Echarts/src/echarts.module.js
浏览文件 @
8b340651
...
...
@@ -13,17 +13,17 @@ export default {
},
methods
:
{
loadLibs
:
loadEchartsLibs
,
init
()
{
init
(
notMerge
)
{
// 如果已经初始化过了,就直接更新配置项
if
(
this
.
chart
&&
this
.
chart
.
setOption
)
{
this
.
chart
.
setOption
(
this
.
option
)
this
.
chart
.
setOption
(
this
.
option
,
{
notMerge
:
notMerge
||
false
}
)
return
}
// 初始化组件
const
chart
=
window
.
echarts
.
init
(
document
.
getElementById
(
this
.
option
.
id
))
// 设置图表配置项
chart
.
setOption
(
this
.
option
)
chart
.
setOption
(
this
.
option
,
{
notMerge
:
notMerge
||
false
}
)
this
.
chart
=
chart
},
changeOption
(
option
)
{
...
...
@@ -45,12 +45,20 @@ export default {
}
}
this
.
option
=
merge
(
this
.
option
,
option
)
const
notMerge
=
option
.
__notMerge
// 清除内部标记
delete
option
.
__notMerge
if
(
notMerge
)
{
this
.
option
=
option
}
else
{
this
.
option
=
merge
(
this
.
option
,
option
)
}
if
(
typeof
window
.
echarts
===
'object'
&&
typeof
window
.
echarts
.
init
===
'function'
)
{
this
.
init
()
this
.
init
(
notMerge
)
}
else
{
this
.
loadLibs
().
then
(()
=>
{
this
.
init
()
this
.
init
(
notMerge
)
})
}
},
...
...
src/components/Echarts/src/hook.ts
浏览文件 @
8b340651
...
...
@@ -44,10 +44,10 @@ export function useEcharts<T extends EchartsInstance, P extends EChartsOption>(
return
[
register
,
{
setOption
:
(
option
:
Partial
<
P
>
):
Promise
<
void
>
=>
{
setOption
:
(
option
:
Partial
<
P
>
,
opts
?:
{
notMerge
?:
boolean
;
lazyUpdate
?:
boolean
}
):
Promise
<
void
>
=>
{
return
new
Promise
((
resolve
)
=>
{
tryOnMounted
(()
=>
{
getInstance
()?.
setOption
(
option
)
getInstance
()?.
setOption
(
option
,
opts
)
resolve
()
})
})
...
...
src/components/Echarts/src/index.vue
浏览文件 @
8b340651
...
...
@@ -14,7 +14,7 @@
this
.
$emit
(
'register'
,
this
)
},
methods
:
{
setOption
(
option
)
{
setOption
(
option
,
opts
)
{
// 处理tooltip formatter函数序列化
if
(
option
.
tooltip
&&
typeof
option
.
tooltip
.
formatter
===
'function'
)
{
// 将函数转换为字符串,在视图层重新构建
...
...
@@ -25,6 +25,11 @@
id
:
this
.
id
,
...
option
,
}
// 保存 opts 供 renderjs 层使用
if
(
opts
?.
notMerge
!==
undefined
)
{
this
.
option
.
__notMerge
=
opts
.
notMerge
}
},
},
}
...
...
src/pages/nongchang/detail/components/WeatherChartPopup.vue
0 → 100644
浏览文件 @
8b340651
<!-- 天气折线图弹窗 -->
<
script
setup
lang=
"ts"
>
import
dayjs
from
'dayjs'
import
{
Echarts
,
useEcharts
}
from
'@/components/Echarts'
import
*
as
WeatherAPI
from
'@/api/model/weather'
const
props
=
defineProps
<
{
visible
:
boolean
type
:
'rain'
|
'temp'
|
'wind'
|
'severe'
lon
:
string
|
number
lat
:
string
|
number
}
>
()
const
emit
=
defineEmits
([
'close'
])
const
[
register
,
chart
]
=
useEcharts
()
// 标题映射
const
titleMap
=
{
rain
:
'降雨预报'
,
temp
:
'温度预报'
,
wind
:
'大风预报'
,
severe
:
'强对流预报'
,
}
// Y 轴单位映射
const
unitMap
=
{
rain
:
'mm'
,
temp
:
'°C'
,
wind
:
'km/h'
,
severe
:
''
,
}
// 加载状态
const
loading
=
ref
(
false
)
const
errorMsg
=
ref
(
''
)
const
updateText
=
ref
(
''
)
// 请求序号,用于取消旧请求
let
requestSeq
=
0
// 监听 visible/type/lon/lat 变化,拉取数据
watch
(
()
=>
[
props
.
visible
,
props
.
type
,
props
.
lon
,
props
.
lat
]
as
const
,
async
([
visible
])
=>
{
if
(
!
visible
)
{
return
}
if
(
!
props
.
lon
||
!
props
.
lat
)
{
errorMsg
.
value
=
'未设置农场坐标位置'
return
}
await
fetchWeatherData
()
},
{
immediate
:
true
},
)
async
function
fetchWeatherData
()
{
const
seq
=
++
requestSeq
loading
.
value
=
true
errorMsg
.
value
=
''
try
{
let
data
:
any
switch
(
props
.
type
)
{
case
'rain'
:
data
=
await
WeatherAPI
.
gridForecastDaily
(
`
${
props
.
lon
}
,
${
props
.
lat
}
`
,
7
)
if
(
seq
!==
requestSeq
)
return
await
nextTick
()
renderRainChart
(
data
.
data
)
break
case
'temp'
:
data
=
await
WeatherAPI
.
gridForecastDaily
(
`
${
props
.
lon
}
,
${
props
.
lat
}
`
,
7
)
if
(
seq
!==
requestSeq
)
return
await
nextTick
()
renderTempChart
(
data
.
data
)
break
case
'wind'
:
data
=
await
WeatherAPI
.
gridForecastDaily
(
`
${
props
.
lon
}
,
${
props
.
lat
}
`
,
7
)
if
(
seq
!==
requestSeq
)
return
await
nextTick
()
renderWindChart
(
data
.
data
)
break
case
'severe'
:
data
=
await
WeatherAPI
.
severeForecast
(
String
(
props
.
lon
),
String
(
props
.
lat
))
if
(
seq
!==
requestSeq
)
return
await
nextTick
()
renderSevereChart
(
data
)
break
}
updateText
.
value
=
`数据更新时间:
${
dayjs
().
format
(
'YYYY-MM-DD HH:mm'
)}
`
}
catch
(
e
:
any
)
{
console
.
error
(
'获取天气数据失败:'
,
e
)
// 如果在此期间有新请求,不覆盖其错误状态
if
(
seq
!==
requestSeq
)
{
return
}
errorMsg
.
value
=
'数据加载失败'
}
finally
{
loading
.
value
=
false
}
}
// 渲染空图表
function
renderEmptyChart
(
hint
?:
string
)
{
chart
.
setOption
(
{
title
:
{
show
:
false
},
tooltip
:
{
show
:
false
},
grid
:
{
top
:
60
,
right
:
30
,
bottom
:
40
,
left
:
60
},
xAxis
:
{
type
:
'category'
,
data
:
[],
show
:
false
},
yAxis
:
{
type
:
'value'
,
show
:
false
},
series
:
[],
graphic
:
{
type
:
'text'
,
left
:
'center'
,
top
:
'middle'
,
style
:
{
text
:
hint
||
'暂无数据'
,
fontSize
:
16
,
fill
:
'#bbb'
,
},
},
},
{
notMerge
:
true
},
)
}
function
renderRainChart
(
data
:
any
)
{
if
(
!
data
?.
daily
||
!
Array
.
isArray
(
data
.
daily
))
{
renderEmptyChart
(
'暂无预报数据'
)
return
}
const
dates
=
data
.
daily
.
map
((
d
:
any
)
=>
dayjs
(
d
.
fxDate
).
format
(
'MM-DD'
))
const
precip
=
data
.
daily
.
map
((
d
:
any
)
=>
Number
.
parseFloat
(
d
.
precip
)
||
0
)
chart
.
setOption
(
{
title
:
{
show
:
false
},
tooltip
:
{
trigger
:
'axis'
},
legend
:
{
top
:
0
,
textStyle
:
{
fontSize
:
14
,
color
:
'#333'
},
},
grid
:
{
top
:
60
,
right
:
30
,
bottom
:
40
,
left
:
60
},
xAxis
:
{
type
:
'category'
,
data
:
dates
,
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
,
margin
:
10
},
axisLine
:
{
lineStyle
:
{
color
:
'#eee'
}
},
axisTick
:
{
show
:
false
},
},
yAxis
:
{
type
:
'value'
,
name
:
'(mm)'
,
nameTextStyle
:
{
fontSize
:
14
,
color
:
'#bbb'
,
padding
:
[
0
,
0
,
0
,
-
10
]
},
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
},
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
splitLine
:
{
lineStyle
:
{
type
:
'dashed'
,
color
:
'#eee'
,
width
:
1
}
},
},
series
:
[
{
name
:
'降水量'
,
type
:
'line'
,
data
:
precip
,
smooth
:
true
,
showSymbol
:
true
,
symbolSize
:
6
,
itemStyle
:
{
color
:
'#6db876'
},
label
:
{
show
:
true
,
position
:
'top'
,
fontSize
:
12
,
color
:
'#666'
,
},
lineStyle
:
{
color
:
'#6db876'
,
width
:
2
},
areaStyle
:
{
color
:
{
type
:
'linear'
,
x
:
0
,
y
:
0
,
x2
:
0
,
y2
:
1
,
colorStops
:
[
{
offset
:
0
,
color
:
'rgba(109,184,118,0.3)'
},
{
offset
:
1
,
color
:
'rgba(109,184,118,0)'
},
],
},
},
},
],
},
{
notMerge
:
true
},
)
}
function
renderTempChart
(
data
:
any
)
{
if
(
!
data
?.
daily
||
!
Array
.
isArray
(
data
.
daily
))
{
renderEmptyChart
(
'暂无预报数据'
)
return
}
const
dates
=
data
.
daily
.
map
((
d
:
any
)
=>
dayjs
(
d
.
fxDate
).
format
(
'MM-DD'
))
const
tempMax
=
data
.
daily
.
map
((
d
:
any
)
=>
Number
.
parseFloat
(
d
.
tempMax
)
||
0
)
const
tempMin
=
data
.
daily
.
map
((
d
:
any
)
=>
Number
.
parseFloat
(
d
.
tempMin
)
||
0
)
chart
.
setOption
(
{
title
:
{
show
:
false
},
tooltip
:
{
trigger
:
'axis'
},
legend
:
{
top
:
0
,
textStyle
:
{
fontSize
:
14
,
color
:
'#333'
},
},
grid
:
{
top
:
60
,
right
:
30
,
bottom
:
40
,
left
:
60
},
xAxis
:
{
type
:
'category'
,
data
:
dates
,
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
,
margin
:
10
},
axisLine
:
{
lineStyle
:
{
color
:
'#eee'
}
},
axisTick
:
{
show
:
false
},
},
yAxis
:
{
type
:
'value'
,
name
:
'(°C)'
,
nameTextStyle
:
{
fontSize
:
14
,
color
:
'#bbb'
,
padding
:
[
0
,
0
,
0
,
-
10
]
},
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
},
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
splitLine
:
{
lineStyle
:
{
type
:
'dashed'
,
color
:
'#eee'
,
width
:
1
}
},
},
series
:
[
{
name
:
'最高温度'
,
type
:
'line'
,
data
:
tempMax
,
smooth
:
true
,
showSymbol
:
true
,
symbolSize
:
6
,
itemStyle
:
{
color
:
'#e74c3c'
},
label
:
{
show
:
true
,
position
:
'top'
,
fontSize
:
12
,
color
:
'#666'
,
},
lineStyle
:
{
color
:
'#e74c3c'
,
width
:
2
},
areaStyle
:
{
color
:
{
type
:
'linear'
,
x
:
0
,
y
:
0
,
x2
:
0
,
y2
:
1
,
colorStops
:
[
{
offset
:
0
,
color
:
'rgba(231,76,60,0.3)'
},
{
offset
:
1
,
color
:
'rgba(231,76,60,0)'
},
],
},
},
},
{
name
:
'最低温度'
,
type
:
'line'
,
data
:
tempMin
,
smooth
:
true
,
showSymbol
:
true
,
symbolSize
:
6
,
itemStyle
:
{
color
:
'#3498db'
},
label
:
{
show
:
true
,
position
:
'bottom'
,
fontSize
:
12
,
color
:
'#666'
,
},
lineStyle
:
{
color
:
'#3498db'
,
width
:
2
},
areaStyle
:
{
color
:
{
type
:
'linear'
,
x
:
0
,
y
:
0
,
x2
:
0
,
y2
:
1
,
colorStops
:
[
{
offset
:
0
,
color
:
'rgba(52,152,219,0.2)'
},
{
offset
:
1
,
color
:
'rgba(52,152,219,0)'
},
],
},
},
},
],
},
{
notMerge
:
true
},
)
}
function
renderWindChart
(
data
:
any
)
{
if
(
!
data
?.
daily
||
!
Array
.
isArray
(
data
.
daily
))
{
renderEmptyChart
(
'暂无预报数据'
)
return
}
const
dates
=
data
.
daily
.
map
((
d
:
any
)
=>
dayjs
(
d
.
fxDate
).
format
(
'MM-DD'
))
const
windDay
=
data
.
daily
.
map
((
d
:
any
)
=>
Number
.
parseFloat
(
d
.
windSpeedDay
)
||
0
)
const
windNight
=
data
.
daily
.
map
((
d
:
any
)
=>
Number
.
parseFloat
(
d
.
windSpeedNight
)
||
0
)
chart
.
setOption
(
{
title
:
{
show
:
false
},
tooltip
:
{
trigger
:
'axis'
},
legend
:
{
top
:
0
,
textStyle
:
{
fontSize
:
14
,
color
:
'#333'
},
},
grid
:
{
top
:
60
,
right
:
30
,
bottom
:
40
,
left
:
60
},
xAxis
:
{
type
:
'category'
,
data
:
dates
,
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
,
margin
:
10
},
axisLine
:
{
lineStyle
:
{
color
:
'#eee'
}
},
axisTick
:
{
show
:
false
},
},
yAxis
:
{
type
:
'value'
,
name
:
'(km/h)'
,
nameTextStyle
:
{
fontSize
:
14
,
color
:
'#bbb'
,
padding
:
[
0
,
0
,
0
,
-
10
]
},
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
},
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
splitLine
:
{
lineStyle
:
{
type
:
'dashed'
,
color
:
'#eee'
,
width
:
1
}
},
},
series
:
[
{
name
:
'白天风速'
,
type
:
'line'
,
data
:
windDay
,
smooth
:
true
,
showSymbol
:
true
,
symbolSize
:
6
,
itemStyle
:
{
color
:
'#e67e22'
},
label
:
{
show
:
true
,
position
:
'top'
,
fontSize
:
12
,
color
:
'#666'
,
},
lineStyle
:
{
color
:
'#e67e22'
,
width
:
2
},
areaStyle
:
{
color
:
{
type
:
'linear'
,
x
:
0
,
y
:
0
,
x2
:
0
,
y2
:
1
,
colorStops
:
[
{
offset
:
0
,
color
:
'rgba(230,126,34,0.3)'
},
{
offset
:
1
,
color
:
'rgba(230,126,34,0)'
},
],
},
},
},
{
name
:
'夜间风速'
,
type
:
'line'
,
data
:
windNight
,
smooth
:
true
,
showSymbol
:
true
,
symbolSize
:
6
,
itemStyle
:
{
color
:
'#3498db'
},
label
:
{
show
:
true
,
position
:
'bottom'
,
fontSize
:
12
,
color
:
'#666'
,
},
lineStyle
:
{
color
:
'#3498db'
,
width
:
2
},
areaStyle
:
{
color
:
{
type
:
'linear'
,
x
:
0
,
y
:
0
,
x2
:
0
,
y2
:
1
,
colorStops
:
[
{
offset
:
0
,
color
:
'rgba(52,152,219,0.2)'
},
{
offset
:
1
,
color
:
'rgba(52,152,219,0)'
},
],
},
},
},
],
},
{
notMerge
:
true
},
)
}
function
renderSevereChart
(
data
:
any
)
{
if
(
!
data
?.
elements
||
!
Array
.
isArray
(
data
.
elements
)
||
data
.
elements
.
length
===
0
)
{
renderEmptyChart
(
'暂无预报数据'
)
return
}
const
elements
=
data
.
elements
// 从第一个要素提取时间轴
const
firstElement
=
elements
[
0
]
if
(
!
firstElement
?.
timeDataList
||
!
Array
.
isArray
(
firstElement
.
timeDataList
)
||
firstElement
.
timeDataList
.
length
===
0
)
{
renderEmptyChart
(
'暂无预报数据'
)
return
}
const
times
=
firstElement
.
timeDataList
.
map
((
d
:
any
)
=>
dayjs
(
d
.
forecastTime
).
format
(
'HH:mm'
))
const
colors
=
[
'#e74c3c'
,
'#3498db'
,
'#f39c12'
]
const
series
=
elements
.
map
((
el
:
any
,
idx
:
number
)
=>
({
name
:
el
.
name
,
type
:
'line'
,
data
:
el
.
timeDataList
.
map
((
d
:
any
)
=>
d
.
value
||
0
),
smooth
:
true
,
showSymbol
:
true
,
symbolSize
:
6
,
itemStyle
:
{
color
:
colors
[
idx
%
colors
.
length
]
},
label
:
{
show
:
true
,
position
:
'top'
,
fontSize
:
12
,
color
:
'#666'
,
},
lineStyle
:
{
color
:
colors
[
idx
%
colors
.
length
],
width
:
2
},
areaStyle
:
{
color
:
{
type
:
'linear'
,
x
:
0
,
y
:
0
,
x2
:
0
,
y2
:
1
,
colorStops
:
[
{
offset
:
0
,
color
:
`
${
colors
[
idx
%
colors
.
length
]}
4D`
},
{
offset
:
1
,
color
:
`
${
colors
[
idx
%
colors
.
length
]}
00`
},
],
},
},
}))
// Y 轴单位:取第一个要素的单位
const
unit
=
elements
[
0
]?.
timeDataList
?.[
0
]?.
unit
||
''
chart
.
setOption
(
{
title
:
{
show
:
false
},
tooltip
:
{
trigger
:
'axis'
},
legend
:
{
top
:
0
,
textStyle
:
{
fontSize
:
14
,
color
:
'#333'
},
},
grid
:
{
top
:
60
,
right
:
30
,
bottom
:
40
,
left
:
60
},
xAxis
:
{
type
:
'category'
,
data
:
times
,
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
,
margin
:
10
},
axisLine
:
{
lineStyle
:
{
color
:
'#eee'
}
},
axisTick
:
{
show
:
false
},
},
yAxis
:
{
type
:
'value'
,
name
:
`(
${
unit
}
)`
,
nameTextStyle
:
{
fontSize
:
14
,
color
:
'#bbb'
,
padding
:
[
0
,
0
,
0
,
-
10
]
},
axisLabel
:
{
fontSize
:
14
,
color
:
'#bbb'
},
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
splitLine
:
{
lineStyle
:
{
type
:
'dashed'
,
color
:
'#eee'
,
width
:
1
}
},
},
series
,
},
{
notMerge
:
true
},
)
}
</
script
>
<
template
>
<view
v-show=
"visible"
class=
"weather-chart-overlay"
@
tap=
"emit('close')"
>
<view
class=
"chart-popup"
@
tap
.
stop
>
<!-- 标题栏 -->
<view
class=
"popup-header"
>
<text
class=
"popup-title"
>
{{
titleMap
[
type
]
}}
</text>
<view
class=
"close-btn"
@
tap=
"emit('close')"
>
<text
class=
"close-icon"
>
×
</text>
</view>
</view>
<!-- 加载/错误状态 -->
<view
v-if=
"loading"
class=
"chart-status"
>
<text>
加载中...
</text>
</view>
<view
v-else-if=
"errorMsg"
class=
"chart-status error"
>
<text>
{{
errorMsg
}}
</text>
<view
class=
"retry-btn"
@
tap=
"fetchWeatherData"
>
<text>
重试
</text>
</view>
</view>
<!-- 图表区域 -->
<view
v-show=
"!loading && !errorMsg"
class=
"chart-container"
>
<Echarts
@
register=
"register"
class=
"chart-area"
/>
</view>
<!-- 更新时间 -->
<view
v-if=
"updateText && !loading && !errorMsg"
class=
"update-info"
>
<text
class=
"update-text"
>
{{
updateText
}}
</text>
</view>
</view>
</view>
</
template
>
<
style
lang=
"scss"
scoped
>
.weather-chart-overlay
{
position
:
fixed
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
z-index
:
998
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background-color
:
rgba
(
0
,
0
,
0
,
0.3
);
}
.chart-popup
{
width
:
90%
;
max-width
:
600px
;
background
:
linear-gradient
(
180deg
,
rgba
(
200
,
255
,
200
,
1
)
0%
,
rgba
(
255
,
255
,
255
,
1
)
40%
);
border-radius
:
32
rpx
;
position
:
relative
;
overflow
:
hidden
;
transform
:
translateY
(
-40%
);
}
.popup-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
10
rpx
30
rpx
10
rpx
;
.popup-title
{
font-size
:
36
rpx
;
font-weight
:
600
;
color
:
#333
;
}
.close-btn
{
width
:
60
rpx
;
height
:
60
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
&:active
{
opacity
:
0.6
;
}
.close-icon
{
font-size
:
36
rpx
;
color
:
#ccc
;
font-weight
:
300
;
}
}
}
.chart-status
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
height
:
400
rpx
;
font-size
:
28
rpx
;
color
:
#999
;
&.error
{
color
:
#e74c3c
;
}
}
.retry-btn
{
margin-top
:
20
rpx
;
padding
:
16
rpx
40
rpx
;
background-color
:
#5db66f
;
color
:
#fff
;
border-radius
:
32
rpx
;
font-size
:
26
rpx
;
&:active
{
opacity
:
0.8
;
}
}
.chart-container
{
width
:
100%
;
height
:
350
rpx
;
}
.chart-area
{
width
:
100%
;
height
:
100%
;
}
.update-info
{
padding
:
0
30
rpx
20
rpx
;
text-align
:
center
;
.update-text
{
font-size
:
20
rpx
;
color
:
#bbb
;
}
}
</
style
>
src/pages/nongchang/detail/index.vue
浏览文件 @
8b340651
...
...
@@ -16,6 +16,7 @@
import
{
ToolBoxWidget
,
useToolBoxWidget
}
from
'@/components/Map/Widgets/ToolBox'
import
*
as
NongchangAPI
from
'@/api/model/nongchang'
import
*
as
farmbaseApi
from
'@/api/model/farmbase'
import
WeatherChartPopup
from
'./components/WeatherChartPopup.vue'
// 页面参数
const
page
=
reactive
<
Page
>
({
...
...
@@ -64,7 +65,11 @@
farmbaseInfo
:
{},
farmbaseInfoList
:
[],
deviceTypeCount
:
[],
activeWeatherType
:
''
as
''
|
'rain'
|
'temp'
|
'wind'
|
'severe'
,
})
// 当前基地中心经纬度(用于天气接口请求)
const
baseCenterLonLat
=
ref
<
[
number
,
number
]
|
null
>
(
null
)
onLoad
(()
=>
{
const
today
=
dayjs
()
for
(
let
i
=
0
;
i
<
5
;
i
++
)
{
...
...
@@ -91,6 +96,51 @@
farmbaseApi
.
getFarmbaseInfoById
({
id
}).
then
((
res
)
=>
{
model
.
farmbaseInfo
=
res
getDeviceTypeCount
()
<<<<<<<
HEAD
=======
// 切换基地后,将地图视角飞到当前基地上方
let
geojson
=
res
.
geojson
if
(
typeof
geojson
===
'string'
)
{
try
{
geojson
=
JSON
.
parse
(
geojson
)
}
catch
(
e
)
{
geojson
=
null
}
}
if
(
geojson
)
{
// 直接从 geometry 坐标计算中心点和缩放范围
const
coords
=
geojson
.
features
?.[
0
]?.
geometry
?.
coordinates
?.[
0
]
if
(
coords
&&
coords
.
length
>
0
)
{
let
minLon
=
Infinity
,
minLat
=
Infinity
,
maxLon
=
-
Infinity
,
maxLat
=
-
Infinity
for
(
const
c
of
coords
)
{
if
(
c
[
0
]
<
minLon
)
minLon
=
c
[
0
]
if
(
c
[
0
]
>
maxLon
)
maxLon
=
c
[
0
]
if
(
c
[
1
]
<
minLat
)
minLat
=
c
[
1
]
if
(
c
[
1
]
>
maxLat
)
maxLat
=
c
[
1
]
}
const
center
:
[
number
,
number
]
=
[(
minLon
+
maxLon
)
/
2
,
(
minLat
+
maxLat
)
/
2
]
// 存储基地中心经纬度,供天气接口使用
baseCenterLonLat
.
value
=
center
// 使用 flyTo 而非 fitBounds,避免 maxBounds 约束干扰
map
.
flyTo
({
center
,
zoom
:
15
,
duration
:
1000
,
})
}
}
else
if
(
res
.
longitude
&&
res
.
latitude
)
{
const
center
:
[
number
,
number
]
=
[
Number
(
res
.
longitude
),
Number
(
res
.
latitude
)]
baseCenterLonLat
.
value
=
center
map
.
flyTo
({
center
,
zoom
:
15
,
duration
:
1000
,
})
}
else
{
console
.
warn
(
'切换基地: 既没有geojson也没有经纬度'
)
}
>>>>>>>
origin
/
dev
})
}
...
...
@@ -183,6 +233,7 @@
},
),
]
<<<<<<<
HEAD
addDefaultGeoJSONSource
(
map
,
`
${
page
.
id
}
-plot`
,
model
.
plots
)
addDefaultSplotLayer
(
map
,
`
${
page
.
id
}
-plot`
,
{
paint
:
{
...
...
@@ -203,6 +254,142 @@
'line-width'
:
2
,
},
})
=======
// 为每个基地创建独立的 source 和 layer
for
(
let
i
=
0
;
i
<
farmbaseRecords
.
length
;
i
++
)
{
const
base
=
farmbaseRecords
[
i
]
let
geojson
=
base
.
geojson
// geojson 可能是字符串,需要解析
if
(
typeof
geojson
===
'string'
)
{
try
{
geojson
=
JSON
.
parse
(
geojson
)
}
catch
(
e
)
{
console
.
error
(
`解析基地
${
base
.
baseName
}
geojson 失败:`
,
e
)
continue
}
}
if
(
!
geojson
)
continue
// 为当前基地分配颜色
const
color
=
colorPool
[
i
%
colorPool
.
length
]
const
sourceId
=
`
${
page
.
id
}
-base-plot-
${
base
.
id
}
`
const
fillLayerId
=
`
${
sourceId
}
-fill`
const
lineLayerId
=
`
${
sourceId
}
-line`
const
labelLayerId
=
`
${
sourceId
}
-label`
// 处理 geojson,注入基地名称属性
const
features
=
geojson
.
features
?
geojson
.
features
:
[
geojson
]
for
(
const
feature
of
features
)
{
feature
.
properties
=
feature
.
properties
||
{}
feature
.
properties
.
baseName
=
base
.
baseName
}
// 添加 source
addDefaultGeoJSONSource
(
map
,
sourceId
,
features
)
// 添加填充层(直接创建,不使用 addDefaultSplotLayer)
map
.
addLayer
({
type
:
'fill'
,
id
:
fillLayerId
,
source
:
sourceId
,
paint
:
{
'fill-color'
:
color
.
fill
,
'fill-opacity'
:
0.5
,
'fill-outline-color'
:
color
.
line
,
},
})
// 添加边框层
map
.
addLayer
({
type
:
'line'
,
id
:
lineLayerId
,
source
:
sourceId
,
layout
:
{
'line-join'
:
'round'
,
'line-cap'
:
'round'
,
},
paint
:
{
'line-color'
:
color
.
line
,
'line-width'
:
3
,
},
})
// 添加基地名称文字标签
map
.
addLayer
({
type
:
'symbol'
,
id
:
labelLayerId
,
source
:
sourceId
,
layout
:
{
'text-field'
:
[
'get'
,
'baseName'
],
'text-size'
:
14
,
'text-anchor'
:
'center'
,
'text-offset'
:
[
0
,
0
],
'text-allow-overlap'
:
true
,
},
paint
:
{
'text-color'
:
'#ffffff'
,
'text-halo-color'
:
color
.
line
,
'text-halo-width'
:
2
,
},
})
}
// 将地图视角飞到第一个基地的上方
if
(
farmbaseRecords
.
length
>
0
)
{
const
firstBase
=
farmbaseRecords
[
0
]
model
.
farmbaseInfo
=
firstBase
let
firstGeojson
=
firstBase
.
geojson
if
(
typeof
firstGeojson
===
'string'
)
{
try
{
firstGeojson
=
JSON
.
parse
(
firstGeojson
)
}
catch
(
e
)
{
firstGeojson
=
null
}
}
if
(
firstGeojson
)
{
const
coords
=
firstGeojson
.
features
?.[
0
]?.
geometry
?.
coordinates
?.[
0
]
if
(
coords
&&
coords
.
length
>
0
)
{
let
minLon
=
Infinity
,
minLat
=
Infinity
,
maxLon
=
-
Infinity
,
maxLat
=
-
Infinity
for
(
const
c
of
coords
)
{
if
(
c
[
0
]
<
minLon
)
minLon
=
c
[
0
]
if
(
c
[
0
]
>
maxLon
)
maxLon
=
c
[
0
]
if
(
c
[
1
]
<
minLat
)
minLat
=
c
[
1
]
if
(
c
[
1
]
>
maxLat
)
maxLat
=
c
[
1
]
}
const
center
:
[
number
,
number
]
=
[(
minLon
+
maxLon
)
/
2
,
(
minLat
+
maxLat
)
/
2
]
baseCenterLonLat
.
value
=
center
map
.
flyTo
({
center
,
zoom
:
15
,
duration
:
0
,
})
}
}
else
if
(
firstBase
.
longitude
&&
firstBase
.
latitude
)
{
const
center
:
[
number
,
number
]
=
[
Number
(
firstBase
.
longitude
),
Number
(
firstBase
.
latitude
)]
baseCenterLonLat
.
value
=
center
map
.
flyTo
({
center
,
zoom
:
15
,
duration
:
0
,
})
}
}
getDeviceTypeCount
()
// 如果农场没有 geojson 但有坐标,则使用坐标定位
if
(
!
farmGeojson
&&
item
.
longitude
&&
item
.
latitude
)
{
model
.
lonlat
=
`
${
item
.
longitude
}
,
${
item
.
latitude
}
`
}
else
if
(
!
farmGeojson
&&
!
item
.
longitude
)
{
model
.
lonlat
=
'111.024108, 29.554847'
Message
.
toast
(
'未设置农场坐标位置,已使用模拟位置数据'
)
}
// 渲染设备数据
>>>>>>>
origin
/
dev
// 渲染设备数据
model
.
devices
=
[
...
...
@@ -309,33 +496,33 @@ map.on('load','custom-image',()=>{
{
name
:
'降雨'
,
icon
:
'/static/images/codefun/rain.png'
,
type
:
'
button'
,
type
:
'
toggle'
as
const
,
handle
:
()
=>
{
Message
.
toast
(
'暂无降雨数据
'
)
onWeatherClick
(
'rain
'
)
},
},
{
name
:
'温度'
,
icon
:
'/static/images/codefun/temp.png'
,
type
:
'
button'
,
type
:
'
toggle'
as
const
,
handle
:
()
=>
{
Message
.
toast
(
'暂无温度数据
'
)
onWeatherClick
(
'temp
'
)
},
},
{
name
:
'强对流'
,
icon
:
'/static/images/codefun/severe.png'
,
type
:
'
button'
,
type
:
'
toggle'
as
const
,
handle
:
()
=>
{
Message
.
toast
(
'暂无强对流数据
'
)
onWeatherClick
(
'severe
'
)
},
},
{
name
:
'大风'
,
icon
:
'/static/images/codefun/wind.png'
,
type
:
'
button'
,
type
:
'
toggle'
as
const
,
handle
:
()
=>
{
Message
.
toast
(
'暂无大风数据
'
)
onWeatherClick
(
'wind
'
)
},
},
{
...
...
@@ -377,6 +564,14 @@ map.on('load','custom-image',()=>{
},
})
}
// 天气按钮点击处理
function
onWeatherClick
(
type
:
'rain'
|
'temp'
|
'wind'
|
'severe'
)
{
if
(
model
.
activeWeatherType
===
type
)
{
model
.
activeWeatherType
=
''
}
else
{
model
.
activeWeatherType
=
type
}
}
// 保留原有的导航栏按钮点击回调(如果有需要的话)
onNavigationBarButtonTap
((
e
)
=>
{
console
.
log
(
e
)
...
...
@@ -591,6 +786,13 @@ map.on('load','custom-image',()=>{
@
submit-success=
"getDeviceTypeCount"
@
close=
"showDialog = false"
/>
<WeatherChartPopup
:visible=
"model.activeWeatherType !== ''"
:type=
"model.activeWeatherType"
:lon=
"baseCenterLonLat?.[0] ?? ''"
:lat=
"baseCenterLonLat?.[1] ?? ''"
@
close=
"model.activeWeatherType = ''"
/>
</view>
</
template
>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论