Compare commits
34 Commits
a8c4bd456e
...
fd44ba2206
Author | SHA1 | Date | |
---|---|---|---|
fd44ba2206 | |||
608ddee02c | |||
30743bd4e0 | |||
![]() |
2e057d7014 | ||
![]() |
95ce33be37 | ||
338c0cf779 | |||
d485768e8f | |||
624fb109d2 | |||
![]() |
71abc7497e | ||
![]() |
c4304f8da7 | ||
417ad4bdcc | |||
ca887983dd | |||
d5bd7072e1 | |||
7b9c9e70a3 | |||
575ec062cb | |||
438b388178 | |||
939566e7d8 | |||
32a1830f9d | |||
c50655e1e9 | |||
7c8a206519 | |||
9e74b959d7 | |||
2272c21814 | |||
9e0ccac1e5 | |||
![]() |
5909699ca8 | ||
![]() |
48bdf3923e | ||
b52ef2f1d8 | |||
47b8e18b56 | |||
edbbf59a3b | |||
![]() |
91756e6a8a | ||
![]() |
bb9d4314d7 | ||
1af489ab4b | |||
49cc00695f | |||
![]() |
9a1093f8f0 | ||
![]() |
1986442641 |
4
.gitignore
vendored
@ -23,3 +23,7 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
sub-operation-service/package-lock.json
|
||||||
|
main/package-lock.json
|
||||||
|
sub-operation-service/vite.config.js
|
||||||
|
main/.eslintrc.cjs
|
||||||
|
@ -39,7 +39,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
// 这里时配置规则的,自己看情况配置
|
// 这里时配置规则的,自己看情况配置
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': 'error',
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
endOfLine: 'auto', // 允许自动检测换行符
|
||||||
|
},
|
||||||
|
],
|
||||||
'no-debugger': 'off',
|
'no-debugger': 'off',
|
||||||
'no-unused-vars': 'off',
|
'no-unused-vars': 'off',
|
||||||
'vue/multi-word-component-names': 'off',
|
'vue/multi-word-component-names': 'off',
|
||||||
|
BIN
main/.yarn/install-state.gz
Normal file
1
main/.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
nodeLinker: node-modules
|
@ -57,7 +57,7 @@ export const rightApps = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// name: 'sub-government-screen-service',
|
// name: 'sub-government-screen-service',
|
||||||
name: 'new-digital-agriculture-screen',
|
name: 'new-digital-agriculture-screen/v2/land',
|
||||||
// entry: VITE_APP_SUB_GSS,
|
// entry: VITE_APP_SUB_GSS,
|
||||||
entry: VITE_APP_SUB_GSR,
|
entry: VITE_APP_SUB_GSR,
|
||||||
// activeRule: '/sub-government-screen-service',
|
// activeRule: '/sub-government-screen-service',
|
||||||
|
@ -35,9 +35,8 @@ import { leftApps, rightApps } from '@/micro/app';
|
|||||||
import { getAssetsFile } from '@/utils';
|
import { getAssetsFile } from '@/utils';
|
||||||
|
|
||||||
const gotoPage = (row) => {
|
const gotoPage = (row) => {
|
||||||
console.log(row);
|
// window.history.pushState({}, row.name, row.activeRule);
|
||||||
console.log(window.history.pushState({}, row.name, row.activeRule));
|
window.location.href = row.activeRule;
|
||||||
window.history.pushState({}, row.name, row.activeRule);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
1562
main/yarn.lock
@ -47,6 +47,7 @@ declare module 'vue' {
|
|||||||
CustomTableOperate: typeof import('./src/components/custom-table-operate/index.vue')['default']
|
CustomTableOperate: typeof import('./src/components/custom-table-operate/index.vue')['default']
|
||||||
CustomTableTree: typeof import('./src/components/custom-table-tree/index.vue')['default']
|
CustomTableTree: typeof import('./src/components/custom-table-tree/index.vue')['default']
|
||||||
NewHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue')['default']
|
NewHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue')['default']
|
||||||
|
NewPie: typeof import('./src/components/custom-echart-hyaline-cake/new-pie.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SubTop: typeof import('./src/components/subTop.vue')['default']
|
SubTop: typeof import('./src/components/subTop.vue')['default']
|
||||||
|
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 417 KiB |
@ -28,7 +28,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const titles = ref([
|
const titles = ref([
|
||||||
{ label: '首页', value: 'home' },
|
// { label: '首页', value: 'home' },
|
||||||
{ label: '土地资源', value: 'land' },
|
{ label: '土地资源', value: 'land' },
|
||||||
{ label: '投入品', value: 'inputs' },
|
{ label: '投入品', value: 'inputs' },
|
||||||
{ label: '生产经营主体', value: 'entities' },
|
{ label: '生产经营主体', value: 'entities' },
|
||||||
|
@ -259,6 +259,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
show: true,
|
show: true,
|
||||||
right: '5%',
|
right: '5%',
|
||||||
top: '25%',
|
top: '25%',
|
||||||
|
@ -4,17 +4,18 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted, nextTick } from 'vue';
|
import { ref, watch, onMounted, nextTick } from 'vue';
|
||||||
import { cloneDeep } from 'lodash';
|
import { merge, cloneDeep } from 'lodash';
|
||||||
import { useEcharts } from '@/hooks/useEcharts';
|
import { useEcharts } from '@/hooks/useEcharts';
|
||||||
|
|
||||||
defineOptions({ name: 'NewHyalineCake' });
|
defineOptions({ name: 'NewHyalineCake' });
|
||||||
|
|
||||||
// 定义组件 props
|
let selectedIndex = null;
|
||||||
|
let hoveredIndex = null;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
chartData: {
|
chartData: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [
|
default: () => [
|
||||||
// 默认示例数据
|
|
||||||
{ name: '项目一', value: 60 },
|
{ name: '项目一', value: 60 },
|
||||||
{ name: '项目二', value: 44 },
|
{ name: '项目二', value: 44 },
|
||||||
{ name: '项目三', value: 32 },
|
{ name: '项目三', value: 32 },
|
||||||
@ -23,18 +24,13 @@ const props = defineProps({
|
|||||||
option: {
|
option: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({
|
default: () => ({
|
||||||
// 控制内外径关系的系数,1 表示无内径(实心饼),值越小内径越大
|
k: 1, // 控制内外径关系的系数,1 表示无内径,值越小内径越大
|
||||||
k: 1,
|
itemGap: 0.1, // 扇形的间距
|
||||||
// 扇形边缘向外偏移距离比例(用于选中效果),单位视图坐标,可调
|
itemHeight: 120, // 扇形高度(影响z轴拉伸程度)
|
||||||
itemGap: 0.2,
|
autoItemHeight: 0, // 自动计算扇形高度时使用的系数(>0时 itemHeight 失效,使用 autoItemHeight * value )
|
||||||
// 扇形高度(影响z轴拉伸程度)
|
opacity: 0.6, // 透明度设置
|
||||||
itemHeight: 120,
|
legendSuffix: '', // 图例后缀
|
||||||
// 自动计算扇形高度时使用的系数(>0时 itemHeight 失效,使用 autoItemHeight * value )
|
// TODO series
|
||||||
autoItemHeight: 0,
|
|
||||||
// 透明度设置
|
|
||||||
opacity: 0.6,
|
|
||||||
// 图例后缀
|
|
||||||
legendSuffix: '',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
@ -47,20 +43,92 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 声明组件触发的事件
|
|
||||||
const emit = defineEmits(['click']);
|
const emit = defineEmits(['click']);
|
||||||
|
|
||||||
// 绑定到DOM的容器引用
|
|
||||||
const chartRef = ref(null);
|
const chartRef = ref(null);
|
||||||
// 使用 useEcharts 钩子初始化 ECharts 实例,并获取控制方法
|
|
||||||
const { setOptions, getInstance } = useEcharts(chartRef);
|
const { setOptions, getInstance } = useEcharts(chartRef);
|
||||||
|
|
||||||
// 存储当前的 ECharts 配置项
|
|
||||||
const chartOption = ref({});
|
const chartOption = ref({});
|
||||||
|
// pie2d 默认配置
|
||||||
|
const defaultPie2dOption = {
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
position: 'inside',
|
||||||
|
formatter: '{c}',
|
||||||
|
color: '#fff',
|
||||||
|
distance: 0,
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
smooth: false,
|
||||||
|
length: props.option.itemGap * 500,
|
||||||
|
length2: props.option.itemGap * 800,
|
||||||
|
lineStyle: { color: '#fff', width: 1 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// 合并父组件传入的 pie2dConfig
|
||||||
|
const pie2dOption = {
|
||||||
|
...defaultPie2dOption,
|
||||||
|
...(props.option.pie2dConfig || {}),
|
||||||
|
};
|
||||||
|
|
||||||
// 参数方程函数:生成每个扇形曲面的参数方程,用于 series-surface.parametricEquation
|
// 初始化图表
|
||||||
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
|
function initChart() {
|
||||||
// 中心弧度用于计算偏移方向
|
// 生成基础的3D饼图配置(也就是默认的配置)
|
||||||
|
const baseOption = getPie3D(props.chartData, props.option.k);
|
||||||
|
// 合并用户配置,确保不修改原始对象
|
||||||
|
const finalOption = merge({}, baseOption, cloneDeep(props.option || {}));
|
||||||
|
chartOption.value = finalOption;
|
||||||
|
|
||||||
|
// 设置图表配置
|
||||||
|
setOptions(chartOption.value);
|
||||||
|
// 等待 DOM + ECharts 初始化完成
|
||||||
|
nextTick(() => {
|
||||||
|
const chart = getInstance();
|
||||||
|
if (!chart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免重复绑定事件
|
||||||
|
// chart.off('click');
|
||||||
|
chart.off('mouseover');
|
||||||
|
chart.off('globalout');
|
||||||
|
|
||||||
|
// chart.on('click', handleClick);
|
||||||
|
chart.on('mouseover', handleMouseover);
|
||||||
|
chart.on('globalout', handleGlobalout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载后绑定事件
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听数据或配置变更,重新初始化图表
|
||||||
|
watch(
|
||||||
|
[() => props.chartData, () => props.option],
|
||||||
|
() => {
|
||||||
|
if (props.chartData && props.chartData.length) {
|
||||||
|
initChart();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* 获取 parametric 曲面方程
|
||||||
|
* @param {Number} startRatio 起点弧度
|
||||||
|
* @param {Number} endRatio 终点弧度
|
||||||
|
* @param {Boolean} isSelected 是否选中
|
||||||
|
* @param {Boolean} isHovered 是否高亮
|
||||||
|
* @param {Number} k 饼图内径/外径的占比
|
||||||
|
* @param {Number} h 饼图高度
|
||||||
|
* @param {Array} offsetZ 浮动系数
|
||||||
|
* @return {Object} parametric 曲面方程
|
||||||
|
*/
|
||||||
|
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h, offsetZ = 0) {
|
||||||
|
// console.log('getParametricEquation params :>> ', startRatio, endRatio, isSelected, isHovered, k, h, offsetZ);
|
||||||
const midRatio = (startRatio + endRatio) / 2;
|
const midRatio = (startRatio + endRatio) / 2;
|
||||||
const startRadian = startRatio * Math.PI * 2;
|
const startRadian = startRatio * Math.PI * 2;
|
||||||
const endRadian = endRatio * Math.PI * 2;
|
const endRadian = endRatio * Math.PI * 2;
|
||||||
@ -70,7 +138,7 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h
|
|||||||
if (startRatio === 0 && endRatio === 1) {
|
if (startRatio === 0 && endRatio === 1) {
|
||||||
isSelected = false;
|
isSelected = false;
|
||||||
}
|
}
|
||||||
// k 取默认值 1/3(环的厚度比例),如果传入 k 则使用传入值
|
// k 取默认值 1/3(扇形内径/外径),如果传入 k 则使用传入值
|
||||||
k = typeof k !== 'undefined' ? k : 1 / 3;
|
k = typeof k !== 'undefined' ? k : 1 / 3;
|
||||||
|
|
||||||
// 计算选中效果的偏移量(基于扇形中心角度)
|
// 计算选中效果的偏移量(基于扇形中心角度)
|
||||||
@ -79,7 +147,6 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h
|
|||||||
// 计算高亮时的放大比例(未高亮时为1)
|
// 计算高亮时的放大比例(未高亮时为1)
|
||||||
const hoverRate = isHovered ? 1.05 : 1;
|
const hoverRate = isHovered ? 1.05 : 1;
|
||||||
|
|
||||||
// 返回 parametric 曲面方程
|
|
||||||
return {
|
return {
|
||||||
u: {
|
u: {
|
||||||
// u 参数控制饼的周向角度:从 -π 到 3π,可以完整绘制一圈
|
// u 参数控制饼的周向角度:从 -π 到 3π,可以完整绘制一圈
|
||||||
@ -117,167 +184,117 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h
|
|||||||
z(u, v) {
|
z(u, v) {
|
||||||
// 在 u < -π/2 时,防止出现不必要的翻转,直接使用基本正弦
|
// 在 u < -π/2 时,防止出现不必要的翻转,直接使用基本正弦
|
||||||
if (u < -Math.PI * 0.5) {
|
if (u < -Math.PI * 0.5) {
|
||||||
return Math.sin(u);
|
return Math.sin(u) + offsetZ * h * 0.1;
|
||||||
}
|
}
|
||||||
// 在 u > 2.5π 时,处理扇形尾部厚度
|
// 在 u > 2.5π 时,处理扇形尾部厚度
|
||||||
if (u > Math.PI * 2.5) {
|
if (u > Math.PI * 2.5) {
|
||||||
return Math.sin(u) * h * 0.1;
|
return Math.sin(u) * h * 0.1 + offsetZ * h * 0.1;
|
||||||
}
|
}
|
||||||
// 正常情况下,z 根据 v 参数控制上下表面的高度差
|
// 正常情况下,z 根据 v 参数控制上下表面的高度差
|
||||||
// 当前图形的高度是Z根据h(每个value的值决定的)
|
// 当前图形的高度是Z根据h(每个value的值决定的)
|
||||||
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
|
return Math.sin(v) > 0 ? 1 * h * 0.1 + offsetZ * h * 0.1 : -1 + offsetZ * h * 0.1;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成整个 3D 饼图的配置项
|
/**
|
||||||
function getPie3D(pieData) {
|
* 获取 3D 饼图的配置项
|
||||||
|
* @param {Array} pieData 饼图数据
|
||||||
|
* @param {Number} internalDiameterRatio 饼图内径/外径的占比
|
||||||
|
* @return {Object} 配置项
|
||||||
|
*/
|
||||||
|
function getPie3D(pieData, internalDiameterRatio) {
|
||||||
const series = [];
|
const series = [];
|
||||||
let sumValue = 0;
|
let sumValue = 0;
|
||||||
// 计算总值
|
let startValue = 0;
|
||||||
pieData.forEach((item) => {
|
let endValue = 0;
|
||||||
sumValue += item.value;
|
const legendData = [];
|
||||||
});
|
|
||||||
|
const k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3;
|
||||||
|
|
||||||
// k 为外径厚度系数
|
|
||||||
const k = props.option.k ?? 1;
|
|
||||||
// 构建每个扇形的 series 数据
|
// 构建每个扇形的 series 数据
|
||||||
pieData.forEach((dataItem, idx) => {
|
for (let i = 0; i < pieData.length; i += 1) {
|
||||||
|
sumValue += pieData[i].value;
|
||||||
|
|
||||||
const seriesItem = {
|
const seriesItem = {
|
||||||
name: dataItem.name ?? `series${idx}`,
|
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
|
||||||
type: 'surface',
|
type: 'surface',
|
||||||
parametric: true,
|
parametric: true,
|
||||||
wireframe: { show: false },
|
wireframe: { show: false },
|
||||||
pieData: dataItem,
|
pieData: pieData[i],
|
||||||
itemStyle: {
|
|
||||||
opacity: props.option.opacity,
|
|
||||||
borderRadius: 300,
|
|
||||||
borderColor: '#fff',
|
|
||||||
borderWidth: 0,
|
|
||||||
},
|
|
||||||
pieStatus: {
|
pieStatus: {
|
||||||
selected: false,
|
selected: false,
|
||||||
hovered: false,
|
hovered: false,
|
||||||
k,
|
k,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// 合并自定义样式(如果有)
|
if (typeof pieData[i].itemStyle !== 'undefined') {
|
||||||
if (dataItem.itemStyle) {
|
const { itemStyle } = pieData[i];
|
||||||
const customStyle = {};
|
typeof pieData[i].itemStyle.color !== 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
|
||||||
if (dataItem.itemStyle.color !== undefined) {
|
typeof pieData[i].itemStyle.opacity !== 'undefined' ? (itemStyle.opacity = pieData[i].itemStyle.opacity) : null;
|
||||||
customStyle.color = dataItem.itemStyle.color;
|
|
||||||
}
|
seriesItem.itemStyle = itemStyle;
|
||||||
if (dataItem.itemStyle.opacity !== undefined) {
|
|
||||||
customStyle.opacity = dataItem.itemStyle.opacity;
|
|
||||||
}
|
|
||||||
seriesItem.itemStyle = { ...seriesItem.itemStyle, ...customStyle };
|
|
||||||
}
|
}
|
||||||
series.push(seriesItem);
|
series.push(seriesItem);
|
||||||
});
|
|
||||||
|
|
||||||
// 计算每个扇形的 startRatio/endRatio 和参数方程
|
|
||||||
let startValue = 0;
|
|
||||||
series.forEach((serie) => {
|
|
||||||
const endValue = startValue + serie.pieData.value;
|
|
||||||
const startRatio = startValue / sumValue;
|
|
||||||
const endRatio = endValue / sumValue;
|
|
||||||
serie.pieData.startRatio = startRatio;
|
|
||||||
serie.pieData.endRatio = endRatio;
|
|
||||||
// 初始均为未选中未高亮
|
|
||||||
serie.parametricEquation = getParametricEquation(
|
|
||||||
startRatio,
|
|
||||||
endRatio,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
k,
|
|
||||||
// 扇形高度:根据配置自动计算或使用固定高度
|
|
||||||
props.option.autoItemHeight > 0 ? props.option.autoItemHeight * serie.pieData.value : props.option.itemHeight
|
|
||||||
);
|
|
||||||
startValue = endValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加一个透明圆环,用于实现 hover 在扇形外面时取消高亮
|
|
||||||
series.push({
|
|
||||||
name: 'mouseoutSeries',
|
|
||||||
type: 'surface',
|
|
||||||
parametric: true,
|
|
||||||
wireframe: { show: false },
|
|
||||||
itemStyle: { opacity: 0 },
|
|
||||||
parametricEquation: {
|
|
||||||
u: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },
|
|
||||||
v: { min: 0, max: Math.PI, step: Math.PI / 20 },
|
|
||||||
x(u, v) {
|
|
||||||
return Math.sin(v) * Math.sin(u) + Math.sin(u);
|
|
||||||
},
|
|
||||||
y(u, v) {
|
|
||||||
return Math.sin(v) * Math.cos(u) + Math.cos(u);
|
|
||||||
},
|
|
||||||
z(u, v) {
|
|
||||||
return Math.cos(v) > 0 ? 0.1 : -0.1;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 基础配置:坐标轴、视角、图例、提示框等
|
|
||||||
const option = Object.assign(
|
|
||||||
{
|
|
||||||
tooltip: {
|
|
||||||
backgroundColor: 'rgba(18, 55, 85, 0.8)',
|
|
||||||
borderColor: '#35d0c0',
|
|
||||||
color: '#fff',
|
|
||||||
position: (point, params, dom, rect, size) => {
|
|
||||||
// 动态调整 tooltip 位置,避免溢出
|
|
||||||
let x = point[0],
|
|
||||||
y = point[1];
|
|
||||||
const [viewW, viewH] = size.viewSize;
|
|
||||||
const [boxW, boxH] = size.contentSize;
|
|
||||||
if (x + boxW > viewW) x -= boxW;
|
|
||||||
if (y + boxH > viewH) y -= boxH;
|
|
||||||
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>
|
|
||||||
${chartOption.value.series[params.seriesIndex].pieData.value}
|
|
||||||
</span>`;
|
|
||||||
}
|
}
|
||||||
return '';
|
|
||||||
},
|
// 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
|
||||||
},
|
// 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
|
||||||
xAxis3D: { min: -1, max: 1 },
|
// console.log(series);
|
||||||
yAxis3D: { min: -1, max: 1 },
|
for (let i = 0; i < series.length; i += 1) {
|
||||||
zAxis3D: { min: -1, max: 1 },
|
endValue = startValue + series[i].pieData.value;
|
||||||
grid3D: {
|
|
||||||
show: false,
|
const z = series[i]?.pieData?.floatZ ?? 0;
|
||||||
boxHeight: 5,
|
series[i].pieData.startRatio = startValue / sumValue;
|
||||||
top: '0',
|
series[i].pieData.endRatio = endValue / sumValue;
|
||||||
left: '-20%',
|
series[i].parametricEquation = getParametricEquation(
|
||||||
viewControl: {
|
series[i].pieData.startRatio,
|
||||||
// 3D 视角设置
|
series[i].pieData.endRatio,
|
||||||
alpha: 60, // 俯仰角度
|
series[i].pieStatus.selected,
|
||||||
distance: 240, // 视角距离
|
series[i].pieStatus.hovered,
|
||||||
rotateSensitivity: 10,
|
k,
|
||||||
zoomSensitivity: 10,
|
props.option.autoItemHeight > 0 ? props.option.autoItemHeight * series[i].pieData.value : props.option.itemHeight,
|
||||||
panSensitivity: 10,
|
z
|
||||||
autoRotate: true,
|
);
|
||||||
autoRotateAfterStill: 2,
|
|
||||||
},
|
startValue = endValue;
|
||||||
|
|
||||||
|
legendData.push(series[i].name);
|
||||||
|
}
|
||||||
|
// 3. 构造透明的 2D pie,用于 legend/label
|
||||||
|
const outerPercent = 50;
|
||||||
|
const innerPercent =
|
||||||
|
internalDiameterRatio != null ? Math.round(internalDiameterRatio * outerPercent) : Math.round(((1 - k) / (1 + k)) * outerPercent);
|
||||||
|
|
||||||
|
const pie2dData = [];
|
||||||
|
for (let i = 0; i < pieData.length; i++) {
|
||||||
|
pie2dData.push({
|
||||||
|
name: pieData[i].name,
|
||||||
|
value: pieData[i].value,
|
||||||
|
// itemStyle: { opacity: 1 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
series.push({
|
||||||
|
name: 'pie2d',
|
||||||
|
type: 'pie',
|
||||||
|
data: pie2dData,
|
||||||
|
radius: [`${innerPercent}%`, `${outerPercent}%`],
|
||||||
|
center: pie2dOption.center,
|
||||||
|
startAngle: props.option.startAngle || 90,
|
||||||
|
clockwise: false,
|
||||||
|
itemStyle: {
|
||||||
|
color: 'transparent',
|
||||||
},
|
},
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
label: pie2dOption.label,
|
||||||
|
labelLine: pie2dOption.labelLine,
|
||||||
|
});
|
||||||
|
// 基础配置:坐标轴、视角、图例、提示框等
|
||||||
|
// 准备待返回的配置项,把准备好的 legendData、series 传入。
|
||||||
|
const option = {
|
||||||
legend: {
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
show: true,
|
show: true,
|
||||||
selectedMode: false,
|
|
||||||
right: '5%',
|
right: '5%',
|
||||||
top: '25%',
|
top: '25%',
|
||||||
orient: 'vertical',
|
orient: 'vertical',
|
||||||
@ -290,48 +307,114 @@ function getPie3D(pieData) {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
// 图例标签显示名称和数值
|
|
||||||
formatter: (name) => {
|
formatter: (name) => {
|
||||||
const item = props.chartData.find((d) => d.name === name);
|
if (props.chartData.length) {
|
||||||
return item ? ` ${name} ${item.value}${props.option.legendSuffix || ''}` : name;
|
const item = props.chartData.filter((item) => item.name === name)[0];
|
||||||
|
return ` ${name} ${item.value}${props.option.legendSuffix ?? ''}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// animation: false,
|
||||||
|
tooltip: {
|
||||||
|
formatter: (params) => {
|
||||||
|
// 只针对 3D surface 系列
|
||||||
|
if (params.seriesType === 'surface') {
|
||||||
|
// params.seriesIndex 是扇区的索引
|
||||||
|
const serie = chartOption.value.series[params.seriesIndex];
|
||||||
|
const realValue = serie.pieData.value;
|
||||||
|
return `${params.seriesName}:${realValue}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis3D: {
|
||||||
|
min: -1,
|
||||||
|
max: 1,
|
||||||
|
},
|
||||||
|
yAxis3D: {
|
||||||
|
min: -1,
|
||||||
|
max: 1,
|
||||||
|
},
|
||||||
|
zAxis3D: {
|
||||||
|
min: -1,
|
||||||
|
max: 1,
|
||||||
|
},
|
||||||
|
grid3D: {
|
||||||
|
show: true,
|
||||||
|
boxHeight: 5,
|
||||||
|
top: '-20%',
|
||||||
|
viewControl: {
|
||||||
|
// 3d效果可以放大、旋转等,请自己去查看官方配置
|
||||||
|
alpha: 35,
|
||||||
|
// beta: 30,
|
||||||
|
rotateSensitivity: 1,
|
||||||
|
zoomSensitivity: 0,
|
||||||
|
panSensitivity: 0,
|
||||||
|
autoRotate: true,
|
||||||
|
distance: 150,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
series,
|
series,
|
||||||
},
|
};
|
||||||
// 将外部配置项覆盖基础配置
|
|
||||||
props.option
|
|
||||||
);
|
|
||||||
|
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化图表
|
// 监听 mouseover,近似实现高亮(放大)效果
|
||||||
function initChart() {
|
function handleMouseover(params) {
|
||||||
// 生成基础的3D饼图配置
|
// console.log('mouseover');
|
||||||
const baseOption = getPie3D(props.chartData);
|
const idx = params.seriesIndex;
|
||||||
// 合并用户配置,确保不修改原始对象
|
const series = chartOption.value.series;
|
||||||
const finalOption = Object.assign({}, baseOption, cloneDeep(props.option || {}));
|
|
||||||
chartOption.value = finalOption;
|
// 如果当前扇形已经高亮,不做任何操作
|
||||||
// 设置图表配置
|
if (hoveredIndex === idx) return;
|
||||||
setOptions(chartOption.value);
|
|
||||||
// 等待 DOM + ECharts 初始化完成
|
// 1. 取消上一个高亮
|
||||||
nextTick(() => {
|
if (hoveredIndex !== null && hoveredIndex >= 0 && series[hoveredIndex]) {
|
||||||
const chart = getInstance();
|
updateSeriesHover(hoveredIndex, false);
|
||||||
if (!chart) {
|
|
||||||
console.warn('ECharts 实例未初始化,事件绑定失败');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 避免重复绑定事件
|
// 2. 设置当前扇形高亮(排除辅助环)
|
||||||
chart.off('click');
|
if (params.seriesName !== 'mouseoutSeries' && series[idx]) {
|
||||||
chart.off('mouseover');
|
updateSeriesHover(idx, true);
|
||||||
chart.off('globalout');
|
hoveredIndex = idx;
|
||||||
|
} else {
|
||||||
|
hoveredIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
chart.on('click', handleClick);
|
// 3. 重新渲染
|
||||||
chart.on('mouseover', handleMouseover);
|
setOptions(chartOption.value);
|
||||||
chart.on('globalout', handleGlobalout);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleGlobalout() {
|
||||||
|
const idx = hoveredIndex;
|
||||||
|
if (idx !== null && idx >= 0 && chartOption.value.series[idx]) {
|
||||||
|
updateSeriesHover(idx, false);
|
||||||
|
hoveredIndex = null;
|
||||||
|
setOptions(chartOption.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抽取高亮/取消高亮的公共逻辑
|
||||||
|
function updateSeriesHover(index, toHover) {
|
||||||
|
const item = chartOption.value.series[index];
|
||||||
|
// 安全取 pieStatus,fallback 到空对象
|
||||||
|
const status = item.pieStatus || {};
|
||||||
|
const isSelected = !!status.selected;
|
||||||
|
const start = item.pieData?.startRatio ?? 0;
|
||||||
|
const end = item.pieData?.endRatio ?? 1;
|
||||||
|
const k = typeof status.k === 'number' ? status.k : 1 / 3;
|
||||||
|
// 计算 newHeight:如果 hover,则加 5,否则按原 height
|
||||||
|
const baseHeight = props.option.autoItemHeight > 0 ? props.option.autoItemHeight : props.option.itemHeight;
|
||||||
|
const newHeight = toHover ? baseHeight + 80 : baseHeight;
|
||||||
|
// 更新 parametricEquation
|
||||||
|
item.parametricEquation = getParametricEquation(start, end, isSelected, toHover, k, newHeight);
|
||||||
|
// 更新状态
|
||||||
|
item.pieStatus = {
|
||||||
|
...status,
|
||||||
|
hovered: toHover,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function handleClick(params) {
|
function handleClick(params) {
|
||||||
// 如果点击的是透明辅助环,则忽略
|
// 如果点击的是透明辅助环,则忽略
|
||||||
if (params.seriesName === 'mouseoutSeries') return;
|
if (params.seriesName === 'mouseoutSeries') return;
|
||||||
@ -367,124 +450,10 @@ function handleClick(params) {
|
|||||||
series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h);
|
series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h);
|
||||||
series[idx].pieStatus.selected = isSelected;
|
series[idx].pieStatus.selected = isSelected;
|
||||||
|
|
||||||
// 更新记录选中的系列索引
|
|
||||||
selectedIndex = isSelected ? idx : null;
|
selectedIndex = isSelected ? idx : null;
|
||||||
|
|
||||||
// 更新图表配置
|
|
||||||
setOptions(optionVal);
|
setOptions(optionVal);
|
||||||
// 触发组件 click 事件供父组件使用
|
|
||||||
emit('click', params);
|
emit('click', params);
|
||||||
}
|
}
|
||||||
// 在函数顶部声明(确保作用域覆盖所有需要的地方)
|
|
||||||
window.debugZValues = {
|
|
||||||
current: null,
|
|
||||||
history: [],
|
|
||||||
};
|
|
||||||
function handleMouseover(params) {
|
|
||||||
if (params.seriesName === 'mouseoutSeries') return;
|
|
||||||
|
|
||||||
const chart = getInstance();
|
|
||||||
const optionVal = chart.getOption(); // 改用实时获取最新配置
|
|
||||||
const series = optionVal.series;
|
|
||||||
const idx = params.seriesIndex;
|
|
||||||
const hh = series[idx].parametricEquation.z();
|
|
||||||
window.debugZValues.current = hh;
|
|
||||||
window.debugZValues.history.push({
|
|
||||||
event: 'mouseover',
|
|
||||||
series: series[idx].name,
|
|
||||||
zValue: hh,
|
|
||||||
time: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
console.log('当前Z值:', hh, '历史记录:', window.debugZValues.history);
|
|
||||||
// 打印移入前的完整状态(调试用)
|
|
||||||
console.log(
|
|
||||||
'[移入] 当前所有扇形状态',
|
|
||||||
series.map((s) => ({
|
|
||||||
name: s.name,
|
|
||||||
hovered: s.pieStatus?.hovered,
|
|
||||||
selected: s.pieStatus?.selected,
|
|
||||||
height: s.parametricEquation?.z(Math.PI, Math.PI),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
// 取消之前的高亮(确保只修改状态,不破坏参数方程)
|
|
||||||
if (hoveredIndex !== null && hoveredIndex !== idx) {
|
|
||||||
const prev = series[hoveredIndex];
|
|
||||||
prev.pieStatus.hovered = false; // 仅修改状态
|
|
||||||
prev.parametricEquation = getParametricEquation(
|
|
||||||
// 重新生成正确方程
|
|
||||||
prev.pieData.startRatio,
|
|
||||||
prev.pieData.endRatio,
|
|
||||||
prev.pieStatus.selected,
|
|
||||||
false, // isHovered=false
|
|
||||||
prev.pieStatus.k,
|
|
||||||
prev.pieData.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置新高亮
|
|
||||||
const current = series[idx];
|
|
||||||
current.pieStatus.hovered = true;
|
|
||||||
current.parametricEquation = getParametricEquation(
|
|
||||||
current.pieData.startRatio,
|
|
||||||
current.pieData.endRatio,
|
|
||||||
current.pieStatus.selected,
|
|
||||||
true, // isHovered=true
|
|
||||||
current.pieStatus.k,
|
|
||||||
current.pieData.value
|
|
||||||
);
|
|
||||||
|
|
||||||
hoveredIndex = idx;
|
|
||||||
chart.setOption({ series }); // 仅更新series部分
|
|
||||||
}
|
|
||||||
function handleGlobalout() {
|
|
||||||
if (hoveredIndex !== null) {
|
|
||||||
const chart = getInstance();
|
|
||||||
const optionVal = chart.getOption();
|
|
||||||
const series = optionVal.series;
|
|
||||||
const prev = series[hoveredIndex];
|
|
||||||
|
|
||||||
// 打印修复前的错误状态(调试用)
|
|
||||||
console.warn('[修复前] 异常状态', {
|
|
||||||
name: prev.name,
|
|
||||||
z: prev.parametricEquation.z(Math.PI, Math.PI),
|
|
||||||
equation: prev.parametricEquation,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 重置状态并重新生成方程
|
|
||||||
prev.pieStatus.hovered = false;
|
|
||||||
prev.parametricEquation = getParametricEquation(
|
|
||||||
prev.pieData.startRatio,
|
|
||||||
prev.pieData.endRatio,
|
|
||||||
prev.pieStatus.selected,
|
|
||||||
false, // isHovered=false
|
|
||||||
prev.pieStatus.k,
|
|
||||||
prev.pieData.value
|
|
||||||
);
|
|
||||||
|
|
||||||
hoveredIndex = null;
|
|
||||||
chart.setOption({ series }, { replaceMerge: 'series' }); // 强制替换series
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 记录当前选中和高亮的系列索引
|
|
||||||
let selectedIndex = null;
|
|
||||||
let hoveredIndex = null;
|
|
||||||
|
|
||||||
// 组件挂载后绑定事件
|
|
||||||
onMounted(() => {
|
|
||||||
initChart();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听数据或配置变更,重新初始化图表
|
|
||||||
watch(
|
|
||||||
[() => props.chartData, () => props.option],
|
|
||||||
() => {
|
|
||||||
if (props.chartData && props.chartData.length) {
|
|
||||||
initChart();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true, deep: true }
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="chartRef" class="chart-container" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from 'vue';
|
||||||
|
import { useEcharts } from '@/hooks/useEcharts';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
chartData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
// 内外半径百分比,比如 ['30%', '50%']
|
||||||
|
radius: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ['30%', '50%'],
|
||||||
|
},
|
||||||
|
// 初始俯视角度
|
||||||
|
alpha: {
|
||||||
|
type: Number,
|
||||||
|
default: 30,
|
||||||
|
},
|
||||||
|
// 初始水平旋转角
|
||||||
|
beta: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
// 是否自动旋转
|
||||||
|
autoRotate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '300px',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const chartRef = ref(null);
|
||||||
|
const { setOptions } = useEcharts(chartRef);
|
||||||
|
|
||||||
|
function buildOption(data) {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b}:{c} ({d}%)',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 20,
|
||||||
|
top: 'center',
|
||||||
|
textStyle: { color: '#fff' },
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie3D',
|
||||||
|
name: '3D 饼图',
|
||||||
|
data,
|
||||||
|
radius: props.radius,
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: '{b}\n{c}',
|
||||||
|
},
|
||||||
|
shading: 'realistic', // 光照效果
|
||||||
|
itemStyle: {
|
||||||
|
opacity: 1,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#333',
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: { fontSize: 16, color: '#fff' },
|
||||||
|
itemStyle: { opacity: 1 },
|
||||||
|
},
|
||||||
|
viewControl: {
|
||||||
|
alpha: props.alpha, // 俯仰角
|
||||||
|
beta: props.beta, // 水平旋转角
|
||||||
|
autoRotate: props.autoRotate,
|
||||||
|
distance: 120,
|
||||||
|
zoomSensitivity: 0,
|
||||||
|
panSensitivity: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let option = buildOption(props.chartData);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setOptions(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.chartData,
|
||||||
|
(v) => {
|
||||||
|
option = buildOption(v);
|
||||||
|
setOptions(option);
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: var(--chart-height);
|
||||||
|
}
|
||||||
|
/* 传递 height 到局部变量 */
|
||||||
|
:root {
|
||||||
|
--chart-height: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -108,35 +108,31 @@ export default {
|
|||||||
}
|
}
|
||||||
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
|
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 xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
|
||||||
let seriesData = [];
|
// 1. 优先使用外部传入的series配置(如果存在)
|
||||||
typeArr.forEach((type, index) => {
|
const externalSeries = props.option?.series || [];
|
||||||
let obj = {
|
option.series = typeArr.map((type, index) => {
|
||||||
name: type,
|
// 2. 合并外部配置(按type匹配)
|
||||||
type: props.type,
|
const externalConfig = externalSeries.find((s) => s.name === type) || {};
|
||||||
smooth: true,
|
|
||||||
|
// 3. 动态生成数据
|
||||||
|
const data = xAxisData.map((x) => {
|
||||||
|
const item = props.chartData.find((item) => item.type === type && item.name === x);
|
||||||
|
return item ? item.value : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 合并配置(外部配置优先,内部生成配置补充)
|
||||||
|
return {
|
||||||
|
type: props.type, // 默认类型
|
||||||
|
name: type, // 必须字段
|
||||||
|
data, // 动态生成的数据
|
||||||
|
smooth: true, // 默认平滑
|
||||||
|
...externalConfig, // 外部配置覆盖内部默认值
|
||||||
|
// 特殊处理areaStyle(保留您的颜色逻辑)
|
||||||
|
areaStyle: externalConfig.areaStyle || (props.option?.color ? setAreaStyle(props.option.color[index]) : undefined),
|
||||||
};
|
};
|
||||||
if (props.option?.color) {
|
|
||||||
obj.areaStyle = setAreaStyle(props.option?.color[index]);
|
|
||||||
}
|
|
||||||
const findItem = props.chartData.find((item) => item.type == type);
|
|
||||||
if (findItem && findItem.color) {
|
|
||||||
obj.color = findItem.color;
|
|
||||||
obj.areaStyle = setAreaStyle(findItem.color[index]);
|
|
||||||
}
|
|
||||||
let data = [];
|
|
||||||
xAxisData.forEach((x) => {
|
|
||||||
let dataArr = props.chartData.filter((item) => type === item.type && item.name == x);
|
|
||||||
if (dataArr && dataArr.length > 0) {
|
|
||||||
data.push(dataArr[0].value);
|
|
||||||
} else {
|
|
||||||
data.push(null);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
obj['data'] = data;
|
|
||||||
seriesData.push(obj);
|
|
||||||
});
|
|
||||||
option.series = seriesData;
|
|
||||||
option.xAxis.data = xAxisData;
|
option.xAxis.data = xAxisData;
|
||||||
|
// console.log(option.series);
|
||||||
setOptions(option);
|
setOptions(option);
|
||||||
startAutoPlay({
|
startAutoPlay({
|
||||||
interval: 2000,
|
interval: 2000,
|
||||||
|
@ -29,6 +29,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'calc(100vh - 78px)',
|
default: 'calc(100vh - 78px)',
|
||||||
},
|
},
|
||||||
|
activeIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ['click'],
|
emits: ['click'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
@ -56,13 +60,16 @@ export default {
|
|||||||
zlevel: 1,
|
zlevel: 1,
|
||||||
name: '漏斗图',
|
name: '漏斗图',
|
||||||
type: 'funnel',
|
type: 'funnel',
|
||||||
|
animation: true, // 开启动画
|
||||||
|
animationDuration: 500, // 过渡时间(毫秒)
|
||||||
|
animationEasing: 'cubicOut', // 缓动效果
|
||||||
top: '11%',
|
top: '11%',
|
||||||
left: 'center',
|
left: 'center',
|
||||||
width: '25%',
|
width: '25%',
|
||||||
sort: 'ascending',
|
sort: 'ascending',
|
||||||
gap: 0,
|
gap: 0,
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: false,
|
||||||
position: 'outside',
|
position: 'outside',
|
||||||
width: '200px',
|
width: '200px',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
@ -79,9 +86,8 @@ export default {
|
|||||||
padding: [5, 6], // 增加标签内边距
|
padding: [5, 6], // 增加标签内边距
|
||||||
},
|
},
|
||||||
labelLine: {
|
labelLine: {
|
||||||
show: true,
|
show: false,
|
||||||
length: 10,
|
length: 30,
|
||||||
length2: 50,
|
|
||||||
smooth: true,
|
smooth: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 1,
|
width: 1,
|
||||||
@ -90,28 +96,6 @@ export default {
|
|||||||
type: 'solid',
|
type: 'solid',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 添加第二段折线
|
|
||||||
// markLine: {
|
|
||||||
// symbol: 'none', // 隐藏端点
|
|
||||||
// lineStyle: {
|
|
||||||
// type: 'solid',
|
|
||||||
// color: '#fff',
|
|
||||||
// width: 1,
|
|
||||||
// },
|
|
||||||
// data: [
|
|
||||||
// // 从默认 labelLine 的终点到自定义位置
|
|
||||||
// [
|
|
||||||
// {
|
|
||||||
// coord: [50, 50], // 第一段线的终点(需动态计算)
|
|
||||||
// name: 'Label1',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// coord: [80, 50], // 第二段线的终点
|
|
||||||
// name: 'Label1',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
show: false,
|
show: false,
|
||||||
borderColor: '#fff',
|
borderColor: '#fff',
|
||||||
@ -141,12 +125,50 @@ export default {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.activeIndex,
|
||||||
|
(newVal) => {
|
||||||
|
showLabel(newVal);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function initCharts() {
|
function initCharts() {
|
||||||
if (props.option) {
|
if (props.option) {
|
||||||
Object.assign(option, cloneDeep(props.option));
|
Object.assign(option, cloneDeep(props.option));
|
||||||
}
|
}
|
||||||
option.series[0].data = props.chartData;
|
option.series[0].data = props.chartData;
|
||||||
setOptions(option);
|
setOptions(option);
|
||||||
|
showLabel(props.activeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态生成带label状态的option
|
||||||
|
function getOption(activeIndex) {
|
||||||
|
// 浅拷贝数据(保留原始引用关系)
|
||||||
|
let myData = props.chartData.map((item, idx) => ({
|
||||||
|
...item,
|
||||||
|
label: {
|
||||||
|
show: idx === activeIndex, // 只显示当前选中层级的标签
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: idx === activeIndex, // 只显示当前选中层级的引导线
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
let myOption = {
|
||||||
|
...option,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
...option.series[0],
|
||||||
|
data: myData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return myOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换显示层级
|
||||||
|
function showLabel(activeIndex) {
|
||||||
|
const newOption = getOption(activeIndex);
|
||||||
|
setOptions(newOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClick(params) {
|
function onClick(params) {
|
||||||
|
@ -54,7 +54,7 @@ const props = defineProps({
|
|||||||
type: Array,
|
type: Array,
|
||||||
default() {
|
default() {
|
||||||
return [
|
return [
|
||||||
{ label: '首页', value: '/new-digital-agriculture-screen/v2/home' },
|
// { label: '首页', value: '/new-digital-agriculture-screen/v2/home' },
|
||||||
{ label: '土地资源', value: '/new-digital-agriculture-screen/v2/land' },
|
{ label: '土地资源', value: '/new-digital-agriculture-screen/v2/land' },
|
||||||
{ label: '投入品监管', value: '/new-digital-agriculture-screen/v2/inputs' },
|
{ label: '投入品监管', value: '/new-digital-agriculture-screen/v2/inputs' },
|
||||||
{ label: '产出品管理', value: '/new-digital-agriculture-screen/v2/entities' },
|
{ label: '产出品管理', value: '/new-digital-agriculture-screen/v2/entities' },
|
||||||
@ -176,7 +176,7 @@ function handleTitleClick(val) {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 31%;
|
width: 31%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
line-height: 90px;
|
line-height: 90px;
|
||||||
.title_content {
|
.title_content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -58,10 +58,10 @@ onMounted(() => {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100;
|
height: 100%;
|
||||||
background:
|
background:
|
||||||
url('@/assets/images/basic/containerBG.png') no-repeat center 100%,
|
url('../assets/images/basic/containerBG.png') no-repeat center 100%,
|
||||||
url('@/assets/images/basic/containerBotBG.png') no-repeat bottom center;
|
url('../assets/images/basic/containerBotBG.png') no-repeat bottom center;
|
||||||
&-header {
|
&-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
@ -19,21 +19,86 @@ export const constantRoutes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/new-digital-agriculture-screen',
|
path: '/new-digital-agriculture-screen',
|
||||||
name: 'layout',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/new-digital-agriculture-screen/v2/home',
|
|
||||||
meta: { title: '首页', icon: 'House' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/new-digital-agriculture-screen/v2/home',
|
|
||||||
component: () => import('@/views/home/index.vue'),
|
|
||||||
name: 'home',
|
name: 'home',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/new-digital-agriculture-screen/v2/land', //原home
|
||||||
meta: { title: '首页', icon: '' },
|
meta: { title: '首页', icon: '' },
|
||||||
|
children: [
|
||||||
|
// {
|
||||||
|
// path: '/new-digital-agriculture-screen/v2/home',
|
||||||
|
// component: () => import('@/views/home/index.vue'),
|
||||||
|
// name: 'home',
|
||||||
|
// meta: { title: '首页', icon: '' },
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
path: '/new-digital-agriculture-screen/v2/land',
|
||||||
|
component: () => import('@/views/land/index.vue'),
|
||||||
|
name: 'land',
|
||||||
|
meta: { title: '土地资源', icon: '' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/new-digital-agriculture-screen/v2/inputs',
|
||||||
|
name: 'inputs',
|
||||||
|
component: () => import('@/views/inputs/index.vue'),
|
||||||
|
meta: { title: '投入品监管', icon: '' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/new-digital-agriculture-screen/v2/entities',
|
||||||
|
name: 'entities',
|
||||||
|
component: () => import('@/views/entities/index.vue'),
|
||||||
|
meta: { title: '产出品管理', icon: '' },
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: 'breed',
|
||||||
|
// name: 'breed',
|
||||||
|
// component: () => import('@/views/breed/index.vue'),
|
||||||
|
// meta: { title: '', icon: '' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: 'plant',
|
||||||
|
// name: 'plant',
|
||||||
|
// component: () => import('@/views/plant/index.vue'),
|
||||||
|
// meta: { title: '', icon: '' },
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
path: '/new-digital-agriculture-screen/v2/business',
|
||||||
|
name: 'business',
|
||||||
|
component: () => import('@/views/business/index.vue'),
|
||||||
|
meta: { title: '生产经营主体', icon: '' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/new-digital-agriculture-screen/v2/trace',
|
||||||
|
name: 'trace',
|
||||||
|
component: () => import('@/views/trace/index.vue'),
|
||||||
|
meta: { title: '农产品溯源', icon: '' },
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: 'early',
|
||||||
|
// name: 'early',
|
||||||
|
// component: () => import('@/views/early/index.vue'),
|
||||||
|
// meta: { title: '', icon: '' },
|
||||||
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// // 原来的首页默认配置
|
||||||
|
// path: '/new-digital-agriculture-screen',
|
||||||
|
// name: 'layout',
|
||||||
|
// component: Layout,
|
||||||
|
// redirect: '/new-digital-agriculture-screen/v2/home',
|
||||||
|
// meta: { title: '首页', icon: 'House' },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/new-digital-agriculture-screen/v2/home',
|
||||||
|
// component: () => import('@/views/home/index.vue'),
|
||||||
|
// name: 'home',
|
||||||
|
// meta: { title: '首页', icon: '' },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
// ...demoRouters,
|
// ...demoRouters,
|
||||||
v2,
|
// v2,
|
||||||
// {
|
// {
|
||||||
// path: '/test',
|
// path: '/test',
|
||||||
// name: 'test',
|
// name: 'test',
|
||||||
|
@ -4,7 +4,7 @@ export default {
|
|||||||
path: '/new-digital-agriculture-screen',
|
path: '/new-digital-agriculture-screen',
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/new-digital-agriculture-screen/v2/home',
|
redirect: '/new-digital-agriculture-screen/v2/land', //原home
|
||||||
meta: { title: '首页', icon: '' },
|
meta: { title: '首页', icon: '' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -50,7 +50,7 @@ export default {
|
|||||||
meta: { title: '生产经营主体', icon: '' },
|
meta: { title: '生产经营主体', icon: '' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/new-digital-agriculture-screen/trace',
|
path: '/new-digital-agriculture-screen/v2/trace',
|
||||||
name: 'trace',
|
name: 'trace',
|
||||||
component: () => import('@/views/trace/index.vue'),
|
component: () => import('@/views/trace/index.vue'),
|
||||||
meta: { title: '农产品溯源', icon: '' },
|
meta: { title: '农产品溯源', icon: '' },
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
GaugeChart,
|
GaugeChart,
|
||||||
ScatterChart,
|
ScatterChart,
|
||||||
EffectScatterChart,
|
EffectScatterChart,
|
||||||
|
FunnelChart,
|
||||||
} from 'echarts/charts';
|
} from 'echarts/charts';
|
||||||
import 'echarts-gl';
|
import 'echarts-gl';
|
||||||
import 'echarts-liquidfill';
|
import 'echarts-liquidfill';
|
||||||
@ -70,6 +71,7 @@ echarts.use([
|
|||||||
MarkPointComponent,
|
MarkPointComponent,
|
||||||
MarkLineComponent,
|
MarkLineComponent,
|
||||||
MarkAreaComponent,
|
MarkAreaComponent,
|
||||||
|
FunnelChart,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export default echarts;
|
export default echarts;
|
||||||
|
@ -20,10 +20,10 @@ const state = reactive({
|
|||||||
option: {
|
option: {
|
||||||
// 数据
|
// 数据
|
||||||
dataset: [],
|
dataset: [],
|
||||||
type: 'column',
|
type: 'row',
|
||||||
rowNum: 6,
|
rowNum: 6,
|
||||||
isAnimation: true,
|
isAnimation: true,
|
||||||
waitTime: 5,
|
waitTime: 12,
|
||||||
unit: '万元',
|
unit: '万元',
|
||||||
sort: true,
|
sort: true,
|
||||||
height: 12,
|
height: 12,
|
||||||
@ -32,9 +32,9 @@ const state = reactive({
|
|||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
carousel: 'single',
|
carousel: 'single',
|
||||||
indexPrefix: 'TOP',
|
indexPrefix: 'TOP',
|
||||||
indexFontSize: 12,
|
indexFontSize: 22,
|
||||||
leftFontSize: 14,
|
leftFontSize: 16,
|
||||||
rightFontSize: 14,
|
rightFontSize: 16,
|
||||||
valueFormatter: (item) => {
|
valueFormatter: (item) => {
|
||||||
return item.value;
|
return item.value;
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,28 @@ const state = reactive({
|
|||||||
radius: '80%',
|
radius: '80%',
|
||||||
center: ['50%', '50%'],
|
center: ['50%', '50%'],
|
||||||
backgroundStyle: {
|
backgroundStyle: {
|
||||||
color: 'transparent',
|
color: {
|
||||||
|
type: 'radial',
|
||||||
|
x: 0.5,
|
||||||
|
y: 0.5,
|
||||||
|
r: 0.8,
|
||||||
|
colorStops: [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(220, 255, 255, 0.3)', // 中心半透明白
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 0.7,
|
||||||
|
color: 'rgba(0, 231, 240, 0.2)', // 中间荧光青
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(0, 150, 200, 0.3)', // 外围高饱和科技青
|
||||||
|
},
|
||||||
|
],
|
||||||
|
globalCoord: false,
|
||||||
|
},
|
||||||
|
blur: 10, // 模糊半径
|
||||||
},
|
},
|
||||||
data: [],
|
data: [],
|
||||||
amplitude: 12, //水波振幅
|
amplitude: 12, //水波振幅
|
||||||
@ -57,6 +78,7 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
outline: {
|
outline: {
|
||||||
|
// 圆圈配置
|
||||||
borderDistance: 2,
|
borderDistance: 2,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@ -85,6 +107,8 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
|
opacity: 1, //水的透明度
|
||||||
|
// 水波配置
|
||||||
color: {
|
color: {
|
||||||
type: 'linear', // 线性渐变
|
type: 'linear', // 线性渐变
|
||||||
x: 0,
|
x: 0,
|
||||||
@ -92,8 +116,9 @@ const state = reactive({
|
|||||||
x2: 0,
|
x2: 0,
|
||||||
y2: 1,
|
y2: 1,
|
||||||
colorStops: [
|
colorStops: [
|
||||||
{ offset: 0, color: 'rgba(13, 204, 163, 0.6)' },
|
{ offset: 0, color: '#6af7e8' }, // 更亮的蓝色
|
||||||
{ offset: 1, color: 'rgba(71, 202, 219, 1)' },
|
// { offset: 0.7, color: '#5fcbc1' },
|
||||||
|
{ offset: 1, color: '#55e7ff' },
|
||||||
],
|
],
|
||||||
global: false, // 默认为 false
|
global: false, // 默认为 false
|
||||||
},
|
},
|
||||||
@ -107,8 +132,9 @@ watch(
|
|||||||
() => props.data,
|
() => props.data,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!isEmpty(val)) {
|
if (!isEmpty(val)) {
|
||||||
state.option.series[0].data = [0, val.percent];
|
// 设置水波纹位置
|
||||||
state.option.series[0].label.formatter = val.percent * 100 + '%';
|
state.option.series[0].data = [val.percent + 0.05, val.percent]; //设置水的数量
|
||||||
|
state.option.series[0].label.formatter = val.percent * 100 + '%'; //显示文本
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -193,20 +193,19 @@ function getPie3D(pieData, internalDiameterRatio) {
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
text: '',
|
text: '',
|
||||||
x: '35%',
|
x: '34%', //文本的位置
|
||||||
y: '30%',
|
y: '32%',
|
||||||
|
itemGap: 4,
|
||||||
|
textAlign: 'center',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
rich: {
|
color: '#ffffff',
|
||||||
a: {
|
fontSize: '26px',
|
||||||
fontSize: 20,
|
fontWeight: 'bold',
|
||||||
color: '#fff',
|
|
||||||
},
|
|
||||||
c: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#fff',
|
|
||||||
padding: [15, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
subtext: '',
|
||||||
|
subtextStyle: {
|
||||||
|
color: '#02FD94',
|
||||||
|
fontSize: '18px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
xAxis3D: {
|
xAxis3D: {
|
||||||
@ -224,14 +223,14 @@ function getPie3D(pieData, internalDiameterRatio) {
|
|||||||
grid3D: {
|
grid3D: {
|
||||||
show: false,
|
show: false,
|
||||||
boxHeight: boxHeight,
|
boxHeight: boxHeight,
|
||||||
left: '-10%',
|
left: '-15%',
|
||||||
top: -20,
|
top: -20,
|
||||||
viewControl: {
|
viewControl: {
|
||||||
alpha: 55, //角度(这个很重要 调节角度的)
|
alpha: 40, //角度(这个很重要 调节角度的)
|
||||||
distance: 150, //调整视角到主体的距离,类似调整zoom(这是整体大小)
|
distance: 150, //调整视角到主体的距离,类似调整zoom(这是整体大小)
|
||||||
rotateSensitivity: 1, //设置为0无法旋转
|
rotateSensitivity: 1, //设置为0无法旋转
|
||||||
zoomSensitivity: 0, //设置为0无法缩放
|
zoomSensitivity: 10, //设置为0无法缩放
|
||||||
panSensitivity: 0, //设置为0无法平移
|
panSensitivity: 10, //设置为0无法平移
|
||||||
autoRotate: true, //自动旋转
|
autoRotate: true, //自动旋转
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -243,7 +242,8 @@ function getPie3D(pieData, internalDiameterRatio) {
|
|||||||
const initData = (pieData = []) => {
|
const initData = (pieData = []) => {
|
||||||
const option = getPie3D(pieData, 0.8);
|
const option = getPie3D(pieData, 0.8);
|
||||||
const { name, value } = option.series[0].pieData;
|
const { name, value } = option.series[0].pieData;
|
||||||
option.title.text = `{a|${value}%}{c|\n${name}}`;
|
option.title.text = value + '%'; //中心默认展示数据
|
||||||
|
option.title.subtext = name; //中心默认展示数据
|
||||||
state.option = option;
|
state.option = option;
|
||||||
state.data = option.series;
|
state.data = option.series;
|
||||||
};
|
};
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
:down-title="'农资企业'"
|
:down-title="'农资企业'"
|
||||||
:label-field="'label'"
|
:label-field="'label'"
|
||||||
:value-field="'value'"
|
:value-field="'value'"
|
||||||
:down-width="'130px'"
|
:down-width="''"
|
||||||
:options="[
|
:options="[
|
||||||
{ value: 1, label: '农企/合作社' },
|
{ value: 1, label: '农企/合作社' },
|
||||||
{ value: 2, label: '农资企业' },
|
{ value: 2, label: '农资企业' },
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<span class="right_btn" @click="handleChange(1)"></span>
|
<span class="right_btn" @click="handleChange(1)"></span>
|
||||||
{{ current.info.name }}
|
{{ current.info.name }}
|
||||||
</section>
|
</section>
|
||||||
<custom-echart-triangle :chart-data="data" height="100%" :option="option" />
|
<custom-echart-triangle :chart-data="data" height="100%" :option="option" :active-index="current.info.level - 1" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -23,14 +23,17 @@ const list = ref([
|
|||||||
{
|
{
|
||||||
name: '茶叶',
|
name: '茶叶',
|
||||||
value: '1',
|
value: '1',
|
||||||
|
level: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '核桃',
|
name: '核桃',
|
||||||
value: '2',
|
value: '2',
|
||||||
|
level: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '玉米',
|
name: '玉米',
|
||||||
value: '3',
|
value: '3',
|
||||||
|
level: 3,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const current = reactive({
|
const current = reactive({
|
||||||
@ -38,7 +41,8 @@ const current = reactive({
|
|||||||
length: list.value.length - 1,
|
length: list.value.length - 1,
|
||||||
info: {
|
info: {
|
||||||
name: '茶叶',
|
name: '茶叶',
|
||||||
value: '20',
|
value: '1',
|
||||||
|
level: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
function handleChange(n) {
|
function handleChange(n) {
|
||||||
|
@ -1,116 +1,235 @@
|
|||||||
<template>
|
<template>
|
||||||
<custom-echart-line :chart-data="state.data" height="100%" :option="state.option" />
|
<custom-echart-line :chart-data="chartData" :option="chartOption" height="100%" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, watch } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { isEmpty, sleep } from '@/utils';
|
|
||||||
|
|
||||||
const props = defineProps({
|
// 1. 数据生成(12个月完整数据)
|
||||||
data: {
|
const chartData = ref(
|
||||||
type: Array,
|
(() => {
|
||||||
default: () => [],
|
// 定义波动参数
|
||||||
},
|
const waveConfig = {
|
||||||
query: {
|
// [月份索引]: 调整系数 (1.0为基础值)
|
||||||
type: String,
|
peaks: { 3: 1.6, 9: 1.5 }, // 4月和10月高峰 (数组索引从0开始)
|
||||||
default: '',
|
valleys: { 0: 0.7, 6: 0.8, 11: 0.6 }, // 1月、7月、12月低谷
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
|
||||||
const state = reactive({
|
// 生成符合波动要求的数据
|
||||||
option: {
|
const generateWaveData = (baseVal) => {
|
||||||
color: ['#35D0C0'],
|
return Array.from({ length: 12 }, (_, i) => {
|
||||||
grid: {
|
if (waveConfig.peaks[i]) return baseVal * waveConfig.peaks[i];
|
||||||
left: '5%',
|
if (waveConfig.valleys[i]) return baseVal * waveConfig.valleys[i];
|
||||||
right: '5%',
|
// 平滑过渡值(在基础值附近波动)
|
||||||
bottom: '5%',
|
return baseVal * (0.9 + Math.random() * 0.2);
|
||||||
top: '10%',
|
}).map((v) => parseFloat(v.toFixed(1)));
|
||||||
containLabel: true,
|
};
|
||||||
|
|
||||||
|
// 各品类基础值
|
||||||
|
return [
|
||||||
|
{ type: '种子种苗', values: generateWaveData(15) },
|
||||||
|
{ type: '化肥', values: generateWaveData(20) },
|
||||||
|
{ type: '农药', values: generateWaveData(10) },
|
||||||
|
].flatMap(({ type, values }) =>
|
||||||
|
values.map((value, i) => ({
|
||||||
|
type,
|
||||||
|
name: `${i + 1}月`,
|
||||||
|
value,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
function hexToRGBA(hex, alpha = 1) {
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16);
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16);
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16);
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 图表配置
|
||||||
|
const chartOption = ref({
|
||||||
|
color: ['#02FD94', '#FEF906', '#01FEFD'],
|
||||||
|
legend: {
|
||||||
|
data: ['种子种苗', '化肥', '农药'],
|
||||||
|
top: 8,
|
||||||
|
itemWidth: 12, // 矩形宽度
|
||||||
|
itemHeight: 12, // 矩形高度
|
||||||
|
icon: 'rect',
|
||||||
|
itemGap: 20,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow',
|
type: 'shadow',
|
||||||
},
|
},
|
||||||
backgroundColor: 'rgba(18, 55, 85, 0.8);',
|
backgroundColor: 'rgba(0,0,0,0.5);',
|
||||||
borderColor: '#35d0c0',
|
borderColor: '#35d0c0',
|
||||||
formatter: (data) => {
|
borderRadius: 8,
|
||||||
const params = data[0];
|
formatter: (params) => `
|
||||||
let str = `<div class="custom-echarts-tips">
|
<div style="font-weight:bold;margin-bottom:5px;color:#fff">${params[0].name}</div>
|
||||||
<span>${params.name}</span><br/>
|
${params
|
||||||
<span>${params.marker} ${params.data} 万元</span>
|
.map(
|
||||||
</div>`;
|
(p) => `
|
||||||
return str;
|
<div style="display:flex;align-items:center;margin:3px 0;color:#fff">
|
||||||
},
|
<span style="display:inline-block;width:8px;height:8px;background:${p.color};margin-right:6px;color:#fff"></span>
|
||||||
},
|
${p.seriesName}: <span style="font-weight:bold;margin-left:5px;color:#fff">${p.value} 吨</span>
|
||||||
xAxis: {
|
</div>
|
||||||
type: 'category',
|
`
|
||||||
// name: '年份',
|
)
|
||||||
axisTick: {
|
.join('')}
|
||||||
show: false,
|
`,
|
||||||
alignWithLabel: false,
|
extraCssText: 'backdrop-filter: blur(8px);',
|
||||||
interval: 'auto',
|
|
||||||
inside: false,
|
|
||||||
length: 5,
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
width: 1,
|
|
||||||
color: 'rgba(28, 158, 222, 1)',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '5%',
|
||||||
|
bottom: '5%',
|
||||||
|
top: '20%',
|
||||||
|
containLabel: true,
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
// name: '',
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
margin: 15,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false, // 隐藏刻度线
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: [],
|
series: [
|
||||||
|
{
|
||||||
|
name: '种子种苗',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
x2: 1,
|
||||||
|
y: 0,
|
||||||
|
y2: 0,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: hexToRGBA('#02FD94', 0) },
|
||||||
|
{ offset: 0.2, color: hexToRGBA('#02FD94', 1) },
|
||||||
|
{ offset: 0.8, color: hexToRGBA('#02FD94', 1) },
|
||||||
|
{ offset: 1, color: hexToRGBA('#02FD94', 0) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: hexToRGBA('#02FD94', 1) }, // 底部较实
|
||||||
|
{ offset: 1, color: hexToRGBA('#02FD94', 0) }, // 顶部较透明
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '化肥',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none', // 关键配置:隐藏数据点标记
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: {
|
||||||
|
// 线条首尾渐淡
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
x2: 1,
|
||||||
|
y: 0,
|
||||||
|
y2: 0,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: hexToRGBA('#E0A21C', 0) },
|
||||||
|
{ offset: 0.2, color: hexToRGBA('#E0A21C', 1) },
|
||||||
|
{ offset: 0.8, color: hexToRGBA('#E0A21C', 1) },
|
||||||
|
{ offset: 1, color: hexToRGBA('#E0A21C', 0) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: hexToRGBA('#FEF906', 1) },
|
||||||
|
{ offset: 1, color: hexToRGBA('#FEF906', 0) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '农药',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none', // 关键配置:隐藏数据点标记
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: {
|
||||||
|
// 线条首尾渐淡
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
x2: 1,
|
||||||
|
y: 0,
|
||||||
|
y2: 0,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: hexToRGBA('#01FEFD', 0) },
|
||||||
|
{ offset: 0.2, color: hexToRGBA('#01FEFD', 1) },
|
||||||
|
{ offset: 0.8, color: hexToRGBA('#01FEFD', 1) },
|
||||||
|
{ offset: 1, color: hexToRGBA('#01FEFD', 0) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: hexToRGBA('#01FEFD', 1) },
|
||||||
|
{ offset: 1, color: hexToRGBA('#01FEFD', 0) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 其他系列同理...
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
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 = [
|
|
||||||
{ value: 5, name: '2020' },
|
|
||||||
{ value: 36, name: '2021' },
|
|
||||||
{ value: 70, name: '2022' },
|
|
||||||
{ value: 56, name: '2023' },
|
|
||||||
{ value: 70, name: '2024' },
|
|
||||||
{ value: 20, name: '2025' },
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 调整倾斜月份标签的间距 */
|
||||||
|
:deep(.echarts-axis-x .echarts-axis-tick) {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,272 +1,84 @@
|
|||||||
<template>
|
<template>
|
||||||
<custom-echart-pie-3d :chart-data="state.data" height="100%" :option="state.option" @click="handleClick" />
|
<div class="order-stats">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col v-for="item in state.list" :key="item.label" :span="12">
|
||||||
|
<div class="order-stats-item flex-column">
|
||||||
|
<div class="order-stats-value">
|
||||||
|
{{ item.value }}<span>{{ item.unit }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="order-stats-label">{{ item.label }}</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
|
||||||
import { reactive, watch } from 'vue';
|
|
||||||
import { isEmpty } from '@/utils';
|
|
||||||
|
|
||||||
const props = defineProps({
|
<script setup>
|
||||||
data: {
|
import { reactive } from 'vue';
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
option: {},
|
list: [
|
||||||
data: [],
|
{
|
||||||
text: '',
|
label: '订单金额',
|
||||||
});
|
value: '548.86',
|
||||||
|
unit: '万元',
|
||||||
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'],
|
|
||||||
bottom: 10,
|
|
||||||
itemGap: 20,
|
|
||||||
show: true,
|
|
||||||
icon: 'rect',
|
|
||||||
itemHeight: 10,
|
|
||||||
itemWidth: 10,
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#B8DDFF',
|
|
||||||
lineHeight: 20,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
text: '',
|
|
||||||
x: 'center',
|
|
||||||
y: 'center',
|
|
||||||
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: 0,
|
|
||||||
top: -30,
|
|
||||||
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.category,
|
|
||||||
value: row.percentage,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
initData(pieData);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
deep: true,
|
label: '订单数量',
|
||||||
immediate: true,
|
value: '110554',
|
||||||
}
|
unit: '笔',
|
||||||
);
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.order-stats {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
line-height: 100px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-image: url('@/assets/images/inputs/order.webp');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
width: 120px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
margin: 10px auto 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background: url('@/assets/images/inputs/bg_label.png') center center no-repeat;
|
||||||
|
text-shadow:
|
||||||
|
0 0 10px #01eeff,
|
||||||
|
0 0 20px #01eeff,
|
||||||
|
0 0 30px #01eeff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -2,16 +2,22 @@
|
|||||||
<centerMap :dialog-title="'投入品'" @mapclick="doMapclick">
|
<centerMap :dialog-title="'投入品'" @mapclick="doMapclick">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="land-map-pop-header">
|
<div class="land-map-pop-header">
|
||||||
<div class="title">{{ currentRegion && currentRegion.name ? currentRegion.name : '投入品' }}</div>
|
<div class="title">{{ currentRegion?.name || '投入品' }}</div>
|
||||||
|
<a class="view-case" @click.prevent="handleViewCase">查看案件 ></a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #dialogContent>
|
<template #dialogContent>
|
||||||
<div class="land-map-pop-content">
|
<div class="land-map-pop-content">
|
||||||
<div v-for="(n, index) in list" :key="index" class="list-item">
|
<div class="section-title">投入品管控情况:</div>
|
||||||
<div class="title">
|
<div class="main-content">
|
||||||
<span class="title-val"> {{ n.title }}</span>
|
<div class="item">
|
||||||
|
<div class="label">巡检次数</div>
|
||||||
|
<div class="value green">562次</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="label">案件次数</div>
|
||||||
|
<div class="value red">22次</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="value">{{ n.value }}{{ n.unit }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -19,85 +25,75 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const unit = ref('家');
|
|
||||||
const list = reactive([
|
|
||||||
{ title: '监管机构', value: '1', color: '#01FEFD', unit: '家' },
|
|
||||||
{ title: '监管人员', value: '2', color: '#FEF906', unit: '人' },
|
|
||||||
{ title: '村级监管员', value: '5', color: '#02FD94', unit: '人' },
|
|
||||||
{ title: '农资经营单位', value: '42', color: '#FE7F03', unit: '家' },
|
|
||||||
{ title: '生产主体', value: '2', color: '#41BDFC', unit: '家' },
|
|
||||||
{ title: '投入品规模', value: '3000', color: '#FC0003', unit: '万元' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
let currentRegion = ref(null);
|
let currentRegion = ref(null);
|
||||||
const doMapclick = (data) => {
|
const doMapclick = (data) => {
|
||||||
currentRegion.value = data;
|
currentRegion.value = data;
|
||||||
list.forEach((v) => {
|
};
|
||||||
v.value = Number(v.value) + 1 * (Math.floor(Math.random() * (6 - 2 + 1)) + 2.12).toFixed(0);
|
|
||||||
});
|
const handleViewCase = () => {
|
||||||
|
// 跳转查看案件逻辑
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.land-map-pop-header {
|
.land-map-pop-header {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
|
padding: 0 6px;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
.title,
|
|
||||||
.value {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
color: $color-white;
|
|
||||||
}
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #00f9ff;
|
||||||
}
|
}
|
||||||
.value {
|
|
||||||
font-size: 14px;
|
.view-case {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #00ff99;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.land-map-pop-content {
|
.land-map-pop-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 10px;
|
padding: 4px 10px;
|
||||||
display: inline-flex;
|
color: #ffffff;
|
||||||
justify-content: flex-start;
|
|
||||||
flex-wrap: wrap;
|
.section-title {
|
||||||
.list-item {
|
margin-bottom: 12px;
|
||||||
width: calc((100% - 10px) / 2);
|
font-size: 15px;
|
||||||
display: inline-flex;
|
text-align: left;
|
||||||
justify-content: space-between;
|
}
|
||||||
padding: 6px 0;
|
|
||||||
.title {
|
.main-content {
|
||||||
display: inline-flex;
|
width: 100%;
|
||||||
justify-content: flex-start;
|
display: flex;
|
||||||
.before {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
gap: 12px;
|
||||||
}
|
|
||||||
.b-content {
|
.item {
|
||||||
width: 8px;
|
display: flex;
|
||||||
height: 8px;
|
justify-content: space-between;
|
||||||
border-radius: 50%;
|
font-size: 16px;
|
||||||
display: inline-block;
|
|
||||||
}
|
.label {
|
||||||
.before,
|
color: #ffffff;
|
||||||
.title-val {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
padding: 0 5px 0 2px;
|
|
||||||
color: $color-custom-main;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
display: inline-block;
|
&.green {
|
||||||
text-align: right;
|
color: #01ffb1;
|
||||||
color: $color-white;
|
}
|
||||||
font-size: 12px;
|
|
||||||
|
&.red {
|
||||||
|
color: #ff4242;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,74 +1,108 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row :gutter="20">
|
<div class="inputs">
|
||||||
<el-col v-for="(item, index) in state.list" :key="item.label" :span="12">
|
<h2 class="inputs-title">全县投入品数量:291.85万吨</h2>
|
||||||
<div class="inputs-item flex-row">
|
<new-hyaline-cake :chart-data="state.data" :option="state.option" :width="'100%'" :height="'188px'" />
|
||||||
<span class="inputs-item-icon">
|
|
||||||
<img :src="getAssetsFile(`images/inputs/${index + 1}.png`)" />
|
|
||||||
</span>
|
|
||||||
<div class="inputs-item-info flex-column">
|
|
||||||
<b>{{ item.label }}</b>
|
|
||||||
<div class="inputs-item-value flex-row">
|
|
||||||
<em>{{ item.value }}</em>
|
|
||||||
<span>{{ item.unit }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, watch } from 'vue';
|
import { reactive, watch } from 'vue';
|
||||||
import { getAssetsFile } from '@/utils';
|
|
||||||
import { isEmpty } from '@/utils';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
data: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
list: [],
|
option: {
|
||||||
});
|
k: 0,
|
||||||
|
opacity: 0.8,
|
||||||
watch(
|
itemGap: 0.1,
|
||||||
() => props.data,
|
// legendSuffix: '万吨',
|
||||||
(val) => {
|
itemHeight: 200,
|
||||||
if (!isEmpty(val)) {
|
startAngle: 60,
|
||||||
state.list = val;
|
grid3D: {
|
||||||
}
|
show: false,
|
||||||
|
boxHeight: 2,
|
||||||
|
top: '-20',
|
||||||
|
bottom: '10',
|
||||||
|
// left: '-20%',
|
||||||
|
viewControl: {
|
||||||
|
//3d效果可以放大、旋转等,请自己去查看官方配置
|
||||||
|
alpha: 40, //角度(这个很重要 调节角度的)
|
||||||
|
beta: -40,
|
||||||
|
distance: 260, //调整视角到主体的距离,类似调整zoom(这是整体大小)
|
||||||
|
rotateSensitivity: 0, //设置旋转灵敏度,为0无法旋转
|
||||||
|
zoomSensitivity: 0, //设置缩放灵敏度,为0无法缩放
|
||||||
|
panSensitivity: 0, //设置平移灵敏度,0无法平移
|
||||||
|
autoRotate: false, //自动旋转
|
||||||
|
autoRotateAfterStill: 2, //在鼠标静止操作后恢复自动旋转的时间间隔,在开启 autoRotate 后有效
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
deep: true,
|
legend: {
|
||||||
immediate: true,
|
show: true,
|
||||||
}
|
top: 'bottom',
|
||||||
);
|
orient: 'horizontal',
|
||||||
|
itemWidth: 12, // 矩形宽度
|
||||||
|
itemHeight: 12, // 矩形高度
|
||||||
|
itemGap: 20,
|
||||||
|
icon: 'rect',
|
||||||
|
left: 'center',
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
formatter: (name) => name,
|
||||||
|
},
|
||||||
|
pie2dConfig: {
|
||||||
|
center: ['50%', '40%'],
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'outside',
|
||||||
|
formatter: (params) => {
|
||||||
|
const total = 221.8 + 70.01; // 或动态计算
|
||||||
|
const percent = ((params.value / total) * 100).toFixed(0);
|
||||||
|
return `{name|${params.name}}\n{value|${params.value} 万吨\n(${percent}%)}`;
|
||||||
|
},
|
||||||
|
rich: {
|
||||||
|
name: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#ffffff',
|
||||||
|
lineHeight: 20,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#79F5AF',
|
||||||
|
lineHeight: 18,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: true,
|
||||||
|
length: 40,
|
||||||
|
length2: 40,
|
||||||
|
lineStyle: { color: '#fff', width: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{ value: 221.8, name: '产业运营平台' },
|
||||||
|
{ value: 70.01, name: '其它', floatZ: 1 },
|
||||||
|
],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.inputs {
|
.inputs {
|
||||||
&-item {
|
&-title {
|
||||||
align-items: center;
|
width: 270px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
margin: 24px auto 0;
|
||||||
|
background-image: url('@/assets/images/inputs/bg_title.png');
|
||||||
|
background-position: center bottom;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100%;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 10px 0;
|
text-align: center;
|
||||||
|
|
||||||
&-icon {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-info {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-value {
|
|
||||||
margin-top: 10px;
|
|
||||||
justify-content: space-between;
|
|
||||||
em {
|
|
||||||
color: #02fd94;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,93 +1,195 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="board">
|
<div height="100%">
|
||||||
<custom-scroll-board v-if="state.loading" :chart-config="state.options" />
|
<custom-scroll-board :chart-config="chartConfig" />
|
||||||
|
<div class="announcement-bar">
|
||||||
|
<!-- 喇叭图标 -->
|
||||||
|
<div class="horn-icon">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 10V14C3 15.1 3.9 16 5 16H6L9 19V5L6 8H5C3.9 8 3 8.9 3 10Z" fill="#34f9b7" />
|
||||||
|
<path d="M16.5 12C16.5 10.23 15.48 8.71 14 7.97V16.02C15.48 15.29 16.5 13.77 16.5 12Z" fill="#34f9b7" />
|
||||||
|
<path
|
||||||
|
d="M14 3.23V5.29C16.89 6.15 19 8.83 19 12C19 15.17 16.89 17.85 14 18.71V20.77C18.01 19.86 21 16.28 21 12C21 7.72 18.01 4.14 14 3.23Z"
|
||||||
|
fill="#34f9b7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 滚动内容容器 -->
|
||||||
|
<div class="scroll-container" ref="scrollContainer">
|
||||||
|
<div class="scroll-content" :style="{ transform: `translateX(${scrollPosition}px)` }">
|
||||||
|
<span class="text-content">
|
||||||
|
距今产业运营平台已为全县投入品花费节省约
|
||||||
|
<span class="highlight">¥1256.8 万元</span>
|
||||||
|
(截至{{ currentDate }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, watch } from 'vue';
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
import { isEmpty } from '@/utils';
|
|
||||||
const props = defineProps({
|
|
||||||
data: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = ['白名单企业', '产品名称', '黑名单企业', '产品名称'];
|
// 表格配置
|
||||||
const len = header.length;
|
const chartConfig = ref({
|
||||||
const state = reactive({
|
attr: {
|
||||||
loading: false,
|
w: 200, // 容器宽度
|
||||||
options: {
|
h: 192, // 容器高度
|
||||||
attr: { w: 200, h: 240 },
|
},
|
||||||
option: {
|
option: {
|
||||||
header,
|
header: ['投入品种类', '平台价格', '市场价格'],
|
||||||
dataset: [
|
dataset: [
|
||||||
// ['富农种源', '京科824', '南方农业', '京科824'],
|
['圆茄种苗', '0.3元/棵', '0.4元/棵'],
|
||||||
// ['富农种源', '京科824', '南方农业', '京科824'],
|
['高氮复合肥', '1850元/吨', '1980元/吨'],
|
||||||
// ['富农种源', '京科824', '南方农业', '京科824'],
|
['硫酸钾', '1250元/吨', '1380元/吨'],
|
||||||
// ['富农种源', '京科824', '南方农业', '京科824'],
|
['高氮复合肥', '1850元/吨', '1980元/吨'],
|
||||||
// ['富农种源', '京科824', '南方农业', '京科824'],
|
['西红柿种苗', '0.3元/棵', '0.4元/棵'],
|
||||||
// ['富农种源', '京科824', '南方农业', '京科824'],
|
|
||||||
// ['富农种源', '京科824', '南方农业', '京科824'],
|
|
||||||
],
|
],
|
||||||
index: false,
|
// 样式配置
|
||||||
columnWidth: [100, 100, 100, 100],
|
headerBGC: 'rgba(53, 208, 192, 0.4)', // 表头背景
|
||||||
align: new Array(len).fill('center'),
|
oddRowBGC: 'rgba(0, 59, 81, 0.1)', // 奇数行背景
|
||||||
rowNum: 5,
|
evenRowBGC: 'rgba(10, 39, 50, 0.1)', // 偶数行背景
|
||||||
waitTime: 5,
|
headerHeight: 40, // 表头高度
|
||||||
headerHeight: 40,
|
columnWidth: [300, 200, 200], // 列宽分配
|
||||||
carousel: 'single',
|
align: ['left', 'center', 'center'], // 列对齐方式
|
||||||
headerBGC: 'rgba(53, 208, 192, 0.4)',
|
rowNum: 4, // 可视行数
|
||||||
oddRowBGC: 'rgba(0, 59, 81, 0.1)',
|
waitTime: 3, // 滚动间隔(秒)
|
||||||
evenRowBGC: 'rgba(10, 39, 50, 0.1)',
|
carousel: 'single', // 滚动方式
|
||||||
},
|
hoverPause: true, // 悬停暂停
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const genBlankAndWihteList = (data = []) => {
|
// 动态数据
|
||||||
const rList = data.filter((row) => row.currentRating === 'r');
|
const currentDate = ref('2023年12月15日');
|
||||||
const bList = data.filter((row) => row.currentRating === 'b');
|
const scrollPosition = ref(0);
|
||||||
const len = rList.length >= bList.length ? rList.length : bList.length;
|
const scrollContainer = ref(null);
|
||||||
let list = new Array(len).fill(['', '', '', '']);
|
let animationFrameId = null;
|
||||||
rList.forEach((item, index) => {
|
|
||||||
let temp = new Array(4).fill('');
|
// 滚动控制
|
||||||
temp[0] = item.belongCompany;
|
const startScrolling = () => {
|
||||||
temp[1] = item.comName;
|
const scrollSpeed = -60; // px/s
|
||||||
list[index] = temp;
|
let lastTime = 0;
|
||||||
});
|
|
||||||
bList.forEach((item, index) => {
|
const scroll = (timestamp) => {
|
||||||
list[index][2] = item.belongCompany;
|
if (!lastTime) lastTime = timestamp;
|
||||||
list[index][3] = item.comName;
|
const delta = (timestamp - lastTime) / 1000; // 秒
|
||||||
});
|
lastTime = timestamp;
|
||||||
return list;
|
|
||||||
|
scrollPosition.value += scrollSpeed * delta;
|
||||||
|
|
||||||
|
// 循环逻辑
|
||||||
|
const containerWidth = scrollContainer.value?.clientWidth || 0;
|
||||||
|
const contentWidth = scrollContainer.value?.scrollWidth || 0;
|
||||||
|
|
||||||
|
if (Math.abs(scrollPosition.value) > contentWidth + 50) {
|
||||||
|
scrollPosition.value = containerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
animationFrameId = requestAnimationFrame(scroll);
|
||||||
|
};
|
||||||
|
|
||||||
|
animationFrameId = requestAnimationFrame(scroll);
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
onMounted(() => {
|
||||||
() => props.data,
|
// 获取真实日期
|
||||||
(val) => {
|
const now = new Date();
|
||||||
if (!isEmpty(val)) {
|
currentDate.value = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`;
|
||||||
state.options.option.dataset = genBlankAndWihteList(val);
|
|
||||||
state.loading = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.board {
|
|
||||||
padding: 10px 0px;
|
|
||||||
|
|
||||||
&:deep(.row-item) {
|
startScrolling();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* 自定义样式 */
|
||||||
|
.dv-scroll-board {
|
||||||
|
font-family: 'PingFang SC', 'PingFang SC-Regular', sans-serif;
|
||||||
|
color: #ffffff;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
||||||
|
:deep(.header-item) {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 700;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.row-item) {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
font-weight: 400;
|
||||||
.ceil {
|
.ceil {
|
||||||
&:nth-child(3),
|
text-align: left;
|
||||||
&:nth-child(4) {
|
padding-left: 16px;
|
||||||
color: $color-danger;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-bar {
|
||||||
|
$primary-color: #0e6965;
|
||||||
|
$secondary-color: #34f9b7;
|
||||||
|
$text-color: #b5f5ff;
|
||||||
|
$bg-color: rgba(0, 60, 107, 0.8);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
background: $bg-color;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border-left: 0.25rem solid $secondary-color;
|
||||||
|
box-shadow: 0 0.25rem 1rem rgba($primary-color, 0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3rem;
|
||||||
|
background: linear-gradient(90deg, rgba($bg-color, 0) 0%, $bg-color 100%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horn-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 1rem;
|
||||||
|
color: $secondary-color;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
filter: drop-shadow(0 0 0.25rem rgba($secondary-color, 0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 1.5rem;
|
||||||
|
|
||||||
|
.scroll-content {
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
will-change: transform;
|
||||||
|
padding-right: 2rem; // 防止文字紧贴边缘
|
||||||
|
|
||||||
|
.text-content {
|
||||||
|
color: $text-color;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-shadow: 0 0 0.5rem rgba($primary-color, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: $secondary-color;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,94 +1,120 @@
|
|||||||
<template>
|
<template>
|
||||||
<custom-echart-bar :chart-data="state.data" height="100%" :option="state.option" />
|
<custom-echart-bar :chart-data="state.data" height="100%" :option="state.option" :is-series="true" />
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
|
||||||
import { reactive, watch } from 'vue';
|
|
||||||
import { isEmpty } from '@/utils';
|
|
||||||
|
|
||||||
const props = defineProps({
|
<script setup>
|
||||||
data: {
|
import { reactive } from 'vue';
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
const rawData = [
|
||||||
|
{ name: '勐撒镇', type: '种子农药', value: 15 },
|
||||||
|
{ name: '勐撒镇', type: '化肥', value: 30 },
|
||||||
|
{ name: '勐撒镇', type: '农药', value: 22 },
|
||||||
|
{ name: '勐永镇', type: '种子农药', value: 18 },
|
||||||
|
{ name: '勐永镇', type: '化肥', value: 28 },
|
||||||
|
{ name: '勐永镇', type: '农药', value: 20 },
|
||||||
|
{ name: '孟定镇', type: '种子农药', value: 21 },
|
||||||
|
{ name: '孟定镇', type: '化肥', value: 25 },
|
||||||
|
{ name: '孟定镇', type: '农药', value: 24 },
|
||||||
|
{ name: '勐简乡', type: '种子农药', value: 13 },
|
||||||
|
{ name: '勐简乡', type: '化肥', value: 20 },
|
||||||
|
{ name: '勐简乡', type: '农药', value: 15 },
|
||||||
|
{ name: '贺派乡', type: '种子农药', value: 17 },
|
||||||
|
{ name: '贺派乡', type: '化肥', value: 18 },
|
||||||
|
{ name: '贺派乡', type: '农药', value: 16 },
|
||||||
|
{ name: '芒洪乡', type: '种子农药', value: 14 },
|
||||||
|
{ name: '芒洪乡', type: '化肥', value: 23 },
|
||||||
|
{ name: '芒洪乡', type: '农药', value: 21 },
|
||||||
|
{ name: '大兴乡', type: '种子农药', value: 12 },
|
||||||
|
{ name: '大兴乡', type: '化肥', value: 17 },
|
||||||
|
{ name: '大兴乡', type: '农药', value: 14 },
|
||||||
|
{ name: '耿马镇', type: '种子农药', value: 19 },
|
||||||
|
{ name: '耿马镇', type: '化肥', value: 26 },
|
||||||
|
{ name: '耿马镇', type: '农药', value: 23 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const towns = ['勐撒镇', '勐永镇', '孟定镇', '勐简乡', '贺派乡', '芒洪乡', '大兴乡', '耿马镇'];
|
||||||
|
const types = ['种子农药', '化肥', '农药'];
|
||||||
|
const colors = ['#15EB90', '#F3F70F', '#08DFE4'];
|
||||||
|
|
||||||
|
const series = types.map((type, idx) => {
|
||||||
|
return {
|
||||||
|
name: type,
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 16,
|
||||||
|
stack: 'total',
|
||||||
|
itemStyle: {
|
||||||
|
color: colors[idx],
|
||||||
|
barBorderRadius: 8,
|
||||||
|
shadowColor: colors[idx],
|
||||||
|
// shadowBlur: 8,
|
||||||
|
shadowOffsetY: -16,
|
||||||
},
|
},
|
||||||
|
z: 10 - idx,
|
||||||
|
data: towns.map((town) => {
|
||||||
|
const found = rawData.find((d) => d.name === town && d.type === type);
|
||||||
|
return found ? found.value : 0;
|
||||||
|
}),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
data: rawData,
|
||||||
option: {
|
option: {
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '5%',
|
top: '20%',
|
||||||
right: '5%',
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
bottom: '5%',
|
bottom: '5%',
|
||||||
top: '10%',
|
|
||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
opacity: 1,
|
||||||
|
width: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
margin: 8,
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
interval: 0,
|
||||||
|
},
|
||||||
|
data: towns,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
color: colors,
|
||||||
|
series,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow',
|
type: 'shadow',
|
||||||
},
|
},
|
||||||
backgroundColor: 'rgba(18, 55, 85, 0.8);',
|
backgroundColor: 'rgba(0,0,0,0.6);',
|
||||||
borderColor: '#35d0c0',
|
borderColor: '#35d0c0',
|
||||||
formatter: (data) => {
|
borderRadius: 8,
|
||||||
const params = data[0];
|
formatter: (params) => `
|
||||||
let str = `<div class="custom-echarts-tips">
|
<div style="font-weight:700;margin-bottom:5px;color:#fff;font-size: 16px;">${params[0].name}</div>
|
||||||
<span>${params.name}</span><br/>
|
${params
|
||||||
<span>${params.marker} ${params.data} 万元</span>
|
.map(
|
||||||
</div>`;
|
(p) => `
|
||||||
return str;
|
<div style="display:flex;align-items:center;margin:3px 0;color:#fff">
|
||||||
|
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${p.color};margin-right:6px;color:#fff"></span>
|
||||||
|
${p.seriesName}: <span style="font-weight:bold;margin-left:5px;color:#fff">${p.value} 吨</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join('')}
|
||||||
|
`,
|
||||||
|
extraCssText: 'backdrop-filter: blur(8px);',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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: [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.data,
|
|
||||||
(val) => {
|
|
||||||
if (!isEmpty(val)) {
|
|
||||||
state.data = val;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
<h2 class="inputs-title">检测批次:{{ state.counts }}次</h2>
|
<h2 class="inputs-title">巡检次数:{{ state.counts }}次</h2>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col v-for="item in state.list" :key="item.label" :span="12">
|
<el-col v-for="item in state.list" :key="item.label" :span="12">
|
||||||
<div class="inputs-item flex-column">
|
<div class="inputs-item flex-column">
|
||||||
@ -31,7 +31,7 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '检测覆盖率',
|
label: '检测覆盖率',
|
||||||
value: '99.98%',
|
value: '25.46%',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
<el-row class="data-home-index">
|
<el-row class="data-home-index">
|
||||||
<el-col :span="6" class="left-charts">
|
<el-col :span="6" class="left-charts">
|
||||||
<div class="left-charts-item">
|
<div class="left-charts-item">
|
||||||
<customBack top-title="投入品监管体系建设" :top-postion="'left'">
|
<customBack top-title="全县投入品数量占比" :top-postion="'left'">
|
||||||
<template #back>
|
<template #back>
|
||||||
<inputsOne :data="state.data.one" />
|
<inputsOne :data="state.data.one" />
|
||||||
</template>
|
</template>
|
||||||
</customBack>
|
</customBack>
|
||||||
</div>
|
</div>
|
||||||
<div class="left-charts-item">
|
<div class="left-charts-item">
|
||||||
<customBack top-title="投入品检测监管" :top-postion="'left'">
|
<customBack top-title="产业运营平台投入品数量" :top-postion="'left'">
|
||||||
<template #back>
|
<template #back>
|
||||||
<inputsTwo :data="state.data.InputDetector" />
|
<inputsFive ref="fiveRef" :data="state.data.five" :query="state.queryCode" />
|
||||||
</template>
|
</template>
|
||||||
</customBack>
|
</customBack>
|
||||||
</div>
|
</div>
|
||||||
<div class="left-charts-item">
|
<div class="left-charts-item">
|
||||||
<customBack top-title="投入品金额对比" :top-postion="'left'">
|
<customBack top-title="全县投入品使用情况" :top-postion="'left'">
|
||||||
<template #back>
|
<template #back>
|
||||||
<inputsThere :data="state.data.there" />
|
<inputsThere :data="state.data.there" />
|
||||||
</template>
|
</template>
|
||||||
@ -29,43 +29,38 @@
|
|||||||
|
|
||||||
<el-col :span="6" class="right-charts">
|
<el-col :span="6" class="right-charts">
|
||||||
<div class="right-charts-item">
|
<div class="right-charts-item">
|
||||||
<customBack top-title="生产主体统计" :top-postion="'right'">
|
<customBack top-title="今日投入品价格" :top-postion="'right'">
|
||||||
<template #back>
|
<template #back>
|
||||||
<inputsFour :data="state.data.ProductEntity" />
|
<inputsSix :data="state.data.BlackWhite" />
|
||||||
|
</template>
|
||||||
|
</customBack>
|
||||||
|
</div>
|
||||||
|
<div class="right-charts-item">
|
||||||
|
<customBack top-title="投入品管控情况" :top-postion="'right'">
|
||||||
|
<template #back>
|
||||||
|
<inputsTwo :data="state.data.InputDetector" />
|
||||||
</template>
|
</template>
|
||||||
</customBack>
|
</customBack>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-charts-item">
|
<div class="right-charts-item">
|
||||||
<customBack
|
<customBack
|
||||||
top-title="历年投入品规模对比"
|
top-title="产业运营平台订单统计"
|
||||||
:top-postion="'right'"
|
:top-postion="'right'"
|
||||||
:down-title="'全县'"
|
:down-title="'按日'"
|
||||||
:label-field="'label'"
|
:label-field="'label'"
|
||||||
:value-field="'value'"
|
:value-field="'value'"
|
||||||
:down-width="'100px'"
|
:down-width="'100px'"
|
||||||
:options="[
|
:options="[
|
||||||
{ label: '全县', value: '530926' },
|
{ label: '按日', value: '0' },
|
||||||
{ label: '耿马镇', value: '42611' },
|
{ label: '按月', value: '1' },
|
||||||
{ label: '勐撒镇', value: '9259' },
|
{ label: '按季度', value: '2' },
|
||||||
{ label: '勐永镇', value: '17787' },
|
{ label: '按年', value: '3' },
|
||||||
{ label: '孟定镇', value: '42610' },
|
|
||||||
{ label: '勐简乡', value: '17788' },
|
|
||||||
{ label: '贺派乡', value: '40161' },
|
|
||||||
{ label: '四排山乡', value: '40163' },
|
|
||||||
{ label: '大兴乡', value: '40159' },
|
|
||||||
]"
|
]"
|
||||||
:is-down="true"
|
:is-down="true"
|
||||||
@command="handleCommand"
|
command="handleCommand"
|
||||||
>
|
>
|
||||||
<template #back>
|
<template #back>
|
||||||
<inputsFive ref="fiveRef" :data="state.data.five" :query="state.queryCode" />
|
<inputsFour :data="state.data.ProductEntity" />
|
||||||
</template>
|
|
||||||
</customBack>
|
|
||||||
</div>
|
|
||||||
<div class="right-charts-item">
|
|
||||||
<customBack top-title="投入品白名单/黑名单" :top-postion="'right'">
|
|
||||||
<template #back>
|
|
||||||
<inputsSix :data="state.data.BlackWhite" />
|
|
||||||
</template>
|
</template>
|
||||||
</customBack>
|
</customBack>
|
||||||
</div>
|
</div>
|
||||||
@ -140,7 +135,7 @@ const loadData = async () => {
|
|||||||
unit: '家',
|
unit: '家',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
InputDetector: [{ JCFGL: 33.33, JCYPPC: 15, zero_count: 13, JCHGL: 86.67, counts: 45 }],
|
InputDetector: [{ JCFGL: 33.33, JCYPPC: 15684, zero_count: 13, JCHGL: 86.67, counts: 45 }],
|
||||||
there: [
|
there: [
|
||||||
{ value: 530, name: '种子' },
|
{ value: 530, name: '种子' },
|
||||||
{ value: 1215, name: '化肥' },
|
{ value: 1215, name: '化肥' },
|
||||||
|
@ -23,13 +23,13 @@ const state = reactive({
|
|||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
className: 'custom-tooltip-container',
|
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow',
|
type: 'shadow',
|
||||||
},
|
},
|
||||||
backgroundColor: 'rgba(0,0,0,0.6);',
|
backgroundColor: 'rgba(0,0,0,0.6);',
|
||||||
borderColor: '#35d0c0',
|
borderColor: '#35d0c0',
|
||||||
|
borderRadius: 8,
|
||||||
formatter: (data) => {
|
formatter: (data) => {
|
||||||
const params = data[0];
|
const params = data[0];
|
||||||
let str = `<div class="custom-echarts-tips" >
|
let str = `<div class="custom-echarts-tips" >
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<section class="_circle">{{ _circleNum }}</section>
|
<section class="_circle">{{ _circleNum }}</section>
|
||||||
<section class="_text">{{ allNum }}万亩</section>
|
<section class="_text">{{ allNum }}万亩</section>
|
||||||
</section>
|
</section>
|
||||||
<section class="_right _scroll">
|
<section ref="scrollRef" class="_right _scroll">
|
||||||
<section
|
<section
|
||||||
v-for="(item, i) in list"
|
v-for="(item, i) in list"
|
||||||
:key="`right_${i}`"
|
:key="`right_${i}`"
|
||||||
@ -23,23 +23,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, computed } from 'vue';
|
import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => {
|
default: () => [],
|
||||||
return [];
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const list = ref([]);
|
const list = ref([]);
|
||||||
const ac = ref(0);
|
const ac = ref(0);
|
||||||
const allNum = ref(0);
|
const allNum = ref(0);
|
||||||
const currNum = ref(0);
|
const currNum = ref(0);
|
||||||
|
const scrollRef = ref(null);
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
() => {
|
() => {
|
||||||
|
allNum.value = 0;
|
||||||
list.value = props.data.map((v, i) => {
|
list.value = props.data.map((v, i) => {
|
||||||
allNum.value += v.value;
|
allNum.value += v.value;
|
||||||
return {
|
return {
|
||||||
@ -49,29 +52,74 @@ watch(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
list.value.sort((a, b) => b.value - a.value);
|
list.value.sort((a, b) => b.value - a.value);
|
||||||
},
|
|
||||||
{
|
if (list.value.length) {
|
||||||
immediate: true,
|
ac.value = list.value[0].id;
|
||||||
|
currNum.value = list.value[0].value;
|
||||||
|
scrollToActive();
|
||||||
|
startAutoScroll();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
function randomColor() {
|
function randomColor() {
|
||||||
let obj = {
|
return `rgb(${rand255()},${rand255()},${rand255()})`;
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
};
|
|
||||||
Object.keys(obj).forEach((key) => {
|
|
||||||
obj[key] = Math.floor(Math.random() * 256);
|
|
||||||
});
|
|
||||||
return `rgb(${obj.r},${obj.g},${obj.b})`;
|
|
||||||
}
|
}
|
||||||
|
function rand255() {
|
||||||
|
return Math.floor(Math.random() * 256);
|
||||||
|
}
|
||||||
|
|
||||||
function handleAc(val) {
|
function handleAc(val) {
|
||||||
ac.value = val;
|
ac.value = val;
|
||||||
currNum.value = list.value.find((v) => v.id == val).value ?? 0;
|
const target = list.value.find((v) => v.id == val);
|
||||||
|
currNum.value = target?.value ?? 0;
|
||||||
|
scrollToActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scrollToActive() {
|
||||||
|
nextTick(() => {
|
||||||
|
const container = scrollRef.value;
|
||||||
|
const activeEl = container?.querySelector('.list_item.active');
|
||||||
|
if (container && activeEl) {
|
||||||
|
const containerTop = container.scrollTop;
|
||||||
|
const containerHeight = container.clientHeight;
|
||||||
|
const elTop = activeEl.offsetTop;
|
||||||
|
const elHeight = activeEl.offsetHeight;
|
||||||
|
if (elTop < containerTop || elTop + elHeight > containerTop + containerHeight) {
|
||||||
|
container.scrollTop = elTop - containerHeight / 2 + elHeight / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const _circleNum = computed(() => {
|
const _circleNum = computed(() => {
|
||||||
let s = ((currNum.value / allNum.value) * 100).toFixed(1);
|
if (!allNum.value) return '0%';
|
||||||
return s + '%';
|
return ((currNum.value / allNum.value) * 100).toFixed(1) + '%';
|
||||||
|
});
|
||||||
|
|
||||||
|
function startAutoScroll() {
|
||||||
|
stopAutoScroll();
|
||||||
|
let index = list.value.findIndex((item) => item.id === ac.value);
|
||||||
|
timer = setInterval(() => {
|
||||||
|
index = (index + 1) % list.value.length;
|
||||||
|
handleAc(list.value[index].id);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAutoScroll() {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
startAutoScroll();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stopAutoScroll();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -156,5 +204,8 @@ const _circleNum = computed(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
._right::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -22,13 +22,13 @@ const state = reactive({
|
|||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
className: 'custom-tooltip-container', // 自定义父容器类名
|
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow',
|
type: 'shadow',
|
||||||
},
|
},
|
||||||
backgroundColor: 'rgba(0,0,0,0.5);',
|
backgroundColor: 'rgba(0,0,0,0.5);',
|
||||||
borderColor: '#35d0c0',
|
borderColor: '#35d0c0',
|
||||||
|
borderRadius: 8,
|
||||||
formatter: (data) => {
|
formatter: (data) => {
|
||||||
const params = data[0];
|
const params = data[0];
|
||||||
let str = `<div class="custom-echarts-tips" >
|
let str = `<div class="custom-echarts-tips" >
|
||||||
@ -42,7 +42,7 @@ const state = reactive({
|
|||||||
barStyle: {
|
barStyle: {
|
||||||
barWidth: 16,
|
barWidth: 16,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 8, // 设置柱子的圆角半径
|
borderRadius: 8,
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
|
@ -15,14 +15,14 @@ const props = defineProps({
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
option: {
|
option: {
|
||||||
k: 0.5,
|
k: 0,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
itemGap: 0.1,
|
itemGap: 0.1,
|
||||||
legendSuffix: '万亩',
|
legendSuffix: '万亩',
|
||||||
itemHeight: 200,
|
itemHeight: 200,
|
||||||
grid3D: {
|
grid3D: {
|
||||||
show: false,
|
show: false,
|
||||||
boxHeight: 1,
|
boxHeight: 3,
|
||||||
top: '0',
|
top: '0',
|
||||||
left: '-20%',
|
left: '-20%',
|
||||||
viewControl: {
|
viewControl: {
|
||||||
|
@ -27,13 +27,13 @@ const state = reactive({
|
|||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
className: 'custom-tooltip-container',
|
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow',
|
type: 'shadow',
|
||||||
},
|
},
|
||||||
backgroundColor: 'rgba(0,0,0,0.6);',
|
backgroundColor: 'rgba(0,0,0,0.6);',
|
||||||
borderColor: '#35d0c0',
|
borderColor: '#35d0c0',
|
||||||
|
borderRadius: 8,
|
||||||
formatter: (data) => {
|
formatter: (data) => {
|
||||||
const params = data[0];
|
const params = data[0];
|
||||||
let str = `<div class="custom-echarts-tips">
|
let str = `<div class="custom-echarts-tips">
|
||||||
|
@ -1,344 +0,0 @@
|
|||||||
let selectedIndex = '';
|
|
||||||
let hoveredIndex = '';
|
|
||||||
option = getPie3D(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'cc',
|
|
||||||
value: 47,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#f77b66',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'aa',
|
|
||||||
value: 44,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#3edce0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bb',
|
|
||||||
value: 32,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#f94e76',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ee',
|
|
||||||
value: 16,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#018ef1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dd',
|
|
||||||
value: 23,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#9e60f9',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
0.59
|
|
||||||
);
|
|
||||||
// 生成扇形的曲面参数方程
|
|
||||||
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
|
|
||||||
// 计算
|
|
||||||
const midRatio = (startRatio + endRatio) / 2;
|
|
||||||
|
|
||||||
const startRadian = startRatio * Math.PI * 2;
|
|
||||||
const endRadian = endRatio * Math.PI * 2;
|
|
||||||
const midRadian = midRatio * Math.PI * 2;
|
|
||||||
|
|
||||||
// 如果只有一个扇形,则不实现选中效果。
|
|
||||||
if (startRatio === 0 && endRatio === 1) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
isSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
k = typeof k !== 'undefined' ? k : 1 / 3;
|
|
||||||
|
|
||||||
// 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
|
|
||||||
const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
|
|
||||||
const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
|
|
||||||
|
|
||||||
// 计算高亮效果的放大比例(未高亮,则比例为 1)
|
|
||||||
const 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(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(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(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;
|
|
||||||
}
|
|
||||||
// 当前图形的高度是Z根据h(每个value的值决定的)
|
|
||||||
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// 生成模拟 3D 饼图的配置项
|
|
||||||
function getPie3D(pieData, internalDiameterRatio) {
|
|
||||||
const series = [];
|
|
||||||
// 总和
|
|
||||||
let sumValue = 0;
|
|
||||||
let startValue = 0;
|
|
||||||
let endValue = 0;
|
|
||||||
const legendData = [];
|
|
||||||
const k =
|
|
||||||
typeof internalDiameterRatio !== 'undefined'
|
|
||||||
? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
|
|
||||||
: 1 / 3;
|
|
||||||
|
|
||||||
// 为每一个饼图数据,生成一个 series-surface 配置
|
|
||||||
for (let i = 0; i < pieData.length; i += 1) {
|
|
||||||
sumValue += pieData[i].value;
|
|
||||||
|
|
||||||
const 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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof pieData[i].itemStyle !== 'undefined') {
|
|
||||||
const { itemStyle } = pieData[i];
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
typeof pieData[i].itemStyle.color !== 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
typeof pieData[i].itemStyle.opacity !== 'undefined'
|
|
||||||
? (itemStyle.opacity = pieData[i].itemStyle.opacity)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
seriesItem.itemStyle = itemStyle;
|
|
||||||
}
|
|
||||||
series.push(seriesItem);
|
|
||||||
}
|
|
||||||
// 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
|
|
||||||
// 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
|
|
||||||
console.log(series);
|
|
||||||
for (let i = 0; i < series.length; i += 1) {
|
|
||||||
endValue = startValue + series[i].pieData.value;
|
|
||||||
|
|
||||||
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,
|
|
||||||
// 我这里做了一个处理,使除了第一个之外的值都是10
|
|
||||||
series[i].pieData.value === series[0].pieData.value ? 35 : 10
|
|
||||||
);
|
|
||||||
|
|
||||||
startValue = endValue;
|
|
||||||
|
|
||||||
legendData.push(series[i].name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 准备待返回的配置项,把准备好的 legendData、series 传入。
|
|
||||||
const option = {
|
|
||||||
// animation: false,
|
|
||||||
tooltip: {
|
|
||||||
formatter: (params) => {
|
|
||||||
if (params.seriesName !== 'mouseoutSeries') {
|
|
||||||
return `${
|
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
xAxis3D: {
|
|
||||||
min: -1,
|
|
||||||
max: 1,
|
|
||||||
},
|
|
||||||
yAxis3D: {
|
|
||||||
min: -1,
|
|
||||||
max: 1,
|
|
||||||
},
|
|
||||||
zAxis3D: {
|
|
||||||
min: -1,
|
|
||||||
max: 1,
|
|
||||||
},
|
|
||||||
grid3D: {
|
|
||||||
show: false,
|
|
||||||
boxHeight: 5,
|
|
||||||
top: '-20%',
|
|
||||||
viewControl: {
|
|
||||||
// 3d效果可以放大、旋转等,请自己去查看官方配置
|
|
||||||
alpha: 35,
|
|
||||||
// beta: 30,
|
|
||||||
rotateSensitivity: 1,
|
|
||||||
zoomSensitivity: 0,
|
|
||||||
panSensitivity: 0,
|
|
||||||
autoRotate: true,
|
|
||||||
distance: 150,
|
|
||||||
},
|
|
||||||
// 后处理特效可以为画面添加高光、景深、环境光遮蔽(SSAO)、调色等效果。可以让整个画面更富有质感。
|
|
||||||
postEffect: {
|
|
||||||
// 配置这项会出现锯齿,请自己去查看官方配置有办法解决
|
|
||||||
enable: false,
|
|
||||||
bloom: {
|
|
||||||
enable: true,
|
|
||||||
bloomIntensity: 0.1,
|
|
||||||
},
|
|
||||||
SSAO: {
|
|
||||||
enable: true,
|
|
||||||
quality: 'medium',
|
|
||||||
radius: 2,
|
|
||||||
},
|
|
||||||
// temporalSuperSampling: {
|
|
||||||
// enable: true,
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
series,
|
|
||||||
};
|
|
||||||
return option;
|
|
||||||
}
|
|
||||||
// 修正取消高亮失败的 bug
|
|
||||||
// 监听 mouseover,近似实现高亮(放大)效果
|
|
||||||
myChart.on('mouseover', function (params) {
|
|
||||||
// 准备重新渲染扇形所需的参数
|
|
||||||
let isSelected;
|
|
||||||
let isHovered;
|
|
||||||
let startRatio;
|
|
||||||
let endRatio;
|
|
||||||
let k;
|
|
||||||
let i;
|
|
||||||
|
|
||||||
// 如果触发 mouseover 的扇形当前已高亮,则不做操作
|
|
||||||
if (hoveredIndex === params.seriesIndex) {
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 否则进行高亮及必要的取消高亮操作
|
|
||||||
} else {
|
|
||||||
// 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
|
|
||||||
if (hoveredIndex !== '') {
|
|
||||||
// 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
|
|
||||||
isSelected = option.series[hoveredIndex].pieStatus.selected;
|
|
||||||
isHovered = false;
|
|
||||||
startRatio = option.series[hoveredIndex].pieData.startRatio;
|
|
||||||
endRatio = option.series[hoveredIndex].pieData.endRatio;
|
|
||||||
k = option.series[hoveredIndex].pieStatus.k;
|
|
||||||
i = option.series[hoveredIndex].pieData.value === option.series[0].pieData.value ? 35 : 10;
|
|
||||||
// 对当前点击的扇形,执行取消高亮操作(对 option 更新)
|
|
||||||
option.series[hoveredIndex].parametricEquation = getParametricEquation(
|
|
||||||
startRatio,
|
|
||||||
endRatio,
|
|
||||||
isSelected,
|
|
||||||
isHovered,
|
|
||||||
k,
|
|
||||||
i
|
|
||||||
);
|
|
||||||
option.series[hoveredIndex].pieStatus.hovered = isHovered;
|
|
||||||
|
|
||||||
// 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
|
|
||||||
hoveredIndex = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
|
|
||||||
if (params.seriesName !== 'mouseoutSeries') {
|
|
||||||
// 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
|
|
||||||
isSelected = option.series[params.seriesIndex].pieStatus.selected;
|
|
||||||
isHovered = true;
|
|
||||||
startRatio = option.series[params.seriesIndex].pieData.startRatio;
|
|
||||||
endRatio = option.series[params.seriesIndex].pieData.endRatio;
|
|
||||||
k = option.series[params.seriesIndex].pieStatus.k;
|
|
||||||
|
|
||||||
// 对当前点击的扇形,执行高亮操作(对 option 更新)
|
|
||||||
option.series[params.seriesIndex].parametricEquation = getParametricEquation(
|
|
||||||
startRatio,
|
|
||||||
endRatio,
|
|
||||||
isSelected,
|
|
||||||
isHovered,
|
|
||||||
k,
|
|
||||||
option.series[params.seriesIndex].pieData.value + 5
|
|
||||||
);
|
|
||||||
option.series[params.seriesIndex].pieStatus.hovered = isHovered;
|
|
||||||
|
|
||||||
// 记录上次高亮的扇形对应的系列号 seriesIndex
|
|
||||||
hoveredIndex = params.seriesIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用更新后的 option,渲染图表
|
|
||||||
myChart.setOption(option);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 修正取消高亮失败的 bug
|
|
||||||
myChart.on('globalout', function () {
|
|
||||||
if (hoveredIndex !== '') {
|
|
||||||
// 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
|
|
||||||
isSelected = option.series[hoveredIndex].pieStatus.selected;
|
|
||||||
isHovered = false;
|
|
||||||
k = option.series[hoveredIndex].pieStatus.k;
|
|
||||||
startRatio = option.series[hoveredIndex].pieData.startRatio;
|
|
||||||
endRatio = option.series[hoveredIndex].pieData.endRatio;
|
|
||||||
// 对当前点击的扇形,执行取消高亮操作(对 option 更新)
|
|
||||||
i = option.series[hoveredIndex].pieData.value === option.series[0].pieData.value ? 35 : 10;
|
|
||||||
option.series[hoveredIndex].parametricEquation = getParametricEquation(
|
|
||||||
startRatio,
|
|
||||||
endRatio,
|
|
||||||
isSelected,
|
|
||||||
isHovered,
|
|
||||||
k,
|
|
||||||
i
|
|
||||||
);
|
|
||||||
option.series[hoveredIndex].pieStatus.hovered = isHovered;
|
|
||||||
|
|
||||||
// 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
|
|
||||||
hoveredIndex = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用更新后的 option,渲染图表
|
|
||||||
myChart.setOption(option);
|
|
||||||
});
|
|
@ -1,11 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<custom-echart-bar :chart-data="state.data" height="100%" :option="state.option" />
|
<custom-echart-bar :chart-data="dataList" height="100%" :option="state.option" />
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive } from 'vue';
|
import { onMounted, reactive } from 'vue';
|
||||||
|
|
||||||
const state = reactive({
|
const dataList = reactive([
|
||||||
data: [
|
|
||||||
{
|
{
|
||||||
name: '其他',
|
name: '其他',
|
||||||
value: 3500,
|
value: 3500,
|
||||||
@ -26,7 +25,47 @@ const state = reactive({
|
|||||||
name: '茶叶',
|
name: '茶叶',
|
||||||
value: 12000,
|
value: 12000,
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
|
|
||||||
|
let titles = reactive([]);
|
||||||
|
let values = reactive([]);
|
||||||
|
const max = Math.max(...dataList);
|
||||||
|
const maxData = dataList.map((item, index) => {
|
||||||
|
return {
|
||||||
|
value: [max, index],
|
||||||
|
name: titles[index],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const barData = dataList.map((item, index) => {
|
||||||
|
return {
|
||||||
|
value: [item, index],
|
||||||
|
name: titles[index],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getValue = () => {
|
||||||
|
let arr = [];
|
||||||
|
for (let i = 0; i < dataList.length; i++) {
|
||||||
|
arr.push(dataList[i].value);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
const getName = () => {
|
||||||
|
let arr = [];
|
||||||
|
for (let i = 0; i < dataList.length; i++) {
|
||||||
|
arr.push(dataList[i].name);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
titles = getName();
|
||||||
|
values = getValue();
|
||||||
|
console.log(titles);
|
||||||
|
console.log(values);
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
option: {
|
option: {
|
||||||
grid: {
|
grid: {
|
||||||
left: '5%',
|
left: '5%',
|
||||||
@ -36,11 +75,13 @@ const state = reactive({
|
|||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
show: false,
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'shadow',
|
type: 'shadow',
|
||||||
},
|
},
|
||||||
backgroundColor: 'rgba(18, 55, 85, 0.8);',
|
className: 'custom-tooltip-container', // 自定义父容器类名
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
borderColor: '#35d0c0',
|
borderColor: '#35d0c0',
|
||||||
formatter: (data) => {
|
formatter: (data) => {
|
||||||
const params = data[0];
|
const params = data[0];
|
||||||
@ -53,19 +94,20 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
barStyle: {
|
barStyle: {
|
||||||
barWidth: 14,
|
barWidth: 14,
|
||||||
|
showBackground: true,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderWidth: 14,
|
borderWidth: 14,
|
||||||
borderRadius: [8, 8, 8, 8], // 设置柱子的圆角半径
|
borderRadius: [10, 10, 10, 10], // 设置柱子的圆角半径
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
x2: 0,
|
x2: 1,
|
||||||
y2: 1,
|
y2: 0,
|
||||||
colorStops: [
|
colorStops: [
|
||||||
{ offset: 0, color: '#35D0C0' },
|
{ offset: 0, color: 'rgba(1,254,253,0)' },
|
||||||
{ offset: 1, color: '#35D0C0' },
|
{ offset: 1, color: '#01fefd' },
|
||||||
],
|
],
|
||||||
global: false,
|
global: false,
|
||||||
},
|
},
|
||||||
@ -76,13 +118,13 @@ const state = reactive({
|
|||||||
show: false,
|
show: false,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dashed',
|
type: 'dashed',
|
||||||
color: '#E5E5E5',
|
color: '#eee',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
show: false,
|
show: false,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#424242',
|
color: '#ffffff',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
show: false,
|
show: false,
|
||||||
@ -93,11 +135,16 @@ const state = reactive({
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: [
|
||||||
|
{
|
||||||
|
// 左侧title
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: ['茶叶', '核桃', '甘蔗', '烟叶', '其他'],
|
data: ['茶叶', '核桃', '甘蔗', '烟叶', '其他'],
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: '#fff',
|
textStyle: {
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '16',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
@ -109,12 +156,123 @@ const state = reactive({
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// 右侧数量
|
||||||
|
type: 'category',
|
||||||
|
inverse: true,
|
||||||
|
offset: 10,
|
||||||
|
axisTick: 'none',
|
||||||
|
axisLine: 'none',
|
||||||
|
show: true,
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '16',
|
||||||
|
},
|
||||||
|
formatter: function (value) {
|
||||||
|
let str = '{title|' + value + '吨}';
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
rich: {
|
||||||
|
title: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: [12000, 8000, 6000, 4000, 3500],
|
||||||
|
},
|
||||||
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
|
name: '值',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
// barWidth: '40%', // 设置柱的宽度
|
zlevel: 1,
|
||||||
// barMinHeight: 2, // 设置柱的最小高度
|
barBorderRadius: 10,
|
||||||
// barGap: '20%', // 设置柱之间的间隙
|
itemStyle: {
|
||||||
|
borderRadius: [10, 10, 10, 10],
|
||||||
|
color: 'rgba(100, 200, 255, 0.3)',
|
||||||
|
},
|
||||||
|
backgroundStyle: {
|
||||||
|
color: 'rgba(100, 200, 255, 0.3)',
|
||||||
|
borderRadius: [10, 10, 10, 10], // 必须与barBorderRadius保持一致
|
||||||
|
},
|
||||||
|
barWidth: 20,
|
||||||
|
data: values,
|
||||||
|
label: {
|
||||||
|
position: [0, -16],
|
||||||
|
align: 'left',
|
||||||
|
show: true,
|
||||||
|
formatter: (params) => {
|
||||||
|
return params.name;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
barMaxWidth: 40,
|
||||||
|
markLine: {
|
||||||
|
label: {
|
||||||
|
color: '#26a69a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '背景',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 20,
|
||||||
|
barGap: '-100%',
|
||||||
|
data: maxData,
|
||||||
|
barBorderRadius: 30,
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
color: 'rgba(105,131,242,.3)',
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
barMaxWidth: 40,
|
||||||
|
markLine: {
|
||||||
|
label: {
|
||||||
|
color: '#26a69a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '内圆',
|
||||||
|
type: 'scatter',
|
||||||
|
hoverAnimation: false,
|
||||||
|
data: barData,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
symbolSize: 22,
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
color: '#26a69a',
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
zlevel: 2,
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '外圆',
|
||||||
|
type: 'scatter',
|
||||||
|
hoverAnimation: false,
|
||||||
|
data: barData,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
symbolSize: 28,
|
||||||
|
symbol: `path://M512 960C264.576 960 64 759.424 64 512S264.576 64 512 64s448 200.576 448 448-200.576 448-448 448z m0-268.8a179.2 179.2 0 1 0 0-358.4 179.2 179.2 0 0 0 0 358.4z`,
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgb(255, 255, 255)',
|
||||||
|
opacity: 1,
|
||||||
|
borderColor: 'rgba(44, 111, 226, 0.2)',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
zlevel: 3,
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section class="line_info" :style="{ '--top': info.show ? '18vh' : '140vh' }">
|
<section class="line_info" :style="{ '--top': info.show ? '18vh' : '140vh' }">
|
||||||
<i class="el-icon-close" @click="handleCloseInfo">X</i>
|
<i class="el-icon-close" @click="handleCloseInfo"></i>
|
||||||
<section>
|
<section>
|
||||||
<section class="info_box">
|
<section class="info_box">
|
||||||
<div>
|
<div>
|
||||||
@ -59,8 +59,8 @@
|
|||||||
<div class="_time">{{ item.time }}</div>
|
<div class="_time">{{ item.time }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<!-- <img src="../../../assets/1.png" style="width: 30%" /> -->
|
<img src="../../../assets/images/trace/testReport.png" style="width: 30%" />
|
||||||
<div>监测报告</div>
|
<div>检测报告</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
@ -104,7 +104,7 @@ function handleCloseInfo() {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 25% 40% 18% 17%;
|
grid-template-columns: 25% 40% 18% 17%;
|
||||||
background-color: rgba(53, 208, 192, 0.3);
|
background-color: rgba(53, 208, 192, 0.3);
|
||||||
color: #c5d0d4;
|
color: #ffffff;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -149,10 +149,10 @@ function handleCloseInfo() {
|
|||||||
}
|
}
|
||||||
.line_info {
|
.line_info {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding: 32px 10px 20px 20px;
|
padding: 24px 10px 16px 16px;
|
||||||
left: 30%;
|
right: 30%;
|
||||||
top: var(--top);
|
top: var(--top);
|
||||||
max-height: 74vh;
|
max-height: 85vh;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
@ -167,14 +167,18 @@ function handleCloseInfo() {
|
|||||||
}
|
}
|
||||||
.el-icon-close {
|
.el-icon-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 8px;
|
right: -40px;
|
||||||
top: 8px;
|
top: 0px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-image: url(../../../assets/images/trace/delete.png);
|
||||||
|
background-size: contain;
|
||||||
}
|
}
|
||||||
> section {
|
> section {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
max-height: calc(74vh - 100px);
|
max-height: calc(85vh - 100px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
|
@ -51,7 +51,7 @@ onMounted(() => {});
|
|||||||
}
|
}
|
||||||
._value {
|
._value {
|
||||||
> span {
|
> span {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
|
@ -36,7 +36,7 @@ const option = reactive({
|
|||||||
autoItemHeight: 2,
|
autoItemHeight: 2,
|
||||||
grid3D: {
|
grid3D: {
|
||||||
show: false,
|
show: false,
|
||||||
boxHeight: 5,
|
boxHeight: 3,
|
||||||
top: '0',
|
top: '0',
|
||||||
left: '-20%',
|
left: '-20%',
|
||||||
viewControl: {
|
viewControl: {
|
||||||
|
@ -51,7 +51,7 @@ import { ref } from 'vue';
|
|||||||
._left {
|
._left {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, computed } from 'vue';
|
import { ref, reactive, onMounted, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
import geoJsonData from './china.json'; // 根据实际情况调整路径;
|
import geoJsonData from './china.json'; // 根据实际情况调整路径;
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -26,6 +27,33 @@ const chinaGeoCoordMap = ref({
|
|||||||
香港: [114.133834, 22.381374],
|
香港: [114.133834, 22.381374],
|
||||||
四川: [104.063707, 30.658753],
|
四川: [104.063707, 30.658753],
|
||||||
河北: [114.511072, 38.054693],
|
河北: [114.511072, 38.054693],
|
||||||
|
北京: [116.46, 39.92],
|
||||||
|
浙江: [120.19, 30.26],
|
||||||
|
山东: [117, 36.65],
|
||||||
|
福建: [119.3, 26.08],
|
||||||
|
上海: [121.48, 31.22],
|
||||||
|
重庆: [106.54, 29.59],
|
||||||
|
江西: [115.89, 28.68],
|
||||||
|
山西: [112.53, 37.87],
|
||||||
|
黑龙江: [126.63, 45.75],
|
||||||
|
陕西: [108.95, 34.27],
|
||||||
|
辽宁: [123.38, 41.8],
|
||||||
|
海南: [110.35, 20.02],
|
||||||
|
湖南: [113, 28.21],
|
||||||
|
宁夏: [106.27, 38.47],
|
||||||
|
湖北: [114.31, 30.52],
|
||||||
|
内蒙古: [111.65, 40.82],
|
||||||
|
天津: [117.2, 39.13],
|
||||||
|
贵州: [106.71, 26.57],
|
||||||
|
甘肃: [103.73, 36.03],
|
||||||
|
江苏: [118.78, 32.04],
|
||||||
|
吉林: [125.35, 43.88],
|
||||||
|
河南: [113.65, 34.76],
|
||||||
|
青海: [101.74, 36.56],
|
||||||
|
安徽: [117.27, 31.86],
|
||||||
|
广西: [108.33, 22.84],
|
||||||
|
西藏: [91.11, 29.97],
|
||||||
|
新疆: [87.68, 43.77],
|
||||||
});
|
});
|
||||||
const chinaDatas = ref([
|
const chinaDatas = ref([
|
||||||
[{ name: '云南', value: 382 }],
|
[{ name: '云南', value: 382 }],
|
||||||
@ -33,7 +61,35 @@ const chinaDatas = ref([
|
|||||||
[{ name: '香港', value: 9256 }],
|
[{ name: '香港', value: 9256 }],
|
||||||
[{ name: '四川', value: 1256 }],
|
[{ name: '四川', value: 1256 }],
|
||||||
[{ name: '河北', value: 382 }],
|
[{ name: '河北', value: 382 }],
|
||||||
|
[{ name: '北京', value: 88 }],
|
||||||
|
[{ name: '浙江', value: 87 }],
|
||||||
|
[{ name: '山东', value: 87 }],
|
||||||
|
[{ name: '福建', value: 87 }],
|
||||||
|
[{ name: '上海', value: 87 }],
|
||||||
|
[{ name: '重庆', value: 87 }],
|
||||||
|
[{ name: '江西', value: 86 }],
|
||||||
|
[{ name: '山西', value: 84 }],
|
||||||
|
[{ name: '黑龙江', value: 83 }],
|
||||||
|
[{ name: '陕西', value: 83 }],
|
||||||
|
[{ name: '辽宁', value: 82 }],
|
||||||
|
[{ name: '海南', value: 82 }],
|
||||||
|
[{ name: '湖南', value: 82 }],
|
||||||
|
[{ name: '宁夏', value: 82 }],
|
||||||
|
[{ name: '湖北', value: 81 }],
|
||||||
|
[{ name: '内蒙古', value: 81 }],
|
||||||
|
[{ name: '天津', value: 80 }],
|
||||||
|
[{ name: '贵州', value: 80 }],
|
||||||
|
[{ name: '甘肃', value: 80 }],
|
||||||
|
[{ name: '江苏', value: 80 }],
|
||||||
|
[{ name: '吉林', value: 80 }],
|
||||||
|
[{ name: '河南', value: 79 }],
|
||||||
|
[{ name: '青海', value: 79 }],
|
||||||
|
[{ name: '广西', value: 78 }],
|
||||||
|
[{ name: '安徽', value: 77 }],
|
||||||
|
[{ name: '新疆', value: 76 }],
|
||||||
|
[{ name: '西藏', value: 76 }],
|
||||||
]);
|
]);
|
||||||
|
// 设置扩散中心
|
||||||
const convertData = (data) => {
|
const convertData = (data) => {
|
||||||
var res = [];
|
var res = [];
|
||||||
for (var i = 0; i < data.length; i++) {
|
for (var i = 0; i < data.length; i++) {
|
||||||
@ -73,6 +129,10 @@ const getSeries = () => {
|
|||||||
width: 1, //尾迹线条宽度
|
width: 1, //尾迹线条宽度
|
||||||
opacity: 1, //尾迹线条透明度
|
opacity: 1, //尾迹线条透明度
|
||||||
curveness: 0.3, //尾迹线条曲直度
|
curveness: 0.3, //尾迹线条曲直度
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#0096FF' },
|
||||||
|
{ offset: 1, color: '#00FF00' },
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: convertData(item[1]),
|
data: convertData(item[1]),
|
||||||
@ -180,7 +240,8 @@ const chartsData = reactive({
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
backgroundColor: 'rgba(18, 55, 85, 0.8);',
|
className: 'custom-tooltip-container', // 自定义父容器类名
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
borderColor: '#35d0c0',
|
borderColor: '#35d0c0',
|
||||||
showDelay: 0,
|
showDelay: 0,
|
||||||
hideDelay: 0,
|
hideDelay: 0,
|
||||||
@ -189,6 +250,7 @@ const chartsData = reactive({
|
|||||||
extraCssText: 'z-index:100',
|
extraCssText: 'z-index:100',
|
||||||
formatter: function (params, ticket, callback) {
|
formatter: function (params, ticket, callback) {
|
||||||
let val = 0;
|
let val = 0;
|
||||||
|
let str = `<div class="custom-echarts-tips"></div>`;
|
||||||
if (typeof params.value == 'number') {
|
if (typeof params.value == 'number') {
|
||||||
val = params.value;
|
val = params.value;
|
||||||
} else {
|
} else {
|
||||||
@ -197,7 +259,7 @@ const chartsData = reactive({
|
|||||||
//根据业务自己拓展要显示的内容
|
//根据业务自己拓展要显示的内容
|
||||||
var res = '';
|
var res = '';
|
||||||
var name = params.name;
|
var name = params.name;
|
||||||
res = "<span style='color:#fff;'>" + name + '</span><br/>数据:' + val;
|
res = `<span style='color:#fff;'> ${name}<br/>数据:${val}</span>`;
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -211,15 +273,27 @@ const chartsData = reactive({
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
roam: true, //是否允许缩放
|
roam: false, //是否允许缩放
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
normal: {
|
normal: {
|
||||||
color: 'rgba(75,255,180,0.3)', //地图背景色
|
color: 'rgba(75,255,180,0.5)', //地图背景色
|
||||||
borderColor: '#4bffb4', //省市边界线00fcff 516a89
|
borderColor: '#4bffb4', //省市边界线00fcff 516a89
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
areaColor: {
|
||||||
|
type: 'radial', // 径向渐变
|
||||||
|
x: 0.5,
|
||||||
|
y: 0.5,
|
||||||
|
r: 0.8,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(17,217,245,0.2)' },
|
||||||
|
{ offset: 1, color: 'rgba(10,209,231,0.6)' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
shadowColor: '#182f68',
|
||||||
|
shadowOffsetY: 2,
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
color: 'rgba(75,255,180,0.5)', //悬浮背景
|
color: 'rgba(10,209,231,0.6)', //悬浮背景
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
:down-title="'1号仓库'"
|
:down-title="'1号仓库'"
|
||||||
:label-field="'label'"
|
:label-field="'label'"
|
||||||
:value-field="'value'"
|
:value-field="'value'"
|
||||||
:down-width="'100px'"
|
:down-width="''"
|
||||||
:options="[
|
:options="[
|
||||||
{ label: '1号仓库', value: '530926' },
|
{ label: '1号仓库', value: '530926' },
|
||||||
{ label: '2号仓库', value: '42611' },
|
{ label: '2号仓库', value: '42611' },
|
||||||
|
1074
package-lock.json
generated
@ -3,7 +3,7 @@ VITE_PORT = 9526
|
|||||||
VITE_APP_MIAN = 'daimp-front-main'
|
VITE_APP_MIAN = 'daimp-front-main'
|
||||||
VITE_APP_MIAN_URL = 'http://localhost:9000'
|
VITE_APP_MIAN_URL = 'http://localhost:9000'
|
||||||
VITE_APP_NAME = 'sub-operation-service'
|
VITE_APP_NAME = 'sub-operation-service'
|
||||||
VITE_APP_BASE_API = '/platform'
|
VITE_APP_BASE_API = '/apis'
|
||||||
VITE_APP_BASE_URL = 'http://192.168.18.99:88'
|
VITE_APP_BASE_URL = 'http://192.168.18.99:88'
|
||||||
VITE_APP_UPLOAD_API = '/uploadApis'
|
VITE_APP_UPLOAD_API = '/uploadApis'
|
||||||
VITE_APP_UPLOAD_URL = 'http://192.168.18.99:9300'
|
VITE_APP_UPLOAD_URL = 'http://192.168.18.99:9300'
|
||||||
|
1
sub-operation-service/.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
nodeLinker: node-modules
|
75
sub-operation-service/auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const computed: typeof import('vue')['computed']
|
||||||
|
const createApp: typeof import('vue')['createApp']
|
||||||
|
const customRef: typeof import('vue')['customRef']
|
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||||
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const h: typeof import('vue')['h']
|
||||||
|
const inject: typeof import('vue')['inject']
|
||||||
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
|
const onMounted: typeof import('vue')['onMounted']
|
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
|
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||||
|
const provide: typeof import('vue')['provide']
|
||||||
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
const readonly: typeof import('vue')['readonly']
|
||||||
|
const ref: typeof import('vue')['ref']
|
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
|
const toRaw: typeof import('vue')['toRaw']
|
||||||
|
const toRef: typeof import('vue')['toRef']
|
||||||
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
|
const toValue: typeof import('vue')['toValue']
|
||||||
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
|
const unref: typeof import('vue')['unref']
|
||||||
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
|
const useId: typeof import('vue')['useId']
|
||||||
|
const useLink: typeof import('vue-router')['useLink']
|
||||||
|
const useModel: typeof import('vue')['useModel']
|
||||||
|
const useRoute: typeof import('vue-router')['useRoute']
|
||||||
|
const useRouter: typeof import('vue-router')['useRouter']
|
||||||
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
|
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||||
|
const watch: typeof import('vue')['watch']
|
||||||
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
}
|
54
sub-operation-service/components.d.ts
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
export {}
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
CenterMap: typeof import('./src/components/centerMap.vue')['default']
|
||||||
|
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
|
||||||
|
copy: typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
|
||||||
|
CostomImg: typeof import('./src/components/costomImg.vue')['default']
|
||||||
|
CustomBack: typeof import('./src/components/customBack.vue')['default']
|
||||||
|
CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.vue')['default']
|
||||||
|
CustomEchartBar: typeof import('./src/components/custom-echart-bar/index.vue')['default']
|
||||||
|
CustomEchartBubble: typeof import('./src/components/custom-echart-bubble/index.vue')['default']
|
||||||
|
CustomEchartColumnLine: typeof import('./src/components/custom-echart-column-line/index.vue')['default']
|
||||||
|
CustomEchartHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/index.vue')['default']
|
||||||
|
CustomEchartLine: typeof import('./src/components/custom-echart-line/index.vue')['default']
|
||||||
|
CustomEchartLineLine: typeof import('./src/components/custom-echart-line-line/index.vue')['default']
|
||||||
|
CustomEchartMaps: typeof import('./src/components/custom-echart-maps/index.vue')['default']
|
||||||
|
CustomEchartMixin: typeof import('./src/components/custom-echart-mixin/index.vue')['default']
|
||||||
|
CustomEchartPictorialBar: typeof import('./src/components/custom-echart-pictorial-bar/index.vue')['default']
|
||||||
|
CustomEchartPie: typeof import('./src/components/custom-echart-pie/index.vue')['default']
|
||||||
|
CustomEchartPie3d: typeof import('./src/components/custom-echart-pie-3d/index.vue')['default']
|
||||||
|
CustomEchartPieGauge: typeof import('./src/components/custom-echart-pie-gauge/index.vue')['default']
|
||||||
|
CustomEchartRadar: typeof import('./src/components/custom-echart-radar/index.vue')['default']
|
||||||
|
CustomEchartScatterBlister: typeof import('./src/components/custom-echart-scatter-blister/index.vue')['default']
|
||||||
|
CustomEchartTriangle: typeof import('./src/components/custom-echart-triangle/index.vue')['default']
|
||||||
|
CustomEchartWaterDroplet: typeof import('./src/components/custom-echart-water-droplet/index.vue')['default']
|
||||||
|
CustomEchartWordCloud: typeof import('./src/components/custom-echart-word-cloud/index.vue')['default']
|
||||||
|
CustomIframe: typeof import('./src/components/custom-iframe/index.vue')['default']
|
||||||
|
CustomImportExcel: typeof import('./src/components/custom-import-excel/index.vue')['default']
|
||||||
|
CustomProgress: typeof import('./src/components/customProgress.vue')['default']
|
||||||
|
CustomRankList: typeof import('./src/components/custom-rank-list/index.vue')['default']
|
||||||
|
CustomRichEditor: typeof import('./src/components/custom-rich-editor/index.vue')['default']
|
||||||
|
CustomScrollBoard: typeof import('./src/components/custom-scroll-board/index.vue')['default']
|
||||||
|
CustomScrollTitle: typeof import('./src/components/custom-scroll-title/index.vue')['default']
|
||||||
|
'CustomScrollTitle copy': typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
|
||||||
|
CustomTableOperate: typeof import('./src/components/custom-table-operate/index.vue')['default']
|
||||||
|
CustomTableTree: typeof import('./src/components/custom-table-tree/index.vue')['default']
|
||||||
|
IndexBak: typeof import('./src/components/page-menu/index-bak.vue')['default']
|
||||||
|
NewHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue')['default']
|
||||||
|
PageLayout: typeof import('./src/components/page-layout/index.vue')['default']
|
||||||
|
PageMenu: typeof import('./src/components/page-menu/index.vue')['default']
|
||||||
|
PagePagination: typeof import('./src/components/page-pagination/index.vue')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
SubTop: typeof import('./src/components/subTop.vue')['default']
|
||||||
|
UpFile: typeof import('./src/components/custom-rich-editor/upFile.js')['default']
|
||||||
|
UpImg: typeof import('./src/components/upImg.vue')['default']
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-operation-service",
|
"name": "digital-agriculture-screen",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@ -7,7 +7,6 @@
|
|||||||
"dev": "vite --mode development",
|
"dev": "vite --mode development",
|
||||||
"build": "vite build --mode production",
|
"build": "vite build --mode production",
|
||||||
"test": "vite build --mode test",
|
"test": "vite build --mode test",
|
||||||
"pre": "vite build --mode pre",
|
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"format": "prettier --write 'src/**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}'",
|
"format": "prettier --write 'src/**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}'",
|
||||||
"eslint": "npx eslint --init",
|
"eslint": "npx eslint --init",
|
||||||
@ -18,11 +17,18 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@smallwei/avue": "^3.6.2",
|
"@smallwei/avue": "^3.6.2",
|
||||||
|
"@vuemap/vue-amap": "^2.0",
|
||||||
|
"@vuemap/vue-amap-loca": "^2.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
|
"echarts-gl": "^2.0.9",
|
||||||
|
"echarts-liquidfill": "^3.1.0",
|
||||||
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"element-plus": "^2.7.2",
|
"element-plus": "^2.7.2",
|
||||||
|
"hls.js": "^1.6.2",
|
||||||
"js-base64": "^3.7.6",
|
"js-base64": "^3.7.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
@ -31,9 +37,11 @@
|
|||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pinia-plugin-persistedstate": "^3.2.1",
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"splitpanes": "^4.0.3",
|
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
"vue-router": "^4.2.5"
|
"vue-cesium": "^3.2.9",
|
||||||
|
"vue-echarts": "^7.0.3",
|
||||||
|
"vue-router": "^4.2.5",
|
||||||
|
"vue3-scroll-seamless": "^1.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.23.7",
|
"@babel/core": "^7.23.7",
|
||||||
|
19
sub-operation-service/src/apis/agricultural.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import request from '@/utils/axios';
|
||||||
|
|
||||||
|
//农资
|
||||||
|
|
||||||
|
//获取农资分类查询数据
|
||||||
|
export function transaction(params = {}) {
|
||||||
|
return request('goods/business/category/transactionType?type=1', {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取农资列表数据
|
||||||
|
export function agriculturalList(params) {
|
||||||
|
return request('goods/business/category/transactionGoodInfo', {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
23
sub-operation-service/src/apis/products.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
// 模拟获取商品列表
|
||||||
|
getProducts: () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve([
|
||||||
|
{ id: 1, name: '耿马绿色蔬菜', imageUrl: 'images/brand/11.png' },
|
||||||
|
{ id: 2, name: '云南高山茶', imageUrl: 'images/brand/12.png' },
|
||||||
|
{ id: 3, name: '新疆大枣', imageUrl: 'images/brand/13.png' },
|
||||||
|
{ id: 4, name: '东北大米', imageUrl: 'images/brand/14.png' },
|
||||||
|
{ id: 5, name: '山东苹果', imageUrl: 'images/brand/15.png' },
|
||||||
|
{ id: 6, name: '四川泡菜', imageUrl: 'images/brand/16.png' },
|
||||||
|
{ id: 7, name: '江苏阳澄湖大闸蟹', imageUrl: 'images/brand/11.png' },
|
||||||
|
{ id: 8, name: '海南椰子', imageUrl: 'images/brand/12.png' },
|
||||||
|
{ id: 9, name: '广东早茶', imageUrl: 'images/brand/13.png' },
|
||||||
|
{ id: 10, name: '北京烤鸭', imageUrl: 'images/brand/14.png' },
|
||||||
|
{ id: 11, name: '西藏青稞酒', imageUrl: 'images/brand/15.png' },
|
||||||
|
{ id: 12, name: '青海牦牛肉', imageUrl: 'images/brand/16.png' },
|
||||||
|
]);
|
||||||
|
}, 500); // 模拟网络延迟
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
18532
sub-operation-service/src/assets/530926geo.json
Normal file
BIN
sub-operation-service/src/assets/images/basic/containerBG.png
Normal file
After Width: | Height: | Size: 2.9 MiB |
BIN
sub-operation-service/src/assets/images/basic/containerBotBG.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
sub-operation-service/src/assets/images/basic/footerBG.png
Normal file
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 10 KiB |
BIN
sub-operation-service/src/assets/images/basic/headerBG.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
sub-operation-service/src/assets/images/basic/leftArrowIcon.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
sub-operation-service/src/assets/images/basic/leftTitleBG.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
sub-operation-service/src/assets/images/basic/rightArrowIcon.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
sub-operation-service/src/assets/images/basic/rightTitleBG.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
sub-operation-service/src/assets/images/basic/tagBG-small.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
sub-operation-service/src/assets/images/basic/tagBG.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
sub-operation-service/src/assets/images/brand/11.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
sub-operation-service/src/assets/images/brand/12.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
sub-operation-service/src/assets/images/brand/13.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
sub-operation-service/src/assets/images/brand/14.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
sub-operation-service/src/assets/images/brand/15.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
sub-operation-service/src/assets/images/brand/1532.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
sub-operation-service/src/assets/images/brand/1533.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
sub-operation-service/src/assets/images/brand/16.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
sub-operation-service/src/assets/images/brand/Apply.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
sub-operation-service/src/assets/images/brand/Authorization.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
sub-operation-service/src/assets/images/brand/product1.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
sub-operation-service/src/assets/images/brand/product2.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
sub-operation-service/src/assets/images/brand/product4.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
sub-operation-service/src/assets/images/brand/product5.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
sub-operation-service/src/assets/images/brand/product6.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
sub-operation-service/src/assets/images/brand/sqzs.png
Normal file
After Width: | Height: | Size: 388 KiB |
BIN
sub-operation-service/src/assets/images/brand/supervision.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
sub-operation-service/src/assets/images/business/b1-1.png
Normal file
After Width: | Height: | Size: 601 KiB |
BIN
sub-operation-service/src/assets/images/business/b1-2.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
sub-operation-service/src/assets/images/business/b2-1.png
Normal file
After Width: | Height: | Size: 601 KiB |
BIN
sub-operation-service/src/assets/images/business/b2-2.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
sub-operation-service/src/assets/images/business/bg_title.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
sub-operation-service/src/assets/images/closing.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
sub-operation-service/src/assets/images/down_1@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/PM2.5.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/bell.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/broadcast.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/danger.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/fall.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/goDown.png
Normal file
After Width: | Height: | Size: 1.6 KiB |