This commit is contained in:
李想 2025-03-24 17:21:53 +08:00
commit 13d7bd86c9
38 changed files with 1524 additions and 215 deletions

View File

@ -24,6 +24,7 @@
"dayjs": "^1.11.11",
"echarts": "^5.6.0",
"echarts-gl": "^2.0.9",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.7.3",
"file-saver": "^2.0.5",
"js-base64": "^3.7.7",

View File

@ -1,23 +1,27 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script>
import { ref, reactive, watchEffect } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '../../hooks/useEcharts';
export default {
name: 'CustomEchartBarLine',
name: 'CustomEchartRadar',
props: {
chartData: {
type: Array,
default: () => [],
required: true,
},
option: {
type: Object,
default: () => ({}),
},
type: {
type: String,
default: 'radar',
},
width: {
type: String,
default: '100%',
@ -27,29 +31,20 @@ export default {
default: 'calc(100vh - 78px)',
},
},
setup(props) {
emits: ['click'],
setup(props, { emit }) {
const chartRef = ref(null);
const { setOptions } = useEcharts(chartRef);
const { setOptions, getInstance } = useEcharts(chartRef);
const option = reactive({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true,
backgroundColor: '#333',
},
},
title: {
text: '',
},
xAxis: {
type: 'category',
data: [],
},
yAxis: [],
legend: {},
radar: {},
tooltip: {},
series: [
{
name: 'bar',
type: 'bar',
type: 'radar',
data: [],
},
],
@ -64,22 +59,33 @@ export default {
Object.assign(option, cloneDeep(props.option));
}
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
let seriesData = [];
typeArr.forEach((type, index) => {
let obj = { name: type };
let indicator = Array.from(
new Set(
props.chartData.map((item) => {
let { name, max } = item;
return { name, max };
})
)
);
let data = [];
typeArr.forEach((type) => {
const radarStyle = props.option?.radarStyle ?? {};
let obj = { name: type, ...radarStyle };
let chartArr = props.chartData.filter((item) => type === item.type);
obj['data'] = chartArr.map((item) => item.value);
obj['type'] = chartArr[0].seriesType;
obj['barWidth'] = chartArr[0].barWidth;
obj['itemStyle'] = chartArr[0].itemStyle;
seriesData.push(obj);
obj['value'] = chartArr.map((item) => item.value);
data.push(obj);
});
option.series = seriesData;
option.xAxis.data = xAxisData;
option.yAxis = props.option.yAxis;
option.radar.indicator = indicator;
option.series[0]['data'] = data;
setOptions(option);
getInstance()?.off('click', onClick);
getInstance()?.on('click', onClick);
}
function onClick(params) {
emit('click', params);
}
return { chartRef };
},
};

View File

@ -0,0 +1,88 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script>
import { ref, reactive, watch, watchEffect } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '../../hooks/useEcharts';
export default {
name: 'customEchartWaterDroplet',
props: {
chartData: {
type: Array,
default: () => [],
},
size: {
type: Object,
default: () => {},
},
option: {
type: Object,
default: () => ({}),
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: 'calc(100vh - 78px)',
},
},
emits: ['click'],
setup(props, { emit }) {
const chartRef = ref(null);
const { setOptions, getInstance, resize } = useEcharts(chartRef);
const option = reactive({
tooltip: {
formatter: '{b} ({c})',
},
series: [
{
type: 'pie',
radius: '72%',
center: ['50%', '55%'],
data: [],
labelLine: { show: true },
label: {
show: true,
formatter: '{b} \n ({d}%)',
color: '#B1B9D3',
},
},
],
});
watchEffect(() => {
props.chartData && initCharts();
});
watch(
() => props.size,
() => {
resize();
},
{
immediate: true,
}
);
function initCharts() {
if (props.option) {
Object.assign(option, cloneDeep(props.option));
}
setOptions(props.option);
resize();
getInstance()?.off('click', onClick);
getInstance()?.on('click', onClick);
}
function onClick(params) {
emit('click', params);
}
return { chartRef };
},
};
</script>

View File

@ -1,25 +1,34 @@
<template>
<div :class="`custom-table-tree ${shadow ? 'custom-table-tree__shadow' : ''}`">
<div v-if="title" class="title">{{ title }}</div>
<el-tree
:data="data"
:node-key="option.nodeKey"
:show-checkbox="option.showCheckbox"
:default-expanded-keys="option.defaultExpandedKeys"
:default-checked-keys="option.defaultCheckedKeys"
:props="option.props ?? option.defaultProps"
@node-click="handleNodeClick"
>
<template #default="{ data: rows }">
<slot :data="rows"></slot>
</template>
</el-tree>
<div class="panel">
<el-input v-if="filter" v-model="state.keyword" clearable placeholder="请输入关键字筛选" class="panel-filter" />
<el-tree
ref="treeRef"
:data="state.list"
:node-key="option.nodeKey"
:show-checkbox="option.showCheckbox"
:default-expanded-keys="option.defaultExpandedKeys"
:default-checked-keys="option.defaultCheckedKeys"
:default-expand-all="option.defaultExpandAll"
:props="option.props ?? option.defaultProps"
:filter-node-method="filterNodeMethod"
@node-click="handleNodeClick"
>
<template #default="{ data: rows }">
<slot :data="rows"></slot>
</template>
</el-tree>
</div>
</div>
</template>
<script setup name="custom-table-tree">
import { reactive, ref, watch } from 'vue';
const props = defineProps({
title: { type: String, default: '' },
shadow: { type: Boolean, default: true },
filter: { type: Boolean, default: false },
data: { type: Array, default: () => [] },
option: {
type: Object,
@ -34,12 +43,42 @@ const props = defineProps({
},
defaultExpandedKeys: [],
defaultCheckedKeys: [],
defaultExpandAll: false,
};
},
},
});
const emit = defineEmits(['node-click']);
const treeRef = ref(null);
const state = reactive({
keyword: '',
list: [],
});
const label = props.option.props?.label ?? 'label';
watch(
() => props.data,
(val) => {
state.list = val;
},
{
immediate: true,
}
);
watch(
() => state.keyword,
(val) => {
treeRef.value.filter(val);
}
);
const filterNodeMethod = (value, data) => {
if (!value) return true;
return data[label].includes(value);
};
const handleNodeClick = (data, node) => {
emit('node-click', data, node);
};
@ -48,6 +87,7 @@ const handleNodeClick = (data, node) => {
.custom-table-tree {
width: 100%;
height: 100%;
min-height: 400px;
min-width: 200px;
@include flex-column();
@ -65,8 +105,15 @@ const handleNodeClick = (data, node) => {
background: var(--el-color-primary);
}
.el-tree {
.panel {
padding: 16px 10px 10px;
&-filter {
margin-bottom: 10px;
}
}
.el-tree {
box-sizing: border-box;
}
}

View File

@ -8,12 +8,13 @@ import CustomRichEditor from './custom-rich-editor';
import CustomEchartBar from './custom-echart-bar';
import CustomEchartPie from './custom-echart-pie';
import CustomEchartLine from './custom-echart-line';
import CustomEchartRadar from './custom-echart-radar';
import CustomEchartMixin from './custom-echart-mixin';
import CustomEchartBarLine from './custom-echart-bar-line';
import customEchartPictorialBar from './custom-echart-pictorial-bar';
import CustomEchartLineLine from './custom-echart-line-line';
import CustomEchartBubble from './custom-echart-bubble';
import CustomEchartPie3d from './custom-echart-pie-3d';
import CustomEchartWaterDroplet from './custom-echart-water-droplet';
export {
SvgIcon,
@ -26,10 +27,11 @@ export {
CustomEchartBar,
CustomEchartPie,
CustomEchartLine,
CustomEchartRadar,
CustomEchartMixin,
CustomEchartBarLine,
customEchartPictorialBar,
CustomEchartLineLine,
CustomEchartBubble,
CustomEchartPie3d,
CustomEchartWaterDroplet,
};

View File

@ -2,6 +2,8 @@ import * as echarts from 'echarts/core';
import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart, GraphChart } from 'echarts/charts';
import 'echarts-gl';
import 'echarts-liquidfill';
import {
TitleComponent,
TooltipComponent,

View File

@ -2190,6 +2190,11 @@ echarts-gl@^2.0.9:
claygl "^1.2.1"
zrender "^5.1.1"
echarts-liquidfill@^3.1.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/echarts-liquidfill/-/echarts-liquidfill-3.1.0.tgz#4ec70f3697382d0404c95fff9f3e8dd85c8377da"
integrity sha512-5Dlqs/jTsdTUAsd+K5LPLLTgrbbNORUSBQyk8PSy1Mg2zgHDWm83FmvA4s0ooNepCJojFYRITTQ4GU1UUSKYLw==
echarts@^5.6.0:
version "5.6.0"
resolved "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz#2377874dca9fb50f104051c3553544752da3c9d6"

View File

@ -466,3 +466,18 @@ export const endDate = (num = 0, type = 'month', formater = 'YYYY-MM-DD HH:mm:ss
if (typeof num === 'string') return dayjs(num).endOf(type).format(formater);
return num === 0 ? dayjs().endOf(type).format(formater) : dayjs().subtract(num, type).endOf(type).format(formater);
};
/**
* @Title: 生成随机数
* @param len
* @returns
*/
export const randomNumber = (len) => {
let randomlen = len ? len : 10;
const chars = '0123456789';
let result = '';
for (let i = 0; i < randomlen; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};

View File

@ -1,8 +1,7 @@
<template>
<div style="margin-top: 16px">
<!-- <el-dialog v-model="isShowVal" title="种植阶段详情" width="1000" center @closed="stageClose"> -->
<el-text class="mx-1" size="large">种植阶段详情</el-text>
<div style="margin-top: 16px">
<el-dialog v-model="isShowVal" title="种植阶段详情" width="1000" center @closed="stageClose">
<!-- <div style="margin-top: 16px"> -->
<avue-crud
ref="stateCrudRef"
v-model="stageState.form"
@ -37,8 +36,8 @@
<custom-table-operate :actions="stageState.options.actions" :data="scope" />
</template>
</avue-crud>
<!-- </el-dialog> -->
</div>
<!-- </div> -->
</el-dialog>
</div>
</template>
<script setup>
@ -90,7 +89,7 @@ let currentRow = reactive({});
const loadList = () => {
if (isShowVal.value) {
console.info('loadList', props);
// console.info('loadList', props);
getStageList();
}
};

View File

@ -34,7 +34,6 @@
</avue-crud>
<stageList :is-show="stageShow" :row-original="state.currentRow" @close="stageHide"></stageList>
<el-text class="mx-1" size="large">种植阶段详情</el-text>
</div>
</template>
<script setup>
@ -136,6 +135,11 @@ const state = reactive({
icon: 'delete',
event: ({ row }) => rowDel(row),
},
{
name: '种植阶段',
icon: 'Operation',
event: ({ row }) => rowStageView(row),
},
],
},
pageData: {
@ -320,10 +324,13 @@ const rowDel = (row, index, done) => {
const rowClick = (row) => {
state.currentRow = { ...row };
stageShow.value = true;
console.info('rowClick', state.currentRow);
};
const stageHide = () => {
stageShow.value = false;
};
const rowStageView = (row, index, done) => {
stageShow.value = true;
};
</script>

View File

@ -1,6 +1,464 @@
<template>
<div style="width: 100%; height: 100%">
<div></div>
<div v-loading="state.loading" class="statistic">
<el-row :gutter="16" style="margin-bottom: 20px">
<el-col :span="12">
<el-card shadow="hover" class="statistic-data">
<b class="statistic-title">综合数据统计</b>
<el-row :gutter="16" style="margin-top: 40px">
<el-col :span="8" class="text-center">
<p>农村人口</p>
<avue-count-up end="27.88" :decimals="2" class="text-primary" />
<em>万人</em>
</el-col>
<el-col :span="8" class="text-center">
<p>耕地面积</p>
<avue-count-up end="103.88" :decimals="2" class="text-warning" />
<em>万亩</em>
</el-col>
<el-col :span="8" class="text-center">
<p>农业总产值</p>
<avue-count-up end="92.88" :decimals="2" class="text-danger" />
<em>亿元</em>
</el-col>
<el-col :span="24" class="text-center" style="margin-top: 90px">
<p>品牌农产品销售情况</p>
<avue-count-up end="17.88" :decimals="2" class="text-success" />
<em>亿元</em>
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover">
<custom-echart-bar :chart-data="state.areaData" height="400px" :option="state.areaOption" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="16" style="margin-bottom: 20px">
<el-col :span="12">
<el-card shadow="hover">
<custom-echart-bar :chart-data="state.breedingData" height="400px" :option="state.breedingOption" />
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover">
<custom-echart-radar :chart-data="state.inputsData" height="400px" :option="state.inputsOption" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="16" style="margin-bottom: 20px">
<el-col :span="12">
<el-card shadow="hover">
<custom-echart-pie :chart-data="state.businessData" height="400px" :option="state.businessOption" />
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover">
<custom-echart-mixin :chart-data="state.codingData" :option="state.codingOption" height="400px" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="home"></script>
<script setup>
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
import { useApp } from '@/hooks';
import { sleep } from '@/utils';
const router = useRouter();
const app = useApp();
const state = reactive({
areaOption: {
// color: ['#fed500'],
title: {
text: '土地分别数据统计',
textStyle: {
color: '#333',
},
},
xAxis: {
type: 'category',
name: '区域',
},
yAxis: {
type: 'value',
name: '亩',
},
tooltip: {
formatter: function (params) {
return `${params.name}${params.value}`;
},
},
barStyle: {
barWidth: 50,
showBackground: true,
itemStyle: {
borderRadius: 20,
},
},
},
areaData: [
{ value: 230, name: '耿马镇' },
{ value: 165, name: '勐永镇' },
{ value: 217, name: '勐撒镇' },
{ value: 200, name: '孟定镇' },
{ value: 305, name: '大兴乡' },
],
breedingOption: {
color: ['#41b879', '#fed500'],
title: {
text: '种养殖综合数据统计',
textStyle: {
color: '#333',
},
},
tooltip: {
formatter: (params) => {
const find = state.breedingData.find((item) => item.name === params.name);
return `${params.name}${params.value}${find.unit}`;
},
},
barStyle: {
barWidth: 50,
showBackground: true,
itemStyle: {
borderRadius: 20,
},
},
},
breedingData: [
{ value: 230, name: '种植面积', unit: '亩' },
{ value: 165, name: '养殖面积', unit: '亩' },
{ value: 217, name: '种植基地', unit: '个' },
{ value: 200, name: '养殖基地', unit: '个' },
],
inputsOption: {
color: ['#ffd500'],
title: {
text: '投入品数据统计',
textStyle: {
color: '#333',
},
},
legend: { show: false },
radar: {
indicator: [],
center: ['50%', '50%'],
radius: 140,
startAngle: 90,
splitNumber: 4,
shape: 'circle',
axisName: {
formatter: '{value}',
// formatter: (params) => {
// const find = state.inputsData.find((item) => item.name === params);
// return `${params}${find.value}${find.unit}`;
// },
color: '#333',
},
splitArea: {
areaStyle: {
color: ['#77EADF', '#26C3BE', '#64AFE9', '#428BD4'],
shadowColor: 'rgba(0, 0, 0, 0.2)',
shadowBlur: 10,
},
},
axisLine: {
lineStyle: {
color: 'rgba(211, 253, 250, 0.8)',
},
},
splitLine: {
lineStyle: {
color: 'rgba(211, 253, 250, 0.8)',
},
},
},
radarStyle: {
areaStyle: {},
},
tooltip: {
formatter: (params) => {
let str = params.marker + params.name + '<br/>';
state.inputsData.forEach((item) => {
str += item.name + '' + item.value + item.unit + '<br/>';
});
return str;
},
},
},
inputsData: [
{ value: 75, name: '农药使用', type: '投入品', max: 100, unit: '吨' },
{ value: 38, name: '农机使用', type: '投入品', max: 100, unit: '台' },
{ value: 74, name: '种源使用', type: '投入品', max: 100, unit: '万吨' },
{ value: 55, name: '兽药使用', type: '投入品', max: 100, unit: '千克' },
{ value: 65, name: '肥料使用', type: '投入品', max: 100, unit: '吨' },
],
businessOption: {
title: {
text: '经营主体分类统计',
textStyle: {
color: '#333',
},
},
legend: {
right: '0',
top: 'middle',
orient: 'vertical',
itemWidth: 10,
itemHeight: 10,
},
label: {
color: '#333',
// formatter: ({ data }) => {
// return data.name + '' + data.value;
// },
},
tooltip: {
formatter: (params) => {
return `${params.name}${params.value}`;
},
},
series: [
{
type: 'pie',
radius: [80, 140],
itemStyle: {
borderRadius: 10,
},
},
],
},
businessData: [
{ value: 28, name: '个体户' },
{ value: 358, name: '村集体' },
{ value: 217, name: '合作社' },
{ value: 128, name: '农资企业' },
{ value: 22, name: '种源企业' },
{ value: 41, name: '生产加工企业' },
],
codingOption: {
color: ['#41b879', '#ffd500'],
title: {
text: '溯源赋码扫码统计',
textStyle: {
color: '#333',
},
},
legend: {},
yAxis: {
type: 'value',
name: '次',
},
barStyle: {
barWidth: 50,
},
// tooltip: {
// formatter: (params) => {
// const arr = state.codingData.filter((item) => item.name === params.name);
// return `${params.name}<br/>
// ${arr[0].type}${arr[0].value}<br/>
// ${arr[1].type}${arr[1].value}`;
// },
// },
},
codingData: [
{
name: '一月',
value: 40,
type: '赋码',
seriesType: 'bar',
},
{
name: '一月',
value: 60,
type: '扫码',
seriesType: 'line',
},
{
name: '二月',
value: 100,
type: '赋码',
seriesType: 'bar',
},
{
name: '二月',
value: 120,
type: '扫码',
seriesType: 'line',
},
{
name: '三月',
value: 80,
type: '赋码',
seriesType: 'bar',
},
{
name: '三月',
value: 124,
type: '扫码',
seriesType: 'line',
},
{
name: '四月',
value: 200,
type: '赋码',
seriesType: 'bar',
},
{
name: '四月',
value: 220,
type: '扫码',
seriesType: 'line',
},
],
loading: false,
});
//
const loadData = async () => {
state.loading = true;
await sleep(500);
state.loading = false;
// GetEntityList(state.query)
// .then((res) => {
// if (res.code === 200) {
// const { current, size, total, records } = res.data;
// state.data = records;
// state.pageData = {
// currentPage: current || 1,
// pageSize: size || 10,
// total: total,
// };
// }
// })
// .catch((err) => {
// app.$message.error(err.msg);
// state.data = [];
// })
// .finally(() => {
// state.loading = false;
// });
};
loadData();
</script>
<style lang="scss" scoped>
.statistic {
.el-card {
border: 0 !important;
border-radius: 16px !important;
}
&-title {
font-size: 19px;
font-weight: bold;
font-family: '黑体';
color: #333;
flex: 1;
}
&-data {
height: 440px;
font-size: 18px;
p {
margin-bottom: 30px;
}
span {
font-size: 30px;
font-weight: bolder;
}
em {
font-style: normal;
font-size: 14px;
margin-left: 5px;
}
}
&-display {
@include flex-column();
background-repeat: no-repeat;
background-position: right bottom;
dt {
@include flex-row();
align-items: center;
margin-bottom: 30px;
i {
display: inline-block;
width: 72px;
height: 72px;
margin-right: 20px;
}
span {
font-size: 18px;
font-weight: 400;
}
}
dd {
span {
font-size: 22px;
font-weight: 500;
color: #000000;
}
}
&.d1 {
dt span {
color: #ff4975;
}
}
&.d2 {
dt span {
color: #41b879;
}
}
&.d3 {
dt span {
color: #3685fe;
}
}
&.d4 {
dt span {
color: #6b66ff;
}
}
}
&-table {
@include flex-row();
align-items: center;
}
&-rank {
li {
@include flex-row();
align-items: center;
margin-top: 16px;
i {
width: 72px;
height: 72px;
margin-right: 20px;
}
span {
@include flex-column();
flex: 1;
font-size: 14px;
}
em {
font-style: normal;
color: #999;
}
}
}
}
</style>

View File

@ -611,11 +611,11 @@ function newTree(arr, i) {
arr.forEach((v) => {
if (i == 0) {
v.value = v.id;
v.label = v.prentLandType;
v.label = v.landType;
v.disabled = !v.children || !v.children.length;
} else {
v.value = v.id;
v.label = v.childLandCategory;
v.label = v.landType;
}
if (v.children) v.children = newTree(v.children, i + 1);
});

View File

@ -83,7 +83,7 @@ let currentRow = reactive({});
const loadList = () => {
if (isShowVal.value) {
console.info('loadList', props);
// console.info('loadList', props);
getList();
}
};

View File

@ -0,0 +1,181 @@
<template>
<div class="inputs-list-warp">
<el-row v-if="!disabledVal" :gutter="20">
<template v-for="(n, index) in inputsList" :key="index">
<el-col :span="10">
<el-form-item label="投入品" label-width="80px">
<el-cascader v-model="n.id" :options="materialTypes" :props="{ value: 'id', label: 'dataName', checkStrictly: true }" />
</el-form-item>
</el-col>
<el-col :span="14">
<el-form-item label="预估投入量" label-width="100px">
<div class="right-warp">
<div class="right-item">
<el-input-number v-model="n.num" :min="1">
<template #suffix>
<span>{{ n.unit }}</span>
</template>
</el-input-number>
</div>
<div class="right-item right-do">
<el-icon :size="'20'" :color="color" @click="addVal">
<CirclePlus />
</el-icon>
<el-icon :size="'20'" :color="color" @click="delVal(index)">
<Remove />
</el-icon>
</div>
</div>
</el-form-item>
</el-col>
</template>
</el-row>
<el-row v-else :gutter="20" class="read-only">
<template v-for="(m, indexm) in listData" :key="indexm">
<el-col :span="16">
<el-cascader
v-model="m.id"
:options="materialTypes"
style="width: 150px"
disabled
:props="{ value: 'id', label: 'dataName', checkStrictly: true }"
/>
</el-col>
<el-col :span="8">
<span>{{ m.num }}</span>
<span>{{ m.unit }}</span>
</el-col>
</template>
</el-row>
</div>
</template>
<script setup>
import { onMounted, reactive, ref, watch } from 'vue';
import { useApp } from '@/hooks';
import inputSuppliesApis from '@/apis/inputSuppliesApi';
import { randomNumber } from '@/utils/index';
import { isEmpty } from '@/utils';
const app = useApp();
const emit = defineEmits(['']);
const props = defineProps({
isDisabled: {
type: Boolean,
default: false,
},
list: {
type: Array,
default: () => {
return [];
},
},
});
const { getMaterailTypes } = inputSuppliesApis;
let inputsList = reactive([]);
const materialTypes = reactive([]);
async function getTypes() {
let res = await getMaterailTypes();
if (res.code == 200) {
materialTypes.push(...res.data);
console.log('types', materialTypes);
}
}
let listData = reactive([]);
let disabledVal = ref(false);
onMounted(() => {
getTypes();
});
watch(
() => (props.isDisabled, props.list),
() => {
disabledVal.value = props.isDisabled;
inputsList = props.list.length > 0 ? props.list : reactive([{ id: '', num: 1, unit: 'ml' }]);
listData = [];
listData = props.list;
},
{
deep: true,
immediate: true,
}
);
const addVal = () => {
// let Id = randomNumber(20);
// console.info('id', Id);
inputsList.push({ id: '', num: 1, unit: 'ml' });
};
const delVal = (index) => {
if (index != undefined) {
if (inputsList.length < 2) {
return app.$message.error('请保留至少一个投入品');
}
inputsList.shift(index, 1);
}
};
const saveList = () => {
let list = [];
if (inputsList.length < 2) {
if (inputsList[0].id == '' || !inputsList[0].id) {
return app.$message.error('请选择投入品');
}
}
list = inputsList.filter((m) => {
return !isEmpty(m.id);
});
return list;
};
defineExpose({
saveList,
});
</script>
<style lang="scss" scoped>
.inputs-list-warp {
border-radius: 4px;
background-color: #ffffff;
.right-warp {
display: inline-flex;
justify-content: space-around;
.right-item {
display: inline-block;
vertical-align: middle;
}
.right-do {
padding-left: 16px;
::v-deep() {
.el-icon {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px dashed #e6e6e6;
margin: 0 16px 0 0;
cursor: pointer;
}
}
}
}
.read-only {
font-size: 12px !important;
::v-deep() {
.el-input__wrapper {
box-shadow: 0 0 0 0 red !important;
background: transparent !important;
}
.el-input__inner {
font-size: 12px !important;
}
.el-input__suffix {
display: none !important;
}
}
}
}
</style>

View File

@ -27,6 +27,22 @@
<el-tag size="small">{{ stageObj[row.stage] }}</el-tag>
</template>
<template #workTime="{ row }">
{{ row.operationDate }}
</template>
<template #input-form="{ row }">
<div style="display: flex; flex-direction: column; height: 100%">
<inputs ref="refinputs" :list="stageState.currentRow.input" :is-disabled="false"></inputs>
</div>
</template>
<template #input="{ row }">
<div style="display: flex; flex-direction: column; height: 100%">
<inputs ref="refinputs" :is-disabled="true" :list="row.input"></inputs>
</div>
</template>
<template #menu="scope">
<custom-table-operate :actions="stageState.options.actions" :data="scope" />
</template>
@ -41,6 +57,7 @@ import { CRUD_OPTIONS } from '@/config';
import { useUserStore } from '@/store/modules/user';
import { getPlantingStage, savePlantingStage, editPlantingStage, delPlantingStage } from '@/apis/land.js';
import { isEmpty, imageToBase64, getAssetsFile, downloadFile } from '@/utils';
import inputs from '../component/inputsList.vue';
const props = defineProps({
isShow: {
@ -56,15 +73,16 @@ const props = defineProps({
});
const emit = defineEmits(['close']);
let refinputs = ref(null);
const { VITE_APP_BASE_API } = import.meta.env;
const app = useApp();
const UserStore = useUserStore();
const stateCrudRef = ref(null);
const stageOptions = reactive([
{ value: '0', label: '苗期' },
{ value: '1', label: '花果期' },
{ value: '2', label: '采收期' },
{ value: 0, label: '苗期' },
{ value: 1, label: '花果期' },
{ value: 2, label: '采收期' },
]);
const workOptions = reactive([
@ -87,7 +105,7 @@ let currentRow = reactive({});
const loadList = () => {
if (isShowVal.value) {
console.info('loadList', props);
// console.info('loadList', props);
getStageList();
}
};
@ -120,29 +138,27 @@ const stageState = reactive({
addBtn: false,
selection: false,
group: [
{ label: '所属阶段', prop: 'stage' },
{ label: '作业类型', prop: 'workType' },
{ label: '作业时间', prop: 'operationDate' },
{
prop: 'inputs',
label: '投入品',
column: [
{
prop: 'input',
span: 24,
labelWidth: 0,
},
],
},
],
column: [
// {
// label: '',
// prop: 'cropId',
// type: 'select',
// remote: false,
// width: '160px',
// showOverflowTooltip: true,
// props: {
// label: 'crop',
// value: 'id',
// },
// dicHeaders: {
// authorization: UserStore.token,
// },
// dicUrl: `${VITE_APP_BASE_API}/land-resource/baseInfo/planTypePage?current=1&size=999`,
// dicFormatter: (res) => res.data.records ?? [],
// rules: [{ required: true, message: '', trigger: 'blur' }],
// },
{
prop: 'input',
label: '投入品',
editDisplay: false,
addDisplay: false,
span: 24,
labelWidth: 0,
},
{
label: '所属阶段',
prop: 'stage',
@ -154,6 +170,8 @@ const stageState = reactive({
message: '请选择',
trigger: 'blur',
},
editDisplay: true,
addDisplay: true,
},
{
label: '作业类型',
@ -174,21 +192,14 @@ const stageState = reactive({
dicFormatter: (res) => res.data.records ?? [],
rules: { required: true, message: '请选择', trigger: 'blur' },
change: handleWorkChange,
},
{
label: '作业时间(多少天后)',
prop: 'workTime',
rules: { required: true, message: '请输入', trigger: 'blur' },
props: {
type: 'Number',
},
addDisabled: true,
editDisplay: false,
viewDisplay: false,
editDisplay: true,
addDisplay: true,
},
{
label: '作业时间',
prop: 'operationDate',
prop: 'workTime',
rules: { required: true, message: '请输入', trigger: 'blur' },
addDisabled: true,
editDisplay: false,
addDisplay: false,
},
@ -222,7 +233,11 @@ async function getStageList() {
.then((res) => {
if (res.code === 200) {
const { current, size, total, records } = res.data;
stageState.data = records || [];
// stageState.data = records || [];
stageState.data =
records.map((m) => {
return { ...m, input: m.input ? JSON.parse(m.input) : [] };
}) || [];
stageState.pageData = {
currentPage: current || 1,
pageSize: size || 10,
@ -285,19 +300,22 @@ const stageRowDel = (row, index, done) => {
.catch(() => {});
};
const stageRowEdit = (row) => {
stageState.currentRow = row;
stateCrudRef.value.rowEdit(row);
};
const onStateAdd = () => {
stageState.currentRow = {};
if (!currentRow.id) {
app.$message.error('请选择种植规划');
return;
}
stateCrudRef.value.rowAdd();
stateCrudRef.value.rowAdd({});
};
const stageRowSave = (row, done, loading) => {
row.planId = currentRow.id;
row.input = JSON.stringify(refinputs.value ? refinputs.value.saveList() : []);
console.info('stageRowSave', row);
savePlantingStage({ ...row })
.then((res) => {
@ -317,6 +335,7 @@ const stageRowSave = (row, done, loading) => {
const stageRowUpdate = (row, index, done, loading) => {
console.info('stageRowUpdate');
row.input = JSON.stringify(refinputs.value ? refinputs.value.saveList() : []);
editPlantingStage(row)
.then((res) => {
if (res.code === 200) {

View File

@ -2,7 +2,7 @@
<div class="custom-page">
<el-row :gutter="20">
<el-col :span="4">
<custom-table-tree title="种养殖基地分类" :data="landTypeData" @node-click="onNodeClick" />
<custom-table-tree title="种养殖基地分类" :data="treeData" @node-click="onNodeClick" />
</el-col>
<el-col :span="20">
<avue-crud
@ -24,7 +24,6 @@
@row-update="rowUpdate"
>
<template #menu-left>
<!-- <el-button type="primary" icon="plus" @click="rowAdd">添加</el-button> -->
<el-button type="danger" icon="delete" @click="onDel(state.selection)">批量删除</el-button>
</template>
@ -47,20 +46,21 @@ import { useRouter } from 'vue-router';
import { useApp } from '@/hooks';
import { useUserStore } from '@/store/modules/user';
import { CRUD_OPTIONS } from '@/config';
import { isEmpty, mockData, sleep } from '@/utils';
import { isEmpty, mockData, sleep, setDicData } from '@/utils';
import { getLandsList } from '@/apis/land';
const { VITE_APP_BASE_API, VITE_APP_NAME } = import.meta.env;
const app = useApp();
const UserStore = useUserStore();
const router = useRouter();
const crudRef = ref(null);
const landTypeData = ref([
const treeData = ref([
{
label: '基地分类',
id: '0',
id: null,
children: [
{ label: '种植基地', id: '01', children: [], pId: '0' },
{ label: '养殖基地', id: '02', children: [], pId: '0' },
{ label: '种植基地', id: '1', children: [], pid: null },
{ label: '养殖基地', id: '2', children: [], pid: null },
],
},
]);
@ -114,38 +114,31 @@ const state = reactive({
label: '地块名',
prop: 'landName',
width: 200,
formslot: true,
type: 'select',
props: {
label: 'landName',
value: 'id',
},
dicUrl: `${VITE_APP_BASE_API}/land-resource/landManage/page?current=1&size=20`,
dicHeaders: {
authorization: UserStore.token,
},
dicFormatter: (res) => res.data?.records ?? [],
filterable: true,
remote: true,
clearable: true,
remoteMethod: (val) => remoteLandList(val),
change: (val) => selectedChange(val),
rules: {
required: true,
message: '请输入',
message: '请选择',
trigger: 'blur',
},
// addDisplay: true,
// editDisplay: true,
// viewDisplay: false,
// overHidden: true,
},
// {
// label: '',
// prop: 'p2',
// width: 200,
// type: 'select',
// // addDisplay: true,
// // editDisplay: true,
// // viewDisplay: false,
// // props: {
// // label: 'areaName',
// // value: 'areaCode',
// // children: 'areaChildVOS',
// // },
// // dicUrl: `${VITE_APP_BASE_API}/system/area/region?areaCode=530000`,
// // dicHeaders: {
// // authorization: UserStore.token,
// // },
// // dicFormatter: (res) => res.data ?? [],
// rules: {
// required: true,
// message: '',
// trigger: 'blur',
// },
// overHidden: true,
// },
{
label: '区域位置',
prop: 'p3',
@ -284,7 +277,7 @@ const loadData = async () => {
state.data = mockData(
{
p1: '耿马镇一号基地',
p2: '耿马镇2025001号地块',
landName: '耿马镇2025001号地块',
p3: '耿马傣族佤族自治县/耿马镇',
p4: '1000',
p5: '张三',
@ -418,4 +411,19 @@ const onDel = (rows = []) => {
const rowDel = (row, index, done) => {
onDel([row]);
};
//
const remoteLandList = async (val) => {
if (isEmpty(val)) return;
const query = { landName: val, current: 1, size: 20 };
const res = await getLandsList(query);
if (res.code === 200) {
setDicData(state.options.column, 'landName', res.data.records);
}
};
//
const selectedChange = ({ value, item }) => {
console.log(430, value, item, item.landName, item.address, item.area);
};
</script>

View File

@ -36,11 +36,19 @@ const startTime = () => {
const chearTime = () => {
if (interval.value) {
interval.value = null;
clearInterval(interval.value);
interval.value = null;
}
};
onMounted(() => {
// startTime();
});
onUnmounted(() => {
// chearTime();
});
defineExpose({
startTime,
chearTime,
@ -50,7 +58,7 @@ defineExpose({
.current-time-warp {
position: fixed;
right: 16px;
top: 32px;
top: 24px;
color: #add8f1;
z-index: 2;
}

View File

@ -466,3 +466,18 @@ export const endDate = (num = 0, type = 'month', formater = 'YYYY-MM-DD HH:mm:ss
if (typeof num === 'string') return dayjs(num).endOf(type).format(formater);
return num === 0 ? dayjs().endOf(type).format(formater) : dayjs().subtract(num, type).endOf(type).format(formater);
};
/**
* @Title: 生成随机数
* @param len
* @returns
*/
export const randomNumber = (len) => {
let randomlen = len ? len : 10;
const chars = '0123456789';
let result = '';
for (let i = 0; i < randomlen; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};

View File

@ -62,10 +62,10 @@ const chartsData = reactive({
{ name: '耿马', value: 100, type: '合作社', seriesType: 'bar', stack: '耿马' },
{ name: '耿马', value: 60, type: '经营企业', seriesType: 'bar', stack: '耿马', itemStyle: { borderRadius: [8, 8, 0, 0] } },
{ name: '耿马', value: 10, type: '趋势', seriesType: 'line' },
{ name: '大香乡', value: 20, type: '个体户', seriesType: 'bar', stack: '耿马' },
{ name: '大香乡', value: 20, type: '村集体', seriesType: 'bar', stack: '耿马' },
{ name: '大香乡', value: 80, type: '合作社', seriesType: 'bar', stack: '耿马' },
{ name: '大香乡', value: 40, type: '经营企业', seriesType: 'bar', stack: '耿马', itemStyle: { borderRadius: [8, 8, 0, 0] } },
{ name: '大香乡', value: 20, type: '个体户', seriesType: 'bar', stack: '大香乡' },
{ name: '大香乡', value: 20, type: '村集体', seriesType: 'bar', stack: '大香乡' },
{ name: '大香乡', value: 80, type: '合作社', seriesType: 'bar', stack: '大香乡' },
{ name: '大香乡', value: 40, type: '经营企业', seriesType: 'bar', stack: '大香乡', itemStyle: { borderRadius: [8, 8, 0, 0] } },
{ name: '大香乡', value: 50, type: '趋势', seriesType: 'line' },
],
});

View File

@ -80,7 +80,7 @@ const classOptions = {
.item-td {
padding: 4px 6px;
&.td-title {
color: #6cd1f9 !important;
color: #6beff9 !important;
}
&.zebra-b {
background: #051225 !important;

View File

@ -156,7 +156,7 @@ watch(
font-size: 13px;
}
.value {
color: #6cd1f9;
color: #6beff9;
font-size: 16px;
font-weight: bold;
margin-top: 6px;

View File

@ -142,7 +142,7 @@ watch(
font-size: 13px;
}
.value {
color: #6cd1f9;
color: #6beff9;
font-size: 16px;
font-weight: bold;
margin-top: 6px;

View File

@ -16,7 +16,7 @@ const plantBreed = ref({
},
},
legend: {
data: ['耕地', '林地', '建设用地'],
data: ['种植面积', '养殖面积', '种植基地', '养殖基地'],
right: '0', // 10%
top: 'middle', //
orient: 'vertical', //
@ -46,9 +46,10 @@ const plantBreed = ref({
],
},
valData: [
{ value: 100, name: '耕地' },
{ value: 105, name: '林地' },
{ value: 217, name: '建设用地' },
{ value: 100, name: '种植面积' },
{ value: 105, name: '养殖面积' },
{ value: 217, name: '种植基地' },
{ value: 315, name: '养殖基地' },
],
});
</script>

View File

@ -1,14 +1,18 @@
<template>
<div class="demo roll-list" style="height: 90%">
<vue3ScrollSeamless class="scroll-wrap" :classOptions="classOptions" :dataList="list">
<div v-for="(item, index) in items" :key="index" class="list-item">
<div class="demo roll-list" style="height: 100%" ref="refroll">
<vue3ScrollSeamless class="scroll-wrap" :classOptions="classOptions" :dataList="datalist">
<div v-for="(item, index) in datalist" :key="index" class="list-item">
<div class="list-item-content">
<div class="list-item-l">
<div class="item-top">
<span class="label"> {{ item.title || '--' }}</span>
<span class="value"> {{ item.value || '0' }}</span>
</div>
<el-progress :percentage="50" :show-text="false" :stroke-width="3" color="#6cd1f9" />
<div class="progress-val">
<div class="progress-warp" :style="{ width: item.percent + 'px' }">
<div class="progress"></div>
</div>
</div>
</div>
<div class="list-item-r">
{{ '0' + (index + 1) }}
@ -31,10 +35,25 @@ const props = defineProps({
});
let list = reactive(props.items);
let refroll = ref(null);
const classOptions = {
singleHeight: 48,
};
let datalist = computed(() => {
let maxwidth = refroll.value && refroll.value.clientWidth;
return list.map((m) => {
return { ...m, percent: parseInt(Number(parseInt(m.value) / max.value) * maxwidth) };
});
});
let max = computed(() => {
let valueList = new Set(list.map((item) => parseInt(item.value)));
let sortValue = [...valueList].sort((a, b) => b - a) || [];
// console.info('valueList', sortValue);
return sortValue.length ? sortValue[0] : 0;
});
</script>
<style scoped lang="scss">
@ -73,7 +92,7 @@ const classOptions = {
}
.value {
font-size: 10px;
color: #6cd1f9;
color: #6beff9;
}
}
}
@ -81,11 +100,31 @@ const classOptions = {
text-align: right;
font-size: 20px;
font-weight: bold;
color: #6cd1f9;
color: #6beff9;
position: absolute;
right: 0;
bottom: 0;
}
.progress-val {
width: calc(100%);
.progress-warp {
.progress {
height: 6px;
border-radius: 6px;
background: linear-gradient(90deg, #45bfe9 0%, #01589c 100%);
animation: expandWidth 1s ease-in-out forwards;
}
@keyframes expandWidth {
from {
width: 0;
}
to {
width: 100%;
}
}
}
}
}
}
.ui-wrap {

View File

@ -1,6 +1,6 @@
<template>
<div class="home-trace-charts">
<custom-echart-bar-line :chart-data="chartsData.valData" :option="chartsData.option" height="100%" />
<custom-echart-mixin :chart-data="chartsData.valData" :option="chartsData.option" height="100%" />
</div>
</template>
<script setup>
@ -25,6 +25,9 @@ const chartsData = ref({
legend: {
data: ['赋码', '扫码'],
},
barStyle: {
barWidth: 18,
},
yAxis: [
{
type: 'value',
@ -74,13 +77,13 @@ const chartsData = ref({
},
},
{ name: '1月', value: 60, type: '扫码', seriesType: 'line' },
{ name: '2月', value: 20, type: '赋码', seriesType: 'bar' },
{ name: '2月', value: 20, type: '赋码', seriesType: 'bar', itemStyle: { borderRadius: [8, 8, 0, 0] } },
{ name: '2月', value: 100, type: '扫码', seriesType: 'line' },
{ name: '3月', value: 80, type: '赋码', seriesType: 'bar' },
{ name: '3月', value: 80, type: '赋码', seriesType: 'bar', itemStyle: { borderRadius: [8, 8, 0, 0] } },
{ name: '3月', value: 100, type: '扫码', seriesType: 'line' },
{ name: '4月', value: 40, type: '赋码', seriesType: 'bar' },
{ name: '4月', value: 40, type: '赋码', seriesType: 'bar', itemStyle: { borderRadius: [8, 8, 0, 0] } },
{ name: '4月', value: 120, type: '扫码', seriesType: 'line' },
{ name: '5月', value: 50, type: '赋码', seriesType: 'bar' },
{ name: '5月', value: 50, type: '赋码', seriesType: 'bar', itemStyle: { borderRadius: [8, 8, 0, 0] } },
{ name: '5月', value: 60, type: '扫码', seriesType: 'line' },
],
});

View File

@ -64,7 +64,7 @@
<div class="home-data-contrast">
<span class="tips">同比去年</span>
<span class="value">4684.629</span>
<el-icon style="vertical-align: middle" class="contrast-icon" color="#6cd1f9">
<el-icon style="vertical-align: middle" class="contrast-icon" color="#6beff9">
<TopRight />
</el-icon>
</div>
@ -140,7 +140,7 @@ let rollDataList = reactive([
.home-data-contrast {
.tips {
font-size: 10px;
color: #6cd1f9;
color: #6beff9;
}
.value {
padding: 0 8px;

View File

@ -103,7 +103,7 @@ div {
}
.content {
justify-content: space-around;
margin-top: 50%;
margin-top: 30%;
.content-item {
height: 36px;
display: inline-block;

View File

@ -1,5 +1,5 @@
<template>
<div class="demo roll-list-land-plan" style="height: 90%">
<div class="demo roll-list-land-plan" style="height: 100%" ref="refroll">
<!-- <div class="list-item-header item-warp" :style="{ flex: listKeys.length }">
<template v-for="(h, indexh) in listKeys" :key="indexh">
<div class="item-td" :style="{ width: 'calc(100% / ' + listKeys.length + ')' }">{{ listKeysHeader[h] }}</div>
@ -12,7 +12,6 @@
<div class="item-content">
<div class="label">{{ item.title }}</div>
<div class="val">
<!-- <el-progress :percentage="50" :show-text="false" :indeterminate="false" :stroke-width="12" color="#6cd1f9" :duration="8" /> -->
<div class="progress-warp" :style="{ width: item.percent + 'px' }">
<div class="progress"></div>
</div>
@ -44,7 +43,10 @@ let list = reactive([
{ title: '农机', value: 45 },
]);
let refroll = ref(null);
let datalist = computed(() => {
let maxwidth = refroll.value && refroll.value.clientWidth;
return list.map((m) => {
return { ...m, percent: parseInt(Number(m.value / max.value) * 200) };
});
@ -97,7 +99,7 @@ onMounted(() => {});
.item-td {
padding: 4px 6px;
&.td-title {
color: #6cd1f9 !important;
color: #6beff9 !important;
}
&.zebra-b {
background: #051225 !important;

View File

@ -80,7 +80,7 @@ const classOptions = {
.item-td {
padding: 4px 6px;
&.td-title {
color: #6cd1f9 !important;
color: #6beff9 !important;
}
&.zebra-b {
background: #051225 !important;

View File

@ -62,8 +62,8 @@ const chartsData = reactive({
});
onMounted(() => {
if (plantBreed.valData && plantBreed.length) {
plantBreed.valData.forEach((m, index) => {
if (chartsData.valData && chartsData.valData.length) {
chartsData.valData.forEach((m, index) => {
let num = 100;
m.value = (Number(m.value) + Math.random() + num).toFixed(2);
});

View File

@ -77,7 +77,7 @@ const classOptions = {
.item-td {
padding: 4px 6px;
&.td-title {
color: #6cd1f9 !important;
color: #6beff9 !important;
}
}
}

View File

@ -81,7 +81,7 @@ const classOptions = {
.item-td {
padding: 4px 6px;
&.td-title {
color: #6cd1f9 !important;
color: #6beff9 !important;
}
&.td-warn {

View File

@ -75,7 +75,7 @@ const classOptions = {
.item-td {
padding: 4px 6px;
&.td-title {
color: #6cd1f9 !important;
color: #6beff9 !important;
}
&.zebra-b {
background: #051225 !important;

View File

@ -1,62 +1,119 @@
<template>
<div class="insect-pests-charts">
<custom-echart-bar :chart-data="chartsData.valData" height="100%" :option="chartsData.option" />
<custom-echart-mixin :chart-data="handelData" :option="chartsData.option" height="100%" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted, computed } from 'vue';
let itemStyle = reactive({
itemStyle: { borderRadius: [8, 8, 0, 0] },
});
let legendList = reactive(['蝗虫', '飞蛾', '其他', '蚜虫']);
const chartsData = reactive({
option: {
grid: {
left: '3%',
right: '4%',
bottom: '2%',
top: '18%',
containLabel: true,
},
color: ['#3685fe', '#41b879', '#ffd500', '#e57373'],
title: {
text: ' ',
textStyle: {
color: '#333',
},
},
label: {
color: '#333',
legend: {
show: true,
data: legendList,
left: '0', // 10%
top: '0', //
itemWidth: 15, //
itemHeight: 8, //
textStyle: {
fontSize: 10, //
color: '#fff', //
},
},
barStyle: {
barWidth: 15,
itemStyle: {
borderRadius: [8, 8, 0, 0], //
},
color: {
type: 'linear', // 线
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#45bfe9' },
{ offset: 1, color: '#01589c' },
],
global: false, // false
},
barWidth: 10,
},
legend: {
show: false,
dataZoom: [
{
type: 'slider', //
startValue: 0, //
endValue: 2, //
},
{
type: 'inside', //
startValue: 0,
endValue: 2,
},
],
yAxis: [
{
type: 'value',
name: ' ',
axisLabel: {
formatter: '{value}',
},
splitLine: {
show: true, // 线
lineStyle: {
type: 'dashed', // 线
width: 0.5, // 线
},
},
itemStyle: { fontSize: 8 },
},
],
grid: {
x: '10%',
x2: '15%',
y: '20%',
y2: '20%',
},
},
valData: [
{ value: 80, type: '经销商', name: '耿马镇' },
{ value: 105, type: '经销商', name: '勐撒镇' },
{ value: 100, type: '经销商', name: '勐永镇' },
{ value: 125, type: '经销商', name: '孟定镇' },
{ value: 217, type: '经销商', name: '勐简乡' },
{ value: 200, type: '经销商', name: '贺派乡' },
{ value: 155, type: '经销商', name: '四排山乡' },
{ value: 80, type: '经销商', name: '芒洪乡' },
{ value: 105, type: '经销商', name: '大兴乡' },
{ name: '1月', value: 40, type: '蝗虫', seriesType: 'bar', ...itemStyle },
{ name: '1月', value: 30, type: '飞蛾', seriesType: 'bar', ...itemStyle },
{ name: '1月', value: 100, type: '其他', seriesType: 'bar', ...itemStyle },
{ name: '1月', value: 60, type: '蚜虫', seriesType: 'bar', ...itemStyle },
{ name: '2月', value: 20, type: '蝗虫', seriesType: 'bar', ...itemStyle },
{ name: '2月', value: 20, type: '飞蛾', seriesType: 'bar', ...itemStyle },
{ name: '2月', value: 80, type: '其他', seriesType: 'bar', ...itemStyle },
{ name: '2月', value: 40, type: '蚜虫', seriesType: 'bar', ...itemStyle },
],
});
const randomVal = (num) => {
let list = [];
for (let i = 0; i < legendList.length; i++) {
let addNum = [10, 8, 2, 5];
let val = {
name: num + '月',
value: Number(Math.random() * 100 + addNum[i]).toFixed(2),
seriesType: 'bar',
type: legendList[i],
...itemStyle,
};
list[i] = val;
}
return list;
};
let handelData = computed(() => {
let list = [];
let maxMouth = 12;
for (let i = 0; i < maxMouth; i++) {
let val = randomVal(i + 1);
list = [...list, ...val];
}
list.map((m) => {
return { ...m, value: Number(m.value + Math.random() + 10).toFixed(2) };
});
// console.info('handelData', list);
return list;
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.insect-pests-charts {

View File

@ -0,0 +1,83 @@
<template>
<div class="irrigation-charts">
<custom-echart-water-droplet height="100%" :option="option" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
let percent = ref(0.6);
const option = reactive({
backgroundColor: 'transparent', //
series: [
{
name: '预估量',
type: 'liquidFill',
radius: '42%',
center: ['50%', '50%'],
backgroundStyle: {
color: 'transparent',
},
data: [percent.value, percent.value],
amplitude: 12, //
label: {
//
position: ['50%', '45%'],
formatter: percent.value * 100 + '%', //,
textStyle: {
fontSize: '26px', //,
color: '#fff',
},
},
outline: {
borderDistance: 3,
itemStyle: {
borderWidth: 2,
borderColor: {
type: 'linear',
x: 1,
y: 0,
x2: 0,
y2: 0,
colorStops: [
{
offset: 0,
color: '#007DFF',
},
{
offset: 0.6,
color: 'rgba(45, 67, 114, 1)',
},
{
offset: 1,
color: 'rgba(45, 67, 114, 1)',
},
],
globalCoord: false,
},
},
},
itemStyle: {
color: {
type: 'linear', // 线
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#45bfe9' },
{ offset: 1, color: '#01589c' },
],
global: false, // false
},
},
},
],
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.irrigation-charts {
height: 100%;
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<div class="pathology-charts">
<custom-echart-mixin :chart-data="handelData" :option="chartsData.option" height="100%" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
let itemStyle = reactive({
itemStyle: { borderRadius: [8, 8, 0, 0] },
});
let legendList = reactive(['黄叶病', '霜霉病', '白粉病', '其他']);
const chartsData = reactive({
option: {
color: ['#3685fe', '#41b879', '#ffd500', '#e57373'],
title: {
text: ' ',
textStyle: {
color: '#333',
},
},
legend: {
show: true,
data: legendList,
left: '0', // 10%
top: '0', //
itemWidth: 15, //
itemHeight: 8, //
textStyle: {
fontSize: 10, //
color: '#fff', //
},
},
barStyle: {
barWidth: 10,
},
dataZoom: [
// {
// type: 'slider', //
// startValue: 0, //
// endValue: 2, //
// },
// {
// type: 'inside', //
// startValue: 0,
// endValue: 2,
// },
],
yAxis: [
{
type: 'value',
name: ' ',
axisLabel: {
formatter: '{value}',
},
splitLine: {
show: true, // 线
lineStyle: {
type: 'dashed', // 线
width: 0.5, // 线
},
},
itemStyle: { fontSize: 8 },
},
],
grid: {
x: '10%',
x2: '10%',
y: '20%',
y2: '20%',
},
},
valData: [
// { name: '1', value: 40, type: '', seriesType: 'bar', ...itemStyle, stack: '1' },
// { name: '1', value: 30, type: '', seriesType: 'bar', ...itemStyle, stack: '1' },
// { name: '1', value: 100, type: '', seriesType: 'bar', ...itemStyle, stack: '1' },
// { name: '1', value: 60, type: '', seriesType: 'bar', ...itemStyle, stack: '1' },
// { name: '2', value: 20, type: '', seriesType: 'bar', ...itemStyle, stack: '2' },
// { name: '2', value: 20, type: '', seriesType: 'bar', ...itemStyle, stack: '2' },
// { name: '2', value: 80, type: '', seriesType: 'bar', ...itemStyle, stack: '2' },
// { name: '2', value: 40, type: '', seriesType: 'bar', ...itemStyle, stack: '2' },
],
});
const randomVal = (num) => {
let list = [];
for (let i = 0; i < legendList.length; i++) {
let addNum = [10, 8, 2, 5];
let val = {
name: num + '月',
value: Number(Math.random() * 100 + addNum[i]).toFixed(2),
seriesType: 'bar',
type: legendList[i],
stack: num + '月',
};
let lastVal = {
...val,
...itemStyle,
};
list[i] = i < legendList.length - 1 ? val : lastVal;
}
return list;
};
let handelData = computed(() => {
let list = [];
let maxMouth = 12;
for (let i = 0; i < maxMouth; i++) {
let val = randomVal(i + 1);
list = [...list, ...val];
}
list.map((m, indexm) => {
return { ...m, value: Number(Number(m.value) + Math.random() + indexm).toFixed(0) };
});
console.info('handelData', list);
return list;
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.pathology-charts {
height: 100%;
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<div class="demo water-fertilizer-charts" style="height: 90%">
<div class="list-item-header item-warp" :style="{ flex: listKeys.length }">
<template v-for="(h, indexh) in listKeys" :key="indexh">
<div class="item-td" :style="{ width: 'calc(100% / ' + listKeys.length + ')' }">{{ listKeysHeader[h] }}</div>
</template>
</div>
<vue3ScrollSeamless class="scroll-wrap" :class-options="classOptions" :data-list="list">
<div v-for="(item, index) in list" :key="index" class="list-item">
<div class="list-item-content">
<div class="list-item-boday item-warp" :style="{ flex: listKeys.length }">
<template v-for="(b, indexb) in listKeys" :key="indexb">
<div class="item-td" :class="item.status == 1 ? 'td-title' : 'td-warn'" :style="{ width: 'calc(100% / ' + listKeys.length + ')' }">
<span v-if="b != 'status'">
{{ item[b] }}
</span>
<el-icon v-else>
<Bell></Bell>
</el-icon>
</div>
</template>
</div>
</div>
</div>
</vue3ScrollSeamless>
</div>
<!-- </div> -->
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed, reactive } from 'vue';
import { vue3ScrollSeamless } from 'vue3-scroll-seamless';
const props = defineProps({
// items: {
// type: Array,
// default: () => [],
// },
});
let list = reactive([
{ title: '耿马镇', waitNum: '24', violation: '14', status: 1 },
{ title: '勐撒镇', waitNum: '16', violation: '14', status: 1 },
{ title: '孟定镇', waitNum: '8', violation: '24', status: 0 },
{ title: '孟简镇', waitNum: '9', violation: '8', status: 1 },
{ title: '孟永镇', waitNum: '14', violation: '15', status: 1 },
]);
const listKeys = reactive(['title', 'waitNum', 'violation', 'status']);
const listKeysHeader = reactive({
title: '乡/镇',
waitNum: '灌溉',
violation: '施肥',
status: '预警',
});
const classOptions = {
singleHeight: 48,
};
</script>
<style scoped lang="scss">
.water-fertilizer-charts {
margin-top: 8px;
.scroll-wrap {
height: 80%;
width: 100%;
margin: 4px auto;
overflow: hidden;
}
.list-item-header {
background: #144482;
font-size: 10px;
width: 100%;
.item-td {
padding: 8px 6px;
}
}
.list-item-boday {
background: transparent;
width: 100%;
.item-td {
padding: 4px 6px;
&.td-title {
color: #6beff9 !important;
}
&.td-warn {
color: red !important;
}
}
}
.item-warp {
display: inline-flex;
justify-content: space-around;
.item-td {
display: inline-block;
vertical-align: middle;
text-align: center;
color: #fff;
}
}
.list-item {
// border-bottom: 1px dashed rgba(255, 255, 255, 0.2);
line-height: 18px;
.list-item-content {
display: inline-flex;
width: 100%;
justify-content: space-around;
position: relative;
}
}
}
.demo {
// display: flex;
// align-items: center;
// justify-content: center;
// margin-top: 10px;
}
</style>

View File

@ -13,25 +13,37 @@
</div>
<div class="left-charts-item">
<customBack top-title="昆虫害监测" :top-postion="'left'">
<template #back></template>
<template #back>
<insectPestsCharts></insectPestsCharts>
</template>
</customBack>
</div>
<div class="left-charts-item">
<customBack top-title="病理害监测" :top-postion="'left'">
<template #back></template>
<template #back>
<pathologyCharts></pathologyCharts>
</template>
</customBack>
</div>
</el-col>
<el-col :span="12">
<el-row style="height: 60%">
<el-col :span="24"> 1-1</el-col>
<el-col :span="24"></el-col>
</el-row>
<el-row style="height: 40%">
<el-col :span="12">
<customBack top-title="水肥检测分析" :top-postion="'left'"> <template #back></template> </customBack>
<customBack top-title="水肥检测分析" :top-postion="'left'">
<template #back>
<waterfertilizerCharts></waterfertilizerCharts>
</template>
</customBack>
</el-col>
<el-col :span="12">
<customBack top-title="智慧水肥灌溉" :top-postion="'right'"> <template #back></template> </customBack>
<customBack top-title="智慧水肥灌溉" :top-postion="'right'">
<template #back>
<irrigationCharts></irrigationCharts>
</template>
</customBack>
</el-col>
</el-row>
</el-col>
@ -61,6 +73,10 @@
import baseBg from '@/components/baseBg.vue';
import customBack from '@/components/customBack.vue';
import plantTypeCharts from './components/plantTypeCharts.vue';
import insectPestsCharts from './components/insectPestsCharts';
import pathologyCharts from './components/pathologyCharts.vue';
import waterfertilizerCharts from './components/waterfertilizerCharts.vue';
import irrigationCharts from './components/irrigationCharts.vue';
</script>
<style lang="scss" scoped>
.data-home-index {