15个文件已修改
3个文件已添加
1851 ■■■■■ 已修改文件
src/assets/img/sdjt.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tools/Logo.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/router.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/util.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/analysisScreen/alarmBigdata.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/analysisScreen/operationBigdata.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/ActivityList.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/CarInfoList.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/ActiveCom.vue 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/CustTypeConfig.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/CustTypeModal.vue 180 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/DataReGasStation.vue 412 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/DataReLineChart.vue 384 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/DataReMechanism.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/DataRePieChart.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/DepartLabel.vue 500 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataAnalysis/components/DepartLabelModal.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/user/Login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/sdjt.jpg
src/components/tools/Logo.vue
@@ -1,15 +1,15 @@
<template>
  <router-link :to="{ name: 'dashboard' }">
    <div class="flex logo-container flex-center">
      <!-- <img class="logo-img" :src="@/assets/img/logo.png" alt="logo" /> -->
      <img v-if="localInfo.logoPath" class="logo-img" :src="localInfo.logoPath" alt="logo" />
      <img v-else class="logo-img" src="@/assets/img/menuLogo.png" alt="logo" />
      <img class="logo-img" :src="homeLogo" alt="logo" />
      <!-- <h1 class="logo-title" v-if="showTitle">{{ title }}</h1> -->
    </div>
  </router-link>
</template>
<script>
import homeLogo from '@/assets/img/sdjt.jpg'
export default {
  name: 'Logo',
@@ -27,11 +27,8 @@
  },
  data() {
    return {
      localInfo: {},
      homeLogo,
    }
  },
  created() {
    this.localInfo = JSON.parse(localStorage.getItem('localInfo'))
  },
}
</script>
@@ -76,4 +73,3 @@
  }
}
</style>
src/config/router.config.js
@@ -17,7 +17,7 @@
    meta: {
      title: '首页'
    },
    redirect: '/dashboard/analysis',
    redirect: '/analysisScreen/operationBigdata',
    children: []
  },
  {
src/utils/util.js
@@ -11,7 +11,7 @@
      //component: () => import('@/components/layouts/BasicLayout'),
      component: resolve => require(['@/components/layouts/TabLayout'], resolve),
      meta: {title: '首页'},
      redirect: '/dashboard/analysis',
      redirect: '/analysisScreen/operationBigdata',
      children: [...generateChildRouters(data)]
    },
    {
src/views/analysisScreen/alarmBigdata.vue
@@ -1,7 +1,8 @@
<template>
  <div>
    <div class="content">
      <div class="header_ct">{{ localInfo.taskViewName }}</div>
        <div class="header_ct"><span class="blue-text">{{ localInfo.operationViewName }}</span>
        可视化大屏</div>
      <div class="tab_page">
        <div class="operation" @click="goOperation">运营数据分析</div>
        <div class="alarm">告警数据分析</div>
@@ -17,7 +18,7 @@
      </div>
      <div class="info_ct">
        <div class="left_ct">
          <div class="block alarm_overview">
          <div class="block alarm_overview" @click="goToAlarmQuery">
            <div class="block_title">
              <div>
                <span class="blue_text">告警</span>
@@ -133,7 +134,7 @@
              </div> -->
            </div>
          </div>
          <div class="block latest_alarm">
          <div class="block latest_alarm" @click="goToAlarmQuery">
            <div class="block_title">
              <div>
                <span class="blue_text">最新</span>
@@ -157,7 +158,7 @@
          </div>
        </div>
        <div class="right_ct">
          <div class="block terminal_statis">
          <div class="block terminal_statis" @click="goToTerminal">
            <div class="block_title">
              <div>
                <span class="blue_text">终端</span>
@@ -366,8 +367,13 @@
      this.getAlarmRankingData()
    },
    goOperation() {
      // this.$router.replace("operationBigdata")
      window.location.href = window.location.origin + '/analysisScreen/operationBigdata'
    },
    goToAlarmQuery() {
      window.location.href = window.location.origin + '/event/oilout'
    },
    goToTerminal() {
      window.location.href = window.location.origin + '/device/camera'
    },
    //获取告警概况数据
    getOverviewData() {
@@ -1039,6 +1045,9 @@
    text-align: center;
    font-size: 40px;
    color: #fff;
    .blue-text {
      color: #5a81f9;
    }
  }
  .tab_page {
    display: flex;
src/views/analysisScreen/operationBigdata.vue
@@ -27,21 +27,21 @@
            </div>
            <img class="title_line" src="@/assets/img/bigdata/daoh.png" alt="" />
            <div class="icon_ct">
              <div class="icon_fa">
              <div class="icon_fa" @click="goToDataAnalysis">
                <div class="icon_img_fa">
                  <img src="@/assets/img/bigdata/cheliuliang.png" alt="" />
                </div>
                <div class="icon_name">车流量</div>
                <div class="icon_num">{{ overviewData.trafficFlow }}</div>
              </div>
              <div class="icon_fa">
              <div class="icon_fa" @click="goToDataAnalysis">
                <div class="icon_img_fa">
                  <img src="@/assets/img/bigdata/jinzhanshu.png" alt="" />
                </div>
                <div class="icon_name">进站数</div>
                <div class="icon_num">{{ overviewData.inboundCount }}</div>
              </div>
              <div class="icon_fa">
              <div class="icon_fa" @click="goToDataAnalysis('oilCount')">
                <div class="icon_img_fa">
                  <img src="@/assets/img/bigdata/jiayoushuliang.png" alt="" />
                </div>
@@ -65,7 +65,7 @@
              <div class="pie_self" id="refuelingRateChart"></div>
            </div>
          </div>
          <div class="block customer_statis">
          <div class="block customer_statis" @click="goToCarInfo">
            <div class="block_title">
              <div>
                <span class="blue_text">客户</span>
@@ -103,7 +103,7 @@
              </div>
            </div>
          </div>
          <div class="block traffic_statis">
          <div class="block traffic_statis" @click="goToDataAnalysis">
            <div class="block_title">
              <div>
                <span class="blue_text">车流量</span>
@@ -126,7 +126,7 @@
            <!-- <img class="person_img" src="@/assets/img/bigdata/person.png" alt="" />
            <img class="car_img" src="@/assets/img/bigdata/car.png" alt="" /> -->
          </div>
          <div class="block refueling_statis">
          <div class="block refueling_statis" @click="goToDataAnalysis">
            <div class="block_title">
              <div>
                <span class="blue_text">加油</span>
@@ -164,7 +164,7 @@
              </div>
            </div>
          </div>
          <div class="block vehicle_type_distribution">
          <div class="block vehicle_type_distribution" @click="goToCarInfo">
            <div class="block_title">
              <div>
                <span class="blue_text">车型</span>
@@ -184,7 +184,7 @@
              </div>
            </div>
          </div>
          <div class="block monthly_sales_statis">
          <div class="block monthly_sales_statis" @click="goToSales">
            <div class="block_title">
              <div>
                <span class="blue_text">月度销售</span>
@@ -281,6 +281,20 @@
    goAlarmPage() {
      window.location.href = window.location.origin + '/analysisScreen/alarmBigdata'
    },
    goToDataAnalysis(target) {
      const scrollTarget = target === 'oilCount' ? 'oil-count-card' : 'traffic-flow-card'
      localStorage.setItem('scrollToTarget', scrollTarget)
      if (target === 'oilCount') {
        localStorage.setItem('trendAnalysisType', '1')
      }
      window.location.href = window.location.origin + '/data/DataReport'
    },
    goToCarInfo() {
      window.location.href = window.location.origin + '/data/carInfo'
    },
    goToSales() {
      window.location.href = window.location.origin + '/data/sales'
    },
    //获取运营概况数据
    getOverviewData() {
      getAction('/jyz/operationData/overview', {}).then((res) => {
src/views/dataAnalysis/ActivityList.vue
@@ -7,6 +7,9 @@
      <a-tab-pane key="2" tab="活动配置" force-render>
        <ActiveCom></ActiveCom>
      </a-tab-pane>
      <a-tab-pane key="3" tab="机构标签" force-render>
        <DepartLabel></DepartLabel>
      </a-tab-pane>
    </a-tabs>
  </div>
</template>
@@ -14,6 +17,7 @@
<script>
import ActiveCom from './components/ActiveCom'
import CustTypeConfig from './components/CustTypeConfig'
import DepartLabel from './components/DepartLabel'
export default {
  name: 'ActivityList',
@@ -22,6 +26,7 @@
  components: {
    ActiveCom,
    CustTypeConfig,
    DepartLabel,
  },
  data() {
src/views/dataAnalysis/CarInfoList.vue
@@ -44,12 +44,17 @@
          />
        </a-form-item>
        <a-form-item label="客户类型">
          <j-dict-select-tag
          <a-select
            style="width: 180px"
            v-model="queryParam.clientType"
            placeholder="请选择"
            dictCode="client_type"
          />
            allowClear
          >
            <a-select-option v-for="item in clientTypeOptions" :key="item.id" :value="item.id">
              {{ item.clientName }}
            </a-select-option>
          </a-select>
        </a-form-item>
          <!-- <a-select style="width: 180px" allowClear v-model="queryParam.clientType" placeholder="请选择客户类型">
            <a-select-option :value="1"> 普通客户 </a-select-option>
            <a-select-option :value="2"> 潜在客户 </a-select-option>
@@ -158,6 +163,7 @@
      description: 'CarInfo列表',
      queryParam: {},
      tagList: [], //所有标签集合
      clientTypeOptions: [], //客户类型列表
      // 表头
      columns: [
        {
@@ -244,6 +250,14 @@
  },
  methods: {
    // 获取客户类型列表
    getClientTypeOptions() {
      getAction('/jyz/client/listClientType').then((res) => {
        if (res.code === 200) {
          this.clientTypeOptions = res.result
        }
      })
    },
    delSlectTag(item) {
      console.log(item)
      let index = this.selectTagList.findIndex((el) => el == item)
@@ -321,10 +335,19 @@
      this.queryParam.orgCode = node.node.dataRef.orgCode
      this.loadData()
    },
    // 获取客户类型列表
    getClientTypeOptions() {
      getAction('/jyz/client/listClientType').then((res) => {
        if (res.code === 200) {
          this.clientTypeOptions = res.result
        }
      })
    },
  },
  created() {
    this.getTagInfo(true)
    this.getClientTypeOptions()
  },
}
</script>
src/views/dataAnalysis/components/ActiveCom.vue
@@ -27,6 +27,8 @@
          <a v-if="new Date().getTime() < Date.parse(record.startTime)" @click="handleEdit(record)">编辑</a>
          <a v-else disabled>编辑</a>
          <a-divider type="vertical" />
          <a @click="viewVehicleFrequency(record)">查看车辆加油频次</a>
          <a-divider type="vertical" />
          <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
            <a>删除</a>
          </a-popconfirm>
@@ -43,6 +45,7 @@
<script>
import ActivityModal from './ActivityModal'
import { JeecgListMixin } from '@tievd/cube-block/lib/mixins/JeecgListMixin'
import { getAction } from '@tievd/cube-block/lib/api/manage'
export default {
  name: 'ActivityList',
@@ -113,11 +116,88 @@
        delete: '/jyz/activity/delete',
        deleteBatch: '/jyz/activity/deleteBatch',
        exportXlsUrl: '/jyz/activity/exportXls',
        getVehicleFrequency: '/jyz/activity/getVehicleFrequency',
      },
    }
  },
  methods: {},
  methods: {
    viewVehicleFrequency(record) {
      console.log('查看车辆加油频次', record)
      getAction(this.url.getVehicleFrequency, { actId: record.id }).then((res) => {
        if (res.code === 200) {
          const result = res.result
          const activity = result.activity || {}
          const beforeData = result.beforeActivity || {}
          const duringData = result.duringActivity || {}
          const afterData = result.afterActivity || {}
          const formatNumber = (num) => num ? Number(num).toFixed(2) : '0.00'
          const content = `
            <div style="padding: 10px;">
              <h3 style="text-align: center; margin-bottom: 15px;">${activity.name || '活动'}</h3>
              <p style="text-align: center; color: #666; margin-bottom: 20px;">
                活动时间:${activity.startTime} ~ ${activity.endTime}
              </p>
              <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
                <thead>
                  <tr style="background-color: #f5f5f5;">
                    <th style="border: 1px solid #ddd; padding: 8px; text-align: center;">统计项</th>
                    <th style="border: 1px solid #ddd; padding: 8px; text-align: center;">活动前</th>
                    <th style="border: 1px solid #ddd; padding: 8px; text-align: center;">活动中</th>
                    <th style="border: 1px solid #ddd; padding: 8px; text-align: center;">活动后</th>
                  </tr>
                </thead>
                <tbody>
                  <tr>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; font-weight: bold;">加油车辆数</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${beforeData.totalVehicles || 0} 辆</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; color: #1890ff;">${duringData.totalVehicles || 0} 辆</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${afterData.totalVehicles || 0} 辆</td>
                  </tr>
                  <tr>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; font-weight: bold;">加油记录数</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${beforeData.totalOilRecords || 0} 笔</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; color: #1890ff;">${duringData.totalOilRecords || 0} 笔</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${afterData.totalOilRecords || 0} 笔</td>
                  </tr>
                  <tr>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; font-weight: bold;">平均加油量</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${formatNumber(beforeData.avgOilVolume)} 升</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; color: #1890ff;">${formatNumber(duringData.avgOilVolume)} 升</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${formatNumber(afterData.avgOilVolume)} 升</td>
                  </tr>
                  <tr>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; font-weight: bold;">总加油量</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${beforeData.totalOilVolume || 0} 升</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center; color: #1890ff;">${duringData.totalOilVolume || 0} 升</td>
                    <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${afterData.totalOilVolume || 0} 升</td>
                  </tr>
                </tbody>
              </table>
              <p style="text-align: center; color: #999; font-size: 12px; margin-top: 10px;">
                注:活动期间数据以蓝色高亮显示
              </p>
            </div>
          `
          this.$info({
            title: '活动加油频次对比统计',
            content: this.$createElement('div', { domProps: { innerHTML: content } }),
            width: 700,
            okText: '确定',
          })
        } else {
          this.$message.error(res.message || '查询失败')
        }
      }).catch((err) => {
        console.error(err)
        this.$message.error('查询失败')
      })
    },
  },
  created() {},
}
src/views/dataAnalysis/components/CustTypeConfig.vue
@@ -46,6 +46,9 @@
<script>
import CustTypeModal from './CustTypeModal'
import { JeecgListMixin } from '@tievd/cube-block/lib/mixins/JeecgListMixin'
import { UI_CACHE_DB_DICT_DATA } from '@tievd/cube-block/lib/store/mutation-types'
import { deleteAction, getAction } from '@tievd/cube-block/lib/api/manage'
import Vue from 'vue'
export default {
  name: 'ActivityList',
@@ -123,6 +126,35 @@
  },
  methods: {
    modalFormOk() {
      this.loadData()
      this.refreshDictCache()
    },
    refreshDictCache() {
      getAction('/sys/dict/queryAllDictItems').then((res) => {
        if (res.success) {
          Vue.ls.remove(UI_CACHE_DB_DICT_DATA)
          Vue.ls.set(UI_CACHE_DB_DICT_DATA, res.result, 7 * 24 * 60 * 60 * 1000)
        }
      })
    },
    handleDelete(id) {
      // 删除前清除前端字典缓存
      // 删除后刷新页面以更新字典缓存
      deleteAction(this.url.delete, { id: id })
        .then((res) => {
          if (res.success) {
            this.$message.success(res.message)
            this.loadData()
            this.refreshDictCache()
          } else {
            this.$message.warning(res.message)
          }
        })
        .finally(() => {
          this.loading = false
        })
    },
    transformConfigText(obj) {
      var text = ''
      if (obj.timeStr == '1,YEARS') {
src/views/dataAnalysis/components/CustTypeModal.vue
@@ -17,6 +17,9 @@
            v-model="clientConfigsList.clientName"
          ></a-input>
        </a-form-item>
        <div v-if="!clientConfigsList.clientConfigs || clientConfigsList.clientConfigs.length === 0" style="text-align: center">
          <a-button type="primary" @click="addConditions"> 添加规则 </a-button>
        </div>
        <div v-for="(subItem, subIndex) in clientConfigsList.clientConfigs" :key="subIndex">
          <div style="display: flex">
            <div>条件{{ subIndex + 1 }}:</div>
@@ -44,34 +47,100 @@
              <a-select-option value="1,YEARS"> 近1年 </a-select-option>
            </a-select>
          </a-form-item>
          <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="加油频次">
          <!-- 规则类型选择:加油频次 OR 加油趋势 -->
          <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="规则类型">
            <a-select
              :disabled="subItem.timeStr == '7,DAYS' || subItem.timeStr == '30,DAYS'"
              style="width: 300px"
              v-model="subItem.countType"
              v-model="subItem.ruleType"
              placeholder="请选择"
              @change="ruleTypeChange($event, subIndex)"
            >
              <a-select-option :value="1"> 累计 </a-select-option>
              <a-select-option :value="2"> 每月 </a-select-option>
              <a-select-option :value="1"> 加油频次 </a-select-option>
              <a-select-option :value="2"> 加油趋势 </a-select-option>
            </a-select>
            <div style="display: flex">
              <a-select style="width: 150px" v-model="subItem.countRef" placeholder="请选择">
                <a-select-option :value="1"> 大于 </a-select-option>
                <a-select-option :value="0"> 等于 </a-select-option>
                <a-select-option :value="-1"> 小于 </a-select-option>
          </a-form-item>
          <!-- 加油频次配置(ruleType=1时显示) -->
          <div v-show="subItem.ruleType == 1">
            <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="加油频次">
              <a-select
                :disabled="subItem.timeStr == '7,DAYS' || subItem.timeStr == '30,DAYS'"
                style="width: 300px"
                v-model="subItem.countType"
                placeholder="请选择"
              >
                <a-select-option :value="1"> 累计 </a-select-option>
                <a-select-option :value="2"> 每月 </a-select-option>
              </a-select>
              <div style="display: flex">
                <a-select style="width: 150px" v-model="subItem.countRef" placeholder="请选择">
                  <a-select-option :value="1"> 大于 </a-select-option>
                  <a-select-option :value="0"> 等于 </a-select-option>
                  <a-select-option :value="-1"> 小于 </a-select-option>
                </a-select>
                <a-input
                  style="width: 150px"
                  placeholder="请输入次数"
                  onkeyup="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{this.value=this.value.replace(/\D/g,'')}"
                  onafterpaste="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'0')}else{this.value=this.value.replace(/\D/g,'')}"
                  v-model="subItem.countNum"
                ></a-input>
              </div>
            </a-form-item>
          </div>
          <!-- 加油趋势配置(ruleType=2时显示) -->
          <div v-show="subItem.ruleType == 2">
            <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="加油趋势">
              <a-select
                style="width: 300px"
                v-model="subItem.countTrend"
                placeholder="请选择"
              >
                <a-select-option :value="1"> 稳定 </a-select-option>
                <a-select-option :value="2"> 减少 </a-select-option>
              </a-select>
              <div v-if="subItem.countTrend == 1" style="color: #999; font-size: 12px; margin-top: 5px;">
                稳定:历史月加油次数≥次数 且 近期月加油次数≥次数
              </div>
              <div v-if="subItem.countTrend == 2" style="color: #999; font-size: 12px; margin-top: 5px;">
                减少:历史月加油次数≥次数 但 近期月加油次数<次数
              </div>
            </a-form-item>
            <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="历史月数">
              <a-select
                style="width: 140px"
                v-model="subItem.historyMonths"
                placeholder="历史月数"
              >
                <a-select-option :value="3"> 3个月 </a-select-option>
                <a-select-option :value="6"> 6个月 </a-select-option>
                <a-select-option :value="9"> 9个月 </a-select-option>
                <a-select-option :value="12"> 12个月 </a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="近期月数">
              <a-select
                style="width: 140px"
                v-model="subItem.recentMonths"
                placeholder="近期月数"
              >
                <a-select-option :value="1"> 1个月 </a-select-option>
                <a-select-option :value="2"> 2个月 </a-select-option>
                <a-select-option :value="3"> 3个月 </a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="固定加油次数">
              <a-input
                style="width: 150px"
                placeholder="请输入次数"
                placeholder="每月固定次数"
                onkeyup="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{this.value=this.value.replace(/\D/g,'')}"
                onafterpaste="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'0')}else{this.value=this.value.replace(/\D/g,'')}"
                v-model="subItem.countNum"
              ></a-input>
            </div>
          </a-form-item>
        </div>
        <div style="text-align: center">
          <a-button type="primary" @click="addConditions"> 增加条件 </a-button>
            </a-form-item>
          </div>
          <div style="text-align: center" v-if="subIndex === clientConfigsList.clientConfigs.length - 1">
            <a-button type="primary" @click="addConditions"> 增加条件 </a-button>
          </div>
        </div>
      </div>
    </a-spin>
@@ -119,6 +188,21 @@
  methods: {
    moment,
    buildCondition(condition = {}) {
      return Object.assign(
        {
          ruleType: 1,
          timeStr: '3,MONTHS',
          countType: 2,
          countRef: 1,
          countNum: 3,
          countTrend: null,
          historyMonths: null,
          recentMonths: null,
        },
        condition
      )
    },
    //删除条件
    delConditions(index) {
      console.log(index)
@@ -129,10 +213,46 @@
        this.clientConfigsList.clientConfigs[index].countType = 1
      }
    },
    trendChange(e, index) {
      // 选择趋势时,设置默认值
      if (e > 0) {
        this.clientConfigsList.clientConfigs[index].historyMonths = 3
        this.clientConfigsList.clientConfigs[index].recentMonths = 3
      } else {
        // 清除趋势配置
        this.clientConfigsList.clientConfigs[index].historyMonths = null
        this.clientConfigsList.clientConfigs[index].recentMonths = null
      }
    },
    ruleTypeChange(e, index) {
      let target = this.clientConfigsList.clientConfigs[index]
      // 切换规则类型时,清空对应的配置
      if (e == 1) {
        // 加油频次:清除趋势配置
        Object.assign(target, {
          ruleType: 1,
          countTrend: null,
          historyMonths: null,
          recentMonths: null,
          countType: target.countType || 2,
          countRef: target.countRef || 1,
        })
      } else if (e == 2) {
        // 加油趋势:清除频次配置,设置默认值
        Object.assign(target, {
          ruleType: 2,
          countType: 2,
          countRef: 1,
          countTrend: target.countTrend || 1,
          historyMonths: target.historyMonths || 3,
          recentMonths: target.recentMonths || 3,
        })
      }
    },
    //新增条件
    addConditions(index) {
      console.log(index)
      this.clientConfigsList.clientConfigs.push({})
      this.clientConfigsList.clientConfigs.push(this.buildCondition())
    },
    disabledDate(current) {
      // Can not select days before today and today
@@ -151,7 +271,7 @@
    add() {
      this.edit({
        clientName: '',
        clientConfigs: [{}],
        clientConfigs: [this.buildCondition()],
      })
    },
    edit(record) {
@@ -163,7 +283,11 @@
      // }
      this.visible = true
      this.model = Object.assign({}, record)
      this.clientConfigsList = record
      this.clientConfigsList = Object.assign({}, record, {
        clientConfigs: (record.clientConfigs || []).length
          ? record.clientConfigs.map((item) => this.buildCondition(item))
          : [],
      })
    },
    close() {
      this.$emit('close')
@@ -183,16 +307,20 @@
        if (JSON.stringify(el) == '{}') {
          isEmpty = true
        }
        for (let k in el) {
          if (el[k] == '' || el[k].length == '0') {
        // 根据规则类型校验
        if (el.ruleType == 1) {
          // 加油频次:检查时间范围、次数类型、关系、次数
          if (!el.timeStr || !el.countRef || !el.countType || !el.countNum) {
            isEmpty = true
          }
        }
        if (!el.timeStr || !el.countRef || !el.countType || !el.countNum) {
          isEmpty = true
          if (el.countRef == 0 || el.countNum == 0) {
            isEmpty = false
        } else if (el.ruleType == 2) {
          // 加油趋势:检查时间范围、趋势、历史月数、近期月数、次数
          if (!el.timeStr || !el.countTrend || !el.historyMonths || !el.recentMonths || !el.countNum) {
            isEmpty = true
          }
        } else {
          // 未选择规则类型
          isEmpty = true
        }
      })
      if (isEmpty) {
src/views/dataAnalysis/components/DataReGasStation.vue
@@ -1,5 +1,5 @@
<template>
  <div style="margin-right: 30px" class="data-reMechanism">
  <div style="margin-right: 30px" class="data-reMechanism" :class="{ 'trend-detail-open': trendDetailVisible }">
    <div style="display: flex; align-items: center">
      <div style="margin-right:12px;font-size:16px;font-weight:bold">趋势分析</div>
      <a-radio-group v-model="trendAnalysisType" @change="trendAnalysisTypeChange">
@@ -9,10 +9,11 @@
        <a-radio-button :value="3"> 通过率 </a-radio-button>
        <a-radio-button :value="4"> 客户 </a-radio-button>
        <a-radio-button :value="5"> 流失率 </a-radio-button>
        <a-radio-button :value="6"> 销售金额 </a-radio-button>
      </a-radio-group>
    </div>
    <div>
      <div style="display: flex; align-items: flex-end; flex-direction: column">
      <div class="trend-analysis-chart" :class="{ 'is-modal-open': trendDetailVisible }" style="display: flex; align-items: flex-end; flex-direction: column">
        <a-radio-group
          style="margin: 20px 0"
          size="small"
@@ -31,6 +32,8 @@
          :chartData="trendAnalysisData"
          style="width: 100%"
          :showpercent="trendAnalysisChartShowPercent"
          :enablePointClick="true"
          @chart-click="handleTrendChartClick"
        ></DataReLineChart>
      </div>
      <div id="trendAnalysisChartID"></div>
@@ -64,7 +67,7 @@
        <a-range-picker style="width: 300px; margin: 0 12px" v-if="isContrast" @change="contrastTimeChange" />
      </div>
      <div class="fueling-overview-block">
        <div class="fueling-overview-item">
        <div class="fueling-overview-item" id="traffic-flow-card">
          <div class="fueling-overview-name">车流量</div>
          <div class="fueling-overview-num">
            <span style="margin-right: 24px">{{ statisTotalObj.carCount }}</span>
@@ -78,7 +81,7 @@
            <span style="color: #16b777" v-if="isContrast">{{ contrastObj.appearCount }}</span>
          </div>
        </div>
        <div class="fueling-overview-item">
        <div class="fueling-overview-item" id="oil-count-card">
          <div class="fueling-overview-name">加油数</div>
          <div class="fueling-overview-num">
            <span style="margin-right: 24px">{{ statisTotalObj.oilCount }}</span>
@@ -120,7 +123,49 @@
            <span style="color: #16b777" v-if="isContrast">{{ contrastObj.rebackRate }}%</span>
          </div>
        </div>
        <div class="fueling-overview-item">
          <div class="fueling-overview-name">销售金额(元)</div>
          <div class="fueling-overview-num">
            <span style="margin-right: 24px">{{ statisTotalObj.totalAmount }}</span>
            <span style="color: #16b777" v-if="isContrast">{{ contrastObj.totalAmount }}</span>
          </div>
        </div>
      </div>
    </div>
    <div class="oil-freq-compare-ct">
      <div class="block-title">活动前后车辆加油频次</div>
      <div class="oil-freq-compare-operator">
        <span>活动前时段</span>
        <a-range-picker
          style="width: 360px; margin: 0 12px"
          :value="beforeTimeRange"
          format="YYYY-MM-DD HH:mm:ss"
          valueFormat="YYYY-MM-DD HH:mm:ss"
          :show-time="{ defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')] }"
          @change="beforeTimeChange"
        />
        <span>活动后时段</span>
        <a-range-picker
          style="width: 360px; margin: 0 12px"
          :value="afterTimeRange"
          format="YYYY-MM-DD HH:mm:ss"
          valueFormat="YYYY-MM-DD HH:mm:ss"
          :show-time="{ defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')] }"
          @change="afterTimeChange"
        />
        <a-button type="primary" @click="getOilFreqCompareData"> 统计频次 </a-button>
      </div>
      <a-table
        class="oil-freq-compare-table"
        rowKey="licenseNum"
        size="middle"
        :columns="oilFreqCompareColumns"
        :dataSource="oilFreqCompareData"
        :loading="oilFreqCompareLoading"
        :scroll="{ y: 420 }"
        :pagination="{ pageSize: 10, showSizeChanger: true, pageSizeOptions: ['10', '20', '50'] }"
      >
      </a-table>
    </div>
    <div>
      <div class="block-title">车型分析</div>
@@ -170,11 +215,61 @@
        </div>
      </div>
    </div>
    <div>
      <div class="block-title">油品类型分析</div>
      <div style="display: flex">
        <DataReLineChart
          style="width: 70%"
          domId="oilTypeLine"
          ref="oilTypeLineRef"
          :chartData="oilTypeLineData"
        ></DataReLineChart>
        <div style="width: 30%">
          <a-radio-group v-model="oilTypeSpecies" size="small" @change="oilTypeSpeciesChange">
            <a-radio-button :value="5"> 油品销量 </a-radio-button>
            <a-radio-button :value="6"> 销售金额 </a-radio-button>
          </a-radio-group>
          <DataRePieChart
            style="width: 100%"
            domId="oilTypePie"
            ref="oilTypePieRef"
            :chartData="oilTypePieData"
          ></DataRePieChart>
        </div>
      </div>
    </div>
    <TopTenCustomers
      :customerData="customerObj"
      :typeOneTotal="customerOneTotal"
      :typeTwoTotal="customerTwoTotal"
    ></TopTenCustomers>
    <a-modal
      centered
      width="70%"
      :footer="null"
      v-model="trendDetailVisible"
      :title="trendDetailTitle"
      :zIndex="5000"
      :getContainer="getModalContainer"
      wrapClassName="trend-detail-modal-wrap"
      @cancel="closeTrendDetail"
    >
      <a-table
        size="middle"
        bordered
        :columns="trendDetailColumns"
        :dataSource="trendDetailData"
        :loading="trendDetailLoading"
        :scroll="{ y: 420 }"
        :pagination="{ pageSize: 10, showSizeChanger: true, pageSizeOptions: ['10', '20', '50'] }"
        :rowKey="
          (record, index) => {
            return record.id || record.licenseNum || record.captureTime || record.startTime || index
          }
        "
      >
      </a-table>
    </a-modal>
  </div>
</template>
@@ -183,6 +278,7 @@
import DataReLineChart from './DataReLineChart'
import DataRePieChart from './DataRePieChart'
import { getAction, postAction } from '@tievd/cube-block/lib/api/manage'
import moment from 'moment'
export default {
  name: 'DataReMechanism',
  components: {
@@ -209,6 +305,7 @@
      description: '数据报表(加油站)',
      vehicleTypeSpecies: 1,
      fuelingStationSpecies: 2,
      oilTypeSpecies: 5,
      searchWay: '1',
      isContrast: false,
      activeTime: '',
@@ -223,15 +320,150 @@
      vehicleTypePieData: {}, //车型分析饼图数据
      fuelLevelLineData: {}, //加油位分析折线图数据
      fuelLevelPieData: {}, //加油位分析饼图数据
      oilTypeLineData: {}, //油品类型分析折线图数据
      oilTypePieData: {}, //油品类型分析饼图数据
      vehicleTypeLineData: {}, //车型分析折线图数据
      trendAnalysisType: 0, //趋势分析选择类型
      trendAnalysisUnit: 'DAYS', //趋势分析选择时间
      trendAnalysisData: {}, //趋势分析数据
      trendAnalysisChart: null, //趋势分析图表
      trendDetailVisible: false,
      trendDetailTitle: '趋势分析明细',
      trendDetailLoading: false,
      trendDetailData: [],
      trendDetailColumns: [],
      beforeStartTime: '',
      beforeEndTime: '',
      afterStartTime: '',
      afterEndTime: '',
      beforeTimeRange: [],
      afterTimeRange: [],
      oilFreqCompareLoading: false,
      oilFreqCompareData: [],
      oilFreqCompareColumns: [
        {
          title: '车牌号',
          align: 'center',
          dataIndex: 'licenseNum',
        },
        {
          title: '活动前加油次数',
          align: 'center',
          dataIndex: 'beforeOilCount',
        },
        {
          title: '活动后加油次数',
          align: 'center',
          dataIndex: 'afterOilCount',
        },
        {
          title: '变化值',
          align: 'center',
          dataIndex: 'diffOilCount',
        },
        {
          title: '变化率',
          align: 'center',
          dataIndex: 'diffRate',
          customRender: function (t) {
            if (t === null || t === undefined) {
              return '--'
            }
            return t + '%'
          },
        },
      ],
    }
  },
  methods: {
    moment,
    getModalContainer() {
      return document.body
    },
    getTrendDetailColumnsByType() {
      let commonIndexColumn = {
        title: '序号',
        dataIndex: '',
        key: 'rowIndex',
        width: 80,
        align: 'center',
        customRender: function (t, r, index) {
          return parseInt(index) + 1
        },
      }
      if (this.trendAnalysisType == 0) {
        return [
          commonIndexColumn,
          { title: '抓拍时间', align: 'center', dataIndex: 'captureTime' },
          { title: '车流量', align: 'center', dataIndex: 'carCount' },
          { title: '车型编码', align: 'center', dataIndex: 'modelCode' },
          { title: '设备编码', align: 'center', dataIndex: 'cameraCode' },
        ]
      }
      if (this.trendAnalysisType == 4 || this.trendAnalysisType == 5) {
        return [
          commonIndexColumn,
          { title: '更新时间', align: 'center', dataIndex: 'updateTimeSelf' },
          { title: '车牌号', align: 'center', dataIndex: 'licenseNum' },
          { title: '客户类型', align: 'center', dataIndex: 'clientName' },
          { title: '累计加油次数', align: 'center', dataIndex: 'oilCount' },
          { title: '累计油品销量', align: 'center', dataIndex: 'oilSum' },
        ]
      }
      if (this.trendAnalysisType == 6) {
        return [
          commonIndexColumn,
          { title: '进站时间', align: 'center', dataIndex: 'startTime' },
          { title: '车牌号', align: 'center', dataIndex: 'licenseNum' },
          { title: '行为类型', align: 'center', dataIndex: 'behaviorText' },
          { title: '加油位', align: 'center', dataIndex: 'oilPosition' },
          { title: '油品销量', align: 'center', dataIndex: 'oilVolume' },
          { title: '销售金额(元)', align: 'center', dataIndex: 'totalAmount' },
          { title: '通过率(分)', align: 'center', dataIndex: 'spandTime' },
        ]
      }
      return [
        commonIndexColumn,
        { title: '进站时间', align: 'center', dataIndex: 'startTime' },
        { title: '车牌号', align: 'center', dataIndex: 'licenseNum' },
        { title: '行为类型', align: 'center', dataIndex: 'behaviorText' },
        { title: '加油位', align: 'center', dataIndex: 'oilPosition' },
        { title: '油品销量', align: 'center', dataIndex: 'oilVolume' },
        { title: '通过率(分)', align: 'center', dataIndex: 'spandTime' },
      ]
    },
    handleTrendChartClick(params) {
      if (!params || !params.name) {
        return
      }
      if (this.$refs.trendAnalysisLineRef && this.$refs.trendAnalysisLineRef.hideTooltip) {
        this.$refs.trendAnalysisLineRef.hideTooltip()
      }
      this.trendDetailVisible = true
      this.trendDetailTitle = '趋势分析明细 - ' + params.name
      this.trendDetailColumns = this.getTrendDetailColumnsByType()
      this.trendDetailLoading = true
      postAction('/jyz/dataTable/statTrendDetail', {
        orgCode: this.selectTreeObj.orgCode,
        startTime: this.startTime,
        endTime: this.endTime,
        trendType: this.trendAnalysisType,
        timeUnit: this.trendAnalysisUnit,
        statTime: params.name,
        seriesName: params.seriesName,
      })
        .then((res) => {
          this.trendDetailData = res.result || []
        })
        .finally(() => {
          this.trendDetailLoading = false
        })
    },
    closeTrendDetail() {
      this.trendDetailVisible = false
      this.trendDetailData = []
    },
    //获取车型/加油位折线图数据
    getVehicleTypeLineData() {
      postAction('/jyz/dataTable/statisMidTable', {
@@ -287,10 +519,46 @@
      this.fuelingStationSpecies = e.target.value
      this.getFuelingStationPieData()
    },
    //获取油品类型饼图数据
    getOilTypePieData() {
      postAction('/jyz/dataTable/statFan', {
        orgCode: this.selectTreeObj.orgCode,
        startTime: this.startTime,
        endTime: this.endTime,
        type: this.oilTypeSpecies,
      }).then((res) => {
        console.log(res)
        this.oilTypePieData = res.result
        this.$nextTick(() => {
          this.$refs.oilTypePieRef.setChart()
        })
      })
    },
    //获取油品类型柱状图数据
    getOilTypeLineData() {
      const barType = this.oilTypeSpecies == 5 ? 3 : 4
      postAction('/jyz/dataTable/statBar', {
        orgCode: this.selectTreeObj.orgCode,
        startTime: this.startTime,
        endTime: this.endTime,
        type: barType,
      }).then((res) => {
        console.log(res)
        this.oilTypeLineData = res.result
        this.$nextTick(() => {
          this.$refs.oilTypeLineRef.setChart()
        })
      })
    },
    oilTypeSpeciesChange(e) {
      this.oilTypeSpecies = e.target.value
      this.getOilTypePieData()
      this.getOilTypeLineData()
    },
    //趋势分析类型切换
    trendAnalysisTypeChange(e) {
      let val = e.target.value
      if (val == 0 || val == 2 || val == 3 || val == 5) {
      if (val == 0 || val == 2 || val == 3 || val == 5 || val == 6) {
        this.trendAnalysisChartShowPercent = true
      } else {
        this.trendAnalysisChartShowPercent = false
@@ -340,15 +608,70 @@
        this.contrastObj = res.result
      })
    },
    beforeTimeChange(e, t) {
      this.beforeTimeRange = t
      this.beforeStartTime = t && t.length ? t[0] : ''
      this.beforeEndTime = t && t.length ? t[1] : ''
    },
    afterTimeChange(e, t) {
      this.afterTimeRange = t
      this.afterStartTime = t && t.length ? t[0] : ''
      this.afterEndTime = t && t.length ? t[1] : ''
    },
    setOilFreqDefaultRangeByActivity(activity) {
      if (!activity || !activity.startTime || !activity.endTime) {
        return
      }
      this.afterStartTime = activity.startTime
      this.afterEndTime = activity.endTime
      this.afterTimeRange = [this.afterStartTime, this.afterEndTime]
      let startMoment = moment(activity.startTime)
      let endMoment = moment(activity.endTime)
      let spanSeconds = endMoment.diff(startMoment, 'seconds')
      if (spanSeconds <= 0) {
        return
      }
      this.beforeEndTime = startMoment.format('YYYY-MM-DD HH:mm:ss')
      this.beforeStartTime = startMoment.clone().subtract(spanSeconds, 'seconds').format('YYYY-MM-DD HH:mm:ss')
      this.beforeTimeRange = [this.beforeStartTime, this.beforeEndTime]
    },
    getOilFreqCompareData() {
      if (!this.beforeStartTime || !this.beforeEndTime || !this.afterStartTime || !this.afterEndTime) {
        this.oilFreqCompareData = []
        return
      }
      this.oilFreqCompareLoading = true
      postAction('/jyz/dataTable/statisOilFreqCompare', {
        orgCode: this.selectTreeObj.orgCode,
        beforeStartTime: this.beforeStartTime,
        beforeEndTime: this.beforeEndTime,
        afterStartTime: this.afterStartTime,
        afterEndTime: this.afterEndTime,
      })
        .then((res) => {
          this.oilFreqCompareData = res.result || []
        })
        .finally(() => {
          this.oilFreqCompareLoading = false
        })
    },
    activeTimeChange(e) {
      console.log(e)
      if (e) {
        let item = this.activeOptions.find((el) => el.id == e)
        this.startTime = item.startTime
        this.endTime = item.endTime
        this.setOilFreqDefaultRangeByActivity(item)
      } else {
        this.startTime = ''
        this.endTime = ''
        this.beforeStartTime = ''
        this.beforeEndTime = ''
        this.afterStartTime = ''
        this.afterEndTime = ''
        this.beforeTimeRange = []
        this.afterTimeRange = []
        this.oilFreqCompareData = []
      }
    },
    activeChange() {},
@@ -389,7 +712,10 @@
      this.getVehicleTypeLineData()
      this.getVehicleStationPieData()
      this.getFuelingStationPieData()
      this.getOilTypePieData()
      this.getOilTypeLineData()
      this.getTrendAnalysis()
      this.getOilFreqCompareData()
    },
    initData() {
      this.getCustomerData()
@@ -398,6 +724,8 @@
      this.getVehicleTypeLineData()
      this.getVehicleStationPieData()
      this.getFuelingStationPieData()
      this.getOilTypePieData()
      this.getOilTypeLineData()
      this.getTrendAnalysis()
    },
  },
@@ -405,12 +733,32 @@
  created() {
    this.initData()
  },
  mounted() {
    this.$nextTick(() => {
      setTimeout(() => {
        const scrollTarget = localStorage.getItem('scrollToTarget')
        if (scrollTarget) {
          const element = document.getElementById(scrollTarget)
          if (element) {
            element.scrollIntoView({ behavior: 'smooth', block: 'center' })
            localStorage.removeItem('scrollToTarget')
          }
        }
        const trendType = localStorage.getItem('trendAnalysisType')
        if (trendType) {
          this.trendAnalysisType = parseInt(trendType)
          localStorage.removeItem('trendAnalysisType')
          this.getTrendAnalysis()
        }
      }, 500)
    })
  },
}
</script>
<style scoped lang="less">
@import '~@assets/less/common.less';
/deep/ .ant-table-content {
  height: 500px;
  height: auto;
  .ant-table-placeholder {
    background: none;
    border: none;
@@ -420,7 +768,29 @@
  background: #fff;
}
.data-reMechanism {
  position: relative;
  isolation: isolate;
  > div {
    position: relative;
    z-index: 2;
  }
  > div:nth-child(2) {
    z-index: 1;
  }
  &.trend-detail-open {
    pointer-events: none;
  }
  .trend-analysis-chart {
    position: relative;
    z-index: 1;
    overflow: hidden;
  }
  .trend-analysis-chart.is-modal-open {
    pointer-events: none;
  }
  .table-operator {
    position: relative;
    z-index: 2;
    margin: 24px 0 0 0;
  }
  .block-title {
@@ -428,6 +798,8 @@
    font-size: 16px;
  }
  .fueling-overview-ct {
    position: relative;
    z-index: 2;
    // margin: 0 12px;
    .fueling-overview-block {
      display: flex;
@@ -443,5 +815,33 @@
      }
    }
  }
  .oil-freq-compare-ct {
    position: relative;
    z-index: 2;
    margin: 8px 12px 24px 12px;
    .oil-freq-compare-operator {
      display: flex;
      align-items: center;
      flex-wrap: wrap;
      gap: 8px 0;
      margin: 12px 0;
    }
    .oil-freq-compare-table {
      /deep/ .ant-table-body {
        min-height: 240px;
      }
      /deep/ .ant-table-pagination.ant-pagination {
        margin: 12px 0 4px 0;
      }
    }
  }
}
</style>
<style lang="less">
.trend-detail-modal-wrap {
  z-index: 5000 !important;
}
.trend-detail-modal-wrap + .ant-modal-mask {
  z-index: 4999 !important;
}
</style>
src/views/dataAnalysis/components/DataReLineChart.vue
@@ -22,13 +22,204 @@
      type: Boolean,
      default: false,
    },
    enablePointClick: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      chartEntity: null,
      zrClickHandler: null,
      zrMoveHandler: null,
      zrMouseOutHandler: null,
      lastHoverIndex: -1,
      lastEmitName: '',
      lastEmitAt: 0,
      lastLegendToggleAt: 0,
    }
  },
  methods: {
    getOffsetPoint(event) {
      if (!event) {
        return null
      }
      if (event.offsetX !== undefined && event.offsetY !== undefined) {
        return [event.offsetX, event.offsetY]
      }
      if (event.event && event.event.offsetX !== undefined && event.event.offsetY !== undefined) {
        return [event.event.offsetX, event.event.offsetY]
      }
      return null
    },
    isPointInGrid(point) {
      if (!this.chartEntity || !this.chartEntity.containPixel || !point) {
        return false
      }
      return this.chartEntity.containPixel({ gridIndex: 0 }, point)
    },
    getXData() {
      return this.chartData && this.chartData.xData ? this.chartData.xData : []
    },
    resolveNearestIndexByOffsetX(offsetX) {
      let xData = this.getXData()
      if (!this.chartEntity || !this.chartEntity.convertToPixel || !xData.length) {
        return -1
      }
      let nearestIndex = -1
      let minDistance = Number.MAX_VALUE
      for (let i = 0; i < xData.length; i++) {
        let xPixel = this.chartEntity.convertToPixel({ xAxisIndex: 0 }, xData[i])
        if (Array.isArray(xPixel)) {
          xPixel = xPixel[0]
        }
        if (xPixel === undefined || xPixel === null || Number.isNaN(Number(xPixel))) {
          continue
        }
        let distance = Math.abs(Number(offsetX) - Number(xPixel))
        if (distance < minDistance) {
          minDistance = distance
          nearestIndex = i
        }
      }
      if (nearestIndex === -1) {
        return -1
      }
      return nearestIndex
    },
    resolveClickIndex(event, params) {
      let xData = this.getXData()
      if (!xData.length) {
        return -1
      }
      let point = this.getOffsetPoint(event)
      if (!this.isPointInGrid(point)) {
        return -1
      }
      if (params && typeof params.dataIndex === 'number' && xData[params.dataIndex] !== undefined) {
        return params.dataIndex
      }
      if (params && params.name) {
        let nameIndex = xData.findIndex((item) => item === params.name)
        if (nameIndex > -1) {
          return nameIndex
        }
      }
      if (this.chartEntity && this.chartEntity.convertFromPixel && event) {
        let xAxisValue = this.chartEntity.convertFromPixel({ xAxisIndex: 0 }, [event.offsetX, event.offsetY])
        if (xAxisValue !== undefined && xAxisValue !== null && !Number.isNaN(Number(xAxisValue))) {
          let xIndex = Math.round(Number(xAxisValue))
          if (xIndex >= 0 && xIndex < xData.length) {
            return xIndex
          }
        }
      }
      if (event && event.offsetX !== undefined && event.offsetX !== null) {
        return this.resolveNearestIndexByOffsetX(event.offsetX)
      }
      return -1
    },
    emitChartClickByIndex(index, seriesName) {
      let xData = this.getXData()
      if (index < 0 || index >= xData.length) {
        return
      }
      let clickName = xData[index]
      let now = Date.now()
      if (this.lastEmitName === clickName && now - this.lastEmitAt < 120) {
        return
      }
      this.lastEmitName = clickName
      this.lastEmitAt = now
      this.$emit('chart-click', {
        name: clickName,
        seriesName: seriesName || null,
      })
    },
    hideTooltip() {
      if (!this.chartEntity) {
        return
      }
      this.chartEntity.dispatchAction({
        type: 'hideTip',
      })
    },
    bindChartClick() {
      if (!this.chartEntity) {
        return
      }
      this.chartEntity.off('click')
      if (this.chartEntity.getZr) {
        let zr = this.chartEntity.getZr()
        if (this.zrClickHandler) {
          zr.off('click', this.zrClickHandler)
        }
        if (this.zrMoveHandler) {
          zr.off('mousemove', this.zrMoveHandler)
        }
        if (this.zrMouseOutHandler) {
          zr.off('globalout', this.zrMouseOutHandler)
        }
      }
      if (!this.enablePointClick) {
        let chartDom = document.getElementById(this.domId)
        if (chartDom) {
          chartDom.style.cursor = 'default'
        }
        return
      }
      let chartDom = document.getElementById(this.domId)
      if (chartDom) {
        chartDom.style.cursor = 'pointer'
      }
      this.chartEntity.on('legendselectchanged', () => {
        this.lastLegendToggleAt = Date.now()
      })
      this.chartEntity.on('click', (params) => {
        if (params && params.componentType === 'legend') {
          return
        }
        if (Date.now() - this.lastLegendToggleAt < 180) {
          return
        }
        let index = this.resolveClickIndex(params && params.event ? params.event : null, params)
        this.emitChartClickByIndex(index, params ? params.seriesName : null)
      })
      this.zrMoveHandler = (event) => {
        let xData = this.getXData()
        if (!xData.length) {
          return
        }
        let xIndex = this.resolveClickIndex(event, null)
        if (xIndex < 0 || xIndex >= xData.length) {
          return
        }
        if (this.lastHoverIndex === xIndex) {
          return
        }
        this.lastHoverIndex = xIndex
        this.chartEntity.dispatchAction({
          type: 'showTip',
          seriesIndex: 0,
          dataIndex: xIndex,
        })
      }
      this.zrMouseOutHandler = () => {
        this.lastHoverIndex = -1
        this.hideTooltip()
      }
      this.zrClickHandler = (event) => {
        if (Date.now() - this.lastLegendToggleAt < 180) {
          return
        }
        let index = this.resolveClickIndex(event, null)
        this.emitChartClickByIndex(index, null)
      }
      let zr = this.chartEntity.getZr()
      zr.on('mousemove', this.zrMoveHandler)
      zr.on('globalout', this.zrMouseOutHandler)
      zr.on('click', this.zrClickHandler)
    },
    setChart() {
      if (!this.chartEntity) {
        let chartDom = document.getElementById(this.domId)
@@ -38,6 +229,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -54,15 +247,29 @@
          shadowBlur: 10,
          formatter: (params) => {
            let strName1 = params[0].name
            let value1 = params[0].value
            let value2 = params[1] ? params[1].value : '未开启'
            return `<div style="color:#fff;font-size:16px;">${strName1}</div>
              <div><span style="color:#fff;display: inline-block;width: 86px;">${
                this.chartData.barName
              }</span><span style="color:#5DB6FB">${value1}
              </span></div><div><span style="color:#fff;display: inline-block;width: 86px;">${
                this.chartData.lineName
              }</span><span style="color:#5DB6FB">${value2}${this.showpercent ? '%' : ''}`
            let result = `<div style="color:#fff;font-size:16px;">${strName1}</div>`
            if (params[0] && this.chartData.barName) {
              let value1 = params[0].value !== undefined ? params[0].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.barName}</span><span style="color:#5DB6FB">${value1}</span></div>`
            }
            if (params[1] && this.chartData.lineName) {
              let value2 = params[1].value !== undefined ? params[1].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.lineName}</span><span style="color:#5DB6FB">${value2}</span></div>`
            }
            if (params[2] && this.chartData.barName2) {
              let value3 = params[2].value !== undefined ? params[2].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.barName2}</span><span style="color:#FF6B6B">${value3}</span></div>`
            }
            if (params[3] && this.chartData.lineName2) {
              let value4 = params[3].value !== undefined ? params[3].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.lineName2}</span><span style="color:#FFD93D">${value4}</span></div>`
            }
            return result
          },
          textStyle: {
            rich: {
@@ -136,10 +343,7 @@
            },
            axisLabel: {
              show: true,
              formatter: '{value} %', //右侧Y轴文字显示
              formatter: (value, index) => {
                return value + (this.showpercent ? '%' : '')
              },
              formatter: this.showpercent ? '{value} %' : '{value}',
              textStyle: {
                color: 'rgba(185, 185, 185, 1)',
              },
@@ -147,67 +351,95 @@
          },
        ],
        series: [
          {
            name: this.chartData.barName,
            type: 'bar',
            barWidth: '12px',
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  {
                    offset: 0,
                    color: '#5B81F9',
                  },
                  {
                    offset: 1,
                    color: '#151A22',
                  },
                ]),
              },
            },
            data: this.chartData.barData,
          },
          {
            name: this.chartData.lineName,
            type: 'line',
            // smooth: true,
            yAxisIndex: 1, //使用的 y 轴的 index,在单个图表实例中存在多个 y轴的时候有用
            symbol: 'emptycircle', //标记的图形为实心圆
            symbolSize: 0, //标记的大小
            areaStyle: {
              normal: {
                color: {
                  type: 'linear',
                  x: 0,
                  y: 0,
                  x2: 0,
                  y2: 1,
                  colorStops: [
                    {
                      offset: 0,
                      color: 'rgba(87, 147, 67, .5)', // 0% 处的颜色
                    },
                    {
                      offset: 1,
                      color: 'rgba(87, 147, 67, 0.1)', // 100% 处的颜色
                    },
                  ],
                  global: false, // 缺省为 false
                },
              },
            },
            itemStyle: {
              color: 'rgba(65, 197, 95, 1)',
            },
            data: this.chartData.lineData,
          },
        ],
        series: [],
      }
      if (this.chartData.barData) {
        option.series.push({
          name: this.chartData.barName,
          type: 'bar',
          barWidth: '12px',
          itemStyle: {
            normal: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: '#5B81F9',
                },
                {
                  offset: 1,
                  color: '#151A22',
                },
              ]),
            },
          },
          data: this.chartData.barData,
        })
      }
      if (this.chartData.lineData) {
        option.series.push({
          name: this.chartData.lineName,
          type: 'line',
          yAxisIndex: 1,
          smooth: true,
          symbol: 'circle',
          symbolSize: 6,
          lineStyle: {
            width: 2,
            color: '#16B777',
          },
          itemStyle: {
            color: '#16B777',
          },
          data: this.chartData.lineData,
        })
      }
      if (this.chartData.barData2) {
        option.series.push({
          name: this.chartData.barName2,
          type: 'bar',
          barWidth: '12px',
          itemStyle: {
            normal: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: '#FF6B6B',
                },
                {
                  offset: 1,
                  color: '#8B0000',
                },
              ]),
            },
          },
          data: this.chartData.barData2,
        })
      }
      if (this.chartData.lineData2) {
        option.series.push({
          name: this.chartData.lineName2,
          type: 'line',
          yAxisIndex: 1,
          smooth: true,
          symbol: 'circle',
          symbolSize: 6,
          lineStyle: {
            width: 2,
            color: '#FFD93D',
          },
          itemStyle: {
            color: '#FFD93D',
          },
          data: this.chartData.lineData2,
        })
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
    setPieUpChart() {
      if (!this.chartEntity) {
@@ -233,6 +465,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -291,6 +525,7 @@
        series: [...data],
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
    setOnlyLineChart() {
      if (!this.chartEntity) {
@@ -301,6 +536,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -434,6 +671,7 @@
        ],
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
  },
}
@@ -441,5 +679,7 @@
<style lang="less" scoped>
.line-chart {
  height: 20vh;
  position: relative;
  overflow: hidden;
}
</style>
src/views/dataAnalysis/components/DataReMechanism.vue
@@ -28,7 +28,7 @@
        <a-range-picker style="width: 300px; margin: 0 12px" v-if="isContrast" @change="contrastTimeChange" />
      </div>
      <div class="fueling-overview-block">
        <div class="fueling-overview-item">
        <div class="fueling-overview-item" id="traffic-flow-card">
          <div class="fueling-overview-name">车流量</div>
          <div class="fueling-overview-num">
            <span style="margin-right: 24px">{{ statisTotalObj.carCount }}</span>
@@ -42,7 +42,7 @@
            <span style="color: #16b777" v-if="isContrast">{{ contrastObj.appearCount }}</span>
          </div>
        </div>
        <div class="fueling-overview-item">
        <div class="fueling-overview-item" id="oil-count-card">
          <div class="fueling-overview-name">加油数</div>
          <div class="fueling-overview-num">
            <span style="margin-right: 24px">{{ statisTotalObj.oilCount }}</span>
@@ -352,6 +352,20 @@
  created() {
    this.initData()
  },
  mounted() {
    this.$nextTick(() => {
      setTimeout(() => {
        const scrollTarget = localStorage.getItem('scrollToTarget')
        if (scrollTarget) {
          const element = document.getElementById(scrollTarget)
          if (element) {
            element.scrollIntoView({ behavior: 'smooth', block: 'center' })
            localStorage.removeItem('scrollToTarget')
          }
        }
      }, 500)
    })
  },
}
</script>
<style scoped lang="less">
src/views/dataAnalysis/components/DataRePieChart.vue
@@ -65,10 +65,10 @@
        legend: {
          orient: 'vertical',
          icon: 'circle',
          right: '0',
          top: '10%',
          right: '2%',
          top: 'middle',
          itemWidth: 14,
          itemGap: 20,
          itemGap: 12,
          textStyle: {
            rich: {
              a: {
@@ -106,9 +106,9 @@
            emphasis: {
              scale: false,
            },
            center: ['40%', '50%'],
            center: ['28%', '52%'],
            top: 'center',
            right: '60%',
            right: '62%',
            label: {
              show: true,
              position: 'inside',
src/views/dataAnalysis/components/DepartLabel.vue
New file
@@ -0,0 +1,500 @@
<template>
  <div class="depart-label-container">
    <div class="table-page-search-wrapper">
      <a-form layout="inline">
        <a-row :gutter="24">
          <a-col :md="6" :sm="12">
            <a-form-item label="标签名称">
              <a-select v-model="queryParam.labelName" placeholder="请选择标签" allowClear @change="searchQuery">
                <a-select-option value="">全部</a-select-option>
                <a-select-option v-for="label in labelList" :key="label" :value="label">
                  {{ label }}
                </a-select-option>
              </a-select>
            </a-form-item>
          </a-col>
          <a-col :md="6" :sm="12">
            <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
            <a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
          </a-col>
        </a-row>
      </a-form>
    </div>
    <div class="table-operator">
      <div class="data-overview-ct">
        <div class="block-title" style="display: flex; align-items: center">
          <div style="margin-right: 12px">数据对比</div>
          <a-switch v-model="isContrast" />
          <a-range-picker
            style="width: 300px; margin: 0 12px"
            v-if="isContrast"
            v-model="contrastTimeRange"
            @change="contrastTimeChange"
            :placeholder="['开始时间', '结束时间']"
          />
        </div>
      </div>
    </div>
    <div class="chart-container" v-if="isContrast && lineChartData.xData && lineChartData.xData.length > 0">
      <div class="chart-block">
        <div class="block-title">数据对比柱状图</div>
        <DataReLineChart
          ref="lineChart"
          :chartData="lineChartData"
          :showpercent="true"
          :domId="'departLabelLineChart'"
        ></DataReLineChart>
      </div>
    </div>
    <a-table
      :columns="columns"
      :data-source="dataSource"
      :pagination="pagination"
      :loading="loading"
      @change="handleTableChange"
      :scroll="{ y: 350 }"
    >
      <span slot="action" slot-scope="text, record">
        <a @click="handleAdd(record)" style="margin-right: 8px">添加标签</a>
        <a-dropdown>
          <a-menu slot="overlay">
            <a-menu-item v-for="label in getLabels(record)" :key="label" @click="() => handleMenuClick(record, label)">
              删除{{ label }}
            </a-menu-item>
          </a-menu>
          <a slot="default">删除标签</a>
        </a-dropdown>
      </span>
    </a-table>
    <DepartLabelModal
      ref="modalForm"
      @ok="modalFormOk"
      :parentCode="queryParam.parentCode"
      :parentId="queryParam.parentId"
    ></DepartLabelModal>
  </div>
</template>
<script>
import { getAction, deleteAction, putAction, postAction } from '@tievd/cube-block/lib/api/manage'
import DepartLabelModal from './DepartLabelModal'
import DataReLineChart from './DataReLineChart'
export default {
  name: 'DepartLabel',
  components: {
    DepartLabelModal,
    DataReLineChart
  },
  data() {
    return {
      queryParam: {
        labelName: '',
        parentCode: JSON.parse(localStorage.getItem("userDepartInfo")).orgCode,
        parentId: JSON.parse(localStorage.getItem("userDepartInfo")).id
      },
      labelList: [],
      dataSource: [],
      loading: false,
      isContrast: false,
      statisTotalObj: {},
      contrastObj: {},
      contrastStartTime: '',
      contrastEndTime: '',
      contrastTimeRange: [],
      lineChartData: {},
      currentOrgData: [],
      contrastOrgData: [],
      pagination: {
        current: 1,
        pageSize: 10,
        total: 0,
        showSizeChanger: true,
        showTotal: (total) => `共 ${total} 条`
      },
      columns: [
        {
          title: '序号',
          dataIndex: '',
          key: 'rowIndex',
          width: 60,
          align: 'center',
          customRender: function (text, record, index) {
            return index + 1
          }
        },
        {
          title: '站点名称',
          align: 'center',
          dataIndex: 'depart_name'
        },
        {
          title: '站点编码',
          align: 'center',
          dataIndex: 'org_code'
        },
        {
          title: '标签名称',
          align: 'center',
          dataIndex: 'label_name'
        },
        {
          title: '创建时间',
          align: 'center',
          dataIndex: 'create_time',
          customRender: function (text) {
            if (!text) return ''
            return text.replace('T', ' ')
          }
        },
        {
          title: '操作',
          dataIndex: 'action',
          align: 'center',
          scopedSlots: { customRender: 'action' }
        }
      ],
      url: {
        list: '/jyz/departLabel/list',
        listLabels: '/jyz/departLabel/listLabels',
        listDeparts: '/jyz/custom/sys/depart/tables',
        delete: '/jyz/departLabel/delete',
        update: '/jyz/departLabel/update',
        queryOrgOilCount: '/jyz/departLabel/queryOrgOilCount'
      }
    }
  },
  watch: {
    isContrast(newVal) {
      if (newVal) {
        this.setDefaultMonthTime()
      }
    }
  },
  created() {
    this.loadLabelList()
    this.loadData()
  },
  methods: {
    loadLabelList() {
      const params = {
        parentCode: this.queryParam.parentCode,
        parentId: this.queryParam.parentId
      }
      getAction(this.url.listLabels, params).then((res) => {
        if (res.success) {
          this.labelList = res.result
          console.log('标签列表:', this.labelList)
        } else {
          console.error('加载标签列表失败:', res.message)
        }
      })
    },
    loadData() {
      this.loading = true
      const params = {
        pageNo: this.pagination.current,
        pageSize: this.pagination.pageSize,
        labelName: this.queryParam.labelName,
        parentCode: this.queryParam.parentCode,
        parentId: this.queryParam.parentId
      }
      getAction(this.url.list, params).then((res) => {
        if (res.success) {
          this.dataSource = res.result.records || []
          this.pagination.total = Number(res.result.total) || 0
          this.getOrgData()
        } else {
          this.$message.error(res.message || '查询失败')
        }
      }).catch(() => {
        this.$message.error('查询失败')
      }).finally(() => {
        this.loading = false
      })
    },
    updateChartData() {
      if (this.dataSource.length === 0) {
        this.lineChartData = {}
        return
      }
      console.log('updateChartData - dataSource:', this.dataSource)
      console.log('updateChartData - currentOrgData:', this.currentOrgData)
      console.log('updateChartData - contrastOrgData:', this.contrastOrgData)
      const xData = this.dataSource.map(item => item.depart_name)
      const currentCarData = this.dataSource.map(item => {
        const orgData = this.currentOrgData.find(d => d.org_code === item.org_code)
        console.log(`Matching ${item.org_code}:`, orgData)
        return orgData ? (orgData.carCount || 0) : 0
      })
      const currentOilData = this.dataSource.map(item => {
        const orgData = this.currentOrgData.find(d => d.org_code === item.org_code)
        return orgData ? (orgData.oilCount || 0) : 0
      })
      const currentStationData = this.dataSource.map(item => {
        const orgData = this.currentOrgData.find(d => d.org_code === item.org_code)
        return orgData ? (orgData.stationCount || 0) : 0
      })
      const currentVolumeData = this.dataSource.map(item => {
        const orgData = this.currentOrgData.find(d => d.org_code === item.org_code)
        return orgData ? (orgData.oilVolume || 0) : 0
      })
      console.log('Final data - xData:', xData)
      console.log('Final data - currentCarData:', currentCarData)
      console.log('Final data - currentOilData:', currentOilData)
      console.log('Final data - currentStationData:', currentStationData)
      console.log('Final data - currentVolumeData:', currentVolumeData)
      this.lineChartData = {
        xData: xData,
        barData: currentCarData,
        lineData: currentOilData,
        barName: '车流量',
        lineName: '加油数',
        barData2: currentStationData,
        lineData2: currentVolumeData,
        barName2: '进站数',
        lineName2: '油品销量'
      }
      this.$nextTick(() => {
        if (this.$refs.lineChart && this.$refs.lineChart.setChart) {
          this.$refs.lineChart.setChart()
        }
      })
    },
    contrastTimeChange(e, t) {
      this.contrastStartTime = t[0]
      this.contrastEndTime = t[1]
      this.getContrastData()
    },
    setDefaultMonthTime() {
      const now = new Date()
      const year = now.getFullYear()
      const month = now.getMonth() + 1
      const day = now.getDate()
      const startTime = `${year}-${String(month).padStart(2, '0')}-01 00:00:00`
      const endTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} 23:59:59`
      this.contrastStartTime = startTime
      this.contrastEndTime = endTime
      this.getContrastData()
    },
    getOrgData() {
      postAction(this.url.queryOrgOilCount, {
        orgCode: this.queryParam.parentCode
      }).then((res) => {
        if (res.success) {
          console.log('getOrgData result:', res.result)
          this.currentOrgData = res.result || []
          this.updateChartData()
        }
      })
    },
    getContrastData() {
      postAction(this.url.queryOrgOilCount, {
        orgCode: this.queryParam.parentCode,
        startTime: this.contrastStartTime,
        endTime: this.contrastEndTime
      }).then((res) => {
        if (res.success) {
          console.log('getContrastData result:', res.result)
          this.contrastOrgData = res.result || []
          this.updateChartData()
        }
      })
    },
    searchQuery() {
      this.pagination.current = 1
      this.loadData()
    },
    searchReset() {
      this.queryParam.labelName = ''
      this.pagination.current = 1
      this.loadData()
    },
    handleTableChange(pagination) {
      this.pagination = pagination
      this.loadData()
    },
    handleAdd(record) {
      this.$refs.modalForm.add(record)
    },
    handleDelete(record) {
      this.$confirm({
        title: '确认删除',
        content: '确定要删除该标签吗?',
        onOk: () => {
          deleteAction(this.url.delete, {
            departId: record.depart_id,
            labelName: record.label_name
          }).then((res) => {
            if (res.success) {
              this.$message.success('删除成功')
              this.loadData()
              this.loadLabelList()
            } else {
              this.$message.error(res.message || '删除失败')
            }
          })
        }
      })
    },
    modalFormOk() {
      this.loadData()
      this.loadLabelList()
    },
    getLabels(record) {
      if (!record.label_name) return []
      return record.label_name.split(',').map(label => label.trim()).filter(label => label)
    },
    handleMenuClick(record, label) {
      this.$confirm({
        title: '确认删除',
        content: `确定要删除"${label}"标签吗?`,
        onOk: () => {
          deleteAction(this.url.delete, {
            departId: record.depart_id,
            labelName: label
          }).then((res) => {
            if (res.success) {
              this.$message.success('删除成功')
              this.loadData()
              this.loadLabelList()
            } else {
              this.$message.error(res.message || '删除失败')
            }
          })
        }
      })
    }
  }
}
</script>
<style scoped lang="less">
.depart-label-container {
  padding: 24px;
  background: linear-gradient(135deg, #1a1f2e 0%, #0f1219 100%);
  min-height: 100vh;
}
.table-page-search-wrapper {
  margin-bottom: 24px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
}
.table-operator {
  margin-bottom: 24px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
}
.data-overview-ct {
  .block-title {
    font-size: 16px;
    font-weight: 600;
    color: #fff;
    display: flex;
    align-items: center;
  }
}
.chart-container {
  margin: 24px 0;
  padding: 24px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
  .chart-block {
    width: 100%;
    .block-title {
      font-size: 18px;
      font-weight: 600;
      margin-bottom: 20px;
      color: #fff;
      padding-bottom: 12px;
      border-bottom: 2px solid rgba(91, 129, 249, 0.5);
    }
  }
}
:deep(.ant-table) {
  background: rgba(255, 255, 255, 0.05);
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  .ant-table-thead > tr > th {
    background: rgba(91, 129, 249, 0.2);
    color: #fff;
    font-weight: 600;
    border-bottom: 2px solid rgba(91, 129, 249, 0.5);
  }
  .ant-table-tbody > tr > td {
    background: transparent;
    color: rgba(255, 255, 255, 0.85);
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  }
  .ant-table-tbody > tr:hover > td {
    background: rgba(91, 129, 249, 0.1);
  }
}
:deep(.ant-btn-primary) {
  background: linear-gradient(135deg, #5b81f9 0%, #3d5cc9 100%);
  border: none;
  box-shadow: 0 4px 12px rgba(91, 129, 249, 0.3);
  &:hover {
    background: linear-gradient(135deg, #6d92ff 0%, #4d6ee0 100%);
    box-shadow: 0 6px 16px rgba(91, 129, 249, 0.4);
  }
}
:deep(.ant-select) {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 8px;
  .ant-select-selection {
    background: transparent;
    border: none;
    color: rgba(255, 255, 255, 0.85);
  }
}
:deep(.ant-form-item-label > label) {
  color: rgba(255, 255, 255, 0.85);
  font-weight: 500;
}
:deep(.a-switch) {
  background: rgba(91, 129, 249, 0.3);
}
</style>
src/views/dataAnalysis/components/DepartLabelModal.vue
New file
@@ -0,0 +1,132 @@
<template>
  <a-modal
    :title="title"
    :width="600"
    :visible="visible"
    :confirmLoading="confirmLoading"
    @ok="handleOk"
    @cancel="handleCancel"
    cancelText="关闭"
  >
    <a-spin :spinning="confirmLoading">
      <a-form :form="form">
        <a-form-item
          :labelCol="labelCol"
          :wrapperCol="wrapperCol"
          label="标签名称"
          hasFeedback
        >
          <a-select
            v-decorator="['labelName', { rules: [{ required: true, message: '请选择标签名称' }] }]"
            placeholder="请选择标签名称"
          >
            <a-select-option v-for="label in labelList" :key="label" :value="label">
              {{ label }}
            </a-select-option>
          </a-select>
        </a-form-item>
      </a-form>
    </a-spin>
  </a-modal>
</template>
<script>
import { postAction, getAction } from '@tievd/cube-block/lib/api/manage'
export default {
  name: 'DepartLabelModal',
  props: {
    parentCode: {
      type: String,
      default: ''
    },
    parentId: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      title: '添加标签',
      visible: false,
      confirmLoading: false,
      labelCol: {
        xs: { span: 24 },
        sm: { span: 5 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      },
      form: this.$form.createForm(this),
      url: {
        add: '/jyz/departLabel/add',
        listLabels: '/jyz/departLabel/listLabels'
      },
      labelList: [],
      currentDepartId: null
    }
  },
  created() {
    this.loadLabelList()
  },
  watch: {
    parentCode() {
      this.loadLabelList()
    },
    parentId() {
      this.loadLabelList()
    }
  },
  methods: {
    loadLabelList() {
      const params = {}
      if (this.parentCode) {
        params.parentCode = this.parentCode
      }
      if (this.parentId) {
        params.parentId = this.parentId
      }
      getAction(this.url.listLabels, params).then((res) => {
        if (res.success) {
          this.labelList = res.result
        }
      })
    },
    add(record) {
      this.form.resetFields()
      this.currentDepartId = record.depart_id
      this.visible = true
    },
    handleOk() {
      const that = this
      this.form.validateFields((err, values) => {
        if (!err) {
          that.confirmLoading = true
          const params = {
            departId: that.currentDepartId,
            labelName: values.labelName
          }
          postAction(that.url.add, params).then((res) => {
            if (res.success) {
              that.$message.success('添加成功')
              that.$emit('ok')
              that.visible = false
            } else {
              that.$message.error(res.message || '添加失败')
            }
          }).finally(() => {
            that.confirmLoading = false
          })
        }
      })
    },
    handleCancel() {
      this.visible = false
    }
  }
}
</script>
<style scoped>
</style>
src/views/user/Login.vue
@@ -283,7 +283,7 @@
        })
    },
    loginSuccess() {
      this.$router.push({path: '/dashboard/analysis'}).catch(() => {
      this.$router.push({path: '/analysisScreen/operationBigdata'}).catch(() => {
        console.log('登录跳转首页出错,这个错误从哪里来的')
      })
      this.$notification.success({