猫晨 2024-06-11 15:01 采纳率: 25%
浏览 11
已结题

vue组件-3D饼图更新问题

问题描述:
下列的代码是我项目中一个3D饼图组件的代码部分,问题是3D饼图的样式更新问题。
若我直接修改第67行:state.isOpenFlag = isOpen.value这一行代码为state.isOpenFlag = true或者state.isOpenFlag = false时,3D饼图的样式会按照预期变化。但是当我用目前的赋值方式为它赋值:state.isOpenFlag = isOpen.value,并在watch中修改它的值时,样式没有任何变化。经输出调试,在watch中isOpenFlag的值是确实发生了改变的。
请问这个问题的原因是什么?要怎么修改呢?


<template>
    <div v-if="!config.isHidden">
        <div class='dataV-wrapper' :style='wrapperStyle'>
            <tc-chart v-if="isShowCom" ref='chartRef' 
                :option='chartOptions' style='width: 100%;height: 100%'
                :isOpenFlag="isOpenFlag"
                :key="refreshKey"
                theme='vab-echarts-theme'/>
        </div>
    </div>
  </template>
  
  <script>
  import TcChart from '@/tc/components/TcChart'
  import {computed, defineComponent, onBeforeUnmount, reactive, ref, toRefs, watch, nextTick } from 'vue'
  import {CanvasRenderer} from 'echarts/renderers'
  import {PieChart} from 'echarts/charts'
  import {GridComponent, LegendComponent, TooltipComponent} from 'echarts/components'
  import {getFieldMap, useDataCenter} from '@/tc/components/dataVComponents/mixins/data-center'
  import {use} from 'echarts/core'
  import store from '@/store'

  use([
  CanvasRenderer,
  PieChart,
  GridComponent,
  TooltipComponent,
  LegendComponent,
])
  
  export default defineComponent({
    name: 'VPie3D',
    components: {
        TcChart
    },
    props: { 
        com: {
            type: Object,
            required: true,
        }
    },
    setup(props) {
        // 组件开始,进入组件公共方法,入参为当前组件相关配置
        useDataCenter(props.com)

        const attr = computed(() => props.com.attr)
        const config = computed(() => props.com.config)
        const sourceConfig = computed(() => props.com.sourceConfig)

        const timer = ref(null)
        const state = reactive({
            isShowCom: true,
            isOpenFlag: false,
        })
        const isRefresh = computed(() => {
            if (config.value.autoLoad) {
                return config.value.autoLoad.isReFresh
            } else {
                return false
            }
        })

        // 开启南丁格尔模式
        const isOpen = ref(config.value.isOpen);
        // var isOpen = false
        console.log('isOpen_s', isOpen.value);
        state.isOpenFlag = isOpen.value

        // 组件相关配置数据
        const wrapperStyle = computed(() => {
            return {
                width: `${attr.value.w}px`,
                height: `${attr.value.h}px`
            }
        })
        // 监听vuex中的组件数据,组件数据加载在组件公共方法中
        const dv_data = computed(() => {
            let item = store.getters['dataV_api/componentsData']
            let curData
            item.forEach(x => {
                if (x.id === props.com.id) {
                    curData = x.data
                }
            })
            return curData
        })

        // 获取fields
        const dv_field = computed(() => {
            return getFieldMap(props.com.sourceConfig.fields)
        })

        // 获取图标数据
        const chartData = computed(() => {
            let curData = []
            // 根据字段映射数据
            if (dv_data.value) {
                dv_data.value.forEach(x => {
                    let curItem = {
                        name: x[dv_field.value.name],
                        value: x[dv_field.value.value]
                    }
                    curData.push(curItem)
                })
                return curData
            } else {
                return []
            }
        })

        // 组件数据配置相关
        const getSeries = () => {
            const {global, label} = config.value
            const curChartData = chartData.value
            return {
                type: 'surface',
                label: {
                    show: label.show,
                    position: label.position,
                    ...label.textStyle,
                    formatter: label.formatter || '{b}',
                    alignTo: label.alignTo,
                    bleedMargin: label.bleedMargin,
                    distanceToLabelLine: label.distanceToLabelLine,
                },
                itemStyle: {
                    borderRadius: global.borderRadius
                },
                center: global.center,
                radius: global.radius,
                data: curChartData
            }
        }

        const filterStyle = computed(() => {
            return {
                'margin-left': `${config.value.filterStyle.marginLeft}px`,
                'margin-top': `${config.value.marginTop}px`,
                 color: `${config.value.filterStyle.color}`,
                'font-size': `${config.value.filterStyle.fontSize}px`,
            }
        })

        const getParametricEquation = (startRatio, endRatio, isSelected, isHovered, k, h, value, maxValue, isOpenFlag) => {
            console.log('getParametricEquation');
            console.log('isOpenFlag', isOpenFlag);
            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 = isSelected ? Math.cos(midRadian) * 0.1 : 0
            const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0
            const hoverRate = isHovered ? 1.05 : 1

            // 调整扇形高度比例(仅在南丁格尔模式下)
            const scaleHeight = isOpenFlag ? (value / maxValue) * h : h
            // 调整扇形半径比例
            const scaleRadius = isOpenFlag ? 1 + (value / maxValue) * 0.5 : 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) * (scaleRadius + Math.cos(v) * k) * hoverRate
                    }
                    if (u > endRadian) {
                        return offsetX + Math.cos(endRadian) * (scaleRadius + Math.cos(v) * k) * hoverRate
                    }
                    return offsetX + Math.cos(u) * (scaleRadius + Math.cos(v) * k) * hoverRate
                },
                y(u, v) {
                    if (u < startRadian) {
                        return offsetY + Math.sin(startRadian) * (scaleRadius + Math.cos(v) * k) * hoverRate
                    }
                    if (u > endRadian) {
                        return offsetY + Math.sin(endRadian) * (scaleRadius + Math.cos(v) * k) * hoverRate
                    }
                    return offsetY + Math.sin(u) * (scaleRadius + 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) * scaleHeight * 0.1
                    }
                    return Math.sin(v) > 0 ? 1 * scaleHeight * 0.1 : -1
                }
            }
        }

        const maxValue = Math.max(...chartData.value.map(item => item.value)) // 获取最大值

        const getPie3D = (pieData, internalDiameterRatio, isOpenFlag=false) => {
            console.log('getPie3D');
            const series = []
            let sumValue = 0
            let startValue = 0
            let endValue = 0
            const legendData = []
            const k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3
            
            // 计算总值和生成系列数据
            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,
                    radius: ['40%', '60%'],
                    type: 'surface',
                    parametric: true,
                    wireframe: { show: false },
                    pieData: pieData[i],
                    pieStatus: { selected: false, hovered: false, k }
                }
                // 处理itemStyle属性
                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,
                    10,
                    series[i].pieData.value, // 传递每个扇形的值
                    maxValue,  //传递最大值isOpen
                    isOpenFlag
                )
                startValue = endValue
                legendData.push(series[i].name)
            }

            // 生成图表配置对象
            const option = computed(() => {
                const { global, tooltip, legend, color } = config.value
                const [ legendTop, legendLeft ] = legend.position.split('-')
                return {
                    legend: {
                        show: legend.show,
                        type: 'scroll',
                        top: legendTop,
                        left: legendLeft,
                        // right: 50,
                        // top: 60,
                        orient: legend.orient,
                        textStyle: {...legend.textStyle},
                        icon: legend.icon,
                        itemHeight: legend.symbol.width,
                        itemWidth: legend.symbol.height,
                        itemGap: legend.symbol.gap,
                        data: legendData,
                    },
                    color: color,
                    tooltip: {
                        show: tooltip.show,
                        textStyle: { ...tooltip.textStyle },
                        padding: [tooltip.background.padding.v, tooltip.background.padding.h],
                        backgroundColor: tooltip.background.color,
                        trigger: 'item',
                        formatter: params => {
                            if (params.seriesName !== 'mouseoutSeries') {
                                return `${params.marker}${params.seriesName}${pieData[params.seriesIndex].value}人`
                            }
                            return ''
                        }
                    },
                    xAxis3D: { min: -1, max: 1 },
                    yAxis3D: { min: -1, max: 1 },
                    zAxis3D: { min: -1, max: 1 },
                    grid3D: {
                        show: false,
                        boxHeight: 25,
                        top: '5%',
                        // left: '-15%',
                        left: "center",
                        viewControl: {
                            alpha: 40,
                            beta: 30,
                            rotateSensitivity: 1,
                            zoomSensitivity: 0,
                            panSensitivity: 0,
                            autoRotateSpeed: 50,
                            autoRotate: true,
                            distance: 300
                        }
                    },
                    series,
                // series: getSeries(),
                }
            })
            return option
        }

        var chartOptions = ref({})
        chartOptions = computed(() => getPie3D(chartData.value, 0, state.isOpenFlag)).value
        const refreshKey = ref(0);
        const chartRef = ref(null)
        watch(config, (newVal,oldVal) => {
            isOpen.value = config.value.isOpen
            state.isOpenFlag = isOpen.value
            nextTick(() => {
                chartOptions = computed(() => getPie3D(chartData.value, 0, state.isOpenFlag)).value
                chartRef.value.initOptionsWatcher()
                chartRef.value.refresh()
                refreshKey.value += 1;
            })
            
            console.log('isOpen_e', isOpen.value);
            console.log('watch');
        }, { deep: true });

        watch(config.value.autoLoad, () => {
            clearInterval(timer.value)
            if (isReFresh.value === true) {
                timer.value === setInterval(() => {
                    state.isShowCom = false
                    // 延时器
                    setTimeout(() => {
                        state.isShowCom = true
                    }, 100)
                }, config.value.autoLoad.timer * 1000)
            } else {
                clearInterval(timer.value)
            }
        }, { immediate: true, deep: true})

        // 页面离开的时候销毁定时器
        onBeforeUnmount(() => {
            clearInterval(timer.value)
        })

        return {
            ...toRefs(state),
            config,
            wrapperStyle,
            filterStyle,
            sourceConfig,
            chartRef,
            chartOptions,
            refreshKey
        }
    },
  })
  </script>
  
  <style scoped>
  .age_distribution {
    position: relative;
    width: 100%;
    height: 400px;
  }
  .age_distribution .title {
    position: absolute;
    top: -7px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 17px;
    font-family: PingFang SC, PingFang SC;
    color: #ffffff;
    letter-spacing: 2px;
  }
  </style>
  
  • 写回答

4条回答 默认 最新

  • 阿里嘎多学长 2024-06-11 15:01
    关注

    以下内容由CHATGPT及阿里嘎多学长共同生成、有用望采纳:


    在Vue中,组件的响应性依赖于Vue的依赖追踪系统。如果一个值没有被正确地作为响应式属性跟踪,即使它的值发生变化,Vue也不会触发相应的更新。在你提供的代码中,问题可能出在watch的设置上。

    以下是一些可能导致3D饼图样式没有更新的原因,以及相应的解决方案:

    1. 响应性问题: 如果isOpen.value没有被Vue正确地追踪为响应式属性,即使它的值变化了,也不会触发更新。确保isOpen是在setup函数中通过ref创建的,这样Vue才能追踪它的值。

    2. watch的深度问题: 你的watch配置中使用了deep: true,理论上可以追踪内部属性的变化。但是,如果isOpen的值是通过非响应式的方式被修改的,那么Vue可能无法检测到这个变化。确保任何对isOpen.value的修改都是通过Vue的响应式系统进行的。

    3. 异步更新问题: 如果isOpen.value的值变化是异步发生的,可能需要等待Vue的下一个更新周期。在watch的回调函数中使用nextTick来确保更新操作在DOM更新之后执行。

    4. 组件内部状态更新问题: 如果TcChart组件内部没有正确处理isOpenFlag属性的变化,那么即使父组件传递了新的值,子组件也不会更新。确保TcChart组件能够响应isOpenFlag属性的变化。

    解决方案:

    • 确保isOpen是通过ref创建的响应式引用。
    • watch的回调中使用nextTick来等待DOM更新:
      watch(config, (newVal, oldVal) => {
        isOpen.value = config.value.isOpen;
        state.isOpenFlag = isOpen.value;
        nextTick(() => {
          // 确保在DOM更新后执行
          chartOptions.value = getPie3D(chartData.value, 0, state.isOpenFlag);
          chartRef.value.initOptionsWatcher();
          chartRef.value.refresh();
          refreshKey.value += 1;
        });
      }, { deep: true });
      
    • 检查TcChart组件,确保它能够响应isOpenFlag的变化。可能需要在TcChart组件中添加对isOpenFlagwatch或使用计算属性。

    如果上述解决方案仍然无法解决问题,你可能需要提供TcChart组件的代码,以便进一步分析问题所在。

    至于参考资料,Vue的官方文档是学习响应性原理和watch API的最佳资源:

    希望这些信息能帮助你解决问题。如果需要更具体的帮助,请提供更多的代码或上下文信息。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论 编辑记录
查看更多回答(3条)

报告相同问题?

问题事件

  • 系统已结题 6月19日
  • 已采纳回答 6月11日
  • 创建了问题 6月11日