yaowanxin
2025-09-18 df07fa2662110d6069071e11915e2f6cea2f400b
Merge branch 'dev' into ywx
已添加39个文件
已修改15个文件
3145 ■■■■■ 文件已修改
.env.development 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.staging 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/HHKJIco.ico 补丁 | 查看 | 原始文档 | blame | 历史
public/MXSCIco.ico 补丁 | 查看 | 原始文档 | blame | 历史
public/RZNY.ico 补丁 | 查看 | 原始文档 | blame | 历史
public/TJXM.ico 补丁 | 查看 | 原始文档 | blame | 历史
public/XYHBico.ico 补丁 | 查看 | 原始文档 | blame | 历史
src/api/financialManagement/expenseManagement.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/backImage@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/biaoti.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/border@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/caiwufenxiback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/chuchangyijianicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/guochengyijianicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongicon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongjineback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongjineicon1@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongjineicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/hetongtitleback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/icon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/jiantou@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/kehuhetongback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/pieback@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shijianmingchengbeijing@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shijianmingxiicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shujutongji@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/shujutongjiicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/yuancailiaoyijianicon@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/BI/zonghetongbingtubiankuang@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/HHKJLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/HHKJView.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/LCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/MXSCBack.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/MXSCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/RZNYLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/RZNYView.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/TJXMLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/TJXMView.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/XYHBLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/XYHBView.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Echarts/echarts.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/carbonManagement/index.vue 1553 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/components/formDia.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index.vue 1478 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -1,8 +1,8 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æ•¦ç…Œé¼Žè¯šç®¡ç†ä¿¡æ¯ç³»ç»Ÿ
VITE_APP_TITLE = æµ‹è¯•进销存管理系统
# å¼€å‘环境配置
VITE_APP_ENV = 'development'
# æ•¦ç…Œé¼Žè¯šç®¡ç†ä¿¡æ¯ç³»ç»Ÿ/开发环境
# æµ‹è¯•进销存管理系统/开发环境
VITE_APP_BASE_API = '/dev-api'
.env.production
@@ -1,10 +1,10 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æ•¦ç…Œé¼Žè¯šç®¡ç†ä¿¡æ¯ç³»ç»Ÿ
VITE_APP_TITLE = æµ‹è¯•进销存管理系统
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
# æ•¦ç…Œé¼Žè¯šç®¡ç†ä¿¡æ¯ç³»ç»Ÿ/生产环境
# æµ‹è¯•进销存管理系统/生产环境
VITE_APP_BASE_API = '/prod-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
.env.staging
@@ -1,10 +1,10 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = æ•¦ç…Œé¼Žè¯šç®¡ç†ä¿¡æ¯ç³»ç»Ÿ
VITE_APP_TITLE = æµ‹è¯•进销存管理系统
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'staging'
# æ•¦ç…Œé¼Žè¯šç®¡ç†ä¿¡æ¯ç³»ç»Ÿ/生产环境
# æµ‹è¯•进销存管理系统/生产环境
VITE_APP_BASE_API = '/stage-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
index.html
@@ -8,8 +8,8 @@
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    />
    <link rel="icon" href="/DHDCico.ico" />
    <title>敦煌鼎诚管理信息系统</title>
    <link rel="icon" href="/ZQHXico.ico" />
    <title>测试进销存管理系统</title>
    <!--[if lt IE 11
      ]><script>
        window.location.href = "/html/ie.html";
package.json
@@ -1,7 +1,7 @@
{
  "name": "ruoyi",
  "version": "3.8.9",
  "description": "敦煌鼎诚管理信息系统",
  "description": "测试进销存管理系统",
  "author": "若依",
  "license": "MIT",
  "type": "module",
@@ -21,6 +21,7 @@
    "@vue-office/excel": "^1.7.14",
    "@vueup/vue-quill": "1.2.0",
    "@vueuse/core": "10.11.0",
    "autofit.js": "^3.2.8",
    "axios": "0.28.1",
    "clipboard": "2.0.11",
    "dayjs": "^1.11.13",
public/HHKJIco.ico
public/MXSCIco.ico
public/RZNY.ico
public/TJXM.ico
public/XYHBico.ico
src/api/financialManagement/expenseManagement.js
@@ -8,6 +8,13 @@
    params,
  });
};
export const listPageAnalysis = (params) => {
  return request({
    url: "/account/accountExpense/report/analysis",
    method: "get",
    params,
  });
};
// æ–°å¢ž
export function add(data) {
src/assets/BI/backImage@2x.png
src/assets/BI/biaoti.png
src/assets/BI/border@2x.png
src/assets/BI/caiwufenxiback@2x.png
src/assets/BI/chuchangyijianicon@2x.png
src/assets/BI/guochengyijianicon@2x.png
src/assets/BI/hetongicon.png
src/assets/BI/hetongjineback@2x.png
src/assets/BI/hetongjineicon1@2x.png
src/assets/BI/hetongjineicon@2x.png
src/assets/BI/hetongtitleback@2x.png
src/assets/BI/icon@2x.png
src/assets/BI/jiantou@2x.png
src/assets/BI/kehuhetongback@2x.png
src/assets/BI/pieback@2x.png
src/assets/BI/shijianmingchengbeijing@2x.png
src/assets/BI/shijianmingxiicon@2x.png
src/assets/BI/shujutongji@2x.png
src/assets/BI/shujutongjiicon@2x.png
src/assets/BI/yuancailiaoyijianicon@2x.png
src/assets/BI/zonghetongbingtubiankuang@2x.png
src/assets/indexViews/HHKJLogo.png
src/assets/indexViews/HHKJView.png
src/assets/indexViews/LCLogo.png
src/assets/indexViews/MXSCBack.png
src/assets/indexViews/MXSCLogo.png
src/assets/indexViews/RZNYLogo.png
src/assets/indexViews/RZNYView.png
src/assets/indexViews/TJXMLogo.png
src/assets/indexViews/TJXMView.png
src/assets/indexViews/XYHBLogo.png
src/assets/indexViews/XYHBView.png
src/components/Echarts/echarts.vue
@@ -76,6 +76,10 @@
    type: Array,
    default: () => []
  },
  visualMap: {
    type: Object,
    default: () => ({})
  },
    option: {
        type: Object,
        default: () => ({})
@@ -113,6 +117,7 @@
  const option = {
    color: props.color.length ? props.color : undefined,
    backgroundColor: props.options.backgroundColor || '#fff',
    textStyle: props.options.textStyle || { color: '#333' },
    xAxis: props.xAxis,
    yAxis: props.yAxis,
    dataset: props.dataset,
@@ -120,6 +125,7 @@
    grid: props.grid,
    legend: props.legend,
    tooltip: props.tooltip,
    visualMap: Object.keys(props.visualMap).length ? props.visualMap : undefined,
  }
  
  chartInstance.clear()
@@ -148,7 +154,7 @@
// Watch all reactive props that affect the chart
watch(
    () => [props.xAxis, props.series, props.legend, props.tooltip],
    () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap],
    () => {
      if (chartInstance) {
        renderChart()
src/layout/components/Sidebar/Logo.vue
@@ -16,7 +16,7 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import useUserStore from '@/store/modules/user'
import defaultLogo from '@/assets/logo/敦煌鼎诚.png' // å¯¼å…¥é»˜è®¤logo
import defaultLogo from '@/assets/indexViews/ZQHXLogo.png' // å¯¼å…¥é»˜è®¤logo
defineProps({
  collapse: {
src/main.js
@@ -76,7 +76,7 @@
app.config.globalProperties.addDateRange = addDateRange;
app.config.globalProperties.selectDictLabel = selectDictLabel;
app.config.globalProperties.selectDictLabels = selectDictLabels;
app.config.globalProperties.javaApi = "http://114.132.189.42:9033";
app.config.globalProperties.javaApi = "http://114.132.189.42:9037";
app.config.globalProperties.HaveJson = (val) => {
  return JSON.parse(JSON.stringify(val));
};
src/router/index.js
@@ -76,20 +76,20 @@
      }
    ]
  },
  {
    path: '/main/MobileChat',
    component: Layout,
    redirect: '',
    hidden: true,
    children: [
      {
        path: '',
        component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
        name: 'MobileChat',
        meta: { title: 'AI对话', icon: 'dashboard', affix: true}
      }
    ]
  },
  // {
  //   path: '/main/MobileChat',
  //   component: Layout,
  //   redirect: '',
  //   hidden: true,
  //   children: [
  //     {
  //       path: '',
  //       component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
  //       name: 'MobileChat',
  //       meta: { title: 'AI对话', icon: 'dashboard', affix: true}
  //     }
  //   ]
  // },
  {
    path: '/user',
    component: Layout,
@@ -111,6 +111,13 @@
    name: "DeviceInfo",
    meta: { title: "设备信息", icon: "monitor" },
  },
  {
    path: "/data-dashboard",
    component: () => import("@/views/reportAnalysis/dataDashboard/index.vue"),
    hidden: true,
    name: "DataDashboard",
    meta: { title: "数据大屏", icon: "dashboard" },
  },
];
// åŠ¨æ€è·¯ç”±ï¼ŒåŸºäºŽç”¨æˆ·æƒé™åŠ¨æ€åŽ»åŠ è½½
src/views/energyManagement/carbonManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1553 @@
<template>
  <div class="carbon-management">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <div class="page-header">
      <div class="header-content">
        <h1 class="page-title">碳排放管理系统</h1>
        <p class="page-subtitle">基于ISO 14064标准 Â· GHG Protocol核算标准</p>
      </div>
      <div class="header-stats">
        <div class="stat-item">
          <span class="stat-label">总碳排放量</span>
          <span class="stat-value">{{totalEmissions}} tCO₂e</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">本月减排</span>
          <span class="stat-value reduction">-{{monthlyReduction}}%</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">碳中和进度</span>
          <span class="stat-value">{{neutralProgress}}%</span>
        </div>
      </div>
    </div>
    <!-- ä¸»è¦å†…容区域 -->
    <div class="dashboard-content">
      <!-- é¡¶éƒ¨æ•°æ®é¢æ¿ -->
      <div class="top-panels">
        <div class="data-panel top-left">
          <div class="panel-title">当前碳排放</div>
          <div class="panel-value">{{carbonData.scope1}} <span class="unit">tCO₂e</span></div>
          <div class="panel-subtitle">范围1直接排放</div>
        </div>
        <div class="data-panel top-center">
          <div class="panel-title">能耗监测</div>
          <div class="panel-value">{{carbonData.scope2}} <span class="unit">tCO₂e</span></div>
          <div class="panel-subtitle">范围2间接排放</div>
        </div>
        <div class="data-panel top-right">
          <div class="panel-title">供应链排放</div>
          <div class="panel-value">{{carbonData.scope3}} <span class="unit">tCO₂e</span></div>
          <div class="panel-subtitle">范围3供应链排放</div>
        </div>
        <div class="data-panel top-far-right">
          <div class="panel-title">减排进度</div>
          <div class="panel-value">{{neutralProgress}} <span class="unit">%</span></div>
          <div class="panel-subtitle">碳中和目标</div>
        </div>
      </div>
      <!-- ä¸­å¿ƒä¸»è§†å›¾åŒºåŸŸ -->
      <div class="center-main-view">
        <!-- å·¦ä¾§æŽ§åˆ¶é¢æ¿ -->
        <div class="left-control-panel">
          <div class="control-section">
            <div class="section-title">碳排放范围</div>
            <el-radio-group v-model="selectedScope" @change="updateScopeData" class="vertical-radio">
              <el-radio-button :value="'all'">全部范围</el-radio-button>
              <el-radio-button :value="'scope1'">范围1</el-radio-button>
              <el-radio-button :value="'scope2'">范围2</el-radio-button>
              <el-radio-button :value="'scope3'">范围3</el-radio-button>
            </el-radio-group>
          </div>
          <div class="control-section">
            <div class="section-title">监测层级</div>
            <el-radio-group v-model="heatmapLevel" @change="updateHeatmapLevel" class="vertical-radio">
              <el-radio-button :value="'device'">设备级</el-radio-button>
              <el-radio-button :value="'line'">产线级</el-radio-button>
              <el-radio-button :value="'enterprise'">企业级</el-radio-button>
            </el-radio-group>
          </div>
        </div>
        <!-- ä¸­å¿ƒçƒ­åЛ图 -->
        <div class="main-heatmap">
          <div class="heatmap-header">
            <h2 class="main-title">碳足迹热力图分析</h2>
            <div class="date-selector">
              <el-date-picker
                v-model="selectedDate"
                type="date"
                placeholder="选择日期"
                size="small"
                @change="updateHeatmapData"
              />
            </div>
          </div>
          <div class="heatmap-view">
            <Echarts ref="heatmapChart"
                     :series="heatmapSeries"
                     :xAxis="heatmapXAxis"
                     :yAxis="heatmapYAxis"
                     :tooltip="heatmapTooltip"
                     :visualMap="heatmapVisualMap"
                     :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                     style="height: 450px"></Echarts>
          </div>
        </div>
        <!-- å³ä¾§æ•°æ®é¢æ¿ -->
        <div class="right-data-panel">
          <div class="data-section">
            <div class="section-title">实时监控</div>
            <div class="mini-chart">
              <Echarts ref="realtimeChart"
                       :series="realtimeSeries"
                       :xAxis="realtimeXAxis"
                                             :chartStyle="chartStyle"
                       :yAxis="realtimeYAxis"
                       :tooltip="realtimeTooltip"
                       :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                       style="height: 300px"></Echarts>
            </div>
          </div>
          <div class="data-section">
            <div class="section-title">趋势分析</div>
            <div class="trend-controls">
              <el-radio-group v-model="trendPeriod" size="small" @change="updateTrendData">
                <el-radio-button :value="'week'">周</el-radio-button>
                <el-radio-button :value="'month'">月</el-radio-button>
                <el-radio-button :value="'year'">å¹´</el-radio-button>
              </el-radio-group>
            </div>
            <div class="mini-chart">
              <Echarts ref="trendChart"
                       :series="trendSeries"
                       :xAxis="trendXAxis"
                       :yAxis="trendYAxis"
                       :tooltip="trendTooltip"
                                             :chartStyle="chartStyle"
                       :legend="trendLegend"
                       :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                       style="height: 200px"></Echarts>
            </div>
          </div>
        </div>
      </div>
      <!-- åº•部进度面板 -->
      <div class="bottom-progress-panel">
        <div class="progress-section">
          <div class="progress-title">2024年减排目标</div>
          <div class="progress-data">
            <span class="current">{{reductionTarget.current}}</span>
            <span class="separator">/</span>
            <span class="target">{{reductionTarget.target}} tCO₂e</span>
          </div>
          <el-progress :percentage="reductionTarget.percentage" :stroke-width="6" color="#00E676"/>
        </div>
        <div class="progress-section">
          <div class="progress-title">碳中和进度</div>
          <div class="progress-data">
            <span class="current">{{neutralTarget.current}}</span>
            <span class="separator">/</span>
            <span class="target">{{neutralTarget.target}} tCO₂e</span>
          </div>
          <el-progress :percentage="neutralTarget.percentage" :stroke-width="6" color="#00D4FF"/>
        </div>
      </div>
      <!-- åº•部数据表格 -->
      <div class="bottom-data-table">
        <div class="table-panel">
          <div class="table-header">
            <h3 class="table-title">碳排放详细数据</h3>
            <div class="table-controls">
              <el-input
                v-model="searchKeyword"
                placeholder="搜索设备或产线"
                size="small"
                style="width: 200px; margin-right: 10px;"
              />
              <el-button type="primary" size="small" @click="exportData">导出数据</el-button>
            </div>
          </div>
          <el-table :data="filteredTableData" style="width: 100%" height="180">
            <el-table-column prop="name" label="设备/产线" width="150"/>
            <el-table-column prop="type" label="类型" width="100"/>
            <el-table-column prop="scope1" label="范围1排放" width="120"/>
            <el-table-column prop="scope2" label="范围2排放" width="120"/>
            <el-table-column prop="scope3" label="范围3排放" width="120"/>
            <el-table-column prop="total" label="总排放量" width="120"/>
            <el-table-column prop="efficiency" label="碳效率" width="100"/>
            <el-table-column prop="status" label="状态" width="100">
              <template #default="scope">
                <el-tag :type="getStatusType(scope.row.status)">{{scope.row.status}}</el-tag>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/Echarts/echarts.vue'
// å“åº”式数据
const selectedScope = ref('all')
const heatmapLevel = ref('device')
const selectedDate = ref(new Date())
const trendPeriod = ref('week')
const searchKeyword = ref('')
// ç¢³æŽ’放数据
const carbonData = ref({
  scope1: 125.6,
  scope2: 89.3,
  scope3: 234.7
})
const chartStyle = {
    width: '96%',
    height: '110%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
// è®¡ç®—属性
const totalEmissions = computed(() => {
  return (carbonData.value.scope1 + carbonData.value.scope2 + carbonData.value.scope3).toFixed(1)
})
const monthlyReduction = ref(8.5)
// è®¡ç®—碳中和进度百分比
const neutralProgress = computed(() => {
  return Math.round(neutralTarget.value.percentage)
})
// å‡æŽ’目标数据
const reductionTarget = ref({
  current: 320.5,
  target: 500,
  percentage: 64.1
})
const neutralTarget = ref({
  current: 1250,
  target: 3800,
  percentage: 32.9
})
// å®žæ—¶ç›‘控图表配置
const realtimeSeries = ref([
  {
    name: '实时碳排放',
    type: 'line',
    smooth: true,
    data: generateRealtimeData(),
    itemStyle: {
      color: '#FF6B6B'
    },
    areaStyle: {
      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
        { offset: 0, color: 'rgba(255, 107, 107, 0.3)' },
        { offset: 1, color: 'rgba(255, 107, 107, 0.1)' }
      ])
    }
  }
])
const realtimeXAxis = [{
  type: 'category',
  data: Array.from({length: 24}, (_, i) => `${i.toString().padStart(2, '0')}:00`),
  axisLabel: { color: '#B8C8E0' }
}]
const realtimeYAxis = [{
  type: 'value',
  name: 'tCO₂e/h',
  axisLabel: { color: '#B8C8E0' },
  nameTextStyle: { color: '#B8C8E0' }
}]
const realtimeTooltip = {
  trigger: 'axis',
  formatter: '{b}: {c} tCO₂e/h'
}
// çƒ­åŠ›å›¾é…ç½®
const heatmapSeries = ref([
  {
    name: '碳排放量',
    type: 'heatmap',
    data: generateHeatmapData(),
    label: {
      show: false
    },
    emphasis: {
      itemStyle: {
        shadowBlur: 10,
        shadowColor: 'rgba(0, 0, 0, 0.5)'
      }
    }
  }
])
const heatmapXAxis = [{
  type: 'category',
  data: Array.from({length: 24}, (_, i) => `${i}:00`),
  splitArea: { show: true },
  axisLabel: { color: '#B8C8E0' }
}]
const heatmapYAxis = [{
  type: 'category',
  data: ['设备A', '设备B', '设备C', '设备D', '设备E', '设备F', '设备G'],
  splitArea: { show: true },
  axisLabel: { color: '#B8C8E0' }
}]
const heatmapTooltip = {
  trigger: 'item',
  formatter: function (params) {
    const [hour, device] = params.data
    const value = params.value[2]
    return `设备: ${heatmapYAxis[0].data[device]}<br/>时间: ${hour}:00<br/>碳排放量: ${value} tCO₂e`
  }
}
const heatmapVisualMap = ref({
  min: 0,
  max: 50,
  calculable: true,
  orient: 'horizontal',
  left: 'center',
  bottom: '5%',
  inRange: {
    color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
  },
  textStyle: { color: '#B8C8E0' }
})
// è¶‹åŠ¿åˆ†æžå›¾è¡¨é…ç½®
const trendSeries = ref([
  {
    name: '范围1',
    type: 'line',
    data: [120, 132, 101, 134, 90, 230, 210],
    itemStyle: { color: '#FF6B6B' }
  },
  {
    name: '范围2',
    type: 'line',
    data: [220, 182, 191, 234, 290, 330, 310],
    itemStyle: { color: '#4ECDC4' }
  },
  {
    name: '范围3',
    type: 'line',
    data: [150, 232, 201, 154, 190, 330, 410],
    itemStyle: { color: '#45B7D1' }
  }
])
const trendXAxis = [{
  type: 'category',
  data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
  axisLabel: { color: '#B8C8E0' }
}]
const trendYAxis = [{
  type: 'value',
  name: 'tCO₂e',
  axisLabel: { color: '#B8C8E0' },
  nameTextStyle: { color: '#B8C8E0' }
}]
const trendTooltip = {
  trigger: 'axis'
}
const trendLegend = {
  data: ['范围1', '范围2', '范围3'],
  textStyle: { color: '#B8C8E0' }
}
// è¡¨æ ¼æ•°æ®
const carbonTableData = ref([
  { name: '生产线A', type: '产线', scope1: 45.2, scope2: 32.1, scope3: 18.7, total: 96.0, efficiency: '良好', status: '正常' },
  { name: '设备B-01', type: '设备', scope1: 12.5, scope2: 8.3, scope3: 5.2, total: 26.0, efficiency: '优秀', status: '正常' },
  { name: '生产线C', type: '产线', scope1: 38.7, scope2: 28.9, scope3: 15.4, total: 83.0, efficiency: '良好', status: '告警' },
  { name: '设备D-02', type: '设备', scope1: 15.8, scope2: 11.2, scope3: 7.1, total: 34.1, efficiency: '一般', status: '正常' },
  { name: '生产线E', type: '产线', scope1: 52.3, scope2: 39.6, scope3: 22.8, total: 114.7, efficiency: '待优化', status: '告警' }
])
// ç”Ÿæˆå®žæ—¶æ•°æ®
function generateRealtimeData() {
  return Array.from({length: 24}, () => (Math.random() * 20 + 10).toFixed(1))
}
// ç”Ÿæˆçƒ­åŠ›å›¾æ•°æ®
function generateHeatmapData() {
  const data = []
  let yAxisLength = 7 // é»˜è®¤è®¾å¤‡çº§
  let baseMultiplier = 1 // åŸºç¡€å€æ•°
  // æ ¹æ®å±‚级确定Y轴长度和数据范围
  if (heatmapLevel.value === 'line') {
    yAxisLength = 5
    baseMultiplier = 2 // äº§çº¿çº§æ•°æ®æ›´å¤§
  } else if (heatmapLevel.value === 'enterprise') {
    yAxisLength = 3
    baseMultiplier = 4 // ä¼ä¸šçº§æ•°æ®æœ€å¤§
  }
  for (let i = 0; i < yAxisLength; i++) {
    for (let j = 0; j < 24; j++) {
      let value
      // ç®€åŒ–的时间段逻辑
      if (j >= 8 && j <= 18) {
        // å·¥ä½œæ—¶é—´æŽ’放量较高
        value = Math.random() * 30 + 20
      } else if (j >= 19 && j <= 22) {
        // æ™šé—´æŽ’放量中等
        value = Math.random() * 20 + 10
      } else {
        // æ·±å¤œå’Œå‡Œæ™¨æŽ’放量较低
        value = Math.random() * 10 + 2
      }
      // æ·»åŠ è®¾å¤‡å·®å¼‚å’Œå±‚çº§å€æ•°
      value *= (0.8 + i * 0.1) * baseMultiplier
      data.push([j, i, Math.round(value * 10) / 10])
    }
  }
  return data
}
// æ›´æ–°èŒƒå›´æ•°æ®
function updateScopeData() {
  // æ ¹æ®é€‰æ‹©çš„范围更新所有相关图表数据
  const scopeMultiplier = {
    'all': 1,
    'scope1': 0.3,
    'scope2': 0.4,
    'scope3': 0.3
  }
  const multiplier = scopeMultiplier[selectedScope.value] || 1
  // æ›´æ–°ç¢³æŽ’放数据显示
  if (selectedScope.value === 'all') {
    carbonData.value = {
      scope1: 125.6,
      scope2: 89.3,
      scope3: 234.7
    }
  } else {
    const baseTotal = 125.6 + 89.3 + 234.7
    carbonData.value = {
      scope1: selectedScope.value === 'scope1' ? 125.6 : 0,
      scope2: selectedScope.value === 'scope2' ? 89.3 : 0,
      scope3: selectedScope.value === 'scope3' ? 234.7 : 0
    }
  }
  // æ›´æ–°çƒ­åŠ›å›¾æ•°æ®
  heatmapSeries.value[0].data = generateHeatmapData().map(item => [
    item[0], item[1], Math.round(item[2] * multiplier * 10) / 10
  ])
  // æ›´æ–°å®žæ—¶ç›‘控数据
  realtimeSeries.value[0].data = generateRealtimeData().map(val =>
    Math.round(parseFloat(val) * multiplier * 10) / 10
  )
}
// æ›´æ–°çƒ­åŠ›å›¾å±‚çº§
function updateHeatmapLevel() {
  // æ ¹æ®å±‚级更新Y轴数据和visualMap范围
  if (heatmapLevel.value === 'device') {
    heatmapYAxis[0].data = ['锅炉A', '压缩机B', '冷却塔C', '风机D', 'æ³µE', '变压器F', '电机G']
    heatmapVisualMap.value.max = 50
  } else if (heatmapLevel.value === 'line') {
    heatmapYAxis[0].data = ['生产线1', '生产线2', '生产线3', '生产线4', '生产线5']
    heatmapVisualMap.value.max = 100
  } else {
    heatmapYAxis[0].data = ['厂区A', '厂区B', '厂区C']
    heatmapVisualMap.value.max = 200
  }
  // æ›´æ–°çƒ­åŠ›å›¾æ•°æ®
  heatmapSeries.value[0].data = generateHeatmapData()
  // æ›´æ–°è¡¨æ ¼æ•°æ®ä»¥åŒ¹é…å½“前层级
  updateTableDataForLevel()
}
// æ ¹æ®å±‚级更新表格数据
function updateTableDataForLevel() {
  const levelConfigs = {
    device: [
      { name: '锅炉A', type: '设备', scope1: 45.2, scope2: 32.1, scope3: 18.7, total: 96.0, efficiency: '良好', status: '正常' },
      { name: '压缩机B', type: '设备', scope1: 38.5, scope2: 28.3, scope3: 15.2, total: 82.0, efficiency: '优秀', status: '正常' },
      { name: '冷却塔C', type: '设备', scope1: 22.8, scope2: 18.9, scope3: 12.3, total: 54.0, efficiency: '良好', status: '告警' },
      { name: '风机D', type: '设备', scope1: 15.6, scope2: 12.4, scope3: 8.1, total: 36.1, efficiency: '一般', status: '正常' },
      { name: 'æ³µE', type: '设备', scope1: 12.3, scope2: 9.8, scope3: 6.4, total: 28.5, efficiency: '优秀', status: '正常' }
    ],
    line: [
      { name: '生产线1', type: '产线', scope1: 125.6, scope2: 89.3, scope3: 56.8, total: 271.7, efficiency: '良好', status: '正常' },
      { name: '生产线2', type: '产线', scope1: 98.4, scope2: 72.1, scope3: 45.2, total: 215.7, efficiency: '优秀', status: '正常' },
      { name: '生产线3', type: '产线', scope1: 87.2, scope2: 65.8, scope3: 41.6, total: 194.6, efficiency: '良好', status: '告警' },
      { name: '生产线4', type: '产线', scope1: 76.9, scope2: 58.3, scope3: 37.1, total: 172.3, efficiency: '一般', status: '正常' },
      { name: '生产线5', type: '产线', scope1: 65.7, scope2: 49.2, scope3: 31.8, total: 146.7, efficiency: '待优化', status: '告警' }
    ],
    enterprise: [
      { name: '厂区A', type: '厂区', scope1: 456.8, scope2: 334.7, scope3: 212.5, total: 1004.0, efficiency: '良好', status: '正常' },
      { name: '厂区B', type: '厂区', scope1: 387.2, scope2: 289.6, scope3: 184.3, total: 861.1, efficiency: '优秀', status: '正常' },
      { name: '厂区C', type: '厂区', scope1: 298.5, scope2: 223.8, scope3: 142.7, total: 665.0, efficiency: '良好', status: '告警' }
    ]
  }
  carbonTableData.value = levelConfigs[heatmapLevel.value] || levelConfigs.device
}
// æ›´æ–°çƒ­åŠ›å›¾æ•°æ®ï¼ˆæ—¥æœŸå˜åŒ–æ—¶ï¼‰
function updateHeatmapData() {
  heatmapSeries.value[0].data = generateHeatmapData()
  // åŒæ—¶æ›´æ–°å…¶ä»–相关数据
  updateScopeData()
}
// æ›´æ–°è¶‹åŠ¿æ•°æ®
function updateTrendData() {
  const trendDataConfigs = {
    week: {
      xAxisData: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
      scope1Data: [120, 132, 101, 134, 90, 80, 75],
      scope2Data: [220, 182, 191, 234, 190, 150, 140],
      scope3Data: [150, 232, 201, 154, 190, 120, 110]
    },
    month: {
      xAxisData: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
      scope1Data: [1200, 1150, 1300, 1250, 1180, 1320, 1280, 1350, 1220, 1290, 1160, 1100],
      scope2Data: [2200, 2100, 2350, 2280, 2150, 2400, 2320, 2450, 2180, 2380, 2120, 2050],
      scope3Data: [1800, 1750, 1950, 1880, 1820, 2000, 1920, 2100, 1850, 1980, 1780, 1720]
    },
    year: {
      xAxisData: ['2019', '2020', '2021', '2022', '2023', '2024'],
      scope1Data: [14500, 14200, 13800, 13500, 13100, 12800],
      scope2Data: [26800, 26200, 25600, 25000, 24400, 23800],
      scope3Data: [22400, 21800, 21200, 20600, 20000, 19400]
    }
  }
  const config = trendDataConfigs[trendPeriod.value] || trendDataConfigs.week
  // æ›´æ–°X轴数据
  trendXAxis[0].data = config.xAxisData
  // æ›´æ–°ç³»åˆ—数据
  trendSeries.value = [
    {
      name: '范围1',
      type: 'line',
      data: config.scope1Data,
      itemStyle: { color: '#FF6B6B' },
      smooth: true
    },
    {
      name: '范围2',
      type: 'line',
      data: config.scope2Data,
      itemStyle: { color: '#4ECDC4' },
      smooth: true
    },
    {
      name: '范围3',
      type: 'line',
      data: config.scope3Data,
      itemStyle: { color: '#45B7D1' },
      smooth: true
    }
  ]
}
// èŽ·å–çŠ¶æ€ç±»åž‹
function getStatusType(status) {
  switch (status) {
    case '正常': return 'success'
    case '告警': return 'warning'
    case '异常': return 'danger'
    default: return 'info'
  }
}
// å¯¼å‡ºæ•°æ®
function exportData() {
  // å‡†å¤‡å¯¼å‡ºæ•°æ®
  const exportDataSet = {
    åŸºæœ¬ä¿¡æ¯: {
      å¯¼å‡ºæ—¶é—´: new Date().toLocaleString('zh-CN'),
      æ•°æ®å±‚级: heatmapLevel.value === 'device' ? '设备级' : heatmapLevel.value === 'line' ? '产线级' : '企业级',
      é€‰æ‹©èŒƒå›´: selectedScope.value === 'all' ? '全部范围' : `范围${selectedScope.value.slice(-1)}`,
      é€‰æ‹©æ—¥æœŸ: selectedDate.value ? selectedDate.value.toLocaleDateString('zh-CN') : '今日'
    },
    ç¢³æŽ’放统计: {
      èŒƒå›´1直接排放: carbonData.value.scope1 + ' tCO₂e',
      èŒƒå›´2间接排放: carbonData.value.scope2 + ' tCO₂e',
      èŒƒå›´3供应链排放: carbonData.value.scope3 + ' tCO₂e',
      æ€»æŽ’放量: totalEmissions.value + ' tCO₂e'
    },
    è¯¦ç»†æ•°æ®: carbonTableData.value,
    çƒ­åŠ›å›¾æ•°æ®: heatmapSeries.value[0].data.map(item => ({
      æ—¶é—´: `${item[0]}:00`,
      è®¾å¤‡åºå·: item[1],
      è®¾å¤‡åç§°: heatmapYAxis.data[item[1]],
      ç¢³æŽ’放量: item[2] + ' tCO₂e'
    }))
  }
  // åˆ›å»ºCSV内容
  let csvContent = '\uFEFF' // BOM for UTF-8
  // åŸºæœ¬ä¿¡æ¯
  csvContent += '基本信息\n'
  Object.entries(exportDataSet.基本信息).forEach(([key, value]) => {
    csvContent += `${key},${value}\n`
  })
  csvContent += '\n'
  // ç¢³æŽ’放统计
  csvContent += '碳排放统计\n'
  Object.entries(exportDataSet.碳排放统计).forEach(([key, value]) => {
    csvContent += `${key},${value}\n`
  })
  csvContent += '\n'
  // è¯¦ç»†æ•°æ®è¡¨æ ¼
  csvContent += '详细数据\n'
  csvContent += '名称,类型,范围1排放,范围2排放,范围3排放,总排放量,碳效率,状态\n'
  exportDataSet.详细数据.forEach(row => {
    csvContent += `${row.name},${row.type},${row.scope1},${row.scope2},${row.scope3},${row.total},${row.efficiency},${row.status}\n`
  })
  csvContent += '\n'
  // çƒ­åŠ›å›¾æ•°æ®ï¼ˆå‰50条)
  csvContent += '热力图数据(前50条)\n'
  csvContent += '时间,设备名称,碳排放量\n'
  exportDataSet.热力图数据.slice(0, 50).forEach(row => {
    csvContent += `${row.时间},${row.设备名称},${row.碳排放量}\n`
  })
  // åˆ›å»ºä¸‹è½½é“¾æŽ¥
  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
  const link = document.createElement('a')
  const url = URL.createObjectURL(blob)
  link.setAttribute('href', url)
  link.setAttribute('download', `碳排放数据_${new Date().toISOString().slice(0, 10)}.csv`)
  link.style.visibility = 'hidden'
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  // æ˜¾ç¤ºæˆåŠŸæ¶ˆæ¯
  console.log('碳排放数据导出成功')
}
// æœç´¢è¿‡æ»¤åŠŸèƒ½
const filteredTableData = computed(() => {
  if (!searchKeyword.value) {
    return carbonTableData.value
  }
  return carbonTableData.value.filter(item =>
    item.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
    item.type.toLowerCase().includes(searchKeyword.value.toLowerCase())
  )
})
// çƒ­åŠ›å›¾ç‚¹å‡»äº‹ä»¶å¤„ç†
function handleHeatmapClick(params) {
  if (params.componentType === 'series') {
    const [hour, deviceIndex, value] = params.data
    const deviceName = heatmapYAxis.data[deviceIndex]
    console.log(`点击了设备: ${deviceName}, æ—¶é—´: ${hour}:00, æŽ’放量: ${value} tCO₂e`)
    // å¯ä»¥åœ¨è¿™é‡Œæ·»åŠ è¯¦ç»†ä¿¡æ¯å¼¹çª—æˆ–è·³è½¬åˆ°è¯¦ç»†é¡µé¢
  }
}
onMounted(() => {
  // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–æ“ä½œ
  console.log('碳管理页面已加载')
  // åˆå§‹åŒ–热力图数据
  updateHeatmapLevel()
  // åˆå§‹åŒ–趋势数据
  updateTrendData()
  // åˆå§‹åŒ–范围数据
  updateScopeData()
  // è®¾ç½®å®šæ—¶å™¨ï¼Œæ¯30秒更新一次实时数据
  const timer = setInterval(() => {
    realtimeSeries.value[0].data = generateRealtimeData()
  }, 30000)
  // æ¸…理定时器
  onBeforeUnmount(() => {
    clearInterval(timer)
  })
})
// æ·»åŠ çƒ­åŠ›å›¾ç‚¹å‡»äº‹ä»¶ç»‘å®š
function bindHeatmapEvents() {
  // è¿™ä¸ªå‡½æ•°å¯ä»¥ç”¨æ¥ç»‘定热力图的点击事件
  // åœ¨å®žé™…使用中,可以通过ECharts的事件系统来实现
}
</script>
<style scoped>
.carbon-management {
  min-height: 100vh;
  background:
    radial-gradient(ellipse at top, rgba(29, 78, 216, 0.15), transparent 50%),
    radial-gradient(ellipse at bottom, rgba(139, 92, 246, 0.15), transparent 50%),
    linear-gradient(135deg, #0a0f1c 0%, #1e293b 25%, #0f172a 50%, #1e293b 75%, #0a0f1c 100%);
  padding: 20px;
  font-family: 'Inter', 'Microsoft YaHei', sans-serif;
  overflow: hidden;
  position: relative;
}
.carbon-management::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background:
    radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
    radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.1) 0%, transparent 50%),
    radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.05) 0%, transparent 50%);
  pointer-events: none;
}
.page-header {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.95) 0%, rgba(30, 41, 59, 0.9) 100%),
    radial-gradient(circle at top right, rgba(59, 130, 246, 0.1), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.2);
  border-radius: 20px;
  padding: 40px;
  margin-bottom: 30px;
  box-shadow:
    0 25px 50px -12px rgba(0, 0, 0, 0.4),
    0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
  overflow: hidden;
  backdrop-filter: blur(20px);
}
.page-header:hover {
  transform: translateY(-2px);
  box-shadow:
    0 32px 64px -12px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.1),
    inset 0 1px 0 rgba(255, 255, 255, 0.15);
}
.page-header::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background:
    linear-gradient(45deg, rgba(59, 130, 246, 0.08) 0%, rgba(147, 51, 234, 0.08) 50%, rgba(236, 72, 153, 0.08) 100%);
  pointer-events: none;
}
.header-content {
  flex: 1;
  position: relative;
  z-index: 1;
}
.page-title {
  font-size: 28px;
  font-weight: bold;
  color: #ffffff;
  margin: 0 0 8px 0;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.page-subtitle {
  font-size: 14px;
  color: #B8C8E0;
  margin: 0;
}
.header-stats {
  display: flex;
  gap: 40px;
  position: relative;
  z-index: 1;
}
.stat-item {
  text-align: center;
  padding: 20px;
  background:
    linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(147, 51, 234, 0.15) 100%),
    radial-gradient(circle at center, rgba(255, 255, 255, 0.05), transparent 70%);
  border-radius: 12px;
  border: 1px solid rgba(148, 163, 184, 0.2);
  position: relative;
  overflow: hidden;
  backdrop-filter: blur(10px);
}
.stat-item:hover {
  transform: translateY(-2px) scale(1.05);
  box-shadow:
    0 20px 25px -5px rgba(59, 130, 246, 0.3),
    0 10px 10px -5px rgba(59, 130, 246, 0.2);
}
.stat-item::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
}
.stat-item:hover::before {
  left: 100%;
}
.stat-label {
  display: block;
  font-size: 12px;
  color: #94A3B8;
  margin-bottom: 8px;
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.stat-value {
  display: block;
  font-size: 28px;
  font-weight: 700;
  color: #00D4FF;
  text-shadow:
    0 0 20px rgba(0, 212, 255, 0.6),
    0 0 40px rgba(0, 212, 255, 0.3);
  position: relative;
  z-index: 1;
}
.stat-value.reduction {
  color: #00E676;
  text-shadow:
    0 0 20px rgba(0, 230, 118, 0.6),
    0 0 40px rgba(0, 230, 118, 0.3);
}
.dashboard-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-height: calc(100vh - 200px);
}
.top-panels {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  gap: 20px;
  height: 120px;
}
.data-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
    radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 10px 10px -5px rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}
.panel-title {
  font-size: 12px;
  color: #94A3B8;
  margin-bottom: 8px;
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.panel-value {
  font-size: 24px;
  font-weight: 700;
  color: #00D4FF;
  text-shadow:
    0 0 20px rgba(0, 212, 255, 0.6),
    0 0 40px rgba(0, 212, 255, 0.3);
  margin-bottom: 4px;
}
.panel-subtitle {
  font-size: 11px;
  color: #B8C8E0;
  font-weight: 400;
}
.unit {
  font-size: 16px;
  color: #94A3B8;
}
.center-main-view {
  display: grid;
  grid-template-columns: 200px 1fr 300px;
  gap: 20px;
  flex: 1;
}
.left-control-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.control-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.section-title {
  font-size: 14px;
  font-weight: 600;
  color: #ffffff;
  margin-bottom: 8px;
}
.vertical-radio {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.main-heatmap {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
}
.heatmap-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.2);
}
.main-title {
  font-size: 18px;
  font-weight: bold;
  color: #ffffff;
  margin: 0;
}
.date-selector {
  display: flex;
  align-items: center;
}
.heatmap-view {
  flex: 1;
}
.right-data-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.data-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.mini-chart {
  width: 100%;
}
.trend-controls {
  margin-bottom: 10px;
}
.bottom-progress-panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  height: 100px;
}
.progress-section {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(16px);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.progress-title {
  font-size: 14px;
  font-weight: 600;
  color: #ffffff;
  margin-bottom: 8px;
}
.progress-data {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin-bottom: 12px;
}
.progress-data .current {
  font-size: 20px;
  font-weight: 700;
  color: #00D4FF;
}
.progress-data .separator {
  font-size: 16px;
  color: #94A3B8;
}
.progress-data .target {
  font-size: 14px;
  color: #B8C8E0;
}
.bottom-data-table {
  margin-top: 20px;
}
.table-panel {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
    radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 20px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 10px 10px -5px rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(16px);
}
.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.2);
}
.table-title {
  font-size: 16px;
  font-weight: 600;
  color: #ffffff;
  margin: 0;
}
.table-controls {
  display: flex;
  align-items: center;
  gap: 10px;
}
.panel-card {
  background:
    linear-gradient(135deg, rgba(15, 27, 46, 0.9) 0%, rgba(30, 41, 59, 0.85) 100%),
    radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.08), transparent 50%);
  border: 1px solid rgba(148, 163, 184, 0.15);
  border-radius: 16px;
  padding: 24px;
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.3),
    0 10px 10px -5px rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  position: relative;
  overflow: hidden;
  backdrop-filter: blur(16px);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.heatmap-card {
  height: 500px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 1px solid rgba(81, 129, 219, 0.3);
  position: relative;
  z-index: 1;
}
.card-title {
  font-size: 18px;
  font-weight: bold;
  color: #ffffff;
  margin: 0;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.heatmap-controls {
  display: flex;
  align-items: center;
}
.scope-stats {
  display: flex;
  flex-direction: column;
  gap: 15px;
  position: relative;
  z-index: 1;
}
.scope-item {
  display: flex;
  align-items: center;
  padding: 20px;
  border-radius: 12px;
  background:
    linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(147, 51, 234, 0.12) 100%),
    radial-gradient(circle at top, rgba(255, 255, 255, 0.05), transparent 60%);
  border-left: 4px solid #00D4FF;
  border: 1px solid rgba(148, 163, 184, 0.15);
  position: relative;
  overflow: hidden;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  backdrop-filter: blur(8px);
}
.scope-item:hover {
  transform: translateY(-3px);
  box-shadow:
    0 15px 30px -5px rgba(59, 130, 246, 0.25),
    0 0 0 1px rgba(255, 255, 255, 0.1);
}
.scope-item::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, #3B82F6, #8B5CF6, #EC4899);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.scope-item:hover::after {
  opacity: 1;
}
/* ç¢³æŽ’放统计样式 */
.carbon-stats {
  display: flex;
  justify-content: space-between;
  margin-bottom: 20px;
  gap: 15px;
}
.carbon-stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 20px;
  background:
    linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(147, 51, 234, 0.12) 100%),
    radial-gradient(circle at top, rgba(255, 255, 255, 0.05), transparent 60%);
  border-radius: 12px;
  border: 1px solid rgba(148, 163, 184, 0.15);
  flex: 1;
  position: relative;
  overflow: hidden;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  backdrop-filter: blur(8px);
}
.carbon-stat-item:hover {
  transform: translateY(-3px);
  box-shadow:
    0 15px 30px -5px rgba(59, 130, 246, 0.25),
    0 0 0 1px rgba(255, 255, 255, 0.1);
}
.carbon-stat-item::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, #3B82F6, #8B5CF6, #EC4899);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.carbon-stat-item:hover::after {
  opacity: 1;
}
.carbon-label {
  color: #94A3B8;
  font-size: 11px;
  text-align: center;
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
.carbon-value {
  color: #00D4FF;
  font-size: 18px;
  font-weight: 700;
  text-shadow:
    0 0 15px rgba(0, 212, 255, 0.6),
    0 0 30px rgba(0, 212, 255, 0.3);
  position: relative;
}
.scope-item.scope1 {
  border-left-color: #FF6B6B;
}
.scope-item.scope2 {
  border-left-color: #FFD93D;
}
.scope-item.scope3 {
  border-left-color: #6BCF7F;
}
.scope-icon {
  font-size: 24px;
  margin-right: 15px;
}
.scope-info {
  flex: 1;
}
.scope-name {
  display: block;
  font-weight: bold;
  color: #ffffff;
  margin-bottom: 5px;
}
.scope-value {
  display: block;
  font-size: 20px;
  font-weight: bold;
  color: #00D4FF;
  margin-bottom: 3px;
  text-shadow: 0 0 8px rgba(0, 212, 255, 0.5);
}
.scope-desc {
  display: block;
  font-size: 12px;
  color: #B8C8E0;
}
.target-progress {
  display: flex;
  flex-direction: column;
  gap: 20px;
  position: relative;
  z-index: 1;
}
.progress-item {
  padding: 15px;
  background: rgba(81, 129, 219, 0.1);
  border-radius: 8px;
  border: 1px solid rgba(81, 129, 219, 0.2);
}
.progress-info {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
.progress-label {
  font-weight: bold;
  color: #ffffff;
}
.progress-value {
  color: #B8C8E0;
  font-size: 14px;
}
.bottom-panel {
  margin-top: 20px;
}
.table-controls {
  display: flex;
  align-items: center;
}
/* Element Plus ç»„件深色主题样式 */
:deep(.el-table) {
  background: transparent !important;
  color: #ffffff !important;
}
:deep(.el-table th) {
  background: rgba(81, 129, 219, 0.2) !important;
  color: #ffffff !important;
  border-bottom: 1px solid rgba(81, 129, 219, 0.3) !important;
}
:deep(.el-table td) {
  background: transparent !important;
  color: #B8C8E0 !important;
  border-bottom: 1px solid rgba(81, 129, 219, 0.1) !important;
}
:deep(.el-table tr:hover > td) {
  background: rgba(81, 129, 219, 0.1) !important;
}
:deep(.el-input__wrapper) {
  background: rgba(15, 27, 46, 0.8) !important;
  border: 1px solid rgba(81, 129, 219, 0.3) !important;
  color: #ffffff !important;
}
:deep(.el-input__inner) {
  color: #ffffff !important;
}
:deep(.el-button--primary) {
  background: linear-gradient(135deg, #5181DB, #D369E0) !important;
  border: none !important;
  box-shadow: 0 0 10px rgba(81, 129, 219, 0.5) !important;
}
/* åž‚直单选按钮组样式 */
:deep(.vertical-radio) {
  display: flex !important;
  flex-direction: column !important;
  gap: 6px !important;
}
:deep(.vertical-radio .el-radio-button) {
  margin: 0 !important;
  width: 100% !important;
}
:deep(.vertical-radio .el-radio-button__inner) {
  background: rgba(59, 130, 246, 0.1) !important;
  border: 1px solid rgba(148, 163, 184, 0.2) !important;
  color: #B8C8E0 !important;
  border-radius: 8px !important;
  padding: 10px 16px !important;
  width: 100% !important;
  text-align: center !important;
  font-size: 12px !important;
  font-weight: 500 !important;
}
:deep(.vertical-radio .el-radio-button__inner:hover) {
  background: rgba(59, 130, 246, 0.2) !important;
  border-color: rgba(59, 130, 246, 0.4) !important;
  color: #ffffff !important;
}
:deep(.vertical-radio .el-radio-button.is-active .el-radio-button__inner) {
  background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important;
  border-color: #3B82F6 !important;
  color: #ffffff !important;
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
}
:deep(.vertical-radio .el-radio-button:first-child .el-radio-button__inner) {
  border-left: 1px solid rgba(148, 163, 184, 0.2) !important;
}
:deep(.el-radio-group .el-radio-button__inner) {
  background: rgba(59, 130, 246, 0.1) !important;
  border: 1px solid rgba(148, 163, 184, 0.2) !important;
  color: #B8C8E0 !important;
  border-radius: 6px !important;
  padding: 6px 12px !important;
  margin: 0 2px !important;
  font-size: 12px !important;
}
:deep(.el-radio-group .el-radio-button__inner:hover) {
  background: rgba(59, 130, 246, 0.2) !important;
  border-color: rgba(59, 130, 246, 0.4) !important;
  color: #ffffff !important;
}
:deep(.el-radio-group .el-radio-button.is-active .el-radio-button__inner) {
  background: linear-gradient(135deg, #3B82F6, #8B5CF6) !important;
  border-color: #3B82F6 !important;
  color: #ffffff !important;
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
}
:deep(.el-date-editor .el-input__wrapper) {
  background: rgba(15, 27, 46, 0.8) !important;
  border: 1px solid rgba(81, 129, 219, 0.3) !important;
}
:deep(.el-progress-bar__outer) {
  background: rgba(81, 129, 219, 0.2) !important;
}
:deep(.el-tag) {
  background: rgba(81, 129, 219, 0.2) !important;
  border: 1px solid rgba(81, 129, 219, 0.3) !important;
  color: #ffffff !important;
}
:deep(.el-tag.el-tag--success) {
  background: rgba(0, 230, 118, 0.2) !important;
  border-color: rgba(0, 230, 118, 0.3) !important;
  color: #00E676 !important;
}
:deep(.el-tag.el-tag--warning) {
  background: rgba(255, 193, 7, 0.2) !important;
  border-color: rgba(255, 193, 7, 0.3) !important;
  color: #FFC107 !important;
}
:deep(.el-tag.el-tag--danger) {
  background: rgba(244, 67, 54, 0.2) !important;
  border-color: rgba(244, 67, 54, 0.3) !important;
  color: #F44336 !important;
}
/* å“åº”式设计 */
@media (max-width: 1200px) {
  .main-content {
    flex-direction: column;
  }
  .header-stats {
    gap: 20px;
  }
}
@media (max-width: 768px) {
  .page-header {
    flex-direction: column;
    text-align: center;
    gap: 20px;
  }
  .header-stats {
    justify-content: center;
  }
  .carbon-management {
    padding: 10px;
  }
}
</style>
src/views/index.vue
@@ -386,7 +386,6 @@
}
// åº”付应收统计
const statisticsReceivable = (type) => {
    console.log(type)
    statisticsReceivablePayable({type: radio1.value}).then((res) => {
        barSeries.value[0].data = [
            // { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } },
src/views/login.vue
@@ -181,8 +181,8 @@
<style lang='scss' scoped>
.login {
  height: 100%;
  background-image: url("../assets/indexViews/DHDCView.png");
  background-size: 100% 100%;
  background-image: url("../assets/indexViews/JZYJView.png");
  background-size: cover;
  position: relative;
}
.title {
src/views/personnelManagement/payrollManagement/components/formDia.vue
@@ -2,7 +2,7 @@
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增入职' : '编辑人员'"
        :title="operationType === 'add' ? '新增薪资' : '编辑薪资'"
        width="50%"
        @close="closeDia"
    >
src/views/reportAnalysis/dataDashboard/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1478 @@
<template>
    <div class="data-dashboard">
      <!-- å…¨å±æŒ‰é’® - ç§»åŠ¨åˆ°å·¦ä¸Šè§’ -->
      <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏显示'">
        <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
        </svg>
        <svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
        </svg>
      </button>
      <!-- é¡¶éƒ¨æ ‡é¢˜æ  -->
      <div class="dashboard-header">
      </div>
      <!-- ä¸»è¦å†…容区域 -->
      <div class="dashboard-content">
      <!-- å·¦ä¾§åŒºåŸŸ -->
      <div class="left-panel">
        <!-- å®¢æˆ·ä¿¡æ¯ç»Ÿè®¡åˆ†æž -->
                <div class="panel-header">
                    <span class="panel-title">客户信息统计分析</span>
                </div>
        <div class="panel-item-customers">
                    <div class="panel-title-second">
                        <div class="panel-title-icon"></div>
                        <div class="total-customers">
                            <span class="label">总合同金额(元)</span>
                            <span class="value">{{sum}}</span>
                        </div>
<!--                        <div class="jiantou"></div>-->
                    </div>
                    <!-- é¥¼å›¾åŒºåŸŸ -->
                    <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 82%;margin-top: 20px">
                        <div style="width: 240px; height: 240px; background-image: url('/src/assets/BI/zonghetongbingtubiankuang@2x.png'); background-size: contain; background-position: center; background-repeat: no-repeat; display: flex; align-items: center; justify-content: center;">
                            <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"
                                             :series="materialPieSeries"
                                             :tooltip="pieTooltip"
                                             :options="{backgroundColor: 'transparent'}"
                                             style="margin-left: 5px;"></Echarts>
                        </div>
                        <ul class="contract-list" style="margin: 0; padding: 0; display: flex; flex-direction: column;justify-content: space-around; height: 100%; overflow-y: auto; scroll-behavior: smooth;" ref="refContractList">
                            <li v-for="item in materialPieSeries[0].data" :key="item.name" style="list-style: none; margin-bottom: 12px;">
                                <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">
                                    <div class="line" :style="{color: item.itemStyle.color}">■ {{item.name}}</div>
                                    <div style="font-weight: 700;font-size: 16px;color: #85B1E4;">ï¿¥{{item.value}}</div>
                                </div>
                            </li>
                        </ul>
                    </div>
        </div>
        <!-- è´¨é‡ç»Ÿè®¡ -->
                <div class="panel-header">
                    <span class="panel-title">质量统计</span>
                </div>
                <div class="main-panel">
                    <div class="panel-item-customers">
                        <div class="quality-cards">
                            <div class="quality-cardSec">
                                <div class="quality-card one"></div>
                                <div class="quality-cardTitle">
                                    <div>原材料已检测数</div>
                                    <div>{{qualityStatisticsObject.supplierNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card two"></div>
                                <div class="quality-cardTitle">
                                    <div>过程检验数量</div>
                                    <div>{{qualityStatisticsObject.processNum}}ä»¶</div>
                                </div>
                            </div>
                            <div class="quality-cardSec">
                                <div class="quality-card three"></div>
                                <div class="quality-cardTitle">
                                    <div>出厂已检数量</div>
                                    <div>{{qualityStatisticsObject.factoryNum}}ä»¶</div>
                                </div>
                            </div>
                        </div>
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="barLegend"
                                         :series="barSeries1"
                                         :tooltip="tooltip"
                                         :xAxis="xAxis1"
                                         :yAxis="yAxis1"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 260px"></Echarts>
                    </div>
                </div>
      </div>
      <!-- ä¸­é—´åŒºåŸŸ -->
      <div class="center-panel">
        <!-- é¡¶éƒ¨ç»Ÿè®¡å¡ç‰‡ -->
        <div class="stats-cards">
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">员工总数</span>
              <span class="card-value">{{totalStaff}}</span>
            </div>
          </div>
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">客户总数</span>
              <span class="card-value">{{totalCustomers}}</span>
            </div>
          </div>
          <div class="stat-card">
            <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
            <div class="card-content">
              <span class="card-label">供应商总数</span>
              <span class="card-value">{{totalSuppliers}}</span>
            </div>
          </div>
        </div>
        <!-- è®¾å¤‡ç»Ÿè®¡ -->
        <div class="equipment-stats">
          <div class="equipment-header">
                        <img src="@/assets/BI/shujutongjiicon@2x.png" alt="图标" class="equipment-icon" />
            <span class="equipment-title">设备统计</span>
          </div>
          <div class="equipment-items">
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentNum}}</span>
              <span class="equipment-label">设备总数</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentRepair}}</span>
              <span class="equipment-label">待维修设备</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{equipmentMaintain}}</span>
              <span class="equipment-label">待保养设备</span>
            </div>
            <div class="equipment-item">
              <span class="equipment-value">{{totalMeasuring}}</span>
              <span class="equipment-label">计量器具总数</span>
            </div>
          </div>
        </div>
        <!-- äº‹ä»¶åç§° -->
        <div class="event-info">
          <div class="event-header">
                        <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" />
            <span class="event-title">事件名称</span>
          </div>
          <div class="event-content">
                        <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList">
   <li v-for="item in todoList" :key="item.id">
    <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">
     <div style="display: flex;justify-content: space-between;align-items: center;">
      <div class="todo-title">待办编号:{{item.approveId}}</div>
      <div class="todo-division">部门:{{item.approveDeptName}}</div>
      <div class="todo-time">{{item.approveTime}}</div>
     </div>
     <div class="todo-division">待办事由:{{item.approveReason}}</div>
    </div>
   </li>
 </ul>
                        <div v-else style="text-align: center">
                            æš‚无数据
                        </div>
          </div>
        </div>
                <div class="financial-header">
                    <span class="financial-title">财务分析</span>
                </div>
                <div class="main-panel">
                    <div class="panel-item-customers">
                        <div class="event-header">
                            <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" />
                            <span class="event-title">经营成果分析</span>
                        </div>
                        <Echarts ref="chart"
                                         :chartStyle="chartStyle"
                                         :grid="grid"
                                         :legend="barLegend1"
                                         :series="barSeries11"
                                         :tooltip="tooltip"
                                         :xAxis="xAxis3"
                                         :yAxis="yAxis3"
                                         :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                         style="height: 300px"></Echarts>
                    </div>
                </div>
      </div>
      <!-- å³ä¾§åŒºåŸŸ -->
      <div class="right-panel">
        <!-- åº”收应付统计 -->
                <div class="panel-header">
                    <span class="panel-title">应收应付统计</span>
                </div>
                <div class="panel-item-customers">
                    <div style="display: flex;justify-content: space-between;margin-bottom: 20px;">
                        <div class="section-title">应收应付统计</div>
                        <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group">
                            <el-radio-button label="按周" :value="1" />
                            <el-radio-button label="按月" :value="2" />
                            <el-radio-button label="按季度" :value="3" />
                        </el-radio-group>
                    </div>
                    <Echarts ref="chart"
                                     :color="barColors2"
                                     :chartStyle="chartStyle"
                                     :grid="grid"
                   :legend="barLegend2"
                                     :series="barSeries"
                                     :tooltip="tooltip"
                                     :xAxis="xAxis"
                                     :yAxis="yAxis"
                                     :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}"
                                     style="height: 260px"></Echarts>
                </div>
        <!-- å›žæ¬¾ä¸Žå¼€ç¥¨åˆ†æž -->
         <div class="panel-header">
                    <span class="panel-title">回款与开票分析</span>
                </div>
        <div class="panel-item-customers" style="padding-top: 60px;">
                    <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"
                                 :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" :options="{backgroundColor: 'transparent', textStyle: {color: '#FFFFFF'}}" style="height: 270px;"></Echarts>
                </div>
      </div>
      </div>
    </div>
</template>
<script setup>
import * as echarts from 'echarts'
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import autofit from 'autofit.js'
import Echarts from "@/components/Echarts/echarts.vue";
import {
    analysisCustomerContractAmounts, getAmountHalfYear,
    homeTodos,
    qualityStatistics,
    statisticsReceivablePayable
} from "@/api/viewIndex.js";
import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js";
import {listCustomer} from "@/api/basicData/customerFile.js";
import {listSupplier} from "@/api/basicData/supplierManageFile.js";
import {getLedgerPage} from "@/api/equipmentManagement/ledger.js";
import {getRepairPage} from "@/api/equipmentManagement/repair.js";
import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js";
import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js";
import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js";
// å…¨å±ç›¸å…³çŠ¶æ€
const isFullscreen = ref(false);
// å“åº”式数据
const currentTime = ref('')
const currentDate = ref('')
const timer = ref(null)
const charts = ref([])
// å›¾è¡¨å¼•用
const customerPieChartRef = ref(null)
const salesBarChartRef = ref(null)
const dataBarChartRef = ref(null)
const financialAreaChartRef = ref(null)
const realtimeLineChartRef = ref(null)
const refContractList = ref(null)
const refTodoList = ref(null)
const timerScroll = ref(null)
const chartStylePie = {
    width: '140%',
    height: '140%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const materialPieSeries = ref([
    {
        type: 'pie',
        radius: ['0%', '90%'],
        avoidLabelOverlap: false,
        itemStyle: {
            borderColor: '#fff',
            borderWidth: 0
        },
        label: {
            show: false
        },
        data: []
    }
])
const pieLegend = reactive({
    show: false,
})
const sum = ref(0)
const totalStaff = ref(0)
const totalCustomers = ref(0)
const totalSuppliers = ref(0)
const yny = ref(0)
const chain = ref(0)
const equipmentNum = ref(0)
const equipmentRepair = ref(0)
const equipmentMaintain = ref(0)
const totalMeasuring = ref(0)
const pieTooltip = reactive({
    trigger: 'item',
    formatter: function (params) {
        // åŠ¨æ€ç”Ÿæˆæç¤ºä¿¡æ¯ï¼ŒåŸºäºŽæ•°æ®é¡¹çš„ name å±žæ€§
        const description = params.name === '本月回款金额' ? '本月回款金额' : '应收款金额';
        return `<div style="color: #B8C8E0">${description} ${params.value}元 ${params.percent}%</div>`;
    },
    position: 'right'
})
const qualityStatisticsObject = ref({
    supplierNum: 0,
    processNum: 0,
    factoryNum: 0,
})
const chartStyle = {
    width: '100%',
    height: '150%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const barSeries = ref([
    {
        name: '应付金额',
        type: 'bar',
        data: [],
        label: {
            show: true,
        },
        itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#00A4ED' },
                { offset: 1, color: '#4EE4FF' }
            ])
        }
    },
    {
        name: '应收金额',
        type: 'bar',
        data: [],
        label: {
            show: true,
        },
        itemStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#537EF5' },
                { offset: 1, color: '#9061F8' }
            ])
        }
    }
])
const radio1 = ref(1)
const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8']
const grid = {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
}
const lineLegend = {
    show: true,
  textStyle: { color: '#B8C8E0' },
    data: ['开票', '回款']
}
const lineSeries = ref([
    {
        type: 'line',
        data: [],
        label: {
            show: true
        },
        showSymbol: true, // æ˜¾ç¤ºåœ†ç‚¹
    },
])
const tooltipLine = {
    trigger: 'axis',
}
const yAxis2 = ref([
    {
        type: 'value',
    }
])
const xAxis2 = ref([
    {
        type: 'category',
        data: [],
        axisLabel: {
            interval: 0,
            formatter: function(value) {
                return value.replace(/~/g, '\n');
            },
        }
    }
])
const barLegend2 = {
    show: true,
    textStyle: { color: '#B8C8E0' },
    data: ['应付金额', '应收金额']
}
const barLegend = {
    show: true,
    textStyle: { color: '#B8C8E0' },
    data: ['原材料不合格数', '过程不合格数', '出厂不合格数']
}
const barLegend1 = {
    show: true,
    textStyle: { color: '#B8C8E0' },
    data: ['总收入', '总支出', '净收入']
}
const barSeries11 = ref([
    {
        name: '总收入',
        type: 'bar',
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#00A4ED' },
                    { offset: 0, color: '#4EE4FF' }
                ]
            }
        },
        data: []
    },
    {
        name: '总支出',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#3378FF' },
                    { offset: 0, color: '#4E8AFF' }
                ]
            }
        },
        data: []
    },
    {
        name: '净收入',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#537EF5' },
                    { offset: 0, color: '#9061F8' }
                ]
            }
        },
        data: []
    },
])
const barSeries1 = ref([
    {
        name: '原材料不合格数',
        type: 'bar',
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#00A4ED' },
                    { offset: 0, color: '#4EE4FF' }
                ]
            }
        },
        data: []
    },
    {
        name: '过程不合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#3378FF' },
                    { offset: 0, color: '#4E8AFF' }
                ]
            }
        },
        data: []
    },
    {
        name: '出厂不合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        itemStyle: {
            color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [
                    { offset: 1, color: '#537EF5' },
                    { offset: 0, color: '#9061F8' }
                ]
            }
        },
        data: []
    },
])
const tooltip = {
    trigger: 'axis',
    axisPointer: {
        type: 'shadow'
    },
    formatter: function (params) {
        let result = params[0].axisValueLabel + '<br/>';
        params.forEach(item => {
            result += `<div style="color: #B8C8E0">${item.marker} ${item.seriesName}: ${item.value}</div>`;
        });
        return result;
    }
}
const xAxis = [{
    type: 'value',
}]
const yAxis = [{
    type: 'category',
    data: ['应收应付统计']
}]
const xAxis1 = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const yAxis1 = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' }
}]
const xAxis3 = ref([{
    type: 'category',
    axisTick: { show: false },
    axisLabel: { color: '#B8C8E0' },
    data: []
}])
const yAxis3 = [{
    type: 'value',
    axisLabel: { color: '#B8C8E0' }
}]
// å¾…办事项
const todoList = ref([])
// çª—口大小变化处理
const handleResize = () => {
  charts.value.forEach(chart => {
    if (chart && chart.resize) {
      chart.resize()
    }
  })
}
// é”€æ¯å›¾è¡¨å®žä¾‹
const disposeCharts = () => {
  charts.value.forEach(chart => {
    if (chart && chart.dispose) {
      chart.dispose()
    }
  })
  charts.value = []
}
// åˆåŒé‡‘额
const analysisCustomer = () => {
    analysisCustomerContractAmounts().then((res) => {
        sum.value = res.data.sum
        yny.value = res.data.yny
        chain.value = res.data.chain
        // ä¸ºæ¯ä¸ªæ•°æ®é¡¹åˆ†é…éšæœºé¢œè‰²
        materialPieSeries.value[0].data = res.data.item.map(item => ({
            ...item,
            itemStyle: { color: getRandomColor() }
        }))
    })
}
// è´¨æ£€ç»Ÿè®¡
const qualityStatisticsInfo = () => {
    qualityStatistics().then((res) => {
        res.data.item.forEach(item => {
            xAxis1.value[0].data.push(item.date)
            barSeries1.value[0].data.push(item.supplierNum)
            barSeries1.value[1].data.push(item.processNum)
            barSeries1.value[2].data.push(item.factoryNum)
        })
        qualityStatisticsObject.value.supplierNum = res.data.supplierNum
        qualityStatisticsObject.value.processNum = res.data.processNum
        qualityStatisticsObject.value.factoryNum = res.data.factoryNum
    })
}
// è´¢åŠ¡ç»Ÿè®¡
const accountStatisticsInfo = () => {
    listPageAnalysis().then((res) => {
        xAxis3.value[0].data = res.data.days
        barSeries11.value[0].data = res.data.totalIncome
        barSeries11.value[1].data = res.data.totalExpense
        barSeries11.value[2].data = res.data.netIncome
    })
}
const getNum = () => {
    const params = {
        pageNum: -1,
        pageSize: -1,
    }
    staffOnJobListPage({...params, staffState: 1}).then(res => {
        totalStaff.value = res.data.total
    })
    listCustomer(params).then((res) => {
        totalCustomers.value = res.total;
    });
    listSupplier(params).then((res) => {
        totalSuppliers.value = res.data.total
    });
}
const getLedgerNum = () => {
    const params = {
        pageNum: -1,
        pageSize: -1,
    }
    getLedgerPage(params).then((res) => {
        equipmentNum.value = res.data.total
    });
    getRepairPage(params).then((res) => {
        equipmentRepair.value = res.data.total
    });
    getUpkeepPage(params).then((res) => {
        equipmentMaintain.value = res.data.total
    });
    measuringInstrumentListPage(params).then((res) => {
        totalMeasuring.value = res.data.total
    });
}
// å¾…办事项
const todoInfoS = () => {
    homeTodos().then((res) => {
        todoList.value = res.data
        // åœ¨èŽ·å–åˆ°å¾…åŠžäº‹é¡¹æ•°æ®åŽï¼Œåˆå§‹åŒ–æ»šåŠ¨åŠŸèƒ½
        nextTick(() => {
            initTodoListScroll()
        })
    })
}
// åº”付应收统计
const statisticsReceivable = (type) => {
    statisticsReceivablePayable({type: radio1.value}).then((res) => {
        // è®¾ç½®åº”付金额数据
        barSeries.value[0].data = [
            { value: res.data.payableMoney }
        ]
        // è®¾ç½®åº”收金额数据
        barSeries.value[1].data = [
            { value: res.data.receivableMoney }
        ]
    })
}
const getAmountHalfYearNum = async () => {
    const res = await getAmountHalfYear()
    console.log(res)
    const monthName = []
    const receiptAmount = []
    const invoiceAmount = []
    res.data.forEach(item => {
        monthName.push(item.month)
        receiptAmount.push(item.receiptAmount)
        invoiceAmount.push(item.invoiceAmount)
    })
    // æ­£ç¡®å“åº”式赋值:创建新的 xAxis å’Œ series å¯¹è±¡
    xAxis2.value[0].data = monthName
    xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~'));
    lineSeries.value = [
        {
            name: '开票',
            type: 'line',
            data: receiptAmount,
            stack: 'Total',
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(131, 207, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(186, 228, 255, 1)'
                    }
                ])
            },
            itemStyle: {
                color: '#2D99FF',
                borderColor: '#2D99FF'
            },
            emphasis: {
                focus: 'series'
            },
            lineStyle: {
                width: 0
            },
            showSymbol: true,
        },
        {
            name: '回款',
            type: 'line',
            data: invoiceAmount,
            stack: 'Total',
            lineStyle: {
                width: 0
            },
            itemStyle: {
                color: '#83CFFF',
                borderColor: '#83CFFF'
            },
            showSymbol: true,
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(54, 153, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(89, 169, 254, 1)'
                    }
                ])
            },
            emphasis: {
                focus: 'series'
            },
        }
    ]
}
// è‡ªåŠ¨è½®æ¢å‘¨ã€æœˆã€å­£åº¦çš„å®šæ—¶å™¨
const autoSwitchTimer = ref(null)
// åˆå§‹åŒ–待办事项列表滚动功能
const initTodoListScroll = () => {
    const todoList = refTodoList.value
    // å¼ºåˆ¶å¯ç”¨æ»šåŠ¨ï¼Œä¸æ£€æŸ¥ä»»ä½•æ¡ä»¶
    if (todoList) {
        // åˆ›å»ºä¸€ä¸ªå…‹éš†é¡¹ï¼Œç”¨äºŽå®žçŽ°æ— ç¼æ»šåŠ¨
        const scrollItems = Array.from(todoList.querySelectorAll('li'))
        if (scrollItems.length > 0) {
            // ç¡®ä¿æœ‰è¶³å¤Ÿçš„项目用于滚动
            // å¦‚果项目太少,多复制几次以确保滚动效果
            if (scrollItems.length < 4) {
                const originalItems = [...scrollItems]
                for (let i = 0; i < 4; i++) {
                    originalItems.forEach(item => {
                        const clone = item.cloneNode(true)
                        todoList.appendChild(clone)
                    })
                }
                // é‡æ–°èŽ·å–æ‰€æœ‰é¡¹ç›®
                scrollItems.push(...Array.from(todoList.querySelectorAll('li')).slice(scrollItems.length));
            }
            const itemHeight = scrollItems[0]?.offsetHeight || 0
            const containerHeight = todoList.clientHeight
            const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
            // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
            for (let i = 0; i < cloneCount; i++) {
                const clone = scrollItems[i % scrollItems.length].cloneNode(true)
                todoList.appendChild(clone)
            }
            let scrollPosition = 0
            const scrollSpeed = 1.5 // å¢žåŠ æ»šåŠ¨é€Ÿåº¦ï¼Œä½¿æ»šåŠ¨æ›´åŠ æ˜Žæ˜¾
            const pauseTime = 3000 // æ»šåŠ¨æš‚åœæ—¶é—´
            let isPaused = false
            let lastTimestamp = 0
            // è¿žç»­æ»šåŠ¨åŠ¨ç”»å‡½æ•°
            function scrollAnimation(timestamp) {
                if (!lastTimestamp) lastTimestamp = timestamp
                const deltaTime = timestamp - lastTimestamp
                lastTimestamp = timestamp
                if (!isPaused) {
                    scrollPosition += scrollSpeed * (deltaTime / 16) // æ ‡å‡†åŒ–为60fps的速度
                    // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
                    const maxScroll = Math.max(todoList.scrollHeight - containerHeight - cloneCount * itemHeight, itemHeight * scrollItems.length)
                    if (scrollPosition >= maxScroll) {
                        scrollPosition = 0
                        todoList.scrollTop = 0
                    } else {
                        todoList.scrollTop = scrollPosition
                    }
                }
                todoList._animationFrame = requestAnimationFrame(scrollAnimation)
            }
            // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
            todoList._animationFrame = requestAnimationFrame(scrollAnimation)
            // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
            const pauseTimer = setInterval(() => {
                isPaused = !isPaused
            }, pauseTime)
            // æ¸…理定时器
            todoList._pauseTimer = pauseTimer
        }
    }
}
const getRandomColor = () => {
    // ç”Ÿæˆæµ…色:R、G、B åˆ†é‡éƒ½åœ¨ 150-255 ä¹‹é—´
    const r = Math.floor(Math.random() * 106) + 150; // 150-255
    const g = Math.floor(Math.random() * 106) + 150; // 150-255
    const b = Math.floor(Math.random() * 106) + 150; // 150-255
    // å°† RGB è½¬æ¢ä¸ºåå…­è¿›åˆ¶é¢œè‰²
    return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
}
// æ›´æ–°æ—¶é—´
const updateTime = () => {
  const now = new Date()
  currentTime.value = now.toLocaleTimeString('zh-CN', { hour12: false })
  currentDate.value = now.toLocaleDateString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    weekday: 'long'
  })
}
// åˆå§‹åŒ–æ—¶é—´
const initTime = () => {
  updateTime()
  timer.value = setInterval(updateTime, 1000)
}
// å…¨å±åŠŸèƒ½å®žçŽ° - é’ˆå¯¹data-dashboard元素
const toggleFullscreen = () => {
    const element = document.querySelector('.data-dashboard')
    if (!element) return
    if (!isFullscreen.value) {
        if (element.requestFullscreen) {
            element.requestFullscreen()
        } else if (element.webkitRequestFullscreen) {
            element.webkitRequestFullscreen()
        } else if (element.msRequestFullscreen) {
            element.msRequestFullscreen()
        }
    } else {
        if (document.exitFullscreen) {
            document.exitFullscreen()
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen()
        } else if (document.msExitFullscreen) {
            document.msExitFullscreen()
        }
    }
}
// ç›‘听全屏变化事件
const handleFullscreenChange = () => {
  const fullscreenElement = document.fullscreenElement ||
                           document.webkitFullscreenElement ||
                           document.msFullscreenElement
  isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('data-dashboard')
}
// ç”Ÿå‘½å‘¨æœŸé’©å­
onMounted(() => {
  initTime()
  // ä½¿ç”¨nextTick确保DOM完全渲染后再初始化图表
  nextTick(() => {
    // åˆå§‹åŒ–autofit自适应
    autofit.init({ dh: 1440, dw: 2560, el: '.data-dashboard', resize: true }, false)
    // æ·»åŠ è‡ªåŠ¨æ»šåŠ¨åŠ¨ç”»æ•ˆæžœ - å®¢æˆ·ä¿¡æ¯åˆ—表
    const contractList = refContractList.value
    if (contractList && contractList.scrollHeight > contractList.clientHeight) {
      // åˆ›å»ºä¸€ä¸ªå…‹éš†é¡¹ï¼Œç”¨äºŽå®žçŽ°æ— ç¼æ»šåŠ¨
      const scrollItems = Array.from(contractList.querySelectorAll('li'))
      const itemHeight = scrollItems[0]?.offsetHeight || 0
      const containerHeight = contractList.clientHeight
      const cloneCount = Math.ceil(containerHeight / itemHeight) + 2
      // å…‹éš†å‰å‡ ä¸ªé¡¹ç›®å¹¶æ·»åŠ åˆ°åˆ—è¡¨æœ«å°¾ï¼Œå®žçŽ°æ— ç¼æ»šåŠ¨
      for (let i = 0; i < cloneCount; i++) {
        const clone = scrollItems[i % scrollItems.length].cloneNode(true)
        contractList.appendChild(clone)
      }
      let scrollPosition = 0
      const scrollSpeed = 1.5 // å¢žåŠ æ»šåŠ¨é€Ÿåº¦ï¼Œä½¿æ»šåŠ¨æ›´åŠ æ˜Žæ˜¾
      const pauseTime = 3000 // æ»šåŠ¨æš‚åœæ—¶é—´
      let isPaused = false
      let lastTimestamp = 0
      // è¿žç»­æ»šåŠ¨åŠ¨ç”»å‡½æ•°
      function scrollAnimation(timestamp) {
        if (!lastTimestamp) lastTimestamp = timestamp
        const deltaTime = timestamp - lastTimestamp
        lastTimestamp = timestamp
        if (!isPaused) {
          scrollPosition += scrollSpeed * (deltaTime / 16) // æ ‡å‡†åŒ–为60fps的速度
          // å½“滚动超过原始内容长度时,重置位置实现无缝滚动
          if (scrollPosition >= contractList.scrollHeight - containerHeight - cloneCount * itemHeight) {
            scrollPosition = 0
            contractList.scrollTop = 0
          } else {
            contractList.scrollTop = scrollPosition
          }
        }
        timerScroll.value = requestAnimationFrame(scrollAnimation)
      }
      // å¯åŠ¨æ»šåŠ¨åŠ¨ç”»
      timerScroll.value = requestAnimationFrame(scrollAnimation)
      // è®¾ç½®æ»šåЍ-暂停-滚动的循环效果
      const pauseTimer = setInterval(() => {
        isPaused = !isPaused
      }, pauseTime)
      // æ¸…理定时器
      contractList._pauseTimer = pauseTimer
    }
    // å¾…办事项列表滚动功能已移至todoInfoS函数中,在获取数据后初始化
  })
  window.addEventListener('resize', handleResize)
  analysisCustomer()
  qualityStatisticsInfo()
    accountStatisticsInfo()
  getNum()
  getLedgerNum()
  todoInfoS()
    statisticsReceivable()
    getAmountHalfYearNum()
  // è®¾ç½®è‡ªåŠ¨è½®æ¢å‘¨ã€æœˆã€å­£åº¦çš„å®šæ—¶å™¨ï¼Œæ¯10秒切换一次
  autoSwitchTimer.value = setInterval(() => {
    // å¾ªçŽ¯åˆ‡æ¢ï¼š1(周) -> 2(月) -> 3(季度) -> 1(周)
    radio1.value = radio1.value === 3 ? 1 : radio1.value + 1
    statisticsReceivable()
  }, 10000) // 10秒切换一次
})
onBeforeUnmount(() => {
  if (timer.value) {
    clearInterval(timer.value)
  }
  if (timerScroll.value) {
    cancelAnimationFrame(timerScroll.value)
  }
  // æ¸…理滚动列表的暂停定时器
  const contractList = refContractList.value
  if (contractList && contractList._pauseTimer) {
    clearInterval(contractList._pauseTimer)
  }
  // æ¸…理待办事项列表的动画和定时器
  const todoList = refTodoList.value
  if (todoList) {
    if (todoList._animationFrame) {
      cancelAnimationFrame(todoList._animationFrame)
      todoList._animationFrame = null
    }
    if (todoList._pauseTimer) {
      clearInterval(todoList._pauseTimer)
      todoList._pauseTimer = null
    }
  }
  // æ¸…理自动轮换周、月、季度的定时器
  if (autoSwitchTimer.value) {
    clearInterval(autoSwitchTimer.value)
    autoSwitchTimer.value = null
  }
  window.removeEventListener('resize', handleResize)
  window.removeEventListener('fullscreenchange', handleFullscreenChange)
  window.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
  window.removeEventListener('MSFullscreenChange', handleFullscreenChange)
  // ç§»é™¤æˆ‘们添加的autofit动态调整监听器
  if (window._autofitUpdateHandler) {
    window.removeEventListener('resize', window._autofitUpdateHandler)
    delete window._autofitUpdateHandler
  }
  disposeCharts()
  // å…³é—­autofit
  autofit.off()
})
</script>
<style scoped>
.data-dashboard {
  position: relative;
  width: 100%;
    height: 100%;
  overflow: hidden;
    background-image: url("@/assets/BI/backImage@2x.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
/* å…¨å±çŠ¶æ€çš„æ ·å¼ */
.data-dashboard:fullscreen {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background-color: inherit;
  z-index: 9999;
}
/* Webkit浏览器前缀 */
.data-dashboard:-webkit-full-screen {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background-color: inherit;
  z-index: 9999;
}
/* MS浏览器前缀 */
.data-dashboard:-ms-fullscreen {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background-color: inherit;
  z-index: 9999;
}
.dashboard-header {
  position: relative;
  z-index: 1;
  height: 170px;
    background-image: url("@/assets/BI/biaoti.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.fullscreen-btn {
  position: absolute;
  top: 10px;
  left: 20px;
  width: 40px;
  height: 40px;
  background: rgba(0, 20, 60, 0.8);
  border: 1px solid rgba(0, 212, 255, 0.3);
  border-radius: 6px;
  color: #00d4ff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
  z-index: 10000;
}
.fullscreen-btn:hover {
  background: rgba(0, 30, 90, 0.9);
  border-color: rgba(0, 212, 255, 0.5);
}
.dashboard-content {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 30px;
  padding: 0 30px;
  height: calc(100% - 120px);
  overflow: hidden;
}
/* ç¡®ä¿å„面板能够正确显示 */
.left-panel, .center-panel, .right-panel {
  overflow: hidden;
}
.left-panel,
.right-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 24px;
    width: 520px;
}
.center-panel {
  flex: 1.5;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.panel-item-customers {
    border: 1px solid #1A58B0;
    padding: 18px;
    width: 100%;
    height: 540px;
}
.panel-title-second {
    height: 60px;
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
    align-items: center;
}
.quality-cards {
    display: flex;
    gap: 12px;
    width: 100%;
    height: 94px;
    justify-content: space-between;
    align-items: center;
}
.quality-cardSec {
    display: flex;
}
.quality-cardTitle {
    font-weight: 400;
    font-size: 14px;
    color: #FFFFFF;
    display: flex;
    align-items: flex-start;
    flex-direction: column;
}
.quality-card {
    width: 80px;
    height: 60px;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.quality-card.one {
    background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png");
}
.quality-card.two {
    background-image: url("@/assets/BI/guochengyijianicon@2x.png");
}
.quality-card.three {
    background-image: url("@/assets/BI/chuchangyijianicon@2x.png");
}
.panel-title-icon {
    width: 60px;
    height: 60px;
    background-image: url("@/assets/BI/hetongicon.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.panel-header {
    background-image: url("@/assets/BI/kehuhetongback@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
}
.panel-title {
    width: 100%;
    font-weight: 500;
    font-size: 16px;
    color: #D9ECFF;
    padding-left: 46px;
    line-height: 36px;
}
.total-customers {
    background-image: url("@/assets/BI/hetongjineback@2x.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    width: 90%;
    height: 60px;
    display: flex;
    align-items: center;
    padding: 0 20px;
    gap: 20px;
}
.total-customers .label {
    font-weight: 500;
    font-size: 16px;
    color: #FFFFFF;
}
.total-customers .value {
    font-weight: 500;
    font-size: 40px;
    background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}
.contract-list {
    margin-top: 16px;
    font-size: 14px;
    color: #666;
    list-style: none;
    padding: 0;
    height: 82%;
    overflow-y: auto;
    width: 460px;
    /* éšè—æ»šåŠ¨æ¡ä½†ä¿ç•™æ»šåŠ¨åŠŸèƒ½ */
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* IE和Edge */
}
/* Chrome、Safari和Opera */
.contract-list::-webkit-scrollbar {
    display: none;
}
.line {
    position: relative;
    width: 230px;
}
.line::after {
    content: '';
    position: absolute;
    right: 2px;
    top: 0;
    bottom: 0;
    width: 1px;
    background-color: #C9C5C5;
    border-radius: 2px;
}
.contract-list li {
    margin-top: 10px;
}
.stats-cards {
  display: flex;
  gap: 30px;
}
.stat-card {
  flex: 1;
  display: flex;
  align-items: center;
    background-image: url("@/assets/BI/border@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  height: 142px;
}
.card-icon {
  width: 100px;
  height: 100px;
  margin: 20px 20px 0 10px;
}
.card-content {
  display: flex;
  flex-direction: column;
    gap: 10px;
}
.card-value {
    font-weight: 500;
    font-size: 40px;
  background: linear-gradient(360deg, #008BFD 0%, #FFFFFF 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.card-label {
    font-weight: 400;
    font-size: 19px;
    color: rgba(208,231,255,0.7);
}
.equipment-stats {
    border: 1px solid #1A58B0;
  padding: 18px;
  height: 240px;
}
.equipment-header {
    font-weight: 500;
    font-size: 21px;
    display: flex;
    border-bottom: 1px solid;
    border-image: linear-gradient( 270deg, rgba(0,126,255,0) 0%, rgba(0,126,255,0.4549) 35%, #007EFF 78%, #007EFF 100%) 1;
    padding-bottom: 2px;
}
.equipment-title {
    font-weight: 500;
    font-size: 21px;
    background: linear-gradient(360deg, #056DFF 0%, #43E8FC 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    line-height: 50px;
}
.equipment-icon {
    width: 50px;
    height: 50px;
}
.equipment-items {
  display: flex;
  justify-content: space-around;
  gap: 30px;
}
.equipment-item {
  text-align: center;
}
.equipment-value {
  display: block;
    font-weight: 500;
    font-size: 40px;
    color: #FFFFFF;
    width: 120px;
    height: 110px;
    line-height: 110px;
    background-image: url("@/assets/BI/shujutongji@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  margin-bottom: 8px;
}
.equipment-label {
    font-weight: 500;
    font-size: 21px;
    color: #FFFFFE;
}
.event-info {
    background-image: url("@/assets/BI/shijianmingchengbeijing@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
  padding: 20px;
  height: 186px;
}
.event-header {
    display: flex;
    align-items: center;
}
.event-icon {
    width: 40px;
    height: 40px;
}
.event-title {
    font-weight: 500;
    font-size: 24px;
    color: #FFFFFE;
    line-height: 30px;
}
.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
  height: 120px; /* æŒ‰ç”¨æˆ·è¦æ±‚调整高度 */
  overflow: hidden;
  font-size: 15px;
}
.todo-list li {
    border-radius: 8px;
    margin-bottom: 12px;
    padding: 12px 40px;
    height: 74px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.todo-title {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
    position: relative;
}
.todo-title::before {
    content: ''; /* å¿…需,表示这里有一个内容 */
    position: absolute;
    left: -10px; /* å®šä½åˆ°å·¦ä¾§ */
    top: 50%; /* åž‚直居中 */
    transform: translateY(-50%); /* å¾®è°ƒåž‚直居中 */
    width: 6px; /* åœ†çš„直径 */
    height: 6px; /* åœ†çš„直径 */
    background: #498CEB;
    border-radius: 50%; /* è®©å…¶å˜æˆåœ†å½¢ */
}
.todo-division {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
}
.todo-time {
    font-weight: 400;
    font-size: 20px;
    color: #FFFFFE;
}
.financial-header {
    background-image: url("@/assets/BI/caiwufenxiback@2x.png");
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
}
.financial-title {
    width: 100%;
    font-weight: 500;
    font-size: 16px;
    color: #D9ECFF;
    padding-left: 46px;
    line-height: 36px;
}
/* è‡ªå®šä¹‰å•选按钮组样式 */
.custom-radio-group :deep(.el-radio-button__inner) {
  background-color: transparent;
  color: white;
  border-color: rgba(255, 255, 255, 0.3);
}
.custom-radio-group :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
  background-color: rgba(255, 255, 255, 0.2);
  color: white;
  border-color: rgba(255, 255, 255, 0.5);
  box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5);
}
</style>
src/views/salesManagement/receiptPayment/index.vue
@@ -224,7 +224,7 @@
    </div>
    <el-dialog
      v-model="dialogFormVisible"
      title="新增发票号页面"
      title="新增回款页面"
      width="70%"
      @close="closeDia"
    >
@@ -318,19 +318,7 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="登记人:" prop="registrant">
              <el-input
                v-model="form.registrant"
                placeholder="请输入"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="来款日期:" prop="receiptPaymentDate">
                        <el-form-item label="回款日期:" prop="receiptPaymentDate">
              <el-date-picker
                style="width: 100%"
                v-model="form.receiptPaymentDate"
@@ -343,6 +331,18 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="登记人:" prop="registrant">
                            <el-input
                                v-model="form.registrant"
                                placeholder="请输入"
                                clearable
                                disabled
                            />
                        </el-form-item>
                    </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
vite.config.js
@@ -8,8 +8,8 @@
  const { VITE_APP_ENV } = env;
  const baseUrl =
    VITE_APP_ENV == "development"
      ? "http://114.132.189.42:8089" // å¼€å‘环境后端接口
      : "http://114.132.189.42:9032"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
      ? "http://114.132.189.42:9036" // å¼€å‘环境后端接口
      : "http://114.132.189.42:9036"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
  return {
    // éƒ¨ç½²ç”Ÿäº§çŽ¯å¢ƒå’Œå¼€å‘çŽ¯å¢ƒä¸‹çš„URL。
@@ -45,7 +45,7 @@
    },
    // vite ç›¸å…³é…ç½®
    server: {
      port: 8001,
      port: 80,
      host: true,
      open: true,
      proxy: {