智慧种植页面首页 田间监测页面

This commit is contained in:
姚俊旭 2025-05-19 13:38:34 +08:00
parent 07c91bd943
commit edbbf59a3b
33 changed files with 20009 additions and 27 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -6,23 +6,36 @@
* @LastEditTime: 2024-01-27 16:07:37
-->
<template>
<template v-if="!item.hidden">
<template v-if="!item.alwaysShow && hasOneShowingChild(item.children, item)">
<layout-link v-if="onlyOneChild.meta" :to="onlyOneChild.path">
<el-menu-item :index="onlyOneChild.path">
<layout-icon :size="20" :icon="onlyOneChild?.meta?.icon" />
<template #title>{{ onlyOneChild.meta && onlyOneChild.meta?.title }}</template>
</el-menu-item>
</layout-link>
</template>
<el-sub-menu v-else :index="item.path" teleported>
<template v-if="item.children">
<!-- <template v-if="!item.alwaysShow && hasOneShowingChild(item.children, item)">-->
<!-- <layout-link :to="onlyOneChild.path">-->
<!-- <el-menu-item :index="onlyOneChild.path">-->
<!-- <template #title>{{ onlyOneChild.meta && onlyOneChild.meta?.title }}</template>-->
<!-- </el-menu-item>-->
<!-- </layout-link>-->
<!-- </template>-->
<el-sub-menu :index="item.path" teleported>
<template #title>
<layout-icon :size="20" :icon="item?.meta?.icon" />
<span>{{ item.meta && item.meta?.title }}</span>
<!-- 左侧图标 -->
<img :src="getAssetsFile('images/smartFarm/' + item.icon)?.href ?? ''" alt="" />
<span>{{ item.title }}</span>
<!-- <layout-icon :size="20" :icon="item?.meta?.icon" />-->
<!-- <span>{{ item.meta && item.meta?.title }}</span>-->
</template>
<!-- 右侧图标替换默认箭头没生效 -->
<template #expand-icon>
<img alt="" :src="getAssetsFile('images/smartFarm/closing.png')" class="isOpen" />
</template>
<sub-item v-for="child in item.children" :key="child.path" :item="child" />
</el-sub-menu>
</template>
<template v-else>
<el-menu-item-group :title="item.title">
<el-menu-item v-for="child in item.children" :key="child.path" :index="child.path">
<span style="color: black !important">{{ child.title }}</span>
</el-menu-item>
</el-menu-item-group>
</template>
</template>
<script setup name="sub-item">
@ -30,6 +43,7 @@ import { ref } from 'vue';
// import { isExternal } from '@/utils/validate.js';
import LayoutLink from './Link';
import LayoutIcon from './Icon';
import { getAssetsFile } from '@/utils/index.js';
// import path from 'path-browserify';
defineProps({
@ -79,3 +93,35 @@ const hasOneShowingChild = (children = [], parent) => {
// return path.resolve(props.basePath, routePath);
// };
</script>
<style scoped>
/* 强制覆盖默认样式 */
.isOpen {
width: 16px !important; /* 设置图片宽度 */
height: 16px !important; /* 设置图片高度 */
margin-left: auto !important; /* 让图标靠右 */
display: block !important; /* 避免被隐藏 */
transition: transform 0.3s; /* 添加旋转动画 */
}
/* 修复父元素可能存在的样式冲突 */
.el-sub-menu__title {
overflow: visible !important; /* 防止图片被裁剪 */
}
.item-children {
text-align: center;
font-size: 16px;
margin-top: 8px;
transition: transform 0.3s ease;
.dot {
display: inline-block;
height: 4px;
margin-right: 15px;
width: 4px;
border-radius: 90px;
background-color: black;
vertical-align: middle;
}
}
</style>

View File

@ -0,0 +1,72 @@
<template>
<div ref="chartRef" style="width: 600px; height: 150px"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
onMounted(() => {
const myChart = echarts.init(chartRef.value);
const option = {
tooltip: {
trigger: 'item',
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
axisLine: {
show: false, // X线
},
axisTick: {
show: false, // X
},
axisLabel: {
color: '#666', //
},
},
yAxis: {
type: 'value',
show: false, // Y
},
series: [
{
data: [120, 200, 150, 80, 70, 110], //
type: 'bar',
barWidth: '20%',
itemStyle: {
color: '#4CAF50', // 绿
borderRadius: [6, 6, 6, 6], // ()
},
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)', //
borderRadius: [6, 6, 6, 6], //
},
label: {
show: false,
position: 'top',
formatter: '', //
},
},
],
grid: {
left: '-40px',
right: '3%',
bottom: '3%',
top: '15%',
containLabel: true,
},
};
myChart.setOption(option);
//
window.addEventListener('resize', function () {
myChart.resize();
});
});
</script>

View File

@ -1,10 +1,10 @@
<template>
<div class="ecommerce-common-warp">
<div class="ecommerce-common-content">
<div class="smartFarm-common-warp">
<div class="smartFarm-common-content">
<div class="left-menu">
<slot v-if="$slots.left" name="left"></slot>
<template v-else>
<leftMenu :current-name="currentName"></leftMenu>
<left-menu :menus="menus"></left-menu>
</template>
</div>
<div class="common-content">
@ -21,13 +21,48 @@ import leftMenu from './leftMenu.vue';
const props = defineProps({
currentName: { type: String, default: 'agricultural' },
});
const menus = reactive([
{
name: 'supplier',
title: '农业环境监测',
icon: 'menu1.png',
path: '/sub-operation-service/smartFarm/main',
isOpen: true,
children: [
{
name: 'supplier',
title: '田间监测',
path: '/sub-operation-service/smartFarm/fieldInspection',
},
{
name: 'supplier',
title: '水质监测',
path: '/sub-operation-service/ecommerce-supplier',
},
{
name: 'supplier',
title: '病虫害监测',
path: '/sub-operation-service/ecommerce-supplier',
},
],
},
{
name: 'purchaser',
title: '生产管理控制',
icon: 'menu3.png',
path: '/sub-operation-service/ecommerce-purchaser',
isOpen: false,
children: [],
},
]);
</script>
<style lang="scss" scoped>
.ecommerce-common-warp {
.smartFarm-common-warp {
width: 100%;
height: calc(100vh - 230px);
text-align: center;
.ecommerce-common-content {
.smartFarm-common-content {
width: $width-main;
margin: auto;
height: 100%;

View File

@ -0,0 +1,85 @@
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const props = defineProps({
devices: {
type: Array,
required: true,
default: () => [],
validator: (items) => {
return items.every((item) => {
return (
typeof item === 'object' &&
item !== null &&
typeof item.id === 'number' &&
typeof item.name === 'string' &&
typeof item.detail === 'string' &&
typeof item.icon === 'string' &&
(!item.status || typeof item.status === 'number')
);
});
},
},
title: {
type: String,
required: true,
default: () => '',
validator: (items) => {
return items;
},
},
});
</script>
<template>
<div>
<el-card style="border-radius: 16px">
<div style="font-size: 16px; font-weight: bold; text-align: left; color: #000">{{ title }}</div>
<div style="display: flex; justify-content: flex-start; flex-wrap: wrap">
<div v-for="(item, index) in devices" :key="index" class="device">
<div v-if="item.status == 0" class="status" style="background-color: #25bf82">正常</div>
<div v-else-if="item.status == -1" class="status" style="background-color: #fe4066">异常</div>
<div style="display: flex; flex-direction: column; justify-content: space-between; height: 100%">
<img v-if="item.icon === 'camera'" :src="getAssetsFile('images/smartFarm/监控.png')" alt="" />
<img v-else-if="item.icon === 'sensor'" :src="getAssetsFile('images/smartFarm/传感器.png')" alt="" />
<div style="text-align: left; font-weight: bold; font-size: 18px">{{ item.name }}</div>
</div>
</div>
</div>
</el-card>
</div>
</template>
<style scoped lang="scss">
.device {
height: 100px;
width: 18%;
background-color: #f5f5f5;
margin: 20px 1%;
border-radius: 16px;
position: relative;
cursor: pointer;
padding: 10px 20px;
img {
height: 35px;
width: 35px;
}
}
.status {
border-radius: 0 16px 0 16px;
height: 30px;
width: 30px;
position: absolute;
right: 0;
top: 0;
display: flex;
justify-content: center;
align-items: center;
color: #ffffff;
font-size: 10px;
}
</style>

View File

@ -0,0 +1,183 @@
<template>
<div ref="mapContainer" style="width: 100%; height: 300px; background: #ffffff; border: 0"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
import json from '@/assets/530926geo.json';
import { getAssetsFile } from '@/utils';
// GeoJSON
const gengmaGeoJSON = json;
//
const towns = ref([
{ name: '孟定镇', coord: [99.01, 23.64], weather: '晴', temp: '28℃', icon: 'sunny' },
{ name: '勐简乡', coord: [99.24, 23.79], weather: '多云', temp: '26℃', icon: 'cloudy' },
{ name: '四排山乡', coord: [99.5, 23.38], weather: '小雨', temp: '24℃', icon: 'rainy' },
{ name: '大兴乡', coord: [99.8, 23.76], weather: '多云', temp: '25℃', icon: 'cloudy' },
{ name: '耿马镇', coord: [99.42, 23.66], weather: '多云', temp: '26℃', icon: 'cloudy' },
{ name: '贺派乡', coord: [99.21, 23.4], weather: '晴', temp: '27℃', icon: 'sunny' },
{ name: '芒洪乡', coord: [99.73, 23.59], weather: '阴', temp: '23℃', icon: 'overcast' },
{ name: '勐永镇', coord: [99.53, 23.99], weather: '小雨', temp: '22℃', icon: 'rainy' },
{ name: '勐撒镇', coord: [99.47, 23.85], weather: '晴', temp: '28℃', icon: 'sunny' },
]);
const mapContainer = ref(null);
//
// function getWeatherIconPath(iconType) {
// return `/images/${iconType}.png`; // 使
// }
function getWeatherIconPath(iconType) {
const iconMap = {
sunny: 'images/smartFarm/sunny.png',
cloudy: 'images/smartFarm/sunnyToCloudy.png',
rainy: 'images/smartFarm/rainy.png',
overcast: 'images/smartFarm/windy.png',
};
let path = iconMap[iconType] ? iconMap[iconType] : 'images/smartFarm/sunny.png';
return getAssetsFile(path);
}
onMounted(() => {
//
if (!towns.value || towns.value.some((t) => !t.weather)) {
console.error('天气数据不完整');
return;
}
const chart = echarts.init(mapContainer.value, 'dark');
//
chart.on('click', (params) => {
if (params.componentType === 'series') {
//
console.log('点击乡镇:', params.name);
showWeatherDetail(params.data);
} else if (params.componentType === 'geo') {
//
console.log('点击地图区域:', params.name);
}
});
//
const showWeatherDetail = (data) => {
console.log(data);
};
//
echarts.registerMap('耿马县', gengmaGeoJSON);
//
const option = {
backgroundColor: '#fff', //
title: {
text: '气象数据',
left: 20,
top: 10,
textStyle: {
color: '#000', //
fontSize: 18,
fontWeight: 'bold',
},
},
geo: {
map: '耿马县',
roam: false,
zoom: 1.1,
itemStyle: {
areaColor: '#a8d8ea', //
borderColor: '#4682B4', //
borderWidth: 1.2,
shadowColor: 'rgba(0, 100, 150, 0.3)',
shadowBlur: 8,
},
emphasis: {
itemStyle: {
areaColor: '#7ac5e0', // 绿
borderWidth: 1.5,
},
},
},
series: [
{
type: 'scatter',
coordinateSystem: 'geo',
symbolSize: 30,
data: towns.value.map((town) => ({
name: town.name,
value: [...town.coord, town.temp],
weather: town.weather,
symbol: `image://${getWeatherIconPath(town.icon)}`,
symbolOffset: [10, -10], //
// 10pxsymbol
label: { offset: [10, 20] },
})),
label: {
show: true,
formatter: (params) => {
// 使rich
return `{name|${params.name}}`;
},
rich: {
name: {
color: '#333',
fontSize: 12,
align: 'center',
lineHeight: 20,
padding: [2, 0],
},
},
position: function (point) {
//
return point[1] > 23.6 ? 'top' : 'bottom';
},
distance: 8,
offset: [0, 0],
padding: [2, 5],
backgroundColor: 'rgba(255,255,255,0.7)',
borderColor: '#4682B4',
borderWidth: 0.5,
borderRadius: 3,
},
tooltip: {
formatter: (params) => `
<div style="font-size:14px;color:#333;font-weight:bold;margin-bottom:5px;">
${params.name}
</div>
<div style="margin:5px 0;">
<span style="display:inline-block;width:70px;">天气</span>
<span style="color:#1E90FF;">${params.data.weather}</span>
</div>
<div>
<span style="display:inline-block;width:70px;">温度</span>
<span style="color:#1E90FF;">${params.value[2]}</span>
</div>
`,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#4682B4',
borderWidth: 1,
padding: 10,
},
},
],
graphic: {
type: 'text',
left: 20,
bottom: 5,
style: {
text: '数据更新于: 2025.01.01 08:00:00',
fill: '#666',
fontSize: 12,
},
},
};
chart.setOption(option);
window.addEventListener('resize', function () {
chart.resize();
});
});
</script>

View File

@ -0,0 +1,130 @@
<template>
<div ref="mapContainer" style="width: 100%; height: 300px; background: #ffffff"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
import json from '@/assets/530926geo.json';
import { getAssetsFile } from '@/utils';
// GeoJSON
const gengmaGeoJSON = json;
//
const towns = ref([
{ name: '孟定镇', coord: [99.01, 23.64] },
{ name: '勐简乡', coord: [99.24, 23.79] },
{ name: '四排山乡', coord: [99.5, 23.38] },
{ name: '大兴乡', coord: [99.8, 23.76] },
{ name: '耿马镇', coord: [99.42, 23.66] },
{ name: '贺派乡', coord: [99.21, 23.4] },
{ name: '芒洪乡', coord: [99.73, 23.59] },
{ name: '勐永镇', coord: [99.53, 23.99] },
{ name: '勐撒镇', coord: [99.47, 23.85] },
]);
const mapContainer = ref(null);
//
// function getWeatherIconPath(iconType) {
// return `/images/${iconType}.png`; // 使
// }
function getWeatherIconPath(iconType) {
const iconMap = {
sunny: 'images/smartFarm/sunny.png',
cloudy: 'images/smartFarm/sunnyToCloudy.png',
rainy: 'images/smartFarm/rainy.png',
overcast: 'images/smartFarm/windy.png',
};
let path = iconMap[iconType] ? iconMap[iconType] : 'images/smartFarm/sunny.png';
return getAssetsFile(path);
}
onMounted(() => {
const chart = echarts.init(mapContainer.value, 'dark');
//
chart.on('click', (params) => {
if (params.componentType === 'series') {
//
console.log('点击乡镇:', params.name);
showWeatherDetail(params.data);
} else if (params.componentType === 'geo') {
//
console.log('点击地图区域:', params.name);
}
});
//
const showWeatherDetail = (data) => {
console.log(data);
};
//
echarts.registerMap('耿马县', gengmaGeoJSON);
//
const option = {
backgroundColor: '#fff', //
title: {
text: '',
left: 20,
top: 10,
textStyle: {
color: '#000', //
fontSize: 18,
fontWeight: 'bold',
},
},
geo: {
map: '耿马县',
roam: false,
zoom: 1.1,
label: {
show: true, //
color: '#333',
fontSize: 12,
formatter: (params) => params.name, //
},
itemStyle: {
areaColor: '#a8d8ea', //
borderColor: '#4682B4', //
borderWidth: 1.2,
shadowColor: 'rgba(0, 100, 150, 0.3)',
shadowBlur: 8,
},
emphasis: {
label: {
show: true,
color: '#1890FF',
},
itemStyle: {
areaColor: '#BAE7FF',
},
},
},
series: [
{
type: 'scatter',
coordinateSystem: 'geo',
symbolSize: 0,
data: towns.value,
label: {
show: true,
position: 'inside', //
formatter: '{b}',
color: '#000',
fontSize: 10,
},
},
],
};
chart.setOption(option);
window.addEventListener('resize', function () {
chart.resize();
});
});
</script>

View File

@ -0,0 +1,115 @@
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
import { useRoute, useRouter } from 'vue-router';
import Hls from 'hls.js';
const route = useRoute();
const router = useRouter();
const currentDevice = ref(0);
const currentPicture = ref(0);
const videoPlayer = ref(null);
const hls = ref(null);
const loading = ref(false);
const error = ref(false);
const props = defineProps({
title: {
type: String,
required: true,
default: () => '',
validator: (items) => {
return items;
},
},
devices: {
type: Array,
required: true,
default: () => [],
validator: (items) => {
return items.every((item) => {
return (
typeof item === 'object' &&
item !== null &&
typeof item.id === 'number' &&
typeof item.name === 'string' &&
typeof item.icon === 'string' &&
typeof item.detail === 'string' &&
(!item.status || typeof item.status === 'number')
);
});
},
},
});
onUnmounted(() => {
if (hls.value) {
hls.value.destroy();
}
});
onMounted(async () => {
if (Hls.isSupported()) {
hls.value = new Hls();
hls.value.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8');
hls.value.attachMedia(videoPlayer.value);
hls.value.on(Hls.Events.MANIFEST_PARSED, () => {
videoPlayer.value.play();
});
} else if (videoPlayer.value.canPlayType('application/vnd.apple.mpegurl')) {
// SafariHLS
videoPlayer.value.src = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';
}
});
</script>
<template>
<el-card style="border-radius: 16px">
<div style="display: flex; justify-content: space-between">
<div style="font-size: 16px; font-weight: bold; text-align: left">{{ title }}</div>
<div style="color: #999999; line-height: 25px">
当前设备
<el-select v-model="currentDevice" placeholder="Select" size="small" style="width: 160px; margin-left: 10px">
<el-option v-for="item in devices" :key="item.value" :label="item.detail" :value="item.id" />
</el-select>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center">
<div class="video-wrapper">
<video ref="videoPlayer" controls autoplay muted></video>
<div v-if="loading" class="status-message">正在加载直播流...</div>
<div v-if="error" class="status-message error">{{ error }}</div>
</div>
<div class="pictures-wrapper">
<img :src="getAssetsFile('images/smartFarm/goUp.png')" alt="" style="width: 100px" />
<img :src="getAssetsFile('images/smartFarm/testPic1.png')" style="width: 80%; margin: 5px 0" :alt="devices[currentDevice].detail" />
<img :src="getAssetsFile('images/smartFarm/testPic1.png')" style="width: 80%; margin: 5px 0" :alt="devices[currentDevice].detail" />
<img :src="getAssetsFile('images/smartFarm/goDown.png')" alt="" style="width: 100px" />
</div>
</div>
</el-card>
</template>
<style scoped lang="scss">
.video-wrapper {
height: 100%;
margin-top: 30px;
width: 58%;
video {
width: 100%;
border-radius: 16px;
}
}
.pictures-wrapper {
width: 38%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
margin-top: 20px;
img {
cursor: pointer;
}
}
</style>

View File

@ -2,25 +2,359 @@
<section>
<common>
<template #main>
<div>农田监测</div>
<div>
<devices :title="'田间监测设备'" :devices="devices"></devices>
</div>
<div style="display: flex; justify-content: space-between; margin-top: 10px">
<stream :title="'田间监测实时监控'" :devices="devices" style="width: 60%"></stream>
<el-card style="width: calc(40% - 20px); border-radius: 16px; padding: 10px">
<div style="font-size: 16px; font-weight: bold; text-align: left">作物生长状态</div>
<div class="plantStatus">
<div class="leftKey">作物名称:</div>
<div class="rightValue">橙子</div>
</div>
<div class="plantStatus">
<div class="leftKey">生长状态:</div>
<div class="rightValue">成熟期</div>
</div>
<div class="plantStatus">
<div class="leftKey">植株形态:</div>
<div class="rightValue">果木型</div>
</div>
<div class="plantStatus">
<div class="leftKey">叶片形态:</div>
<div class="rightValue">阔叶型</div>
</div>
<div class="plantStatus">
<div class="leftKey">生长态势:</div>
<div class="rightValue">良好</div>
</div>
<div class="plantStatus">
<div class="leftKey">田间有机质含量:</div>
<div class="rightValue">橙子</div>
</div>
<div class="plantStatus">
<div class="leftKey">田间有机质含量:</div>
<div class="rightValue">16%</div>
</div>
<div class="plantStatus">
<div class="leftKey">生长趋势图</div>
<div class="rightValue">&nbsp;</div>
</div>
<div ref="chartRef1" style="width: 100%; height: 150px"></div>
</el-card>
</div>
</template>
</common>
</section>
</template>
<script setup>
import { ref } from 'vue';
import { ref, onMounted, onBeforeUnmount } from 'vue';
import Common from '../components/common.vue';
import Devices from '@/views/smartFarm/components/devices.vue';
import Stream from '@/views/smartFarm/components/stream.vue';
import * as echarts from 'echarts';
/* --------------- data --------------- */
// #region
// DOM
const chartRef = ref(null);
// ECharts
let chartInstance = null;
//
const colorList = ['#9E87FF', '#73DDFF'];
// x
const xData = ['1月', '2月', '3月', '4月', '5月', '6月'];
const devices = ref([
{
name: 'A-001',
icon: 'camera',
detail: 'A区-监控设备9',
status: '0',
id: 0,
},
{
name: 'A-002',
icon: 'camera',
detail: 'A区-监控设备66',
status: '0',
id: 1,
},
{
name: 'A-003',
icon: 'sensor',
detail: 'A区-监控设备7',
status: '0',
id: 2,
},
{
name: 'A-004',
icon: 'sensor',
detail: 'A区-监控设备1',
status: '-1',
id: 3,
},
{
name: 'A-005',
icon: 'sensor',
detail: 'A区-监控设备5',
status: '-1',
id: 4,
},
{
name: 'A-006',
icon: 'camera',
status: '0',
detail: 'A区-监控设备21',
id: 5,
},
{
name: 'A-007',
icon: 'camera',
status: '0',
detail: 'A区-监控设备4',
id: 6,
},
{
name: 'A-008',
detail: 'A区-监控设备3',
icon: 'camera',
status: '0',
id: 7,
},
]);
// #endregion
/* --------------- methods --------------- */
// #region
//
const initChart = () => {
//
const option = {
backgroundColor: '#fff',
title: {
text: '简单折线图',
textStyle: {
fontSize: 12,
fontWeight: 400,
},
left: 'center',
top: '5%',
},
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 20,
textStyle: {
color: '#556677',
},
},
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
show: true,
backgroundColor: '#fff',
color: '#556677',
borderColor: 'rgba(0,0,0,0)',
shadowColor: 'rgba(0,0,0,0)',
shadowOffsetY: 0,
},
lineStyle: {
width: 0,
},
},
backgroundColor: '#fff',
textStyle: {
color: '#5c6c7c',
},
padding: [10, 10],
extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)',
},
grid: {
top: '15%',
},
xAxis: [
{
type: 'category',
data: xData,
axisLine: {
lineStyle: {
color: 'rgba(107,107,107,0.37)',
},
},
axisTick: {
show: false,
},
axisLabel: {
interval: 0,
textStyle: {
color: '#999',
},
margin: 15,
},
axisPointer: {
label: {
padding: [11, 5, 7],
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#fff',
},
{
offset: 0.9,
color: '#fff',
},
{
offset: 0.9,
color: '#33c0cd',
},
{
offset: 1,
color: '#33c0cd',
},
],
global: false,
},
},
},
boundaryGap: false,
},
],
yAxis: [
{
type: 'value',
axisTick: {
show: false,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(107,107,107,0.37)',
},
},
axisLabel: {
textStyle: {
color: '#999',
},
},
splitLine: {
show: false,
},
},
],
series: [
{
name: 'Adidas',
type: 'line',
data: [10, 10, 30, 12, 15, 3, 7],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: '#9effff',
},
{
offset: 1,
color: '#9E87FF',
},
]),
shadowColor: 'rgba(158,135,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[0],
borderColor: colorList[0],
},
},
{
name: 'Nike',
type: 'line',
data: [5, 12, 11, 14, 25, 16, 10],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#73DD39',
},
{
offset: 1,
color: '#73DDFF',
},
]),
shadowColor: 'rgba(115,221,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[1],
borderColor: colorList[1],
},
},
],
};
if (chartRef.value) {
// domecharts
chartInstance = echarts.init(chartRef.value);
//
chartInstance.setOption(option);
//
window.addEventListener('resize', resizeChart);
}
};
//
onMounted(() => {
initChart();
});
//
onBeforeUnmount(() => {
if (chartInstance) {
window.removeEventListener('resize', resizeChart);
chartInstance.dispose();
chartInstance = null;
}
});
//
const resizeChart = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// #endregion
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.plantStatus {
display: flex;
justify-content: space-between;
font-size: 14px;
margin: 7px 0;
.leftKey {
color: #000000;
}
.rightValue {
color: #25bf82;
}
}
</style>

View File

@ -3,7 +3,289 @@
<common>
<template #main>
<div>
<el-card shadow="hover"> </el-card>
<el-card shadow="hover">
<el-row>
<el-col :span="10">
<map-comp style="height: 300px; width: 100%; border: 0"></map-comp>
</el-col>
<el-col :span="1">&nbsp;</el-col>
<el-col :span="13">
<div class="location">
耿马县·孟定镇<img :src="getAssetsFile('images/smartFarm/location.png')" height="20" style="margin-left: 8px" alt="" />
</div>
<div style="display: flex; justify-content: space-around">
<el-card v-for="(item, index) in weatherData" :key="index" :body-style="{ padding: 0 }" shadow="always" class="weatherCards">
<div>{{ item.time }}</div>
<div><img :src="getAssetsFile('images/smartFarm/' + item.weather + '.png')" alt="" height="50" /></div>
<div>{{ item.temp }}</div>
</el-card>
</div>
<div class="details">
<div class="details-block">
<div class="detail">
<div class="leftTitle">温度</div>
<div class="rightValue">
{{ currentData.temp }}
</div>
</div>
<div class="detail">
<div class="leftTitle">PM2.5</div>
<div class="rightValue">{{ currentData.PM2 }}μg/</div>
</div>
<div class="detail">
<div class="leftTitle">作物虫害</div>
<div class="rightValue">
{{ currentData.bugs === 0 ? '无' : '有' }}
</div>
</div>
<div class="detail">
<div class="leftTitle">土壤温度</div>
<div class="rightValue">{{ currentData.dustTemp }}</div>
</div>
</div>
<div class="details-block">
<div class="detail">
<div class="leftTitle">湿度</div>
<div class="rightValue">{{ currentData.wet }}%</div>
</div>
<div class="detail">
<div class="leftTitle">PM10</div>
<div class="rightValue">{{ currentData.temp }}μg/</div>
</div>
<div class="detail">
<div class="leftTitle">作物病害</div>
<div class="rightValue">
{{ currentData.sick === 0 ? '无' : '有' }}
</div>
</div>
<div class="detail">
<div class="leftTitle">土壤湿度</div>
<div class="rightValue">{{ currentData.dustWet }}%</div>
</div>
</div>
<div class="details-block">
<div class="detail">
<div class="leftTitle">风向</div>
<div class="rightValue">
{{ currentData.wind }}
</div>
</div>
<div class="detail">
<div class="leftTitle">光照度</div>
<div class="rightValue">{{ currentData.light }}Lux</div>
</div>
<div class="detail">
<div class="leftTitle">风速</div>
<div class="rightValue">{{ currentData.temp }}m/s</div>
</div>
<div class="detail">
<div class="leftTitle">土壤酸碱度</div>
<div class="rightValue">
{{ currentData.PH }}
</div>
</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
<el-card shadow="hover" style="margin-top: 10px">
<div style="display: flex; padding: 20px">
<div style="width: 30%">
<div style="font-size: 18px; font-weight: bold; text-align: left">土壤数据</div>
<div style="display: flex">
<div class="dustData">
<div>
<img :src="getAssetsFile('images/smartFarm/光照传感器.png')" alt="" />
光照
</div>
<div class="values">2000Lux</div>
</div>
<div class="dustData">
<div>
<img :src="getAssetsFile('images/smartFarm/排风.png')" alt="" />
排风
</div>
<div class="values">15/h</div>
</div>
<div class="dustData">
<div>
<img :src="getAssetsFile('images/smartFarm/蒸腾.png')" alt="" />
蒸腾
</div>
<div class="values">2000Lux</div>
</div>
</div>
<div style="display: flex">
<div class="dustData">
<div>
<img :src="getAssetsFile('images/smartFarm/土壤湿度.png')" alt="" />
湿度
</div>
<div class="values">26%</div>
<div class="values">34%</div>
</div>
<div class="dustData">
<div>
<img :src="getAssetsFile('images/smartFarm/土壤温度.png')" alt="" />
温度
</div>
<div class="values">32</div>
<div class="values">28</div>
</div>
<div class="dustData">
<div>
<img :src="getAssetsFile('images/smartFarm/空气.png')" alt="" />
空气
</div>
<div class="values">300ppm</div>
<div class="values">34%</div>
</div>
</div>
<div class="report">
<div><img :src="getAssetsFile('images/smartFarm/bell.png')" alt="" /></div>
<div class="warning">
<div>温度</div>
<div>36</div>
</div>
<div class="warning">
<div>超高</div>
<div>4</div>
</div>
</div>
</div>
<div style="width: 35%">
<map-simple style="height: 320px; width: 100%"></map-simple>
</div>
<div style="width: 35%">
<div style="margin-top: 70px; display: flex; text-align: left">
<el-col :span="12">
<el-row class="dataTitle">泵压管控mpa</el-row>
<el-row>
<el-col :span="12">
<span style="font-size: 15px">输入</span>
<span class="values">11</span>
</el-col>
<el-col :span="12">
<span style="font-size: 15px">末端</span>
<span class="values">2</span>
</el-col>
</el-row>
</el-col>
<el-col :span="12">
<el-row class="dataTitle">灌溉流量m²/h</el-row>
<el-row>
<el-col :span="12">
<span style="font-size: 15px">灌溉</span>
<span class="values">18</span>
</el-col>
<el-col :span="12">
<span style="font-size: 15px">回液</span>
<span class="values">18</span>
</el-col>
</el-row>
</el-col>
</div>
<div style="display: flex; text-align: left">
<el-col :span="12">
<el-row class="dataTitle">水肥监测</el-row>
<el-row>
<el-col :span="12">
<span style="font-size: 15px">PH</span>
<span class="values">8</span>
</el-col>
<el-col :span="12">
<span style="font-size: 15px">输入</span>
<span class="values">18</span>
</el-col>
</el-row>
</el-col>
<el-col :span="12">&nbsp;</el-col>
</div>
<el-row class="dataTitle">灌溉流量m²/h</el-row>
<charts-flow style="height: 150px; width: 100%"></charts-flow>
</div>
</div>
</el-card>
<el-card shadow="hover" style="margin-top: 10px">
<div style="display: flex; padding: 20px">
<div style="width: 50%">
<div style="font-size: 18px; font-weight: bold; text-align: left">产能预测</div>
<div style="display: flex; justify-content: flex-start; margin: 20px 0">
<div class="plansBlock" style="background-color: #25bf82">
<div style="">预计生产</div>
<div style="font-weight: 900">300</div>
</div>
<div class="plansBlock" style="background-color: #ffbe4d; margin-left: 5%">
<div>预计产值</div>
<div style="font-weight: 900">1500万元</div>
</div>
</div>
<div class="coins">
<div style="width: 28%">
<div style="text-align: left; font-size: 14px; color: #999999">水质综合评分</div>
<div style="display: flex; justify-content: space-between; margin-top: 5px">
<div style="display: flex; align-items: center; font-size: 20px">
<div class="shu" style="background-color: #3685fe"></div>
<div style="margin-left: 5px">87</div>
</div>
<div style="line-height: 30px">
<img :src="getAssetsFile('images/smartFarm/fall.png')" alt="" style="width: 25px; height: 25px" />
</div>
</div>
</div>
<div style="width: 28%">
<div style="text-align: left; font-size: 14px; color: #999999">病虫害管控评分</div>
<div style="display: flex; justify-content: space-between; margin-top: 5px">
<div style="display: flex; align-items: center; font-size: 20px">
<div class="shu" style="background-color: #25bf82"></div>
<div style="margin-left: 5px">87</div>
</div>
<div style="line-height: 30px">
<img :src="getAssetsFile('images/smartFarm/rise.png')" alt="" style="width: 25px; height: 25px" />
</div>
</div>
</div>
<div style="width: 28%">
<div style="text-align: left; font-size: 14px; color: #999999">环境综合评分</div>
<div style="display: flex; justify-content: space-between; margin-top: 5px">
<div style="display: flex; align-items: center; font-size: 20px">
<div class="shu" style="background-color: #ffd500"></div>
<div style="margin-left: 5px">87</div>
</div>
<div style="line-height: 30px">
<img :src="getAssetsFile('images/smartFarm/rise.png')" alt="" style="width: 25px; height: 25px" />
</div>
</div>
</div>
</div>
<div class="vars">
<div style="display: flex; justify-content: space-between; width: 45%">
<div style="color: #999999">种植面积</div>
<div style="color: #25bf82; font-weight: 900">500</div>
</div>
<div style="display: flex; justify-content: space-between; width: 45%">
<div style="color: #999999">水质监测</div>
<div style="color: #25bf82; font-weight: 900">1266</div>
</div>
</div>
<div class="vars">
<div style="display: flex; justify-content: space-between; width: 45%">
<div style="color: #999999">病虫害监测</div>
<div style="color: #25bf82; font-weight: 900">367</div>
</div>
<div style="display: flex; justify-content: space-between; width: 45%">
<div style="color: #999999">环境监测</div>
<div style="color: #25bf82; font-weight: 900">1547</div>
</div>
</div>
</div>
<div style="width: 50%">
<img style="width: 100%" :src="getAssetsFile('images/smartFarm/产能预测.png')" alt="" />
</div>
</div>
</el-card>
</div>
</template>
</common>
@ -11,12 +293,63 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import Common from './components/common.vue';
import * as echarts from 'echarts';
import MapComp from '@/views/smartFarm/components/mapComp.vue';
import { getAssetsFile } from '@/utils/index.js';
import MapSimple from '@/views/smartFarm/components/mapSimple.vue';
import ChartsFlow from '@/views/smartFarm/components/charts-flow.vue';
/* --------------- data --------------- */
// #region
const weatherData = ref([
{
weather: 'sunny',
time: '15:00',
temp: '18',
},
{
weather: 'sunnyToCloudy',
time: '16:00',
temp: '19',
},
{
weather: 'thunderRain',
time: '17:00',
temp: '19',
},
{
weather: 'rainy',
time: '18:00',
temp: '18',
},
{
weather: 'rainy',
time: '19:00',
temp: '15',
},
{
weather: 'windy',
time: '20:00',
temp: '13',
},
]);
const currentData = ref({
temp: 18, //
PM2: 80, // μg/m³
bugs: 0, //
dustTemp: 15, //
wet: 56, // 湿 %
PM10: 120, // μg/m³
sick: 0, //
dustWet: 64, // 湿%
wind: '东南风', //
light: 500, // Lux
windSpeed: 1.5, // m/s
PH: 6.5, //
});
// #endregion
/* --------------- methods --------------- */
@ -25,4 +358,121 @@ import Common from './components/common.vue';
// #endregion
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.vars {
width: 95%;
margin: 15px 0;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.shu {
width: 4px;
height: 30px;
border-radius: 2px;
}
.coins {
display: flex;
width: 95%;
height: 60px;
justify-content: space-between;
}
.plansBlock {
height: 90px;
width: 45%;
border-radius: 16px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
padding: 10px 20px;
color: #ffffff;
font-size: 16px;
}
.values {
color: #25bf82;
font-size: 15px;
margin-left: 5px;
}
.dataTitle {
margin: 4px 0;
color: #999999;
}
.warning {
div {
margin: 4px 0;
}
}
.report {
height: 70px;
width: 100%;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: space-around;
background: linear-gradient(to left, #ffc9d4 0%, #ffffff 100% /* 底部稍深青色 */);
img {
height: 40px;
}
}
.dustData {
width: 33%;
height: 70px;
display: flex;
justify-content: space-between;
flex-direction: column;
font-size: 15px;
margin: 20px 0;
.values {
color: #25bf82;
}
img {
height: 20px;
margin-right: 5px;
}
}
.details {
display: flex;
justify-content: space-around;
.details-block {
width: 29%;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.detail {
display: flex;
justify-content: space-between;
padding: 8px 0;
.leftTitle {
font-size: 15px;
color: #999999;
}
.rightValue {
font-size: 15px;
color: #25bf82;
}
}
}
.location {
padding: 20px 0;
text-align: right;
font-size: 15px;
color: #000000;
}
.weatherCards {
display: flex;
flex-direction: column;
justify-content: space-around;
font-size: 15px;
color: #999999;
text-align: center;
height: 120px;
width: 70px;
background: linear-gradient(to left bottom, #cfffec 0%, /* 顶部浅青色 */ #fbfefd 70%, /* 中间渐变色 */ #ffffff 100% /* 底部稍深青色 */);
border-radius: 12px;
border: none;
box-shadow: 0 4px 12px rgba(0, 150, 136, 0.15);
}
</style>