From 60c31db526d133d9fef6337fdf0aa7f707e509ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E9=B8=BF?= Date: Thu, 15 May 2025 14:54:07 +0800 Subject: [PATCH] =?UTF-8?q?fix-3D=E9=A5=BC=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 2 - .../new-hyaline-cake.vue | 107 ++++-- src/views/land/components/landFive.vue | 1 + src/views/land/components/landOne.vue | 3 +- src/views/land/components/landSix.vue | 5 +- src/views/land/components/landThere.vue | 1 + src/views/land/components/lsh.txt | 344 ++++++++++++++++++ 7 files changed, 419 insertions(+), 44 deletions(-) create mode 100644 src/views/land/components/lsh.txt diff --git a/components.d.ts b/components.d.ts index 5e2d588..971b418 100644 --- a/components.d.ts +++ b/components.d.ts @@ -42,8 +42,6 @@ declare module 'vue' { CustomScrollTitle: typeof import('./src/components/custom-scroll-title/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'] - IndexCo: typeof import('./src/components/custom-echart-line/index-co.vue')['default'] - IndexRe: typeof import('./src/components/new-hyaline-cake/index-re.vue')['default'] NewHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] diff --git a/src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue b/src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue index 1256fe2..2f39c56 100644 --- a/src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue +++ b/src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue @@ -74,10 +74,10 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h k = typeof k !== 'undefined' ? k : 1 / 3; // 计算选中效果的偏移量(基于扇形中心角度) - const offsetX = Math.cos(midRadian) * props.option.itemGap; - const offsetY = Math.sin(midRadian) * props.option.itemGap; + 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.08 : 1; + const hoverRate = isHovered ? 1.05 : 1; // 返回 parametric 曲面方程 return { @@ -88,7 +88,7 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h step: Math.PI / 32, }, v: { - // v 参数控制扇形的径向方向(厚度方向) + // v 从 0 - 2π 参数控制扇形的径向方向(厚度方向) min: 0, max: Math.PI * 2, step: Math.PI / 20, @@ -124,6 +124,7 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h return Math.sin(u) * h * 0.1; } // 正常情况下,z 根据 v 参数控制上下表面的高度差 + // 当前图形的高度是Z根据h(每个value的值决定的) return Math.sin(v) > 0 ? 1 * h * 0.1 : -1; }, }; @@ -374,67 +375,95 @@ function handleClick(params) { // 触发组件 click 事件供父组件使用 emit('click', params); } +// 在函数顶部声明(确保作用域覆盖所有需要的地方) +window.debugZValues = { + current: null, + history: [], +}; function handleMouseover(params) { - const optionVal = chartOption.value; + 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 (params.seriesName === 'mouseoutSeries') { - return; - } - // 如果此扇形已经高亮,无需重复操作 - if (hoveredIndex === idx) { - return; - } - console.log('hoveredIndexOne :>> ', hoveredIndex); - console.log('idx= :>> ', idx); - // 取消之前高亮的扇形 - if (hoveredIndex !== null) { + // 取消之前的高亮(确保只修改状态,不破坏参数方程) + if (hoveredIndex !== null && hoveredIndex !== idx) { const prev = series[hoveredIndex]; - const isSelected = prev.pieStatus.selected; + prev.pieStatus.hovered = false; // 仅修改状态 prev.parametricEquation = getParametricEquation( + // 重新生成正确方程 prev.pieData.startRatio, prev.pieData.endRatio, - isSelected, - false, + prev.pieStatus.selected, + false, // isHovered=false prev.pieStatus.k, prev.pieData.value ); - prev.pieStatus.hovered = false; - hoveredIndex = null; } - // 设置当前扇形的高亮状态 - const isSelected = series[idx].pieStatus.selected; - const startRatio = series[idx].pieData.startRatio; - const endRatio = series[idx].pieData.endRatio; - const k = series[idx].pieStatus.k; - const h = series[idx].pieData.value; - series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, true, k, h); - series[idx].pieStatus.hovered = true; - hoveredIndex = idx; - console.log('hoveredIndexTwo :>> ', hoveredIndex); + // 设置新高亮 + 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 + ); - setOptions(optionVal); + hoveredIndex = idx; + chart.setOption({ series }); // 仅更新series部分 } function handleGlobalout() { if (hoveredIndex !== null) { - const optionVal = chartOption.value; + const chart = getInstance(); + const optionVal = chart.getOption(); const series = optionVal.series; const prev = series[hoveredIndex]; - const isSelected = prev.pieStatus.selected; + + // 打印修复前的错误状态(调试用) + 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, - isSelected, - false, + prev.pieStatus.selected, + false, // isHovered=false prev.pieStatus.k, prev.pieData.value ); - prev.pieStatus.hovered = false; + hoveredIndex = null; - setOptions(optionVal); + chart.setOption({ series }, { replaceMerge: 'series' }); // 强制替换series } } // 记录当前选中和高亮的系列索引 diff --git a/src/views/land/components/landFive.vue b/src/views/land/components/landFive.vue index ea48e76..394f99b 100644 --- a/src/views/land/components/landFive.vue +++ b/src/views/land/components/landFive.vue @@ -23,6 +23,7 @@ const state = reactive({ containLabel: true, }, tooltip: { + className: 'custom-tooltip-container', trigger: 'axis', axisPointer: { type: 'shadow', diff --git a/src/views/land/components/landOne.vue b/src/views/land/components/landOne.vue index 7581ab4..27ed2f3 100644 --- a/src/views/land/components/landOne.vue +++ b/src/views/land/components/landOne.vue @@ -22,11 +22,12 @@ const state = reactive({ containLabel: true, }, tooltip: { + className: 'custom-tooltip-container', // 自定义父容器类名 trigger: 'axis', axisPointer: { type: 'shadow', }, - backgroundColor: 'rgba(0,0,0,0.6);', + backgroundColor: 'rgba(0,0,0,0.5);', borderColor: '#35d0c0', formatter: (data) => { const params = data[0]; diff --git a/src/views/land/components/landSix.vue b/src/views/land/components/landSix.vue index eb201ad..5caa33b 100644 --- a/src/views/land/components/landSix.vue +++ b/src/views/land/components/landSix.vue @@ -15,10 +15,11 @@ const props = defineProps({ const state = reactive({ option: { + k: 0.5, opacity: 1, itemGap: 0.1, legendSuffix: '万亩', - itemHeight: 500, + itemHeight: 200, grid3D: { show: false, boxHeight: 1, @@ -31,7 +32,7 @@ const state = reactive({ rotateSensitivity: 10, //设置旋转灵敏度,为0无法旋转 zoomSensitivity: 10, //设置缩放灵敏度,为0无法缩放 panSensitivity: 10, //设置平移灵敏度,0无法平移 - autoRotate: true, //自动旋转 + autoRotate: false, //自动旋转 autoRotateAfterStill: 2, //在鼠标静止操作后恢复自动旋转的时间间隔,在开启 autoRotate 后有效 }, }, diff --git a/src/views/land/components/landThere.vue b/src/views/land/components/landThere.vue index 0c76b3e..ba8bf3f 100644 --- a/src/views/land/components/landThere.vue +++ b/src/views/land/components/landThere.vue @@ -27,6 +27,7 @@ const state = reactive({ containLabel: true, }, tooltip: { + className: 'custom-tooltip-container', trigger: 'axis', axisPointer: { type: 'shadow', diff --git a/src/views/land/components/lsh.txt b/src/views/land/components/lsh.txt new file mode 100644 index 0000000..ec93880 --- /dev/null +++ b/src/views/land/components/lsh.txt @@ -0,0 +1,344 @@ +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 + }
${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); +});