浏览代码

Merge branch 'wangzi_dev' of git.xiaojukeji.com:jacklijiajia/thoth-frontend into http_test

wangziqian 5 年之前
父节点
当前提交
86a80c2482
共有 28 个文件被更改,包括 2823 次插入29 次删除
  1. 2 0
      package.json
  2. 83 0
      src/api/statisticsApi/requireStatistics.js
  3. 99 0
      src/api/statisticsApi/taskStatistics.js
  4. 2 2
      src/apiConfig/mock.js
  5. 二进制
      src/assets/defect_images/数据4.png
  6. 120 0
      src/components/chart/antvChart.vue
  7. 19 6
      src/router/index.js
  8. 35 1
      src/views/projectManage/bugList/file/createdBug.vue
  9. 18 2
      src/views/projectManage/projectList/projectIndex.vue
  10. 17 1
      src/views/projectManage/requirement/list/index.vue
  11. 20 3
      src/views/projectManage/taskList/taskIndex.vue
  12. 48 0
      src/views/quality/components/belongChart.vue
  13. 142 0
      src/views/quality/components/belongRequirementChart.vue
  14. 97 0
      src/views/quality/components/bugList.vue
  15. 70 0
      src/views/quality/components/changeRequireChart.vue
  16. 156 0
      src/views/quality/components/cycleStatistic.vue
  17. 102 0
      src/views/quality/components/developmentCycle.vue
  18. 164 0
      src/views/quality/components/distributionChart.vue
  19. 191 0
      src/views/quality/components/statusChart.vue
  20. 76 0
      src/views/quality/components/statusStayChart.vue
  21. 72 0
      src/views/quality/components/tendencyChart.vue
  22. 11 11
      src/views/quality/defectStatistics.vue
  23. 602 0
      src/views/quality/requireStatistics.vue
  24. 626 0
      src/views/quality/taskStatistics.vue
  25. 0 0
      src/views/reportManagement/daily/components/dailyDetails.vue
  26. 17 1
      src/views/workbench/team/components/needsList.vue
  27. 17 1
      src/views/workbench/team/components/projectList.vue
  28. 17 1
      src/views/workbench/team/components/taskList.vue

+ 2 - 0
package.json

@@ -15,6 +15,8 @@
     "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
   },
   "dependencies": {
+    "@antv/data-set": "^0.11.5",
+    "@antv/g2": "^4.0.15",
     "@didi/omega-tracker": "^2.2.8",
     "@fullcalendar/bootstrap": "^4.4.0",
     "@fullcalendar/core": "^4.4.0",

+ 83 - 0
src/api/statisticsApi/requireStatistics.js

@@ -0,0 +1,83 @@
+import request from '@/utils/request'
+import { TeamManagement } from '@/apiConfig/api'
+
+// 需求累计数据
+export function getSummary(data) {
+  return request({
+    url: TeamManagement + '/requirement/getSummary',
+    method: 'post',
+    data
+  })
+}
+// 需求状态累计流图
+export function getCumulativeFlowDiagram(data) {
+  return request({
+    url: TeamManagement + '/requirement/getCumulativeFlowDiagram',
+    method: 'post',
+    data
+  })
+}
+// 需求趋势图
+export function getRequireCountTrend(data) {
+  return request({
+    url: TeamManagement + '/requirement/getRequireCountTrend',
+    method: 'post',
+    data
+  })
+}
+// 需求方向分布图
+export function getOrntDistributeData(data) {
+  return request({
+    url: TeamManagement + '/requirement/getOrntDistributeData',
+    method: 'post',
+    data
+  })
+}
+// 需求周期统计
+export function getRequirePeriodicData(data) {
+  return request({
+    url: TeamManagement + '/requirement/getRequirePeriodicData',
+    method: 'post',
+    data
+  })
+}
+// 研发交付周期分布图
+export function getRequireRdDeliveryPeriodicData(data) {
+  return request({
+    url: TeamManagement + '/requirement/getRequireRdDeliveryPeriodicData',
+    method: 'post',
+    data
+  })
+}
+// 需求分布图
+export function getDistributeData(data) {
+  return request({
+    url: TeamManagement + '/requirement/getDistributeData',
+    method: 'post',
+    data
+  })
+}
+// 状态停留时长分布
+export function getStatusStayData(data) {
+  return request({
+    url: TeamManagement + '/requirement/getStatusStayData',
+    method: 'post',
+    data
+  })
+}
+// 排期发生变更的需求
+export function getReqUnlockData(data) {
+  return request({
+    url: TeamManagement + '/requirement/getReqUnlockData',
+    method: 'post',
+    data
+  })
+}
+// 缺陷统计
+export function getBugStatisticData(data) {
+  return request({
+    url: TeamManagement + '/requirement/getBugStatisticData',
+    method: 'post',
+    data
+  })
+}

+ 99 - 0
src/api/statisticsApi/taskStatistics.js

@@ -0,0 +1,99 @@
+import request from '@/utils/request'
+import { TeamManagement } from '@/apiConfig/api'
+
+// 任务累计数据
+export function getSummary(data) {
+  return request({
+    url: TeamManagement + '/task/getSummary',
+    method: 'post',
+    data
+  })
+}
+// 任务状态累计流图
+export function getCumulativeFlowDiagram(data) {
+  return request({
+    url: TeamManagement + '/task/getCumulativeFlowDiagram',
+    method: 'post',
+    data
+  })
+}
+// 任务趋势图
+export function getTaskCountTrend(data) {
+  return request({
+    url: TeamManagement + '/task/getTaskCountTrend',
+    method: 'post',
+    data
+  })
+}
+// 所属需求方向分布
+export function getRequirementOrientationDistributeData(data) {
+  return request({
+    url: TeamManagement + '/task/getRequirementOrientationDistributeData',
+    method: 'post',
+    data
+  })
+}
+// 任务周期统计
+export function getTaskPeriodicData(data) {
+  return request({
+    url: TeamManagement + '/task/getTaskPeriodicData',
+    method: 'post',
+    data
+  })
+}
+// 研发交付周期分布图
+export function getTaskRdDeliveryPeriodicData(data) {
+  return request({
+    url: TeamManagement + '/task/getTaskRdDeliveryPeriodicData',
+    method: 'post',
+    data
+  })
+}
+// 任务分布图
+export function getDistributeData(data) {
+  return request({
+    url: TeamManagement + '/task/getDistributeData',
+    method: 'post',
+    data
+  })
+}
+// 状态停留时长分布
+export function getStatusStayData(data) {
+  return request({
+    url: TeamManagement + '/task/getStatusStayData',
+    method: 'post',
+    data
+  })
+}
+// 排期模块分布
+export function getModuleDistributeData(data) {
+  return request({
+    url: TeamManagement + '/task/getModuleDistributeData',
+    method: 'post',
+    data
+  })
+}
+// 排期发生变更的任务
+export function getTaskUnlockData(data) {
+  return request({
+    url: TeamManagement + '/task/getTaskUnlockData',
+    method: 'post',
+    data
+  })
+}
+// 报告统计
+export function getReportSummary(data) {
+  return request({
+    url: TeamManagement + '/task/getReportSummary',
+    method: 'post',
+    data
+  })
+}
+// 缺陷统计
+export function getBugStatisticData(data) {
+  return request({
+    url: TeamManagement + '/task/getBugStatisticData',
+    method: 'post',
+    data
+  })
+}

+ 2 - 2
src/apiConfig/mock.js

@@ -1,5 +1,5 @@
 /*eslint-disable*/   //规避eslint的检查,如没有eslint可不写
 // export const mockUrl = 'http://10.179.88.110:8089' // stable
-// export const mockUrl = 'http://mock.Intra.xiaojukeji.com' // 线上
-export const mockUrl = 'http://10.179.101.226:8089' // test
+export const mockUrl = 'http://mock.Intra.xiaojukeji.com' // 线上
+// export const mockUrl = 'http://10.179.101.226:8089' // test
 // export const mockUrl = 'http://172.23.145.20:8089'//local

二进制
src/assets/defect_images/数据4.png


+ 120 - 0
src/components/chart/antvChart.vue

@@ -0,0 +1,120 @@
+<template>
+  <div :id="id" class="antv-chart" />
+</template>
+<script>
+import DataSet from '@antv/data-set'
+import { Chart } from '@antv/g2'
+const ds = new DataSet()
+export default {
+  props: {
+    id: {
+      type: String,
+      default: 'antv-chart',
+      required: false
+    },
+    option: {
+      type: Object,
+      default: () => null,
+      required: false
+    },
+    data: {
+      type: Object,
+      default: () => null,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    option: {
+      handler(newValue, oldValue) {
+        this.$nextTick(() => {
+          this.initChart()
+        })
+      },
+      deep: true
+    },
+    data: {
+      handler(newValue, oldValue) {
+        this.$nextTick(() => {
+          this.initChart()
+        })
+      },
+      deep: true
+    }
+  },
+  mounted() {
+    this.initChart()
+  },
+  methods: {
+    initChart() {
+      this.chart && this.chart.destroy()
+      const dv = ds.createView().source(this.data, {
+        type: 'hierarchy'
+      })
+      dv.transform({
+        type: 'hierarchy.partition'
+      })
+      this.chart = new Chart({
+        container: this.id,
+        autoFit: true,
+        height: 400,
+        padding: 0
+      })
+      this.chart.data(
+        dv.getAllNodes().map((node) => {
+          if (node) {
+            return {
+              name: node.data.name,
+              value: node.value,
+              depth: node.depth,
+              x: node.x,
+              y: node.y
+            }
+          }
+        })
+      )
+      this.chart.scale({
+        x: { nice: true },
+        y: { nice: true }
+      })
+      this.chart.tooltip({
+        showTitle: false,
+        showMarkers: false
+      })
+      this.chart.axis(false)
+      this.chart.legend(false)
+      this.chart
+        .polygon()
+        .position('x*y')
+        .color('name')
+        .tooltip('name*value', function(name, value) {
+          return {
+            name,
+            value
+          }
+        })
+        .label(
+          'name',
+          {
+            offset: 0,
+            style: {
+              textBaseline: 'middle',
+              fill: '#000',
+              shadowBlur: 10,
+              shadowColor: '#fff'
+            },
+            layout: {
+              type: 'limit-in-shape'
+            }
+          }
+        )
+      this.chart.interaction('element-active')
+      this.chart.render()
+    }
+  }
+}
+</script>

+ 19 - 6
src/router/index.js

@@ -406,6 +406,18 @@ export const constantRoutes = [{
     component: (resolve) => require(['@/views/quality/qualityMeasurement.vue'], resolve),
     meta: { title: '老版统计' }
   },
+  {
+    path: 'requireStatistics',
+    name: '需求统计',
+    component: (resolve) => require(['@/views/quality/requireStatistics.vue'], resolve),
+    meta: { title: '需求统计' }
+  },
+  {
+    path: 'taskStatistics',
+    name: '任务统计',
+    component: (resolve) => require(['@/views/quality/taskStatistics.vue'], resolve),
+    meta: { title: '任务统计' }
+  },
   {
     path: 'defectStatistics',
     name: '缺陷统计',
@@ -503,6 +515,12 @@ export const constantRoutes = [{
     }
     ]
   },
+  {
+    path: '/apiManagement',
+    component: (resolve) => require(['@/views/apiManagement/automatic'], resolve),
+    name: '接口管理',
+    meta: { title: '接口管理' }
+  },
   {
     path: '/online-quality',
     component: (resolve) => require(['@/views/Platform/presentation/testa'], resolve),
@@ -564,12 +582,6 @@ export const constantRoutes = [{
     }
     ]
   },
-  {
-    path: '/apiManagement',
-    component: (resolve) => require(['@/views/apiManagement/automatic'], resolve),
-    name: '接口管理',
-    meta: { title: '接口管理' }
-  },
   {
     path: '/newWeb',
     component: (resolve) => require(['@/views/newWeb/index'], resolve),
@@ -620,6 +632,7 @@ const createRouter = () => new Router({
   scrollBehavior: () => ({ y: 0 }),
   routes: constantRoutes
 })
+
 const router = createRouter()
 router.beforeEach((to, from, next) => {
   console.log(this)

+ 35 - 1
src/views/projectManage/bugList/file/createdBug.vue

@@ -16,7 +16,15 @@
               <div style="width:100%; margin: 0 4%;">
                 <el-form-item label="所属任务" prop="taskId">
                   <el-select v-model="formInline.taskId" filterable placeholder="请选择" style="width:100%;" @click.native="bugListSelect">
-                    <el-option v-for="item in taskEnumList" :key="item.id" :label="item.name" :value="item.id" />
+                    <el-option v-for="item in taskEnumList" :key="item.id" :label="item.name" :value="item.id">
+                      <div class="belong-task">
+                        <div class="modules-name">
+                          <span v-if="item.moduleInfoName" class="modules">{{ item.moduleInfoName | limit(15) }}</span>
+                          <span class="name">{{ item.name }}</span>
+                        </div>
+                        <div class="task-id">{{ item.taskId }}</div>
+                      </div>
+                    </el-option>
                   </el-select>
                 </el-form-item>
                 <el-form-item label="优先级" prop="priorityLevel" style="white-space: nowrap;">
@@ -246,6 +254,15 @@ export default {
   components: {
     normalDialog
   },
+  filters: {
+    limit(e, limit) {
+      if (e.length > limit) {
+        return e.substring(0, limit) + '...'
+      } else {
+        return e
+      }
+    }
+  },
   props: {
     getBugList: {
       type: Function,
@@ -683,4 +700,21 @@ export default {
     margin-right: 5px;
   }
 }
+.belong-task {
+  max-width: 500px;
+  display: flex;
+  justify-content: space-between;
+  .modules-name {
+    width: 80%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .modules,.task-id{
+    color: #999999;
+  }
+  .name {
+    color: #333333;
+  }
+}
 </style>

+ 18 - 2
src/views/projectManage/projectList/projectIndex.vue

@@ -3,6 +3,12 @@
     <div class="stylus-head">
       <div class="stylus-title">
         <span style="font-size: 22px;letter-spacing: 1px;font-weight: 600;color: #333B4A;padding-left: 15px">项目</span>
+        <div class="new-tab-open">
+          <el-switch
+            v-model="newTabOpen"
+            active-text="新标签页跳转"
+          />
+        </div>
         <div>
           <el-button type="primary" size="mini" @click="home_created_project">新建项目</el-button>
         </div>
@@ -331,6 +337,7 @@ import '@/views/projectManage/publicCss/index.css'
 export default {
   data() {
     return {
+      newTabOpen: false, // 是否新的tab页打开
       curIndex: 1,
       pageSize: 15,
       activeColor: 'red',
@@ -442,8 +449,13 @@ export default {
     optionsClear() {
       this.options = []
     },
-    link_project(e) {
-      this.$router.push({ name: '项目详情', query: { id: e }})
+    link_project(id) {
+      if (this.newTabOpen) {
+        const newTab = this.$router.resolve({ name: '项目详情', query: { id: id }})
+        window.open(newTab.href, '_blank')
+      } else {
+        this.$router.push({ name: '项目详情', query: { id: id }})
+      }
     },
     showSelect() {
       this.DetailedScreening = !this.DetailedScreening
@@ -527,6 +539,10 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.new-tab-open {
+  position: absolute;
+  left: 120px;
+}
 .task {
   /deep/ .el-dialog__title {
     line-height: 24px;

+ 17 - 1
src/views/projectManage/requirement/list/index.vue

@@ -3,6 +3,12 @@
     <div class="stylus-head">
       <div class="stylus-title">
         <span style="font-size: 22px;letter-spacing: 1px;font-weight: 600;color: #333B4A;padding-left: 15px">需求</span>
+        <div class="new-tab-open">
+          <el-switch
+            v-model="newTabOpen"
+            active-text="新标签页跳转"
+          />
+        </div>
         <el-button type="primary" size="mini" @click="createDialogVisible = true">新建需求</el-button>
       </div>
       <el-divider style="color: #EEF0F5;" />
@@ -221,6 +227,7 @@ export default {
   },
   data() {
     return {
+      newTabOpen: false, // 是否新的tab页打开
       DemandStatus: true, // 筛选需求时长
       extraUrgent: extraUrgent,
       priorityColors: ['#F56C6C', '#FF8952', '#F5E300', '#7ED321', '#61D3B8', '#69B3FF', '#BDBDBD'],
@@ -448,13 +455,22 @@ export default {
       this.getTableData()
     },
     getToRequirementDetails(id) { // table点击跳转
-      this.$router.push({ name: '需求详情', query: { id: id }})
+      if (this.newTabOpen) {
+        const newTab = this.$router.resolve({ name: '需求详情', query: { id: id }})
+        window.open(newTab.href, '_blank')
+      } else {
+        this.$router.push({ name: '需求详情', query: { id: id }})
+      }
     }
   }
 }
 </script>
 
 <style scoped lang="scss">
+.new-tab-open {
+  position: absolute;
+  left: 120px;
+}
 .el-loading-mask {
   z-index: 8;
 }

+ 20 - 3
src/views/projectManage/taskList/taskIndex.vue

@@ -3,6 +3,12 @@
     <div class="stylus-head">
       <div class="stylus-title">
         <span style="font-size: 22px;letter-spacing: 1px;font-weight: 600;color: #333B4A;padding-left: 15px">任务</span>
+        <div class="new-tab-open">
+          <el-switch
+            v-model="newTabOpen"
+            active-text="新标签页跳转"
+          />
+        </div>
         <div>
           <el-button
             type="primary"
@@ -226,6 +232,7 @@ export default {
   },
   data() {
     return {
+      newTabOpen: false, // 是否新的tab页打开
       header_show: true,
       props: { multiple: true },
       priorityColors: ['#F56C6C', '#FF8952', '#F5E300', '#7ED321', '#61D3B8', '#69B3FF', '#BDBDBD'],
@@ -344,8 +351,13 @@ export default {
         this.$refs.task_createdUpdata.init(1)
       })
     },
-    link_task(e) {
-      this.$router.push({ name: '任务详情', query: { id: e }})
+    link_task(id) {
+      if (this.newTabOpen) {
+        const newTab = this.$router.resolve({ name: '任务详情', query: { id: id }})
+        window.open(newTab.href, '_blank')
+      } else {
+        this.$router.push({ name: '任务详情', query: { id: id }})
+      }
     },
     query_Reset() {
       // 重置
@@ -418,7 +430,12 @@ export default {
   }
 }
 </script>
-
+<style lang="scss" scoped>
+.new-tab-open {
+  position: absolute;
+  left: 120px;
+}
+</style>
 <style>
 .el-loading-mask {
   z-index: 8;

+ 48 - 0
src/views/quality/components/belongChart.vue

@@ -0,0 +1,48 @@
+<template>
+  <section>
+    <div class="chart-contain">
+      <antv-chart v-if="chartData" :data="chartData" />
+    </div>
+  </section>
+</template>
+<script>
+import antvChart from '@/components/chart/antvChart'
+export default {
+  components: { antvChart },
+  props: {
+    id: {
+      type: String,
+      default: 'belong-chart',
+      required: false
+    },
+    chartData: {
+      type: Object,
+      default: () => null,
+      required: false
+    }
+  },
+  data() {
+    return {
+      echartsOption: null
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.chartData = newV
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {}
+}
+</script>
+<style lang="scss" scoped>
+.chart-contain {
+  position: relative;
+  height: 400px;
+  width: 84%;
+  margin: 20px auto;
+}
+</style>

+ 142 - 0
src/views/quality/components/belongRequirementChart.vue

@@ -0,0 +1,142 @@
+<template>
+  <section>
+    <div class="control">
+      <el-row type="flex" align="middle">
+        <el-col :span="6">
+          <div class="total">需求总数 <span>{{ total }}</span> 个</div>
+        </el-col>
+        <el-col :span="10" :offset="8" class="col-flex-end">
+          <div class="bar-pie" :class="[barOrPie==='bar'?'active':'']" @click="changeBarOrPie('bar')">柱状图</div>
+          <div class="bar-pie" :class="[barOrPie==='pie'?'active':'']" @click="changeBarOrPie('pie')">饼图</div>
+        </el-col>
+      </el-row>
+    </div>
+    <div class="chart-contain">
+      <normal-echart v-if="echartsOption" :chart-id="id" :option="echartsOption" />
+    </div>
+  </section>
+</template>
+<script>
+import normalEchart from '@/components/chart/normalEchart'
+export default {
+  components: { normalEchart },
+  props: {
+    id: {
+      type: String,
+      default: 'belong-requirement-chart',
+      required: false
+    },
+    chartData: {
+      type: Object,
+      default: () => null,
+      required: false
+    },
+    activeTab: {
+      type: String,
+      default: '1',
+      required: false
+    }
+  },
+  data() {
+    return {
+      echartsOption: null,
+      barOrPie: 'bar', // 柱状图or饼图
+      total: 0
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        if (newV) {
+          this.total = this.chartData.yaxis[0].data.reduce((prev, curr) => prev + curr)
+        }
+        this.changeBarOrPie(this.barOrPie)
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  mounted() {
+    this.changeBarOrPie(this.barOrPie)
+  },
+  methods: {
+    statusChange(e) {
+      this.$emit('change')
+    },
+    changeBarOrPie(type) { // 饼图柱状图切换
+      this.barOrPie = type
+      if (!this.chartData) return
+      if (type === 'bar') {
+        this.echartsOption = {
+          color: ['#3AA1FF'],
+          tooltip: { trigger: 'axis', axisPointer: { type: 'line' }}, // 默认为直线,可选为:'line' | 'shadow'
+          grid: { left: '0', right: '0', top: '10%', bottom: '0', containLabel: true },
+          xAxis: [{ type: 'category', data: this.chartData.xaxis, axisLabel: { interval: 0, rotate: 15 }, axisTick: { alignWithLabel: true }}],
+          yAxis: [{ type: 'value', axisLine: { show: false }, splitLine: { lineStyle: { type: 'dashed' }}}],
+          series: [{
+            name: '数量', type: 'bar', barWidth: '20px', data: this.chartData.yaxis[0] && this.chartData.yaxis[0].data || [],
+            itemStyle: { normal: { label: { show: true, formatter: '{c}', position: 'top' }}}
+          }]
+        }
+      } else {
+        const newArr = this.chartData.xaxis.map((item, index) => {
+          return {
+            value: this.chartData.yaxis[0] && this.chartData.yaxis[0].data[index] || null,
+            name: item
+          }
+        })
+        this.echartsOption = {
+          color: ['#1890FF', '#13C2C2', '#2FC25B', '#FACC14', '#F04864', '#8543E0'],
+          grid: { left: '0', right: '0', top: '5%', bottom: '0' },
+          tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c} ({d}%)' },
+          legend: { orient: 'vertical', left: 'right', top: 'center', data: this.chartData.xaxis },
+          series: [{
+            name: '数量', type: 'pie', radius: ['45%', '60%'], right: '30%', label: { position: 'outer', alignTo: 'edge', margin: 20 }, data: newArr,
+            itemStyle: { normal: { label: { show: true, formatter: '{b} : {c} ({d}%)' }, labelLine: { show: true }}}
+          }]
+        }
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.chart-contain {
+  position: relative;
+  height: 400px;
+  width: 84%;
+  margin: 20px auto;
+}
+.control{
+  width: 84%;
+  margin: auto;
+  margin-top: 20px;
+}
+.col-flex-end {
+  display: flex;
+  justify-content: flex-end;
+}
+.bar-pie {
+  font-size: 14px;
+  width: 40%;
+  max-width: 100px;
+  display: inline-block;
+  padding: 6px 10px;
+  margin-left: 5%;
+  color: #50A6FF;
+  border: 1px solid #50A6FF;
+  border-radius: 4px;
+  text-align: center;
+  cursor: pointer;
+}
+.active {
+  color: #ffffff;
+  background: #50A6FF;
+}
+.total {
+  color: #333333;
+  span {
+    color: #61AFFF;
+  }
+}
+</style>

+ 97 - 0
src/views/quality/components/bugList.vue

@@ -0,0 +1,97 @@
+<template>
+  <section>
+    <div class="repair-list">
+      <div v-for="(item, index) in chartData.bugCount" :key="'time'+index" class="repair-item">
+        <div :class="{'item-detail': index === 0}">
+          <span>{{ item.label }}</span>
+          <div class="repair-time">{{ item.countStr }}<span> 个</span></div>
+          <div v-show="Number(item.chainRatio)>=0" class="repair-up">环比:<i class="el-icon-caret-top" /><span>{{ item.chainRatio }}%</span></div>
+          <div v-show="Number(item.chainRatio)<0" class="repair-down">环比:<i class="el-icon-caret-bottom" /><span>{{ item.chainRatio.substring(1,item.chainRatio.length) }}%</span></div>
+          <div v-show="item.chainRatio === '--'" class="repair-up">环比:<span>{{ item.chainRatio }}%</span></div>
+        </div>
+      </div>
+    </div>
+    <div class="repair-list">
+      <div v-for="(item, index) in chartData.averageBugCount" :key="'time'+index" class="repair-item">
+        <div :class="{'item-detail': index === 0}">
+          <span>{{ item.label }}</span>
+          <div class="repair-time">{{ item.countStr }}<span> 个</span></div>
+          <div v-show="Number(item.chainRatio)>=0" class="repair-up">环比:<i class="el-icon-caret-top" /><span>{{ item.chainRatio }}%</span></div>
+          <div v-show="Number(item.chainRatio)<0" class="repair-down">环比:<i class="el-icon-caret-bottom" /><span>{{ item.chainRatio.substring(1,item.chainRatio.length) }}%</span></div>
+          <div v-show="item.chainRatio === '--'" class="repair-up">环比:<span>{{ item.chainRatio }}%</span></div>
+        </div>
+      </div>
+    </div>
+    <div class="repair-list">
+      <div v-for="(item, index) in chartData.averageBugRepairTime" :key="'time'+index" class="repair-item">
+        <div :class="{'item-detail': index === 0}">
+          <span>{{ item.label }}</span>
+          <div class="repair-time">{{ item.countStr }}<span> h</span></div>
+          <div v-show="Number(item.chainRatio)>=0" class="repair-up">环比:<i class="el-icon-caret-top" /><span>{{ item.chainRatio }}%</span></div>
+          <div v-show="Number(item.chainRatio)<0" class="repair-down">环比:<i class="el-icon-caret-bottom" /><span>{{ item.chainRatio.substring(1,item.chainRatio.length) }}%</span></div>
+          <div v-show="item.chainRatio === '--'" class="repair-up">环比:<span>{{ item.chainRatio }}%</span></div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+<script>
+export default {
+  props: {
+    chartData: {
+      type: Object,
+      default: () => {},
+      required: false
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.chartData = newV
+      },
+      immediate: true,
+      deep: true
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.repair-list {
+  display: flex;
+  justify-content: space-between;
+  width: 84%;
+  margin: 20px auto;
+  background:rgba(255,255,255,1);
+  box-shadow:0px 2px 8px rgba(0,0,0,0.15);
+  border-radius: 4px;
+  .repair-item {
+    position: relative;
+    width: 20%;
+    font-size: 12px;
+    font-weight: bold;
+    padding: 11px 17px 11px 17px;
+    display: flex;
+    padding-left: 60px;
+    flex-direction: column;
+    color: #909399;
+    .repair-time {
+      color: #303133;
+      font-size: 18px;
+      margin:25px 0;
+      span {
+        font-size: 12px;
+        color: #666666;
+      }
+    }
+  }
+  .item-detail{
+    border-right:1px solid rgba(112,112,112,0.12);
+  }
+  .repair-up i , .repair-up span{
+    color:#F32850
+  }
+  .repair-down i, .repair-down span {
+    color:#9FFF39
+  }
+}
+</style>

+ 70 - 0
src/views/quality/components/changeRequireChart.vue

@@ -0,0 +1,70 @@
+<template>
+  <section>
+    <div class="chart-contain">
+      <el-table
+        :data="chartData"
+        border
+        max-height="400px"
+        :header-cell-style="{background: 'rgba(232,232,232,0.6)',color:'#333333'}"
+        style="width: 100%"
+      >
+        <el-table-column
+          prop="id"
+          label="任务ID"
+          width="180"
+          align="center"
+        />
+        <el-table-column
+          prop="name"
+          label="任务名称"
+          width="180"
+          align="center"
+        />
+        <el-table-column
+          prop="count"
+          label="排期解锁次数"
+          align="center"
+        />
+        <el-table-column
+          label="解锁原因"
+          align="center"
+        >
+          <template slot-scope="scope">
+            <div v-for="(item,index) in scope.row.remarkList" :key="'item-require'+index" class="reason">{{ item }}</div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </section>
+</template>
+<script>
+export default {
+  props: {
+    chartData: {
+      type: Array,
+      default: () => [],
+      required: false
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.chartData = newV
+      },
+      immediate: true,
+      deep: true
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.chart-contain {
+  position: relative;
+  width: 84%;
+  margin: auto;
+  margin-top: 20px;
+}
+.reason {
+  text-align: left;
+}
+</style>

+ 156 - 0
src/views/quality/components/cycleStatistic.vue

@@ -0,0 +1,156 @@
+<template>
+  <section>
+    <el-row type="flex" align="middle">
+      <div class="repair-list">
+        <div v-for="(item, index) in chartData" :key="'time'+index" class="repair-item" :class="[Number(item.relativeRatio)<0?'repair-slow':'repair-rise']">
+          <div class="repair-item-point" :class="['point'+index]" />
+          <span>
+            {{ item.label }}
+            <template v-if="type === 'require' && showTips">
+              <el-tooltip v-if="index === 0" class="item" effect="dark" content="研发交付周期定义:需求排期里开发、联调、上线类型排期的总周期" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 1" class="item" effect="dark" content="研发周期定义:需求排期里开发、联调类型排期的总周期" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 2" class="item" effect="dark" content="测试周期定义:需求排期里测试类型排期的总周期" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 3" class="item" effect="dark" content="提测等待测试时长定义:需求排期里提测排期结束日期距离测试排期开始日期的天数" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+            </template>
+            <template v-if="type === 'task' && showTips">
+              <el-tooltip v-if="index === 0" class="item" effect="dark" content="研发交付周期定义:任务排期里开发、联调、上线类型排期的总周期" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 1" class="item" effect="dark" content="研发周期定义:任务排期里开发、联调类型排期的总周期" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 2" class="item" effect="dark" content="测试周期定义:任务排期里测试类型排期的总周期" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 3" class="item" effect="dark" content="提测等待测试时长定义:任务排期里提测排期结束日期距离测试排期开始日期的天数" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+            </template>
+          </span>
+          <div v-if="item.countStr" class="repair-time">{{ item.countStr }}<span> 天</span></div>
+          <div v-if="item.total || item.total === 0" class="repair-time">
+            {{ item.total }}<span class="unit"> 个</span>
+            <span v-if="item.count > 0">
+              /<span class="reject">打回{{ item.count }}个</span>
+            </span>
+          </div>
+          <div v-show="Number(item[childData])>=0" class="repair-up">环比:<i class="el-icon-caret-top" /><span>{{ item[childData] }}%</span></div>
+          <div v-show="Number(item[childData])<0" class="repair-down">环比:<i class="el-icon-caret-bottom" /><span>{{ item[childData].substring(1,item[childData].length) }}%</span></div>
+          <div v-show="item[childData] === '--'" class="repair-up">环比:<span>{{ item[childData] }}%</span></div>
+        </div>
+      </div>
+    </el-row>
+  </section>
+</template>
+<script>
+export default {
+  props: {
+    chartData: {
+      type: Array,
+      default: () => [],
+      required: false
+    },
+    childData: {
+      type: String,
+      default: 'chainRatio',
+      required: false
+    },
+    type: {
+      type: String,
+      default: 'require',
+      required: false
+    },
+    showTips: {
+      type: Boolean,
+      default: true,
+      required: false
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.chartData = newV
+      },
+      immediate: true,
+      deep: true
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.repair-list {
+  display: flex;
+  justify-content: space-between;
+  width: 84%;
+  margin: 20px auto;
+  .repair-item {
+    position: relative;
+    width: 20%;
+    font-size: 12px;
+    font-weight: bold;
+    padding: 11px 17px 6px 17px;
+    display: flex;
+    flex-direction: column;
+    color: #909399;
+    background:rgba(255,255,255,1);
+    box-shadow:0px 2px 8px rgba(0,0,0,0.15);
+    border-radius: 4px;
+    .repair-time {
+      color: #303133;
+      font-size: 18px;
+      margin:25px 0;
+      span {
+        font-size: 12px;
+        color: #666666;
+      }
+      .reject {
+        color:#F01A1A;
+      }
+    }
+    .repair-item-point {
+      position: absolute;
+      top: 18px;
+      left: 6px;
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+    }
+    .point0 {
+      background-color: #1890FF;
+    }
+    .point1 {
+      background-color: #52C41A;
+    }
+    .point2 {
+      background-color: #D675F0;
+    }
+    .point3 {
+      background-color: #ECAD00;
+    }
+  }
+  .repair-rise {
+    background-image: url('../../../../src/assets/defect_images/rise.png');
+    background-size: 100% 100%;
+    background-position: center;
+  }
+  .repair-slow {
+    background-image: url('../../../../src/assets/defect_images/slow.png');
+    background-size: 100% 100%;
+    background-position: center;
+  }
+  .repair-up i , .repair-up span{
+    color:#F32850
+  }
+  .repair-down i, .repair-down span {
+    color:#9FFF39
+  }
+}
+</style>

+ 102 - 0
src/views/quality/components/developmentCycle.vue

@@ -0,0 +1,102 @@
+<template>
+  <section>
+    <div class="chart-contain">
+      <normal-echart v-if="echartsOption" :chart-id="id" :option="echartsOption" />
+    </div>
+  </section>
+</template>
+<script>
+import normalEchart from '@/components/chart/normalEchart'
+import echarts from 'echarts'
+export default {
+  components: { normalEchart },
+  props: {
+    id: {
+      type: String,
+      default: 'develop-cycle-chart',
+      required: false
+    },
+    chartData: {
+      type: Array,
+      default: () => [],
+      required: false
+    }
+  },
+  data() {
+    return {
+      echartsOption: null
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.setChart()
+      },
+      deep: true,
+      immediate: true
+    },
+    timeType: {
+      handler(newV) {
+        this.timeType = newV
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    setChart() {
+      this.echartsOption = {
+        grid: { left: '0%', right: '5%', bottom: '0%', top: '10%', containLabel: true },
+        tooltip: {
+          showDelay: 0,
+          formatter: function(params) {
+            return `交付日期:${params.data[0]}<br/> 研发交付周期:${params.data[1]}天`
+          },
+          axisPointer: {
+            show: true,
+            type: 'cross',
+            lineStyle: {
+              type: 'dashed',
+              width: 1
+            }
+          }
+        },
+        xAxis: {
+          type: 'time'
+
+        },
+        yAxis: {
+          type: 'value'
+        },
+        series: [{
+          name: '',
+          data: this.chartData,
+          type: 'scatter',
+          symbolSize: 10,
+          itemStyle: {
+            shadowBlur: 10,
+            shadowColor: 'rgba(25, 100, 150, 0.5)',
+            shadowOffsetY: 5,
+            color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [{
+              offset: 0,
+              color: 'rgb(129, 227, 238)'
+            }, {
+              offset: 1,
+              color: 'rgb(25, 183, 207)'
+            }])
+          }
+        }]
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.chart-contain {
+  position: relative;
+  height: 400px;
+  width: 84%;
+  margin: auto;
+  margin-top: 20px;
+}
+</style>
+

+ 164 - 0
src/views/quality/components/distributionChart.vue

@@ -0,0 +1,164 @@
+<template>
+  <section>
+    <div class="control">
+      <el-row type="flex" align="middle">
+        <el-col :span="4">
+          <el-select v-model="curStatus" size="small" @change="statusChange">
+            <el-option
+              v-for="item in statusList"
+              :key="item.code"
+              :disabled="activeTab === '2'&&item.code === 1"
+              :label="item.label"
+              :value="item.code"
+            />
+          </el-select>
+        </el-col>
+        <el-col :span="10" :offset="10" class="col-flex-end">
+          <div class="bar-pie" :class="[barOrPie==='bar'?'active':'']" @click="changeBarOrPie('bar')">柱状图</div>
+          <div class="bar-pie" :class="[barOrPie==='pie'?'active':'']" @click="changeBarOrPie('pie')">饼图</div>
+        </el-col>
+      </el-row>
+    </div>
+    <div class="chart-contain">
+      <normal-echart v-if="echartsOption" :chart-id="id" :option="echartsOption" />
+    </div>
+  </section>
+</template>
+<script>
+import normalEchart from '@/components/chart/normalEchart'
+export default {
+  components: { normalEchart },
+  props: {
+    id: {
+      type: String,
+      default: 'distribute-chart',
+      required: false
+    },
+    statusList: {
+      type: Array,
+      default: () => [],
+      required: true
+    },
+    chartData: {
+      type: Object,
+      default: () => null,
+      required: false
+    },
+    status: {
+      type: Number,
+      default: NaN,
+      required: true
+    },
+    activeTab: {
+      type: String,
+      default: '1',
+      required: false
+    }
+  },
+  data() {
+    return {
+      echartsOption: null,
+      curStatus: 1, // 当前状态
+      barOrPie: 'bar' // 柱状图or饼图
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.changeBarOrPie(this.barOrPie)
+      },
+      deep: true,
+      immediate: true
+    },
+    status: {
+      handler(newV) {
+        this.curStatus = newV
+      },
+      immediate: true
+    },
+    activeTab: {
+      handler(newV) {
+        this.activeTab = newV
+      },
+      immediate: true
+    }
+  },
+  mounted() {
+    this.changeBarOrPie(this.barOrPie)
+  },
+  methods: {
+    statusChange(e) {
+      this.$emit('update:status', this.curStatus)
+      this.$emit('change')
+    },
+    changeBarOrPie(type) { // 饼图柱状图切换
+      this.barOrPie = type
+      if (!this.chartData) return
+      if (type === 'bar') {
+        this.echartsOption = {
+          color: ['#3AA1FF'],
+          tooltip: { trigger: 'axis', axisPointer: { type: 'line' }}, // 默认为直线,可选为:'line' | 'shadow'
+          grid: { left: '0', right: '0', top: '10%', bottom: '0', containLabel: true },
+          xAxis: [{ type: 'category', data: this.chartData.xaxis, axisLabel: { interval: 0, rotate: 0 }, axisTick: { alignWithLabel: true }}],
+          yAxis: [{ type: 'value', axisLine: { show: false }, splitLine: { lineStyle: { type: 'dashed' }}}],
+          series: [{
+            name: '数量', type: 'bar', barWidth: '20px', data: this.chartData.yaxis[0] && this.chartData.yaxis[0].data || [],
+            itemStyle: { normal: { label: { show: true, formatter: '{c}', position: 'top' }}}
+          }]
+        }
+      } else {
+        const newArr = this.chartData.xaxis.map((item, index) => {
+          return {
+            value: this.chartData.yaxis[0] && this.chartData.yaxis[0].data[index] || null,
+            name: item
+          }
+        })
+        this.echartsOption = {
+          color: ['#1890FF', '#13C2C2', '#2FC25B', '#FACC14', '#F04864', '#8543E0'],
+          grid: { left: '0', right: '0', top: '5%', bottom: '0' },
+          tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c} ({d}%)' },
+          legend: { orient: 'vertical', left: 'right', top: 'center', data: this.chartData.xaxis },
+          series: [{
+            name: '数量', type: 'pie', radius: ['45%', '60%'], right: '30%', label: { position: 'outer', alignTo: 'edge', margin: 20 }, data: newArr,
+            itemStyle: { normal: { label: { show: true, formatter: '{b} : {c} ({d}%)' }, labelLine: { show: true }}}
+          }]
+        }
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.chart-contain {
+  position: relative;
+  height: 400px;
+  width: 84%;
+  margin: 20px auto;
+}
+.control{
+  width: 84%;
+  margin: auto;
+  margin-top: 20px;
+}
+.col-flex-end {
+  display: flex;
+  justify-content: flex-end;
+}
+.bar-pie {
+  font-size: 14px;
+  width: 40%;
+  max-width: 100px;
+  display: inline-block;
+  padding: 6px 10px;
+  margin-left: 5%;
+  color: #50A6FF;
+  border: 1px solid #50A6FF;
+  border-radius: 4px;
+  text-align: center;
+  cursor: pointer;
+}
+.active {
+  color: #ffffff;
+  background: #50A6FF;
+}
+</style>

+ 191 - 0
src/views/quality/components/statusChart.vue

@@ -0,0 +1,191 @@
+<template>
+  <section>
+    <div class="control">
+      <div class="pile-line" :class="[pileOrLine==='pile'?'active':'']" @click="changePileOrLine('pile')">堆叠面积图</div>
+      <div class="pile-line" :class="[pileOrLine==='line'?'active':'']" @click="changePileOrLine('line')">折线图</div>
+    </div>
+    <div class="chart-contain">
+      <normal-echart v-if="echartsOption" :chart-id="id" :option="echartsOption" />
+    </div>
+  </section>
+</template>
+<script>
+import normalEchart from '@/components/chart/normalEchart'
+export default {
+  components: { normalEchart },
+  props: {
+    id: {
+      type: String,
+      default: 'status-chart',
+      required: false
+    },
+    chartData: {
+      type: Object,
+      default: () => null,
+      required: false
+    },
+    timeType: {
+      type: String,
+      default: 'week',
+      required: false
+    }
+  },
+  data() {
+    return {
+      echartsOption: null,
+      pileOrLine: 'pile' // 图的类型,pile:堆叠面积图,line:折线图
+    }
+  },
+  computed: {
+    seriesData: {
+      get() {
+        if (!this.chartData) return []
+        const newArr = []
+        Object.entries(this.chartData).forEach(values => {
+          const item = {
+            name: values[0],
+            type: 'line',
+            smooth: 0.5,
+            stack: '总量',
+            areaStyle: {},
+            data: values[1].map(item => item.count),
+            dayTime: values[1].map(item => item.label)
+          }
+          newArr.push(item)
+        })
+        return newArr
+      }
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.changePileOrLine(this.pileOrLine)
+      },
+      deep: true,
+      immediate: true
+    },
+    timeType: {
+      handler(newV) {
+        this.timeType = newV
+      },
+      immediate: true
+    }
+  },
+  mounted() {
+    this.changePileOrLine(this.pileOrLine)
+  },
+  methods: {
+    changePileOrLine(type) { // 图类型切换
+      this.pileOrLine = type
+      if (type === 'pile') {
+        this.setPile()
+      } else {
+        this.setLine()
+      }
+    },
+    handlerXdata(arr) {
+      if (!this.seriesData[0]) return []
+      let newArr = []
+      if (this.seriesData[0].dayTime.length <= 12) {
+        newArr = this.seriesData[0].dayTime
+      } else {
+        const gap = Math.floor(this.seriesData[0].dayTime.length / 12)
+        newArr = this.seriesData[0].dayTime.filter((item, index) => {
+          return index % gap === 0
+        })
+      }
+      return newArr
+    },
+    setPile() {
+      this.echartsOption = {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            label: {
+              backgroundColor: '#6a7985'
+            }
+          }
+        },
+        legend: {
+          data: this.seriesData.map(item => item.name),
+          left: '0%'
+        },
+        grid: { left: '0%', right: '5%', bottom: '5%', top: '10%', containLabel: true },
+        xAxis: [
+          {
+            type: 'category',
+            boundaryGap: false,
+            data: this.handlerXdata(this.seriesData)
+          }
+        ],
+        yAxis: [{ type: 'value' }],
+        series: this.seriesData
+      }
+    },
+    setLine() {
+      this.echartsOption = {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'cross',
+            label: {
+              backgroundColor: '#6a7985'
+            }
+          }
+        },
+        legend: {
+          data: this.seriesData.map(item => item.name),
+          left: '0%'
+        },
+        grid: { left: '0%', right: '5%', bottom: '5%', top: '10%', containLabel: true },
+        xAxis: [
+          {
+            type: 'category',
+            boundaryGap: false,
+            data: this.handlerXdata(this.seriesData)
+          }
+        ],
+        yAxis: [{ type: 'value' }],
+        series: this.seriesData.map(item => {
+          return {
+            ...item,
+            areaStyle: null
+          }
+        })
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.control {
+  display: flex;
+  justify-content: flex-end;
+  width: 84%;
+  margin: 20px auto;
+  .pile-line{
+    font-size: 14px;
+    width: 100px;
+    display: inline-block;
+    padding: 6px 10px;
+    margin-left: 20px;
+    color: #50A6FF;
+    border: 1px solid #50A6FF;
+    border-radius: 4px;
+    text-align: center;
+    cursor: pointer;
+  }
+  .active {
+    color: #ffffff;
+    background: #50A6FF;
+  }
+}
+.chart-contain {
+  position: relative;
+  height: 400px;
+  width: 84%;
+  margin: auto;
+  margin-top: 20px;
+}
+</style>

+ 76 - 0
src/views/quality/components/statusStayChart.vue

@@ -0,0 +1,76 @@
+<template>
+  <section>
+    <div class="chart-contain">
+      <normal-echart v-if="echartsOption" :chart-id="id" :option="echartsOption" />
+    </div>
+  </section>
+</template>
+<script>
+import normalEchart from '@/components/chart/normalEchart'
+export default {
+  components: { normalEchart },
+  props: {
+    id: {
+      type: String,
+      default: 'status-stay-chart',
+      required: false
+    },
+    chartData: {
+      type: Object,
+      default: () => null,
+      required: false
+    }
+  },
+  data() {
+    return {
+      echartsOption: null
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.setChart()
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    setChart() {
+      if (!this.chartData) return
+      const newArr = this.chartData.yaxis.filter(item => { return item.name !== '全部' })
+      const colorArr = ['#409EFF', '#F8CE5C', '#F2904F', '#5EE2BE', '#D873F5', '#7479F5']
+      this.echartsOption = {
+        color: colorArr,
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { type: 'line' },
+          formatter: params => {
+            let total = 0
+            let backString = ``
+            params.map((item, index) => {
+              total = total + item.value
+              backString = backString + `<span style="color: ${colorArr[index] || ''}">${item.seriesName}</span>:${item.value}个</br>`
+            })
+            return backString + `<span style="color: #F04864">总和</span>:${total}个`
+          }
+        },
+        legend: { data: newArr.map(item => { return item.name }), left: 0, top: 0 },
+        grid: { left: '0', right: '0', top: '8%', bottom: '0', containLabel: true },
+        xAxis: { type: 'category', data: this.chartData.xaxis, axisTick: { alignWithLabel: true }},
+        yAxis: { type: 'value', axisLine: { show: false }, splitLine: { lineStyle: { type: 'dashed' }}, axisLabel: { formatter: '{value}个' }},
+        series: newArr.map(item => ({ ...item, type: 'bar', stack: '总和', barWidth: '20px' }))
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.chart-contain {
+  position: relative;
+  height: 400px;
+  width: 84%;
+  margin: 20px auto;
+}
+
+</style>

+ 72 - 0
src/views/quality/components/tendencyChart.vue

@@ -0,0 +1,72 @@
+<template>
+  <section>
+    <div class="chart-contain">
+      <normal-echart v-if="echartsOption" :chart-id="id" :option="echartsOption" />
+    </div>
+  </section>
+</template>
+<script>
+import normalEchart from '@/components/chart/normalEchart'
+export default {
+  components: { normalEchart },
+  props: {
+    id: {
+      type: String,
+      default: 'tendencyChart',
+      required: false
+    },
+    chartData: {
+      type: Object,
+      default: () => null,
+      required: false
+    },
+    typeTitle: {
+      type: String,
+      default: '需求数量',
+      required: false
+    }
+  },
+  data() {
+    return {
+      echartsOption: null
+    }
+  },
+  watch: {
+    chartData: {
+      handler(newV) {
+        this.setChart()
+      },
+      immediate: true
+    }
+  },
+  mounted() {
+    this.setChart()
+  },
+  methods: {
+    setChart() {
+      if (!this.chartData) return
+      this.echartsOption = {
+        color: ['#3AA1FF'],
+        tooltip: { trigger: 'axis', axisPointer: { type: 'line' }}, // 默认为直线,可选为:'line' | 'shadow'
+        grid: { left: '0', right: '0', top: '5%', bottom: '0', containLabel: true },
+        xAxis: [{ type: 'category', data: this.chartData.xaxis, axisLabel: { interval: this.chartData.xaxis.length > 15 ? 1 : 0, rotate: 15 }, axisTick: { alignWithLabel: true }}],
+        yAxis: [{ type: 'value', axisLine: { show: false }, splitLine: { lineStyle: { type: 'dashed' }}}],
+        series: [
+          {
+            name: this.typeTitle, type: 'line', barWidth: '20px', smooth: true, data: this.chartData.yaxis[0] && this.chartData.yaxis[0].data || [],
+            itemStyle: { normal: { label: { show: true, formatter: '{c}', position: 'top' }}}
+          }
+        ]
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.chart-contain {
+  position: relative;
+  height: 400px;
+  width: 84%;
+  margin: 20px auto;
+}
+</style>

+ 11 - 11
src/views/quality/defectStatistics.vue

@@ -193,7 +193,7 @@
           :border="false"
           :max-height="350"
           :tree-props="{children: 'childModuleDatas', hasChildren: 'hasChildren'}"
-          :header-cell-style="{background:'#FAFAFA',color: '#444444'}"
+          :header-cell-style="{background: 'rgba(232,232,232,0.6)',color:'#333333'}"
           class="repair-table"
           @sort-change="moduleTableSortChange"
         >
@@ -226,14 +226,14 @@
               <span class="repair-span">{{ scope.row.repairData.detail[1].label }}:{{ scope.row.repairData.detail[1].total }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="repairTimeAvgData" label="平均修复时长">
+          <el-table-column prop="repairTimeAvgData" label="平均修复时长" min-width="120">
             <template slot-scope="scope">
               <span class="table-repair-item3 repair-span">{{ scope.row.repairTimeAvgData.total }}</span>
               <span class="repair-span">{{ scope.row.repairTimeAvgData.detail[0].label }}:{{ scope.row.repairTimeAvgData.detail[0].total }}</span>
               <span class="repair-span">{{ scope.row.repairTimeAvgData.detail[1].label }}:{{ scope.row.repairTimeAvgData.detail[1].total }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="repairTimeAvgPurgeNhData" label="平均修复时长(去除节假日)">
+          <el-table-column prop="repairTimeAvgPurgeNhData" label="平均修复时长(去除节假日)" min-width="120">
             <template slot-scope="scope">
               <span class="table-repair-item4 repair-span">{{ scope.row.repairTimeAvgPurgeNhData.total }}</span>
               <span class="repair-span">{{ scope.row.repairTimeAvgPurgeNhData.detail[0].label }}:{{ scope.row.repairTimeAvgData.detail[0].total }}</span>
@@ -251,7 +251,7 @@
           :border="false"
           :max-height="350"
           :tree-props="{children: 'childModuleDatas', hasChildren: 'hasChildren'}"
-          :header-cell-style="{background:'#FAFAFA',color: '#444444'}"
+          :header-cell-style="{background: 'rgba(232,232,232,0.6)',color:'#333333'}"
           class="repair-table"
           @sort-change="memberTableSortChange"
         >
@@ -284,14 +284,14 @@
               <span class="repair-span">{{ scope.row.repairData.detail[1].label }}:{{ scope.row.repairData.detail[1].total }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="repairTimeAvgData" label="平均修复时长">
+          <el-table-column prop="repairTimeAvgData" label="平均修复时长" min-width="120">
             <template slot-scope="scope">
               <span class="table-repair-item3 repair-span">{{ scope.row.repairTimeAvgData.total }}</span>
               <span class="repair-span">{{ scope.row.repairTimeAvgData.detail[0].label }}:{{ scope.row.repairTimeAvgData.detail[0].total }}</span>
               <span class="repair-span">{{ scope.row.repairTimeAvgData.detail[1].label }}:{{ scope.row.repairTimeAvgData.detail[1].total }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="repairTimeAvgPurgeNhData" label="平均修复时长(去除节假日)">
+          <el-table-column prop="repairTimeAvgPurgeNhData" label="平均修复时长(去除节假日)" min-width="120">
             <template slot-scope="scope">
               <span class="table-repair-item4 repair-span">{{ scope.row.repairTimeAvgPurgeNhData.total }}</span>
               <span class="repair-span">{{ scope.row.repairTimeAvgPurgeNhData.detail[0].label }}:{{ scope.row.repairTimeAvgData.detail[0].total }}</span>
@@ -333,8 +333,6 @@ export default {
       defectForm: {}, // 筛选表单
       stratAndEnd: [], // 开始结束日期
       dateType: 'week', // 时间选择类型
-      teamsOptions: [{ code: 1, label: '团队' }],
-      modulesOptions: [{ code: 1, label: '模块' }],
       moduleList: [], // 模块列表
       activeName: 'first',
       activeTab: 1,
@@ -771,8 +769,7 @@ export default {
   }
   .data-total {
     display: flex;
-    width: 94%;
-    margin: auto;
+    width: 100%;
     justify-content: space-between;
     padding-bottom: 40px;
     ul,li{
@@ -780,7 +777,7 @@ export default {
     }
     .data-item {
       position: relative;
-      width: 27%;
+      width: 25%;
       border-radius: 8px;
       article {
         display: flex;
@@ -976,14 +973,17 @@ export default {
       .table-repair-item1,.table-repair-item4 {
         color:#4089FF;
         margin-right: 5px;
+        font-size: 16px;
       }
       .table-repair-item2 {
         color:#47D4D5;
         margin-right: 5px;
+        font-size: 16px;
       }
       .table-repair-item3 {
         color:#FD978A;
         margin-right: 5px;
+        font-size: 16px;
       }
     }
     .chart-contain {

+ 602 - 0
src/views/quality/requireStatistics.vue

@@ -0,0 +1,602 @@
+<template>
+  <el-container class="defect-container">
+    <el-header class="defect-main" style="height: auto;">
+      <el-form :model="chartForm" class="demo-form-inline" :inline="true">
+        <el-form-item label="时间:">
+          <div class="date-select">
+            <span :class="[dateType==='week'?'date-active':'']" class="date-item" @click.stop="dateType='week';setDate('week')">本周</span>
+            <span :class="[dateType==='month'?'date-active':'']" class="date-item" @click.stop="dateType='month';setDate('month')">本月</span>
+            <span :class="[dateType==='year'?'date-active':'']" class="date-item" @click.stop="dateType='year';setDate('year')">本年</span>
+          </div>
+          <el-date-picker
+            v-model="stratAndEnd"
+            type="daterange"
+            align="right"
+            unlink-panels
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            size="small"
+            value-format="yyyy.MM.dd"
+            :default-time="['00:00:00','23:59:59']"
+            @change="dateType = ''"
+          />
+        </el-form-item>
+      </el-form>
+      <el-form :model="chartForm" class="demo-form-inline" :inline="true">
+        <el-form-item label="团队:">
+          <el-select
+            v-model="chartForm.team"
+            placeholder="请选择"
+            size="small"
+            clearable
+            filterable
+            multiple
+          >
+            <el-option-group
+              v-for="group in teamOptions"
+              :key="group.label"
+              :label="group.label"
+            >
+              <el-option
+                v-for="item in group.options"
+                :key="item.teamId"
+                :label="item.teamName"
+                :value="item.teamId"
+              />
+            </el-option-group>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="需求方向:">
+          <el-cascader
+            v-model="chartForm.rqmtOrntIds"
+            size="small"
+            clearable
+            collapse-tags
+            :props="{
+              value: 'id',
+              label: 'rqmtOrntName',
+              children: 'childRqmtOrnts',
+              multiple: true
+            }"
+            :options="moduleList"
+            placeholder="请选择"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" size="mini" @click="onSubmit">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <div class="data-total">
+        <div v-for="(item,index) in Summary" :key="'Summary'+index" class="data-item" :class="['item'+ (index + 1)]">
+          <article>
+            <div class="item-top">
+              <div class="item-circle">
+                <img v-show="index === 0" src="../../../src/assets/defect_images/add.png">
+                <img v-show="index === 1" src="../../../src/assets/defect_images/repair.png">
+                <img v-show="index === 2" src="../../../src/assets/defect_images/reopen.png">
+                <img v-show="index === 3" src="../../../src/assets/defect_images/reopen.png">
+              </div>
+              <span>{{ item.label }}</span>
+              <el-tooltip v-if="index === 0" class="item" effect="dark" content="统计区间内,新建的需求数量" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 1" class="item" effect="dark" content="需求最近一次更新状态为“PRD评审通过”在统计区间内,并且当前状态是“PRD评审通过”及之后状态的需求数量" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 2" class="item" effect="dark" content="需求最近一次更新状态为“已上线”在统计区间内,并且当前状态是“已上线”及之后状态的需求数量" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 3" class="item" effect="dark" content="需求最近一次更新状态为“Hold”在统计区间内,并且当前状态为“Hold”的需求数量" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+            </div>
+            <div class="item-title">{{ item.total }}</div>
+            <div class="item-line" />
+            <div v-show="Number(item.relativeRatio)>=0" class="item-up">环比:<i class="el-icon-caret-top" />{{ item.relativeRatio }}%</div>
+            <div v-show="Number(item.relativeRatio)<0" class="item-down">环比:<i class="el-icon-caret-bottom" />{{ item.relativeRatio.substring(1,item.relativeRatio.length) }}%</div>
+            <div v-show="item.relativeRatio === '--'" class="item-down">环比:{{ item.relativeRatio }}%</div>
+          </article>
+        </div>
+      </div>
+      <div class="chart-item bottom-padding">
+        <h3>状态累积流量图</h3>
+        <status-chart :chart-data="cumulativeData" />
+      </div>
+    </el-header>
+    <el-main class="charts-main">
+      <el-tabs v-model="activeTab" class="tab-change" @tab-click="onSubmit">
+        <el-tab-pane name="1">
+          <span slot="label" class="tab-item">累计新增</span>
+        </el-tab-pane>
+        <el-tab-pane name="2">
+          <span slot="label" class="tab-item">累计上线</span>
+        </el-tab-pane>
+        <el-tab-pane name="3">
+          <span slot="label" class="tab-item">累计PRD评审通过</span>
+        </el-tab-pane>
+      </el-tabs>
+      <div class="chart-item">
+        <h3 v-show="activeTab === '1'">新增趋势图</h3>
+        <h3 v-show="activeTab === '2'">上线趋势图</h3>
+        <h3 v-show="activeTab === '3'">PRD评审趋势图</h3>
+        <tendency-chart :chart-data="tendencyData" />
+      </div>
+      <div class="chart-item">
+        <h3>周期统计<span>(根据排期计算)</span></h3>
+        <div class="chart-item-tip">
+          <i class="el-icon-warning-outline" />
+          <span>仅统计状态已变更“已排期”且排期不为空的需求</span>
+        </div>
+        <cycle-statistic :chart-data="cycleData" />
+      </div>
+      <div class="chart-item">
+        <h3>研发交付周期分布图<span>(根据排期计算)</span></h3>
+        <div class="chart-item-tip">
+          <i class="el-icon-warning-outline" />
+          <span>仅统计状态已变更“已排期”且排期不为空的需求;横坐标表示需求交付日期,纵坐标代表研发交付周期(研发、联调、上线类型排期的总周期)</span>
+        </div>
+        <development-cycle :chart-data="developmentCycleData" />
+      </div>
+      <div class="chart-item">
+        <h3>需求分布图</h3>
+        <distribution-chart
+          :chart-data="distributeData"
+          :status-list="distributeStatusList"
+          :status.sync="distributeStatus"
+          :active-tab="activeTab"
+          @change="getDistributeData()"
+        />
+      </div>
+      <div class="chart-item">
+        <h3>状态停留分布图</h3>
+        <status-stay-chart :chart-data="statusStayData" />
+      </div>
+      <div class="chart-item">
+        <h3>需求方向分布图</h3>
+        <belong-chart :chart-data="orntDistributeData" />
+      </div>
+      <div class="chart-item">
+        <h3>排期发生变更的任务(<span class="strong-font">{{ changeTotal }}</span>个)</h3>
+        <change-require-chart :chart-data="changeRequireData" />
+      </div>
+      <div class="chart-item">
+        <h3>缺陷统计</h3>
+        <bug-list :chart-data="bugListData" />
+      </div>
+    </el-main>
+  </el-container>
+</template>
+<script>
+import moment from 'moment'
+moment.locale('zh-cn')
+import { settingQueryBizRqmtOrntList } from '@/api/requirement'
+import { teamQueryTeamInfoList } from '@/api/configure'
+import {
+  getSummary,
+  getCumulativeFlowDiagram,
+  getRequireCountTrend,
+  getRequirePeriodicData,
+  getRequireRdDeliveryPeriodicData,
+  getDistributeData,
+  getStatusStayData,
+  getOrntDistributeData,
+  getReqUnlockData,
+  getBugStatisticData
+} from '@/api/statisticsApi/requireStatistics'
+import statusChart from './components/statusChart'
+import tendencyChart from './components/tendencyChart'
+import cycleStatistic from './components/cycleStatistic'
+import developmentCycle from './components/developmentCycle'
+import distributionChart from './components/distributionChart'
+import statusStayChart from './components/statusStayChart'
+import belongChart from './components/belongChart'
+import changeRequireChart from './components/changeRequireChart'
+import bugList from './components/bugList'
+export default {
+  components: {
+    statusChart,
+    tendencyChart,
+    cycleStatistic,
+    developmentCycle,
+    distributionChart,
+    statusStayChart,
+    belongChart,
+    changeRequireChart,
+    bugList
+  },
+  data() {
+    return {
+      bugCountTimeType: 1, // 获取趋缺陷势图数据接口入参:1本周 2本月 3本年
+      timeTypeList: [ // 日期选择
+        { code: 1, label: '周' },
+        { code: 2, label: '月' },
+        { code: 3, label: '年' }
+      ],
+      teamOptions: [], // 团队列表
+      chartForm: {}, // 筛选表单
+      stratAndEnd: [], // 开始结束日期
+      dateType: 'week', // 时间选择类型
+      moduleList: [], // 需求方向列表
+      Summary: [], // 顶部数据
+      activeTab: '1', // tab标签
+      cumulativeData: null, // 需求状态累计流数据
+      tendencyData: null, // 趋势图数据
+      cycleData: [], // 周期统计数据
+      developmentCycleData: [], // 研发交付周期分布数据
+      distributeStatus: 1, // 需求分布图需求状态
+      distributeStatusList: [
+        { code: 1, label: '需求状态' },
+        { code: 2, label: '需求等级' },
+        { code: 3, label: '需求类型' },
+        { code: 4, label: 'pm' },
+        { code: 5, label: '跟版客户端' }
+      ], // 需求分布图需求状态列表
+      orntDistributeData: null, // 所属需求方向数据
+      distributeData: null, // 需求分布图数据
+      statusStayData: null, // 状态停留图数据
+      moduleDistribute: null, // 模块分布图数据
+      changeRequireData: null, // 排期变更需求数据
+      changeTotal: 0, // 变更总数
+      bugListData: {} // 缺陷统计数据
+    }
+  },
+  computed: {
+    globalParams: { // 通用接口参数
+      get() {
+        const rqmtOrntIds = this.chartForm.rqmtOrntIds
+        const team = this.chartForm.team
+        const params = {
+          startTime: this.stratAndEnd[0] || null,
+          endTime: this.stratAndEnd[1] || null,
+          bizId: Number(localStorage.getItem('bizId')),
+          teamIds: team && team.length > 0 ? team : null,
+          rqmtOrntIds: rqmtOrntIds && rqmtOrntIds.length > 0 ? rqmtOrntIds : null
+        }
+        return params
+      }
+    },
+    timeType: {
+      get() {
+        let timeType = 0
+        switch (this.dateType) {
+          case 'week':
+            timeType = 1
+            break
+          case 'month':
+            timeType = 2
+            break
+          case 'year':
+            timeType = 3
+            break
+          default:
+            timeType = 0
+            break
+        }
+        return timeType
+      }
+    }
+  },
+  created() {
+    this.$store.state.data.status = true
+    this.setDate(this.dateType)
+    this.getTeamList()
+    this.getRequireDirection()
+  },
+  methods: {
+    onSubmit() {
+      if (this.activeTab === '2' && this.distributeStatus === 1) {
+        this.distributeStatus = 2
+      }
+      this.getSummary()
+      this.getCumulativeFlowDiagram()
+      this.getRequireCountTrend()
+      this.getCycleData()
+      this.getDevelopmentCycle()
+      this.getOrntDistributeData()
+      this.getDistributeData()
+      this.getStatusStayData()
+      this.getReqUnlockData()
+      this.getBugStatisticData()
+    },
+    setDate(type) { // 日期筛选
+      let startDate = null
+      let endDate = null
+      switch (type) {
+        case 'week':
+          startDate = moment().startOf('week').format('YYYY.MM.DD')
+          endDate = moment().endOf('week').format('YYYY.MM.DD')
+          this.bugCountTimeType = this.timeTypeList[0].code
+          break
+        case 'month':
+          startDate = moment().startOf('month').format('YYYY.MM.DD')
+          endDate = moment().endOf('month').format('YYYY.MM.DD')
+          this.bugCountTimeType = this.timeTypeList[1].code
+          break
+        case 'year':
+          startDate = moment().startOf('year').format('YYYY.MM.DD')
+          endDate = moment().endOf('year').format('YYYY.MM.DD')
+          this.bugCountTimeType = this.timeTypeList[2].code
+          break
+      }
+      this.stratAndEnd = [startDate, endDate]
+      this.onSubmit()
+    },
+    async getRequireDirection() { // 需求方向列表
+      const res = await settingQueryBizRqmtOrntList(Number(localStorage.getItem('bizId')))
+      if (res.code === 200) {
+        this.moduleList = this.getRequireData(res.data)
+      }
+    },
+    getRequireData(data) {
+      for (let i = 0; i < data.length; i++) {
+        data[i].childRqmtOrnts.length < 1 ? delete data[i].childRqmtOrnts : this.getRequireData(data[i].childRqmtOrnts)
+      }
+      return data
+    },
+    async getTeamList() { // 获取我的团队和所有团队
+      const myRes = await teamQueryTeamInfoList({ type: 0 })// 我的团队
+      const allRes = await teamQueryTeamInfoList({ type: 1 })// 所有团队
+      this.teamOptions = [
+        { label: '我的团队', options: [...myRes.data.list] },
+        { label: '全部团队', options: [...allRes.data.list] }
+      ]
+    },
+    async getSummary() { // 获取顶部数据
+      const res = await getSummary(this.globalParams)
+      if (res.code === 200) this.Summary = res.data || []
+    },
+    async getRequireCountTrend() { // 趋势图日期变动
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab),
+        timeType: this.timeType
+      }
+      const res = await getRequireCountTrend(params)
+      if (res.code === 200) this.tendencyData = res.data
+    },
+    async getCumulativeFlowDiagram() { // 需求状态累计流图
+      const params = {
+        ...this.globalParams,
+        timeType: this.timeType
+      }
+      const res = await getCumulativeFlowDiagram(params)
+      if (res.code === 200) this.cumulativeData = res.data
+    },
+    async getCycleData() { // 周期统计数据
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getRequirePeriodicData(params)
+      if (res.code === 200) this.cycleData = res.data
+    },
+    async getDevelopmentCycle() { // 获取研发交付周期分布数据
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getRequireRdDeliveryPeriodicData(params)
+      if (res.code === 200) {
+        res.data.length > 0
+          ? this.developmentCycleData = res.data
+          : this.developmentCycleData = [moment().format('YYYY-MM-DD'), '0']
+      }
+    },
+    async getDistributeData() { // 获取需求分布图
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab),
+        distributeType: this.distributeStatus
+      }
+      const res = await getDistributeData(params)
+      if (res.code === 200) this.distributeData = res.data
+    },
+    async getStatusStayData() { // 状态停留时长分布
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getStatusStayData(params)
+      if (res.code === 200) this.statusStayData = res.data
+    },
+    async getOrntDistributeData() { // 获取需求方向分布
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getOrntDistributeData(params)
+      if (res.code === 200) this.orntDistributeData = this.handlerData(res.data)
+    },
+    handlerData(arr) { // 处理需求方向分布图数据
+      const bfs = (arr) => {
+        for (const item of arr) {
+          if (item.children && item.children.length > 0) {
+            bfs(item.children)
+          } else {
+            delete item.children
+          }
+        }
+      }
+      bfs(arr)
+      return { children: arr, name: '需求方向分布' }
+    },
+    async getReqUnlockData() { // 排期发生变更的需求
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab),
+        distributeType: this.distributeStatus
+      }
+      const res = await getReqUnlockData(params)
+      if (res.code === 200) {
+        this.changeRequireData = res.data || []
+        this.changeTotal = this.changeRequireData.length
+      }
+    },
+    async getBugStatisticData() { // 缺陷统计
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getBugStatisticData(params)
+      if (res.code === 200) this.bugListData = res.data
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.defect-container {
+  width: 100%;
+  height:100%;
+  background:#F2F3F6;
+  display: inline-block;
+  color: #666666;
+  h3 {
+    font-size: 18px;
+    margin: 0;
+    padding: 0;
+    span {
+      font-size: 14px;
+      color: #666666;
+    }
+  }
+  .defect-main {
+    padding: 20px 20px 0 20px;
+    height:100%;
+    width: calc(100%-60px);
+    background:#ffffff;
+    margin: 10px;
+    border-radius: 4px;
+    .date-select {
+      line-height: 20px;
+      display: inline-block;
+      .date-item {
+        display: inline-block;
+        width: 40px;
+        text-align: center;
+        cursor: pointer;
+        border-radius: 2px;
+      }
+      .date-active {
+        color: #FFFFFF;
+        background-color: #409EFF;
+      }
+    }
+  }
+}
+.data-total {
+  display: grid;
+  width: 100%;
+  margin: auto;
+  grid-template-columns: repeat(4,22%);
+  grid-gap: 4%;
+  padding-bottom: 40px;
+  ul,li{
+    padding:0;margin:0;list-style:none
+  }
+  .data-item {
+    position: relative;
+    width: 100%;
+    border-radius: 8px;
+    article {
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      color: #FFFFFF;
+    }
+    .item-top {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      padding: 10px 20px;
+      font-size: 13px;
+      .item-circle {
+        height: 36px;
+        width: 36px;
+        border-radius: 50%;
+        background:rgba(255,255,255,0.24);
+        margin-right: 10px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        img {
+          height: 20px;
+          width: 20px;
+        }
+      }
+      span {
+        margin-right: 5px;
+      }
+    }
+    .item-line {
+      width: 100%;
+      border-bottom:1px solid rgba(255,255,255,0.2);
+    }
+    .item-title {
+      font-size: 40px;
+      padding: 5% 20px;
+    }
+    .item-up,.item-down {
+      font-size: 13px;
+      padding: 10px 20px 0 20px;
+      margin-bottom: 20px;
+    }
+    .item-up i {
+      color:#F32850
+    }
+    .item-down i {
+      color:#9FFF39
+    }
+  }
+  .item1 {
+    background-image: url('../../../src/assets/defect_images/数据1.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(64,137,255,0.30);
+  }
+  .item2 {
+    background-image: url('../../../src/assets/defect_images/数据2.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(78,188,250,0.40);
+  }
+  .item3 {
+    background-image: url('../../../src/assets/defect_images/数据3.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(255,136,134,0.30);
+  }
+  .item4 {
+    background-image: url('../../../src/assets/defect_images/数据4.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(83,97,255,0.30);
+  }
+}
+.bottom-padding {
+  padding-bottom: 30px;
+}
+.charts-main {
+  padding-bottom: 20px;
+  width:calc(100%-60px);
+  background:#ffffff;
+  margin: 10px;
+  border-radius: 4px;
+  .chart-item {
+    margin-bottom: 30px;
+  }
+  .tab-change {
+    overflow: hidden;
+  }
+  .chart-item-tip {
+    margin-top: 12px;
+    margin-bottom: 10px;
+    font-size: 12px;
+    color: #E6A23C;
+  }
+}
+.strong-font {
+  color: #F01A1A !important;
+  font-size: 18px !important;
+}
+</style>

+ 626 - 0
src/views/quality/taskStatistics.vue

@@ -0,0 +1,626 @@
+<template>
+  <el-container class="defect-container">
+    <el-header class="defect-main" style="height: auto;">
+      <el-form :model="chartForm" class="demo-form-inline" :inline="true">
+        <el-form-item label="时间:">
+          <div class="date-select">
+            <span :class="[dateType==='week'?'date-active':'']" class="date-item" @click.stop="dateType='week';setDate('week')">本周</span>
+            <span :class="[dateType==='month'?'date-active':'']" class="date-item" @click.stop="dateType='month';setDate('month')">本月</span>
+            <span :class="[dateType==='year'?'date-active':'']" class="date-item" @click.stop="dateType='year';setDate('year')">本年</span>
+          </div>
+          <el-date-picker
+            v-model="stratAndEnd"
+            type="daterange"
+            align="right"
+            unlink-panels
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            size="small"
+            value-format="yyyy.MM.dd"
+            :default-time="['00:00:00','23:59:59']"
+            @change="dateType = ''"
+          />
+        </el-form-item>
+
+      </el-form>
+      <el-form :model="chartForm" class="demo-form-inline" :inline="true">
+        <el-form-item label="团队:">
+          <el-select
+            v-model="chartForm.team"
+            placeholder="请选择"
+            size="small"
+            clearable
+            filterable
+            multiple
+          >
+            <el-option-group
+              v-for="group in teamOptions"
+              :key="group.label"
+              :label="group.label"
+            >
+              <el-option
+                v-for="item in group.options"
+                :key="item.teamId"
+                :label="item.teamName"
+                :value="item.teamId"
+              />
+            </el-option-group>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="技术模块:">
+          <el-cascader
+            v-model="chartForm.moduleIds"
+            size="small"
+            clearable
+            collapse-tags
+            :props="{ multiple: true }"
+            :options="moduleList"
+            placeholder="请选择"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" size="mini" @click="onSubmit">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <div class="data-total">
+        <div v-for="(item,index) in Summary" :key="'Summary'+index" class="data-item" :class="['item'+ (index + 1)]">
+          <article>
+            <div class="item-top">
+              <div class="item-circle">
+                <img v-show="index === 0" src="../../../src/assets/defect_images/add.png">
+                <img v-show="index === 1" src="../../../src/assets/defect_images/repair.png">
+                <img v-show="index === 2" src="../../../src/assets/defect_images/reopen.png">
+              </div>
+              <span>{{ item.label }}</span>
+              <el-tooltip v-if="index === 0" class="item" effect="dark" content="统计区间内,新建的任务数量" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 1" class="item" effect="dark" content="统计区间内,任务执行过更新状态为“已上线”的操作并且当前状态是“已上线”的任务数量" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+              <el-tooltip v-if="index === 2" class="item" effect="dark" content="统计区间内,任务执行过更新状态为“Hold”的操作并且当前状态为“Hold”的任务数量" placement="top-start">
+                <i class="el-icon-info" />
+              </el-tooltip>
+            </div>
+            <div class="item-title">{{ item.total }}</div>
+            <div class="item-line" />
+            <div v-show="Number(item.relativeRatio)>=0" class="item-up">环比:<i class="el-icon-caret-top" />{{ item.relativeRatio }}%</div>
+            <div v-show="Number(item.relativeRatio)<0" class="item-down">环比:<i class="el-icon-caret-bottom" />{{ item.relativeRatio.substring(1,item.relativeRatio.length) }}%</div>
+            <div v-show="item.relativeRatio === '--'" class="item-down">环比:{{ item.relativeRatio }}%</div>
+          </article>
+        </div>
+      </div>
+      <div class="chart-item bottom-padding">
+        <h3>状态累积流量图</h3>
+        <status-chart :chart-data="cumulativeData" />
+      </div>
+    </el-header>
+    <el-main class="charts-main">
+      <el-tabs v-model="activeTab" class="tab-change" @tab-click="onSubmit">
+        <el-tab-pane name="1">
+          <span slot="label" class="tab-item">累计新增</span>
+        </el-tab-pane>
+        <el-tab-pane name="2">
+          <span slot="label" class="tab-item">累计上线</span>
+        </el-tab-pane>
+      </el-tabs>
+      <div class="chart-item">
+        <h3 v-show="activeTab === '1'">新增趋势图</h3>
+        <h3 v-show="activeTab === '2'">上线趋势图</h3>
+        <tendency-chart :chart-data="tendencyData" type-title="任务数量" />
+      </div>
+      <div class="chart-item">
+        <h3>所属需求方向分布图</h3>
+        <belong-requirement-chart :chart-data="belongRequirementData" />
+      </div>
+      <div class="chart-item">
+        <h3>周期统计<span>(根据排期计算)</span></h3>
+        <div class="chart-item-tip">
+          <i class="el-icon-warning-outline" />
+          <span>仅统计状态已变更“已排期”且排期不为空的任务</span>
+        </div>
+        <cycle-statistic :chart-data="cycleData" type="task" />
+      </div>
+      <div class="chart-item">
+        <h3>研发交付周期分布图<span>(根据排期计算)</span></h3>
+        <div class="chart-item-tip">
+          <i class="el-icon-warning-outline" />
+          <span>仅统计状态已变更“已排期”且排期不为空的任务;横坐标表示任务交付日期,纵坐标代表研发交付周期(研发、联调、上线类型排期的总周期)</span>
+        </div>
+        <development-cycle :chart-data="developmentCycleData" />
+      </div>
+      <div class="chart-item">
+        <h3>任务分布图</h3>
+        <distribution-chart
+          :chart-data="distributeData"
+          :status-list="distributeStatusList"
+          :status.sync="distributeStatus"
+          :active-tab="activeTab"
+          @change="getDistributeData()"
+        />
+      </div>
+      <div class="chart-item">
+        <h3>状态停留分布图</h3>
+        <status-stay-chart :chart-data="statusStayData" />
+      </div>
+      <div class="chart-item">
+        <h3>模块分布图</h3>
+        <belong-chart :chart-data="moduleDistributeData" />
+      </div>
+      <div class="chart-item">
+        <h3>排期发生变更的任务(<span class="strong-font">{{ changeTotal }}</span>个)</h3>
+        <change-require-chart :chart-data="changeTaskData" />
+      </div>
+      <div class="chart-item">
+        <h3>报告统计</h3>
+        <cycle-statistic :chart-data="reportSummaryData" child-data="relativeRatio" :show-tips="false" />
+      </div>
+      <div class="chart-item">
+        <h3>缺陷统计</h3>
+        <bug-list :chart-data="bugListData" />
+      </div>
+    </el-main>
+  </el-container>
+</template>
+<script>
+import moment from 'moment'
+moment.locale('zh-cn')
+import { settingQueryBizModuleList } from '@/api/defectManage'
+import { teamQueryTeamInfoList } from '@/api/configure'
+import {
+  getSummary,
+  getCumulativeFlowDiagram,
+  getTaskCountTrend,
+  getRequirementOrientationDistributeData,
+  getTaskPeriodicData,
+  getTaskRdDeliveryPeriodicData,
+  getDistributeData,
+  getStatusStayData,
+  getModuleDistributeData,
+  getTaskUnlockData,
+  getReportSummary,
+  getBugStatisticData
+} from '@/api/statisticsApi/taskStatistics'
+import statusChart from './components/statusChart'
+import tendencyChart from './components/tendencyChart'
+import belongRequirementChart from './components/belongRequirementChart'
+import cycleStatistic from './components/cycleStatistic'
+import developmentCycle from './components/developmentCycle'
+import distributionChart from './components/distributionChart'
+import statusStayChart from './components/statusStayChart'
+import belongChart from './components/belongChart'
+import changeRequireChart from './components/changeRequireChart'
+import bugList from './components/bugList'
+export default {
+  components: {
+    statusChart,
+    tendencyChart,
+    belongRequirementChart,
+    cycleStatistic,
+    developmentCycle,
+    distributionChart,
+    statusStayChart,
+    belongChart,
+    changeRequireChart,
+    bugList
+  },
+  data() {
+    return {
+      bugCountTimeType: 1, // 获取趋缺陷势图数据接口入参:1本周 2本月 3本年
+      timeTypeList: [ // 日期选择
+        { code: 1, label: '周' },
+        { code: 2, label: '月' },
+        { code: 3, label: '年' }
+      ],
+      teamOptions: [], // 团队列表
+      chartForm: {}, // 筛选表单
+      stratAndEnd: [], // 开始结束日期
+      dateType: 'week', // 时间选择类型
+      moduleList: [], // 任务方向列表
+      Summary: [], // 顶部数据
+      activeTab: '1', // tab标签
+      cumulativeData: null, // 任务状态累计流数据
+      tendencyData: null, // 趋势图数据
+      belongRequirementData: null, // 所属需求方向分布图
+      cycleData: [], // 周期统计数据
+      developmentCycleData: [], // 研发交付周期分布数据
+      distributeStatus: 1, // 任务分布图任务状态
+      distributeStatusList: [
+        { code: 1, label: '任务状态' },
+        { code: 2, label: '任务等级' },
+        { code: 3, label: '开发负责人' },
+        { code: 4, label: '测试负责人' },
+        { code: 5, label: '跟版客户端' },
+        { code: 6, label: '直接归属' }
+      ], // 任务分布图任务状态列表
+      moduleDistributeData: null, // 任务模块分布
+      distributeData: null, // 任务分布图数据
+      statusStayData: null, // 状态停留图数据
+      moduleDistribute: null, // 模块分布图数据
+      changeTaskData: null, // 排期变更任务数据
+      changeTotal: 0, // 变更总数
+      reportSummaryData: null, // 报告统计数据
+      bugListData: {} // 缺陷统计数据
+    }
+  },
+  computed: {
+    globalParams: { // 通用接口参数
+      get() {
+        const moduleIds = this.chartForm.moduleIds
+        const team = this.chartForm.team
+        const params = {
+          startTime: this.stratAndEnd[0] || null,
+          endTime: this.stratAndEnd[1] || null,
+          bizId: Number(localStorage.getItem('bizId')),
+          teamIds: team && team.length > 0 ? team : null,
+          moduleIds: moduleIds && moduleIds.length > 0 ? moduleIds : null
+        }
+        return params
+      }
+    },
+    timeType: {
+      get() {
+        let timeType = 0
+        switch (this.dateType) {
+          case 'week':
+            timeType = 1
+            break
+          case 'month':
+            timeType = 2
+            break
+          case 'year':
+            timeType = 3
+            break
+          default:
+            timeType = 0
+            break
+        }
+        return timeType
+      }
+    }
+  },
+  created() {
+    this.$store.state.data.status = true
+    this.setDate(this.dateType)
+    this.getTeamList()
+    this.getMoudlesList()
+  },
+  methods: {
+    onSubmit() {
+      if (this.activeTab === '2' && this.distributeStatus === 1) {
+        this.distributeStatus = 2
+      }
+      this.getSummary()
+      this.getCumulativeFlowDiagram()
+      this.getTaskCountTrend()
+      this.getBelongRequirementData()
+      this.getCycleData()
+      this.getDevelopmentCycle()
+      this.getModuleDistributeData()
+      this.getDistributeData()
+      this.getStatusStayData()
+      this.getTaskUnlockData()
+      this.getReportSummary()
+      this.getBugStatisticData()
+    },
+    setDate(type) { // 日期筛选
+      let startDate = null
+      let endDate = null
+      switch (type) {
+        case 'week':
+          startDate = moment().startOf('week').format('YYYY.MM.DD')
+          endDate = moment().endOf('week').format('YYYY.MM.DD')
+          this.bugCountTimeType = this.timeTypeList[0].code
+          break
+        case 'month':
+          startDate = moment().startOf('month').format('YYYY.MM.DD')
+          endDate = moment().endOf('month').format('YYYY.MM.DD')
+          this.bugCountTimeType = this.timeTypeList[1].code
+          break
+        case 'year':
+          startDate = moment().startOf('year').format('YYYY.MM.DD')
+          endDate = moment().endOf('year').format('YYYY.MM.DD')
+          this.bugCountTimeType = this.timeTypeList[2].code
+          break
+      }
+      this.stratAndEnd = [startDate, endDate]
+      this.onSubmit()
+    },
+    async getMoudlesList() { // 技术模块
+      const res = await settingQueryBizModuleList(Number(localStorage.getItem('bizId')))
+      if (res.code === 200) {
+        this.moduleList = this.handlerModules(res.data)
+      }
+    },
+    handlerModules(arr) {
+      return arr.map(item => ({
+        ...item,
+        value: item.id,
+        label: item.moduleName,
+        children: item.childModules.length ? this.handlerModules(item.childModules) : null
+      }))
+    },
+    async getTeamList() { // 获取我的团队和所有团队
+      const myRes = await teamQueryTeamInfoList({ type: 0 })// 我的团队
+      const allRes = await teamQueryTeamInfoList({ type: 1 })// 所有团队
+      this.teamOptions = [
+        { label: '我的团队', options: [...myRes.data.list] },
+        { label: '全部团队', options: [...allRes.data.list] }
+      ]
+    },
+    async getSummary() { // 获取顶部数据
+      const res = await getSummary(this.globalParams)
+      if (res.code === 200) this.Summary = res.data || []
+    },
+    async getTaskCountTrend() { // 趋势图日期变动
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab),
+        timeType: this.timeType
+      }
+      const res = await getTaskCountTrend(params)
+      if (res.code === 200) this.tendencyData = res.data
+    },
+    async getBelongRequirementData() { // 所属需求方向分布图
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getRequirementOrientationDistributeData(params)
+      if (res.code === 200) this.belongRequirementData = res.data
+    },
+    async getCumulativeFlowDiagram() { // 任务状态累计流图
+      const params = {
+        ...this.globalParams,
+        timeType: this.timeType
+      }
+      const res = await getCumulativeFlowDiagram(params)
+      if (res.code === 200) this.cumulativeData = res.data
+    },
+    async getCycleData() { // 周期统计数据
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getTaskPeriodicData(params)
+      if (res.code === 200) this.cycleData = res.data
+    },
+    async getDevelopmentCycle() { // 获取研发交付周期分布数据
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getTaskRdDeliveryPeriodicData(params)
+      if (res.code === 200) {
+        res.data.length > 0
+          ? this.developmentCycleData = res.data
+          : this.developmentCycleData = [moment().format('YYYY-MM-DD'), '0']
+      }
+    },
+    async getDistributeData() { // 获取任务分布图
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab),
+        distributeType: this.distributeStatus
+      }
+      const res = await getDistributeData(params)
+      if (res.code === 200) this.distributeData = res.data
+    },
+    async getStatusStayData() { // 状态停留时长分布
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getStatusStayData(params)
+      if (res.code === 200) this.statusStayData = res.data
+    },
+    async getModuleDistributeData() { // 获取模块分布
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getModuleDistributeData(params)
+      if (res.code === 200) this.moduleDistributeData = this.handlerData(res.data)
+    },
+    handlerData(arr) { // 处理任务模块分布图数据
+      const bfs = (arr) => {
+        for (const item of arr) {
+          if (item.children && item.children.length > 0) {
+            delete item.value
+            bfs(item.children)
+          } else {
+            delete item.children
+          }
+        }
+      }
+      bfs(arr)
+      return { children: arr, name: '模块分布' }
+    },
+    async getTaskUnlockData() { // 排期发生变更的任务
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab),
+        distributeType: this.distributeStatus
+      }
+      const res = await getTaskUnlockData(params)
+      if (res.code === 200) {
+        this.changeTaskData = res.data || []
+        this.changeTotal = this.changeTaskData.length
+      }
+    },
+    async getReportSummary() { // 报告统计
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getReportSummary(params)
+      if (res.code === 200) this.reportSummaryData = res.data
+    },
+    async getBugStatisticData() { // 缺陷统计
+      const params = {
+        ...this.globalParams,
+        type: Number(this.activeTab)
+      }
+      const res = await getBugStatisticData(params)
+      if (res.code === 200) this.bugListData = res.data
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.defect-container {
+  width: 100%;
+  height:100%;
+  background:#F2F3F6;
+  display: inline-block;
+  color: #666666;
+  h3 {
+    font-size: 18px;
+    margin: 0;
+    padding: 0;
+    span {
+      font-size: 14px;
+      color: #666666;
+    }
+  }
+  .defect-main {
+    padding: 20px 20px 0 20px;
+    height:100%;
+    width: calc(100%-60px);
+    background:#ffffff;
+    margin: 10px;
+    border-radius: 4px;
+    .date-select {
+      line-height: 20px;
+      display: inline-block;
+      .date-item {
+        display: inline-block;
+        width: 40px;
+        text-align: center;
+        cursor: pointer;
+        border-radius: 2px;
+      }
+      .date-active {
+        color: #FFFFFF;
+        background-color: #409EFF;
+      }
+    }
+  }
+}
+.data-total {
+  display: grid;
+  width: 100%;
+  margin: auto;
+  grid-template-columns: repeat(3,25%);
+  grid-gap: 12.5%;
+  padding-bottom: 40px;
+  ul,li{
+    padding:0;margin:0;list-style:none
+  }
+  .data-item {
+    position: relative;
+    width: 100%;
+    border-radius: 8px;
+    article {
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      color: #FFFFFF;
+    }
+    .item-top {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      padding: 10px 20px;
+      font-size: 13px;
+      .item-circle {
+        height: 36px;
+        width: 36px;
+        border-radius: 50%;
+        background:rgba(255,255,255,0.24);
+        margin-right: 10px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        img {
+          height: 20px;
+          width: 20px;
+        }
+      }
+      span {
+        margin-right: 5px;
+      }
+    }
+    .item-line {
+      width: 100%;
+      border-bottom:1px solid rgba(255,255,255,0.2);
+    }
+    .item-title {
+      font-size: 40px;
+      padding: 5% 20px;
+    }
+    .item-up,.item-down {
+      font-size: 13px;
+      padding: 10px 20px 0 20px;
+      margin-bottom: 20px;
+    }
+    .item-up i {
+      color:#F32850
+    }
+    .item-down i {
+      color:#9FFF39
+    }
+  }
+  .item1 {
+    background-image: url('../../../src/assets/defect_images/数据1.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(64,137,255,0.30);
+  }
+  .item2 {
+    background-image: url('../../../src/assets/defect_images/数据2.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(78,188,250,0.40);
+  }
+  .item3 {
+    background-image: url('../../../src/assets/defect_images/数据3.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(255,136,134,0.30);
+  }
+  .item4 {
+    background-image: url('../../../src/assets/defect_images/数据4.png');
+    background-size: 100% 100%;
+    background-position: 50% 50%;
+    box-shadow:0px 50px 24px -25px rgba(83,97,255,0.30);
+  }
+}
+.bottom-padding {
+  padding-bottom: 30px;
+}
+.charts-main {
+  padding-bottom: 20px;
+  width:calc(100%-60px);
+  background:#ffffff;
+  margin: 10px;
+  border-radius: 4px;
+  .chart-item {
+    margin-bottom: 30px;
+  }
+  .tab-change {
+    overflow: hidden;
+  }
+  .chart-item-tip {
+    margin-top: 12px;
+    margin-bottom: 10px;
+    font-size: 12px;
+    color: #E6A23C;
+  }
+}
+.strong-font {
+  color: #F01A1A !important;
+  font-size: 18px !important;
+}
+</style>

+ 0 - 0
src/views/reportManagement/daily/components/DailyDetails.vue → src/views/reportManagement/daily/components/dailyDetails.vue


+ 17 - 1
src/views/workbench/team/components/needsList.vue

@@ -5,6 +5,12 @@
       <span :class="{'color-blue': status===1}" @click="setStatus(1)">进行中的需求</span>
       <span :class="{'color-blue': status===0}" @click="setStatus(0)">未开始的需求</span>
       <span :class="{'color-blue': status===2}" @click="setStatus(2)">已上线的需求</span>
+      <span class="new-tab-open">
+        <el-switch
+          v-model="newTabOpen"
+          active-text="新标签页跳转"
+        />
+      </span>
     </div>
     <el-table
       ref="planTable"
@@ -125,6 +131,7 @@ export default {
   },
   data() {
     return {
+      newTabOpen: false, // 是否新的tab页打开
       extraUrgent: extraUrgent, // 紧急图片
       needsDataList: [], // 需求列表
       allStatus: [], // 状态列表
@@ -204,7 +211,12 @@ export default {
       }
     },
     needs_link(id) {
-      this.$router.push({ name: '需求详情', query: { id: id }})
+      if (this.newTabOpen) {
+        const newTab = this.$router.resolve({ name: '需求详情', query: { id: id }})
+        window.open(newTab.href, '_blank')
+      } else {
+        this.$router.push({ name: '需求详情', query: { id: id }})
+      }
     }
   }
 }
@@ -229,6 +241,10 @@ export default {
 >>>.el-row .el-col {
   margin: 10px 0;
 }
+.new-tab-open {
+  position: absolute;
+  right: 0px;
+}
 .color-blue {
 	color:#409EFF;
 }

+ 17 - 1
src/views/workbench/team/components/projectList.vue

@@ -5,6 +5,12 @@
       <span :class="{'color-blue': status===1}" @click="setStatus(1)">进行中的项目</span>
       <span :class="{'color-blue': status===0}" @click="setStatus(0)">未开始的项目</span>
       <span :class="{'color-blue': status===2}" @click="setStatus(2)">已完成的项目</span>
+      <span class="new-tab-open">
+        <el-switch
+          v-model="newTabOpen"
+          active-text="新标签页跳转"
+        />
+      </span>
     </div>
     <el-table
       ref="planTable"
@@ -89,6 +95,7 @@ export default {
   },
   data() {
     return {
+      newTabOpen: false, // 是否新的tab页打开
       projectList: [], // 需求列表
       allStatus: [ // 状态列表
         { value: 0, name: '未开始' },
@@ -164,7 +171,12 @@ export default {
       }
     },
     project_link(id) {
-      this.$router.push({ name: '项目详情', query: { id: id }})
+      if (this.newTabOpen) {
+        const newTab = this.$router.resolve({ name: '项目详情', query: { id: id }})
+        window.open(newTab.href, '_blank')
+      } else {
+        this.$router.push({ name: '项目详情', query: { id: id }})
+      }
     }
   }
 }
@@ -189,6 +201,10 @@ export default {
 >>>.el-row .el-col {
   margin: 10px 0;
 }
+.new-tab-open {
+  position: absolute;
+  right: 0px;
+}
 .color-blue {
 	color:#409EFF;
 }

+ 17 - 1
src/views/workbench/team/components/taskList.vue

@@ -5,6 +5,12 @@
       <span :class="{'color-blue': status===1}" @click="setStatus(1)">进行中的任务</span>
       <span :class="{'color-blue': status===0}" @click="setStatus(0)">未开始的任务</span>
       <span :class="{'color-blue': status===2}" @click="setStatus(2)">已上线的任务</span>
+      <span class="new-tab-open">
+        <el-switch
+          v-model="newTabOpen"
+          active-text="新标签页跳转"
+        />
+      </span>
     </div>
     <el-row v-if="!showHeader" class="select-main" type="flex" align="center">
       <el-col :span="1" class="flex-align-center">
@@ -166,6 +172,7 @@ export default {
   },
   data() {
     return {
+      newTabOpen: false, // 是否新的tab页打开
       imgUrl: imgUrl,
       tableList: [], // 排期bable验证
       showTaskDialog: false, // 状态弹窗
@@ -368,7 +375,12 @@ export default {
     },
 
     link_task(id) { // 跳转到任务详情页
-      this.$router.push({ name: '任务详情', query: { id: id }})
+      if (this.newTabOpen) {
+        const newTab = this.$router.resolve({ name: '任务详情', query: { id: id }})
+        window.open(newTab.href, '_blank')
+      } else {
+        this.$router.push({ name: '任务详情', query: { id: id }})
+      }
     }
   }
 }
@@ -411,6 +423,10 @@ export default {
 >>>.el-row .el-col {
   margin: 10px 0;
 }
+.new-tab-open {
+  position: absolute;
+  right: 0px;
+}
 .task-main {
   display: flex;
   flex-direction: column;