zhanghao 4 miesięcy temu
rodzic
commit
9834a1afdf

+ 8 - 8
src/api/screen/engineering.js

@@ -4,7 +4,7 @@ import request from '@/utils/request'
 
 export function examine(appOrg) {
   return request({
-    url: '/large/one/getProjectNumX',
+    url: '/large/production/getProjectNumX',
     method: 'get'
   })
 }
@@ -12,48 +12,48 @@ export function examine(appOrg) {
 
 export function monitor(appOrg) {
   return request({
-    url: '/large/two/getProjectCameraNumList',
+    url: '/large/monitor/getProjectCameraNumList',
     method: 'get'
   })
 }
 
 export function getProjectNumX() {
   return request({
-    url: 'large/three/getIssueListLastWeek',
+    url: 'large/check/getIssueListLastWeek',
     method: 'get'
   })
 }
 export function getIssueList() {
   return request({
-    url: '/large/three/getProjectNumThisWeek',
+    url: '/large/check/getProjectNumThisWeek',
     method: 'get'
   })
 }
 
 export function geterProjectList() {
   return request({
-    url: '/large/three/planCheckList',
+    url: '/large/check/planCheckList',
     method: 'get'
   })
 }
 
 export function getProjectThisWeek() {
   return request({
-    url: '/large/three/planDoneList',
+    url: '/large/check/planDoneList',
     method: 'get'
   })
 }
 
 export function getProjectLastWeek() {
   return request({
-    url:'/large/three/getIssueNum',
+    url:'/large/check/getIssueNum',
     method: 'get'
   })
 }
 
 export function getProjectNumThisWeek() {
   return request({
-    url: '/large/three/getOverIssueNum',
+    url: '/large/check/getOverIssueNum',
     method: 'get'
   })
 }

+ 17 - 0
src/api/xxlJob/dashboard.js

@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+export function getChartInfo(params) {
+  return request({
+    url: '/xxl-job-admin/chartInfo',
+    method: 'post',
+    params: params
+  })
+}
+
+export function getDashboardInfo(params) {
+  return request({
+    url: '/xxl-job-admin/dashboardInfo',
+    method: 'post',
+    data: params
+  })
+}

+ 49 - 0
src/api/xxlJob/group.js

@@ -0,0 +1,49 @@
+import request from '@/utils/request'
+
+export function getGroupList(params) {
+  return request({
+    url: '/xxl-job-admin/jobgroup/pageList',
+    method: 'post',
+    data: params
+  })
+}
+
+export function getGroupListAll() {
+  return request({
+      url: '/xxl-job-admin/jobgroup/listAll',
+      method: 'get'
+    }
+  )
+}
+
+export function create(params) {
+  return request({
+    url: '/xxl-job-admin/jobgroup/add',
+    method: 'post',
+    data: params
+  })
+}
+
+export function remove(id) {
+  return request({
+    url: '/xxl-job-admin/jobgroup/remove',
+    method: 'get',
+    params: {id: id}
+  })
+}
+
+export function update(params) {
+  return request({
+    url: '/xxl-job-admin/jobgroup/update',
+    method: 'post',
+    data: params
+  })
+}
+
+export function groupDetail(id){
+  return request({
+    url: '/xxl-job-admin/jobgroup/loadById',
+    method: 'post',
+    params: {'id':id}
+  })
+}

+ 65 - 0
src/api/xxlJob/jobinfo.js

@@ -0,0 +1,65 @@
+import request from '@/utils/request'
+
+export function getJobPageList(params) {
+   return request({
+    url: '/xxl-job-admin/jobinfo/pageList',
+    method: 'post',
+     params: params
+  })
+}
+
+export function create(params) {
+  return request({
+    url: '/xxl-job-admin/jobinfo/add',
+    method: 'post',
+    data: params
+  })
+}
+
+export function remove(id) {
+  return request({
+    url: '/xxl-job-admin/jobinfo/remove',
+    method: 'get',
+    params: {id: id}
+  })
+}
+
+export function update(params) {
+  return request({
+    url: '/xxl-job-admin/jobinfo/update',
+    method: 'post',
+    data: params
+  })
+}
+//执行一次触发
+export function jobTrigger(param) {
+  return request({
+    url: '/xxl-job-admin/jobinfo/trigger',
+    method: 'post',
+    params: param
+  })
+}
+
+export function nextTriggerTime(scheduleType,scheduleConf) {
+  return request({
+    url: '/xxl-job-admin/jobinfo/nextTriggerTime',
+    method: 'post',
+    data: 'scheduleType='+scheduleType+'&scheduleConf='+scheduleConf
+  })
+}
+
+export function jobStart(id) {
+  return request({
+    url: '/xxl-job-admin/jobinfo/start',
+    method: 'post',
+    params: {id: id}
+  })
+}
+
+export function jobStop(id) {
+  return request({
+    url: '/xxl-job-admin/jobinfo/stop',
+    method: 'post',
+    params: {id: id}
+  })
+}

+ 42 - 0
src/api/xxlJob/logs.js

@@ -0,0 +1,42 @@
+import request from '@/utils/request'
+
+export function getJobsByGroup(groupId) {
+  return request({
+    url: '/xxl-job-admin/joblog/getJobsByGroup',
+    method: 'post',
+    params: { jobGroup: groupId }
+  })
+}
+
+export function getLogsList(params) {
+  return request({
+    url: '/xxl-job-admin/joblog/pageList',
+    method: 'post',
+    params: params
+  })
+}
+
+export function stopJob(id) {
+  return request({
+    url: '/xxl-job-admin/joblog/logKill',
+    method: 'get',
+    params: { id: id }
+  })
+}
+
+export function getJobDetail(params) {
+  return request({
+    url: '/xxl-job-admin/joblog/logDetailCat',
+    method: 'post',
+    params: params
+  })
+}
+
+export function clearLogs(params) {
+  return request({
+    url: '/xxl-job-admin/joblog/clearLog',
+    method: 'get',
+    params: params
+  })
+}
+

+ 107 - 0
src/views/xxlJob/dashboard/components/BarChart.vue

@@ -0,0 +1,107 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}"/>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+require('echarts/theme/macarons') // echarts theme
+import { debounce } from '@/utils'
+
+const animationDuration = 6000
+
+export default {
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '300px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+    this.__resizeHanlder = debounce(() => {
+      if (this.chart) {
+        this.chart.resize()
+      }
+    }, 150)
+    window.addEventListener('resize', this.__resizeHanlder)
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    window.removeEventListener('resize', this.__resizeHanlder)
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+
+      this.chart.setOption({
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        grid: {
+          top: 10,
+          left: '2%',
+          right: '2%',
+          bottom: '3%',
+          containLabel: true
+        },
+        xAxis: [{
+          type: 'category',
+          data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+          axisTick: {
+            alignWithLabel: true
+          }
+        }],
+        yAxis: [{
+          type: 'value',
+          axisTick: {
+            show: false
+          }
+        }],
+        series: [{
+          name: 'pageA',
+          type: 'bar',
+          stack: 'vistors',
+          barWidth: '60%',
+          data: [79, 52, 200, 334, 390, 330, 220],
+          animationDuration
+        }, {
+          name: 'pageB',
+          type: 'bar',
+          stack: 'vistors',
+          barWidth: '60%',
+          data: [80, 52, 200, 334, 390, 330, 220],
+          animationDuration
+        }, {
+          name: 'pageC',
+          type: 'bar',
+          stack: 'vistors',
+          barWidth: '60%',
+          data: [30, 52, 200, 334, 390, 330, 220],
+          animationDuration
+        }]
+      })
+    }
+  }
+}
+</script>

+ 175 - 0
src/views/xxlJob/dashboard/components/LineChart.vue

@@ -0,0 +1,175 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}"></div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+import { debounce } from '@/utils'
+
+require('echarts/theme/macarons') // echarts theme
+
+export default {
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '350px'
+    },
+    autoResize: {
+      type: Boolean,
+      default: true
+    },
+    chartData: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.initChart()
+    if (this.autoResize) {
+      this.__resizeHanlder = debounce(() => {
+        if (this.chart) {
+          this.chart.resize()
+        }
+      }, 100)
+      window.addEventListener('resize', this.__resizeHanlder)
+    }
+
+    // 监听侧边栏的变化
+    const sidebarElm = document.getElementsByClassName('sidebar-container')[0]
+    sidebarElm.addEventListener('transitionend', this.__resizeHanlder)
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    if (this.autoResize) {
+      window.removeEventListener('resize', this.__resizeHanlder)
+    }
+
+    const sidebarElm = document.getElementsByClassName('sidebar-container')[0]
+    // console.info(sidebarElm)
+    if (sidebarElm != null && sidebarElm !== undefined) {
+      sidebarElm.removeEventListener('transitionend', this.__resizeHanlder)
+    }
+
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    setOptions({ successData, failData, runningData, lineCharDates } = {}) {
+      this.chart.setOption({
+        xAxis: {
+          data: lineCharDates,
+          boundaryGap: false,
+          axisTick: {
+            show: false
+          }
+        },
+        grid: {
+          left: 10,
+          right: 10,
+          bottom: 20,
+          top: 30,
+          containLabel: true
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'cross'
+          },
+          padding: [5, 10]
+        },
+        yAxis: {
+          axisTick: {
+            show: false
+          }
+        },
+        legend: {
+          data: ['成功', '失败', '进行中']
+        },
+        series: [{
+          name: '成功',
+          itemStyle: {
+            normal: {
+              color: '#9ACD32',
+              lineStyle: {
+                color: '#9ACD32',
+                width: 2
+              }
+            }
+          },
+          smooth: true,
+          type: 'line',
+          data: successData,
+          animationDuration: 2800,
+          animationEasing: 'cubicInOut'
+        },
+          {
+            name: '失败',
+            smooth: true,
+            type: 'line',
+            itemStyle: {
+              normal: {
+                color: '#FF005A',
+                lineStyle: {
+                  color: '#FF005A',
+                  width: 2
+                }
+                /*areaStyle: {
+                  color: '#FF005A'
+                }*/
+              }
+            },
+            data: failData,
+            animationDuration: 2800,
+            animationEasing: 'quadraticOut'
+          }, {
+            name: '进行中',
+            smooth: true,
+            type: 'line',
+            itemStyle: {
+              normal: {
+                color: '#FFD700',
+                lineStyle: {
+                  color: '#FFD700',
+                  width: 2
+                }
+                /*areaStyle: {
+                  color: '#FFD700'
+                }*/
+              }
+            },
+            data: runningData,
+            animationDuration: 2800,
+            animationEasing: 'quadraticOut'
+          }]
+      })
+    },
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.chartData)
+    }
+  }
+}
+</script>

+ 138 - 0
src/views/xxlJob/dashboard/components/PanelGroup.vue

@@ -0,0 +1,138 @@
+<template>
+  <el-row :gutter="32" type="flex" justify="center" class="panel-group">
+    <el-col :span="8" :xs="12" :sm="12" :lg="8" class="card-panel-col">
+      <div class="card-panel">
+        <div class="card-panel-icon-wrapper icon-people">
+          <svg-icon icon-class="advisory" class-name="card-panel-icon"/>
+        </div>
+        <div class="card-panel-description">
+          <div class="card-panel-text">任务总数</div>
+          <count-to :start-val="0" :end-val="groupData.jobInfoCount" :duration="2000" class="card-panel-num"/>
+        </div>
+      </div>
+    </el-col>
+    <el-col :span="8" :xs="12" :sm="12" :lg="8" class="card-panel-col">
+      <div class="card-panel">
+        <div class="card-panel-icon-wrapper icon-message">
+          <svg-icon icon-class="activity" class-name="card-panel-icon"/>
+        </div>
+        <div class="card-panel-description">
+          <div class="card-panel-text">调度总次数</div>
+          <count-to :start-val="0" :end-val="groupData.jobLogCount" :duration="2000" class="card-panel-num"/>
+        </div>
+      </div>
+    </el-col>
+    <el-col :span="8" :xs="12" :sm="12" :lg="8" class="card-panel-col">
+      <div class="card-panel">
+        <div class="card-panel-icon-wrapper icon-shoppingCard">
+          <svg-icon icon-class="guide" class-name="card-panel-icon"/>
+        </div>
+        <div class="card-panel-description">
+          <div class="card-panel-text">在线执行器数量</div>
+          <count-to :start-val="0" :end-val="groupData.executorCount" :duration="2000" class="card-panel-num"/>
+        </div>
+      </div>
+    </el-col>
+  </el-row>
+</template>
+
+<script>
+  import CountTo from 'vue-count-to'
+
+  export default {
+    props: {
+      groupData: {
+        type: Object,
+        required: true
+      }
+    },
+    components: {
+      CountTo
+    },
+    mounted() {
+    },
+    data() {
+      return {}
+    },
+    methods: {
+      handleSetLineChartData(type) {
+        this.$emit('handleSetLineChartData', type)
+      }
+    }
+  }
+</script>
+
+<style rel="stylesheet/scss" lang="scss" slot-scope>
+  .panel-group {
+    margin-top: 18px;
+    .card-panel-col {
+      margin-bottom: 16px;
+    }
+    .card-panel {
+      height: 108px;
+      cursor: pointer;
+      font-size: 12px;
+      position: relative;
+      overflow: hidden;
+      color: #666;
+      background: #fff;
+      box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
+      border-color: rgba(0, 0, 0, .05);
+      &:hover {
+        .card-panel-icon-wrapper {
+          color: #fff;
+        }
+        .icon-people {
+          background: #40c9c6;
+        }
+        .icon-message {
+          background: #36a3f7;
+        }
+        .icon-money {
+          background: #f4516c;
+        }
+        .icon-shoppingCard {
+          background: #34bfa3
+        }
+      }
+      .icon-people {
+        color: #40c9c6;
+      }
+      .icon-message {
+        color: #36a3f7;
+      }
+      .icon-money {
+        color: #f4516c;
+      }
+      .icon-shoppingCard {
+        color: #34bfa3
+      }
+      .card-panel-icon-wrapper {
+        float: left;
+        margin: 14px 0 0 14px;
+        padding: 16px;
+        transition: all 0.38s ease-out;
+        border-radius: 6px;
+      }
+      .card-panel-icon {
+        float: left;
+        font-size: 48px;
+      }
+      .card-panel-description {
+        float: right;
+        font-weight: bold;
+        margin: 26px;
+        margin-left: 0px;
+        .card-panel-text {
+          line-height: 18px;
+          color: rgba(0, 0, 0, 0.45);
+          font-size: 16px;
+          margin-bottom: 12px;
+        }
+        .card-panel-num {
+          font-size: 20px;
+        }
+      }
+    }
+  }
+</style>

+ 116 - 0
src/views/xxlJob/dashboard/components/PieChart.vue

@@ -0,0 +1,116 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}"/>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+require('echarts/theme/macarons') // echarts theme
+import { debounce } from '@/utils'
+
+export default {
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '300px'
+    },
+    chartData: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+      // pieCharData:{
+      //   jobSuccess: this.chartData.jobSuccess,
+      //   jobFail: this.chartData.jobFail,
+      //   jobRunning: this.chartData.jobRunning
+      // }
+    }
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.initChart()
+    this.__resizeHanlder = debounce(() => {
+      if (this.chart) {
+        this.chart.resize()
+      }
+    }, 100)
+    window.addEventListener('resize', this.__resizeHanlder)
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    window.removeEventListener('resize', this.__resizeHanlder)
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    setOptions(charData) {
+      this.chart.setOption({
+        title: {
+          // 标题及位置
+          text: '成功比例图',
+          left: 'center'
+        },
+        tooltip: {
+          // 悬停样式
+          trigger: 'item',
+          formatter: '{a} <br/>{b} : {c} ({d}%)'
+        },
+        legend: {
+          // 图例,竖排
+          left: 'center',
+          bottom: '0'
+          // orient: "vertical"
+        },
+        calculable: true,
+        series: [
+          {
+            // 提示框标题
+            name: '成功比例图',
+            type: 'pie',
+            roseType: 'radius',
+            // 各部分饼占父元素百分比
+            radius: [15, 95],
+            center: ['50%', '50%'],
+            data: [
+              { value: charData.jobSuccess, name: '成功' },
+              { value: charData.jobFail, name: '失败' },
+              { value: charData.jobRunning, name: '进行中' }
+            ],
+            animationEasing: 'cubicInOut',
+            animationDuration: 2600
+          }
+        ]
+      })
+    },
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.chartData)
+    }
+  }
+}
+</script>
+<style rel="stylesheet/scss" lang="scss">
+.el-col-lg-8 {
+  width: 50%;
+}
+</style>

+ 121 - 0
src/views/xxlJob/dashboard/components/RaddarChart.vue

@@ -0,0 +1,121 @@
+<template>
+  <div :class="className" :style="{height:height,width:width}"/>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+require('echarts/theme/macarons') // echarts theme
+import { debounce } from '@/utils'
+
+const animationDuration = 3000
+
+export default {
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '300px'
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  mounted() {
+    this.initChart()
+    this.__resizeHanlder = debounce(() => {
+      if (this.chart) {
+        this.chart.resize()
+      }
+    }, 100)
+    window.addEventListener('resize', this.__resizeHanlder)
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    window.removeEventListener('resize', this.__resizeHanlder)
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+
+      this.chart.setOption({
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        radar: {
+          radius: '66%',
+          center: ['50%', '42%'],
+          splitNumber: 8,
+          splitArea: {
+            areaStyle: {
+              color: 'rgba(127,95,132,.3)',
+              opacity: 1,
+              shadowBlur: 45,
+              shadowColor: 'rgba(0,0,0,.5)',
+              shadowOffsetX: 0,
+              shadowOffsetY: 15
+            }
+          },
+          indicator: [
+            { name: 'Sales', max: 10000 },
+            { name: 'Administration', max: 20000 },
+            { name: 'Information Techology', max: 20000 },
+            { name: 'Customer Support', max: 20000 },
+            { name: 'Development', max: 20000 },
+            { name: 'Marketing', max: 20000 }
+          ]
+        },
+        legend: {
+          left: 'center',
+          bottom: '10',
+          data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']
+        },
+        series: [{
+          type: 'radar',
+          symbolSize: 0,
+          areaStyle: {
+            normal: {
+              shadowBlur: 13,
+              shadowColor: 'rgba(0,0,0,.2)',
+              shadowOffsetX: 0,
+              shadowOffsetY: 10,
+              opacity: 1
+            }
+          },
+          data: [
+            {
+              value: [5000, 7000, 12000, 11000, 15000, 14000],
+              name: 'Allocated Budget'
+            },
+            {
+              value: [4000, 9000, 15000, 15000, 13000, 11000],
+              name: 'Expected Spending'
+            },
+            {
+              value: [5500, 11000, 12000, 15000, 12000, 12000],
+              name: 'Actual Spending'
+            }
+          ],
+          animationDuration: animationDuration
+        }]
+      })
+    }
+  }
+}
+</script>

+ 174 - 0
src/views/xxlJob/dashboard/index.vue

@@ -0,0 +1,174 @@
+<template>
+  <div class="dashboard-editor-container">
+    <!-- 调度统计 -->
+    <panel-group :group-data="panelGroupData"/>
+
+    <el-row style="margin-bottom: 10px">
+      <el-col :span="12">
+        <div>调度报表</div>
+      </el-col>
+      <el-col :span="12" style="text-align: right">
+        <el-date-picker
+          size="small"
+          v-model="dateDuring"
+          type="datetimerange"
+          format="yyyy-MM-dd HH:mm:ss"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          align="right"
+          :default-time="['00:00:00','23:59:59']"
+        >
+        </el-date-picker>
+        <el-button type="primary" size="small" @click="getScheduleInfo">查询</el-button>
+      </el-col>
+    </el-row>
+
+    <el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
+      <!-- 折线图 -->
+      <el-col :span="16">
+        <div class="chart-wrapper">
+          <line-chart :chart-data="lineChartData"/>
+        </div>
+      </el-col>
+      <!-- 饼图 -->
+      <el-col :span="8">
+        <div class="chart-wrapper">
+          <pie-chart :chart-data="pieCharData"/>
+        </div>
+      </el-col>
+
+    </el-row>
+  </div>
+</template>
+
+<script>
+import PanelGroup from './components/PanelGroup'
+import LineChart from './components/LineChart'
+import PieChart from './components/PieChart'
+import { getChartInfo, getDashboardInfo } from '@/api/xxlJob/dashboard'
+
+export default {
+  name: 'DashboardAdmin',
+  components: {
+    PanelGroup,
+    LineChart,
+    PieChart
+  },
+  data() {
+    return {
+      lineChartData: {
+        successData: [],
+        failData: [],
+        runningData: [],
+        lineCharDates: []
+      },
+      pieCharData: {
+        jobSuccess: 0,
+        jobFail: 0,
+        jobRunning: 0
+      },
+      dateDuring: [],
+      panelGroupData: {
+        jbobLogCount: null,
+        jobInfoCount: null,
+        executorCount: null
+      }
+    }
+  },
+  beforeCreate() {
+
+  },
+  computed: {
+    // 默认时间
+    timeDefault() {
+      let date = new Date()
+      // 通过时间戳计算
+      let defalutStartTime = date.getTime() - 7 * 24 * 3600 * 1000 // 转化为时间戳
+      let defalutEndTime = date.getTime()
+      let startDateNs = new Date(defalutStartTime)
+      let endDateNs = new Date(defalutEndTime)
+      // 月,日 不够10补0
+      defalutStartTime = startDateNs.getFullYear() +
+        '-' + ((startDateNs.getMonth() + 1) >= 10 ? (startDateNs.getMonth() + 1) : '0' + (startDateNs.getMonth() + 1)) +
+        '-' + (startDateNs.getDate() >= 10 ? startDateNs.getDate() : '0' + startDateNs.getDate()) + ' 00:00:00'
+      defalutEndTime = endDateNs.getFullYear() + '-' +
+        ((endDateNs.getMonth() + 1) >= 10 ? (endDateNs.getMonth() + 1) : '0' + (endDateNs.getMonth() + 1)) + '-' +
+        (endDateNs.getDate() >= 10 ? endDateNs.getDate() : '0' + endDateNs.getDate()) + ' 23:59:59'
+      return [defalutStartTime, defalutEndTime]
+    }
+  },
+  created() {
+    this.dateDuring = this.timeDefault
+    this.getDashBoardInfo()
+    this.getScheduleInfo()
+  },
+  methods: {
+    getDashBoardInfo() {
+      getDashboardInfo().then(res => {
+        const {
+          code, content: {
+            jobInfoCount, jobLogCount, jobLogSuccessCount, executorCount
+          }
+        } = res
+        if (res.code === 200) {
+          this.panelGroupData.jobInfoCount = jobInfoCount
+          this.panelGroupData.jobLogCount = jobLogCount
+          this.panelGroupData.executorCount = executorCount
+        }
+      }).catch(function(err) {
+        console.log(err.message)
+      })
+    },
+
+    getScheduleInfo() {
+      const timeScope = {
+        startDate: this.dateDuring[0],
+        endDate: this.dateDuring[1]
+      }
+      // console.info("timeScope.startDate:" + JSON.stringify(timeScope))
+
+      getChartInfo(timeScope).then(res => {
+        const {
+          code, content: {
+            triggerCountRunningTotal, triggerCountSucTotal, triggerCountFailTotal,
+            triggerDayList, triggerDayCountRunningList, triggerDayCountFailList, triggerDayCountSucList
+          }
+        } = res
+        if (res.code === 200) {
+          // console.info("chart info data refresh")
+          this.lineChartData.lineCharDates = triggerDayList
+          this.lineChartData.successData = triggerDayCountSucList
+          this.lineChartData.failData = triggerDayCountFailList
+          this.lineChartData.runningData = triggerDayCountRunningList
+
+          this.pieCharData.jobRunning = triggerCountRunningTotal
+          this.pieCharData.jobSuccess = triggerCountSucTotal
+          this.pieCharData.jobFail = triggerCountFailTotal
+        }
+      }).catch(function(err) {
+        // console.log(err.message)
+      })
+      // console.info(JSON.stringify(this.pieCharData))
+    }
+  }
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" slot-scope>
+.dashboard-editor-container {
+  padding: 32px;
+  background-color: rgb(240, 242, 245);
+
+  .chart-wrapper {
+    background: #fff;
+    padding: 16px 16px 0;
+    margin-bottom: 32px;
+  }
+
+  .panel-group {
+    margin-top: 0px;
+  }
+}
+</style>

+ 347 - 0
src/views/xxlJob/executor/index.vue

@@ -0,0 +1,347 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container" style="margin-bottom:20px">
+      <span>AppName</span>
+      <el-input @keyup.enter.native="handleFilter" style="width: 200px;margin-left: 10px" class="filter-item"
+                placeholder="AppName" v-model="listQuery.appname" clearable>
+      </el-input>
+      <span style="margin-left:20px">名称</span>
+      <el-input @keyup.enter.native="handleFilter" style="width: 200px;margin-left: 10px" class="filter-item"
+                placeholder="名称" v-model="listQuery.title" clearable>
+      </el-input>
+      <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-search"
+                 @click="handleFilter">搜索
+      </el-button>
+      <el-button class="filter-item" style="margin-left: 10px;" @click="executorCreate" v-hasPermi="['xxl:job:group:add']"
+                 :type="pageBoolean.addColor" icon="el-icon-plus">{{ pageBoolean.addText }}
+      </el-button>
+    </div>
+
+    <el-table
+      ref='list'
+      :key='tableKey'
+      :data="list"
+      v-loading="listLoading"
+      element-loading-text="数据加载中,请稍后"
+      highlight-current-row
+      fit
+      select-on-indeterminate
+      style="width: 100%">
+      <el-table-column
+        label="序号"
+        type="index"
+        width="50">
+      </el-table-column>
+      <el-table-column align="center" label="AppName" min-width="100">
+        <template slot-scope="scope">
+          <span class="link-type">{{scope.row.appname}}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="100" align="center" label="名称" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span>{{scope.row.title}}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="60" align="center" label="注册方式" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span v-html="formatRegDesc(scope.row.addressType)"></span>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="120" align="center" label="OnLine机器地址" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span v-html="formatAddressList(scope.row.addressList)"></span>
+        </template>
+      </el-table-column>
+
+      <el-table-column align="center" label="操作" min-width="80">
+        <template slot-scope="scope">
+          <el-button size="small" type="primary" @click="executorModify(scope.row)" v-hasPermi="['xxl:job:group:update']" >编辑
+          </el-button>
+          <el-button
+            size="small"
+            type="danger"
+            @click="warnBeforeDelete(scope.$index, scope.row)"
+            v-hasPermi="['xxl:job:group:remove']"
+          >删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div v-show="!listLoading" class="pagination-container">
+      <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
+                     :current-page.sync="listQuery.currentPage"
+                     :page-sizes="[10,20,30,50]" :page-size="listQuery.length"
+                     layout="total, sizes, prev, pager, next, jumper" :total="total">
+      </el-pagination>
+    </div>
+
+    <el-dialog :title="dialogStatus=='create' ? '新增执行器' : '编辑执行器'" :visible.sync="dialogFormVisible" center>
+      <el-form :model="form" :rules="rules" ref="form">
+        <el-form-item label="AppName" label-width="120px" prop="appname">
+          <el-input v-model="form.appname" auto-complete="off" maxlength="180"
+                    placeholder="请输入AppName"></el-input>
+        </el-form-item>
+        <el-form-item label="名称" label-width="120px" prop="title">
+          <el-input v-model="form.title" auto-complete="off" maxlength="180"
+                    placeholder="请输入名称"></el-input>
+        </el-form-item>
+        <el-form-item label="注册方式" label-width="120px" prop="addressType">
+          <el-select v-model="form.addressType" placeholder="请选择">
+            <el-option v-for="item of addressTypeEnum"
+                       :key="item.code"
+                       :label="item.msg"
+                       :value="item.code"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="地址列表" label-width="120px" prop="addressList">
+          <el-input v-model="form.addressList" auto-complete="off" maxlength="180" type="textarea"
+                    placeholder="请输入执行器地址,多个地址用逗号分隔"></el-input>
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">取 消</el-button>
+        <el-button v-if="dialogStatus=='create'" type="primary" @click="addForm">确 定</el-button>
+        <el-button v-else type="primary" @click="updateForm">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import {create, getGroupList, remove, update} from "@/api/xxlJob/group";
+  import qs from 'qs';
+
+  export default {
+    name: 'list',
+    props: {},
+    components: {},
+    data() {
+      return {
+        addressTypeEnum: [{code: 0, msg: '自动注册'}, {code: 1, msg: "手动注册"}],
+        dialogStatus: 'create',
+        dialogFormVisible: false,
+        userTypeCode: null,
+        userTypes: [{code: 0, msg: '超级管理员'}, {code: 1, msg: '普通用户'}],
+
+        modifyData: '',
+        form: {
+          id: null, appname: '', title: '', addressType: null, addressList: ''
+        },
+        rules: {
+          appname: [{required: true, message: '请输入AppName', trigger: 'blur'}],
+          title: [{required: true, message: '请输入执行器名称', trigger: 'blur'}],
+          // order: [{required: true, message: '请输入排列顺序', trigger: 'blur'}],
+          addressType: [{required: true, message: '请选择注册类型', trigger: 'blur'}]
+        },
+        list: null,
+        total: 0,
+        listLoading: true,
+        listQuery: {
+          appname: '',
+          title: '',
+          start: 0,
+          length: 10,
+          currentPage: 1
+        },
+        tableKey: 0,
+        pageBoolean: {
+          delColor: 'primary',
+          delText: '批量删除',
+          addColor: 'success',
+          addText: '添加执行器'
+        },
+      }
+    },
+    created() {
+    },
+    mounted() {
+      this.fetchTableData()
+    },
+    watch: {},
+    methods: {
+      fetchTableData() {
+        getGroupList(qs.stringify(this.listQuery)).then(res => {
+          this.listLoading = false
+          const {data, recordsTotal} = res
+          this.list = data
+          this.total = recordsTotal
+          if (this.total === 0) {
+            this.$message({
+              message: '执行器列表为空',
+              type: 'warning'
+            })
+          }
+        }).catch(function (err) {
+          console.log(err)
+        })
+      },
+      executorCreate() {
+        this.form = {
+          id: null, appname: '', title: '', addressType: null, addressList: ''
+        }
+        this.dialogFormVisible = true
+        this.dialogStatus = 'create'
+      },
+      addForm() {
+        // console.info("addForm",this.form)
+        this.$refs['form'].validate((valid) => {
+          if (valid) {
+            create(this.form).then(res => {
+              if (res.code === 200) {
+                this.dialogFormVisible = false
+                this.$message({
+                  message: "新增成功",
+                  type: 'success'
+                })
+                this.handleRefresh()
+              } else {
+                this.$message(res.msg)
+              }
+              this.listLoading = false
+            }).catch(function (err) {
+              console.log(err.message)
+            })
+          } else {
+            return false;
+          }
+        });
+      },
+      executorModify(row) { // 修改执行器信息
+        this.dialogFormVisible = true
+        this.dialogStatus = 'update'
+        this.form = row
+      },
+      updateForm() {
+        this.$refs['form'].validate((valid) => {
+          if (valid) {
+            update(this.form).then(res => {
+              if (res.code === 200) {
+                this.dialogFormVisible = false
+                this.$message({
+                  message: "更新成功",
+                  type: 'success'
+                })
+              } else {
+                this.$message(res.msg)
+              }
+            }).catch(function (err) {
+              console.log(err.message)
+            })
+          } else {
+            return false;
+          }
+        });
+      },
+      /*modifyComplete(data) {
+        if (!data) return false
+        this.handleRefresh()
+        this.dialogFormVisible = false
+      },*/
+      warnBeforeDelete(idx, row) {
+        this.$confirm(
+          '确认删除当前执行器?',
+          '提示',
+          {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'error'
+          }
+        ).then(response => {
+          this.batchDelete(idx, row)
+        }).catch(reject => console.log(reject))
+      },
+      batchDelete(idx, row) {
+        remove(row.id).then(res => {
+            if (res.code === 200) {
+              this.$message({
+                message: '删除成功',
+                type: 'success',
+                duration: 2000
+              })
+              const index = this.list.indexOf(idx)
+              this.list.splice(idx, 1)
+              this.total--
+            } else {
+              this.$message({
+                message: '删除失败',
+                type: 'error',
+                duration: 2000
+              })
+            }
+          }
+        ).catch(function (err) {
+          console.log(err.message)
+        })
+      },
+
+      formatAddressList(addressList) {
+        if (addressList !== null && addressList !== '') {
+          let addressArr = addressList.split(",")
+          let showAddress = ''
+          addressArr.forEach(function (val) {
+            showAddress += val + '<br>'
+          })
+          return showAddress
+        }else{
+          return "无"
+        }
+      },
+      formatRegDesc(type){
+          switch(type){
+            case 0:
+              return "自动注册"
+            case 1:
+              return ""
+          }
+      },
+
+      handleFilter() {
+        // console.info("handlerFilter")
+        this.fetchTableData()
+      },
+      //每页数改变回调
+      handleSizeChange(val) {
+        this.listQuery.length = val
+        this.fetchTableData()
+      },
+      //翻页回调
+      handleCurrentChange(val) {
+        this.listQuery.start = (val - 1) * this.listQuery.length
+        this.fetchTableData()
+      },
+      handleRefresh() {
+        this.list = null
+        this.total = null
+        this.fetchTableData()
+      }
+    }
+  }
+</script>
+<style rel="stylesheet/scss" lang="scss" slot-scope>
+
+  .app-container {
+    padding: 20px;
+  }
+
+  .pagination-container {
+    margin-top: 30px;
+  }
+</style>
+<style rel="stylesheet/scss" lang="scss">
+  .dialogModifyLessonManage {
+    .el-dialog__header {
+      padding-bottom: 0;
+    }
+    .el-dialog__body {
+      padding: 0
+    }
+    .createPost-main-container {
+      padding: 20px 0 !important;
+    }
+  }
+</style>

+ 30 - 0
src/views/xxlJob/jobinfo/components/FormGroup.vue

@@ -0,0 +1,30 @@
+<template>
+  <div class="split-line">
+    <span style="font-size: large;line-height: 15px;">{{groupName}}</span>
+    <hr>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "formGroup",
+  props:{
+    groupName: {
+      type: String,
+      default: ''
+    }
+  }
+}
+</script>
+
+<style scoped>
+  hr{
+    background-color: #d0cdcd;
+    border: none;
+    height: 1px;
+    margin-top: 5px;
+  }
+  .split-line{
+    margin-top: 10px;
+  }
+</style>

+ 857 - 0
src/views/xxlJob/jobinfo/index.vue

@@ -0,0 +1,857 @@
+<template>
+  <div class="app-container">
+    <el-form :inline="true" size="small">
+      <el-form-item label="执行器" lable-width="auto">
+        <el-select v-model="tableQuery.jobGroup" placeholder="请选择" @change="groupChange" filterable>
+          <el-option
+            v-for="item in groupListData"
+            :key="item.id"
+            :label="item.title"
+            :value="item.id"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <!--          运行状态-->
+      <el-form-item label="" width="auto" class="triggerCondition">
+        <el-select v-model="tableQuery.triggerStatus" placeholder="请选择">
+          <el-option
+            v-for="(item) in triggerStatusEnum"
+            :key="item.val"
+            :label="item.title"
+            :value="item.val"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="">
+        <el-input v-model="tableQuery.jobDesc" placeholder="请输入任务描述" clearable></el-input>
+      </el-form-item>
+
+      <el-form-item label="">
+        <el-input v-model="tableQuery.executorHandler" placeholder="请输入JobHandler" clearable></el-input>
+      </el-form-item>
+      <el-form-item label="">
+        <el-input v-model="tableQuery.author" placeholder="请输入负责人" clearable></el-input>
+      </el-form-item>
+      <el-form-item class="searchBtn">
+        <el-button type="primary" @click="getJobList">查询</el-button>
+      </el-form-item>
+      <el-button class="filter-item" style="margin-left: 10px;" @click="jobCreate" size="small"
+                 v-hasPermi="['xxl:job:info:add']"
+                 :type="pageBoolean.addColor" icon="el-icon-plus"
+      >{{ pageBoolean.addText }}
+      </el-button>
+    </el-form>
+    <el-table
+      ref="multipleTable"
+      :data="jobList"
+      v-loading="listLoading"
+      element-loading-text="数据加载中,请稍后"
+      border
+      fit
+      :highlight-current-row="false"
+      style="width: 100%"
+    >
+      <el-table-column label="任务ID" align="center" min-width="30" prop="id">
+        <template slot-scope="scope"> {{ scope.row.id }}</template>
+      </el-table-column>
+      <el-table-column align="center" prop="jobDesc" label="任务描述" min-width="100">
+        <template slot-scope="scope"> {{ scope.row.jobDesc }}</template>
+      </el-table-column>
+      <el-table-column align="center" prop="gulueType" label="调度类型" min-width="100">
+        <template slot-scope="scope"> {{ scope.row.glueType }}: {{ scope.row.executorHandler }}</template>
+      </el-table-column>
+      <el-table-column align="center" prop="schedule" label="运行模式" min-width="60">
+        <template slot-scope="scope">{{ scope.row.scheduleType }}: {{ scope.row.scheduleConf }}</template>
+      </el-table-column>
+      <el-table-column align="center" prop="author" label="负责人" min-width="50">
+        <template slot-scope="scope"> {{ scope.row.author }}</template>
+      </el-table-column>
+      <el-table-column align="center" prop="triggerStatus" label="状态" min-width="40"
+                       :filters="[{ text: 'RUNNING', value: 1 }, { text: 'STOP', value: 0 }]"
+                       :filter-method="filterTriggerStatus"
+                       filter-placement="bottom-end"
+      >
+        <template slot-scope="scope">
+          <el-tag size="small"
+                  :type="scope.row.triggerStatus === 0 ? 'info' : 'success'"
+                  disable-transitions v-html="formatTriggerStatus(scope.row.triggerStatus)"
+          ></el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" prop="name" label="操作" show-overflow-tooltip min-width="60">
+        <template slot-scope="scope">
+          <el-dropdown size="small" trigger="click" split-button type="primary"
+                       @command="(command)=>{handleRowOp(command,scope.row)}"
+          >
+            操作
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item command="beforeRunOnce" v-hasPermi="['xxl:job:info:trigger']">执行一次</el-dropdown-item>
+              <el-dropdown-item command="regNode">注册节点</el-dropdown-item>
+              <el-dropdown-item v-if="scope.row.scheduleType == 'CRON' || scope.row.scheduleType == 'FIX_RATE'"
+                                command="nextTriggerTime"
+              >下次执行时间
+              </el-dropdown-item>
+              <hr>
+              <el-dropdown-item command="startOrStop" v-hasPermi="['xxl:job:info:start']" v-if="scope.row.triggerStatus === 0">
+                启动
+              </el-dropdown-item>
+              <el-dropdown-item command="startOrStop" v-hasPermi="['xxl:job:info:stop']" v-if="scope.row.triggerStatus === 1">
+                停止
+              </el-dropdown-item>
+              <el-dropdown-item command="editJob" v-hasPermi="['xxl:job:info:update']">编辑</el-dropdown-item>
+              <el-dropdown-item command="deleteJob" v-hasPermi="['xxl:job:info:remove']">删除</el-dropdown-item>
+              <el-dropdown-item command="copyJob">复制</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-row type="flex" justify="space-between" style="padding-top:20px;">
+      <el-col style="text-align:right;">
+        <el-pagination
+          background
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          :current-page.sync="tableQuery.currentPage"
+          :page-sizes="[10, 20, 30, 50]"
+          :page-size="tableQuery.length"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+        >
+        </el-pagination>
+      </el-col>
+    </el-row>
+
+    <!--    dialog    -->
+    <el-dialog width="60%" id="jobFormDialog" :title="formDialogType=='create' ? '新增任务' : '编辑任务'"
+               :visible.sync="dialogFormVisible" center
+    >
+      <el-form :model="jobForm" :rules="rules" ref="jobForm" label-width="100px" size="small">
+        <div>
+          <form-group group-name="基础配置"/>
+        </div>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="执行器" prop="jobGroup">
+              <el-select v-model="jobForm.jobGroup" placeholder="请选择">
+                <el-option v-for="item in groupListData"
+                           :key="item.id"
+                           :label="item.title"
+                           :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="负责人" prop="author">
+              <el-input v-model="jobForm.author" placeholder="请输入负责人"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="任务描述" prop="jobDesc">
+              <el-input v-model="jobForm.jobDesc" label="请输入任务描述"></el-input>
+            </el-form-item>
+            <el-form-item label="报警邮件" prop="alarmEmail">
+              <el-input v-model="jobForm.alarmEmail" label="请输入任务描述"></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <div>
+            <form-group group-name="调度配置"/>
+          </div>
+          <el-col :span="12">
+            <el-form-item label="调度类型" prop="glueType">
+              <el-select v-model="jobForm.scheduleType" placeholder="请选择">
+                <el-option v-for="item in scheduleTypeEnum"
+                           :key="item.val"
+                           :label="item.title"
+                           :value="item.val"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12" v-if="jobForm.scheduleType != 'NONE'">
+            <el-form-item label="cron表达式" prop="scheduleConf">
+              <el-input v-model="jobForm.scheduleConf" placeholder="请输入cron执行表达式">
+                <template slot="append">
+                  <el-button type="primary" @click="handleShowCron">
+                    生成
+                    <i class="el-icon-time el-icon--right"></i>
+                  </el-button>
+                </template>
+              </el-input>
+            </el-form-item>
+          </el-col>
+
+        </el-row>
+        <div>
+          <form-group group-name="任务配置"/>
+        </div>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="运行模式" prop="glueType">
+              <el-select v-model="jobForm.glueType" placeholder="请选择" :disabled="formDialogType=='update'">
+                <el-option v-for="item in glueTypeEnum"
+                           :key="item.val"
+                           :label="item.title"
+                           :value="item.val"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="JobHandler" prop="executorHandler">
+              <el-input v-model="jobForm.executorHandler" label="executorHandler"
+                        :disabled="jobForm.glueType != null && jobForm.glueType.search('GLUE') === 0"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col>
+            <el-form-item label="任务参数" prop="executorParam">
+              <el-input type="textarea" :rows="2" placeholder="请输入任务参数" v-model="jobForm.executorParam">
+              </el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <div class="chart-wrapper">
+          <form-group group-name="高级配置"/>
+        </div>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="路由策略" prop="misfireStrategy">
+              <el-select v-model="jobForm.executorRouteStrategy" placeholder="请选择">
+                <el-option v-for="item in routeStrategyEnum"
+                           :key="item.val"
+                           :label="item.title"
+                           :value="item.val"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="调度过期策略" prop="misfireStrategy">
+              <el-select v-model="jobForm.misfireStrategy" placeholder="请选择">
+                <el-option v-for="item in failStrategyEnum"
+                           :key="item.val"
+                           :label="item.title"
+                           :value="item.val"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="任务超时时间" prop="executorTimeout">
+              <el-input v-model.number="jobForm.executorTimeout" placeholder="请输入超时时间,单位秒,大于零时生效"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="子任务ID" prop="childJobId">
+              <el-input v-model="jobForm.childJobId" placeholder="请输入子任务的ID,如果存在多个则逗号分隔"></el-input>
+            </el-form-item>
+            <el-form-item label="阻塞处理策略" prop="executorBlockStrategy">
+              <el-select v-model="jobForm.executorBlockStrategy" placeholder="请选择">
+                <el-option v-for="item in blockStrategyEnum"
+                           :key="item.val"
+                           :label="item.title"
+                           :value="item.val"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="失败重试次数" prop="executorFailRetryCount">
+              <el-input v-model.number="jobForm.executorFailRetryCount" placeholder="重试次数,大于零时生效"></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false" size="small">取 消</el-button>
+        <el-button v-if="formDialogType=='create'" type="primary" @click="executorAddForm" size="small">新 增</el-button>
+        <el-button v-else type="primary" @click="executorUpdateForm" size="small">更 新</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="Cron表达式生成器" :visible.sync="openCron" append-to-body destroy-on-close class="scrollbar">
+      <crontab @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
+    </el-dialog>
+
+    <el-dialog width="50%" id="runOnceDialog" title="执行一次" :visible.sync="runOnceDialogVisible" center>
+      <el-form :model="runOnceForm" ref="runOnceForm" label-width="80px" size="small">
+        <el-row>
+          <el-col :span="22">
+            <el-form-item label="任务参数" prop="executorParam">
+              <el-input type="textarea" :rows="2" placeholder="请输入任务参数" v-model="runOnceForm.executorParam">
+              </el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="22">
+            <el-form-item label="机器地址" prop="addressList">
+              <el-input type="textarea" :rows="2" placeholder="请输入任务参数" v-model="runOnceForm.addressList">
+              </el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="runOnce">执 行</el-button>
+        <el-button @click="runOnceDialogVisible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+
+<script>
+import { getGroupList, groupDetail } from '@/api/xxlJob/group'
+import FormGroup from './components/FormGroup'
+
+import {
+  create,
+  getJobPageList,
+  jobStart,
+  jobStop,
+  jobTrigger,
+  nextTriggerTime,
+  remove,
+  update
+} from '@/api/xxlJob/jobinfo'
+import Crontab from '@/components/Crontab'
+
+export default {
+  name: 'JobsList',
+  components: {
+    FormGroup, Crontab
+  },
+  watch: {},
+  data() {
+    return {
+      openCron: false,
+      expression: '',
+      formDialogType: '',
+      groupListData: [],
+      //禁用jobhandler,仅在
+      disableJobHandler: false,
+      //运行模式枚举
+      glueTypeEnum: [{ val: 'BEAN', title: 'BEAN' }],
+      //调度模式枚举
+      scheduleTypeEnum: [{ val: 'NONE', title: '无' }, { val: 'CRON', title: 'CRON' }, {
+        val: 'FIX_RATE',
+        title: '固定速度'
+      }],
+      routeStrategyEnum: [{ val: 'FIRST', title: '第一个' }, { val: 'LAST', title: '最后一个' },
+        { val: 'ROUND', title: '轮询' }, { val: 'RANDOM', title: '随机' },
+        { val: 'CONSISTENT_HASH', title: '一致性HASH' }, { val: 'LEAST_FREQUENTLY_USED', title: '最不经常使用' },
+        { val: 'LEAST_RECENTLY_USED', title: '最近最久未使用' }, { val: 'FAILOVER', title: '故障转移' },
+        { val: 'BUSYOVER', title: '忙碌转移' }, { val: 'SHARDING_BROADCAST', title: '分片广播' }],
+      blockStrategyEnum: [{ val: 'SERIAL_EXECUTION', title: '单机串行' }, { val: 'DISCARD_LATER', title: '丢弃后续调度' },
+        { val: 'COVER_EARLY', title: '覆盖之前调度' }],
+      failStrategyEnum: [{ val: 'DO_NOTHING', title: '忽略' }, { val: 'FIRE_ONCE_NOW', title: '立即执行一次' }],
+      triggerStatusEnum: [{ val: -1, title: '全部' }, { val: 1, title: '启动' }, { val: 0, title: '停止' }],
+      total: null,
+      jobList: null,
+      listLoading: true,
+
+      dialogFormVisible: false,
+      runOnceDialogVisible: false,
+
+      tableQuery: {
+        jobGroup: null,
+        triggerStatus: -1,
+        jobDesc: '',
+        executorHandler: '',
+        author: '',
+        length: 10,
+        start: 0,
+        currentPage: 1
+      },
+      //执行器查询
+      groupQuery: {
+        title: '',
+        start: 0,
+        length: 100
+      },
+      jobForm: {
+        id: null,
+        //执行器
+        jobGroup: null,
+        //负责人
+        author: '',
+        //任务描述
+        jobDesc: '',
+        alarmEmail: '',
+        //调度类型
+        scheduleType: '',
+        scheduleConf: '',
+        //运行模式
+        glueType: null,
+        //jobHandler
+        executorHandler: '',
+        //任务参数
+        executorParam: '',
+        //路由策略
+        executorRouteStrategy: '',
+        //调度过期策略
+        misfireStrategy: '',
+        //任务超时时间
+        executorTimeout: null,
+        childJobId: null,
+        //阻塞处理策略
+        executorBlockStrategy: '',
+        //失败重试次数
+        executorFailRetryCount: null
+      },
+      rules: {
+        jobGroup: [{ required: true, message: '请选择执行器', trigger: 'blur' }],
+        author: [{ required: true, message: '请输入负责人', trigger: 'blur' }],
+        jobDesc: [{ required: true, message: '请输入任务描述', trigger: 'blur' }],
+
+        scheduleType: [{ required: true, message: '请选择调度类型', trigger: 'blur' }],
+        scheduleConf: [{ required: true, message: '请输入Cron表达式', trigger: 'blur' }],
+
+        glueType: [{ required: true, message: '请选择运行模式', trigger: 'blur' }],
+        executorHandler: [{ required: true, message: '请输入执行器BEAN名称', trigger: 'blur' }],
+
+        misfireStrategy: [{ required: false, message: '请选择失败策略', trigger: 'blur' }],
+        alarmEmail: [{ required: false, message: '请输入邮箱地址', trigger: 'blur' }, {
+          type: 'email',
+          message: '请输入正确的邮箱地址',
+          trigger: ['blur', 'change']
+        }]
+      },
+      runOnceForm: {
+        id: null,
+        //任务参数
+        executorParam: '',
+        addressList: ''
+      },
+
+      registerAddrList: [],
+      pageBoolean: {
+        delColor: 'primary',
+        delText: '批量删除',
+        addColor: 'success',
+        addText: '添加任务'
+      },
+      operateText: ['执行', '暂停', '删除']
+    }
+  },
+  mounted() {
+    // this.initEnums()
+    this.fetchGroupList()
+  },
+  methods: {
+    /** cron表达式按钮操作 */
+    handleShowCron() {
+      this.expression = this.jobForm.scheduleConf
+      this.openCron = true
+    },
+    /** 确定后回传值 */
+    crontabFill(value) {
+      this.jobForm.scheduleConf = value
+    },
+    //获取执行器
+    fetchGroupList() {
+      getGroupList(this.groupQuery).then(res => {
+        this.$nextTick(() => {
+          this.groupListData = res.data
+          if (res.recordsTotal === 0) {
+            this.$message({
+              message: '执行器列表为空,需要先创建执行器',
+              type: 'warning'
+            })
+            return
+          }
+          this.tableQuery.jobGroup = res.data[0].id
+          this.jobForm.jobGroup = res.data[0].id
+          //回调获取job
+          this.getJobList()
+        })
+      }).catch(function(err) {
+        console.log(err)
+      })
+    },
+    //任务列表
+    getJobList() {
+      this.listLoading = false
+      getJobPageList(this.tableQuery).then(res => {
+        const { data, recordsTotal } = res
+        this.total = recordsTotal
+        this.jobList = data
+        this.listLoading = false
+      }).catch(reject => {
+        this.listLoading = false
+      })
+    },
+    refreshJobList() {
+      this.tableQuery.start = 0
+      this.tableQuery.currentPage = 1
+      this.getJobList()
+    },
+    //操作按钮处理函数,根据操作类型再进行分发
+    handleRowOp(op, row) {
+      switch (op) {
+        case 'jobCreate': {
+          this.jobCreate()
+          break
+        }
+        case 'editJob': {
+          this.editJob(row)
+          break
+        }
+        case 'beforeRunOnce': {
+          this.beforeRunOnce(row)
+          break
+        }
+        case 'regNode': {
+          this.regNode(row.id)
+          break
+        }
+        case 'nextTriggerTime': {
+          this.nextTriggerTime(row.scheduleType, row.scheduleConf)
+          break
+        }
+        case 'startOrStop': {
+          this.startOrStop(row)
+          break
+        }
+        case 'deleteJob': {
+          this.deleteJob(row.id)
+          break
+        }
+        case 'copyJob': {
+          this.copyJob(row)
+          break
+        }
+      }
+    },
+    jobCreate() {
+      this.jobForm = {
+        id: null,
+        jobGroup: null,
+        executorRouteStrategy: 'RANDOM',
+        scheduleType: 'CRON',
+        glueType: 'BEAN',
+        executorParam: '',
+        executorBlockStrategy: 'SERIAL_EXECUTION',
+        executorTimeout: 0,
+        author: '',
+        jobDesc: '',
+        scheduleConf: '',
+        executorHandler: '',
+        childJobId: null,
+        misfireStrategy: 'DO_NOTHING',
+        alarmEmail: ''
+      }
+      this.dialogFormVisible = true
+      this.formDialogType = 'create'
+    },
+    //新增任务
+    executorAddForm() {
+      this.$refs['jobForm'].validate((valid) => {
+        if (valid) {
+          delete this.jobForm.id
+          create(this.jobForm).then(res => {
+            if (res.code === 200) {
+              this.dialogFormVisible = false
+              this.$message({
+                message: '新增成功',
+                type: 'success'
+              })
+              this.refreshJobList()
+            } else {
+              this.$message(res.msg)
+            }
+          }).catch(function(err) {
+            console.log(err.message)
+          })
+        } else {
+          return false
+        }
+      })
+    },
+    editJob(row) {
+      // console.info('编辑任务',row)
+      this.dialogFormVisible = true
+      this.formDialogType = 'update'
+      this.jobForm = row
+      delete this.jobForm.addTime
+      delete this.jobForm.glueUpdatetime
+      delete this.jobForm.updateTime
+      //删除调度状态
+      delete this.jobForm.triggerStatus
+      //清除创建时产生的校验提示
+      this.resetForm('jobForm')
+    },
+    //修改任务
+    executorUpdateForm() {
+      this.$refs['jobForm'].validate((valid) => {
+        if (valid) {
+          if (this.jobForm.executorFailRetryCount == null) {
+            this.jobForm.executorFailRetryCount = 0
+          }
+          if (this.jobForm.executorTimeout == null) {
+            this.jobForm.executorTimeout = 0
+          }
+          update(this.jobForm).then(res => {
+            if (res.code === 200) {
+              this.dialogFormVisible = false
+              this.$message({
+                message: '更新成功',
+                type: 'success'
+
+              })
+              this.refreshJobList()
+            } else {
+              this.$message(res.msg)
+            }
+          }).catch(function(err) {
+            console.log(err.message)
+          })
+        } else {
+          return false
+        }
+      })
+    },
+    beforeRunOnce(row) {
+      this.runOnceForm.id = row.id
+      this.runOnceForm.executorParam = row.executorParam
+      this.runOnceDialogVisible = true
+    },
+    runOnce() {
+      jobTrigger(this.runOnceForm).then(res => {
+        if (res.code === 200) {
+          this.$message({
+            message: '触发成功',
+            type: 'success'
+          })
+        } else {
+          this.$message(res.msg)
+        }
+      }).catch(function(err) {
+        this.$message(err)
+      })
+      this.runOnceDialogVisible = false
+    },
+    regNode(id) {
+      groupDetail(id).then(res => {
+        let innerHtml = ''
+        if (res.code === 200) {
+          let registryList = res.content.registryList
+          for (const index in registryList) {
+            innerHtml = innerHtml + '<span class="onlineNode">' + (Number(index) + 1) + '. ' + registryList[index] + '</span>'
+          }
+        }
+        this.$alert(innerHtml, '注册节点', {
+          dangerouslyUseHTMLString: true
+        })
+      })
+    },
+    nextTriggerTime(scheduleType, scheduleConf) {
+      nextTriggerTime(scheduleType, scheduleConf).then(res => {
+        let innerHtml = ''
+        if (res.code === 200) {
+          let triggerTimeList = res.content
+          for (const index in triggerTimeList) {
+            innerHtml = innerHtml + '<span class="triggerTimeList">' + triggerTimeList[index] + '</span></br>'
+          }
+        }
+        this.$alert(innerHtml, '下次执行时间', {
+          dangerouslyUseHTMLString: true,
+          center: true
+        })
+      })
+    },
+    startOrStop(row) {
+      let status = row.triggerStatus
+      let statusDesc = status === 1 ? '停止' : '启动'
+      //当前运行,暂停
+      this.$confirm('确认' + statusDesc + '该任务?', '提示',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'info'
+        }
+      ).then(res => {
+        if (status === 1) {
+          jobStop(row.id).then(res => {
+            if (res.code === 200) {
+              this.$message({ message: '操作成功', type: 'success' })
+              this.refreshJobList()
+            } else {
+              this.$message({ message: res.msg, type: 'error' })
+            }
+          })
+        } else if (status === 0) {
+          jobStart(row.id).then(res => {
+            if (res.code === 200) {
+              this.$message({ message: '操作成功', type: 'success' })
+              this.refreshJobList()
+            } else {
+              this.$message({ message: res.msg, type: 'error' })
+            }
+          })
+        }
+      }).catch(reject => console.log(reject))
+    },
+    deleteJob(id) {
+      this.$confirm('确认删除该任务?', '提示',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warn'
+        }
+      ).then(res => {
+        remove(id).then(res => {
+          if (res.code === 200) {
+            this.$message({
+              message: '删除成功',
+              type: 'success'
+            })
+            this.refreshJobList()
+          } else {
+            this.$message({
+              message: res.msg,
+              type: 'error'
+            })
+          }
+        }).catch(function(err) {
+          this.$message(err)
+        })
+      }).catch(reject => console.log(reject))
+    },
+    copyJob(row) {
+      this.jobForm = row
+      delete this.jobForm.addTime
+      delete this.jobForm.updateTime
+      delete this.jobForm.glueUpdatetime
+      delete this.jobForm.triggerLastTime
+      delete this.jobForm.triggerNextTime
+      delete this.jobForm.glueSource
+      delete this.jobForm.glueRemark
+      this.formDialogType = 'create'
+      this.dialogFormVisible = true
+    },
+
+    filterTriggerStatus(value, row) {
+      return row.triggerStatus === value
+    },
+    formatTriggerStatus(status) {
+      return status === 1 ? 'RUNNING' : 'STOP'
+    },
+    handleSizeChange(val) {
+      this.tableQuery.length = val
+      this.tableQuery.start = 0
+      this.getJobList()
+    },
+    handleCurrentChange(val) {
+      this.tableQuery.start = (val - 1) * this.tableQuery.length
+      this.tableQuery.currentPage = val
+      this.getJobList()
+    },
+    changeCronHandle(val) {
+      this.jobForm.scheduleConf = val
+    },
+    groupChange(item) {
+      this.tableQuery.jobGroup = item
+      this.jobForm.jobGroup = item
+      //触发查询
+      // this.refreshJobList()
+    }
+  },
+  computed: {}
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" slot-scope>
+.top {
+  padding: 10px;
+}
+
+.fitersLine {
+  .el-form-item__content {
+    margin-left: 0 !important;
+    width: 130px !important;
+  }
+
+  .el-form-item__label {
+    min-width: auto !important;
+  }
+
+  .searchBtn {
+    width: 60px !important;
+  }
+}
+
+.el-dialog--center .el-dialog__body {
+  padding: 0px 25px 0px;
+}
+
+.triggerCondition {
+  .el-form-item__content {
+    width: 100px !important;
+  }
+}
+
+.el-table .cell {
+  line-height: 1;
+  padding: 0 3px;
+}
+
+.el-dialog {
+  max-width: 642px;
+}
+
+.onlineNode {
+  background-color: springgreen;
+  border-radius: 10px;
+  padding: 3px 10px;
+}
+
+.triggerTimeList {
+  margin-left: 20%;
+}
+</style>
+<style rel="stylesheet/scss" lang="scss">
+.el-button--mini {
+  padding: 7px 10px;
+}
+
+.el-button + .el-button {
+  margin-left: 1px;
+}
+
+.el-upload {
+  text-align: left
+}
+
+
+.el-dialog__header {
+  padding-bottom: 0;
+}
+
+.el-steps--simple {
+  margin: 10px 0;
+}
+
+.border-none {
+  input {
+    border: none;
+    padding: 0;
+    text-align: center;
+
+    &:focus {
+      border: 1px solid #dcdfe6
+    }
+  }
+}
+
+.el-table--enable-row-hover {
+  .el-table__body {
+    tr:hover > td {
+      background: transparent;
+    }
+  }
+}
+
+</style>

+ 660 - 0
src/views/xxlJob/logs/index.vue

@@ -0,0 +1,660 @@
+<template>
+  <div class="app-container">
+    <el-form :inline="true">
+      <el-form-item label="执行器" prop="请选择执行器">
+        <el-select size="small" v-model="queryParams.jobGroup" @change="groupChange" filterable
+                   placeholder="请选择执行器"
+        >
+          <el-option value="0" key="0" label="全部"></el-option>
+          <el-option
+            v-for="item in groupListData"
+            :key="item.id"
+            :label="item.title"
+            :value="item.id"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="任务" prop="请选择任务">
+        <el-select size="small" v-model="queryParams.jobId" placeholder="请选择任务" clearable>
+          <el-option key="0" value="0" label="全部"></el-option>
+          <el-option
+            v-for="item in jobs"
+            :key="item.id"
+            :label="item.jobDesc"
+            :value="item.id"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="日志状态" prop="请选择日志状态">
+        <el-select size="small" v-model="queryParams.logStatus" placeholder="请选择日志状态">
+          <el-option
+            v-for="item in statusEnum"
+            :key="item.code"
+            :label="item.msg"
+            :value="item.code"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-date-picker
+          size="small"
+          v-model="queryParams.timeScope"
+          type="datetimerange"
+          range-separator="~"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          editable
+          clearable
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :picker-options="pickerOptions"
+        >
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" size="small" @click="getLogList">查询</el-button>
+        <el-button type="primary" size="small" @click="clearJobLogs" v-hasPermi="['xxl:job:log:remove']">清除日志</el-button>
+      </el-form-item>
+    </el-form>
+    <el-table
+      ref="list"
+      :key="tableKey"
+      :data="logTableList"
+      v-loading="listLoading"
+      element-loading-text="数据加载中,请稍后"
+      border
+      fit
+      highlight-current-row
+      select-on-indeterminate
+      style="width: 100%"
+    >
+
+      <el-table-column align="center" label="任务ID" min-width="60">
+        <template slot-scope="scope">
+          <el-button type="text" @click="jobDetail(scope.row)">{{ scope.row.jobId }}</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="jobHandler" min-width="80">
+        <template slot-scope="scope">
+          <span class="link-type">{{ scope.row.executorHandler }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="调度时间" min-width="120">
+        <template slot-scope="scope">
+          <span class="link-type">{{ parseTime(scope.row.triggerTime) }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="40" align="center" label="调度结果" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span :class="{'sucColor':scope.row.triggerCode===200,'failColor':scope.row.triggerCode===500}">
+            {{ scope.row.triggerCode === 200 ? '成功' : (scope.row.triggerCode === 500 ? '失败' : '') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column min-width="40" align="center" label="调度备注" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <el-button type="text" @click="triggerMsgDetail(scope.row)">查看</el-button>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="100" align="center" label="执行时间" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.handleTime) }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column min-width="40" align="center" label="执行结果" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span :class="{'sucColor':scope.row.handleCode===200,'failColor':scope.row.handleCode===500}">
+            {{ scope.row.handleCode === 200 ? '成功' : (scope.row.handleCode === 500 ? '失败' : '') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column min-width="40" align="center" label="执行备注" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span>{{ scope.row.handleMsg === '' ? '无' : scope.row.handleMsg }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="操作" min-width="60">
+        <template slot-scope="scope">
+          <el-button size="mini" type="primary" v-show="scope.row.triggerCode===200" @click="exeLogDetail(scope.row)">
+            日志详情
+          </el-button>
+          <el-button size="mini" type="danger" v-show="scope.row.triggerCode===200 && scope.row.handleCode===0"
+                     v-hasPermi="['xxl:job:log:kill']"
+                     @click="stopJob(scope.row.id)"
+          >终止
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div v-show="!listLoading" class="pagination-container">
+      <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
+                     :current-page.sync="queryParams.currentPage"
+                     :page-sizes="[10,20,30,50]" :page-size="queryParams.length"
+                     layout="total, sizes, prev, pager, next, jumper" :total="total"
+      >
+      </el-pagination>
+    </div>
+
+    <el-dialog class="dialog-detail-view" title="执行日志" custom-class="detail-dialog" width="60%"
+               :visible.sync="dialogFormVisible" center @close="onCloseDetail"
+    >
+      <pre v-html="jobLogDetail.logContent"></pre>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="dialogFormVisible = false">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="清除执行器日志" :visible.sync="clearLogDialog" center>
+      <el-form :model="clearForm" :rules="rules" ref="clearForm">
+        <el-form-item class="clear-log-form-view" label="执行器" prop="jobGroup">
+          <el-select v-model="clearForm.jobGroup" @change="groupClearChange" placeholder="请选择执行器">
+            <el-option value="0" key="0" label="全部"></el-option>
+            <el-option
+              v-for="item in groupListData"
+              :key="item.id"
+              :label="item.title"
+              :value="item.id"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item class="clear-log-form-view" label="任务" prop="jobId" clearable>
+          <el-select v-model="clearForm.jobId" placeholder="请选择任务">
+            <el-option value="0" key="0" label="全部"></el-option>
+            <el-option
+              v-for="item in clearJobs"
+              :key="item.id"
+              :label="item.jobDesc"
+              :value="item.id"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item class="clear-log-form-view" label="清理方式" prop="type">
+          <el-select v-model="clearForm.type" placeholder="请选择清理方式">
+            <el-option
+              v-for="item in logTimeEnum"
+              :key="item.code"
+              :label="item.msg"
+              :value="item.code"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="clearLogDialog = false">取 消</el-button>
+        <el-button type="primary" @click="exeClearLogs">确 定</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import { getGroupList } from '@/api/xxlJob/group'
+import { clearLogs, getJobDetail, getLogsList, stopJob, getJobsByGroup } from '@/api/xxlJob/logs'
+import qs from 'qs'
+import { formartUstTime } from '@/utils'
+
+export default {
+  name: 'LogList',
+  props: {},
+  components: {},
+  data() {
+    return {
+      statusEnum: [{ code: -1, msg: '全部' }, { code: 1, msg: '成功' }, { code: 2, msg: '失败' }, { code: 3, msg: '进行中' }],
+      groupListData: [],
+      jobs: [],
+      clearJobs: [],
+      dialogStatus: 'create',
+      dialogFormVisible: false,
+      clearLogDialog: false,
+      modifyData: '',
+      logTableList: null,
+      total: null,
+      listLoading: true,
+      queryParams: {
+        currentPage: 1,
+        jobGroup: '0',
+        jobId: '0',
+        start: 0,
+        length: 10,
+        logStatus: -1,
+        filterTime: '', //[new Date(2000, 10, 10, 10, 10), new Date(2000, 10, 11, 10, 10)],
+        timeScope: []
+      },
+      tableKey: 0,
+      // 刷新定时器
+      detailTailTimer: null,
+      jobLogDetail: {
+        logContent: ''
+      },
+      logTimeEnum: [{ code: 1, msg: '一个月前' }, { code: 2, msg: '三个月前' }, { code: 3, msg: '六个月前' }, {
+        code: 4,
+        msg: '一年之前'
+      },
+        { code: 5, msg: '一千条之前' }, { code: 6, msg: '一万条之前' }, { code: 7, msg: '三万条之前' }, {
+          code: 8,
+          msg: '十万条之前'
+        }, { code: 9, msg: '所有' }],
+      clearForm: {
+        jobGroup: '0',
+        jobId: '0',
+        // logStatus: null,
+        type: 1
+      },
+      rules: {
+        groupListData: [{ required: true, message: '请选择执行器', trigger: 'blur' }],
+        jobId: [{ required: true, message: '请选择任务', trigger: 'blur' }],
+        type: [{ required: true, message: '请选择时间段', trigger: 'blur' }]
+      }, pickerOptions: {
+        shortcuts: [{
+          text: '今日',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setHours(0)
+            start.setMinutes(0)
+            start.setSeconds(0)
+
+            end.setDate(end.getDate() + 1)
+            end.setHours(0)
+            end.setMinutes(0)
+            end.setSeconds(0)
+            end.setTime(end.getTime() - 1000)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            start.setHours(0)
+            start.setMinutes(0)
+            start.setSeconds(0)
+
+            end.setDate(end.getDate() + 1)
+            end.setHours(0)
+            end.setMinutes(0)
+            end.setSeconds(0)
+            end.setTime(end.getTime() - 1000)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近一个月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            start.setHours(0)
+            start.setMinutes(0)
+            start.setSeconds(0)
+            end.setDate(end.getDate() + 1)
+            end.setHours(0)
+            end.setMinutes(0)
+            end.setSeconds(0)
+            end.setTime(end.getTime() - 1000)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近三个月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
+            start.setHours(0)
+            start.setMinutes(0)
+            start.setSeconds(0)
+            end.setDate(end.getDate() + 1)
+            end.setHours(0)
+            end.setMinutes(0)
+            end.setSeconds(0)
+            end.setTime(end.getTime() - 1000)
+            picker.$emit('pick', [start, end])
+          }
+        }]
+      }
+
+    }
+  },
+  created() {
+  },
+  mounted() {
+    this.getLogList()
+    this.fetchGroupList()
+  },
+  watch: {
+    'jobLogDetail.logContent': function(val, oldVal) {
+      const reg = RegExp('.*xxl-job job callback finish.\\n$')
+      if (val.match(reg) && this.detailTailTimer != null) {
+        console.info('clean interval ', this.detailTailTimer)
+        clearInterval(this.detailTailTimer)
+        this.detailTailTimer = null
+      }
+    }
+  },
+  methods: {
+    getLogList() {
+      //对象深拷贝,去除属性
+      let params = JSON.parse(JSON.stringify(this.queryParams))
+      if (params.timeScope.length > 0) {
+        params.filterTime = params.timeScope[0] + ' - ' + params.timeScope[1]
+        params.timeScope = null
+        params.currentPage = null
+      }
+      getLogsList(params).then(res => {
+        const { data, recordsTotal } = res
+        this.listLoading = false
+        this.logTableList = data
+        this.total = recordsTotal
+        if (this.total === 0) {
+          this.$message({
+            message: '检索结果为空',
+            type: 'warning'
+          })
+        }
+      }).catch(function(err) {
+        console.log(err)
+      })
+    },
+    //获取执行器
+    fetchGroupList() {
+      getGroupList(qs.stringify(this.groupQuery)).then(res => {
+          const { data, recordsTotal } = res
+          this.groupListData = data
+          if (recordsTotal === 0) {
+            this.$message({
+              message: '执行器列表为空,需要先创建执行器',
+              type: 'warning'
+            })
+            return
+          }
+      }).catch(function(err) {
+        console.log(err)
+      })
+    },
+    groupChange() {
+      this.queryParams.jobId = '0'
+      if (this.queryParams.jobGroup !== '0') {
+        getJobsByGroup(this.queryParams.jobGroup).then(res => {
+          const { code, content } = res
+          if (res.code === 200) {
+            this.jobs = content
+          }
+        }).catch(function(err) {
+          console.log(err.message)
+        })
+      }
+    },
+    groupClearChange() {
+      this.clearForm.jobId = '0'
+      if (this.clearForm.jobGroup !== null) {
+        getJobsByGroup(this.clearForm.jobGroup).then(res => {
+          const { code, content } = res
+          if (res.code === 200) {
+            this.clearJobs = content
+          }
+        }).catch(function(err) {
+          console.log(err.message)
+        })
+      }
+    },
+    stopJob(id) {
+      stopJob(id).then(res => {
+        const { code, msg } = res
+        if (code === 200) {
+          this.$message({
+            message: '终止成功',
+            type: 'success'
+          })
+        } else {
+          this.$message({
+            message: '终止失败',
+            type: 'warning'
+          })
+        }
+        this.getLogList();
+      }).catch(function(err) {
+        console.log(err.message)
+      })
+    },
+    exeLogDetail(row) {
+      let params = {
+        'executorAddress': row.executorAddress,
+        'triggerTime': new Date(row.triggerTime).getTime(),
+        'logId': row.id,
+        'fromLineNum': 1
+      }
+      this.logTail(params)
+      this.detailTailTimer = setInterval(() => {
+        getJobDetail(params).then(res => {
+          this.jobLogDetail = res.content
+          if (res.code === 200) {
+            this.dialogFormVisible = true
+          } else {
+            this.$message({
+              message: '查询失败',
+              type: 'warning'
+            })
+          }
+        }).catch(function(err) {
+          console.log(err.message)
+        })
+      }, 2000)
+      console.info(this.detailTailTimer)
+    },
+    logTail(params) {
+      getJobDetail(params).then(res => {
+        this.jobLogDetail = res.content
+        if (res.code === 200) {
+          this.dialogFormVisible = true
+        } else {
+          this.$message({
+            message: '查询失败',
+            type: 'warning'
+          })
+        }
+      }).catch(function(err) {
+        console.log(err.message)
+      })
+    },
+    onCloseDetail() {
+      if (this.detailTailTimer != null) {
+        this.onCloseDetail = null
+        clearInterval(this.detailTailTimer)
+      }
+    },
+    clearJobLogs() {
+      this.clearLogDialog = true
+    },
+    exeClearLogs() {
+      this.$refs['clearForm'].validate((valid) => {
+        if (valid) {
+          clearLogs(this.clearForm).then(res => {
+            if (res.code === 200) {
+              this.$message({
+                message: '清除成功',
+                type: 'success'
+              })
+              this.clearLogDialog = false
+              this.handleRefresh()
+            } else {
+              this.$message({
+                message: '清除失败',
+                type: 'warning'
+              })
+            }
+          }).catch(function(err) {
+            console.log(err.message())
+          })
+        }
+      })
+    },
+    handleFilter() {
+      this.queryParams.start = 1
+      this.getLogList()
+    },
+    handleSizeChange(val) {
+      this.queryParams.length = val
+      this.getLogList()
+    },
+    handleCurrentChange(val) {
+      this.queryParams.start = (val - 1) * this.queryParams.length
+      this.getLogList()
+    },
+    handleRefresh() {
+      this.list = null
+      this.total = null
+      this.queryParams.start = 0
+      this.getLogList()
+    },
+    triggerMsgDetail(row) {
+      this.$alert(row.triggerMsg, '调度备注', {
+        dangerouslyUseHTMLString: true,
+        closeOnPressEscape: true
+      }).catch(() => {
+      })
+    },
+    jobDetail(row) {
+      let desc = '执行器地址:' + row.executorAddress + '<br>' +
+        'JobHandler:' + row.executorHandler + '<br>' +
+        '任务参数:' + row.executorParam
+      this.$alert(desc, '任务描述', {
+        dangerouslyUseHTMLString: true,
+        closeOnPressEscape: true
+      }).catch(() => {
+      })
+    },
+    formartHandlerTime(time) {
+      console.log('=========')
+      console.log(time)
+      if (time == null || time === undefined) {
+        return '-'
+      }
+      return formartUstTime(time)
+    }
+  }
+}
+</script>
+<style rel="stylesheet/scss" lang="scss" slot-scope>
+.app-container {
+  padding: 20px;
+}
+
+.pagination-container {
+  margin-top: 30px;
+}
+
+.fitersLine {
+  .el-select .el-input {
+    width: 135px;
+  }
+}
+
+.detail-dialog {
+  pre {
+    overflow: auto;
+    word-break: break-all;
+    word-wrap: break-word;
+    display: block;
+    font-size: 12px;
+    font-weight: 400;
+  }
+}
+</style>
+<style rel="stylesheet/scss" lang="scss">
+.clear-log-form-view {
+  .el-select {
+    width: 80%;
+  }
+}
+
+.sucColor {
+  color: seagreen;
+}
+
+.failColor {
+  color: #f56c6c;
+}
+
+.top {
+  padding: 10px;
+}
+
+.el-form--inline .el-form-item {
+  margin-right: 2px;
+}
+
+.drop-down-view {
+  .el-dropdown {
+    vertical-align: top;
+  }
+
+  .el-dropdown + .el-dropdown {
+    margin-left: 15px;
+  }
+
+  .el-icon-arrow-down {
+    font-size: 12px;
+  }
+
+  .el-button {
+    padding: 8px 7px;
+  }
+
+}
+
+.el-button--mini {
+  padding: 7px 10px;
+}
+
+.el-button + .el-button {
+  margin-left: 1px;
+}
+
+.dialog-detail-view {
+  .el-form-item {
+    margin-bottom: 0px;
+  }
+
+  .el-form-item__content {
+    line-height: 30px;
+  }
+
+  .el-form-item__label {
+    line-height: 30px;
+  }
+}
+
+.el-dialog__header {
+  padding-bottom: 0;
+}
+
+.el-dialog__body {
+  padding: 0
+}
+
+.createPost-main-container {
+  padding: 20px 0 !important;
+}
+
+.fitersLine {
+  .el-form-item__content {
+    margin-left: 0 !important
+  }
+
+  .el-form-item__label {
+    min-width: auto !important;
+  }
+
+  .el-select {
+    margin-top: 3px;
+  }
+}
+</style>

+ 5 - 28
src/views/zcustom/camera/index.vue

@@ -9,22 +9,6 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="在线标志" prop="onlineFlag">
-        <el-input
-          v-model="queryParams.onlineFlag"
-          placeholder="请输入在线标志"
-          clearable
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="接入标志" prop="connectFlag">
-        <el-input
-          v-model="queryParams.connectFlag"
-          placeholder="请输入接入标志"
-          clearable
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -83,18 +67,6 @@
           <span v-if="scope.row.status == 2">禁用</span>
         </template>
       </el-table-column>
-      <el-table-column label="在线标志" align="center">
-        <template slot-scope="scope">
-          <span v-if="scope.row.onlineFlag == 0">在线</span>
-          <span v-if="scope.row.onlineFlag == 2">离线</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="接入标志" align="center">
-        <template slot-scope="scope">
-          <span v-if="scope.row.connectFlag == 0">已接入</span>
-          <span v-if="scope.row.connectFlag == 2">未接入</span>
-        </template>
-      </el-table-column>
       <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
@@ -129,6 +101,9 @@
         <el-form-item label="摄像头名称" prop="cameraName">
           <el-input v-model="form.cameraName" placeholder="请输入摄像头名称" />
         </el-form-item>
+        <el-form-item label="摄像头编码" prop="cameraCode">
+          <el-input v-model="form.cameraCode" placeholder="请输入摄像头编码" />
+        </el-form-item>
         <el-form-item label="所属项目" prop="projectId">
           <el-select v-model="form.projectId" placeholder="请选择">
             <el-option
@@ -183,6 +158,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         cameraName: null,
+        cameraCode: null,
         projectId: null,
         type: null,
         status: null,
@@ -249,6 +225,7 @@ export default {
       this.form = {
         id: null,
         cameraName: null,
+        cameraCode: null,
         projectId: null,
         type: null,
         status: "0",

+ 91 - 3
src/views/zcustom/project/index.vue

@@ -63,10 +63,15 @@
         </template>
       </el-table-column>
       <el-table-column label="项目名称" align="center" prop="projectName" />
-      <el-table-column label="接入标志" align="center">
+      <el-table-column label="项目分类" align="center">
         <template slot-scope="scope">
-          <span v-if="scope.row.connectFlag == 0">已接入</span>
-          <span v-if="scope.row.connectFlag == 2">未接入</span>
+          <span v-if="scope.row.subdivision <= 10">{{ typeListA[scope.row.subdivision - 1].name }}</span>
+          <span v-if="scope.row.subdivision > 10">{{ typeListB[scope.row.subdivision - 1].name }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center">
+        <template slot-scope="scope">
+          {{ scope.row.createTime }}
         </template>
       </el-table-column>
       <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
@@ -113,6 +118,26 @@
             </el-option>
           </el-select>
         </el-form-item>
+        <el-form-item label="项目类型" prop="type">
+          <el-radio v-model="flag" label="1">在建</el-radio>
+          <el-radio v-model="flag" label="2">施工</el-radio>
+        </el-form-item>
+        <el-form-item label="项目小类" prop="unitCode">
+          <el-select v-model="form.subdivision" placeholder="请选择">
+            <el-option
+              v-if="flag === '1'"
+              v-for="item in typeListA"
+              :key="item.key"
+              :label="item.name"
+              :value="item.key"/>
+            <el-option
+              v-if="flag === '2'"
+              v-for="item in typeListB"
+              :key="item.key"
+              :label="item.name"
+              :value="item.key"/>
+          </el-select>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -130,6 +155,68 @@ export default {
   name: "Project",
   data() {
     return {
+      typeListA:[
+        {
+          key: 1,
+          name:'可研评审'
+        },
+        {
+          key: 2,
+          name:'初设评审'
+        },
+        {
+          key: 3,
+          name:'政府立项备案'
+        },
+        {
+          key: 4,
+          name:'土地手续'
+        },
+        {
+          key: 5,
+          name:'用地规划'
+        },
+        {
+          key: 6,
+          name:'工程规划'
+        },
+        {
+          key: 7,
+          name:'设计招标'
+        },
+        {
+          key: 8,
+          name:'施工招标'
+        },
+        {
+          key: 9,
+          name:'监理招标'
+        },
+        {
+          key: 10,
+          name:'施工许可'
+        },
+      ],
+      typeListB:[
+        {
+          key: 11,
+          name:'基础施工'
+        },
+        {
+          key: 12,
+          name:'主体工程'
+        },
+        {
+          key: 13,
+          name:'室内外装修'
+        },
+        {
+          key: 14,
+          name:'室外工程'
+        }
+      ],
+      flag: '1',
+
       // 根路径
       baseURL: process.env.VUE_APP_BASE_API,
       // 遮罩层
@@ -242,6 +329,7 @@ export default {
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset();
+      this.flag = Number(row.subdivision) < 10 ? '1' : '2'
       const id = row.id || this.ids
       getProject(id).then(response => {
         this.form = response.data;