hyingbo
2 天以前 5636ee8eb5d1108668d0abf1e425268bde14922d
mdc首页开发
已添加2个文件
已修改4个文件
1200 ■■■■■ 文件已修改
src/api/signage.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/index.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/page/GlobalHeader.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/Analysis.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/TodoList.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/mdcIndex/MdcManagerSignage.vue 1144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/signage.js
@@ -1,6 +1,18 @@
import { getAction, deleteAction, putAction, postAction, httpAction } from '@/api/manage'
export default {
  // èŽ·å–æ‰€æœ‰è½¦é—´ä¿¡æ¯
  getAllWorkShop: id => getAction('/mdc/home/getAllWorkShop', {}),
  // æ ¹æ®ç”¨æˆ·ID获取用户信息
  getUserByIdApi: id => getAction('sys/api/getUserById', { id }),
  // è®¾å¤‡è¿è¡ŒçŠ¶æ€
  getEquipmentStatusStatisticsApi: productionId => getAction('/mdc/home/equipmentStatusStatistics', { productionId }),
  // è®¾å¤‡åˆ©ç”¨çއ
  getEquipmentUtilizationStatisticsApi: productionId => getAction('/mdc/home/equipmentUtilizationStatistics', { productionId }),
  // å…¨åŽ‚å‰15天利用率折线图
  getEquipmentDayUtilizationStatisticsApi: productionId => getAction('/mdc/home/equipmentDayUtilizationStatistics', { productionId }),
  // è®¾å¤‡OEE统计
  getEquipmentOEEStatistics: productionId => getAction('/mdc/home/equipmentOEEStatistics', { productionId }),
  // è®¾å¤‡OEE和利用率对比
  getEquipmentMonthStatisticsApi: productionId => getAction('/mdc/home/equipmentMonthStatistics', { productionId }),
}
src/assets/index.png
src/components/page/GlobalHeader.vue
@@ -17,8 +17,7 @@
        :type="collapsed ? 'menu-unfold' : 'menu-fold'"
        @click="toggle"/>
      <span v-if="device === 'desktop'">欢迎进入 Jeecg-Boot ä¼ä¸šçº§ä½Žä»£ç å¹³å°</span>
      <span v-else>Jeecg-Boot</span>
      <span v-if="device === 'desktop'">欢迎进入 MDC智慧车间</span>
      <user-menu :theme="theme"/>
    </div>
src/views/dashboard/Analysis.vue
@@ -1,17 +1,25 @@
<template>
  <Component :is="currentSignage" :userType="userType" :productionCode="productionCode"
  <Component :is="currentSignage"
             :userType="userType"
             :productionCode="productionCode"
             :workshopSectionProductionCode="workshopSectionProductionCode"
             v-if="[1,2,3,4].includes(userType)"
             >
  </Component>
  <div v-else>  <!-- ä¸Žç»„件渲染互斥 -->
    <img src="@/assets/index.png" style="width: 100%;height: 785px">
  </div>
</template>
<script>
  import signageApi from '@/api/signage'
  import MdcManagerSignage from './mdcIndex/MdcManagerSignage.vue'
  import DncManagerSignage from './dncIndex/DncManagerSignage.vue'
  export default {
    name: "Analysis",
    components: {
      MdcManagerSignage,
      DncManagerSignage
    },
    data() {
@@ -20,7 +28,7 @@
        productionCode: '',
        branchFactoryProductionCode: '',
        workshopSectionProductionCode: '',
        userType: ''
        userType: '',
      }
    },
    created() {
@@ -28,20 +36,27 @@
    },
    methods: {
      showModuleByUserInfo() {
        const id = JSON.parse(localStorage.getItem('pro__Login_Userinfo')).value.id
        // å®‰å…¨å¤„理:先判断localStorage中是否存在用户信息,避免JSON.parse报错
        const userInfoStr = localStorage.getItem('pro__Login_Userinfo')
        if (!userInfoStr) {
          this.currentSignage = '' // æ— ç”¨æˆ·ä¿¡æ¯æ—¶ä¸æ¸²æŸ“组件
          return
        }
        const id = JSON.parse(userInfoStr).value.id
        signageApi.getUserByIdApi(id)
          .then(res => {
            console.log("res", res.userType)
            this.userType = res.userType
            this.userType = res.userType // èµ‹å€¼åŽè‡ªåŠ¨è§¦å‘æ¡ä»¶æ¸²æŸ“åˆ¤æ–­
            // æ ¹æ®userType匹配对应组件(恢复case1和case4的逻辑)
            switch (this.userType) {
              // case 1:
              //   //刀具管理
              //   this.currentSignage = 'EquipmentSignage'
              //   break
              // case 2:
              //   //mdc
              //   this.currentSignage = 'WorkshopSectionSignage'
              //   break
              case 2:
                // mdc
                this.currentSignage = 'MdcManagerSignage'
                break
              case 3:
                //dnc
                this.currentSignage = 'DncManagerSignage'
@@ -55,6 +70,12 @@
                break
            }
          })
          .catch(err => {
            // æŽ¥å£è¯·æ±‚失败时,默认显示图片
            console.error('获取用户类型失败:', err)
            this.userType = ''
            this.currentSignage = ''
          })
      }
    }
  }
src/views/dashboard/TodoList.vue
@@ -587,7 +587,7 @@
  margin: 0;
  box-sizing: border-box;
  /* æ–°å¢žï¼šè®¾ç½®å®¹å™¨æœ€å¤§é«˜åº¦ï¼ˆå¯æ ¹æ®é¡µé¢å¸ƒå±€è°ƒæ•´ï¼Œå¦‚500px/80vh) */
  max-height: 80vh;
  max-height: 100vh;
  /* æ–°å¢žï¼šåž‚直方向溢出时显示滚动条,水平方向溢出隐藏(避免布局错乱) */
  overflow-y: auto;
  overflow-x: hidden;
src/views/dashboard/mdcIndex/MdcManagerSignage.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1144 @@
<template>
  <div class="home-container">
    <div class="tab-nav">
      <div
        v-for="(tab, index) in tabList"
        :key="index"
        :class="['tab-item', activeTab === index ? 'tab-active' : '']"
        @click="handleTabChange(index)"
      >
        {{ tab.label }}
      </div>
    </div>
    <div class="chart-container">
      <div class="left-cards">
        <div class="card left-cards-card">
          <div id="running_state_chart" style="width:100%;height: 45%;max-height: 45vh"></div>
          <div id="efficiency_chart" style="width: 100%;height: 55%;max-height: 55vh"></div>
        </div>
      </div>
      <div class="right-cards">
        <div class="card right-top-card">
          <div id="first15DaysEfficiency_chart" style="width:100%;height: 100%;max-height: 50vh"></div>
        </div>
        <div class="right-bottom-card">
          <div class="card right-bottom-left-card">
            <div id="bar_chart" style="width:100%;height: 100%;max-height: 50vh"></div>
          </div>
          <div class="card right-bottom-right-card">
            <div id="double_bar_chart" style="width:100%;height: 100%;max-height: 50vh"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import signageApi from '@/api/signage'
import moment from 'moment'
export default {
  name: 'DncManagerSignage',
  components: {},
  data() {
    return {
      tabList: [],
      activeTab: -1, // åˆå§‹æ— é€‰ä¸­é¡µç­¾ï¼ˆ-1表示未选择状态)
      firstEnterClientWidth: null,
      pieChartRadius: ['45%', '55%'],
      normalPieChartRadius: ['45%', '55%'],
      currentPageProductionId: null,
      runningStateChart: '',
      efficiencyChart: '',
      efficiencyData: [],
      barChart: '',
      barChartData: [],
      doubleBarChart: '',
      doubleBarChartData: {},
      runningStateChartDataRequireFinished: false,
      first15DaysEfficiencyChart: '',
      first15DaysEfficiencyData: {
        dataList: [],
        dateList: []
      },
      runningStateData: [
        { value: '0', name: '关机' },
        { value: '0', name: '报警' },
        { value: '0', name: '待机' },
        { value: '0', name: '运行' }
      ]
    }
  },
  mounted() {
    window.addEventListener('resize', this.handleWindowResize)
    // this.re_drawPieChart()
    this.getAllWorkShop()
    this.getChartDataByApi()
  },
  beforeDestroy() {
    // ç»„件销毁前移除监听
    window.removeEventListener('resize', this.handleWindowResize)
    this.destroyEchartsInstances()
  },
  methods: {
    getAllWorkShop() {
      this.tabList = []
      signageApi.getAllWorkShop()
        .then(res => {
          if (res.success) {
            this.tabList = res.result.map(workshop => ({
              label: workshop.productionName,
              factoryId: workshop.id
            }))
            if (this.tabList.length > 0) {
              this.activeTab = -1
              this.destroyEchartsInstances()
              this.getChartDataByApi()
            }
          } else {
            this.tabList = []
            this.activeTab = -1
          }
        })
        .catch(err => {
          this.tabList = []
          this.activeTab = -1
          this.destroyEchartsInstances()
          this.getChartDataByApi()
        })
    },
    destroyEchartsInstances() {
      const charts = [
        this.runningStateChart,
        this.efficiencyChart,
        this.first15DaysEfficiencyChart,
        this.barChart,
        this.doubleBarChart
      ]
      charts.forEach(chart => {
        if (chart && chart.dispose) chart.dispose()
      })
    },
    handleTabChange(index) {
      // 1. å¦‚果点击的是“已选中的页签”,执行“取消选中”逻辑
      if (this.activeTab === index) {
        this.activeTab = -1; // é‡ç½®ä¸ºæœªé€‰ä¸­çŠ¶æ€
        this.destroyEchartsInstances(); // é”€æ¯æ‰€æœ‰å›¾è¡¨å®žä¾‹
        this.getChartDataByApi();
        return;
      }
      // 2. å¸¸è§„切换页签逻辑(原有逻辑保留)
      this.activeTab = index;
      this.destroyEchartsInstances();
      this.getChartDataByApi();
    },
    /* è°ƒç”¨æŽ¥å£èŽ·å–å›¾è¡¨æ•°æ®æ±‡æ€»æ–¹æ³• */
    getChartDataByApi() {
      let currentFactoryId = ''
      if (this.tabList[this.activeTab] != null && this.tabList[this.activeTab] !== undefined) {
        currentFactoryId = this.tabList[this.activeTab].factoryId
      }
      console.log('currentFactoryId', currentFactoryId)
      this.getRunningStateDataByApi(currentFactoryId)
      this.getEfficiencyDataByApi(currentFactoryId)
      this.getFirst15DaysEfficiencyDataByApi(currentFactoryId)
      this.getBarChartDataByApi(currentFactoryId)
      this.getDoubleBarChartDataByApi(currentFactoryId)
    },
    /* è°ƒç”¨æŽ¥å£èŽ·å–è®¾å¤‡è¿è¡ŒçŠ¶æ€ */
    getRunningStateDataByApi(productionCode) {
      this.runningStateChart = this.$echarts.init(document.getElementById('running_state_chart'))
      this.runningStateChart.showLoading({
        text: '数据加载中 ...',
        color: '#0696e1', // åŠ è½½åŠ¨ç”»é¢œè‰²
        textColor: '#fff',
        maskColor: 'transparent' // é®ç½©å±‚
      })
      signageApi.getEquipmentStatusStatisticsApi(productionCode)
        .then(res => {
          if (res.success) {
            this.runningStateData = res.result.list
            this.currentPageProductionId = res.result.productionId
            this.runningStateChartDataRequireFinished = true
            this.drawRunningStateChart(res.result.productionId)
          }
        })
    },
    /* è°ƒç”¨æŽ¥å£èŽ·å–è®¾å¤‡åˆ©ç”¨çŽ‡ */
    getEfficiencyDataByApi(productionCode) {
      this.efficiencyChart = this.$echarts.init(document.getElementById('efficiency_chart'))
      this.efficiencyChart.showLoading({
        text: '数据加载中 ...',
        color: '#0696e1', // åŠ è½½åŠ¨ç”»é¢œè‰²
        textColor: '#fff'
      })
      signageApi.getEquipmentUtilizationStatisticsApi(productionCode)
        .then(res => {
          if (res.success) {
            this.efficiencyData = res.result
            this.drawEfficiencyChart()
          }
        })
    },
    /* è°ƒç”¨æŽ¥å£èŽ·å–å‰15天利用率 */
    getFirst15DaysEfficiencyDataByApi(productionCode) {
      this.first15DaysEfficiencyChart = this.$echarts.init(document.getElementById('first15DaysEfficiency_chart'))
      this.first15DaysEfficiencyChart.showLoading({
        text: '数据加载中 ...',
        color: '#0696e1', // åŠ è½½åŠ¨ç”»é¢œè‰²
        textColor: '#000000',
        maskColor: 'transparent' // é®ç½©å±‚
      })
      signageApi.getEquipmentDayUtilizationStatisticsApi(productionCode)
        .then(res => {
          if (res.success) {
            this.first15DaysEfficiencyData = res.result
            this.drawFirst15DaysEfficiencyDataChart()
          }
        })
    },
    /* ç»˜åˆ¶å‰7天利用率柱图 */
    drawFirst15DaysEfficiencyDataChart() {
      this.first15DaysEfficiencyData.dateList.forEach(item => {
        if (!this.first15DaysEfficiencyData.dataList.map(item => item.date).includes(item)) {
          const dateObj = {
            date: item.date,
            openRate: 0,
            startRate: 0,
            utilizationRate: 0
          }
          this.first15DaysEfficiencyData.dataList.push(dateObj)
        }
      })
      const dateList = this.first15DaysEfficiencyData.dataList.map(item => item.date)
      const newData = {
        xAxis: dateList,
        yAxis: [
          {
            name: '利用率',
            value: this.first15DaysEfficiencyData.dataList.map(item => item.utilizationRate)
          },
          {
            name: '开动率',
            value: this.first15DaysEfficiencyData.dataList.map(item => item.startRate)
          },
          {
            name: '开机率',
            value: this.first15DaysEfficiencyData.dataList.map(item => item.openRate)
          }
        ],
        yAxisName: '前15天利用率(%)'
      }
      let legendData = []
      let seriesData = []
      let colorArr = ['#A7F0C1', '#FAE893', '#66DFE2']
      legendData = newData.yAxis.map((item) => item.name)
      seriesData = newData.yAxis.map((item1, index1) => {
        return {
          name: item1.name,
          type: 'bar',
          symbol: 'circle',
          symbolSize: 8,
          itemStyle: {
            color: colorArr[index1],
            barBorderRadius: 100
          },
          lineStyle: {
            width: 2
          },
          markPoint: {
            show: true
          },
          yAxisIndex: 1,
          data: item1.value // æŠ˜çº¿å›¾çš„æ•°æ®
        }
      })
      const option = {
        grid: {
          containLabel: true,
          bottom: '1%',
          top: '20%',
          left: '2%',
          right: '1%'
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          },
          formatter: function(params) {
            let result = ''
            params.forEach((item, index) => {
              let dom = `<span style="display:inline-block;width:10px;height:10px;border-radius:100px;margin-right:5px;background:${item.color}"></span>${item.seriesName}:${item.value}%`
              if (index === 0) {
                result = `<span style="font-weight:bolder;">${item.name}</span>`
              }
              result += '<br />' + dom
            })
            return result
          }
        },
        legend: {
          top: 20,
          right: 'center',
          data: legendData,
          itemGap: 10,
          textStyle: {
            fontSize: 14,
            color: '#000000'
          }
        },
        xAxis: {
          data: newData.xAxis || [],
          axisLabel: {
            interval: 0,
            show: true,
            fontSize: 14,
            color: '#000000'
            // rotate: -30,
          },
          axisLine: {
            show: true,
            lineStyle: {
              color: '#000000'
            }
          },
          axisTick: {
            show: true,
            alignWithLabel: true
          }
        },
        yAxis: [
          {
            name: newData.yAxisName,
            nameTextStyle: {
              color: '#1AD8DE',
              fontSize: 18,
              padding: [0, 0, 0, 110]
            },
            nameGap: 30,
            type: 'value',
            position: 'left',
            axisLine: {
              show: true,
              lineStyle: {
                color: '#000000'
              }
            },
            axisTick: {
              show: false
            },
            splitLine: {
              show: false,
              lineStyle: {
                color: '#000000'
              }
            }
          },
          {
            type: 'value',
            position: 'right',
            splitNumber: 5,
            max: 100,
            axisLabel: {
              show: true,
              color: '#000000',
              fontSize: 14
            },
            axisLine: {
              show: true,
              lineStyle: {
                color: '#000000'
              }
            },
            axisTick: {
              show: true
            },
            splitLine: {
              show: false,
              lineStyle: {
                color: '#000000'
              }
            }
          }
        ],
        series: seriesData,
        dataZoom: {
          show: false,
          startValue: 0, // ä»Žå¤´å¼€å§‹ã€‚
          endValue: 14 // ä¸€æ¬¡æ€§å±•示几个
        }
        // toolbox: {
        //   show: true,
        //   feature: {
        //     mark: { show: true },
        //     magicType: { show: true, type: ['line', 'bar'] },
        //     restore: { show: true },
        //     saveAsImage: { show: true, name: '前7天利用率统计图', pixelRatio: 1 }
        //   }
        // }
      }
      this.first15DaysEfficiencyChart.setOption(option, true)
      this.first15DaysEfficiencyChart.hideLoading()
      this.first15DaysEfficiencyChart.on('click', params => {
        this.$router.push({
          name: 'mdc-base-StatisticsChart',
          params: {
            isEquipment: true,
            productionId: params.name,
            tierName: this.first15DaysEfficiencyData.dataList.find(item => item.date === params.name).date
          }
        })
      })
    },
    /* è°ƒç”¨æŽ¥å£èŽ·å–è®¾å¤‡OEE统计 */
    getBarChartDataByApi(productionCode) {
      this.barChart = this.$echarts.init(document.getElementById('bar_chart'))
      this.barChart.showLoading({
        text: '数据加载中 ...',
        color: '#0696e1', // åŠ è½½åŠ¨ç”»é¢œè‰²
        textColor: '#fff'
      })
      signageApi.getEquipmentOEEStatistics(productionCode)
        .then(res => {
          if (res.success && res.result) {
            this.barChartData = res.result
            this.drawBarChart()
          }
        })
        .finally(() => {
          this.barChart.hideLoading()
        })
    },
    /* ç»˜åˆ¶å•柱图 */
    drawBarChart() {
      const defaultData = []
      const colorArray = ['#79CEAA', '#F589A2', '#6FBF9D', '#66DFE2', '#A7F0C1', '#FAE893', '#F7B7A0']
      const dataMax = this.barChartData.length > 0 ? +this.barChartData.sort((x, y) => +y.value - +x.value)[0].value : 0
      let yAxisMax
      if (dataMax === 0) yAxisMax = 1 // è‹¥æ•°æ®ä¸­æœ€å¤§å€¼ä¸º0,则将背景默认值设置为1
      else yAxisMax = Math.ceil(dataMax / 5) * 5 // è®¾ç½®æŸ±å›¾èƒŒæ™¯é˜´å½±é»˜è®¤å€¼ï¼Œæ€è·¯ä¸ºæ•°æ®æœ€å¤§å€¼æœ€æŽ¥è¿‘的能被5整除的数字
      const yAxisInterval = yAxisMax / 5 // åŒæ—¶å°†åˆ»åº¦å€¼åˆ†æˆ5份
      this.barChartData.forEach(item => defaultData.push(yAxisMax))
      const option = {
        title: {
          show: true, // æ˜¯å¦æ˜¾ç¤ºæ ‡é¢˜ï¼Œé»˜è®¤ä¸ºtrue
          text: '', // ä¸»æ ‡é¢˜æ–‡æœ¬
          x: 'left', // æ ‡é¢˜æ°´å¹³å®‰æ”¾ä½ç½®ï¼Œå¯é€‰å€¼ä¸º'left'、'center'、'right'或具体的水平坐标值
          y: 'top', // æ ‡é¢˜åž‚直安放位置,可选值为'top'、'bottom'、'center'或具体的垂直坐标值
          textStyle: {
            // ä¸»æ ‡é¢˜æ–‡æœ¬æ ·å¼
            fontSize: 18,
            fontWeight: 'normal',
            color: '#1AD8DE'
          }
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          },
          formatter: function(params) {
            return '<span style="font-weight:bolder;">' + params[0].name + '</span><br/>' +
              '<span style="display:inline-block; width:10px; height:10px; border-radius:100px; margin-right:5px; background:' + params[0].color + '"></span>' + ' OEE: ' + params[0].value + '%'
          },
          // backgroundColor: 'rgba(9, 24, 48, 0.5)',
          borderColor: 'rgba(75, 253, 238, 0.4)',
          textStyle: {
            // color: '#CFE3FC'
          },
          borderWidth: 1
        },
        grid: {
          top: '20%',
          left: '10%'
        },
        xAxis: [{
          name: '',
          nameLocation: 'middle',
          nameGap: 40, // xè½´name与横坐标轴线的间距
          type: 'category',
          data: this.barChartData.map(item => item.productionId),
          axisLine: {
            lineStyle: {
              color: '#000000'
            }
          },
          axisLabel: {
            show: true, // æ˜¯å¦æ˜¾ç¤ºåˆ»åº¦æ ‡ç­¾ï¼Œé»˜è®¤æ˜¾ç¤º
            interval: 0, // åæ ‡è½´åˆ»åº¦æ ‡ç­¾çš„æ˜¾ç¤ºé—´éš”,在类目轴中有效;默认会采用标签不重叠的策略间隔显示标签;可以设置成0强制显示所有标签;如果设置为1,表示『隔一个标签显示一个标签』,如果值为2,表示隔两个标签显示一个标签,以此类推。
            rotate: this.barChartData.length >= 6 ? -30 : 0, // åˆ»åº¦æ ‡ç­¾æ—‹è½¬çš„角度,在类目轴的类目标签显示不下的时候可以通过旋转防止标签之间重叠;旋转的角度从-90度到90度
            inside: false, // åˆ»åº¦æ ‡ç­¾æ˜¯å¦æœå†…,默认朝外
            margin: 10, // åˆ»åº¦æ ‡ç­¾ä¸Žè½´çº¿ä¹‹é—´çš„距离
            formatter: value => {
              return `${this.barChartData.find(item => item.productionId === value).name}`
            },
            fontSize: 14
          },
          axisTick: {
            show: true,
            alignWithLabel: true
          }
        }],
        yAxis: [{
          name: '%',
          min: 0,
          max: yAxisMax,
          interval: yAxisInterval,
          axisLabel: {
            formatter: '{value}',
            color: '#000000',
            fontSize: 14
          },
          axisTick: {
            show: false
          },
          axisLine: {
            show: false,
            lineStyle: {
              color: '#000000'
            }
          },
          splitLine: {
            show: false,
            lineStyle: {
              color: 'rgba(255,255,255,0.12)'
            }
          }
        }],
        series: [{
          type: 'bar',
          data: this.barChartData,
          barWidth: this.barChartData.length > 5 ? '40%' : 30,
          itemStyle: {
            color: function(params) {
              let num = colorArray.length
              return colorArray[params.dataIndex % num]
            },
            barBorderRadius: 100
          },
          zlevel: 1,
          label: {
            show: false,
            lineHeight: 10,
            formatter: params => {
              if (+params.value === 0) return ''
              else return params.value
            },
            position: 'top',
            textStyle: {
              color: '#000000',
              fontSize: 16
            }
          }
        }]
      }
      option.title.text = moment().subtract(1, 'month').format('M月') + `OEE`
      this.barChart.setOption(option, true)
    },
    /* ç»˜åˆ¶è®¾å¤‡è¿è¡ŒçŠ¶æ€çŽ«ç‘°é¥¼å›¾ */
    drawRunningStateChart() {
      const option = {
        height: 300,
        title: {
          show: true, // æ˜¯å¦æ˜¾ç¤ºæ ‡é¢˜ï¼Œé»˜è®¤ä¸ºtrue
          text: '设备状态', // ä¸»æ ‡é¢˜æ–‡æœ¬
          x: 'left', // æ ‡é¢˜æ°´å¹³å®‰æ”¾ä½ç½®ï¼Œå¯é€‰å€¼ä¸º'left'、'center'、'right'或具体的水平坐标值
          y: 'top', // æ ‡é¢˜åž‚直安放位置,可选值为'top'、'bottom'、'center'或具体的垂直坐标值
          textStyle: {
            // ä¸»æ ‡é¢˜æ–‡æœ¬æ ·å¼
            fontSize: 18,
            fontWeight: 'normal',
            color: '#1AD8DE'
          }
        },
        tooltip: {
          trigger: 'item',
          formatter: function(params) {
            return '<span style="font-weight:bolder;">' + params.name + '</span><br/>' +
              '<span style="display:inline-block; width:10%; height:10%; border-radius:100px; margin-right:5px; background:' + params.color + '"></span>' + `${params.value}(${params.percent}%)`
          }
        },
        legend: {
          top: 'auto',
          left: 'center',
          bottom: '10%', // åº•部距离
          orient: 'horizontal', // æ°´å¹³æŽ’列
          right: '10%',
          // bottom: "0",
          itemWidth: 14,
          itemHeight: 14,
          icon: 'roundRect',
          itemGap: 15,
          textStyle: {
            color: '#000',
            fontSize: 14,
            padding: [0, 0, 0, 0]
          },
          data: ['关机', '报警', '待机', '运行']
        },
        grid: {
          containLabel: true
        },
        series: [
          {
            type: 'pie',
            roseType: 'angle', // çŽ«ç‘°å›¾
            // selectedMode: "single",
            radius: this.pieChartRadius,
            center: ['45%', '55%'],
            color: [
              '#8B8B8B',
              '#F56436',
              '#FFFF40',
              '#0FC61A'
            ],
            label: {
              position: 'outside',
              show: true,
              color: '#000',
              // textBorderColor: 'inherit',
              // textBorderWidth: 1,
              fontSize: 16,
              formatter: function(params) {
                if (params.name !== '') {
                  return `${params.name}:${params.value}`
                }
              }
            },
            labelLine: {
              show: true,
              length2: 10,
              length: 10
            },
            data: this.runningStateData
          }
        ]
      }
      this.runningStateChart.setOption(option, true)
      this.runningStateChart.hideLoading()
    },
    /* ç»˜åˆ¶è®¾å¤‡åˆ©ç”¨çŽ‡èƒ¶å›Šå›¾ */
    drawEfficiencyChart() {
      const data = this.efficiencyData || []
      // æ— è®ºæ•°æ®æ˜¯å¦ä¸ºç©ºï¼Œå…ˆå–消loading状态
      this.efficiencyChart.hideLoading()
      // æ ‡é¢˜æ–‡æœ¬ç»Ÿä¸€å¤„理
      const titleText = `${moment().subtract(1, 'days').format('M月D日')}各车间利用率排行`
      if (data.length === 0) {
        // æ•°æ®ä¸ºç©ºæ—¶åªå±•示标题
        const option = {
          title: {
            show: true,
            text: titleText,
            x: 'left',
            y: 'top',
            textStyle: {
              fontSize: 18,
              fontWeight: 'normal',
              color: '#1AD8DE'
            }
          },
          // éšè—æ‰€æœ‰è½´å’Œç½‘æ ¼
          xAxis: { show: false },
          yAxis: [{ show: false }, { show: false }],
          grid: { show: false },
          series: []
        }
        this.efficiencyChart.setOption(option, true)
        return
      }
      // æ•°æ®ä¸ä¸ºç©ºæ—¶çš„æ­£å¸¸å¤„理逻辑(保持不变)
      const colorArray = [
        { top: '#79CEAA', bottom: '#79CEAA' },
        { top: '#F589A2', bottom: '#F589A2' },
        { top: '#6FBF9D', bottom: '#6FBF9D' },
        { top: '#66DFE2', bottom: '#66DFE2' },
        { top: '#A7F0C1', bottom: '#A7F0C1' },
        { top: '#FAE893', bottom: '#FAE893' },
        { top: '#F7B7A0', bottom: '#F7B7A0' }
      ]
      const dataMax = +data.sort((x, y) => +y.value - +x.value)[0].value
      let yAxisMax = dataMax === 0 ? 1 : Math.ceil(dataMax / 5) * 5
      const yAxisInterval = yAxisMax / 5
      const option = {
        title: {
          show: true,
          text: titleText,
          x: 'left',
          y: 'top',
          textStyle: {
            fontSize: 18,
            fontWeight: 'normal',
            color: '#1AD8DE'
          }
        },
        grid: {
          left: '3%',
          right: '5%',
          bottom: '0%',
          top: '6%',
          containLabel: true
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: { type: 'none' },
          formatter: function(params) {
            return `<span style="font-weight:bolder;">${params[0].name}</span><br/>
                <span style="display:inline-block; width:10px; height:10px; border-radius:100px; margin-right:5px; background:${params[0].color.colorStops[params[0].dataIndex].color}"></span>
                ${params[0].seriesName} : ${params[0].value}%`
          }
        },
        xAxis: {
          name: '',
          nameTextStyle: { color: '#000000' },
          axisLabel: {
            margin: 20,
            textStyle: { color: '#000000' }
          },
          show: true,
          min: 0,
          max: 'dataMax',
          interval: yAxisInterval,
          type: 'value',
          axisTick: { show: false },
          splitLine: { show: false }
        },
        yAxis: [{
          type: 'category',
          inverse: true,
          triggerEvent: true,
          axisLabel: {
            show: true,
            textStyle: { color: '#000000', fontSize: '14', fontWeight: 'bolder' },
            formatter: function(value) {
              return `${data.find(item => item.productionCode === value).name}`
            }
          },
          splitLine: { show: false },
          axisTick: { show: false },
          axisLine: { show: false },
          data: data.map(item => item.productionCode)
        }, {
          type: 'category',
          inverse: true,
          axisTick: 'none',
          axisLine: 'none',
          show: true,
          axisLabel: {
            textStyle: { color: '#000000', fontSize: '14' },
            formatter: '{value}%'
          },
          data: data
        }],
        series: [{
          name: '利用率',
          type: 'bar',
          zlevel: 1,
          itemStyle: {
            barBorderRadius: 100,
            color: function(params) {
              const num = colorArray.length
              return {
                type: 'linear',
                colorStops: [{
                  offset: 0,
                  color: colorArray[params.dataIndex % num].bottom
                }, {
                  offset: 1,
                  color: colorArray[params.dataIndex % num].top
                }]
              }
            }
          },
          barWidth: 12,
          data: data
        }]
      }
      this.efficiencyChart.setOption(option, true)
    },
    re_drawPieChart() {
      const clientWidth = document.body.clientWidth || document.documentElement.clientWidth
      if (this.firstEnterClientWidth != 1920) {
        this.pieChartRadius = this.normalPieChartRadius.map(item => item = (+item.slice(0, -1) * (clientWidth / 1920)) + '%')
      } else {
        this.pieChartRadius = this.normalPieChartRadius.map(item => item = (+item.slice(0, -1) * (clientWidth / this.firstEnterClientWidth)) + '%')
      }
      console.log('pieChartRadius', this.pieChartRadius)
    },
    /* è°ƒç”¨æŽ¥å£èŽ·å–è®¾å¤‡OEE和利用率对比 */
    getDoubleBarChartDataByApi(productionCode) {
      this.doubleBarChart = this.$echarts.init(document.getElementById('double_bar_chart'))
      this.doubleBarChart.showLoading({
        text: '数据加载中 ...',
        color: '#0696e1', // åŠ è½½åŠ¨ç”»é¢œè‰²
        textColor: '#000000',
        maskColor: 'transparent' // é®ç½©å±‚
      })
      signageApi.getEquipmentMonthStatisticsApi(productionCode)
        .then(res => {
          if (res.success) {
            this.doubleBarChartData = res.result
            this.drawDoubleBarChart()
          }
        })
    },
    /* ç»˜åˆ¶åŒæŸ±å›¾ */
    drawDoubleBarChart() {
      const option = {
        title: {
          text: '月利用率OEE统计',
          left: 'left',
          top: 'top',
          textStyle: {
            fontSize: 18,
            fontWeight: 'normal',
            color: '#1AD8DE'
          }
        },
        color: ['#66DFE2', '#79CEAA'],
        tooltip: {
          confine: true,
          formatter: function(params) {
            return '<span style="font-weight:bolder;">' + params.name + '</span><br/>' +
              '<span style="display:inline-block; width:10px; height:10px; border-radius:100px; margin-right:5px; background:' + params.color + '"></span>' + params.seriesName + ' : ' + params.value + '%'
          }
        },
        grid: {
          left: '5%',
          right: '4%',
          bottom: '10%',
          top: '20%',
          containLabel: true
        },
        legend: {
          icon: 'roundRect',
          orient: 'horizontal',
          left: 'center',
          itemWidth: 14,
          itemHeight: 14,
          formatter: ['{a|{name}}'].join('\n'),
          textStyle: {
            fontSize: 14,
            color: '#000000',
            height: 8,
            rich: {
              a: {
                verticalAlign: 'bottom'
              }
            }
          },
          data: ['OEE', 'TEEP']
        },
        xAxis: {
          type: 'category',
          data: this.doubleBarChartData.dateList,
          axisLine: {
            lineStyle: {
              color: 'rgba(0,0,0)'
            }
          },
          axisLabel: {
            fontSize: 14,
            color: '#000000'
          },
          axisTick: {
            show: true
          }
        },
        yAxis: [
          {
            name: '%',
            nameTextStyle: {
              color: '#000000'
            },
            type: 'value',
            min: 0,
            minInterval: 1,
            axisLine: {
              show: true
            },
            axisTick: {
              show: true
            },
            splitLine: {
              show: false,
              lineStyle: {
                color: 'rgba(255, 255, 255, 0.15)'
                // type: 'dashed', // dotted è™šçº¿
              }
            },
            axisLabel: {
              fontSize: 14,
              color: '#000000',
              fontFamily: 'Bebas'
            }
          },
          {
            type: 'value',
            axisLine: {
              show: true
            },
            axisTick: {
              show: false
            },
            splitLine: {
              show: false
            },
            axisLabel: {
              fontSize: 14,
              formatter: '{value}%', // å³ä¾§Y轴文字显示
              fontFamily: 'Bebas',
              color: '#6A93B9'
            },
            splitArea: {
              show: false
            }
          }],
        series: [{
          type: 'bar',
          barWidth: 15,
          itemStyle: { barBorderRadius: 100 },
          name: 'OEE',
          data: this.doubleBarChartData.oeeList,
          label: {
            show: false,
            lineHeight: 10,
            formatter: params => {
              if (+params.value === 0) return ''
              else return params.value
            },
            position: 'inside',
            textStyle: {
              color: '#000000',
              fontSize: 12
            }
          }
        }, {
          type: 'bar',
          barWidth: 15,
          itemStyle: { barBorderRadius: 100 },
          name: 'TEEP',
          data: this.doubleBarChartData.utilizationList,
          label: {
            show: false,
            lineHeight: 10,
            formatter: params => {
              if (+params.value === 0) return ''
              else return params.value
            },
            position: 'inside',
            textStyle: {
              color: '#000000',
              fontSize: 12
            }
          }
        }
        ]
      }
      this.doubleBarChart.setOption(option, true)
      this.doubleBarChart.hideLoading()
    },
    /**
     * çª—口尺寸变化时触发
     * è°ƒæ•´å›¾è¡¨å°ºå¯¸ä»¥é€‚应分辨率
     */
    handleWindowResize() {
      // this.re_drawPieChart()
      if (this.runningStateChart) this.runningStateChart.resize()
      if (this.efficiencyChart) this.efficiencyChart.resize()
      if (this.first15DaysEfficiencyChart) this.first15DaysEfficiencyChart.resize()
      if (this.barChart) this.barChart.resize()
      if (this.doubleBarChart) this.doubleBarChart.resize()
    }
  }
}
</script>
<style scoped>
/* é¡µç­¾å¯¼èˆªæ ·å¼ */
.tab-nav {
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  background-color: #ffffff;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  overflow-x: auto;
  scrollbar-width: none;
}
.tab-nav::-webkit-scrollbar {
  display: none;
}
.tab-item {
  padding: 8px 16px;
  font-size: 14px;
  color: #666666;
  border-radius: 20px;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.3s ease;
}
.tab-active {
  background-color: #0696e1;
  color: #ffffff;
  font-weight: 500;
}
.tab-item:hover {
  background-color: #f0f5ff;
  color: #0696e1;
}
/* ä¸»å®¹å™¨æ ·å¼ */
.home-container {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding: 1px;
  box-sizing: border-box;
  gap: 16px;
  background-color: #f0f2f7;
}
/* å›¾è¡¨å®¹å™¨æ ·å¼ */
.chart-container {
  display: flex;
  gap: 16px;
  flex: 1;
  min-width: 0;
}
/* å·¦ä¾§å¡ç‰‡åŒºåŸŸ */
.left-cards {
  flex: 1;
  min-width: 0;
}
/* å³ä¾§å¡ç‰‡åŒºåŸŸ */
.right-cards {
  flex: 2;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* é€šç”¨å¡ç‰‡æ ·å¼ */
.card {
  background: #ffffff;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  padding: 16px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  justify-content: flex-start;
}
/* å·¦ä¾§å¡ç‰‡ */
.left-cards-card {
  height: 100%;
  min-height: 100vh;
}
/* å³ä¾§é¡¶éƒ¨å¡ç‰‡ */
.right-top-card {
  flex: 1;
  min-height: 50vh;
}
/* å³ä¾§åº•部容器 */
.right-bottom-card {
  flex: 1;
  min-width: 0;
  min-height: 50vh;
  display: flex;
  gap: 16px;
}
/* å³ä¾§åº•部左右卡片 */
.right-bottom-left-card,
.right-bottom-right-card {
  flex: 1;
}
/* å“åº”式调整 */
@media (max-width: 1200px) {
  .chart-container {
    gap: 12px;
  }
  .card {
    padding: 12px;
  }
}
@media (max-width: 992px) {
  .chart-container {
    flex-direction: column;
  }
  .left-cards, .right-cards {
    width: 100%;
  }
  .left-cards {
    margin-bottom: 16px;
  }
  .right-bottom-card {
    flex-direction: column;
  }
  .tab-item {
    padding: 6px 12px;
    font-size: 13px;
  }
}
@media (max-width: 768px) {
  .home-container {
    padding: 8px;
  }
  .tab-nav {
    padding: 8px;
    margin-bottom: 8px;
  }
}
</style>