This commit is contained in:
李想 2025-04-30 14:54:16 +08:00
commit 6ed519564a
18 changed files with 1560 additions and 125 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -8,7 +8,8 @@
<el-dialog
v-model="isShow"
:title="currentMap.name + dialogTitle"
width="360"
:width="dialogWidth"
:style="{ 'background-image': 'url(' + getAssetsFile(bgImageVal) + ')' }"
:show-close="false"
:before-close="handleClose"
custom-class="map-info-dialog"
@ -22,7 +23,7 @@
</template>
<script setup>
import { isEmpty, getAssetsFile } from '@/utils';
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import geoJsonData from '../components/530926geo.json'; //
const route = useRoute();
@ -37,9 +38,19 @@ const props = defineProps({
return [];
},
},
bgImage: {
type: String,
default: 'images/vsualized/mapopup.png',
},
dialogWidth: {
type: Number,
default: 360,
},
});
var iconUrl = getAssetsFile('images/vsualized/gmmap2.png').href;
let bgImageVal = computed(() => {
return props.bgImage;
});
const isShow = ref(false);
let geoData = geoJsonData;
let mapName = ref('ZJ' + route.name);
@ -174,7 +185,7 @@ const chartsData = reactive({
scale: 3, //
brushType: 'stroke', // 'stroke' 'fill'
},
hoverAnimation: false,
hoverAnimation: true,
},
],
},
@ -183,16 +194,21 @@ const chartsData = reactive({
let currentMap = reactive({});
const mapClick = (data) => {
isShow.value = true;
currentMap = data;
emit('mapclick', currentMap);
if (props.markerData.length && props.markerData.length > 0) {
if (data.seriesType == 'effectScatter') {
isShow.value = true;
currentMap = data;
emit('mapclick', currentMap);
}
} else {
isShow.value = true;
currentMap = data;
emit('mapclick', currentMap);
}
};
const handleClose = () => {
isShow.value = false;
};
onMounted(() => {
console.info('iconUrl', iconUrl);
});
const emit = defineEmits(['mapclick']);
</script>
@ -203,7 +219,10 @@ div {
::v-deep() {
.el-dialog {
background: url('@/assets/images/vsualized/mapopup.png') no-repeat left top;
background: url(iconUrl) no-repeat left top;
background-repeat: no-repeat;
background-size: cover;
border-radius: 8px !important;
min-height: 200px;
max-height: 500px;
overflow-y: auto;

View File

@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash';
import { useEcharts } from '@/hooks/useEcharts';
export default {
name: 'customEchartWaterDroplet',
name: 'CustomEchartWaterDroplet',
props: {
chartData: {
type: Array,

View File

@ -54,14 +54,14 @@ const props = defineProps({
type: Array,
default() {
return [
{ label: '首页', value: 'home' },
{ label: '土地资源', value: 'land' },
{ label: '投入品监管', value: 'inputs' },
{ label: '产出品管理', value: 'entities' },
{ label: '首页', value: '/v2/home' },
{ label: '土地资源', value: '/v2/land' },
{ label: '投入品监管', value: '/v2/inputs' },
{ label: '产出品管理', value: '/v2/entities' },
// { label: '', value: 'plant' },
// { label: '', value: 'breed' },
{ label: '生产经营主体', value: 'business' },
{ label: '农产品溯源', value: 'trace' },
{ label: '生产经营主体', value: '/v2/business' },
{ label: '农产品溯源', value: '/v2/trace' },
// { label: '', value: 'early' },
];
},
@ -122,7 +122,7 @@ function handleTitleBtn(t = -1) {
function handleTitleClick(val) {
activeTitle.value = val;
// emit('changeTitle', val);
router.push({ name: val });
router.push({ path: val });
}
</script>

View File

@ -171,10 +171,14 @@ const handleCommand = (data) => {
top: 50%;
transform: translateY(-50%);
z-index: 2;
border: 1px solid $color-custom-main;
border-radius: 4px;
padding: 6px;
text-align: center;
white-space: nowrap;
.el-dropdown {
border: 1px solid $color-custom-main;
padding: 6px;
border-radius: 4px;
}
}
}
</style>

View File

@ -20,13 +20,13 @@ export default {
meta: { title: '土地资源', icon: '' },
},
{
path: 'inputs',
path: '/v2/inputs',
name: 'inputs',
component: () => import('@/views/inputs/index.vue'),
meta: { title: '投入品监管', icon: '' },
},
{
path: 'entities',
path: '/v2/entities',
name: 'entities',
component: () => import('@/views/entities/index.vue'),
meta: { title: '产出品管理', icon: '' },
@ -44,7 +44,7 @@ export default {
// meta: { title: '', icon: '' },
// },
{
path: 'business',
path: '/v2/business',
name: 'business',
component: () => import('@/views/business/index.vue'),
meta: { title: '生产经营主体', icon: '' },

View File

@ -0,0 +1,109 @@
<template>
<div class="rank">
<custom-rank-List :chart-config="state" />
</div>
</template>
<script setup>
import { reactive, watch } from 'vue';
import { isEmpty } from '@/utils';
const props = defineProps({
data: {
type: Array,
default: () => [],
},
});
const state = reactive({
attr: { w: '100%', h: 240 },
option: {
//
dataset: [],
type: 'column',
rowNum: 6,
isAnimation: true,
waitTime: 5,
unit: '万元',
sort: true,
height: 12,
color: 'linear-gradient(90deg,rgba(53,208,192,0.00), #35d0c0)',
textColor: '#fff',
borderRadius: '12px',
carousel: 'single',
indexPrefix: 'TOP',
indexFontSize: 12,
leftFontSize: 14,
rightFontSize: 14,
valueFormatter: (item) => {
return item.value;
},
},
});
watch(
() => props.data,
(val) => {
if (!isEmpty(val)) {
state.option.dataset = val;
}
},
{
deep: true,
immediate: true,
}
);
</script>
<style scoped lang="scss">
.rank {
padding: 10px 20px;
&:deep(.row-item) {
.ranking-info {
color: #35d0c0 !important;
font-family: 'DingTalk JinBuTi, DingTalk JinBuTi-Regular';
font-weight: 700;
}
.inside-column {
border-radius: 8px;
}
}
&:deep(.row-item-1) {
.ranking-info {
color: #fe7f03 !important;
}
.ranking-value {
color: #fe7f03 !important;
}
.inside-column {
background: linear-gradient(90deg, rgba(254, 127, 3, 0), #fe7f03) !important;
}
}
&:deep(.row-item-2) {
.ranking-info {
color: #fef906 !important;
}
.ranking-value {
color: #fef906 !important;
}
.inside-column {
background: linear-gradient(90deg, rgba(254, 249, 6, 0), #fef906) !important;
}
}
&:deep(.row-item-3) {
.ranking-info {
color: #02fd94 !important;
}
.ranking-value {
color: #02fd94 !important;
}
.inside-column {
background: linear-gradient(90deg, rgba(2, 253, 148, 0), #02fd94) !important;
}
}
}
</style>

View File

@ -0,0 +1,210 @@
<template>
<div class="business">
<div class="business-left">
<custom-echart-water-droplet width="100%" height="100%" :option="state.option" />
<div class="business-title">证件齐全率</div>
</div>
<div class="business-right">
<div class="business-title">临期预警</div>
<ul class="business-info">
<li class="success">
<b>正常</b>
<span>253</span>
</li>
<li class="warning">
<b>临期</b>
<span>5</span>
</li>
<li class="danger">
<b>已过期</b>
<span>0</span>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue';
import { isEmpty } from '@/utils';
const props = defineProps({
data: {
type: Object,
default: () => {},
},
});
const state = reactive({
option: {
backgroundColor: 'transparent', //
series: [
{
name: '',
type: 'liquidFill',
radius: '80%',
center: ['50%', '50%'],
backgroundStyle: {
color: 'transparent',
},
data: [],
amplitude: 12, //
label: {
position: ['50%', '45%'],
// formatter: 0.3998 * 100 + '%', //,
textStyle: {
fontSize: '20px', //,
color: '#fff',
},
},
outline: {
borderDistance: 2,
itemStyle: {
borderWidth: 2,
borderColor: {
type: 'linear',
x: 1,
y: 0,
x2: 0,
y2: 0,
colorStops: [
{
offset: 0,
color: 'rgba(71, 202, 219, 0.5)',
},
{
offset: 0.6,
color: 'rgba(45, 209, 185, 0.5)',
},
{
offset: 1,
color: 'rgba(13, 204, 163, 0.5)',
},
],
globalCoord: false,
},
},
},
itemStyle: {
color: {
type: 'linear', // 线
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(13, 204, 163, 0.6)' },
{ offset: 1, color: 'rgba(71, 202, 219, 1)' },
],
global: false, // false
},
},
},
],
},
});
watch(
() => props.data,
(val) => {
if (!isEmpty(val)) {
state.option.series[0].data = [0, val.percent];
state.option.series[0].label.formatter = val.percent * 100 + '%';
}
},
{
deep: true,
immediate: true,
}
);
</script>
<style lang="scss" scoped>
.business {
width: 100%;
height: 100%;
@include flex-row();
&-title {
width: 160px;
margin: 0 auto;
height: 32px;
line-height: 32px;
font-size: 16px;
font-weight: 400;
text-align: center;
color: #ffffff;
text-shadow: 2px 0px 10px 0px #01eeff;
background: url('@/assets/images/business/bg_title.png');
background-repeat: no-repeat;
background-size: contain;
text-shadow:
0 0 10px #01eeff,
0 0 20px #01eeff,
0 0 30px #01eeff,
0 0 40px #01eeff;
}
&-left,
&-right {
@include flex-column();
flex: 1;
}
&-info {
width: 100%;
@include flex-column();
text-align: center;
li {
@include flex-column();
font-size: 16px;
font-weight: 400;
color: #fff;
margin-top: 16px;
b {
margin-bottom: 10px;
&::before {
display: inline-block;
content: '';
width: 12px;
height: 12px;
border-radius: 6px;
margin-right: 10px;
}
}
&.success {
span {
color: #02fd94;
}
b {
&::before {
background-color: #02fd94;
}
}
}
&.warning {
span {
color: #fef906;
}
b {
&::before {
background-color: #fef906;
}
}
}
&.danger {
span {
color: #fc0003;
}
b {
&::before {
background-color: #fc0003;
}
}
}
}
}
}
</style>

View File

@ -1,19 +1,89 @@
<template>
<centerMap :dialog-title="'投入品'" :marker-data="markerData" @mapclick="doMapclick">
<centerMap
:dialog-title="'经营主体'"
:marker-data="markerData"
:bg-image="'images/vsualized/mapopup1.png'"
:dialog-width="240"
@mapclick="doMapclick"
>
<template #header>
<div class="land-map-pop-header">
<div class="title">{{ currentRegion && currentRegion.name ? currentRegion.name : '投入品' }}</div>
<div class="buiness-map-pop-header">
<div class="title">{{ currentRegion && currentRegion.name ? currentRegion.name : '经营主体' }}</div>
</div>
</template>
<template #dialogContent>
<!-- <div class="land-map-pop-content">
<div v-for="(n, index) in list" :key="index" class="list-item">
<div class="title">
<span class="title-val"> {{ n.title }}</span>
</div>
<div class="value">{{ n.value }}{{ n.unit }}</div>
<div class="buiness-map-pop-content">
<div class="addr">
<el-icon :size="'18px'" :color="'#fff'">
<LocationFilled />
</el-icon>
{{ testInfo.addr || ' --' }}
</div>
</div> -->
<div class="tips-warp">
<div class="label">负责人</div>
<div class="val">{{ testInfo.user || '--' }}</div>
</div>
<div class="tips-warp">
<div class="label">联系方式</div>
<div class="val">{{ testInfo.tel || '--' }}</div>
</div>
<div class="tips-warp">
<div class="label">注册资本</div>
<div class="val">{{ testInfo.capital || '--' }}</div>
</div>
<div class="tips-warp">
<div class="label">成立时间</div>
<div class="val">{{ testInfo.time || '--' }}</div>
</div>
<div class="tips-warp">
<div class="label">信用等级</div>
<div class="val">{{ testInfo.credit || '--' }}</div>
</div>
<div class="img-list">
<div class="img-item">
<el-image
style="width: 80px; height: 60px; cursor: pointer"
:preview-src-list="
testInfo.imglist.map((m) => {
return getAssetsFile(m);
})
"
:src="getAssetsFile(testInfo.imglist[0])"
fit="cover"
lazy
/>
<div class="img-name">营业执照</div>
</div>
<div class="img-item">
<el-image
style="width: 80px; height: 60px; cursor: pointer"
:preview-src-list="
testInfo.imglist.map((m) => {
return getAssetsFile(m);
})
"
:src="getAssetsFile(testInfo.imglist[1])"
fit="cover"
lazy
/>
<div class="img-name">经营许可证</div>
</div>
<div class="img-item">
<el-image
style="width: 80px; height: 60px; cursor: pointer"
:preview-src-list="
testInfo.imglist.map((m) => {
return getAssetsFile(m);
})
"
:src="getAssetsFile(testInfo.imglist[2])"
fit="cover"
lazy
/>
<div class="img-name">其他整数</div>
</div>
</div>
</div>
</template>
</centerMap>
</template>
@ -26,37 +96,57 @@ const list = reactive([
{ title: '年总产值', value: 3.49, color: '#01FEFD', unit: '亿元' },
{ title: '年人均收入', value: 6.98, color: '#FEF906', unit: '万元' },
]);
let testInfo = reactive({});
let currentRegion = ref(null);
const doMapclick = (data) => {
currentRegion.value = data;
if (data.name == '永星食品加工厂') {
testInfo = {
addr: '云南省临沧市耿马傣族佤族自治县孟定镇101号',
user: '张强',
tel: '15331683325',
capital: '500万',
time: '2018年12月1日',
credit: 'AA',
imglist: ['images/vsualized/home1.png', 'images/vsualized/home1.png', 'images/vsualized/home1.png'],
};
}
if (data.name == '欣欣种源企业') {
testInfo = {
addr: '云南省临沧市耿马傣族佤族自治耿马镇102号',
user: '李欣',
tel: '13713575206',
capital: '600万',
time: '2020年10月1日',
credit: 'AA',
imglist: ['images/vsualized/home1.png', 'images/vsualized/home1.png', 'images/vsualized/home1.png'],
};
}
};
let markerData = reactive([
[
//
{
name: '永星食品加工厂',
value: [99.081993, 23.554045, 150], // , ,
symbol: 'image://' + getAssetsFile('images/vsualized/marker.png'),
itemStyle: {
color: '#4bffb4', //
},
//
{
name: '永星食品加工厂',
value: [99.081993, 23.524045, 150], // , ,
symbol: 'image://' + getAssetsFile('images/vsualized/marker.png'),
itemStyle: {
color: '#4bffb4', //
},
{
name: '耿马镇',
value: [99.402267, 23.538889, 150], // , ,
symbol: 'image://' + getAssetsFile('images/vsualized/marker.png'),
itemStyle: {
color: '#4bffb4', //
},
},
{
name: '欣欣种源企业',
value: [99.402267, 23.538889, 150], // , ,
symbol: 'image://' + getAssetsFile('images/vsualized/marker.png'),
itemStyle: {
color: '#4bffb4', //
},
],
},
]);
</script>
<style lang="scss" scoped>
.land-map-pop-header {
.buiness-map-pop-header {
display: inline-flex;
justify-content: space-between;
width: 100%;
@ -74,45 +164,43 @@ let markerData = reactive([
font-size: 14px;
}
}
.land-map-pop-content {
.buiness-map-pop-content {
width: 100%;
gap: 10px;
display: inline-flex;
justify-content: flex-start;
flex-wrap: wrap;
.list-item {
width: calc((100% - 10px) / 1);
.addr {
width: 100%;
display: inline-flex;
color: #fff;
text-align: left;
}
.tips-warp {
width: 100%;
display: inline-flex;
justify-content: space-between;
padding: 6px 0;
.title {
display: inline-flex;
justify-content: flex-start;
.before {
display: inline-flex;
flex-direction: column;
justify-content: center;
}
.b-content {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.before,
.title-val {
display: inline-block;
vertical-align: middle;
padding: 0 5px 0 2px;
color: $color-custom-main;
}
text-align: left;
margin: 6px 0;
.label {
color: #fef906;
}
.value {
display: inline-block;
text-align: right;
color: $color-white;
font-size: 12px;
.val {
color: #fff;
}
}
.img-list {
width: 100%;
display: inline-flex;
justify-content: space-between;
gap: 10;
flex-wrap: wrap;
.img-item {
width: calc((100% - 10px) / 2);
margin-bottom: 8px;
.el-image {
background: transparent !important;
}
.img-name {
color: #fff;
padding: 6px 0;
}
}
}
}

View File

@ -0,0 +1,302 @@
<template>
<custom-echart-pie-3d :chart-data="state.data" height="100%" :option="state.option" />
</template>
<script setup>
import { reactive, watch } from 'vue';
import { isEmpty } from '@/utils';
const props = defineProps({
data: {
type: Array,
default: () => [],
},
});
const state = reactive({
option: {},
data: [],
text: '',
});
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
//
let midRatio = (startRatio + endRatio) / 2;
let startRadian = startRatio * Math.PI * 2;
let endRadian = endRatio * Math.PI * 2;
let midRadian = midRatio * Math.PI * 2;
//
if (startRatio === 0 && endRatio === 1) {
isSelected = false;
}
// / k 1/3
k = typeof k !== 'undefined' ? k : 1 / 3;
// x y 0
let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
// 1
let hoverRate = isHovered ? 1.05 : 1;
//
return {
u: {
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32,
},
v: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 20,
},
x: function (u, v) {
if (u < startRadian) {
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
},
y: function (u, v) {
if (u < startRadian) {
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
},
z: function (u, v) {
if (u < -Math.PI * 0.5) {
return Math.sin(u);
}
if (u > Math.PI * 2.5) {
return Math.sin(u) * h * 0.1;
}
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
},
};
}
function fomatFloat(num, n) {
var f = parseFloat(num);
if (isNaN(f)) {
return false;
}
f = Math.round(num * Math.pow(10, n)) / Math.pow(10, n); // n
var s = f.toString();
var rs = s.indexOf('.');
if (rs < 0) {
rs = s.length;
s += '.';
}
while (s.length <= rs + n) {
s += '0';
}
return s;
}
function getHeight3D(series, height) {
series.sort((a, b) => {
return b.pieData.value - a.pieData.value;
});
return (height * 20) / series[0].pieData.value;
}
function getPie3D(pieData, internalDiameterRatio) {
let series = [];
let sumValue = 0;
let startValue = 0;
let endValue = 0;
let legendData = [];
let legendBfb = [];
let k = 1 - internalDiameterRatio;
pieData.sort((a, b) => {
return b.value - a.value;
});
for (let i = 0; i < pieData.length; i++) {
sumValue += pieData[i].value;
let seriesItem = {
//
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
type: 'surface',
//
parametric: true,
//线
wireframe: {
show: false,
},
pieData: pieData[i],
pieStatus: {
selected: false,
hovered: false,
k: k,
},
//()
// center: ['50%', '100%']
};
//
if (typeof pieData[i].itemStyle != 'undefined') {
let itemStyle = {};
typeof pieData[i].itemStyle.color != 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
typeof pieData[i].itemStyle.opacity != 'undefined' ? (itemStyle.opacity = pieData[i].itemStyle.opacity) : null;
seriesItem.itemStyle = itemStyle;
}
series.push(seriesItem);
}
legendData = [];
legendBfb = [];
for (let i = 0; i < series.length; i++) {
endValue = startValue + series[i].pieData.value * 1;
series[i].pieData.startRatio = startValue / sumValue;
series[i].pieData.endRatio = endValue / sumValue;
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
false,
false,
k,
series[i].pieData.value
);
startValue = endValue;
let bfb = fomatFloat(series[i].pieData.value / sumValue, 4);
legendData.push({
name: series[i].name,
value: bfb,
});
legendBfb.push({
name: series[i].name,
value: bfb,
});
}
let boxHeight = getHeight3D(series, 15);
let option = {
legendSuffix: '%',
legend: {
data: legendData,
// color: ['#8FD7FC', '#466BE7', '#F4BB29', '#49C384', '#8FD7FC', '#466BE7', '#F4BB29', '#49C384'],
orient: 'vertical',
right: 10,
top: 'center',
itemGap: 20,
show: true,
icon: 'rect',
itemHeight: 16,
itemWidth: 16,
textStyle: {
fontSize: 14,
color: '#B8DDFF',
lineHeight: 20,
},
formatter: (name) => {
if (state.data.length) {
const item = state.data.filter((item) => item.name === name)[0];
return `${name} ${item.pieData.value}${state.option.legendSuffix ?? ''}`;
}
},
},
tooltip: {
backgroundColor: 'rgba(18, 55, 85, 0.8);',
borderColor: '#35d0c0',
color: '#fff',
position: function (point, params, dom, rect, size) {
var x = point[0];
var y = point[1];
var viewWidth = size.viewSize[0];
var viewHeight = size.viewSize[1];
var boxWidth = size.contentSize[0];
var boxHeight = size.contentSize[1];
// tooltip 使
if (x + boxWidth > viewWidth) {
x = x - boxWidth;
}
if (y + boxHeight > viewHeight) {
y = y - boxHeight;
}
// tooltip
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
return [x, y];
},
formatter: (params) => {
if (params.seriesName !== 'mouseoutSeries') {
return `
<span style="color:#FFF">
${params.seriesName}<br/>
<span style="display:inline-block;
margin-right:5px;
border-radius:10px;
width:10px;
height:10px;
background-color:${params.color};"></span>
${option.series[params.seriesIndex].pieData.value}
</span>
`;
}
return '';
},
},
xAxis3D: {
min: -1,
max: 1,
},
yAxis3D: {
min: -1,
max: 1,
},
zAxis3D: {
min: -1,
max: 1,
},
grid3D: {
show: false,
boxHeight: boxHeight,
left: '-20%',
top: -20,
viewControl: {
alpha: 30, //( )
distance: 150, //zoom()
rotateSensitivity: 1, //0
zoomSensitivity: 0, //0
panSensitivity: 0, //0
autoRotate: true, //
},
},
series: series,
};
return option;
}
const initData = (pieData = []) => {
const option = getPie3D(pieData, 0.8);
state.option = option;
state.data = option.series;
};
watch(
() => props.data,
(val) => {
if (!isEmpty(val)) {
const pieData = val.map((row) => {
return {
name: row.name,
value: row.value,
};
});
initData(pieData);
}
},
{
deep: true,
immediate: true,
}
);
</script>

View File

@ -0,0 +1,274 @@
<template>
<custom-echart-pie-3d :chart-data="state.data" height="100%" :option="state.option" @click="handleClick" />
</template>
<script setup>
import { reactive, watch } from 'vue';
import { isEmpty } from '@/utils';
const props = defineProps({
data: {
type: Array,
default: () => [],
},
});
const state = reactive({
option: {},
data: [],
text: '',
});
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
//
let midRatio = (startRatio + endRatio) / 2;
let startRadian = startRatio * Math.PI * 2;
let endRadian = endRatio * Math.PI * 2;
let midRadian = midRatio * Math.PI * 2;
//
if (startRatio === 0 && endRatio === 1) {
isSelected = false;
}
// / k 1/3
k = typeof k !== 'undefined' ? k : 1 / 3;
// x y 0
let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
// 1
let hoverRate = isHovered ? 1.05 : 1;
//
return {
u: {
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32,
},
v: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 20,
},
x: function (u, v) {
if (u < startRadian) {
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
},
y: function (u, v) {
if (u < startRadian) {
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
},
z: function (u, v) {
if (u < -Math.PI * 0.5) {
return Math.sin(u);
}
if (u > Math.PI * 2.5) {
return Math.sin(u) * h * 0.1;
}
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
},
};
}
function fomatFloat(num, n) {
var f = parseFloat(num);
if (isNaN(f)) {
return false;
}
f = Math.round(num * Math.pow(10, n)) / Math.pow(10, n); // n
var s = f.toString();
var rs = s.indexOf('.');
if (rs < 0) {
rs = s.length;
s += '.';
}
while (s.length <= rs + n) {
s += '0';
}
return s;
}
function getHeight3D(series, height) {
series.sort((a, b) => {
return b.pieData.value - a.pieData.value;
});
return (height * 20) / series[0].pieData.value;
}
function getPie3D(pieData, internalDiameterRatio) {
let series = [];
let sumValue = 0;
let startValue = 0;
let endValue = 0;
let legendData = [];
let legendBfb = [];
let k = 1 - internalDiameterRatio;
pieData.sort((a, b) => {
return b.value - a.value;
});
for (let i = 0; i < pieData.length; i++) {
sumValue += pieData[i].value;
let seriesItem = {
//
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
type: 'surface',
//
parametric: true,
//线
wireframe: {
show: false,
},
pieData: pieData[i],
pieStatus: {
selected: false,
hovered: false,
k: k,
},
//()
// center: ['50%', '100%']
};
//
if (typeof pieData[i].itemStyle != 'undefined') {
let itemStyle = {};
typeof pieData[i].itemStyle.color != 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
typeof pieData[i].itemStyle.opacity != 'undefined' ? (itemStyle.opacity = pieData[i].itemStyle.opacity) : null;
seriesItem.itemStyle = itemStyle;
}
series.push(seriesItem);
}
legendData = [];
legendBfb = [];
for (let i = 0; i < series.length; i++) {
endValue = startValue + series[i].pieData.value * 1;
series[i].pieData.startRatio = startValue / sumValue;
series[i].pieData.endRatio = endValue / sumValue;
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
false,
false,
k,
series[i].pieData.value
);
startValue = endValue;
let bfb = fomatFloat(series[i].pieData.value / sumValue, 4);
legendData.push({
name: series[i].name,
value: bfb,
});
legendBfb.push({
name: series[i].name,
value: bfb,
});
}
let boxHeight = getHeight3D(series, 15);
let option = {
legend: {
data: legendData,
color: ['#8FD7FC', '#466BE7', '#F4BB29', '#49C384', '#8FD7FC', '#466BE7', '#F4BB29', '#49C384'],
orient: 'vertical',
right: 10,
top: 'center',
itemGap: 20,
show: true,
icon: 'rect',
itemHeight: 16,
itemWidth: 16,
textStyle: {
fontSize: 14,
color: '#B8DDFF',
lineHeight: 20,
},
},
title: {
text: '',
x: '35%',
y: '30%',
textStyle: {
rich: {
a: {
fontSize: 20,
color: '#fff',
},
c: {
fontSize: 12,
color: '#fff',
padding: [15, 0],
},
},
},
},
xAxis3D: {
min: -1,
max: 1,
},
yAxis3D: {
min: -1,
max: 1,
},
zAxis3D: {
min: -1,
max: 1,
},
grid3D: {
show: false,
boxHeight: boxHeight,
left: '-10%',
top: -20,
viewControl: {
alpha: 55, //( )
distance: 150, //zoom()
rotateSensitivity: 1, //0
zoomSensitivity: 0, //0
panSensitivity: 0, //0
autoRotate: true, //
},
},
series: series,
};
return option;
}
const initData = (pieData = []) => {
const option = getPie3D(pieData, 0.8);
const { name, value } = option.series[0].pieData;
option.title.text = `{a|${value}%}{c|\n${name}}`;
state.option = option;
state.data = option.series;
};
const handleClick = (params) => {
const findItem = state.data.find((el) => el.name === params.seriesName);
state.option.title.text = `{a|${findItem?.pieData?.value}%}{c|\n${params.seriesName}}`;
};
watch(
() => props.data,
(val) => {
if (!isEmpty(val)) {
const pieData = val.map((row) => {
return {
name: row.name,
value: row.value,
};
});
initData(pieData);
}
},
{
deep: true,
immediate: true,
}
);
</script>

View File

@ -0,0 +1,77 @@
<template>
<custom-echart-line :chart-data="state.data" height="100%" :option="state.option" />
</template>
<script setup>
import { reactive, watch } from 'vue';
import { isEmpty } from '@/utils';
const props = defineProps({
data: {
type: Array,
default: () => [],
},
});
const state = reactive({
option: {
color: ['#35D0C0'],
grid: {
left: '5%',
right: '5%',
bottom: '5%',
top: '10%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
backgroundColor: 'rgba(18, 55, 85, 0.8);',
borderColor: '#35d0c0',
formatter: (data) => {
const params = data[0];
let str = `<div class="custom-echarts-tips">
<span>${params.name}</span><br/>
<span>${params.marker} ${params.data} 万亩</span>
</div>`;
return str;
},
},
xAxis: {
type: 'category',
// name: '',
axisTick: {
show: false,
alignWithLabel: false,
interval: 'auto',
inside: false,
length: 5,
lineStyle: {
type: 'solid',
width: 1,
color: 'rgba(28, 158, 222, 1)',
},
},
},
yAxis: {
type: 'value',
// name: '',
},
},
data: [],
});
watch(
() => props.data,
(val) => {
if (!isEmpty(val)) {
state.data = val;
}
},
{
deep: true,
immediate: true,
}
);
</script>

View File

@ -0,0 +1,139 @@
<template>
<custom-echart-bar :chart-data="state.data" height="100%" :option="state.option" />
</template>
<script setup>
import { reactive, watch } from 'vue';
import { isEmpty, sleep } from '@/utils';
const props = defineProps({
data: {
type: Array,
default: () => [],
},
query: {
type: String,
default: '',
},
});
const state = reactive({
option: {
grid: {
left: '5%',
right: '5%',
bottom: '5%',
top: '10%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
backgroundColor: 'rgba(18, 55, 85, 0.8);',
borderColor: '#35d0c0',
formatter: (data) => {
const params = data[0];
let str = `<div class="custom-echarts-tips">
<span>${params.name}</span><br/>
<span>${params.marker} ${params.data} </span>
</div>`;
return str;
},
},
barStyle: {
barWidth: 15,
itemStyle: {
borderRadius: [8, 8, 0, 0],
},
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#35D0C0' },
{ offset: 1, color: '#35D0C0' },
],
global: false,
},
},
xAxis: {
type: 'category',
// name: '',
axisTick: {
show: false,
alignWithLabel: false,
interval: 'auto',
inside: false,
length: 5,
lineStyle: {
type: 'solid',
width: 1,
color: 'rgba(28, 158, 222, 1)',
},
},
},
yAxis: {
type: 'value',
// name: '()',
},
},
data: [],
});
const loadData = async (code = '') => {
state.loading = true;
// GetInputsInfo()
// .then((res) => {
// if (res.code === 200) {
// state.data = res.data;
// }
// })
// .catch((err) => {
// app.$message.error(err.msg);
// });
await sleep(500);
state.data = [
{ name: '耿马镇', value: 70 },
{ name: '勐撒镇', value: 203 },
{ name: '勐永镇', value: 54 },
{ name: '孟定镇', value: 35 },
{ name: '勐简乡', value: 95 },
{ name: '贺派乡', value: 62 },
{ name: '四排山乡', value: 84 },
{ name: '芒洪乡', value: 82 },
{ name: '大兴乡', value: 64 },
{ name: '信阳', value: 55 },
{ name: '新乡', value: 32 },
{ name: '大同', value: 51 },
];
};
watch(
() => props.data,
(val) => {
if (!isEmpty(val)) {
state.data = val;
}
},
{
deep: true,
immediate: true,
}
);
watch(
() => props.query,
(val) => {
if (!isEmpty(val)) {
loadData(val);
}
},
{
deep: true,
immediate: true,
}
);
</script>

View File

@ -2,18 +2,40 @@
<el-row class="data-home-index">
<el-col :span="6" class="left-charts">
<div class="left-charts-item">
<customBack top-title=" " :top-postion="'left'">
<template #back> </template>
<customBack top-title="生产经营主体数量统计" :top-postion="'left'">
<template #back>
<businessOne :data="state.data.one" />
</template>
</customBack>
</div>
<div class="left-charts-item">
<customBack top-title=" " :top-postion="'left'">
<template #back> </template>
<customBack
top-title="生产经营主体数量"
:top-postion="'left'"
:down-title="'农资企业'"
:label-field="'label'"
:value-field="'value'"
:down-width="'100px'"
:options="[
{ value: 1, label: '农企/合作社' },
{ value: 2, label: '农资企业' },
{ value: 3, label: '种源企业' },
{ value: 4, label: '生产加工企业' },
{ value: 5, label: '农户' },
]"
:is-down="true"
@command="handleCommand"
>
<template #back>
<businessTwo :data="state.data.two" :query="state.queryCode" />
</template>
</customBack>
</div>
<div class="left-charts-item">
<customBack top-title=" " :top-postion="'left'">
<template #back> </template>
<customBack top-title="近三年用地规模曲线" :top-postion="'left'">
<template #back>
<businessThere :data="state.data.there" />
</template>
</customBack>
</div>
</el-col>
@ -22,18 +44,24 @@
</el-col>
<el-col :span="6" class="right-charts">
<div class="right-charts-item">
<customBack top-title=" " :top-postion="'right'">
<template #back> </template>
<customBack top-title="证件状态看板" :top-postion="'right'">
<template #back>
<businessFour :data="state.data.four" />
</template>
</customBack>
</div>
<div class="right-charts-item">
<customBack top-title=" " :top-postion="'right'">
<template #back> </template>
<customBack top-title="生产经营主体年收益排行" :top-postion="'right'">
<template #back>
<businessFive :data="state.data.five" />
</template>
</customBack>
</div>
<div class="right-charts-item">
<customBack top-title=" " :top-postion="'right'">
<template #back> </template>
<customBack top-title="生产经营主体用地情况" :top-postion="'right'">
<template #back>
<businessSix :data="state.data.six" />
</template>
</customBack>
</div>
</el-col>
@ -42,10 +70,114 @@
<script setup>
import { nextTick, reactive, ref } from 'vue';
import { useApp } from '@/hooks';
import { sleep } from '@/utils';
import businessMap from './components/businessMap.vue';
import businessOne from './components/businessOne.vue';
import businessTwo from './components/businessTwo.vue';
import businessThere from './components/businessThere.vue';
import businessFour from './components/businessFour.vue';
import businessFive from './components/businessFive.vue';
import businessSix from './components/businessSix.vue';
const app = useApp();
const fiveRef = ref(null);
const state = reactive({
loading: false,
data: {},
queryCode: '',
});
//
const loadData = async () => {
state.loading = true;
// GetInputsInfo()
// .then((res) => {
// if (res.code === 200) {
// state.data = res.data;
// }
// })
// .catch((err) => {
// app.$message.error(err.msg);
// });
await sleep(500);
state.data = {
one: [
{ value: 13.8, name: '农企/合作社' },
{ value: 23.8, name: '农资企业' },
{ value: 24.1, name: '种源企业' },
{ value: 29.8, name: '生产加工企业' },
{ value: 8.5, name: '农户' },
],
two: [
{ name: '耿马镇', value: 870 },
{ name: '勐撒镇', value: 603 },
{ name: '勐永镇', value: 854 },
{ name: '孟定镇', value: 635 },
{ name: '勐简乡', value: 795 },
{ name: '贺派乡', value: 662 },
{ name: '四排山乡', value: 584 },
{ name: '芒洪乡', value: 682 },
{ name: '大兴乡', value: 664 },
{ name: '信阳', value: 555 },
{ name: '新乡', value: 532 },
{ name: '大同', value: 511 },
],
there: [
{ value: 66, name: '2021' },
{ value: 100, name: '2022' },
{ value: 50, name: '2023' },
{ value: 150, name: '2024' },
{ value: 80, name: '2025' },
],
four: {
percent: 0.3998,
success: 253,
warning: 5,
danger: 0,
},
five: [
{ name: '耿马镇', value: 87.84 },
{ name: '勐撒镇', value: 60.7 },
{ name: '勐永镇', value: 85.84 },
{ name: '孟定镇', value: 63.25 },
{ name: '勐简乡', value: 79.45 },
{ name: '贺派乡', value: 66.22 },
{ name: '四排山乡', value: 58.34 },
{ name: '芒洪乡', value: 68.12 },
{ name: '大兴乡', value: 66.34 },
{ name: '信阳', value: 55.75 },
{ name: '新乡', value: 53.32 },
{ name: '大同', value: 51.11 },
],
six: [
{
name: '农企/合作社',
value: 25,
},
{
name: '农资企业',
value: 40,
},
{
name: '种源企业',
value: 24,
},
{
name: '生产加工企业',
value: 32,
},
{
name: '农户',
value: 32,
},
],
};
state.loading = false;
};
loadData();
const handleCommand = (data) => {
state.queryCode = data.value;
};
</script>
<style lang="scss" scoped>
.data-home-index {

View File

@ -54,8 +54,8 @@ const datalist = reactive([
{ label: '农药使用(吨)', value: 5000, icon: 'pesticide.png' },
{ label: '化肥使用(吨)', value: 9000, icon: 'fertilizer.png' },
{ label: '饲料(吨)', value: 88943, icon: 'feeduse.png' },
{ label: '兽药(吨)', value: 10, icon: 'animalm.png' },
{ label: '农机使用(台)', value: 8000, icon: 'farmuse.png' },
//{ label: '()', value: 10, icon: 'animalm.png' },
//{ label: '使()', value: 8000, icon: 'farmuse.png' },
]);
onMounted(() => {

View File

@ -26,6 +26,7 @@
<el-col :span="12">
<inputsMap></inputsMap>
</el-col>
<el-col :span="6" class="right-charts">
<div class="right-charts-item">
<customBack top-title="生产主体统计" :top-postion="'right'">

View File

@ -2,13 +2,106 @@
<custom-echart-bar :chart-data="state.data" height="100%" :option="state.option" />
</template>
<script setup>
import { reactive, watch } from 'vue';
import { isEmpty } from '@/utils';
const props = defineProps({
data: {
type: Array,
default: () => [],
import { ref, reactive, onMounted } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
let valData = reactive([
{ value: 658, name: '生产溯源码' },
{ value: 526, name: '有效溯源码' },
]);
const chartsData = reactive({
option: {
backgroundColor: 'transparent',
title: {
zlevel: 0,
text: 806,
subtext: '设备管理',
top: '38%',
left: '80%',
textAlign: 'center',
textStyle: {
color: '#fff',
fontSize: 14,
},
subtextStyle: {
fontSize: 10,
color: '#fff',
},
show: false,
},
color: [
{
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(40, 218, 239, 1)' },
{ offset: 1, color: 'rgba(130, 249, 255, 0.8)' },
],
},
{
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{ offset: 0, color: 'rgba(198, 201, 24, 1)' },
{ offset: 1, color: 'rgba(198, 201, 24, 0.8)' },
],
},
{
x: 1,
y: 1,
x2: 0,
y2: 0,
colorStops: [
{
offset: 0,
color: 'rgba(15, 44, 88, 1)', // 0%
},
{
offset: 0.7,
color: 'rgba(40, 55, 255, 1)', // 100%
},
{
offset: 1,
color: 'rgba(244, 245, 255, 1)', // 100%
},
],
},
{
x: 1,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(75, 238, 114, 0.2)', // 0%
},
{
offset: 1,
color: 'rgba(75, 238, 114, 1)', // 100%
},
],
},
{
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(255, 19, 0, 0.2)', // 0%
},
{
offset: 1,
color: 'rgba(251, 95, 79, 1)', // 100%
},
],
},
],
},
});
@ -106,17 +199,4 @@ const state = reactive({
],
},
});
watch(
() => props.data,
(val) => {
if (!isEmpty(val)) {
state.data = val;
}
},
{
deep: true,
immediate: true,
}
);
</script>