FaceAnalyzeMall.nvue 9.28 KB
<template>
  <view>
    <CardNvue :title="t('app.title.genderStatistics')" style="margin-bottom: 20rpx;">
      <template #content>
        <ChartsNvue ref="genderRef" />
      </template>
    </CardNvue>
    <!-- #ifdef MP-WEIXIN -->
    <CardNvue :title="t('app.title.ageStatistics')">
    <!-- #endif -->
    <!-- #ifndef MP-WEIXIN -->
    <CardNvue :title="t('app.title.ageStatistics')" style="padding:24rpx 24rpx 0">
    <!-- #endif -->
      <template #content>
        <ChartsNvue v-if="renderFlag === '1'" ref="ageRef" />
        <view class="p-empty" v-if="renderFlag === '0'" style="margin-top: 40rpx;">
          <image class="p-empty-img" src="/static/common/empty.png" mode=""></image>
          <text class="p-empty-text chart-text">{{ t('maintenance.monitor.project.empty.text') }}</text>
        </view>
      </template>
    </CardNvue>
  </view>
</template>

<script setup>
  import {
    getFaceAgeApi,
    getFaceGenderApi,
    getFaceAnalyzeStaMallApi
  } from '@/api'
  import CardNvue from './Card.nvue'
  import ChartsNvue from '@/components/Charts.nvue'
  import {
    nextTick,
    onMounted,
    ref
  } from 'vue'
  import {
    rpx2Px,
    getStageObj
  } from '@/utils'
  import {
    t
  } from '@/plugins/index.js'

  const renderFlag = ref('')

  const options = ref({})
  const initData = async (params) => {
    options.value = params
    if (!params.storeId) {
      // 集团图表
      await getAcountChartData()
    } else {
      // 门店图表
      await getStoreChartData()
    }
  }
  const getStoreChartData = async () => {
    try {
      const account = await getStageObj('account')
      const params = {
        startDate: options.value.startDate,
        endDate: options.value.endDate,
        orgIds: options.value.storeId,
        chartIds: "295,296"
      }
      // 性别
      const {
        data
      } = await getFaceAnalyzeStaMallApi(params)

      const {
        faceAge,
        faceGender
      } = data.body
      // 渲染年龄图表
      renderAgeCharts(faceAge)
      // 渲染性别图表
      renderGenderCharts(faceGender)
    } catch (e) {
      console.log(e);
    }
  }


  const getAcountChartData = async () => {
    try {
      const account = await getStageObj('account')
      // 参数设置位置:

      const params = {
        startDate: options.value.startDate,
        endDate: options.value.endDate,
        accountId: account.id,
        groupId: options.value.groupId ? options.value.groupId : -1,
      }
      // 性别
      const {
        data: faceGender
      } = await getFaceGenderApi(params)
      // 渲染性别图表
      renderGenderCharts(faceGender)
      // 年龄
      const {
        data: faceAge
      } = await getFaceAgeApi(params)
      // 渲染年龄图表
      renderAgeCharts(faceAge)
    } catch (e) {
      console.log(e);
    }
  }

  /* ********* 性别统计 ********** */
  const genderRef = ref(null)
  const renderGenderCharts = (val = {
    series: [{
      data: []
    }]
  }) => {
    let bgColor = '#fff';
    let echartData = val.series?.[0]?.data;

    let formatPercent = function(num, total) {
      return (num * 100 / total).toFixed(2) + '%';
    }
    let total = echartData?.reduce((a, b) => {
      return a + b.value * 1
    }, 0) || 0

    const option = {
      title: {
        text: t('table.totalNum'),
        textStyle: {
          color: '#6D778F',
          fontSize: rpx2Px(26),
        },
        left: 'center',
        top: '32%',
        subtext: `${total}`,
        subtextStyle: {
          fontSize: rpx2Px(36),
          color: '#1D2129',
          fontWeight: 'bold'
        }
      },
      color: ['#387CF5', '#FFA620'],
      tooltip: {
        trigger: 'item',
        confine: true,
        formatter: `{b}: {d}%`
      },
      legend: {
        show: true, // 显示图例
        selectedMode: true, // 启用切换功能
        data: echartData.map(v => v.name), // 动态绑定数据名称
        bottom: 0, // 图例置于底部
        orient: 'horizontal', // 水平排列
        itemWidth: 12, // 图例标记宽度
        itemHeight: 12, // 图例标记高度
        itemGap: 30,
        textStyle: {
          color: '#666', // 文字颜色
          fontSize: 12
        }
      },
      series: [{
        type: 'pie',
        radius: ['50%', '80%'], // 保持与原系列相同半径
        center: ['50%', '42%'], // 保持中心点一致
        data: [{
          value: 100, // 值需≥原系列数据总和
          itemStyle: {
            color: '#f3f5f9' // 设置背景色
          }
        }],
        silent: true,
        hoverAnimation: false, // 禁用悬停动画
        label: {
          show: false
        }, // 隐藏标签
        labelLine: {
          show: false
        }, // 隐藏标签引导线
        tooltip: {
          show: false
        } // 禁用提示框
      }, {
        type: 'pie',
        radius: ['55%', '75%'],
        center: ['50%', '42%'],
        data: echartData,
        hoverAnimation: false,
        label: {
          show: true,
          formatter: `{b}\n{d}%`
        },
        itemStyle: {
          borderColor: '#F3F5F9',
          borderWidth: 3
        },
        labelLine: {
          show: true,
          length: rpx2Px(20),
          length2: rpx2Px(20),
          smooth: true
        },
        tooltip: {
          show: false
        },
      }]
    };
    genderRef.value?.initCharts(option)
  }


  /* ********* 年龄统计 ********** */
  const ageRef = ref(null)
  const renderAgeCharts = async (val) => {
    if (val.series.length > 0) {
      renderFlag.value = '1'
    } else {
      renderFlag.value = '0'
      return
    }
    const option = rightCenterBarConfig(val, '', 15)
    // #ifndef MP-WEIXIN
    nextTick(() => {
      setTimeout(() => {
        ageRef.value?.initCharts(option)
      }, 0)
    })
    // #endif
    // #ifdef MP-WEIXIN
      let attempts = 0;
      while (!ageRef.value && attempts < 40) {
        await new Promise(resolve => setTimeout(resolve, 50));
        attempts++;
      }
      if (ageRef.value?.initCharts) {
        ageRef.value.initCharts(option);
      } else {
        console.warn('年龄图表组件未加载完成', ageRef.value);
      }
    // #endif
  }

  function rightCenterBarConfig(confineData, nameType, barWidth) {
    let x = confineData?.series.map(item => item.name) || [];
    let male = confineData?.series.map(item => item.data[0] || 0) || [];
    let female = confineData?.series.map(item => item.data[1] || 0) || [];
    const percentData = []
    const totalData = confineData?.series.reduce((sum, curr) => {
      let currTotal = curr.data.reduce((currSum, item) => currSum + item, 0)
      percentData.push(currTotal)
      return sum + currTotal
    }, 0) || 0

    return {
      tooltip: {
        trigger: "axis",
        textStyle: {},
        confine: true,
        triggerOn: 'click'
      },
      grid: {
        bottom: '0%',
        left: '0%',
        right: '0%',
        top: '6%',
      },
      xAxis: {
        type: "value",
        splitLine: {
          show: false,
        },
        axisTick: {
          show: false
        },
        axisLine: {
          show: false
        },
        axisLabel: {
          show: false,
        }
      },
      yAxis: [{
        type: "category",
        data: x,
        axisTick: {
          show: false
        },
        axisPointer: {
          type: "shadow",
        },
        axisLine: {
          show: false,
          lineStyle: {
            color: "#BDD8FB",
            fontSize: 12
          }
        },
        axisLabel: {
          fontSize: 13,
          textStyle: {
            color: "#202328",
            align: "left",
            verticalAlign: 'top',
            padding: [-25, 0, 0, 10]
          }
        }
      }, {
        type: 'category',
        inverse: true,
        axisTick: 'none',
        axisLine: 'none',
        show: true,
        offset: -13,
        axisLabel: {
          textStyle: {
            color: '#6D778F',
            fontSize: 13,
            align: 'right'
          },
          verticalAlign: 'bottom',
          padding: [20, 0, 13, 0],

          // #ifdef APP-NVUE
          formatter: `function(value){
            if(${totalData} === 0){
              return '0%'
            }
						return (value * 100 / ${totalData}).toFixed(2) + '%';
					}`,
          // #endif
          // #ifndef APP-NVUE
          formatter: function(value) {
            if (totalData === 0) {
              return '0%'
            }
            return `${(value*100/totalData).toFixed(2)}%`;
          },
          // #endif
        },
        data: percentData.reverse(),
      }],
      series: [{
          name: t('ParamName.maleAgeDistribution'),
          type: "bar",
          stack: "all",
          data: male,
          barWidth: 16,
          itemStyle: {
            color: "#4277F7",
            borderRadius: [3, 0, 0, 3]
          },
        },
        {
          name: t('ParamName.femaleAgeDistribution'),
          type: "bar",
          stack: "all",
          data: female,
          barWidth: 16,
          itemStyle: {
            color: "#FFA620",
            borderRadius: [0, 3, 3, 0]
          },
          showBackground: true,
          backgroundStyle: {
            color: '#F7F8FB',
            borderRadius: [3, 3, 3, 3]
          }
        }
      ]
    };
  };

  defineExpose({
    initData
  })
</script>