forked from drone/command-center-dashboard

chenyao
2025-04-03 8b92f8cb5763eaf8a8a5d93a4690c923c2de1a85
feat: 任务管理功能
3 files modified
11 files added
1002 ■■■■■ changed files
src/api/home/common.js 27 ●●●●● patch | view | raw | blame | history
src/api/home/task.js 52 ●●●●● patch | view | raw | blame | history
src/assets/images/signMachineNest/return.png patch | view | raw | blame | history
src/store/modules/user.js 1 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineLeft/MachineLeft.vue 22 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskManage.vue 9 ●●●● patch | view | raw | blame | history
src/views/TaskManage/components/SearchBox.vue 192 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskIntermediateContent/TaskIntermediateContent.vue 148 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskLeft/TaskIndustry.vue 132 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskLeft/TaskLeft.vue 19 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskLeft/TaskTotal.vue 100 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskRight/TaskEvent.vue 150 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskRight/TaskRight.vue 25 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskRight/TaskTime.vue 125 ●●●●● patch | view | raw | blame | history
src/api/home/common.js
New file
@@ -0,0 +1,27 @@
import request from '@/axios'
// 字典查询 行业 算法 工单类型
export const getDictionary = params => {
    return request({
        url: `/blade-system/dict-biz/listByCodes?code=${params}`,
        method: 'get',
    })
}
// 区域
export const deptsByAreaCode = params => {
    return request({
        url: `/blade-system/dept/deptsByAreaCode`,
        method: 'get',
        params,
    })
}
// 获取机场信息
export const getDockInfo = params => {
    return request({
        url: `/drone-device-core/dp/home/getDockInfo`,
        method: 'get',
        params,
    })
}
src/api/home/task.js
New file
@@ -0,0 +1,52 @@
import request from '@/axios'
// 任务总数统计
export const totalJobNum = params => {
    return request({
        url: '/drone-device-core/wayline/waylineJobInfo/totalJobNum',
        method: 'get',
        params: params,
    })
}
// 其他任务统计
export const jobStatistics = data => {
    return request({
        url: '/drone-device-core/wayline/waylineJobInfo/jobStatistics',
        method: 'post',
        data: data,
    })
}
// 行业任务统计
export const industryJobNumPieChart = data => {
    return request({
        url: '/drone-device-core/wayline/waylineJobInfo/industryJobNumPieChart',
        method: 'post',
        data: data,
    })
}
// 时间任务统计
export const jobNumBar = data => {
    return request({
        url: '/drone-device-core/wayline/waylineJobInfo/jobNumBar',
        method: 'post',
        data: data,
    })
}
// 事件任务统计
export const jobEventBar = data => {
    return request({
        url: '/drone-device-core/wayline/waylineJobInfo/jobEventBar',
        method: 'post',
        data: data,
    })
}
// 任务列表
export const jobList = data => {
    return request({
        url: '/drone-device-core/wayline/waylineJobInfo/jobList?current=' + data.current + '&size=' + data.size,
        method: 'post',
        data: {},
    })
}
src/assets/images/signMachineNest/return.png
src/store/modules/user.js
@@ -299,7 +299,6 @@
            setToken(token)
            state.token = token
            setStore({ name: 'token', content: state.token })
            console.log('顶顶顶111', token)
            window.localStorage.setItem(ELocalStorageKey.Token, token)
        },
        SET_REFRESH_TOKEN: (state, refreshToken) => {
src/views/SignMachineNest/components/MachineLeft/MachineLeft.vue
@@ -3,7 +3,10 @@
    <!--时间 天气-->
    <common-weather></common-weather>
    <!-- 机巢数据 -->
     <MachineData></MachineData>
    <MachineData></MachineData>
    <div class="do-return" @click="goBack">
      <img src="@/assets/images/signMachineNest/return.png" alt="" />
    </div>
     <!--巡检任务详情-->
     <InspectionRaskDetails></InspectionRaskDetails>
  </div>
@@ -13,6 +16,13 @@
import CommonWeather from '@/components/CommonWeather.vue';
import MachineData from './MachineData.vue';
import InspectionRaskDetails from './InspectionRaskDetails.vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
  router.push('/index');
};
</script>
<style scoped lang="scss">
@@ -20,5 +30,15 @@
  position: absolute;
  top: 88px;
  color: #e7f5ff;
  .do-return {
    position: absolute;
    top: 56px;
    right: -50px;
    cursor: pointer;
    img {
      width: 40px;
      height: 40px;
    }
  }
}
</style>
src/views/TaskManage/TaskManage.vue
@@ -1,11 +1,16 @@
<script setup>
import TaskLeft from "./components/TaskLeft/TaskLeft.vue";
import TaskRight from "./components/TaskRight/TaskRight.vue";
import TaskIntermediateContent from "./components/TaskIntermediateContent/TaskIntermediateContent.vue";
</script>
<template>
  TaskManage
  <TaskLeft/>
  <TaskIntermediateContent />
  <TaskRight/>
</template>
<style scoped lang="scss">
</style>
src/views/TaskManage/components/SearchBox.vue
New file
@@ -0,0 +1,192 @@
<template>
  <div class="search-box">
    <el-form :model="searchForm" inline>
      <el-form-item label="任务名称">
        <el-input v-model="searchForm.name" placeholder="请输入任务名称" clearable />
      </el-form-item>
      <el-form-item label="所属部门">
        <el-select
          v-model="searchForm.deptId"
          placeholder="请选择部门"
          clearable
        >
          <el-tree-select
            v-model="searchForm.deptId"
            :data="deptTreeData"
            node-key="id"
            :props="{
              label: 'name',
              children: 'children'
            }"
            check-strictly
            clearable
          />
        </el-select>
      </el-form-item>
      <el-form-item label="周期">
        <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
          <el-option label="日" value="日" />
          <el-option label="周" value="周" />
          <el-option label="月" value="月" />
          <el-option label="年" value="年" />
        </el-select>
      </el-form-item>
      <el-form-item label="任务状态">
        <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
          <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
        </el-select>
      </el-form-item>
      <el-form-item label="任务时间">
        <el-date-picker
          v-model="searchForm.dateRange"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          value-format="YYYY-MM-DD"
        />
      </el-form-item>
      <el-form-item label="任务算法">
        <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
          <el-option label="识别火情" value="日" />
          <el-option label="识别车辆" value="周" />
          <el-option label="识别船只" value="月" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSearch">查询</el-button>
        <el-button @click="handleReset">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script setup>
import { getDictionary, deptsByAreaCode, getDockInfo } from '@/api/home/common';
const searchForm = reactive({
  name: '',
  job_info_num: '',
  status: '',
  dateRange: []
});
const statusOptions = [
  { label: '待执行', value: 1 },
  { label: '执行中', value: 2 },
  { label: '已完成', value: 3 },
  { label: '已取消', value: 4 },
  { label: '执行失败', value: 5 }
];
// 部门以及机巢
const deptTreeData = ref([]);
const emit = defineEmits(['search']);
const handleSearch = () => {
  emit('search', {
    ...searchForm,
    begin_time: searchForm.dateRange[0],
    end_time: searchForm.dateRange[1]
  });
};
const handleReset = () => {
  searchForm.name = '';
  searchForm.job_info_num = '';
  searchForm.status = '';
  searchForm.dateRange = [];
  handleSearch();
};
// 请求字典字段
const requestDictionary = () => {
  getDictionary('SF,HYLB').then((res) => {
    if (res.data.code !== 0) {
      // 处理数据
      console.log('111111',res.data)
    }
  });
};
// 处理部门数据为树形结构
const handleDeptData = (data) => {
  const buildTree = (items, parentId = 0) => {
    const result = [];
    for (const item of items) {
      if (item.parent_id === parentId) {
        const children = buildTree(items, item.id);
        if (children.length) {
          item.children = children;
        }
        result.push(item);
      }
    }
    return result;
  };
  return buildTree(data);
};
// 部门信息
const getDeptsByAreaCode = () => {
  deptsByAreaCode().then((res) => {
    if (res.data.code !== 0) {
      deptTreeData.value = res.data;
      // 处理数据
      console.log('22222',res.data)
    }
  });
};
// 机巢信息
const requestDockInfo = () => {
  getDockInfo().then((res) => {
    if (res.data.code !== 0) {
      // 处理数据
      console.log('33333',res.data)
    }
  });
};
onMounted(() => {
  requestDictionary();
  deptsByAreaCode();
  requestDockInfo();
});
</script>
<style lang="scss" scoped>
.search-box {
  position: absolute;
  top: 120px;
  left: 450px;
  width: calc(100% - 400px - 400px - 100px);
  height:60px;
  color: #fff;
  background: rgba(31, 62, 122, 0.35);
  padding: 10px 20px;
  :deep(.el-form) {
    --el-text-color-regular: #fff;
    .el-form-item {
      margin-bottom: 0;
      margin-right: 20px;
      .el-form-item__label {
        color: #fff;
      }
      .el-input,
      .el-select,
      .el-date-editor {
        width: 200px;
        --el-input-bg-color: transparent;
        --el-input-border-color: rgba(255, 255, 255, 0.3);
        --el-input-text-color: #fff;
        --el-input-placeholder-color: rgba(255, 255, 255, 0.5);
      }
    }
  }
}
</style>
src/views/TaskManage/components/TaskIntermediateContent/TaskIntermediateContent.vue
New file
@@ -0,0 +1,148 @@
<!-- 任务统计表格 -->
<template>
  <SearchBox></SearchBox>
  <div class="task-intermediate-content">
    <el-table :data="jobListData" style="width: 100%" height="calc(100vh - 180px)">
      <el-table-column label="序号" width="60">
        <template #default="scope">
          {{ (jobListParams.page - 1) * jobListParams.limit + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column prop="job_info_num" label="任务编号" />
      <el-table-column prop="name" label="任务名称"  />
      <el-table-column prop="dept_name" label="所属部门"  />
      <el-table-column prop="device_names" label="所属机巢"  />
      <el-table-column prop="ai_type_str" label="关联算法"  />
      <el-table-column prop="status" label="任务状态" >
        <template #default="scope">
          <el-tag :type="getStatusType(scope.row.status)">
            {{ getStatusText(scope.row.status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="industry_type_str" label="任务类型" />
      <el-table-column prop="event_number" label="关联事件" />
      <el-table-column prop="begin_time" label="任务时间" />
      <el-table-column prop="creator_name" label="创建人" />
      <el-table-column label="操作" >
        <template #default="scope">
          <el-button link type="primary" @click="handleDetail(scope.row)">查看</el-button>
        </template>
      </el-table-column>
    </el-table>
    <div class="pagination">
      <el-pagination
        v-model:current-page="jobListParams.page"
        v-model:page-size="jobListParams.limit"
        :page-sizes="[10, 20, 30, 50]"
        layout="total, sizes, prev, pager, next"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>
<script setup>
import SearchBox from '../SearchBox.vue';
import { jobList } from '@/api/home/task';
const jobListParams = reactive({
  page: 1,
  limit: 10,
});
const jobListData = ref([]);
const total = ref(0);
// 获取任务列表
const getJobList = () => {
  jobList({current:1,size:10}).then(res => {
    if (res.data.code !== 0) return;
    jobListData.value = res.data.data.records;
    total.value = res.data.data.total;
  });
};
// 状态文字
const getStatusText = (status) => {
  const statusMap = {
    1: '待执行',
    2: '执行中',
    3: '已完成',
    4: '已取消',
    5: '执行失败'
  };
  return statusMap[status] || '未知';
};
// 状态标签类型
const getStatusType = (status) => {
  const typeMap = {
    1: 'info',
    2: 'warning',
    3: 'success',
    4: '',
    5: 'danger'
  };
  return typeMap[status] || '';
};
// 查看详情
const handleDetail = (row) => {
  console.log('查看详情', row);
};
// 分页大小改变
const handleSizeChange = (val) => {
  jobListParams.limit = val;
  getJobList();
};
// 页码改变
const handleCurrentChange = (val) => {
  jobListParams.page = val;
  getJobList();
};
onMounted(() => {
  getJobList();
});
</script>
<style lang="scss" scoped>
.task-intermediate-content {
  position: absolute;
  top: 190px;
  width: calc(100% - 400px - 400px - 100px);
  left: 450px;
  height: 770px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  :deep(.el-table) {
    background-color: transparent;
    --el-table-tr-bg-color: transparent;
    --el-table-border-color: rgba(255, 255, 255, 0.1);
    --el-table-header-bg-color: rgba(31, 62, 122, 0.5);
    --el-table-header-text-color: #fff;
    --el-table-text-color: #fff;
    .el-table__body tr:hover > td {
      background-color: rgba(31, 62, 122, 0.3) !important;
    }
  }
  .pagination {
    padding: 20px 0;
    display: flex;
    justify-content: flex-end;
    :deep(.el-pagination) {
      --el-pagination-bg-color: transparent;
      --el-pagination-text-color: #fff;
      --el-pagination-button-color: #fff;
      --el-pagination-hover-color: #409eff;
    }
  }
}
</style>
src/views/TaskManage/components/TaskLeft/TaskIndustry.vue
New file
@@ -0,0 +1,132 @@
<!-- 业务统计 -->
 <template>
  <common-title title="行业统计" :style="{ marginLeft: pxToRem(14) }"></common-title>
  <div class="task-industry">
    <div class="chart" ref="chartRef"></div>
  </div>
 </template>
<script setup>
import CommonTitle from '@/components/CommonTitle.vue';
import * as echarts from 'echarts';
import { industryJobNumPieChart } from '@/api/home/task';
const chartRef = ref(null);
let chart = null;
const option = {
  tooltip: {
    trigger: 'item',
    formatter: '{b}: {c} ({d}%)'
  },
  legend: {
    orient: 'horizontal',
    left: 'center',
    bottom: '5%',
    textStyle: {
      color: '#fff',
      fontSize: 12
    },
    itemWidth: 10,
    itemHeight: 10,
    itemGap: 10,
    formatter: function(name) {
      let data = option.series[0].data;
      let total = 0;
      let tarValue = 0;
      for (let i = 0; i < data.length; i++) {
        total += data[i].value;
        if (data[i].name === name) {
          tarValue = data[i].value;
        }
      }
      let percentage = ((tarValue / total) * 100).toFixed(1);
      return `${name} ${percentage}%`;
    }
  },
  series: [
    {
      name: '行业统计',
      type: 'pie',
      radius: '60%',
      center: ['35%', '50%'],
      itemStyle: {
        borderRadius: 4,
        borderColor: 'rgba(255,255,255,0.2)',
        borderWidth: 1
      },
      label: {
        show: true,
        position: 'inside',
        formatter: '{c}',
        fontSize: 12,
        color: '#fff'
      },
      labelLine: {
        show: false
      },
      emphasis: {
        scale: true,
        scaleSize: 10
      }
    }
  ]
};
// 获取行业统计数据
const getIndustryJobNumPieChart = () => {
  industryJobNumPieChart().then(res => {
    if (res.data.code !== 0) return;
    const data = res.data.data.map(item => ({
      name: item.name,
      value: item.value,
      itemStyle: {
        color: getRandomColor()
      }
    }));
    option.series[0].data = data;
    chart.setOption(option);
  });
};
// 生成随机颜色
const getRandomColor = () => {
  const colors = ['#1890FF', '#36CBCB', '#4ECB73', '#FBD437', '#F2637B', '#975FE5'];
  return colors[Math.floor(Math.random() * colors.length)];
};
onMounted(() => {
  chart = echarts.init(chartRef.value);
  getIndustryJobNumPieChart();
  window.addEventListener('resize', () => {
    chart?.resize();
  });
});
onUnmounted(() => {
  window.removeEventListener('resize', chart?.resize);
  chart?.dispose();
});
</script>
<style lang="scss" scoped>
.task-industry {
  font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
  margin-left: 29px;
  padding: 16px 16px;
  width: 340px;
  height: 400px;
  background: linear-gradient(
    270deg,
    rgba(31, 62, 122, 0) 0%,
    rgba(31, 62, 122, 0.35) 21%,
    #1f3e7a 100%
  );
  border-radius: 0px 0px 0px 0px;
  opacity: 0.85;
  .chart {
    width: 100%;
    height: 100%;
  }
}
</style>
src/views/TaskManage/components/TaskLeft/TaskLeft.vue
New file
@@ -0,0 +1,19 @@
<template>
  <div class="task-left">
    <TaskTotal />
    <TaskIndustry />
  </div>
</template>
<script setup>
import TaskTotal from './TaskTotal.vue';
import TaskIndustry from './TaskIndustry.vue';
</script>
<style lang="scss" scoped>
.task-left {
  // width: 300px;
  position: relative;
  top: 20px;
}
</style>
src/views/TaskManage/components/TaskLeft/TaskTotal.vue
New file
@@ -0,0 +1,100 @@
<!-- 任务统计 -->
<template>
  <common-title title="任务统计" :style="{ marginLeft: pxToRem(14) }"></common-title>
  <div class="task-total">
    <div class="card" v-for="item in list">
      <div>
        <div class="value">{{ item.value }}</div>
        <div class="name">{{item.name}}</div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { pxToRem } from '@/utils/rem';
import CommonTitle from '@/components/CommonTitle.vue';
import { totalJobNum, jobStatistics  } from "@/api/home/task";
const list = ref([
  { name: '总任务数', value: '1888'},
  { name: '计划执行', value: '18'},
  { name: '执行中', value: '999'},
  { name: '待执行', value: '8888'},
  { name: '已执行', value: '666'},
  { name: '执行失败', value: '2222'},
]);
// 获取任务统计总数
const getTotalJobNum = () => {
  totalJobNum().then((res) => {
    if (res.data.code !== 0) returen;
    list.value[0].value = res.data.data;
  });
};
// 获取其他任务统计
const getJobStatistics = () => {
  jobStatistics().then((res) => {
    if (res.data.code !== 0) returen;
    list.value[1].value = res.data.data.planned_executions;
    list.value[2].value = res.data.data.running_num;
    list.value[3].value = res.data.data.pending_executions;
    list.value[4].value = res.data.data.executed;
    list.value[5].value = res.data.data.failed_executions;
  });
};
onMounted(() => {
  getTotalJobNum();
  getJobStatistics();
});
</script>
<style lang="scss" scoped>
.task-total {
  font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
  margin-left: 29px;
  padding: 16px 16px;
  width: 340px;
  height: 400px;
  background: linear-gradient(
    270deg,
    rgba(31, 62, 122, 0) 0%,
    rgba(31, 62, 122, 0.35) 21%,
    #1f3e7a 100%
  );
  border-radius: 0px 0px 0px 0px;
  opacity: 0.85;
  margin-bottom: 12px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 20px;
  padding: 20px;
  .card {
    /* position: absolute;
            top: 8px;
            right: 10px;
            width: 200px; */
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            justify-content: flex-start;
            line-height: 22px;
            padding: 0 10px 10px 0;
      color: #ffffff;
    .name {
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 400;
      font-size: 14px;
    }
    .value {
      font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
      font-weight: 400;
      font-size: 26px;
    }
  }
}
</style>
src/views/TaskManage/components/TaskRight/TaskEvent.vue
New file
@@ -0,0 +1,150 @@
<!-- 任务事件统计 -->
<template>
  <common-title title="任务事件统计"></common-title>
  <div class="task-event">
    <div class="chart" ref="chartRef"></div>
  </div>
 </template>
<script setup>
import CommonTitle from '@/components/CommonTitle.vue';
import * as echarts from 'echarts';
import { jobEventBar } from '@/api/home/task';
import dayjs from 'dayjs';
// 日期
const currenDate = dayjs().format('YYYY-MM-DD');
const newTime = ref([currenDate, currenDate]);
// 图表
const chartRef = ref(null);
let chart = null;
const option = {
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'shadow'
    }
  },
  legend: {
    data: ['任务', '事件'],
    textStyle: {
      color: '#fff'
    },
    bottom: '5%'
  },
  grid: {
    top: '15%',
    left: '3%',
    right: '4%',
    bottom: '15%',
    containLabel: true
  },
  xAxis: {
    type: 'category',
    axisLine: {
      lineStyle: {
        color: '#fff'
      }
    },
    axisLabel: {
      color: '#fff',
      interval: 0,
      rotate: 30
    }
  },
  yAxis: {
    type: 'value',
    axisLine: {
      lineStyle: {
        color: '#fff'
      }
    },
    splitLine: {
      lineStyle: {
        color: 'rgba(255, 255, 255, 0.1)'
      }
    },
    axisLabel: {
      color: '#fff'
    }
  },
  series: [
    {
      name: '任务',
      type: 'bar',
      barWidth: '20%',
      itemStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: '#1EE7E7' },
          { offset: 1, color: 'rgba(30, 231, 231, 0.1)' }
        ])
      }
    },
    {
      name: '事件',
      type: 'bar',
      barWidth: '20%',
      itemStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: '#0070FF' },
          { offset: 1, color: 'rgba(0, 112, 255, 0.1)' }
        ])
      }
    }
  ]
};
// 获取行业统计数据
const getJobEventBar = (value,date_enum) => {
  jobEventBar({date_enum}).then(res => {
    if (res.data.code !== 0) return;
    option.xAxis.data = res.data.data.map(item => item.name);
    option.series[0].data = res.data?.data.map(item => item.data[0].value);
    option.series[1].data = res.data?.data.map(item => item.data[1].value);
    chart.setOption(option);
  });
};
// 生成随机颜色
const getRandomColor = () => {
  const colors = ['#1890FF', '#36CBCB', '#4ECB73', '#FBD437', '#F2637B', '#975FE5'];
  return colors[Math.floor(Math.random() * colors.length)];
};
onMounted(() => {
  chart = echarts.init(chartRef.value);
  getJobEventBar(newTime.value, 'CURRENT_YEAR');
  window.addEventListener('resize', () => {
    chart?.resize();
  });
});
onUnmounted(() => {
  window.removeEventListener('resize', chart?.resize);
  chart?.dispose();
});
</script>
<style lang="scss" scoped>
.task-event {
  font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
  margin-left: 29px;
  padding: 16px 16px;
  width: 340px;
  height: 400px;
  background: linear-gradient(
    270deg,
    rgba(31, 62, 122, 0) 0%,
    rgba(31, 62, 122, 0.35) 21%,
    #1f3e7a 100%
  );
  border-radius: 0px 0px 0px 0px;
  opacity: 0.85;
  .chart {
    width: 100%;
    height: 100%;
  }
}
</style>
src/views/TaskManage/components/TaskRight/TaskRight.vue
New file
@@ -0,0 +1,25 @@
<template>
  <div class="task-right">
    <TaskTime />
    <TaskEvent />
  </div>
</template>
<script setup>
import TaskTime from './TaskTime.vue';
import TaskEvent from './TaskEvent.vue';
</script>
<style scoped lang="scss">
.task-right {
  position: absolute;
  top: 122px;
  right: 31px;
  width: 404px;
  .titleBox {
    width: 404px;
    height: 43px;
  }
}
</style>
src/views/TaskManage/components/TaskRight/TaskTime.vue
New file
@@ -0,0 +1,125 @@
<!-- 任务事件统计 -->
<template>
  <common-title title="任务时间统计"></common-title>
  <div class="task-time">
    <div class="chart" ref="chartRef"></div>
  </div>
 </template>
<script setup>
import CommonTitle from '@/components/CommonTitle.vue';
import * as echarts from 'echarts';
import { jobNumBar } from '@/api/home/task';
import dayjs from 'dayjs';
// 日期
const currenDate = dayjs().format('YYYY-MM-DD');
const newTime = ref([currenDate, currenDate]);
const chartRef = ref(null);
let chart = null;
const option = {
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'shadow'
    }
  },
  grid: {
    top: '15%',
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  xAxis: {
    type: 'category',
    axisLine: {
      lineStyle: {
        color: '#fff'
      }
    },
    axisLabel: {
      color: '#fff',
      interval: 0,
      rotate: 30
    }
  },
  yAxis: {
    type: 'value',
    axisLine: {
      lineStyle: {
        color: '#fff'
      }
    },
    splitLine: {
      lineStyle: {
        color: 'rgba(255, 255, 255, 0.1)'
      }
    },
    axisLabel: {
      color: '#fff'
    }
  },
  series: [
    {
      type: 'bar',
      barWidth: '40%',
      itemStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: '#1EE7E7' },
          { offset: 1, color: 'rgba(30, 231, 231, 0.1)' }
        ])
      }
    }
  ]
};
// 获取任务时间统计数据
const getJobNumBar = (value,date_enum) => {
  jobNumBar({date_enum}).then(res => {
    if (res.data.code !== 0) return;
    option.xAxis.data = res.data.data.map(item => item.name);
    option.series[0].data = res.data.data.map(item => item.value);
    chart.setOption(option);
  });
};
onMounted(() => {
  chart = echarts.init(chartRef.value);
  getJobNumBar(newTime.value, 'CURRENT_YEAR');
  window.addEventListener('resize', () => {
    chart?.resize();
  });
});
onUnmounted(() => {
  window.removeEventListener('resize', chart?.resize);
  chart?.dispose();
});
</script>
<style lang="scss" scoped>
.task-time {
  font-family: YouSheBiaoTiHei, YouSheBiaoTiHei;
  margin-left: 29px;
  padding: 16px 16px;
  width: 340px;
  height: 400px;
  background: linear-gradient(
    270deg,
    rgba(31, 62, 122, 0) 0%,
    rgba(31, 62, 122, 0.35) 21%,
    #1f3e7a 100%
  );
  border-radius: 0px 0px 0px 0px;
  opacity: 0.85;
  .chart {
    width: 100%;
    height: 100%;
  }
}
</style>