冲突合并
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -23,3 +23,7 @@ dist-ssr | ||||
| *.njsproj | ||||
| *.sln | ||||
| *.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: { | ||||
|     'prettier/prettier': 'error', | ||||
|     'prettier/prettier': [ | ||||
|       'error', | ||||
|       { | ||||
|         endOfLine: 'auto', // 允许自动检测换行符
 | ||||
|       }, | ||||
|     ], | ||||
|     'no-debugger': 'off', | ||||
|     'no-unused-vars': 'off', | ||||
|     'vue/multi-word-component-names': 'off', | ||||
|  | ||||
| @ -43,6 +43,7 @@ declare module 'vue' { | ||||
|     CustomTableOperate: typeof import('./src/components/custom-table-operate/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'] | ||||
|     NewPie: typeof import('./src/components/custom-echart-hyaline-cake/new-pie.vue')['default'] | ||||
|     RouterLink: typeof import('vue-router')['RouterLink'] | ||||
|     RouterView: typeof import('vue-router')['RouterView'] | ||||
|     SubTop: typeof import('./src/components/subTop.vue')['default'] | ||||
|  | ||||
| @ -9,16 +9,13 @@ import { useEcharts } from '@/hooks/useEcharts'; | ||||
| 
 | ||||
| defineOptions({ name: 'NewHyalineCake' }); | ||||
| 
 | ||||
| // 记录当前选中和高亮的系列索引 | ||||
| let selectedIndex = null; | ||||
| let hoveredIndex = null; | ||||
| 
 | ||||
| // 定义组件 props | ||||
| const props = defineProps({ | ||||
|   chartData: { | ||||
|     type: Array, | ||||
|     default: () => [ | ||||
|       // 默认示例数据 | ||||
|       { name: '项目一', value: 60 }, | ||||
|       { name: '项目二', value: 44 }, | ||||
|       { name: '项目三', value: 32 }, | ||||
| @ -27,8 +24,8 @@ const props = defineProps({ | ||||
|   option: { | ||||
|     type: Object, | ||||
|     default: () => ({ | ||||
|       k: 1, // 控制内外径关系的系数,1 表示无内径(实心饼),值越小内径越大 | ||||
|       itemGap: 0.1, // 扇形边缘向外偏移距离比例(用于选中效果),扇形的间距,单位视图坐标,可调 | ||||
|       k: 1, // 控制内外径关系的系数,1 表示无内径,值越小内径越大 | ||||
|       itemGap: 0.1, // 扇形的间距 | ||||
|       itemHeight: 120, // 扇形高度(影响z轴拉伸程度) | ||||
|       autoItemHeight: 0, // 自动计算扇形高度时使用的系数(>0时 itemHeight 失效,使用 autoItemHeight * value ) | ||||
|       opacity: 0.6, // 透明度设置 | ||||
| @ -46,17 +43,79 @@ const props = defineProps({ | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| // 声明组件触发的事件 | ||||
| const emit = defineEmits(['click']); | ||||
| 
 | ||||
| // 绑定到DOM的容器引用 | ||||
| const chartRef = ref(null); | ||||
| // 使用 useEcharts 钩子初始化 ECharts 实例,并获取控制方法 | ||||
| const { setOptions, getInstance } = useEcharts(chartRef); | ||||
| 
 | ||||
| // 存储当前的 ECharts 配置项 | ||||
| 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 || {}), | ||||
| }; | ||||
| 
 | ||||
| // 初始化图表 | ||||
| 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 起点弧度 | ||||
| @ -69,8 +128,7 @@ const chartOption = ref({}); | ||||
|  * @return {Object} parametric 曲面方程 | ||||
|  */ | ||||
| function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h, offsetZ = 0) { | ||||
|   console.log('getParametricEquation params :>> ', startRatio, endRatio, isSelected, isHovered, k, h, offsetZ); | ||||
|   // 中心弧度用于计算偏移方向 | ||||
|   // console.log('getParametricEquation params :>> ', startRatio, endRatio, isSelected, isHovered, k, h, offsetZ); | ||||
|   const midRatio = (startRatio + endRatio) / 2; | ||||
|   const startRadian = startRatio * Math.PI * 2; | ||||
|   const endRadian = endRatio * Math.PI * 2; | ||||
| @ -89,7 +147,6 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h | ||||
|   // 计算高亮时的放大比例(未高亮时为1) | ||||
|   const hoverRate = isHovered ? 1.05 : 1; | ||||
| 
 | ||||
|   // 返回 parametric 曲面方程 | ||||
|   return { | ||||
|     u: { | ||||
|       // u 参数控制饼的周向角度:从 -π 到 3π,可以完整绘制一圈 | ||||
| @ -155,10 +212,6 @@ function getPie3D(pieData, internalDiameterRatio) { | ||||
| 
 | ||||
|   const k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3; | ||||
| 
 | ||||
|   // 计算总值 | ||||
|   // pieData.forEach((item) => { | ||||
|   //   sumValue += item.value; | ||||
|   // }); | ||||
|   // 构建每个扇形的 series 数据 | ||||
|   for (let i = 0; i < pieData.length; i += 1) { | ||||
|     sumValue += pieData[i].value; | ||||
| @ -169,13 +222,8 @@ function getPie3D(pieData, internalDiameterRatio) { | ||||
|       parametric: true, | ||||
|       wireframe: { show: false }, | ||||
|       pieData: pieData[i], | ||||
|       // itemStyle: { | ||||
|       //   opacity: props.option.opacity, | ||||
|       //   borderRadius: 300, | ||||
|       //   borderColor: '#fff', | ||||
|       // } | ||||
|       pieStatus: { | ||||
|         selected: true, | ||||
|         selected: false, | ||||
|         hovered: false, | ||||
|         k, | ||||
|       }, | ||||
| @ -189,9 +237,10 @@ function getPie3D(pieData, internalDiameterRatio) { | ||||
|     } | ||||
|     series.push(seriesItem); | ||||
|   } | ||||
| 
 | ||||
|   // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数, | ||||
|   // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。 | ||||
|   console.log(series); | ||||
|   // console.log(series); | ||||
|   for (let i = 0; i < series.length; i += 1) { | ||||
|     endValue = startValue + series[i].pieData.value; | ||||
| 
 | ||||
| @ -212,6 +261,34 @@ function getPie3D(pieData, internalDiameterRatio) { | ||||
| 
 | ||||
|     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 = { | ||||
| @ -240,12 +317,12 @@ function getPie3D(pieData, internalDiameterRatio) { | ||||
|     // 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}`; | ||||
|         // 只针对 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 ''; | ||||
|       }, | ||||
| @ -276,23 +353,6 @@ function getPie3D(pieData, internalDiameterRatio) { | ||||
|         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, | ||||
|   }; | ||||
| @ -301,7 +361,7 @@ function getPie3D(pieData, internalDiameterRatio) { | ||||
| 
 | ||||
| // 监听 mouseover,近似实现高亮(放大)效果 | ||||
| function handleMouseover(params) { | ||||
|   console.log('mouseover'); | ||||
|   // console.log('mouseover'); | ||||
|   const idx = params.seriesIndex; | ||||
|   const series = chartOption.value.series; | ||||
| 
 | ||||
| @ -345,7 +405,7 @@ function updateSeriesHover(index, toHover) { | ||||
|   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 + 5 : baseHeight; | ||||
|   const newHeight = toHover ? baseHeight + 80 : baseHeight; | ||||
|   // 更新 parametricEquation | ||||
|   item.parametricEquation = getParametricEquation(start, end, isSelected, toHover, k, newHeight); | ||||
|   // 更新状态 | ||||
| @ -355,34 +415,6 @@ function updateSeriesHover(index, toHover) { | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| // 初始化图表 | ||||
| function initChart() { | ||||
|   // 生成基础的3D饼图配置(也就是默认的配置) | ||||
|   const baseOption = getPie3D(props.chartData, props.option.k); | ||||
|   // 合并用户配置,确保不修改原始对象 | ||||
|   // const finalOption = Object.assign({}, baseOption, cloneDeep(props.option || {})); | ||||
|   const finalOption = merge({}, baseOption, cloneDeep(props.option || {})); | ||||
|   chartOption.value = finalOption; | ||||
|   // 设置图表配置 | ||||
|   setOptions(chartOption.value); | ||||
|   // 等待 DOM + ECharts 初始化完成 | ||||
|   nextTick(() => { | ||||
|     const chart = getInstance(); | ||||
|     if (!chart) { | ||||
|       console.warn('ECharts 实例未初始化,事件绑定失败'); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 避免重复绑定事件 | ||||
|     // chart.off('click'); | ||||
|     chart.off('mouseover'); | ||||
|     chart.off('globalout'); | ||||
| 
 | ||||
|     // chart.on('click', handleClick); | ||||
|     chart.on('mouseover', handleMouseover); | ||||
|     chart.on('globalout', handleGlobalout); | ||||
|   }); | ||||
| } | ||||
| function handleClick(params) { | ||||
|   // 如果点击的是透明辅助环,则忽略 | ||||
|   if (params.seriesName === 'mouseoutSeries') return; | ||||
| @ -418,35 +450,10 @@ function handleClick(params) { | ||||
|   series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h); | ||||
|   series[idx].pieStatus.selected = isSelected; | ||||
| 
 | ||||
|   // 更新记录选中的系列索引 | ||||
|   selectedIndex = isSelected ? idx : null; | ||||
| 
 | ||||
|   // 更新图表配置 | ||||
|   setOptions(optionVal); | ||||
|   // 触发组件 click 事件供父组件使用 | ||||
|   emit('click', params); | ||||
| } | ||||
| // 在函数顶部声明(确保作用域覆盖所有需要的地方) | ||||
| window.debugZValues = { | ||||
|   current: null, | ||||
|   history: [], | ||||
| }; | ||||
| 
 | ||||
| // 组件挂载后绑定事件 | ||||
| onMounted(() => { | ||||
|   initChart(); | ||||
| }); | ||||
| 
 | ||||
| // 监听数据或配置变更,重新初始化图表 | ||||
| watch( | ||||
|   [() => props.chartData, () => props.option], | ||||
|   () => { | ||||
|     if (props.chartData && props.chartData.length) { | ||||
|       initChart(); | ||||
|     } | ||||
|   }, | ||||
|   { immediate: true, deep: true } | ||||
| ); | ||||
| </script> | ||||
| 
 | ||||
| <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> | ||||
| @ -42,20 +42,49 @@ const state = reactive({ | ||||
|       icon: 'rect', | ||||
|       left: 'center', | ||||
|       textStyle: { | ||||
|         color: '#fff', // 根据你的主题调整颜色 | ||||
|         fontSize: 14, | ||||
|       }, | ||||
|       itemStyle: { | ||||
|         display: 'float', | ||||
|         top: '120px', | ||||
|         backgroundColor: 'rgba(255, 255, 255, 0.2)', | ||||
|         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: 76, name: '产业运营平台' }, | ||||
|     { value: 24, name: '其它', floatZ: 1 }, | ||||
|     { value: 221.8, name: '产业运营平台' }, | ||||
|     { value: 70.01, name: '其它', floatZ: 1 }, | ||||
|   ], | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| @ -43,6 +43,7 @@ const chartConfig = ref({ | ||||
|       ['圆茄种苗', '0.3元/棵', '0.4元/棵'], | ||||
|       ['高氮复合肥', '1850元/吨', '1980元/吨'], | ||||
|       ['硫酸钾', '1250元/吨', '1380元/吨'], | ||||
|       ['高氮复合肥', '1850元/吨', '1980元/吨'], | ||||
|       ['西红柿种苗', '0.3元/棵', '0.4元/棵'], | ||||
|     ], | ||||
|     // 样式配置 | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
|       <section class="_circle">{{ _circleNum }}</section> | ||||
|       <section class="_text">{{ allNum }}万亩</section> | ||||
|     </section> | ||||
|     <section class="_right _scroll"> | ||||
|     <section ref="scrollRef" class="_right _scroll"> | ||||
|       <section | ||||
|         v-for="(item, i) in list" | ||||
|         :key="`right_${i}`" | ||||
| @ -23,23 +23,26 @@ | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { ref, watch, computed } from 'vue'; | ||||
| import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   data: { | ||||
|     type: Array, | ||||
|     default: () => { | ||||
|       return []; | ||||
|     }, | ||||
|     default: () => [], | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const list = ref([]); | ||||
| const ac = ref(0); | ||||
| const allNum = ref(0); | ||||
| const currNum = ref(0); | ||||
| const scrollRef = ref(null); | ||||
| let timer = null; | ||||
| 
 | ||||
| watch( | ||||
|   () => props.data, | ||||
|   () => { | ||||
|     allNum.value = 0; | ||||
|     list.value = props.data.map((v, i) => { | ||||
|       allNum.value += v.value; | ||||
|       return { | ||||
| @ -49,29 +52,74 @@ watch( | ||||
|       }; | ||||
|     }); | ||||
|     list.value.sort((a, b) => b.value - a.value); | ||||
| 
 | ||||
|     if (list.value.length) { | ||||
|       ac.value = list.value[0].id; | ||||
|       currNum.value = list.value[0].value; | ||||
|       scrollToActive(); | ||||
|       startAutoScroll(); | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     immediate: true, | ||||
|   } | ||||
|   { immediate: true } | ||||
| ); | ||||
| 
 | ||||
| function randomColor() { | ||||
|   let obj = { | ||||
|     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})`; | ||||
|   return `rgb(${rand255()},${rand255()},${rand255()})`; | ||||
| } | ||||
| function rand255() { | ||||
|   return Math.floor(Math.random() * 256); | ||||
| } | ||||
| 
 | ||||
| function handleAc(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(() => { | ||||
|   let s = ((currNum.value / allNum.value) * 100).toFixed(1); | ||||
|   return s + '%'; | ||||
|   if (!allNum.value) return '0%'; | ||||
|   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> | ||||
| 
 | ||||
| @ -156,5 +204,8 @@ const _circleNum = computed(() => { | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   ._right::-webkit-scrollbar { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | ||||
| @ -17,7 +17,7 @@ const state = reactive({ | ||||
|   option: { | ||||
|     k: 0, | ||||
|     opacity: 1, | ||||
|     itemGap: 0.2, | ||||
|     itemGap: 0.1, | ||||
|     legendSuffix: '万亩', | ||||
|     itemHeight: 200, | ||||
|     grid3D: { | ||||
|  | ||||
| @ -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); | ||||
| }); | ||||
							
								
								
									
										1074
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						| @ -3,7 +3,7 @@ VITE_PORT = 9526 | ||||
| VITE_APP_MIAN = 'daimp-front-main' | ||||
| VITE_APP_MIAN_URL = 'http://localhost:9000' | ||||
| 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_UPLOAD_API = '/uploadApis' | ||||
| VITE_APP_UPLOAD_URL = 'http://192.168.18.99:9300' | ||||
|  | ||||
							
								
								
									
										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, | ||||
|   "version": "0.0.1", | ||||
|   "type": "module", | ||||
| @ -7,7 +7,6 @@ | ||||
|     "dev": "vite --mode development", | ||||
|     "build": "vite build --mode production", | ||||
|     "test": "vite build --mode test", | ||||
|     "pre": "vite build --mode pre", | ||||
|     "preview": "vite preview", | ||||
|     "format": "prettier --write 'src/**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}'", | ||||
|     "eslint": "npx eslint --init", | ||||
| @ -18,10 +17,16 @@ | ||||
|   "dependencies": { | ||||
|     "@element-plus/icons-vue": "^2.3.1", | ||||
|     "@smallwei/avue": "^3.6.2", | ||||
|     "@vuemap/vue-amap": "^2.0", | ||||
|     "@vuemap/vue-amap-loca": "^2.0", | ||||
|     "@wangeditor/editor": "^5.1.23", | ||||
|     "@wangeditor/editor-for-vue": "^5.1.12", | ||||
|     "animate.css": "^4.1.1", | ||||
|     "axios": "^1.6.5", | ||||
|     "echarts": "^5.6.0", | ||||
|     "echarts-gl": "^2.0.9", | ||||
|     "echarts-liquidfill": "^3.1.0", | ||||
|     "echarts-wordcloud": "^2.1.0", | ||||
|     "element-plus": "^2.7.2", | ||||
|     "hls.js": "^1.6.2", | ||||
|     "js-base64": "^3.7.6", | ||||
| @ -32,9 +37,11 @@ | ||||
|     "pinia": "^2.1.7", | ||||
|     "pinia-plugin-persistedstate": "^3.2.1", | ||||
|     "screenfull": "^6.0.2", | ||||
|     "splitpanes": "^4.0.3", | ||||
|     "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": { | ||||
|     "@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, | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										
											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/1 (1).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 61 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/1 (2).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 55 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/1 (3).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 59 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/1 (4).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 53 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/1 (5).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 46 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/1 (6).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 62 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/2 (1).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/2 (2).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/2 (3).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 22 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/p (1).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/p (3).png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 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/生产管理控制.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/brand/用户信息.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 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/识别二维码.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 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/normal.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/smartFarm/分光器.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/smartFarm/悬浮物.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/smartFarm/水质溶解氧.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/smartFarm/水质电导率.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/smartFarm/浊度.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/smartFarm/温度.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/smartFarm/酸碱度.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/userCenter/menu1-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 600 B | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/userCenter/menu1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/userCenter/menu2-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 669 B | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/userCenter/menu2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 804 B | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/userCenter/menu3-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 964 B | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/userCenter/menu3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 772 B | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/bottombj.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/gmmap.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 170 KiB | 
| After Width: | Height: | Size: 7.4 KiB | 
| After Width: | Height: | Size: 8.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/home/area.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.7 KiB | 
| After Width: | Height: | Size: 7.1 KiB | 
| After Width: | Height: | Size: 7.4 KiB | 
| After Width: | Height: | Size: 6.1 KiB | 
| After Width: | Height: | Size: 6.7 KiB | 
| After Width: | Height: | Size: 47 KiB | 
| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/home/nav.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
| After Width: | Height: | Size: 5.5 KiB | 
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 3.1 KiB | 
| After Width: | Height: | Size: 7.4 KiB | 
| After Width: | Height: | Size: 1.7 KiB | 
| After Width: | Height: | Size: 5.2 KiB | 
| After Width: | Height: | Size: 7.4 KiB | 
| After Width: | Height: | Size: 7.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/home1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 78 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/home2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 82 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/home3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 81 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/home4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 77 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/homeb.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 812 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/hraderbg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 69 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/mapopup.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 38 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/mapopup1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 66 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/marker.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								sub-operation-service/src/assets/images/vsualized/screenbg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 285 KiB | 
							
								
								
									
										18532
									
								
								sub-operation-service/src/components/530926geo.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										279
									
								
								sub-operation-service/src/components/centerMap.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,279 @@ | ||||
| <template> | ||||
|   <div class="map-center-warp"> | ||||
|     <!-- <img :src="getAssetsFile('images/vsualized/gmmap.png')" class="map-img" /> --> | ||||
|     <div class="map-pos"> | ||||
|       <custom-echart-maps height="100%" width="100%" :option="chartsData.option" :geo="geoData" :name="mapName" @click="mapClick" /> | ||||
|     </div> | ||||
| 
 | ||||
|     <el-dialog | ||||
|       v-model="isShow" | ||||
|       :title="currentMap.name + dialogTitle" | ||||
|       :width="dialogWidth" | ||||
|       :style="{ 'background-image': 'url(' + getAssetsFile(bgImageVal) + ')' }" | ||||
|       :show-close="false" | ||||
|       :before-close="handleClose" | ||||
|       custom-class="map-info-dialog" | ||||
|     > | ||||
|       <template #header="{ close, titleId, titleClass }"> | ||||
|         <slot name="header"></slot> | ||||
|       </template> | ||||
|       <slot name="dialogContent"></slot> | ||||
|     </el-dialog> | ||||
|   </div> | ||||
| </template> | ||||
| <script setup> | ||||
| import { isEmpty, getAssetsFile } from '@/utils'; | ||||
| import { ref, reactive, onMounted, computed } from 'vue'; | ||||
| import { useRoute } from 'vue-router'; | ||||
| import geoJsonData from '../components/530926geo.json'; // 根据实际情况调整路径 | ||||
| const route = useRoute(); | ||||
| const props = defineProps({ | ||||
|   dialogTitle: { | ||||
|     type: String, | ||||
|     default: '首页', | ||||
|   }, | ||||
|   markerData: { | ||||
|     type: Array, | ||||
|     default: () => { | ||||
|       return []; | ||||
|     }, | ||||
|   }, | ||||
|   bgImage: { | ||||
|     type: String, | ||||
|     default: 'images/vsualized/mapopup.png', | ||||
|   }, | ||||
|   dialogWidth: { | ||||
|     type: Number, | ||||
|     default: 360, | ||||
|   }, | ||||
| }); | ||||
| var iconUrl = getAssetsFile('images/vsualized/gmmap2.png').href; | ||||
| let bgImageVal = computed(() => { | ||||
|   return props.bgImage; | ||||
| }); | ||||
| const isShow = ref(false); | ||||
| let geoData = geoJsonData; | ||||
| let mapName = ref('ZJ' + route.name); | ||||
| let mapConfig = reactive({ | ||||
|   map: mapName.value, | ||||
|   zoom: 1, | ||||
|   viewControl: { | ||||
|     distance: 115, | ||||
|     alpha: 60, | ||||
|     beta: 0, | ||||
|     minBeta: -360, | ||||
|     maxBeta: 720, | ||||
|     // 限制视角,使不能旋转缩放平移 | ||||
|     // rotateSensitivity: 0, | ||||
|     // zoomSensitivity: 0, | ||||
|     // panSensitivity: 0, | ||||
|   }, | ||||
|   // itemStyle: { | ||||
|   //   // 三维地理坐标系组件 中三维图形的视觉属性,包括颜色,透明度,描边等。 | ||||
|   //   color: 'rgba(75,255,180,0.2)', // 地图板块的颜色 | ||||
|   //   opacity: 1, // 图形的不透明度 [ default: 1 ] | ||||
|   //   borderWidth: 1.5, // (地图板块间的分隔线)图形描边的宽度。加上描边后可以更清晰的区分每个区域   [ default: 0 ] | ||||
|   //   borderColor: '#4bffb4', // 图形描边的颜色。[ default: #333 ] | ||||
|   // }, | ||||
|   itemStyle: { | ||||
|     normal: { | ||||
|       borderColor: '#4bffb4', // 设置镇边界的颜色 | ||||
|       borderWidth: 1, // 设置镇边界的宽度 | ||||
|       areaColor: 'rgba(75,255,180,0.2)', // 设置背景透明,只显示边界 | ||||
|     }, | ||||
|     emphasis: { | ||||
|       borderColor: '#4bffb4', | ||||
|       borderWidth: 2, | ||||
|       areaColor: 'rgba(75,255,180,0.5)', // 设置背景透明,只显示边界 | ||||
|       label: { | ||||
|         color: '#fff', | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   select: { | ||||
|     // 选中样式:ml-citation{ref="2,7" data="citationList"} | ||||
|     itemStyle: { | ||||
|       areaColor: 'rgba(75,255,180,0.6)', | ||||
|       borderColor: '#4bffb4', | ||||
|       borderWidth: 2, | ||||
|     }, | ||||
|     label: { | ||||
|       color: '#fff', | ||||
|     }, | ||||
|   }, | ||||
| 
 | ||||
|   label: { | ||||
|     show: true, | ||||
|     distance: 0, | ||||
|     color: '#fff', | ||||
|     padding: [6, 4, 2, 4], | ||||
|     borderRadius: 4, | ||||
|     textStyle: { | ||||
|       fontSize: 12, | ||||
|       color: '#fff', // 地图初始化区域字体颜色 | ||||
|       borderWidth: 0, | ||||
|       borderColor: '#000', | ||||
|     }, | ||||
|   }, | ||||
|   // emphasis: { | ||||
|   //   //高亮状态的效果 | ||||
|   //   label: { | ||||
|   //     show: true, | ||||
|   //     color: '#fff', | ||||
|   //   }, | ||||
|   //   itemStyle: { | ||||
|   //     color: 'rgba(75,255,180,0.3)', // 地图板块的颜色 | ||||
|   //   }, | ||||
|   // }, | ||||
| }); | ||||
| 
 | ||||
| const chartsData = reactive({ | ||||
|   option: { | ||||
|     title: { | ||||
|       text: '', | ||||
|       left: 'center', | ||||
|     }, | ||||
|     tooltip: { | ||||
|       trigger: 'item', | ||||
|       formatter: function (params) { | ||||
|         if (params.seriesType === 'effectScatter') { | ||||
|           return `${params.name}: (${params.value[0]}, ${params.value[1]})`; | ||||
|         } | ||||
|         return params.name; | ||||
|       }, | ||||
|     }, | ||||
|     toolbox: { | ||||
|       show: false, | ||||
|       orient: 'vertical', | ||||
|       left: 'right', | ||||
|       top: 'center', | ||||
|       feature: { | ||||
|         // dataView: { readOnly: false }, | ||||
|         // restore: {}, | ||||
|         // saveAsImage: {}, | ||||
|       }, | ||||
|     }, | ||||
|     geo: { | ||||
|       roam: true, | ||||
|       left: '30px', | ||||
|       show: false, | ||||
|       zlevel: -1, // 必须设置, | ||||
|       ...mapConfig, | ||||
|     }, | ||||
|     series: [ | ||||
|       { | ||||
|         roam: true, | ||||
|         type: 'map', | ||||
|         left: '30px', | ||||
|         ...mapConfig, | ||||
|       }, | ||||
|       { | ||||
|         name: '闪烁散点', | ||||
|         type: 'effectScatter', // 使用 effectScatter 类型 | ||||
|         coordinateSystem: 'geo', | ||||
|         data: props.markerData, | ||||
|         symbolSize: function (val) { | ||||
|           return val[2] ? val[2] / 10 : 10; // 如果没有数值,默认大小 | ||||
|         }, | ||||
|         label: { | ||||
|           formatter: '{b}', | ||||
|           position: 'right', | ||||
|           show: false, | ||||
|         }, | ||||
|         rippleEffect: { | ||||
|           period: 4, // 波纹动画周期 | ||||
|           scale: 3, // 波纹缩放比例 | ||||
|           brushType: 'stroke', // 波纹绘制方式:'stroke' 或 'fill' | ||||
|         }, | ||||
|         hoverAnimation: true, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| let currentMap = reactive({}); | ||||
| 
 | ||||
| const mapClick = (data) => { | ||||
|   if (props.markerData.length && props.markerData.length > 0) { | ||||
|     if (data.seriesType == 'effectScatter') { | ||||
|       isShow.value = true; | ||||
|       currentMap = data; | ||||
|       emit('mapclick', currentMap); | ||||
|     } | ||||
|   } else { | ||||
|     isShow.value = true; | ||||
|     currentMap = data; | ||||
|     emit('mapclick', currentMap); | ||||
|   } | ||||
| }; | ||||
| const handleClose = () => { | ||||
|   isShow.value = false; | ||||
| }; | ||||
| 
 | ||||
| const emit = defineEmits(['mapclick']); | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| div { | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| ::v-deep() { | ||||
|   .el-dialog { | ||||
|     background: url(iconUrl) no-repeat left top; | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: cover; | ||||
|     border-radius: 8px !important; | ||||
|     min-height: 200px; | ||||
|     max-height: 500px; | ||||
|     overflow-y: auto; | ||||
|     background-size: 100% 100%; | ||||
|     padding: 16px; | ||||
|     margin-top: 15%; | ||||
|   } | ||||
|   .el-dialog__header { | ||||
|     margin-top: 10px; | ||||
|     text-align: left; | ||||
|     padding-left: 48px; | ||||
|     .el-dialog__title, | ||||
|     i { | ||||
|       color: #fff !important; | ||||
|     } | ||||
|     .el-dialog__headerbtn { | ||||
|       top: 8px !important; | ||||
|     } | ||||
|   } | ||||
|   .map-dialog-my-header { | ||||
|     margin-top: 4px; | ||||
|     display: inline-flex; | ||||
|     justify-content: space-between; | ||||
|     h4 { | ||||
|       font-weight: normal !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| .map-center-warp { | ||||
|   width: 100%; | ||||
|   text-align: center; | ||||
|   position: relative; | ||||
|   height: 90%; | ||||
|   .map-img { | ||||
|     width: 80%; | ||||
|     height: 80%; | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     left: 50%; | ||||
|     object-fit: contain; | ||||
|     transform: translateX(-50%); | ||||
|     max-width: 1000px; | ||||
|     max-height: 1000px; | ||||
|   } | ||||
|   .map-pos { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     left: 50%; | ||||
|     transform: translateX(-50%); | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										54
									
								
								sub-operation-service/src/components/code-dialog/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,54 @@ | ||||
| <template> | ||||
|   <el-dialog | ||||
|     v-model="state.visible" | ||||
|     draggable | ||||
|     title="溯源码" | ||||
|     width="40%" | ||||
|     :close-on-click-modal="false" | ||||
|     :close-on-press-escape="false" | ||||
|     @close="onClose" | ||||
|   > | ||||
|     <div class="code-panel"> | ||||
|       <div class="code-panel-picture"> | ||||
|         <el-image style="width: 200px; height: 200px" :src="row.orCodeUrl" fit="cover" lazy /> | ||||
|       </div> | ||||
|       <el-button type="primary" @click="downloadFile(row.orCodeUrl, `${row.productName}-溯源码.png`, 'image')"> 下载溯源码</el-button> | ||||
|     </div> | ||||
|   </el-dialog> | ||||
| </template> | ||||
| <script setup name="code-dialog"> | ||||
| import { reactive } from 'vue'; | ||||
| import { downloadFile } from '@/utils'; | ||||
| const props = defineProps({ | ||||
|   row: { | ||||
|     type: Object, | ||||
|     default: () => {}, | ||||
|   }, | ||||
| }); | ||||
| const emit = defineEmits(['on-close']); | ||||
| 
 | ||||
| const state = reactive({ | ||||
|   visible: false, | ||||
| }); | ||||
| 
 | ||||
| const onClose = () => { | ||||
|   state.visible = false; | ||||
| }; | ||||
| 
 | ||||
| defineExpose({ | ||||
|   show: () => { | ||||
|     state.visible = true; | ||||
|   }, | ||||
|   hide: () => { | ||||
|     onClose(); | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .code { | ||||
|   &-panel { | ||||
|     padding-bottom: 40px; | ||||
|     text-align: center; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @ -79,24 +79,23 @@ const toPreview = () => { | ||||
|   } | ||||
|   .viewer-btn-warp { | ||||
|     position: absolute; | ||||
|     top: 50%; | ||||
|     left: 0; | ||||
|     display: none; | ||||
|     width: 100%; | ||||
|     text-align: center; | ||||
|     top: 50%; | ||||
|     left: 0; | ||||
|     transform: translateY(-50%); | ||||
|   } | ||||
|   .viewer-btn { | ||||
|     background: $color-balck-mask; | ||||
|     display: inline-block; | ||||
|     padding: 4px 8px; | ||||
|     font-size: 14px; | ||||
|     border-radius: 16px; | ||||
|     color: $color-fff; | ||||
|     font-size: 14px; | ||||
|     background: $color-balck-mask; | ||||
|     cursor: pointer; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .c-custom-img-warp:hover { | ||||
|   .viewer-btn-warp { | ||||
|     display: inline-block !important; | ||||
|  | ||||
| @ -0,0 +1,220 @@ | ||||
| <template> | ||||
|   <div class="carousel" style="width: 500px"> | ||||
|     <el-carousel | ||||
|       v-if="type === 'image'" | ||||
|       ref="carouselRef" | ||||
|       :interval="option.interval" | ||||
|       :arrow="option.arrow" | ||||
|       :indicator-position="option.indicatorPosition" | ||||
|       @change="handleChange" | ||||
|     > | ||||
|       <el-carousel-item v-for="(item, index) in data" :key="index"> | ||||
|         <img :src="item.image" style="width: 100%; height: auto" /> | ||||
|       </el-carousel-item> | ||||
|     </el-carousel> | ||||
| 
 | ||||
|     <div v-if="type === 'video'" class="carousel-video"> | ||||
|       <!-- <img :src="state.videoPicture" class="carousel-video-picture" /> --> | ||||
|       <video ref="videoRef" controls class="carousel-video-video" width="100%" height="100%" @ended="pauseVideo"> | ||||
|         <source :src="state.videoUrl" type="video/mp4" /> | ||||
|         Your browser does not support the video tag. | ||||
|       </video> | ||||
|       <!-- <span class="carousel-video-btn" @click="handlePlay"> | ||||
|         <el-icon><VideoPlay /></el-icon> | ||||
|       </span> --> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="carousel-container"> | ||||
|       <span class="carousel-arrow carousel-arrow-left" @click="handleLeftClick"> | ||||
|         <el-icon><ArrowLeftBold /></el-icon> | ||||
|       </span> | ||||
|       <el-scrollbar ref="scrollbarRef" class="carousel-list"> | ||||
|         <div | ||||
|           v-for="(item, index) in data" | ||||
|           :key="index" | ||||
|           :class="`carousel-list-item ${state.current === index ? 'active' : ''}`" | ||||
|           @click="handleClick(index)" | ||||
|         > | ||||
|           <el-image style="width: 100px; height: 100px" :src="item.image ?? item" fit="cover" /> | ||||
|         </div> | ||||
|       </el-scrollbar> | ||||
|       <span class="carousel-arrow carousel-arrow-right" @click="handleRightClick"> | ||||
|         <el-icon><ArrowRightBold /></el-icon> | ||||
|       </span> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script setup name="custom-carousel-picture"> | ||||
| import { reactive, ref } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   data: { type: Array, default: () => [] }, | ||||
|   type: { type: String, default: 'image' }, | ||||
|   option: { | ||||
|     type: Object, | ||||
|     default: () => { | ||||
|       return { | ||||
|         height: '', | ||||
|         initialIndex: 0, | ||||
|         autoplay: true, | ||||
|         loop: true, | ||||
|         interval: 3000, | ||||
|         arrow: 'never', | ||||
|         indicatorPosition: 'none', | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const emit = defineEmits(['']); | ||||
| 
 | ||||
| const carouselRef = ref(null); | ||||
| const scrollbarRef = ref(null); | ||||
| const videoRef = ref(null); | ||||
| 
 | ||||
| const state = reactive({ | ||||
|   current: 0, | ||||
|   isReady: false, | ||||
|   videoPicture: '', | ||||
|   videoUrl: '', | ||||
| }); | ||||
| 
 | ||||
| const handleChange = (cur, last) => { | ||||
|   state.current = cur; | ||||
| }; | ||||
| 
 | ||||
| const handleLeftClick = () => { | ||||
|   // const index = carouselRef.value.activeIndex; | ||||
|   // carouselRef.value.setActiveItem(index + 1); | ||||
|   scrollbarRef.value.setScrollLeft(scrollbarRef.value.wrapRef.scrollLeft - 120); | ||||
| }; | ||||
| 
 | ||||
| const handleRightClick = () => { | ||||
|   // const index = carouselRef.value.activeIndex; | ||||
|   // carouselRef.value.setActiveItem(index - 1); | ||||
|   scrollbarRef.value.setScrollLeft(scrollbarRef.value.wrapRef.scrollLeft + 120); | ||||
| }; | ||||
| 
 | ||||
| const playVideo = () => { | ||||
|   if (videoRef.value) { | ||||
|     videoRef.value.play(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const pauseVideo = () => { | ||||
|   if (videoRef.value) { | ||||
|     videoRef.value.pause(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const handleClick = (index) => { | ||||
|   const { type, data } = props; | ||||
|   switch (type) { | ||||
|     case 'image': { | ||||
|       carouselRef.value.setActiveItem(index); | ||||
|       break; | ||||
|     } | ||||
|     case 'video': { | ||||
|       const url = data[index]; | ||||
|       state.videoUrl = url; | ||||
|       playVideo(); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const handlePlay = () => { | ||||
|   playVideo(); | ||||
| }; | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .carousel { | ||||
|   display: flex; | ||||
|   width: 100%; | ||||
|   flex-direction: column; | ||||
|   &-container { | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     align-items: center; | ||||
|     margin-top: 30px; | ||||
|   } | ||||
|   &-list { | ||||
|     flex: 3; | ||||
|     :deep(.el-scrollbar__bar) { | ||||
|       display: none !important; | ||||
|     } | ||||
|     :deep(.el-scrollbar__view) { | ||||
|       display: flex; | ||||
|       flex-direction: row; | ||||
|       align-items: center; | ||||
|     } | ||||
|     &-item { | ||||
|       display: flex; | ||||
|       justify-content: center; | ||||
|       align-items: center; | ||||
|       margin: 10px; | ||||
|       width: 100px; | ||||
|       height: 100px; | ||||
|       border: 2px solid #ffffff; | ||||
|       border-radius: 4px; | ||||
|       text-align: center; | ||||
|       color: var(--el-color-danger); | ||||
|       background: var(--el-color-danger-light-9); | ||||
|       flex-shrink: 0; | ||||
|       cursor: pointer; | ||||
|       &.active { | ||||
|         border-color: var(--el-color-primary); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   &-arrow { | ||||
|     width: 50px; | ||||
|     text-align: center; | ||||
|     .el-icon { | ||||
|       display: inline-block; | ||||
|       margin: 0 auto; | ||||
|       width: 40px; | ||||
|       height: 40px; | ||||
|       font-size: 32px; | ||||
|       line-height: 40px; | ||||
|       cursor: pointer; | ||||
|     } | ||||
|   } | ||||
|   &-video { | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     min-height: 300px; | ||||
|     &-picture { | ||||
|       position: absolute; | ||||
|       z-index: 10; | ||||
|     } | ||||
|     &-video { | ||||
|       position: absolute; | ||||
|       z-index: 11; | ||||
|     } | ||||
|     &-btn { | ||||
|       position: absolute; | ||||
|       top: 50%; | ||||
|       left: 50%; | ||||
|       z-index: 12; | ||||
|       display: flex; | ||||
|       justify-content: center; | ||||
|       align-items: center; | ||||
|       margin-top: -25px; | ||||
|       margin-left: -25px; | ||||
|       width: 50px; | ||||
|       height: 50px; | ||||
|       border-radius: 25px; | ||||
|       background: rgb(0 0 0 / 20%); | ||||
|       flex-direction: row; | ||||
|       cursor: pointer; | ||||
|       .el-icon { | ||||
|         font-size: 32px; | ||||
|         color: #ffffff; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										115
									
								
								sub-operation-service/src/components/custom-echart-bar/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,115 @@ | ||||
| <template> | ||||
|   <div ref="chartRef" :style="{ height, width }"></div> | ||||
| </template> | ||||
| <script> | ||||
| import { ref, reactive, watchEffect } from 'vue'; | ||||
| import { cloneDeep } from 'lodash'; | ||||
| import { useEcharts } from '@/hooks/useEcharts'; | ||||
| export default { | ||||
|   name: 'CustomEchartBar', | ||||
|   props: { | ||||
|     chartData: { | ||||
|       type: Array, | ||||
|       default: () => [], | ||||
|       required: true, | ||||
|     }, | ||||
|     option: { | ||||
|       type: Object, | ||||
|       default: () => ({}), | ||||
|     }, | ||||
|     type: { | ||||
|       type: String, | ||||
|       default: 'bar', | ||||
|     }, | ||||
|     width: { | ||||
|       type: String, | ||||
|       default: '100%', | ||||
|     }, | ||||
|     height: { | ||||
|       type: String, | ||||
|       default: 'calc(100vh - 78px)', | ||||
|     }, | ||||
|     isSeries: { | ||||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|   }, | ||||
|   emits: ['click'], | ||||
|   setup(props, { emit }) { | ||||
|     const chartRef = ref(null); | ||||
|     const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef); | ||||
|     const option = reactive({ | ||||
|       tooltip: { | ||||
|         trigger: 'axis', | ||||
|         axisPointer: { | ||||
|           type: 'shadow', | ||||
|           label: { | ||||
|             show: true, | ||||
|             backgroundColor: '#333', | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       legend: { | ||||
|         top: 30, | ||||
|       }, | ||||
|       grid: { | ||||
|         top: 60, | ||||
|       }, | ||||
|       xAxis: { | ||||
|         type: 'category', | ||||
|         data: [], | ||||
|       }, | ||||
|       yAxis: { | ||||
|         type: 'value', | ||||
|       }, | ||||
|       series: [], | ||||
|     }); | ||||
| 
 | ||||
|     watchEffect(() => { | ||||
|       props.chartData && initCharts(); | ||||
|     }); | ||||
| 
 | ||||
|     function initCharts() { | ||||
|       if (props.option) { | ||||
|         Object.assign(option, cloneDeep(props.option)); | ||||
|       } | ||||
|       let typeArr = Array.from(new Set(props.chartData.map((item) => item.type))); | ||||
|       let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name))); | ||||
|       let seriesData = []; | ||||
|       typeArr.forEach((type, index) => { | ||||
|         const barStyle = props.option?.barStyle ?? {}; | ||||
|         let obj = { name: type, type: props.type, ...barStyle }; | ||||
|         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; | ||||
|         if (props.option?.color) { | ||||
|           obj.color = props.option?.color[index]; | ||||
|         } | ||||
|         seriesData.push(obj); | ||||
|       }); | ||||
|       option.series = props.isSeries && option.series.length > 0 ? option.series : seriesData; | ||||
|       option.xAxis.data = xAxisData; | ||||
|       setOptions(option); | ||||
|       startAutoPlay({ | ||||
|         interval: 2000, | ||||
|         seriesIndex: 0, | ||||
|         showTooltip: true, | ||||
|       }); | ||||
|       getInstance()?.off('click', onClick); | ||||
|       getInstance()?.on('click', onClick); | ||||
|     } | ||||
| 
 | ||||
|     function onClick(params) { | ||||
|       emit('click', params); | ||||
|     } | ||||
|     return { chartRef }; | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @ -0,0 +1,103 @@ | ||||
| <template> | ||||
|   <div ref="chartRef" :style="{ height, width }"></div> | ||||
| </template> | ||||
| <script> | ||||
| import { ref, reactive, watch, watchEffect } from 'vue'; | ||||
| import { cloneDeep } from 'lodash'; | ||||
| import { useEcharts } from '@/hooks/useEcharts'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'customEchartBubble', | ||||
|   props: { | ||||
|     chartData: { | ||||
|       type: Array, | ||||
|       default: () => [], | ||||
|     }, | ||||
|     size: { | ||||
|       type: Object, | ||||
|       default: () => {}, | ||||
|     }, | ||||
|     option: { | ||||
|       type: Object, | ||||
|       default: () => ({}), | ||||
|     }, | ||||
|     width: { | ||||
|       type: String, | ||||
|       default: '100%', | ||||
|     }, | ||||
|     height: { | ||||
|       type: String, | ||||
|       default: 'calc(100vh - 78px)', | ||||
|     }, | ||||
|   }, | ||||
|   emits: ['click'], | ||||
|   setup(props, { emit }) { | ||||
|     const chartRef = ref(null); | ||||
|     const { setOptions, getInstance, resize } = useEcharts(chartRef); | ||||
|     const option = reactive({ | ||||
|       tooltip: { | ||||
|         formatter: function (params) { | ||||
|           console.log(params); | ||||
|           var str = params.marker + '' + params.data.name + '</br>' + '交易额:' + params.data.value + '万元</br>'; | ||||
|           return str; | ||||
|         }, | ||||
|       }, | ||||
|       animationDurationUpdate: function (idx) { | ||||
|         // 越往后的数据延迟越大 | ||||
|         return idx * 100; | ||||
|       }, | ||||
|       animationEasingUpdate: 'bounceIn', | ||||
|       color: ['#fff', '#fff', '#fff'], | ||||
|       series: [ | ||||
|         { | ||||
|           type: 'graph', | ||||
|           layout: 'force', | ||||
|           force: { | ||||
|             repulsion: 80, | ||||
|             edgeLength: 20, | ||||
|           }, | ||||
|           roam: true, | ||||
|           label: { | ||||
|             normal: { | ||||
|               show: true, | ||||
|             }, | ||||
|           }, | ||||
|           data: props.chartData, | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
| 
 | ||||
|     watchEffect(() => { | ||||
|       props.chartData && initCharts(); | ||||
|     }); | ||||
| 
 | ||||
|     watch( | ||||
|       () => props.size, | ||||
|       () => { | ||||
|         console.info(' option.series[0]', props.chartData); | ||||
|         resize(); | ||||
|       }, | ||||
|       { | ||||
|         immediate: true, | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     function initCharts() { | ||||
|       if (props.option) { | ||||
|         Object.assign(option, cloneDeep(props.option)); | ||||
|       } | ||||
|       option.series[0].data = props.chartData; | ||||
|       setOptions(option); | ||||
|       resize(); | ||||
|       getInstance()?.off('click', onClick); | ||||
|       getInstance()?.on('click', onClick); | ||||
|     } | ||||
| 
 | ||||
|     function onClick(params) { | ||||
|       emit('click', params); | ||||
|     } | ||||
| 
 | ||||
|     return { chartRef }; | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @ -0,0 +1,147 @@ | ||||
| <template> | ||||
|   <div ref="chartRef" :style="{ height, width }"></div> | ||||
| </template> | ||||
| <script> | ||||
| import { ref, reactive, watchEffect, watch } from 'vue'; | ||||
| import { cloneDeep } from 'lodash'; | ||||
| import { useEcharts } from '@/hooks/useEcharts'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'CustomEchartLine', | ||||
|   props: { | ||||
|     chartData: { | ||||
|       type: Array, | ||||
|       default: () => [], | ||||
|       required: true, | ||||
|     }, | ||||
|     option: { | ||||
|       type: Object, | ||||
|       default: () => ({}), | ||||
|     }, | ||||
|     type: { | ||||
|       type: String, | ||||
|       default: 'line', | ||||
|     }, | ||||
|     width: { | ||||
|       type: String, | ||||
|       default: '100%', | ||||
|     }, | ||||
|     height: { | ||||
|       type: String, | ||||
|       default: 'calc(100vh - 78px)', | ||||
|     }, | ||||
|   }, | ||||
|   emits: ['click'], | ||||
|   setup(props, { emit }) { | ||||
|     const chartRef = ref(null); | ||||
|     const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef); | ||||
|     const option = reactive({ | ||||
|       tooltip: { | ||||
|         trigger: 'axis', | ||||
|         axisPointer: { | ||||
|           type: 'shadow', | ||||
|           label: { | ||||
|             show: true, | ||||
|             backgroundColor: '#333', | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       legend: { | ||||
|         top: 30, | ||||
|       }, | ||||
|       grid: { | ||||
|         top: 60, | ||||
|       }, | ||||
|       xAxis: { | ||||
|         type: 'category', | ||||
|         data: [], | ||||
|       }, | ||||
|       yAxis: { | ||||
|         type: 'value', | ||||
|       }, | ||||
|       series: [], | ||||
|     }); | ||||
|     const xData = ref([]); | ||||
|     const yDataColumn = ref([]); | ||||
|     const yDataLine = ref([]); | ||||
| 
 | ||||
|     watchEffect(() => { | ||||
|       props.chartData && initCharts(); | ||||
|     }); | ||||
| 
 | ||||
|     watch( | ||||
|       () => props.chartData, | ||||
|       () => { | ||||
|         console.info('props.chartData变化', props.chartData); | ||||
|         props.chartData && initCharts(); | ||||
|       }, | ||||
|       { | ||||
|         deep: true, | ||||
|         immediate: true, | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|     function hexToRGBA(hex, alpha = 1) { | ||||
|       let hexCode = hex.replace('#', ''); | ||||
|       if (hexCode.length === 3) { | ||||
|         hexCode = hexCode | ||||
|           .split('') | ||||
|           .map((char) => char + char) | ||||
|           .join(''); | ||||
|       } | ||||
|       const r = parseInt(hexCode.slice(0, 2), 16); | ||||
|       const g = parseInt(hexCode.slice(2, 4), 16); | ||||
|       const b = parseInt(hexCode.slice(4, 6), 16); | ||||
|       return `rgba(${r}, ${g}, ${b}, ${alpha})`; | ||||
|     } | ||||
| 
 | ||||
|     function setAreaStyle(color) { | ||||
|       return { | ||||
|         color: { | ||||
|           type: 'linear', | ||||
|           x: 0, | ||||
|           y: 0, | ||||
|           x2: 0, | ||||
|           y2: 1, | ||||
|           colorStops: [ | ||||
|             { | ||||
|               offset: 0, | ||||
|               color: hexToRGBA(color, 1), | ||||
|             }, | ||||
|             { | ||||
|               offset: 1, | ||||
|               color: hexToRGBA(color, 0.2), | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
|     function initCharts() { | ||||
|       xData.value = props.chartData.map((item) => item.name); | ||||
|       yDataColumn.value = props.chartData.map((item) => item.value1); | ||||
|       yDataLine.value = props.chartData.map((item) => item.value); | ||||
|       if (props.option) { | ||||
|         Object.assign(option, cloneDeep(props.option)); | ||||
|       } | ||||
|       option.series[0].data = yDataColumn.value; | ||||
|       option.series[1].data = yDataLine.value; | ||||
|       option.xAxis.data = xData.value; | ||||
|       setOptions(option); | ||||
|       startAutoPlay({ | ||||
|         interval: 2000, | ||||
|         seriesIndex: 0, | ||||
|         showTooltip: true, | ||||
|       }); | ||||
|       getInstance()?.off('click', onClick); | ||||
|       getInstance()?.on('click', onClick); | ||||
|     } | ||||
| 
 | ||||
|     function onClick(params) { | ||||
|       emit('click', params); | ||||
|     } | ||||
| 
 | ||||
|     return { chartRef }; | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @ -0,0 +1,294 @@ | ||||
| <template> | ||||
|   <div ref="chartRef" style="width: 100%; height: 170px"></div> | ||||
| </template> | ||||
| <script> | ||||
| import { ref, reactive, watchEffect } from 'vue'; | ||||
| import { cloneDeep } from 'lodash'; | ||||
| import { useEcharts } from '@/hooks/useEcharts'; | ||||
| export default { | ||||
|   name: 'CustomEchartHyalineCake', | ||||
|   props: { | ||||
|     chartData: { | ||||
|       type: Array, | ||||
|       default: () => [ | ||||
|         { | ||||
|           name: '项目一', | ||||
|           value: 60, | ||||
|         }, | ||||
|         { | ||||
|           name: '项目二', | ||||
|           value: 44, | ||||
|         }, | ||||
|         { | ||||
|           name: '项目三', | ||||
|           value: 32, | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     option: { | ||||
|       type: Object, | ||||
|       default: () => ({ | ||||
|         k: 1, | ||||
|         opacity: '0,6', | ||||
|         itemGap: 0.2, | ||||
|         itemHeight: 120, | ||||
|         autoItemHeight: 0, | ||||
|         legendSuffix: '', | ||||
|       }), | ||||
|     }, | ||||
|     type: { | ||||
|       type: String, | ||||
|       default: 'bar', | ||||
|     }, | ||||
|     width: { | ||||
|       type: String, | ||||
|       default: '100%', | ||||
|     }, | ||||
|     height: { | ||||
|       type: String, | ||||
|       default: 'calc(100vh - 78px)', | ||||
|     }, | ||||
|     isSeries: { | ||||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|   }, | ||||
|   emits: ['click'], | ||||
|   setup(props, { emit }) { | ||||
|     const chartRef = ref(null); | ||||
|     const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef); | ||||
|     const option = ref({}); | ||||
|     watchEffect(() => { | ||||
|       props.chartData && initCharts(); | ||||
|     }); | ||||
| 
 | ||||
|     function initCharts() { | ||||
|       if (props.option) { | ||||
|         option.value = Object.assign(option, cloneDeep(props.option)); | ||||
|       } | ||||
|       option.value = getPie3D(props.chartData, props.option.opacity); | ||||
|       setOptions(option.value); | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|         isSelected = false; | ||||
|       } | ||||
|       k = typeof k !== 'undefined' ? k : 1 / 3; | ||||
|       const offsetX = Math.cos(midRadian) * props.option.itemGap; | ||||
|       const offsetY = Math.sin(midRadian) * props.option.itemGap; | ||||
|       const hoverRate = 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; | ||||
|           } | ||||
|           return Math.sin(v) > 0 ? 1 * h * 0.1 : -1; | ||||
|         }, | ||||
|       }; | ||||
|     } | ||||
|     // 生成模拟 3D 饼图的配置项 | ||||
|     function getPie3D(pieData) { | ||||
|       const series = []; | ||||
|       // 总和 | ||||
|       let sumValue = 0; | ||||
|       let startValue = 0; | ||||
|       let endValue = 0; | ||||
|       const legendData = []; | ||||
|       const k = props.option.k ?? 1; | ||||
|       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], | ||||
| 
 | ||||
|           itemStyle: { | ||||
|             // color: colors[i], // 自定义颜色 | ||||
|             opacity: props.option.opacity, | ||||
|             borderRadius: 300, | ||||
|             borderColor: '#fff', | ||||
|             borderWidth: 0, | ||||
|           }, | ||||
|           pieStatus: { | ||||
|             selected: false, | ||||
|             hovered: false, | ||||
|             k, | ||||
|           }, | ||||
|         }; | ||||
|         if (typeof pieData[i].itemStyle !== 'undefined') { | ||||
|           const { itemStyle } = pieData[i]; | ||||
|           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); | ||||
|       } | ||||
|       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, | ||||
|           props.option.autoItemHeight > 0 ? props.option.autoItemHeight * series[i].pieData.value : props.option.itemHeight | ||||
|         ); | ||||
|         startValue = endValue; | ||||
|         legendData.push(series[i].name); | ||||
|       } | ||||
|       const option = Object.assign( | ||||
|         { | ||||
|           tooltip: { | ||||
|             // tooltip样式调整添加这个类名 | ||||
|             className: 'custom-tooltip-container', // 自定义父容器类名 | ||||
|             backgroundColor: 'rgba(0,0,0,0.5)', | ||||
|             borderColor: '#35d0c0', | ||||
|             color: '#fff', | ||||
|             position: function (point, params, dom, rect, size) { | ||||
|               var x = point[0]; | ||||
|               var y = point[1]; | ||||
|               var viewWidth = size.viewSize[0]; | ||||
|               var viewHeight = size.viewSize[1]; | ||||
|               var boxWidth = size.contentSize[0]; | ||||
|               var boxHeight = size.contentSize[1]; | ||||
|               // 判断 tooltip 位置,调整其位置使其不会超出图表边界 | ||||
|               if (x + boxWidth > viewWidth) { | ||||
|                 x = x - boxWidth; | ||||
|               } | ||||
|               if (y + boxHeight > viewHeight) { | ||||
|                 y = y - boxHeight; | ||||
|               } | ||||
|               // 保证 tooltip 始终在图表内部 | ||||
|               if (x < 0) { | ||||
|                 x = 0; | ||||
|               } | ||||
|               if (y < 0) { | ||||
|                 y = 0; | ||||
|               } | ||||
| 
 | ||||
|               return [x, y]; | ||||
|             }, | ||||
|             formatter: (params) => { | ||||
|               if (params.seriesName !== 'mouseoutSeries') { | ||||
|                 return ` | ||||
|                 <span style="color:#FFF"> | ||||
|                   ${params.seriesName}<br/> | ||||
|                     <span style="display:inline-block; | ||||
|                     margin-right:5px; | ||||
|                     border-radius:10px; | ||||
|                     width:10px; | ||||
|                     height:10px; | ||||
|                     background-color:${params.color};"></span> | ||||
|                     ${option.series[params.seriesIndex].pieData.value} | ||||
|                   </span> | ||||
|                   `; | ||||
|               } | ||||
|               return ''; | ||||
|             }, | ||||
|           }, | ||||
|           xAxis3D: { | ||||
|             min: -1, | ||||
|             max: 1, | ||||
|           }, | ||||
|           yAxis3D: { | ||||
|             min: -1, | ||||
|             max: 1, | ||||
|           }, | ||||
|           zAxis3D: { | ||||
|             min: -1, | ||||
|             max: 1, | ||||
|           }, | ||||
|           grid3D: { | ||||
|             show: false, | ||||
|             boxHeight: 5, | ||||
|             top: '0', | ||||
|             left: '-20%', | ||||
|             viewControl: { | ||||
|               //3d效果可以放大、旋转等,请自己去查看官方配置 | ||||
|               alpha: 60, //角度(这个很重要 调节角度的) | ||||
|               distance: 240, //调整视角到主体的距离,类似调整zoom(这是整体大小) | ||||
|               rotateSensitivity: 10, //设置旋转灵敏度,为0无法旋转 | ||||
|               zoomSensitivity: 10, //设置缩放灵敏度,为0无法缩放 | ||||
|               panSensitivity: 10, //设置平移灵敏度,0无法平移 | ||||
|               autoRotate: true, //自动旋转 | ||||
|               autoRotateAfterStill: 2, //在鼠标静止操作后恢复自动旋转的时间间隔,在开启 autoRotate 后有效 | ||||
|             }, | ||||
|           }, | ||||
|           legend: { | ||||
|             show: true, | ||||
|             right: '25%', | ||||
|             top: '25%', | ||||
|             orient: 'vertical', | ||||
|             icon: 'circle', | ||||
|             itemHeight: 12, | ||||
|             itemWidth: 12, | ||||
|             itemGap: 10, | ||||
|             textStyle: { | ||||
|               color: '#fff', | ||||
|               fontSize: 14, | ||||
|               fontWeight: '400', | ||||
|             }, | ||||
|             formatter: (name) => { | ||||
|               if (props.chartData.length) { | ||||
|                 const item = props.chartData.filter((item) => item.name === name)[0]; | ||||
|                 return `${name}`; | ||||
|               } | ||||
|             }, | ||||
|           }, | ||||
|           series, | ||||
|         }, | ||||
|         props.option | ||||
|       ); | ||||
|       return option; | ||||
|     } | ||||
|     function onClick(params) { | ||||
|       emit('click', params); | ||||
|     } | ||||
|     return { chartRef }; | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @ -0,0 +1,492 @@ | ||||
| <template> | ||||
|   <div ref="chartRef" :style="{ width: width, height: height }"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { ref, watch, onMounted, nextTick } from 'vue'; | ||||
| import { cloneDeep } from 'lodash'; | ||||
| import { useEcharts } from '@/hooks/useEcharts'; | ||||
| 
 | ||||
| defineOptions({ name: 'NewHyalineCake' }); | ||||
| 
 | ||||
| // 定义组件 props | ||||
| const props = defineProps({ | ||||
|   chartData: { | ||||
|     type: Array, | ||||
|     default: () => [ | ||||
|       // 默认示例数据 | ||||
|       { name: '项目一', value: 60 }, | ||||
|       { name: '项目二', value: 44 }, | ||||
|       { name: '项目三', value: 32 }, | ||||
|     ], | ||||
|   }, | ||||
|   option: { | ||||
|     type: Object, | ||||
|     default: () => ({ | ||||
|       // 控制内外径关系的系数,1 表示无内径(实心饼),值越小内径越大 | ||||
|       k: 1, | ||||
|       // 扇形边缘向外偏移距离比例(用于选中效果),单位视图坐标,可调 | ||||
|       itemGap: 0.2, | ||||
|       // 扇形高度(影响z轴拉伸程度) | ||||
|       itemHeight: 120, | ||||
|       // 自动计算扇形高度时使用的系数(>0时 itemHeight 失效,使用 autoItemHeight * value ) | ||||
|       autoItemHeight: 0, | ||||
|       // 透明度设置 | ||||
|       opacity: 0.6, | ||||
|       // 图例后缀 | ||||
|       legendSuffix: '', | ||||
|     }), | ||||
|   }, | ||||
|   width: { | ||||
|     type: String, | ||||
|     default: '100%', | ||||
|   }, | ||||
|   height: { | ||||
|     type: String, | ||||
|     default: '100%', | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| // 声明组件触发的事件 | ||||
| const emit = defineEmits(['click']); | ||||
| 
 | ||||
| // 绑定到DOM的容器引用 | ||||
| const chartRef = ref(null); | ||||
| // 使用 useEcharts 钩子初始化 ECharts 实例,并获取控制方法 | ||||
| const { setOptions, getInstance } = useEcharts(chartRef); | ||||
| 
 | ||||
| // 存储当前的 ECharts 配置项 | ||||
| const chartOption = ref({}); | ||||
| 
 | ||||
| // 参数方程函数:生成每个扇形曲面的参数方程,用于 series-surface.parametricEquation | ||||
| 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) { | ||||
|     isSelected = false; | ||||
|   } | ||||
|   // k 取默认值 1/3(环的厚度比例),如果传入 k 则使用传入值 | ||||
|   k = typeof k !== 'undefined' ? k : 1 / 3; | ||||
| 
 | ||||
|   // 计算选中效果的偏移量(基于扇形中心角度) | ||||
|   const offsetX = isSelected ? Math.cos(midRadian) * props.option.itemGap : 0; | ||||
|   const offsetY = isSelected ? Math.sin(midRadian) * props.option.itemGap : 0; | ||||
|   // 计算高亮时的放大比例(未高亮时为1) | ||||
|   const hoverRate = isHovered ? 1.05 : 1; | ||||
| 
 | ||||
|   // 返回 parametric 曲面方程 | ||||
|   return { | ||||
|     u: { | ||||
|       // u 参数控制饼的周向角度:从 -π 到 3π,可以完整绘制一圈 | ||||
|       min: -Math.PI, | ||||
|       max: Math.PI * 3, | ||||
|       step: Math.PI / 32, | ||||
|     }, | ||||
|     v: { | ||||
|       // v 从 0 - 2π 参数控制扇形的径向方向(厚度方向) | ||||
|       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) { | ||||
|       // 在 u < -π/2 时,防止出现不必要的翻转,直接使用基本正弦 | ||||
|       if (u < -Math.PI * 0.5) { | ||||
|         return Math.sin(u); | ||||
|       } | ||||
|       // 在 u > 2.5π 时,处理扇形尾部厚度 | ||||
|       if (u > Math.PI * 2.5) { | ||||
|         return Math.sin(u) * h * 0.1; | ||||
|       } | ||||
|       // 正常情况下,z 根据 v 参数控制上下表面的高度差 | ||||
|       // 当前图形的高度是Z根据h(每个value的值决定的) | ||||
|       return Math.sin(v) > 0 ? 1 * h * 0.1 : -1; | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| // 生成整个 3D 饼图的配置项 | ||||
| function getPie3D(pieData) { | ||||
|   const series = []; | ||||
|   let sumValue = 0; | ||||
|   // 计算总值 | ||||
|   pieData.forEach((item) => { | ||||
|     sumValue += item.value; | ||||
|   }); | ||||
| 
 | ||||
|   // k 为外径厚度系数 | ||||
|   const k = props.option.k ?? 1; | ||||
|   // 构建每个扇形的 series 数据 | ||||
|   pieData.forEach((dataItem, idx) => { | ||||
|     const seriesItem = { | ||||
|       name: dataItem.name ?? `series${idx}`, | ||||
|       type: 'surface', | ||||
|       parametric: true, | ||||
|       wireframe: { show: false }, | ||||
|       pieData: dataItem, | ||||
|       itemStyle: { | ||||
|         opacity: props.option.opacity, | ||||
|         borderRadius: 300, | ||||
|         borderColor: '#fff', | ||||
|         borderWidth: 0, | ||||
|       }, | ||||
|       pieStatus: { | ||||
|         selected: false, | ||||
|         hovered: false, | ||||
|         k, | ||||
|       }, | ||||
|     }; | ||||
|     // 合并自定义样式(如果有) | ||||
|     if (dataItem.itemStyle) { | ||||
|       const customStyle = {}; | ||||
|       if (dataItem.itemStyle.color !== undefined) { | ||||
|         customStyle.color = dataItem.itemStyle.color; | ||||
|       } | ||||
|       if (dataItem.itemStyle.opacity !== undefined) { | ||||
|         customStyle.opacity = dataItem.itemStyle.opacity; | ||||
|       } | ||||
|       seriesItem.itemStyle = { ...seriesItem.itemStyle, ...customStyle }; | ||||
|     } | ||||
|     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 ''; | ||||
|         }, | ||||
|       }, | ||||
|       xAxis3D: { min: -1, max: 1 }, | ||||
|       yAxis3D: { min: -1, max: 1 }, | ||||
|       zAxis3D: { min: -1, max: 1 }, | ||||
|       grid3D: { | ||||
|         show: false, | ||||
|         boxHeight: 5, | ||||
|         top: '0', | ||||
|         left: '-20%', | ||||
|         viewControl: { | ||||
|           // 3D 视角设置 | ||||
|           alpha: 60, // 俯仰角度 | ||||
|           distance: 240, // 视角距离 | ||||
|           rotateSensitivity: 10, | ||||
|           zoomSensitivity: 10, | ||||
|           panSensitivity: 10, | ||||
|           autoRotate: true, | ||||
|           autoRotateAfterStill: 2, | ||||
|         }, | ||||
|       }, | ||||
|       legend: { | ||||
|         show: true, | ||||
|         selectedMode: false, | ||||
|         right: '5%', | ||||
|         top: '25%', | ||||
|         orient: 'vertical', | ||||
|         icon: 'circle', | ||||
|         itemHeight: 12, | ||||
|         itemWidth: 12, | ||||
|         itemGap: 10, | ||||
|         textStyle: { | ||||
|           color: '#fff', | ||||
|           fontSize: 14, | ||||
|           fontWeight: '400', | ||||
|         }, | ||||
|         // 图例标签显示名称和数值 | ||||
|         formatter: (name) => { | ||||
|           const item = props.chartData.find((d) => d.name === name); | ||||
|           return item ? `  ${name}      ${item.value}${props.option.legendSuffix || ''}` : name; | ||||
|         }, | ||||
|       }, | ||||
|       series, | ||||
|     }, | ||||
|     // 将外部配置项覆盖基础配置 | ||||
|     props.option | ||||
|   ); | ||||
| 
 | ||||
|   return option; | ||||
| } | ||||
| 
 | ||||
| // 初始化图表 | ||||
| function initChart() { | ||||
|   // 生成基础的3D饼图配置 | ||||
|   const baseOption = getPie3D(props.chartData); | ||||
|   // 合并用户配置,确保不修改原始对象 | ||||
|   const finalOption = Object.assign({}, baseOption, cloneDeep(props.option || {})); | ||||
|   chartOption.value = finalOption; | ||||
|   // 设置图表配置 | ||||
|   setOptions(chartOption.value); | ||||
|   // 等待 DOM + ECharts 初始化完成 | ||||
|   nextTick(() => { | ||||
|     const chart = getInstance(); | ||||
|     if (!chart) { | ||||
|       console.warn('ECharts 实例未初始化,事件绑定失败'); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 避免重复绑定事件 | ||||
|     chart.off('click'); | ||||
|     chart.off('mouseover'); | ||||
|     chart.off('globalout'); | ||||
| 
 | ||||
|     chart.on('click', handleClick); | ||||
|     chart.on('mouseover', handleMouseover); | ||||
|     chart.on('globalout', handleGlobalout); | ||||
|   }); | ||||
| } | ||||
| function handleClick(params) { | ||||
|   // 如果点击的是透明辅助环,则忽略 | ||||
|   if (params.seriesName === 'mouseoutSeries') return; | ||||
| 
 | ||||
|   const optionVal = chartOption.value; | ||||
|   const series = optionVal.series; | ||||
|   const idx = params.seriesIndex; | ||||
| 
 | ||||
|   // 新的选中状态(取反) | ||||
|   const isSelected = !series[idx].pieStatus.selected; | ||||
|   const isHovered = series[idx].pieStatus.hovered; | ||||
|   const k = series[idx].pieStatus.k; | ||||
|   const startRatio = series[idx].pieData.startRatio; | ||||
|   const endRatio = series[idx].pieData.endRatio; | ||||
|   const h = series[idx].pieData.value; | ||||
| 
 | ||||
|   // 如果之前有其他扇形被选中,取消其选中状态 | ||||
|   if (selectedIndex !== null && selectedIndex !== idx) { | ||||
|     const prev = series[selectedIndex]; | ||||
|     prev.parametricEquation = getParametricEquation( | ||||
|       prev.pieData.startRatio, | ||||
|       prev.pieData.endRatio, | ||||
|       false, | ||||
|       false, | ||||
|       prev.pieStatus.k, | ||||
|       prev.pieData.value | ||||
|     ); | ||||
|     prev.pieStatus.selected = false; | ||||
|     selectedIndex = null; | ||||
|   } | ||||
| 
 | ||||
|   // 设置当前扇形的选中/取消选中状态 | ||||
|   series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h); | ||||
|   series[idx].pieStatus.selected = isSelected; | ||||
| 
 | ||||
|   // 更新记录选中的系列索引 | ||||
|   selectedIndex = isSelected ? idx : null; | ||||
| 
 | ||||
|   // 更新图表配置 | ||||
|   setOptions(optionVal); | ||||
|   // 触发组件 click 事件供父组件使用 | ||||
|   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> | ||||
| 
 | ||||
| <style scoped> | ||||
| /* 可根据需要自定义图表容器样式 */ | ||||
| </style> | ||||
 2090205686@qq.com
						2090205686@qq.com