forked from drone/command-center-dashboard

shuishen
2025-04-03 fc633bd6fcb581ab2fe34f32d71023c2af017cb5
Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
8 files modified
6 files renamed
17 files added
2 files deleted
1658 ■■■■■ changed files
.env.development 2 ●●●●● patch | view | raw | blame | history
.env.production 2 ●●●●● patch | view | raw | blame | history
.env.test 3 ●●●● patch | view | raw | blame | history
src/api/home/common.js 27 ●●●●● patch | view | raw | blame | history
src/api/home/machineNest.js 20 ●●●●● 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/layout/Header.vue 2 ●●● patch | view | raw | blame | history
src/store/modules/user.js 1 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineLeft/InspectionRaskDetails.vue 4 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineLeft/MachineData.vue patch | view | raw | blame | history
src/views/SignMachineNest/MachineLeft/MachineLeft.vue 46 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue 10 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineMonitor.vue patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineRight.vue patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineStatus.vue 16 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceEvent.vue 82 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceJob/DeviceJob.vue 74 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceJob/DeviceJobDetails/DeviceJobDetails.vue 130 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceJob/DeviceJobDetails/JobRelatedEvents.vue 92 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineTableDetails/MachineTableDetails.vue 75 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/SignMachineNest.vue 9 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineLeft/MachineLeft.vue 24 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineRight/components/MachineTableDetails.vue 87 ●●●●● 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
.env.development
@@ -5,4 +5,6 @@
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
# VITE_APP_API_URL = https://wrj.shuixiongit.com/api
# VITE_APP_API_URL = http://192.168.1.7
# ws地址
VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
.env.production
@@ -3,3 +3,5 @@
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
# ws地址
VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
.env.test
@@ -1,4 +1,5 @@
#测试环境配置
VITE_APP_ENV = 'test'
# ws地址
VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
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/machineNest.js
@@ -46,3 +46,23 @@
        params: {},
    })
}
// 设备-任务列表
export const getDeviceJobList = (data,params) => {
    return request({
        url: `/drone-device-core/wayline/waylineJobInfo/jobList`,
        method: 'post',
        data,
        params
    })
}
// 设备-事件列表
export const getDeviceEventList = (data,params) => {
    return request({
        url: `/drone-device-core/jobEvent/eventPage`,
        method: 'post',
        data,
        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/layout/Header.vue
@@ -50,7 +50,7 @@
]);
const handleClick = ({ path, name }) => {
  if (!['首页', '任务管理'].includes(name)) return ElMessage.warning('正在开发中');
  if (!['首页', '任务管理'].includes(name)) return ElMessage.warning('正在加急开发中...');
  // 更新 leftList 的 active 状态
  leftList.value.forEach(item => {
    item.active = item.path === path;
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/MachineLeft/InspectionRaskDetails.vue
File was renamed from src/views/SignMachineNest/components/MachineLeft/InspectionRaskDetails.vue
@@ -14,7 +14,7 @@
import { pxToRem } from '@/utils/rem';
import CommonTitle from '@/components/CommonTitle.vue';
import CommonDateTime from '@/components/CommonDateTime.vue';
import { jobNumBar, eventNumPie } from '@/api/home/index.js'
import { jobNumBar, eventNumPie } from '@/api/home'
// 日期
const currenDate = dayjs().format('YYYY-MM-DD');
@@ -230,4 +230,4 @@
        margin-top: 10px;
    }
}
</style>
</style>
src/views/SignMachineNest/MachineLeft/MachineData.vue
src/views/SignMachineNest/MachineLeft/MachineLeft.vue
New file
@@ -0,0 +1,46 @@
<template>
  <div class="machine-left">
    <!--时间 天气-->
    <common-weather></common-weather>
    <!-- 机巢数据 -->
    <MachineData></MachineData>
    <div class="do-return" @click="goBack">
      <img src="../../../assets/images/signMachineNest/return.png" alt="" />
    </div>
     <!--巡检任务详情-->
     <InspectionRaskDetails></InspectionRaskDetails>
  </div>
</template>
<script setup>
import CommonWeather from '@/components/CommonWeather.vue';
import MachineData from './MachineData.vue';
import InspectionRaskDetails from './InspectionRaskDetails.vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
const router = useRouter();
const store = useStore()
const goBack = () => {
  store.commit('setSingleUavHome', {});
};
</script>
<style scoped lang="scss">
.machine-left {
  position: absolute;
  top: 88px;
  color: #e7f5ff;
  .do-return {
    position: absolute;
    top: 50px;
    right: -50px;
    cursor: pointer;
    img {
      width: 40px;
      height: 40px;
    }
  }
}
</style>
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue
File was renamed from src/views/SignMachineNest/components/MachineRight/InspectionRaskList.vue
@@ -30,12 +30,12 @@
              </span>
            </div>
            <div class="left-b">
              <img src="@/assets/images/signMachineNest/machineRight/date.png" alt="" />{{ item.begin_time }}
              <img src="@/assets/images/signMachineNest/machineRight/name.png" alt="" />{{ item.creator_name || '' }}
              <img src="../../../assets/images/signMachineNest/machineRight/date.png" alt="" />{{ item.begin_time }}
              <img src="../../../assets/images/signMachineNest/machineRight/name.png" alt="" />{{ item.creator_name || '' }}
            </div>
          </div>
          <div class="right">
            <img src="@/assets/images/signMachineNest/machineRight/return.png" alt="">
            <img src="../../../assets/images/signMachineNest/machineRight/return.png" alt="">
          </div>
        </div>
      </div>
@@ -51,7 +51,7 @@
<script setup>
import { Search } from '@element-plus/icons-vue';
import CommonTitle from '@/components/CommonTitle.vue';
import { getBeforeJob, getTodayJob } from '@/api/home/index.js'
import { getBeforeJob, getTodayJob } from '@/api/home'
const isMore = ref(true);
// 控制加载状态
@@ -273,4 +273,4 @@
      }
    }
 }
</style>
</style>
src/views/SignMachineNest/MachineRight/MachineMonitor.vue
src/views/SignMachineNest/MachineRight/MachineRight.vue
src/views/SignMachineNest/MachineRight/MachineStatus.vue
File was renamed from src/views/SignMachineNest/components/MachineRight/MachineStatus.vue
@@ -4,7 +4,7 @@
  <div :style="{ marginLeft: pxToRem(14) }">
    <div class="machine-status">
      <div class="info">
        <img src="@/assets/images/signMachineNest/machineRight/wrj.png" alt="">
        <img src="../../../assets/images/signMachineNest/machineRight/wrj.png" alt="">
        <div class="info-right">
          <div class="name">{{ osdVisible?.callsign || '--' }}</div>
          <div class="wz">
@@ -20,28 +20,28 @@
      <div class="status">
        <div class="card">
          <div>
            <img src="@/assets/images/signMachineNest/machineRight/height.png" alt="">
            <img src="../../../assets/images/signMachineNest/machineRight/height.png" alt="">
            <span class="text">实时真高</span>
          </div>
          <div class="text-data">{{ detailInfo.height || '--' }}<span class="text">米</span></div>
        </div>
        <div class="card">
          <div>
            <img src="@/assets/images/signMachineNest/machineRight/speed.png" alt="">
            <img src="../../../assets/images/signMachineNest/machineRight/speed.png" alt="">
            <span class="text">飞行速度</span>
          </div>
          <div class="text-data">{{ detailInfo.horizontal_speed }}<span class="text">米/秒</span></div>
        </div>
        <div class="card">
          <div>
            <img src="@/assets/images/signMachineNest/machineRight/signal.png" alt="">
            <img src="../../../assets/images/signMachineNest/machineRight/signal.png" alt="">
            <span class="text">信号强度</span>
          </div>
          <div class="text-data">{{ detailInfo.quality }}</div>
        </div>
        <div class="card">
          <div>
            <img src="@/assets/images/signMachineNest/machineRight/electricity.png" alt="">
            <img src="../../../assets/images/signMachineNest/machineRight/electricity.png" alt="">
            <span class="text">电池电量</span>
          </div>
          <div v-if="drone_charge_state.capacity_percent != 0" class="text-data">
@@ -51,14 +51,14 @@
        </div>
        <div class="card">
          <div>
            <img src="@/assets/images/signMachineNest/machineRight/distance.png" alt="">
            <img src="../../../assets/images/signMachineNest/machineRight/distance.png" alt="">
            <span class="text">飞行距离</span>
          </div>
          <div class="text-data">{{ singleTotal.flight_mileage }}<span class="text">km</span></div>
        </div>
        <div class="card">
          <div>
            <img src="@/assets/images/signMachineNest/machineRight/duration.png" alt="">
            <img src="../../../assets/images/signMachineNest/machineRight/duration.png" alt="">
            <span class="text">飞行时长</span>
          </div>
          <div class="text-data">{{ singleTotal.hour_count }}<span class="text">h</span></div>
@@ -75,7 +75,7 @@
import { EModeCode, EDockModeText, EModeText } from '@/utils/staticData/device';
import { getLnglatAltitude } from '@/utils/cesium/mapUtil.js';
import { useStore } from 'vuex';
import MachineTableDetails from '@/views/SignMachineNest/components/MachineRight/components/MachineTableDetails.vue';
import MachineTableDetails from '@/views/SignMachineNest/MachineRight/MachineTableDetails/MachineTableDetails.vue';
const store = useStore();
// 获取机巢信息
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceEvent.vue
New file
@@ -0,0 +1,82 @@
<template>
    <el-divider content-position="left">相关事件 {{total}}次</el-divider>
    <el-table :data="list" style="width: 100%">
        <el-table-column prop="id" label="id" />
        <el-table-column prop="event_name" label="名称" />
        <el-table-column prop="media_type" label="类型" />
        <el-table-column prop="ai_types" label="关联算法" />
        <el-table-column prop="photo_url" label="图片url" />
        <el-table-column prop="video_url" label="视频url" />
        <el-table-column prop="status" label="状态">
            <template #default="scope">
                <el-tag v-if="scope.row.status === 0" type="info">待处理</el-tag>
                <el-tag v-if="scope.row.status === 1" type="success">待分拨</el-tag>
                <el-tag v-if="scope.row.status === 2" type="warning">待处理</el-tag>
                <el-tag v-if="scope.row.status === 3" type="success">处理中</el-tag>
                <el-tag v-if="scope.row.status === 4" type="success">已完成</el-tag>
                <el-tag v-if="scope.row.status === 5" type="success">已完结</el-tag>
            </template>
        </el-table-column>
    <el-table-column prop="event_dict_key" label="event_dict_key" />
    <el-table-column prop="create_time" label="创建时间" />
    <el-table-column label="操作" >
      <template #default="scope">
        <el-button type="primary" link @click="examine(scope.row)">审核</el-button>
        <el-button type="primary" link @click="distribution(scope.row)">分拨</el-button>
      </template>
    </el-table-column>
    </el-table>
    <el-pagination
        background
        :page-sizes="[10, 20, 30, 50]"
        v-model:current-page="sizeParams.current"
        v-model:page-size="sizeParams.size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @change="pageChange"
    />
</template>
<script setup>
import { useStore } from 'vuex'
import { getDeviceEventList } from '@/api/home/machineNest'
import { ElMessage } from 'element-plus';
const list = ref([])
const params = ref({
    device_sn: null,
})
const sizeParams = ref({
    current: 1,
    size: 10,
})
const store = useStore()
const device_sn = computed(() => store.state.home.singleUavHome.device_sn)
const total = ref(0)
const distribution = row => {
    ElMessage.warning('正在加急开发中...')
}
const examine = row => {
  ElMessage.warning('正在加急开发中...')
}
const getList = () => {
    params.value.device_sn = device_sn.value
    getDeviceEventList(params.value, sizeParams.value).then(res => {
        const resData = res?.data?.data || {}
        list.value = resData.records
        total.value = resData.total
    })
}
const pageChange = val => {
    sizeParams.value.current = val
    getList()
}
onMounted(() => {
    getList()
})
</script>
<style scoped lang="scss"></style>
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceJob/DeviceJob.vue
New file
@@ -0,0 +1,74 @@
<template>
    <el-divider content-position="left">相关任务 {{ total }}次</el-divider>
    <el-table :data="list" style="width: 100%">
        <el-table-column prop="id" label="id" />
        <el-table-column prop="name" label="名称" />
        <el-table-column prop="industry_type_str" label="任务类型" />
        <el-table-column prop="ai_type_str" label="关联算法" />
        <el-table-column prop="event_number" label="事件工单" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column prop="area_code" label="地区代码" />
        <el-table-column prop="begin_time" label="开始时间" />
        <el-table-column prop="end_time" label="结束时间" />
        <el-table-column prop="creator_name" label="创建者" />
        <el-table-column label="操作">
            <template #default="scope">
                <el-button type="primary" link @click="viewJob(scope.row)">查看</el-button>
            </template>
        </el-table-column>
    </el-table>
    <el-pagination
        background
        :page-sizes="[10, 20, 30, 50]"
        v-model:current-page="sizeParams.current"
        v-model:page-size="sizeParams.size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @change="pageChange"
    />
  <DeviceJobDetails v-model:show="deviceJobDetailsShow"/>
</template>
<script setup>
import { useStore } from 'vuex'
import { getDeviceJobList } from '@/api/home/machineNest'
import DeviceJobDetails from './DeviceJobDetails/DeviceJobDetails.vue';
const list = ref([])
const params = ref({
    device_sn: null,
})
const sizeParams = ref({
    current: 1,
    size: 10,
})
const store = useStore()
const device_sn = computed(() => store.state.home.singleUavHome.device_sn)
const total = ref(0)
const deviceJobDetailsShow = ref(false)
const jobId = ref(null)
provide('jobId', jobId)
const viewJob = (row) => {
  jobId.value = row.id
  deviceJobDetailsShow.value = true
}
const getList = () => {
    params.value.device_sn = device_sn.value
    getDeviceJobList(params.value, sizeParams.value).then(res => {
        const resData = res?.data?.data || {}
        list.value = resData.records
        total.value = resData.total
    })
}
const pageChange = val => {
    sizeParams.value.current = val
    getList()
}
onMounted(() => {
    getList()
})
</script>
<style scoped lang="scss"></style>
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceJob/DeviceJobDetails/DeviceJobDetails.vue
New file
@@ -0,0 +1,130 @@
<!-- 任务详情 -->
<template>
    <el-dialog
        append-to-body
        modal-class="detailsOfHistoricalTasks"
        v-model="isShow"
        title="历史任务详情"
        :width="pxToRem(1500)"
        :close-on-click-modal="false"
        :destroy-on-close="true"
    >
        <div class="content">
            <div class="contentLeft">
                <el-divider content-position="left">详情</el-divider>
                <div class="infoBox">
                    <div class="itemBox" v-for="item in infoList">
                        <div class="itemTitle">{{ item.name }}:</div>
                        <div class="itemValue">{{ item.value }}</div>
                    </div>
                </div>
                <JobRelatedEvents v-if="isShow" />
                <el-divider content-position="left">任务成果 xxx个</el-divider>
                <div class="imgListBox">
                    <div v-for="item in imgList" class="imgItem">xxx图片</div>
                </div>
            </div>
            <div class="contentRight">map</div>
        </div>
    </el-dialog>
</template>
<script setup>
import { pxToRem } from '@/utils/rem'
import JobRelatedEvents from './JobRelatedEvents.vue'
const isShow = defineModel('show')
const imgList = ref([
    { name: '图片1', url: 'xxx' },
    { name: '图片1', url: 'xxx' },
    { name: '图片1', url: 'xxx' },
    { name: '图片1', url: 'xxx' },
])
const infoList = ref([
    { name: '任务编号', value: 'xxx' },
    { name: '任务所属机巢', value: 'xxx' },
    { name: '关联算法', value: 'xxx' },
    { name: '任务名称', value: 'xxx' },
    { name: '所属单位', value: 'xxx' },
    { name: '任务类型', value: 'xxx' },
    { name: '任务周期', value: 'xxx' },
    { name: '飞行事件', value: 'xxx' },
    { name: '任务频次', value: 'xxx' },
    { name: '任务描述', value: 'xxx' },
])
onMounted(() => {})
</script>
<style lang="scss">
.detailsOfHistoricalTasks {
    .el-dialog {
        --el-dialog-margin-top: 5vh;
    }
    .el-pagination {
        justify-content: center;
        padding: 20px;
    }
    .el-dialog__body {
        height: 80vh;
    }
}
</style>
<style lang="scss" scoped>
.content {
    display: flex;
    height: 100%;
    .contentLeft {
        width: 900px;
        overflow: auto;
        .infoBox {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px; // 列之间的间距
            padding: 10px;
            .itemBox {
                background-color: #fff;
                border: 1px solid #ddd;
                border-radius: 4px;
                padding: 15px;
                box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
                display: flex;
                align-items: center;
            }
            .itemTitle {
                font-weight: bold;
                color: #333;
            }
            .itemValue {
                color: #666;
            }
        }
        .imgListBox {
            display: flex;
            .imgItem {
                width: 200px;
                height: 200px;
            }
        }
    }
    .contentRight {
        width: 0;
        flex-grow: 1;
        background: #19ad8d;
    }
}
</style>
src/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceJob/DeviceJobDetails/JobRelatedEvents.vue
New file
@@ -0,0 +1,92 @@
<template>
    <el-divider content-position="left">关联事件 {{total}}个</el-divider>
    <el-table :data="list" style="width: 100%" >
        <el-table-column prop="event_num" label="编号" />
        <el-table-column prop="event_name" label="名称" />
        <el-table-column prop="media_type" label="类型" />
        <el-table-column prop="photo_url" label="图片url" />
        <el-table-column prop="video_url" label="视频url" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column prop="status" label="状态">
            <template #default="scope">
                <el-tag v-if="scope.row.status === 0" type="info">待处理</el-tag>
                <el-tag v-if="scope.row.status === 1" type="success">待分拨</el-tag>
                <el-tag v-if="scope.row.status === 2" type="warning">待处理</el-tag>
                <el-tag v-if="scope.row.status === 3" type="success">处理中</el-tag>
                <el-tag v-if="scope.row.status === 4" type="success">已完成</el-tag>
                <el-tag v-if="scope.row.status === 5" type="success">已完结</el-tag>
            </template>
        </el-table-column>
        <el-table-column prop="wayline_job_id" label="    任务id" />
    <el-table-column label="操作" >
      <template #default="scope">
        <el-button type="primary" link @click="examine(scope.row)">审核</el-button>
        <el-button type="primary" link @click="distribution(scope.row)">分拨</el-button>
      </template>
    </el-table-column>
    </el-table>
    <el-pagination
        background
        :page-sizes="[10, 20, 30, 50]"
        v-model:current-page="sizeParams.current"
        v-model:page-size="sizeParams.size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @change="pageChange"
    />
</template>
<script setup>
import { useStore } from 'vuex'
import { getDeviceEventList } from '@/api/home/machineNest'
import { ElMessage } from 'element-plus';
const jobId = inject("jobId");
const list = ref([
  {event_num:''},
  {event_num:''},
  {event_num:''},
  {event_num:''},
  {event_num:''},
  {event_num:''},
  {event_num:''},
  {event_num:''},
  {event_num:''},
  {event_num:''},
])
const params = ref({
    device_sn: null,
})
const sizeParams = ref({
    current: 1,
    size: 1,
})
const store = useStore()
const child_sn = computed(() => store.state.home.singleUavHome.child_sn)
const total = ref(10)
const distribution = row => {
    ElMessage.warning('正在加急开发中...')
}
const examine = row => {
  ElMessage.warning('正在加急开发中...')
}
const getList = () => {
    params.value.device_sn = child_sn.value
    getDeviceEventList(params.value, sizeParams.value).then(res => {
        const resData = res?.data?.data || {}
        list.value = resData.records
        total.value = resData.total
    })
}
const pageChange = val => {
    sizeParams.value.current = val
    getList()
}
onMounted(() => {
    // getList()
})
</script>
<style scoped lang="scss"></style>
src/views/SignMachineNest/MachineRight/MachineTableDetails/MachineTableDetails.vue
New file
@@ -0,0 +1,75 @@
<!-- 机巢列表详情 -->
<template>
    <el-dialog
        modal-class="machineTableDetails"
        v-model="isShowDetails"
        title="机巢详情"
        :width="pxToRem(1500)"
        :close-on-click-modal="false"
        :destroy-on-close="true"
    >
        <el-divider content-position="left">详情</el-divider>
        <div class="infoBox">
            <div class="itemBox" v-for="item in infoList">
                <div class="itemTitle">{{ item.name }}:</div>
                <div class="itemValue">{{ item.value }}</div>
            </div>
        </div>
        <DeviceJob v-if="isShowDetails" />
        <DeviceEvent v-if="isShowDetails" />
    </el-dialog>
</template>
<script setup>
import DeviceEvent from '@/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceEvent.vue'
import DeviceJob from '@/views/SignMachineNest/MachineRight/MachineTableDetails/DeviceJob/DeviceJob.vue'
import { pxToRem } from '@/utils/rem';
const isShowDetails = defineModel('show')
const infoList = ref([
    { name: '机巢名称', value: 'xxx' },
    { name: '机巢所属地区', value: 'xxx' },
    { name: '机巢状态', value: 'xxx' },
    { name: '任务成功', value: 'xxx' },
    { name: '机巢位置', value: 'xxx' },
])
onMounted(() => {})
</script>
<style lang="scss">
.machineTableDetails {
    .el-pagination {
        text-align: left;
        padding: 20px;
    }
}
</style>
<style lang="scss" scoped>
.infoBox {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px; // 列之间的间距
    padding: 10px;
    .itemBox {
        background-color: #fff;
        border: 1px solid #ddd;
        border-radius: 4px;
        padding: 15px;
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
    }
    .itemTitle {
        font-weight: bold;
        color: #333;
    }
    .itemValue {
        margin-top: 5px;
        color: #666;
    }
}
</style>
src/views/SignMachineNest/SignMachineNest.vue
@@ -4,8 +4,8 @@
</template>
<script setup>
import MachineLeft from './components/MachineLeft/MachineLeft.vue'
import MachineRight from './components/MachineRight/MachineRight.vue';
import MachineLeft from '@/views/SignMachineNest/MachineLeft/MachineLeft.vue'
import MachineRight from '@/views/SignMachineNest/MachineRight/MachineRight.vue';
import { useConnectWebSocket } from '../../utils/websocket/connect-websocket';
import { getWebsocketUrl } from '@/websocket/util/config';
import { EBizCode } from '@/utils/staticData/enums.js';
@@ -31,7 +31,6 @@
const messageHandler = (result) => {
  let payload = JSON.parse(result) // 为了兼容聊天消息
  // console.log('payload', payload)
  if (!payload) return
  switch (payload.biz_code) {
    case EBizCode.GatewayOsd: {
@@ -83,6 +82,8 @@
  getFlightStatistics(singleUavHome.value.device_sn).then(res => {
    if (res.data.code !== 0) return;
    const result = res.data.data;
    console.log(result);
    console.log(result);
    store.commit('setSingleTotal', result);
  })
};
@@ -95,4 +96,4 @@
onUnmounted(() => {
  connectWs?.value?.close();
});
</script>
</script>
src/views/SignMachineNest/components/MachineLeft/MachineLeft.vue
File was deleted
src/views/SignMachineNest/components/MachineRight/components/MachineTableDetails.vue
File was deleted
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>