This commit is contained in:
沈鸿 2025-05-22 10:39:13 +08:00
commit 1d983eba78
19 changed files with 1436 additions and 469 deletions

View File

@ -0,0 +1,60 @@
<template>
<div style="width: 100%; height: 100%">
<div class="basic">
<div v-for="(item, index) in data" :key="index" class="line">
<div class="title">{{ item.title }}</div>
<div class="values">{{ item.value }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
/* --------------- data --------------- */
// #region
const props = defineProps({
data: {
type: Array,
default: () => [],
},
});
// #endregion
/* --------------- methods --------------- */
// #region
// #endregion
</script>
<style lang="scss" scoped>
.basic {
width: 100%;
margin-top: 10px;
height: 100%;
border: 2px solid #01fefd;
border-radius: 16px;
backdrop-filter: blur(2px);
display: flex;
flex-direction: column;
justify-content: space-between;
color: #ffffff;
padding: 20px;
.line {
display: flex;
justify-content: space-between;
text-align: left;
line-height: 20px;
.title {
font-size: 16px;
width: 40%;
}
.values {
font-size: 20px;
width: 60%;
}
}
}
</style>

View File

@ -13,6 +13,7 @@ const props = defineProps({
});
const state = reactive({
total: 0, //
option: {
grid: {
left: '5%',
@ -31,11 +32,12 @@ const state = reactive({
borderRadius: 8,
formatter: (data) => {
const params = data[0];
let str = `<div class="custom-echarts-tips" >
const percentage = ((params.value / state.total) * 100).toFixed(2);
return `<div class="custom-echarts-tips" >
<span>${params.name}</span><br/>
<span>${params.marker} ${params.data} 万亩</span>
<span>${params.marker} ${params.data} km²</span><br/>
<span>占比 ${percentage}%</span>
</div>`;
return str;
},
extraCssText: 'backdrop-filter: blur(8px);',
},
@ -85,6 +87,7 @@ watch(
(val) => {
if (!isEmpty(val)) {
state.data = val;
state.total = val.reduce((sum, item) => sum + item.value, 0); //
}
},
{

View File

@ -0,0 +1,118 @@
<template>
<div style="display: flex; justify-content: space-around; width: 100%">
<section class="land_area_all">
<section v-for="(item, index) in data" :key="index" class="_left">
<section class="_circle">{{ item.value }}</section>
<section class="_text">{{ item.title }}</section>
</section>
</section>
</div>
</template>
<script setup>
import { ref } from 'vue';
/* --------------- data --------------- */
// #region
const props = defineProps({
data: {
type: Array,
default: () => [],
},
});
// #endregion
/* --------------- methods --------------- */
// #region
// #endregion
</script>
<style lang="scss" scoped>
.land_area_all {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
height: 100%;
width: 100%; /* 限制宽度 */
max-width: 400px; /* 可选最大值 */
._left {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: 100%;
._circle {
padding-top: 18%;
display: flex;
justify-content: center;
align-items: center;
font-family: 'JinBuTi';
width: 80%;
aspect-ratio: 1/1;
font-size: 18px;
color: #fff;
border-radius: 50%;
font-weight: bold;
background: url('../../../assets/images/land/countCircleBG.png') no-repeat center center / cover;
}
._text {
width: 100%;
height: 40px;
background: url('../../../assets/images/land/countTextBG.png') no-repeat center center / cover;
text-align: center;
line-height: 40px;
color: #fff;
font-size: 18px;
text-shadow: 0 0 1px #fff;
}
}
._right {
padding-right: 4px;
height: 100%;
overflow-y: auto;
.list_item {
padding: 0 24px;
position: relative;
display: flex;
grid-template-columns: 60% 40%;
justify-content: space-between;
align-items: center;
height: 32px;
color: #fff;
cursor: pointer;
user-select: none;
&.active {
background: rgba(38, 122, 102, 0.3);
border: 1px solid #35d0c0;
border-radius: 4px;
overflow: hidden;
.spot {
transform: scale(1.2);
}
._label {
font-size: 16px;
}
}
._label {
._spot {
display: inline-block;
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-25%);
width: 8px;
height: 8px;
border-radius: 50%;
}
}
._value {
text-align: right;
}
}
}
._right::-webkit-scrollbar {
display: none;
}
}
</style>

View File

@ -2,46 +2,20 @@
<el-row class="data-home-index">
<el-col :span="6" class="left-charts">
<div class="left-charts-item">
<customBack top-title="耕地面积统计" :top-postion="'left'">
<customBack top-title="基础地理信息" :top-postion="'left'">
<template #back>
<!-- <landTwo :data="state.data.two" />-->
<basic-info :data="state.data.basic"></basic-info>
</template>
</customBack>
</div>
<div class="left-charts-item">
<customBack top-title="土地分布数据" :top-postion="'left'">
<template #back>
<landOne :data="state.data.one" />
</template>
</customBack>
</div>
<div class="left-charts-item">
<customBack top-title="农村土地资源" :top-postion="'left'">
<template #back>
<landTwo :data="state.data.two" />
</template>
</customBack>
</div>
<div class="left-charts-item">
<customBack
top-title="项目效益分析"
:top-postion="'left'"
:down-title="'全县'"
:label-field="'label'"
:value-field="'value'"
:down-width="'100px'"
:options="[
{ label: '全县', value: '530926' },
{ label: '耿马镇', value: '42611' },
{ label: '勐撒镇', value: '9259' },
{ label: '勐永镇', value: '17787' },
{ label: '孟定镇', value: '42610' },
{ label: '勐简乡', value: '17788' },
{ label: '贺派乡', value: '40161' },
{ label: '四排山乡', value: '40163' },
{ label: '大兴乡', value: '40159' },
]"
:is-down="true"
@command="handleCommand"
>
<template #back>
<landThere :data="state.data.there" :query="state.queryCode" />
</template>
</customBack>
</div>
</el-col>
<el-col :span="12">
<landMap />
@ -79,9 +53,10 @@
</customBack>
</div>
<div class="right-charts-item">
<customBack top-title="全县作物情况" :top-postion="'right'">
<customBack top-title="土地违规使用风险预警" :top-postion="'right'">
<template #back>
<landSix :data="state.data.six" />
<warning :data="state.data.warnings"></warning>
<!-- <landSix :data="state.data.six" />-->
</template>
</customBack>
</div>
@ -100,6 +75,8 @@ import landSix from './components/landSix.vue';
import { useApp } from '@/hooks';
import { sleep } from '@/utils';
import { GetLandInfo } from '@/apis/land';
import BasicInfo from './components/basicInfo.vue';
import Warning from '@/views/land/components/warning.vue';
const state = reactive({
loading: false,
@ -122,15 +99,15 @@ const loadData = async () => {
await sleep(500);
state.data = {
one: [
{ value: 20, name: '耿马镇' },
{ value: 15, name: '勐撒镇' },
{ value: 12, name: '勐永镇' },
{ value: 16, name: '孟定镇' },
{ value: 8, name: '勐简乡' },
{ value: 12, name: '贺派乡' },
{ value: 10, name: '四排山乡' },
{ value: 9, name: '芒洪乡' },
{ value: 8, name: '大兴乡' },
{ value: 441.96, name: '耿马镇' },
{ value: 515.11, name: '勐撒镇' },
{ value: 399.6, name: '勐永镇' },
{ value: 1069.15, name: '孟定镇' },
{ value: 281.19, name: '勐简乡' },
{ value: 251.74, name: '贺派乡' },
{ value: 349.86, name: '四排山乡' },
{ value: 314, name: '芒洪乡' },
{ value: 456, name: '大兴乡' },
],
two: [
{ name: '耿马镇', value: 87.84 },
@ -200,6 +177,18 @@ const loadData = async () => {
value: 32.1,
},
],
basic: [
{ value: '3837km²', title: '政区域面积' },
{ value: '9个乡,3个农场', title: '下辖地区' },
{ value: '27.88万人', title: '常住人口' },
{ value: '18.8℃ - 19.2℃', title: '年均气温' },
{ value: '946.2毫米(山区)', title: '年降水量' },
{ value: '优越,适宜农业发展', title: '光热水土条件' },
],
warnings: [
{ value: 2, title: '当前风险预警数量' },
{ value: 30, title: '已处理土地违规案件' },
],
};
state.loading = false;
};
@ -240,7 +229,7 @@ const handleCommand = (data) => {
}
.left-charts-item {
width: calc(100% - 5px);
height: calc((100% - 30px) / 3);
height: calc((100% - 30px) / 2);
}
.right-charts {

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -140,6 +140,18 @@ export const constantRoutes = [
name: 'intelligentFertilizer',
meta: { title: '智能配肥' },
},
{
path: '/sub-operation-service/smartFarm/IntelligentIrrigation',
component: () => import('@/views/smartFarm/inspection/IntelligentIrrigation.vue'),
name: 'IntelligentIrrigation',
meta: { title: '智能灌溉' },
},
{
path: '/sub-operation-service/smartFarm/IntelligentSeedSoaking',
component: () => import('@/views/smartFarm/inspection/IntelligentSeedSoaking.vue'),
name: 'IntelligentSeedSoaking',
meta: { title: '智能浸种' },
},
{
path: '/sub-operation-service/smartFarm/growSeedlings',
component: () => import('@/views/smartFarm/manageControl/growSeedlings.vue'),

View File

@ -25,7 +25,7 @@ const leftMenu = reactive([
{ name: 'agricultural', title: '农资交易', icon: 'menu2.png', path: '/sub-operation-service/ecommerce-agricultural' },
{ name: 'supplier', title: '供应商服务', icon: 'menu1.png', path: '/sub-operation-service/ecommerce-supplier' },
{ name: 'purchaser', title: '采购商服务', icon: 'menu3.png', path: '/sub-operation-service/ecommerce-purchaser' },
{ name: 'land', title: '土地交易', icon: 'menu4.png', path: '/sub-operation-service/ecommerce-land' },
// { name: 'land', title: '', icon: 'menu4.png', path: '/sub-operation-service/ecommerce-land' },
]);
let currentIndex = ref(0);

View File

@ -0,0 +1,347 @@
<script setup>
import { ref, reactive, onMounted, watch, onBeforeUnmount } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
import * as echarts from 'echarts';
// DOM
const chartRef = ref(null);
// ECharts
let chartInstance = null;
//
const colorList = ['#3685FE', '#FFD500', '#25BF82'];
// x
const xData = ['1月', '2月', '3月', '4月', '5月', '6月'];
//
const option = {
backgroundColor: '#fff',
title: {
text: '',
textStyle: {
fontSize: 12,
fontWeight: 400,
},
left: 'center',
top: '5%',
},
legend: {
icon: 'circle',
top: '0',
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: '20%',
},
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',
show: false,
axisTick: {
show: false,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(107,107,107,0.37)',
},
},
axisLabel: {
textStyle: {
color: '#999',
},
},
splitLine: {
show: false,
},
},
],
series: [
{
name: '茎秆高度',
type: 'line',
data: [10, 10, 30, 12, 15, 3],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: '#3685FE',
shadowColor: 'rgba(158,135,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[0],
borderColor: colorList[0],
},
},
{
name: '叶片温度',
type: 'line',
data: [5, 12, 11, 14, 25, 16],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: '#FFD500',
shadowColor: 'rgba(115,221,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[1],
borderColor: colorList[1],
},
},
{
name: '果实大小',
type: 'line',
data: [6, 14, 17, 25, 21, 10],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: '#25BF82',
shadowColor: 'rgba(115,221,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[2],
borderColor: colorList[2],
},
},
],
};
const props = defineProps({
data: {
type: Array,
required: true,
default: () => [],
},
chartsData: {
type: Array,
required: false,
default: () => [],
},
title: {
type: String,
required: true,
default: () => '',
validator: (items) => {
return items;
},
},
showImage: {
type: Boolean,
default: false,
},
showCharts: {
type: Boolean,
default: false,
},
imageList: {
type: Array,
default: () => [],
},
});
/* --------------- methods --------------- */
// #region
//
const initChart = () => {
if (chartRef.value) {
// domecharts
chartInstance = echarts.init(chartRef.value);
//
chartInstance.setOption(option);
// tooltip
// showMaxValueTooltip();
//
window.addEventListener('resize', resizeChart);
}
};
const showMaxValueTooltip = () => {
if (!chartInstance) return;
//
let maxValue = -Infinity;
let maxSeriesIndex = 0;
let maxDataIndex = 0;
option.series.forEach((series, seriesIndex) => {
series.data.forEach((value, dataIndex) => {
if (value > maxValue) {
maxValue = value;
maxSeriesIndex = seriesIndex;
maxDataIndex = dataIndex;
}
});
});
//
setTimeout(() => {
chartInstance.dispatchAction({
type: 'showTip',
seriesIndex: maxSeriesIndex,
dataIndex: maxDataIndex,
});
}, 300);
};
//
onMounted(() => {
initChart();
});
//
onBeforeUnmount(() => {
if (chartInstance) {
window.removeEventListener('resize', resizeChart);
chartInstance.dispose();
chartInstance = null;
}
});
//
const resizeChart = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// #endregion
</script>
<template>
<div>
<el-card style="border-radius: 16px; min-height: 316px">
<div class="my-card-title" style="">{{ title }}</div>
<div v-for="(item, index) in data" :key="index" class="show-data-box">
<div class="left-title">{{ item.title }}:</div>
<div :style="{ color: item.status == '0' ? '#FE4066' : '#25BF82' }">{{ item.statusText }}</div>
</div>
<div v-if="showImage">
<p style="font-size: 14px; text-align: left; color: #000000; margin-bottom: 10px">灌溉记录:</p>
<el-image
style="width: 100%"
:src="imageList[0]"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="imageList"
show-progress
:initial-index="4"
fit="cover"
/>
</div>
<div v-if="showCharts">
<div ref="chartRef" style="width: 100%; height: 200px"></div>
</div>
</el-card>
</div>
</template>
<style scoped lang="scss">
.my-card-title {
font-size: 16px;
font-weight: bold;
text-align: left;
color: #000;
margin-bottom: 16px;
}
.show-data-box {
display: flex;
justify-content: space-between;
font-size: 14px;
margin: 7px 0;
color: #000000;
}
</style>

View File

@ -37,6 +37,8 @@ const icons = [
{ 雨量计: 'rainGauge' },
{ 土壤传感器: 'soilSensor' },
{ 积水传感器: 'waterLoggingSensor' },
{ 土壤温度: 'temperature' },
{ 土壤湿度: 'humidity' },
];
const props = defineProps({
@ -44,19 +46,6 @@ const props = defineProps({
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,
@ -72,8 +61,8 @@ const props = defineProps({
<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 style="font-size: 16px; font-weight: bold; text-align: left; color: #000; margin-bottom: 20px">{{ title }}</div>
<div style="display: flex; justify-content: flex-start; flex-wrap: wrap; gap: 20px">
<div v-for="(item, index) in devices" :key="index" class="device">
<div v-if="item.status == 1" class="status" style="background-color: #25bf82">正常</div>
<div v-else-if="item.status == 0" class="status" style="background-color: #999999">离线</div>
@ -109,6 +98,8 @@ const props = defineProps({
<img v-else-if="item.icon === 'rainGauge'" :src="getAssetsFile('images/smartFarm/雨量计.png')" alt="" />
<img v-else-if="item.icon === 'soilSensor'" :src="getAssetsFile('images/smartFarm/土壤传感器.png')" alt="" />
<img v-else-if="item.icon === 'waterLoggingSensor'" :src="getAssetsFile('images/smartFarm/积水传感器.png')" alt="" />
<img v-else-if="item.icon === 'temperature'" :src="getAssetsFile('images/smartFarm/土壤温度.png')" alt="" />
<img v-else-if="item.icon === 'humidity'" :src="getAssetsFile('images/smartFarm/土壤湿度.png')" alt="" />
<div style="text-align: left; font-weight: bold; font-size: 18px">{{ item.name }}</div>
</div>
</div>
@ -120,9 +111,8 @@ const props = defineProps({
<style scoped lang="scss">
.device {
height: 100px;
width: 14%;
width: 14.6%;
background-color: #f5f5f5;
margin: 20px 1%;
border-radius: 16px;
position: relative;
cursor: pointer;

View File

@ -1,19 +1,22 @@
<template>
<div class="smartFarm-left-menu-warp">
<div class="left-menu">
<div v-for="(n, index) in leftMenu" :key="index" class="left-menu-item" style="position: relative">
<div style="display: flex; justify-content: flex-start; align-items: center" @click.stop="toLink(index)">
<div
v-for="(n, index) in leftMenu"
:key="index"
class="left-menu-item"
style="position: relative"
@click.stop="
toLink(index);
openList(index);
"
>
<div style="display: flex; justify-content: flex-start; align-items: center">
<div class="item-img">
<img :src="getAssetsFile('images/smartFarm/' + n.icon)?.href ?? ''" alt="" />
</div>
<span :class="n.isOpen ? 'active' : ''" class="item-title">{{ n.title }}</span>
<img
v-if="n.children && n.isOpen"
alt=""
:src="getAssetsFile('images/smartFarm/closing.png')"
class="isOpen"
@click.stop="openList(index)"
/>
<img v-if="n.children && n.isOpen" alt="" :src="getAssetsFile('images/smartFarm/closing.png')" class="isOpen" />
<img
v-if="n.children && !n.isOpen"
alt=""
@ -25,7 +28,7 @@
<div v-if="n.children && n.isOpen" class="item-children">
<div v-for="(item, indexC) in n.children" :key="indexC">
<ul style="overflow: visible; padding-left: 40px; text-align: left; list-style-type: disc !important">
<li :class="indexC === currentCIndex ? 'active' : ''" @click.stop="toLinkSub(index, indexC)">
<li :class="item.name === currentCIndex ? 'active' : ''" @click.stop="toLinkSub(index, item.name)">
<div class="dot"></div>
{{ item.title }}
</li>
@ -43,17 +46,13 @@ import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
// const props = defineProps({
// currentName: { type: String, default: 'dashboard' },
// });
const leftMenu = reactive([
{
name: 'supplier',
name: 'inspection',
title: '农业环境监测',
icon: 'menu1.png',
path: '/sub-operation-service/smartFarm/main',
isOpen: true,
isOpen: false,
children: [
{
name: 'fieldInspection',
@ -85,6 +84,18 @@ const leftMenu = reactive([
icon: 'menu1.png',
path: '/sub-operation-service/smartFarm/intelligentFertilizer',
},
{
name: 'IntelligentIrrigation',
title: '智能灌溉',
icon: 'menu1.png',
path: '/sub-operation-service/smartFarm/IntelligentIrrigation',
},
{
name: 'IntelligentSeedSoaking',
title: '智能浸种',
icon: 'menu1.png',
path: '/sub-operation-service/smartFarm/IntelligentSeedSoaking',
},
],
},
{
@ -95,31 +106,31 @@ const leftMenu = reactive([
isOpen: false,
children: [
{
name: 'control',
name: 'growSeedlings',
title: '一体育苗',
icon: 'menu3.png',
path: '/sub-operation-service/smartFarm/growSeedlings',
},
{
name: 'control',
name: 'pestPrevention',
title: '病虫害预防',
icon: 'menu3.png',
path: '/sub-operation-service/smartFarm/pestPrevention',
},
{
name: 'control',
name: 'irrigationSystem',
title: '喷灌滴灌',
icon: 'menu3.png',
path: '/sub-operation-service/smartFarm/irrigationSystem',
},
{
name: 'control',
name: 'drainageControl',
title: '排集水控制',
icon: 'menu3.png',
path: '/sub-operation-service/smartFarm/drainageControl',
},
{
name: 'control',
name: 'openCurtain',
title: '开窗卷帘',
icon: 'menu3.png',
path: '/sub-operation-service/smartFarm/openCurtain',
@ -129,32 +140,31 @@ const leftMenu = reactive([
]);
let currentIndex = ref(0);
let currentCIndex = ref(-1);
// watch(
// () => props.currentName,
// () => {
// console.info('currentName', props.currentName);
// currentIndex.value = leftMenu.findIndex((m) => {
// return m.name === props.currentName;
// });
// },
// { deep: true, immediate: true }
// );
let currentCIndex = ref('');
const toLink = (index) => {
currentIndex.value = index;
currentCIndex.value = -1;
window.sessionStorage.setItem('currentOpen', index);
if (index === 0) {
window.sessionStorage.setItem('currentChild', 'main');
}
currentCIndex.value = '';
let path = index !== undefined ? leftMenu[index].path : null;
if (path) {
router.push(path);
}
};
const toLinkSub = (index, c) => {
const toLinkSub = (index, name) => {
console.info('index', index);
console.info('c', c);
currentCIndex.value = c;
let path = leftMenu[index].children[c].path;
console.info('currentChild', name);
currentCIndex.value = name;
window.sessionStorage.setItem('currentChild', name);
let path;
for (let i in leftMenu[index].children) {
if (leftMenu[index].children[i].name === name) {
path = leftMenu[index].children[i].path;
}
}
if (path) {
console.info('path', path);
router.push(path);
@ -164,6 +174,22 @@ const openList = (index) => {
currentIndex.value = index;
leftMenu[index].isOpen = !leftMenu[index].isOpen;
};
onMounted(() => {
const currentMenu = window.sessionStorage.getItem('currentOpen');
if (currentMenu) {
for (let i in leftMenu) {
leftMenu[i].isOpen = i === currentMenu;
}
}
const currentChild = window.sessionStorage.getItem('currentChild');
if (currentChild && currentChild === 'main') {
currentIndex.value = 0;
currentCIndex.value = '';
} else if (currentChild) {
currentCIndex.value = currentChild;
}
});
</script>
<style lang="scss" scoped>
.fz {
@ -179,7 +205,7 @@ const openList = (index) => {
color: $color-main;
}
.smartFarm-left-menu-warp {
padding: 0 30px;
padding: 0 30px 0 10px;
width: 100%;
height: 100%;
.left-menu {

View File

@ -10,21 +10,6 @@ const props = defineProps({
type: Array,
required: true,
default: () => [],
validator: (items) => {
return items.every((item) => {
return (
typeof item === 'object' &&
item !== null &&
typeof item.id === 'number' &&
typeof item.isOpen === 'number' &&
typeof item.isOperation === 'number' &&
typeof item.name === 'string' &&
typeof item.serial === 'string' &&
typeof item.icon === 'string' &&
(!item.status || typeof item.status === 'number')
);
});
},
},
title: {
type: String,

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import { ref, watch, onMounted, onUnmounted, computed } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
import { useRoute, useRouter } from 'vue-router';
import Hls from 'hls.js';
@ -28,7 +28,8 @@ const props = defineProps({
required: true,
default: () => [],
validator: (items) => {
return items.every((item) => {
const validItems = items.filter((item) => item.icon === 'camera');
return validItems.every((item) => {
return (
typeof item === 'object' &&
item !== null &&
@ -43,6 +44,18 @@ const props = defineProps({
},
});
const emit = defineEmits(['changeDevice']);
//
const showWeatherDetail = (data) => {
emit('changeDevice', { message: data });
};
// currentDevice
watch(currentDevice, (newValue, oldValue) => {
showWeatherDetail(newValue);
// console.log(`count ${oldValue} ${newValue}`);
});
onUnmounted(() => {
if (hls.value) {
hls.value.destroy();

View File

@ -0,0 +1,156 @@
<template>
<section>
<common>
<template #main>
<div>
<devices :title="'智能灌溉设备'" :devices="devices"></devices>
</div>
<div style="display: flex; align-items: stretch; justify-content: space-between; margin-top: 20px">
<stream :title="'灌溉监测实时监控'" :devices="devices" style="width: 60%; margin-right: 20px; height: fit-content"></stream>
<DataDisplay :title="'灌溉分析'" :data="rightData" :show-image="true" :image-list="srcList" style="flex: 1"></DataDisplay>
</div>
</template>
</common>
</section>
</template>
<script setup>
import { ref, onMounted } 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 DataDisplay from '@/views/smartFarm/components/dataDisplay.vue';
const devices = ref([
{
name: 'A-001',
icon: 'temperature',
detail: 'A区-监控设备9',
status: '1', // 0: 线 1: -1:
id: 0,
},
{
name: 'A-002',
icon: 'temperature',
detail: 'A区-监控设备66',
status: '1',
id: 1,
},
{
name: 'A-003',
icon: 'humidity',
detail: 'A区-监控设备7',
status: '0',
id: 2,
},
{
name: 'A-004',
icon: 'humidity',
detail: 'A区-监控设备1',
status: '-1',
id: 3,
},
{
name: 'A-005',
icon: 'valve',
detail: 'A区-监控设备5',
status: '-1',
id: 4,
},
{
name: 'A-006',
icon: 'valve',
status: '0',
detail: 'A区-监控设备21',
id: 5,
},
{
name: 'A-007',
icon: 'sprinkler',
status: '0',
detail: 'A区-监控设备4',
id: 6,
},
{
name: 'A-008',
detail: 'A区-监控设备3',
icon: 'sprinkler',
status: '-1',
id: 7,
},
{
name: 'A-009',
detail: 'A区-监控设备9',
icon: 'waterSupplyValve',
status: '-1',
id: 8,
},
{
name: 'A-010',
detail: 'A区-监控设备10',
icon: 'waterSupplyValve',
status: '0',
id: 9,
},
{
name: 'A-011',
detail: 'A区-监控设备3',
icon: 'intelligentValveControl',
status: '1',
id: 10,
},
{
name: 'A-012',
detail: 'A区-监控设备3',
icon: 'intelligentValveControl',
status: '1',
id: 11,
},
]);
const srcList = ref(['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg']);
const rightData = ref([
{
title: '土壤温度', //
status: '1', // 0: 1:
statusText: '正常', //
},
{
title: '土壤养分含量',
status: '1',
statusText: '正常',
},
{
title: '光照强度',
status: '0',
statusText: '偏高',
},
{
title: '降水量',
status: '0',
statusText: '偏低',
},
{
title: '空气湿度',
status: '1',
statusText: '正常',
},
{
title: '风速',
status: '1',
statusText: '微风',
},
{
title: '设备状态',
status: '1',
statusText: '正常',
},
{
title: '用水储备',
status: '1',
statusText: '充足',
},
]);
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,156 @@
<template>
<section>
<common>
<template #main>
<div>
<devices :title="'智能浸种设备'" :devices="devices"></devices>
</div>
<div style="display: flex; align-items: stretch; justify-content: space-between; margin-top: 20px">
<stream :title="'浸种监测实时监控'" :devices="devices" style="width: 60%; margin-right: 20px; height: fit-content"></stream>
<DataDisplay :title="'浸种分析'" :data="rightData" :show-image="true" :image-list="srcList" style="flex: 1"></DataDisplay>
</div>
</template>
</common>
</section>
</template>
<script setup>
import { ref, onMounted } 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 DataDisplay from '@/views/smartFarm/components/dataDisplay.vue';
const devices = ref([
{
name: 'A-001',
icon: 'tempSensor',
detail: 'A区-监控设备9',
status: '1', // 0: 线 1: -1:
id: 0,
},
{
name: 'A-002',
icon: 'tempSensor',
detail: 'A区-监控设备66',
status: '1',
id: 1,
},
{
name: 'A-003',
icon: 'heater',
detail: 'A区-监控设备7',
status: '1',
id: 2,
},
{
name: 'A-004',
icon: 'heater',
detail: 'A区-监控设备1',
status: '-1',
id: 3,
},
{
name: 'A-005',
icon: 'heat',
detail: 'A区-监控设备5',
status: '1',
id: 4,
},
{
name: 'A-006',
icon: 'disinfectionLamp',
status: '0',
detail: 'A区-监控设备21',
id: 5,
},
{
name: 'A-007',
icon: 'timer',
status: '1',
detail: 'A区-监控设备4',
id: 6,
},
{
name: 'A-008',
detail: 'A区-监控设备3',
icon: 'timer',
status: '-1',
id: 7,
},
{
name: 'A-009',
detail: 'A区-监控设备9',
icon: 'mixer',
status: '1',
id: 8,
},
{
name: 'A-010',
detail: 'A区-监控设备10',
icon: 'liquidSensor',
status: '1',
id: 9,
},
{
name: 'A-011',
detail: 'A区-监控设备3',
icon: 'liquidSensor',
status: '0',
id: 10,
},
{
name: 'A-012',
detail: 'A区-监控设备3',
icon: 'disinfectionLamp',
status: '1',
id: 11,
},
]);
const srcList = ref(['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg']);
const rightData = ref([
{
title: '温度', //
status: '1', // 0: 1:
statusText: '21℃ 正常', //
},
{
title: '湿度',
status: '1',
statusText: '18℃ 正常',
},
{
title: '光照强度',
status: '1',
statusText: '正常',
},
{
title: '浸泡液位',
status: '1',
statusText: '正常',
},
{
title: '溶解氧含量',
status: '1',
statusText: '36% 正常',
},
{
title: '酸碱度',
status: '1',
statusText: '6.7 正常',
},
{
title: '设备状态',
status: '1',
statusText: '正常',
},
{
title: '种子状态',
status: '1',
statusText: '出芽',
},
]);
</script>
<style lang="scss" scoped></style>

View File

@ -1,7 +1,227 @@
<script setup></script>
<script setup>
import Devices from '@/views/smartFarm/components/devices.vue';
import Common from '@/views/smartFarm/components/common.vue';
import stream from '@/views/smartFarm/components/stream.vue';
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { getAssetsFile } from '@/utils/index.js';
import * as echarts from 'echarts';
import BugData from '@/views/smartFarm/components/bugData.vue';
import SickData from '@/views/smartFarm/components/sickData.vue';
const currentDevice = ref(0);
const devices = ref([
{
name: 'A-001',
icon: 'temp',
detail: 'A区-监控设备1',
status: '1',
id: 0,
},
{
name: 'A-002',
icon: 'temp',
detail: 'A区-监控设备2',
status: '1',
id: 1,
},
{
name: 'A-003',
icon: 'ph',
detail: 'A区-监控设备3',
status: '1',
id: 2,
},
{
name: 'A-004',
icon: 'ph',
detail: 'A区-监控设备4',
status: '-1',
id: 3,
},
{
name: 'A-005',
icon: 'O2',
detail: 'A区-监控设备5',
status: '1',
id: 4,
},
{
name: 'A-006',
icon: 'elect',
detail: 'A区-监控设备6',
status: '0',
id: 5,
},
{
name: 'B-001',
icon: 'light',
detail: 'B区-监控设备1',
status: '1',
id: 6,
},
{
name: 'B-002',
icon: 'elect',
detail: 'B区-监控设备2',
status: '-1',
id: 7,
},
{
name: 'B-003',
icon: 'dust',
detail: 'B区-监控设备3',
status: '1',
id: 8,
},
{
name: 'B-004',
icon: 'dust',
detail: 'B区-监控设备4',
status: '1',
id: 9,
},
{
name: 'B-005',
icon: 'float',
detail: 'B区-监控设备5',
status: '1',
id: 10,
},
{
name: 'B-006',
icon: 'float',
detail: 'B区-监控设备6',
status: '1',
id: 11,
},
]);
const rules = ref([
{
name: '温度',
icon: 'temp',
unit: '℃',
goat: '15',
status: '1',
},
{
name: 'PH值',
icon: 'ph',
unit: '',
goat: '6.5',
status: '1',
},
{
name: '溶解氧',
icon: 'O2',
goat: '0.4',
unit: 'mg/L',
status: '1',
},
{
name: '色度',
icon: 'light',
goat: '15',
unit: 'PCU',
status: '1',
},
{
name: '浊度',
icon: 'dust',
goat: '1.5',
unit: 'NTU',
status: '1',
},
{
name: '导电率',
icon: 'elect',
goat: '3000',
unit: 'μS/cm',
status: '1',
},
{
name: '悬浮物',
icon: 'float',
goat: '9000',
unit: 'mg/L',
status: '0',
},
]);
const isFullscreen = ref(false);
const toggleFullscreen = () => {
isFullscreen.value = !isFullscreen.value;
};
const chooseIcon = (type) => {
switch (type) {
case 'light':
return '分光器.png';
case 'float':
return '悬浮物.png';
case 'O2':
return '水质溶解氧.png';
case 'elect':
return '水质电导率.png';
case 'dust':
return '浊度.png';
case 'temp':
return '温度.png';
case 'ph':
return '酸碱度.png';
}
};
// #endregion
</script>
<template>
<div></div>
<div>
<common>
<template #main>
<div>
<devices :title="'病虫害监测设备'" :devices="devices"></devices>
</div>
<div style="margin-top: 10px; display: flex; justify-content: space-between">
<div style="display: flex; justify-content: space-between; margin-top: 10px">
<stream :title="'病害监测实时监控'" :devices="devices" style="width: 60%; height: fit-content"></stream>
<div style="width: 38%">
<el-card style="border-radius: 16px; padding: 10px">
<div style="font-size: 16px; font-weight: bold; text-align: left; color: #000">虫害数据</div>
<sick-data style="width: 38%; min-width: 300px"></sick-data>
</el-card>
</div>
</div>
</div>
<div style="margin-top: 10px; display: flex; justify-content: space-between">
<div style="display: flex; justify-content: space-between; margin-top: 10px; width: 100%">
<stream :title="'虫害监测实时监控'" :devices="devices" style="width: 60%; height: fit-content"></stream>
<div style="width: 38%">
<el-card style="border-radius: 16px; padding: 10px">
<div style="font-size: 16px; font-weight: bold; text-align: left; color: #000">虫害数据</div>
<bug-data style="width: 38%; min-width: 300px"></bug-data>
</el-card>
</div>
</div>
</div>
</template>
</common>
</div>
</template>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.rates {
margin: 10px 0;
height: 100px;
font-size: 14px;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
background-size: 80%;
background-repeat: no-repeat;
background-position: center;
width: 25%;
img {
height: 20px;
}
}
</style>

View File

@ -7,90 +7,65 @@
</div>
<div style="margin-top: 10px; display: flex; justify-content: space-between">
<div style="display: flex; justify-content: space-between; margin-top: 10px; width: 100%">
<el-card style="width: 60%">
<el-card style="width: 60%; margin-right: 20px; border-radius: 16px">
<div style="display: flex; justify-content: space-between">
<div style="font-size: 16px; font-weight: bold; text-align: left">实时环境监测</div>
<div style="color: #999999; line-height: 25px">
当前设备
<el-select v-model="currentDevice" placeholder="Select" size="small" style="width: 160px; margin-left: 10px">
<el-select
v-model="currentDevice"
placeholder="Select"
size="small"
style="width: 160px; margin-left: 10px"
@change="changeDevice($event)"
>
<el-option v-for="item in devices" :key="item.value" :label="item.detail" :value="item.id" />
</el-select>
</div>
</div>
<div class="envData">
<div class="dt">
<div class="values">21</div>
<div class="values">{{ monitorData.co2 }}</div>
<div class="points">空气温度</div>
</div>
<div class="dt">
<div class="values">61%</div>
<div class="values">{{ monitorData.humidity }}</div>
<div class="points">空气湿度</div>
</div>
<div class="dt">
<div class="values">1600mm</div>
<div class="points">空气温度</div>
<div class="values">{{ monitorData.rainfall }}</div>
<div class="points">降水量</div>
</div>
<div class="dt">
<div class="values">东南风</div>
<div class="values">{{ monitorData.wind }}</div>
<div class="points">风向</div>
</div>
</div>
<div class="envData">
<div class="dt">
<div class="values">12μg/</div>
<div class="values">{{ monitorData.pm25 }}</div>
<div class="points">PM2.5</div>
</div>
<div class="dt">
<div class="values">679Lux</div>
<div class="values">{{ monitorData.light }}</div>
<div class="points">光照强度</div>
</div>
<div class="dt">
<div class="values">440ppm</div>
<div class="values">{{ monitorData.co2 }}</div>
<div class="points">二氧化碳浓度</div>
</div>
<div class="dt">
<div class="values">1.5m/s</div>
<div class="values">{{ monitorData.windSpeed }}</div>
<div class="points">风速</div>
</div>
</div>
<div class="notices">
<div v-for="(item, index) in notices" :key="index">
<img :src="getAssetsFile('images/smartFarm/bell.png')" alt="" style="height: 20px" />
<img :src="getAssetsFile('images/smartFarm/bell.png')" alt="" style="height: 20px; margin-right: 5px" />
<span style="font-size: 14px">{{ item.title }}</span>
</div>
</div>
</el-card>
<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">6.5 正常</div>
</div>
<div class="plantStatus">
<div class="leftKey">养分含量:</div>
<div class="errorValue">N 元素含量不足</div>
</div>
<div class="plantStatus">
<div class="leftKey">重金属含量:</div>
<div class="errorValue">Hg 金属含量超标</div>
</div>
<div class="plantStatus">
<div class="leftKey">水温:</div>
<div class="errorValue">38 偏高</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>
</el-card>
<DataDisplay :title="'环境分析报告'" :data="rightData" style="flex: 1"></DataDisplay>
</div>
</div>
</template>
@ -99,10 +74,12 @@
</template>
<script setup>
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import Devices from '@/views/smartFarm/components/devices.vue';
import Common from '@/views/smartFarm/components/common.vue';
import { getAssetsFile } from '@/utils/index.js';
import DataDisplay from '@/views/smartFarm/components/dataDisplay.vue';
import Mock from 'mockjs';
/* --------------- data --------------- */
// #region
@ -195,14 +172,68 @@ const devices = ref([
const currentDevice = ref(0);
const notices = ref([
{
title: '2025年1月1日,预计将会有特大暴雨,请提前做好防护措施!',
title: '2025年1月2日,预计将会有特大暴雨,请提前做好防护措施!',
content: '',
},
{
title: '2025年1月1日预计将会有雨,请提前做好防护措施!',
title: '2025年1月1日预计将会有大雨,请提前做好防护措施!',
content: '',
},
]);
let monitorData = ref({});
let rightData = ref([]);
const changeDevice = (id) => {
currentDevice.value = id;
monitorData.value = getMockData()[0];
rightData.value = getMockData()[1];
};
//
const getMockData = () => {
//
const leftData = Mock.mock({
co2: () => randomRange(200, 800, 0) + 'ppm', // CO300~800ppm
temperature: () => randomRange(-10, 40, 1) + '℃', // -10~40
humidity: () => randomRange(10, 100, 0) + '%', // 湿10%~100%
wind: '@pick(["东南风", "西南风", "东北风", "西北风"])',
rainfall: () => randomRange(0, 200, 1) + 'mm', // 0~200mm1
pm25: () => randomRange(0, 300, 0) + 'μg/m³', // PM2.50~300μg/m³
light: () => randomRange(100, 1000, 0) + 'Lux', // 100~1000Lux
windSpeed: () => randomRange(0, 10, 1) + 'm/s', // 0~10m/s1
});
//
const rightData = Mock.mock({
'list|6': [
{
//
'title|+1': ['酸碱度(pH)', '养分含量', '重金属含量', '水温', '水浑浊度', '盐分含量'],
'status|1': ['0', '1'],
'unit|+1': ['pH值', '%', 'mg/kg', '℃', 'NTU', 'dS/m'],
//
statusText: function () {
const statusMap = {
'酸碱度(pH)':
this.status === '0' ? `酸碱度${Mock.mock('@float(4.0, 9.0, 1, 1)')}${Mock.mock('@pick(["酸性过强","碱性过强"])')}` : '正常',
养分含量: this.status === '0' ? `${Mock.mock('@pick(["N","P","K","Ca","Mg"])')}元素含量不足` : '正常',
重金属含量: this.status === '0' ? `${Mock.mock('@pick(["镉","铅","砷","汞"])')}超标(${Mock.mock('@float(1, 5, 1, 1)')}mg/kg)` : '正常',
水温: this.status === '0' ? `${Mock.mock('@integer(10, 40)')}℃(${Mock.mock('@pick(["低温胁迫","高温胁迫"])')}` : '正常',
水浑浊度: this.status === '0' ? `浊度${Mock.mock('@integer(10, 50)')}NTU` : '正常',
盐分含量: this.status === '0' ? `盐分${Mock.mock('@float(2, 5, 1, 1)')}dS/m` : '正常',
};
return statusMap[this.title];
},
},
],
});
return [leftData, rightData.list];
};
//
const randomRange = (min, max, fixed = 0) => {
return Mock.Random.float(min, max, fixed).toFixed(fixed);
};
onMounted(() => {
changeDevice(0);
});
// #endregion
/* --------------- methods --------------- */
@ -253,10 +284,12 @@ const chooseIcon = (type) => {
.envData {
display: flex;
justify-content: space-between;
margin: 20px 0;
flex-wrap: wrap;
margin-top: 20px;
.dt {
width: 24%;
width: 25%;
text-align: left;
margin-bottom: 20px;
.values {
color: #25bf82;
font-size: 20px;

View File

@ -6,43 +6,8 @@
<devices :title="'田间监测设备'" :devices="devices"></devices>
</div>
<div style="display: flex; justify-content: space-between; margin-top: 10px">
<stream :title="'田间监测实时监控'" :devices="devices" style="width: 60%; height: fit-content"></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: 200px"></div>
</el-card>
<stream :title="'田间监测实时监控'" :devices="devices" style="width: 60%; height: fit-content" @change-device="changeDevice"></stream>
<data-display style="width: calc(40% - 20px)" title="作物生长状态" show-charts :data="textData"></data-display>
</div>
</template>
</common>
@ -55,29 +20,62 @@ 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';
import Mock from 'mockjs';
import DataDisplay from '@/views/smartFarm/components/dataDisplay.vue';
/* --------------- data --------------- */
// #region
// DOM
const chartRef1 = ref(null);
// ECharts
let chartInstance = null;
//
const colorList = ['#3685FE', '#FFD500', '#25BF82'];
// x
const xData = ['1月', '2月', '3月', '4月', '5月', '6月'];
const textData = ref([
{
title: '作物名称', //
status: '1', // 0: 1:
statusText: '橙子', //
},
{
title: '生长状态',
status: '1',
statusText: '成熟期',
},
{
title: '植株形态',
status: '1',
statusText: '果木型',
},
{
title: '叶片形态',
status: '1',
statusText: '阔叶型',
},
{
title: '生长态势',
status: '1',
statusText: '良好',
},
{
title: '田间有机质含量',
status: '1',
statusText: '16%',
},
{
title: '生长趋势图',
status: '1',
statusText: '',
},
]);
const devices = ref([
{
name: 'A-001',
icon: 'camera',
detail: 'A区-监控设备9',
status: '0',
status: '1',
id: 0,
},
{
name: 'A-002',
icon: 'camera',
detail: 'A区-监控设备66',
status: '0',
status: '1',
id: 1,
},
{
@ -123,258 +121,118 @@ const devices = ref([
id: 7,
},
]);
//
const option = {
backgroundColor: '#fff',
title: {
text: '',
textStyle: {
fontSize: 12,
fontWeight: 400,
},
left: 'center',
top: '5%',
},
legend: {
icon: 'circle',
top: '0',
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: '20%',
},
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',
show: false,
axisTick: {
show: false,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(107,107,107,0.37)',
},
},
axisLabel: {
textStyle: {
color: '#999',
},
},
splitLine: {
show: false,
},
},
],
series: [
{
name: '茎秆高度',
type: 'line',
data: [10, 10, 30, 12, 15, 3],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: '#3685FE',
shadowColor: 'rgba(158,135,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[0],
borderColor: colorList[0],
},
},
{
name: '叶片温度',
type: 'line',
data: [5, 12, 11, 14, 25, 16],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: '#FFD500',
shadowColor: 'rgba(115,221,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[1],
borderColor: colorList[1],
},
},
{
name: '果实大小',
type: 'line',
data: [6, 14, 17, 25, 21, 10],
symbolSize: 1,
symbol: 'circle',
smooth: true,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
width: 5,
color: '#25BF82',
shadowColor: 'rgba(115,221,255, 0.3)',
shadowBlur: 10,
shadowOffsetY: 20,
},
itemStyle: {
color: colorList[2],
borderColor: colorList[2],
},
},
],
};
// #endregion
/* --------------- methods --------------- */
// #region
//
const initChart = () => {
if (chartRef1.value) {
// domecharts
chartInstance = echarts.init(chartRef1.value);
//
chartInstance.setOption(option);
// tooltip
// showMaxValueTooltip();
//
window.addEventListener('resize', resizeChart);
}
const changeDevice = (params) => {
console.log(params);
textData.value = generateCropReport();
};
const showMaxValueTooltip = () => {
if (!chartInstance) return;
//
let maxValue = -Infinity;
let maxSeriesIndex = 0;
let maxDataIndex = 0;
option.series.forEach((series, seriesIndex) => {
series.data.forEach((value, dataIndex) => {
if (value > maxValue) {
maxValue = value;
maxSeriesIndex = seriesIndex;
maxDataIndex = dataIndex;
}
});
});
//
setTimeout(() => {
chartInstance.dispatchAction({
type: 'showTip',
seriesIndex: maxSeriesIndex,
dataIndex: maxDataIndex,
});
}, 300);
//
//
const AGRICULTURE_KNOWLEDGE = {
crops: ['橙子', '蓝莓', '水稻', '番茄', '小麦', '苹果', '葡萄'],
plantTypes: {
橙子: '乔木型',
蓝莓: '灌木型',
水稻: '禾本型',
番茄: '藤本型',
小麦: '草本型',
苹果: '乔木型',
葡萄: '藤本型',
},
leafTypes: {
橙子: '革质叶',
蓝莓: '卵形叶',
水稻: '线形叶',
番茄: '羽状叶',
小麦: '披针叶',
苹果: '椭圆形叶',
葡萄: '掌状叶',
},
growthStages: {
橙子: ['幼苗期', '生长期', '开花期', '结果期'],
蓝莓: ['萌芽期', '开花期', '果实膨大期', '成熟期'],
水稻: ['分蘖期', '拔节期', '抽穗期', '灌浆期'],
番茄: ['育苗期', '定植期', '开花坐果期', '采收期'],
小麦: ['出苗期', '越冬期', '返青期', '成熟期'],
苹果: ['花芽期', '展叶期', '果实发育期', '成熟期'],
葡萄: ['萌芽期', '新梢生长期', '果实生长期', '成熟期'],
},
organicMatterRange: {
橙子: [12, 20],
蓝莓: [8, 15],
水稻: [15, 25],
番茄: [10, 18],
小麦: [18, 30],
苹果: [10, 22],
葡萄: [12, 18],
},
};
//
onMounted(() => {
initChart();
//
function generateCropReport() {
const crop = Mock.Random.pick(AGRICULTURE_KNOWLEDGE.crops);
const isHealthy = Mock.Random.integer(0, 10) > 1; // 80%
const growthStage = Mock.Random.pick(AGRICULTURE_KNOWLEDGE.growthStages[crop]);
//
const abnormalTexts = {
growth: ['发育迟缓', '生长停滞', '提前成熟'],
leaf: ['枯黄', '卷曲', '病斑'],
status: ['欠佳', '受阻', '严重滞后'],
organic: ['过低', '严重不足', '失衡'],
};
return [
{
title: '作物名称',
status: isHealthy ? 1 : 0,
statusText: crop,
},
{
title: '生长状态',
status: isHealthy ? 1 : 0,
statusText: isHealthy ? growthStage : `${Mock.Random.pick(abnormalTexts.growth)}${growthStage}`,
},
{
title: '植株形态',
status: isHealthy ? 1 : 0,
statusText: AGRICULTURE_KNOWLEDGE.plantTypes[crop],
},
{
title: '叶片形态',
status: isHealthy ? 1 : 0,
statusText: isHealthy
? AGRICULTURE_KNOWLEDGE.leafTypes[crop]
: `${Mock.Random.pick(abnormalTexts.leaf)}${AGRICULTURE_KNOWLEDGE.leafTypes[crop]}`,
},
{
title: '生长态势',
status: isHealthy ? 1 : 0,
statusText: isHealthy ? '良好' : Mock.Random.pick(abnormalTexts.status),
},
{
title: '田间有机质含量',
status: isHealthy ? 1 : 0,
statusText: isHealthy
? `${Mock.Random.integer(...AGRICULTURE_KNOWLEDGE.organicMatterRange[crop])}%`
: `${Mock.Random.integer(3, 8)}% (${Mock.Random.pick(abnormalTexts.organic)})`,
},
{
title: '生长趋势图',
status: isHealthy ? 1 : 0,
statusText: '',
},
];
}
// 7
const cropMonitoringData = Mock.mock({
'data|7': [generateCropReport],
});
//
onBeforeUnmount(() => {
if (chartInstance) {
window.removeEventListener('resize', resizeChart);
chartInstance.dispose();
chartInstance = null;
}
});
//
const resizeChart = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// #endregion
</script>

View File

@ -7,7 +7,7 @@
</div>
<div style="display: flex; justify-content: space-between; margin-top: 10px">
<stream :title="'施肥监测实时监控'" :devices="devices" style="width: 60%; height: fit-content"></stream>
<el-card style="width: calc(40% - 20px); border-radius: 16px; padding: 10px">
<el-card style="width: calc(40% - 20px); border-radius: 16px">
<div style="font-size: 16px; font-weight: bold; text-align: left">配肥分析</div>
<div class="plantStatus">
<div class="leftKey">土壤酸碱度:</div>

View File

@ -36,6 +36,7 @@ export default defineConfig(({ command, mode }) => {
port: VITE_PORT,
open: true,
https: false,
origin: 'http://localhost:9526',
headers: {
'Access-Control-Allow-Origin': '*',
},