Browse Source

甘特图上线

panxiandiao_i 5 years ago
parent
commit
ed66aa5b24

+ 1 - 0
package.json

@@ -30,6 +30,7 @@
     "qrcodejs2": "0.0.2",
     "simditor": "^2.3.26",
     "swiper": "^4.5.0",
+    "v-gantt-chart": "^1.3.7",
     "v-jsoneditor": "^1.2.2",
     "vue": "2.6.10",
     "vue-fullcalendar": "^1.0.9",

+ 2 - 0
src/main.js

@@ -3,6 +3,7 @@ import Vue from 'vue'
 import 'normalize.css/normalize.css' // A modern alternative to CSS resets
 
 import ElementUI from 'element-ui'
+import vGanttChart from 'v-gantt-chart'
 import 'element-ui/lib/theme-chalk/index.css'
 // import locale from 'element-ui/lib/locale/lang/en' // lang i18n 英文
 
@@ -16,6 +17,7 @@ import '@/icons' // icon
 // import '@/permission' // permission control/
 import htmlToPdf from '@/utils/htmlToPdf'
 Vue.use(htmlToPdf)
+Vue.use(vGanttChart)
 
 /**
  * If you don't want to use mock-server

+ 54 - 0
src/views/projectManage/projectList/gantta-left-project.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="set-flex">
+    <span>{{ dataLeft.role }}</span>
+    <span>{{ dataLeft.name }}</span>
+    <el-tooltip effect="dark" :disabled="!dataLeft.taskName || dataLeft.taskName.length <= 3" :content="dataLeft.taskName" placement="top">
+      <span>{{ dataLeft.taskName | filterCount }}</span>
+    </el-tooltip>
+    <span>{{ dataLeft.workTime }}d</span>
+  </div>
+</template>
+
+<script>
+export default {
+  filters: {
+    filterCount(e) {
+      if (e.length > 3) return e.slice(0, 3) + '...'
+      return e
+    }
+  },
+  props: {
+    dataLeft: {
+      type: Object,
+      default() {
+        return {}
+      }
+    }
+  },
+  created() {
+    // console.log(this.dataLeft.taskName.length)
+  }
+}
+</script>
+
+<style lang="stylus" scoped>
+  .set-flex
+    padding-top 5px
+    width 100%
+    height 100%
+    display flex
+    // border-top 1px solid #fff
+    // border-left 1px solid #fff
+    justify-content space-around
+    span
+      font-size 14px
+      width 25%
+      font-family MicrosoftYaHei
+      color rgba(51,59,74,1)
+      display flex
+      justify-content center
+      align-items center
+      // overflow hidden
+      // text-overflow ellipsis
+      // white-space nowrap
+</style>

+ 8 - 1
src/views/projectManage/projectList/projectListIndex.vue

@@ -249,7 +249,14 @@ export default {
       this.$router.push({ name: '任务创建', query: { id: scope.id }})
     },
     projectShow(e) {
-      this.$router.push({ name: '项目查看', query: { id: e }})
+      // 新页面跳转
+      const { href } = this.$router.resolve({
+        name: '项目查看',
+        query: {
+          id: e
+        }
+      })
+      window.open(href, '_blank')
     },
     projectShowData(e) {
       this.$router.push({ name: '项目创建', query: { id: e }})

+ 138 - 2
src/views/projectManage/projectList/projectPreview.vue

@@ -48,6 +48,39 @@
           <div class="divide-fullparts"><span style="font-weight:bold;">备注 :</span>&nbsp;&nbsp;&nbsp;{{ form.remarks }}</div>
         </div>
       </div>
+      <div class="block-gantta">
+        <v-gantt-chart
+          :title-height="titleHeight"
+          :title-width="titleWidth"
+          :cell-width="cellWidth"
+          :cell-height="cellHeight"
+          :start-time="startTime"
+          :end-time="endTime"
+          :time-lines="timeLines"
+          :datas="datas"
+          :scale="scale"
+        >
+          <template v-slot:block="{data,item}">
+            <gantta-right
+              :data-right="data"
+              :item="item"
+              :update-time-lines="updateTimeLines"
+              :cell-height="cellHeight"
+            />
+          </template>
+          <template v-slot:left="{data}">
+            <gantta-left-project :data-left="data" />
+          </template>
+          <template v-slot:title>
+            <div class="head">
+              <span>角色</span>
+              <span>人员</span>
+              <span>任务</span>
+              <span>工作量</span>
+            </div>
+          </template>
+        </v-gantt-chart>
+      </div>
       <div class="block-end">
         <el-tabs v-model="activeName">
           <el-tab-pane label="任务列表" name="first">
@@ -224,10 +257,29 @@
 <script>
 import { getProjectData, updateProject, deleteTaskData, deleteDailyReport, deleteProjectTestReport } from '@/api/projectPage'
 import { bugGetEnum } from '@/api/defectManage' // 下拉菜单data
+import dayjs from 'dayjs'
+import GanttaLeftProject from './gantta-left-project.vue'
+import GanttaRight from '../taskList/gantta-right.vue'
 
 export default {
+  components: {
+    // vGanttChart,
+    GanttaLeftProject,
+    GanttaRight
+  },
   data() {
     return {
+      timeDuring: [],
+      timeLines: [],
+      titleHeight: 50,
+      titleWidth: 250,
+      cellWidth: 60,
+      cellHeight: 30,
+      scale: 1440,
+      tableDataNone: [],
+      startTime: dayjs('2019-11-01').toString(),
+      endTime: dayjs('2020-12-01').toString(),
+      datas: [],
       form: {},
       deleteTaskDialogVisible: false,
       deleteDailyTestDialogVisible: false,
@@ -252,9 +304,24 @@ export default {
     this.idDetail()
   },
   mounted() {
+    history.pushState(null, null, document.URL)
+    // window.addEventListener('popstate', function() {
+    //   history.pushState(null, null, document.URL)
+    // })
     document.getElementsByClassName('app-main')[0].style.cssText = 'overflow:auto'
   },
   methods: {
+    updateTimeLines(timeA, timeB) {
+      this.timeLines = [
+        {
+          time: timeA
+        },
+        {
+          time: timeB,
+          color: '#747e80'
+        }
+      ]
+    },
     deleteTaskDataOut(e) {
       this.deleteTaskDialogVisible = true
       this.pauseId = e
@@ -315,6 +382,50 @@ export default {
       getProjectData(this.$route.query.id).then(res => {
         if (res.code === 200) {
           this.form = res.data
+          if (this.form.schedules) {
+            const noNull = this.form.schedules.map((eachData) => ({
+              qaSchedulesList: eachData.qaSchedulesList || [],
+              rdSchedulesList: eachData.rdSchedulesList || []
+            }))
+            console.log(noNull)
+            this.timeDuring = noNull.reduce((a, c) => {
+              return [...a, ...[...c.qaSchedulesList.map((eachData) => {
+                return eachData.startCaseTime
+              }), ...c.qaSchedulesList.map((eachData) => {
+                return eachData.endCaseTime
+              }), ...c.qaSchedulesList.map((eachData) => {
+                return eachData.startTestTime
+              }), ...c.qaSchedulesList.map((eachData) => {
+                return eachData.endTestTime
+              }), ...c.qaSchedulesList.map((eachData) => {
+                return eachData.onlimeTime
+              }), ...c.rdSchedulesList.map((eachDataRD) => {
+                return eachDataRD.devStartTime
+              }), ...c.rdSchedulesList.map((eachDataRD) => {
+                return eachDataRD.devEndTime
+              }), ...c.rdSchedulesList.map((eachDataRD) => {
+                return eachDataRD.joinStartTime
+              }), ...c.rdSchedulesList.map((eachDataRD) => {
+                return eachDataRD.joinEndTime
+              }), ...c.rdSchedulesList.map((eachDataRD) => {
+                return eachDataRD.launchTestTime
+              })]]
+            }, []).filter((eachTime) => eachTime).map((eachTime) => new Date(eachTime).getTime())
+            if (this.timeDuring.length >= 2) {
+              this.endTime = dayjs(Math.max.apply(this, this.timeDuring)).add(1, 'days').toString()
+              this.startTime = dayjs(Math.min.apply(this, this.timeDuring)).subtract(1, 'days').toString()
+            }
+            this.datas = noNull.reduce((a, c) => {
+              return [...a, ...[...c.rdSchedulesList.map((eachDataRD, index) => ({
+                ...eachDataRD,
+                gtArray: [{ start: eachDataRD.devStartTime ? dayjs(eachDataRD.devStartTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachDataRD.devEndTime ? dayjs(eachDataRD.devEndTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachDataRD.devStartTime ? '开发周期' : '', index: eachDataRD.devStartTime ? index : '' }, { start: eachDataRD.joinStartTime ? dayjs(eachDataRD.joinStartTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachDataRD.joinEndTime ? dayjs(eachDataRD.joinEndTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachDataRD.joinStartTime ? '联调时间' : '', index: eachDataRD.joinStartTime ? index : '' }, { start: eachDataRD.launchTestTime ? dayjs(eachDataRD.launchTestTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachDataRD.launchTestTime ? dayjs(eachDataRD.launchTestTime).add(1, 'days').format('YYYY-MM-DD HH:mm:ss') : '', name: eachDataRD.launchTestTime ? '提测时间' : '', index: eachDataRD.launchTestTime ? index : '' }].filter((eachTimeRD) => eachTimeRD.start)
+              })), ...c.qaSchedulesList.map((eachData, index) => ({
+                ...eachData,
+                gtArray: [{ start: eachData.startCaseTime ? dayjs(eachData.startCaseTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachData.endCaseTime ? dayjs(eachData.endCaseTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachData.startCaseTime ? '用例时间' : '', index: eachData.startCaseTime ? Math.abs((6 - index)) : '' }, { start: eachData.startTestTime ? dayjs(eachData.startTestTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachData.endTestTime ? dayjs(eachData.endTestTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachData.startTestTime ? '测试时间' : '', index: eachData.startTestTime ? Math.abs((6 - index)) : '' }, { start: eachData.onlimeTime ? dayjs(eachData.onlimeTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachData.onlimeTime ? dayjs(eachData.onlimeTime).add(1, 'days').format('YYYY-MM-DD HH:mm:ss') : '', name: eachData.onlimeTime ? '上线时间' : '', index: eachData.onlimeTime ? Math.abs((6 - index)) : '' }].filter((eachTime) => eachTime.start)
+              }))]]
+            }, [])
+            // console.log(this.datas)
+          }
           this.projectTestReports = res.data.projectTestReports
           this.dailyTestReports = res.data.dailyTestReports
           this.listData = res.data.taskInfoDOList
@@ -335,7 +446,14 @@ export default {
     },
     // 跳转任务页面 获取任务点击id
     getClickId(ele) {
-      this.$router.push({ name: '任务查看', query: { id: ele.id }})
+      // 新页面跳转
+      const { href } = this.$router.resolve({
+        name: '任务查看',
+        query: {
+          id: ele.id
+        }
+      })
+      window.open(href, '_blank')
     },
     // 日报预览跳转
     JumpDaily(val) {
@@ -384,7 +502,6 @@ export default {
 </script>
 
 <style lang="stylus" scoped>
-
   .set-background
     background-color #F2F3F6
     display flex
@@ -397,6 +514,25 @@ export default {
       width 100%
       margin 20px 0
       padding 10px 30px
+    .block-gantta
+      background-color rgba(255,255,255,1)
+      box-shadow 0px 0px 11px 0px rgba(238,240,245,1)
+      border-radius 7px
+      width 100%
+      margin 20px 0
+      padding 20px 30px
+      .head
+        width 100%
+        height 100%
+        display flex
+        // border-top 1px solid #fff
+        // border-left 1px solid #fff
+        justify-content space-around
+        background-color white
+        span
+          font-size 14px
+          font-family MicrosoftYaHei
+          color rgba(111,124,147,1)
     .block-end
       background-color rgba(255,255,255,1)
       box-shadow 0px 0px 11px 0px rgba(238,240,245,1)

+ 38 - 0
src/views/projectManage/taskList/gantta-left.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="set-flex">
+    <span>{{ dataLeft.role }}</span>
+    <span>{{ dataLeft.name }}</span>
+    <span>{{ dataLeft.workTime }}d</span>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    dataLeft: {
+      type: Object,
+      default() {
+        return {}
+      }
+    }
+  },
+  created() {
+    // console.log(this.dataLeft)
+  }
+}
+</script>
+
+<style lang="stylus" scoped>
+  .set-flex
+    padding-top 5px
+    width 100%
+    height 100%
+    display flex
+    // border-top 1px solid #fff
+    // border-left 1px solid #fff
+    justify-content space-around
+    span
+      font-size 14px
+      font-family MicrosoftYaHei
+      color rgba(51,59,74,1)
+</style>

+ 76 - 0
src/views/projectManage/taskList/gantta-right.vue

@@ -0,0 +1,76 @@
+<template>
+  <div slot="reference" class="plan" :style="{'background-color':colorList[item.index % 7], 'margin-top':0.1*cellHeight+'px'}" @click="onClick">
+    <div>
+      <span>{{ item.name }}</span>
+    </div>
+    <!-- <div class="passenger">{{item.passenger}}人</div> -->
+  </div>
+</template>
+
+<script>
+// import dayjs from 'dayjs'
+export default {
+  props: {
+    dataRight: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    updateTimeLines: {
+      type: Function,
+      default: () => true
+    },
+    item: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    cellHeight: {
+      type: Number,
+      default() {
+        return 1
+      }
+    }
+  },
+  data() {
+    return {
+      colorList: ['#FFA87F', '#F1C1FB', '#A1DEFF', '#FF8B7F', '#BCED86', '#FAD291', '#7FEEC0']
+    }
+  },
+  created() {
+    console.log(this.item)
+  },
+  // computed: {
+  //   startToString() {
+  //     return dayjs(this.item.startCaseTime).format('HH:mm')
+  //   },
+  //   endToString() {
+  //     return dayjs(this.item.endCaseTime).format('HH:mm')
+  //   }
+  // },
+  // mounted() {
+  //   console.log(1)
+  // },
+  methods: {
+    onClick() {
+      this.updateTimeLines(this.item.start, this.item.end)
+    }
+  }
+}
+</script>
+
+<style lang="stylus" scoped>
+  .plan
+    display flex
+    align-items center
+    box-sizing border-box
+    height 80%
+    border 1px solid #f0f0f0
+    border-radius 5px
+    color #333333
+    padding-left 5px
+    font-size 0.8rem
+    // opacity: 0.8
+</style>

+ 257 - 57
src/views/projectManage/taskList/taskCreate.vue

@@ -54,14 +54,6 @@
           </div>
           <el-form-item label="需求文档" label-width="112px"><el-input v-model="form.mrdUrl" placeholder="请填写" style="width:89.6%;" /></el-form-item>
           <el-form-item label="技术文档" label-width="112px"><el-input v-model="form.devUrl" placeholder="请填写" style="width:89.6%;" /></el-form-item>
-          <!-- <el-form-item class="submit">
-            <el-button type="danger" plain size="mini" @click="$router.go(-1)">取 消</el-button>
-            <el-button class="move-button" size="mini" type="primary" @click="createFormData(form)">保 存</el-button>
-          </el-form-item> -->
-        </div>
-      </div>
-      <div class="block">
-        <div class="block-flex">
           <div class="line-between">
             <el-form-item label="标签" label-width="124px">
               <el-input v-model="form.tag" autocomplete="off" placeholder="请填写" style="width:76%;" />
@@ -75,9 +67,10 @@
                 @change="handleChangeGroup"
                 @visible-change="realTimeGroupGet"
               />
+              <!-- <el-input v-model="form.group" autocomplete="off" placeholder="请填写" style="width:76%;" /> -->
             </el-form-item>
           </div>
-          <div class="line-between">
+          <div class="line-between-add">
             <el-form-item label="产品" label-width="124px">
               <el-select v-model="form.pm" multiple filterable placeholder="公司邮箱前缀" style="width:76%;" @visible-change="realTimeChange">
                 <el-option
@@ -88,7 +81,7 @@
                 />
               </el-select>
             </el-form-item>
-            <el-form-item label="开发" label-width="147px" prop="rd">
+            <!-- <el-form-item label="开发" label-width="147px" prop="rd">
               <el-select v-model="form.rd" multiple filterable placeholder="公司邮箱前缀" style="width:76%;" @visible-change="realTimeChange">
                 <el-option
                   v-for="item in optionsRD"
@@ -97,34 +90,127 @@
                   :value="item.email"
                 />
               </el-select>
-            </el-form-item>
+            </el-form-item> -->
           </div>
-          <div class="line-between-add">
-            <el-form-item label="测试" label-width="124px" prop="qa">
-              <el-select v-model="form.qa" multiple filterable placeholder="公司邮箱前缀" style="width:76%;" @visible-change="realTimeChange">
-                <el-option
-                  v-for="item in optionsQA"
-                  :key="item.id"
-                  :label="item.name"
-                  :value="item.email"
-                />
-              </el-select>
-            </el-form-item>
-          </div>
-          <el-form-item label="描述" label-width="112px"><el-input v-model="form.description" type="textarea" placeholder="任务描述" rows="3" style="width:89.6%;" /></el-form-item>
+          <!-- <el-form-item class="submit">
+            <el-button type="danger" plain size="mini" @click="$router.go(-1)">取 消</el-button>
+            <el-button class="move-button" size="mini" type="primary" @click="createFormData(form)">保 存</el-button>
+          </el-form-item> -->
         </div>
       </div>
-      <div class="block">
+      <div class="block" style="padding: 29px 20px;">
         <div class="block-flex">
-          <div class="line-between">
+          <div v-for="(itemMember, index) in developmentMember" :key="index" class="line-between-customize">
+            <span>开发</span>
+            <el-select v-model="itemMember.rd" filterable clearable placeholder="请选择" style="width: 9%;" @visible-change="realTimeChange">
+              <el-option
+                v-for="item in optionsRD"
+                :key="item.id"
+                :label="item.name"
+                :value="item.email"
+              />
+            </el-select>
+            <span>开发周期</span>
+            <el-tooltip :disabled="itemMember.developmentTimeValue === null || itemMember.developmentTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.developmentTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.developmentTimeValue"
+                :disabled="!itemMember.rd"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>联调时间</span>
+            <el-tooltip :disabled="itemMember.testTimeValue === null || itemMember.testTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.testTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.testTimeValue"
+                :disabled="!itemMember.rd"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>提测时间</span>
+            <el-tooltip :disabled="!itemMember.commitTimeValue" effect="dark" :content="getKonwTime(itemMember.commitTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.commitTimeValue"
+                :disabled="!itemMember.rd"
+                clearable
+                style="width:9%;"
+                type="date"
+                placeholder="提测"
+              />
+            </el-tooltip>
+            <span>工作量: {{ totalTimeCom(itemMember.developmentTimeValue, itemMember.testTimeValue) }}d</span>
+            <el-button v-if="index === 0" size="mini" type="primary" icon="el-icon-plus" circle @click="developmentMember.push({rd: '', developmentTimeValue: [], testTimeValue: [], commitTimeValue: ''})" />
+            <el-button v-else size="mini" type="info" icon="el-icon-minus" circle @click="developmentMember.splice(developmentMember.findIndex((itemDelete, indexDelete) => indexDelete === index), 1)" />
+          </div>
+          <div v-for="(itemMember, index) in testMember" :key="999 - index" class="line-between-customize">
+            <span>测试</span>
+            <el-select v-model="itemMember.qa" filterable clearable placeholder="请选择" style="width: 9%;" @visible-change="realTimeChange">
+              <el-option
+                v-for="item in optionsQA"
+                :key="index + 999 + item.id"
+                :label="item.name"
+                :value="item.email"
+              />
+            </el-select>
+            <span>用例时间</span>
+            <el-tooltip :disabled="itemMember.developmentTimeValue === null || itemMember.developmentTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.developmentTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.developmentTimeValue"
+                :disabled="!itemMember.qa"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>测试时间</span>
+            <el-tooltip :disabled="itemMember.testTimeValue === null || itemMember.testTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.testTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.testTimeValue"
+                :disabled="!itemMember.qa"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>上线时间</span>
+            <el-tooltip :disabled="!itemMember.commitTimeValue" effect="dark" :content="getKonwTime(itemMember.commitTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.commitTimeValue"
+                :disabled="!itemMember.qa"
+                clearable
+                style="width:9%;"
+                type="date"
+                placeholder="提测"
+              />
+            </el-tooltip>
+            <span>工作量: {{ totalTimeCom(itemMember.developmentTimeValue, itemMember.testTimeValue) }}d</span>
+            <el-button v-if="index === 0" size="mini" type="primary" icon="el-icon-plus" circle @click="testMember.push({qa: '', developmentTimeValue: [], testTimeValue: [], commitTimeValue: ''})" />
+            <el-button v-else size="mini" type="info" icon="el-icon-minus" circle @click="testMember.splice(testMember.findIndex((itemDelete, indexDelete) => indexDelete === index), 1)" />
+          </div>
+          <!-- <div class="line-between">
             <el-form-item label="需求评审时间" label-width="166px">
               <el-date-picker v-model="form.mrdTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
             <el-form-item label="计划提测时间" label-width="189px">
               <el-date-picker v-model="form.launchTestPlanTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
-          </div>
-          <div class="line-between">
+          </div> -->
+          <!-- <div class="line-between">
             <el-form-item label="计划开始开发时间" label-width="166px">
               <el-date-picker v-model="form.startDevPlanTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
@@ -136,8 +222,13 @@
             <el-form-item label="计划上线时间" label-width="166px">
               <el-date-picker v-model="form.onlinePlanTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
+          </div> -->
+          <div class="line-between-customize-remark" style="margin-top:25px">
+            <span>描述</span><el-input v-model="form.description" style="width: 95.7%;" type="textarea" placeholder="任务描述" rows="3" />
+          </div>
+          <div class="line-between-customize-remark" style="margin-top:25px">
+            <span>备注</span><el-input v-model="form.remark" style="width: 95.7%;" type="textarea" placeholder="备注" rows="3" />
           </div>
-          <el-form-item label="备注" label-width="146px"><el-input v-model="form.remark" type="textarea" placeholder="项目描述" rows="3" style="width:90%;" /></el-form-item>
           <el-form-item class="submit">
             <el-button type="danger" plain size="mini" @click="$router.go(-1)">取 消</el-button>
             <el-button class="move-button" size="mini" type="primary" @click="createFormData(form)">确 定</el-button>
@@ -164,8 +255,25 @@ export default {
         pm: [],
         rd: [],
         qa: [],
-        group: ''
+        group: '',
+        schedule: {}
       },
+      developmentMember: [
+        {
+          rd: '',
+          developmentTimeValue: [],
+          testTimeValue: [],
+          commitTimeValue: ''
+        }
+      ],
+      testMember: [
+        {
+          qa: '',
+          developmentTimeValue: [],
+          testTimeValue: [],
+          commitTimeValue: ''
+        }
+      ],
       optionsGroup: [],
       optionsPM: [],
       optionsRD: [],
@@ -194,10 +302,10 @@ export default {
         name: [{ required: true, message: '任务名称不能为空', trigger: 'change' }],
         priority: [{ required: true, message: '优先级不能为空', trigger: 'change' }],
         // stage: [{ required: true, message: '进展不能为空', trigger: 'change' }],
-        clientType: [{ required: true, message: '工程模块为空', trigger: 'change' }],
+        clientType: [{ required: true, message: '工程模块为空', trigger: 'change' }]
         // pm: [{ required: true, message: '产品人员不能为空', trigger: 'change' }],
-        rd: [{ required: true, message: '开发人员不能为空', trigger: 'change' }],
-        qa: [{ required: true, message: '测试人员不能为空', trigger: 'change' }]
+        // rd: [{ required: true, message: '开发人员不能为空', trigger: 'change' }],
+        // qa: [{ required: true, message: '测试人员不能为空', trigger: 'change' }]
       }
     }
   },
@@ -222,13 +330,13 @@ export default {
         this.taskTypeStr = res.data.taskAndRoutineEnumList
       })
     },
-    // 接口不接受空值处理
-    emptyJudge(obj) {
-      for (const key in obj) {
-        if (!obj[key] || obj[key].length === 0) {
-          delete obj[key]
-        }
-      }
+    // 时间标签
+    getKonwTime(time) {
+      if (!time || time.length === 0) return
+      if (time.length === undefined) return time.getFullYear() + '-' + (time.getMonth() + 1) + '-' + time.getDate()
+      return time.map((eachTime) => {
+        return eachTime.getFullYear() + '-' + (eachTime.getMonth() + 1) + '-' + eachTime.getDate() + ' ' + eachTime.getHours() + ':' + eachTime.getMinutes() + ':' + eachTime.getSeconds()
+      }).join('至')
     },
     // 根据id获取数据
     async idDetail() {
@@ -269,7 +377,18 @@ export default {
       this.businessTypeStr = []
       this.$set(this.form, 'type', '')
       this.$set(this.form, 'clientType', '')
+      this.$set(this.form, 'pm', '')
+      this.$set(this.form, 'rd', '')
+      this.$set(this.form, 'qa', '')
       this.$set(this.form, 'group', '')
+      this.developmentMember = this.developmentMember.map((eachData) => ({
+        ...eachData,
+        rd: ''
+      }))
+      this.testMember = this.testMember.map((eachData) => ({
+        ...eachData,
+        qa: ''
+      }))
       this.realTimeGroupGet()
       this.getMember()
     },
@@ -283,25 +402,27 @@ export default {
       }
       this.$set(this.form, 'clientType', '')
     },
+    // 相差时间计算
+    totalTimeCom(develop, test) {
+      if (develop === null || develop === undefined) develop = []
+      if (test === null || test === undefined) test = []
+      if (develop.length !== 0 && test.length === 0) {
+        return ((develop[1].getTime() - develop[0].getTime()) / (1000 * 3600 * 24)).toFixed(1)
+      }
+      if (develop.length === 0 && test.length !== 0) {
+        return ((test[1].getTime() - test[0].getTime()) / (1000 * 3600 * 24)).toFixed(1)
+      }
+      if (develop.length !== 0 && test.length !== 0) {
+        return ((test[1].getTime() - test[0].getTime() + develop[1].getTime() - develop[0].getTime()) / (1000 * 3600 * 24)).toFixed(1)
+      }
+      if (develop.length === 0 && test.length === 0) {
+        return 0
+      }
+    },
     // 点击一次更新一次
     realTimeChange(e) {
       if (e === true) this.getMember()
     },
-    // 分组选择
-    handleChangeGroup(value) {
-      console.log(value)
-    },
-    realTimeGroupGet() {
-      listComment({ bizId: this.form.bizId ? this.form.bizId : this.bizJson, type: 1 }).then((res) => {
-        const reduceNa = (arr) => {
-          return arr.reduce((a, c) => {
-            return c.child.length !== 0 ? [...a, { id: c.commentInfo.id, name: c.commentInfo.content, child: reduceNa(c.child) }] : [...a, { id: c.commentInfo.id, name: c.commentInfo.content }]
-          }, [])
-        }
-        res.code === 200 ? this.optionsGroup = reduceNa(res.data) : this.errorFun(res.msg)
-      })
-    },
-
     // 人员搜索
     getMember() {
       this.objDataPM = { bizId: this.form.bizId, email: '', role: 'pm' }
@@ -317,8 +438,37 @@ export default {
         }
       }))
     },
+    // 分组选择
+    handleChangeGroup(value) {
+      console.log(value)
+    },
+    realTimeGroupGet() {
+      listComment({ bizId: this.bizJson, type: 1 }).then((res) => {
+        const reduceNa = (arr) => {
+          return arr.reduce((a, c) => {
+            return c.child.length !== 0 ? [...a, { id: c.commentInfo.id, name: c.commentInfo.content, child: reduceNa(c.child) }] : [...a, { id: c.commentInfo.id, name: c.commentInfo.content }]
+          }, [])
+        }
+        res.code === 200 ? this.optionsGroup = reduceNa(res.data) : this.errorFun(res.msg)
+      })
+    },
+    // 处理开发时间提测时间是否为全空
+    filterNa(arr) {
+      return arr.filter((eachData) => {
+        return Object.values(eachData).some(item => item !== '' && (item === null ? item !== null : item.length !== 0))
+      })
+    },
+    // 接口不接受空值处理
+    emptyJudge(obj) {
+      for (const key in obj) {
+        if (!obj[key] || obj[key].length === 0) {
+          delete obj[key]
+        }
+      }
+    },
     // 新建项目
     createFormData(form) {
+      console.log(form)
       this.$refs.form.validate((valid) => {
         if (valid) {
           form.projectId = this.$route.query.id
@@ -335,6 +485,34 @@ export default {
             form.group = form.group[form.group.length - 1]
           }
           this.emptyJudge(form)
+          if (this.filterNa(this.developmentMember).length !== 0) {
+            const pauseDevelopmentMember = this.filterNa(this.developmentMember)
+            form.schedule.rdSchedulesList = pauseDevelopmentMember.map((eachData) => ({
+              ename: eachData.rd ? eachData.rd : '',
+              name: eachData.rd ? this.optionsRD.filter((eachMember) => eachMember.email === eachData.rd)[0].name : '',
+              role: 'rd',
+              devStartTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[0],
+              devEndTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[1],
+              joinStartTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[0],
+              joinEndTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[1],
+              launchTestTime: eachData.commitTimeValue ? eachData.commitTimeValue : '',
+              workTime: this.totalTimeCom(eachData.developmentTimeValue, eachData.testTimeValue)
+            }))
+          }
+          if (this.filterNa(this.testMember).length !== 0) {
+            const pauseDevelopmentMember = this.filterNa(this.testMember)
+            form.schedule.qaSchedulesList = pauseDevelopmentMember.map((eachData) => ({
+              ename: eachData.qa ? eachData.qa : '',
+              name: eachData.qa ? this.optionsQA.filter((eachMember) => eachMember.email === eachData.qa)[0].name : '',
+              role: 'qa',
+              startCaseTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[0],
+              endCaseTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[1],
+              startTestTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[0],
+              endTestTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[1],
+              onlimeTime: eachData.commitTimeValue ? eachData.commitTimeValue : '',
+              workTime: this.totalTimeCom(eachData.developmentTimeValue, eachData.testTimeValue)
+            }))
+          }
           this.userData = { id: '', ename: this.userInformation, name: this.userNames }
           this.objData = { taskInfo: form, user: this.userData }
           createTaskData(this.objData).then(res => {
@@ -380,8 +558,30 @@ export default {
       display flex
     .block-flex >>> .el-form-item__content
       width 100%
+    .block-flex >>> .el-date-editor .el-range-separator
+      padding 0
+      width auto
     .force-height >>> .w-e-text-container
       height 80px !important
+    .line-between-customize-remark
+      width 100%
+      display flex
+      justify-content space-between
+      align-items center
+      span
+        font-size 14px
+        color rgba(51,59,74,1)
+        white-space nowrap
+    .line-between-customize
+      width 100%
+      height 50px
+      display flex
+      justify-content space-between
+      align-items center
+      span
+        font-size 14px
+        color rgba(51,59,74,1)
+        white-space nowrap
     .line-between
       width 100%
       display flex
@@ -392,8 +592,8 @@ export default {
       width 100%
     .line-between-add >>> .el-form-item
       width 50%
-    .submit
-      margin 0 9%
+    // .submit
+    //   margin 0 9%
     .submit >>> .el-form-item__content
       margin-top 40px
       display flex

+ 9 - 1
src/views/projectManage/taskList/taskListIndex.vue

@@ -471,6 +471,7 @@ export default {
       moveTask({ id: this.pauseTaskMoveId, projectId: this.taskProjectId }, user).then(res => {
         res.code === 200 ? this.successFun('move') : this.errorFun(res.msg)
         this.dialogVisibleTaskMove = false
+        this.bugListSelectBeforeGet()
       })
     },
     dialogMoveTask(e) {
@@ -898,7 +899,14 @@ export default {
       this.dataQueryInSearch()
     },
     taskShow(e) {
-      this.$router.push({ name: '任务查看', query: { id: e }})
+      // 新页面跳转
+      const { href } = this.$router.resolve({
+        name: '任务查看',
+        query: {
+          id: e
+        }
+      })
+      window.open(href, '_blank')
     },
     projectShowData(e) {
       this.$router.push({ name: '任务更新', query: { id: e }})

+ 137 - 38
src/views/projectManage/taskList/taskPreview.vue

@@ -34,7 +34,7 @@
           <div class="divide-fourparts"><span style="font-weight:bold;">平台类型 :</span>&nbsp;&nbsp;&nbsp;{{ form.typeString }}</div>
           <div class="divide-fourparts"><span style="font-weight:bold;">是否免测 :</span>&nbsp;&nbsp;&nbsp;{{ form.noTestString }}</div>
           <div class="divide-fourparts"><span style="font-weight:bold;">测试 :</span>&nbsp;&nbsp;&nbsp;{{ form.qaList }}</div>
-          <div class="divide-fourparts"><span style="font-weight:bold;">分组 :</span>&nbsp;&nbsp;&nbsp;{{ form.groupList }}</div>
+          <div class="divide-fourparts"><span style="font-weight:bold;">分组 :</span>&nbsp;&nbsp;&nbsp;{{ form.groupName }}</div>
         </div>
         <div class="display-messege">
           <div class="divide-fourparts"><span style="font-weight:bold;">业务模块 :</span>&nbsp;&nbsp;&nbsp;{{ form.clientTypeString }}</div>
@@ -52,6 +52,38 @@
           <div class="divide-fullparts"><span style="font-weight:bold;">需求文档 :</span>&nbsp;&nbsp;&nbsp;{{ form.mrdUrl }}</div>
         </div>
       </div>
+      <div class="block-gantta">
+        <v-gantt-chart
+          :title-height="titleHeight"
+          :title-width="titleWidth"
+          :cell-width="cellWidth"
+          :cell-height="cellHeight"
+          :start-time="startTime"
+          :end-time="endTime"
+          :time-lines="timeLines"
+          :datas="datas"
+          :scale="scale"
+        >
+          <template v-slot:block="{data,item}">
+            <gantta-right
+              :data-right="data"
+              :item="item"
+              :update-time-lines="updateTimeLines"
+              :cell-height="cellHeight"
+            />
+          </template>
+          <template v-slot:left="{data}">
+            <gantta-left :data-left="data" />
+          </template>
+          <template v-slot:title>
+            <div class="head">
+              <span>角色</span>
+              <span>人员</span>
+              <span>工作量</span>
+            </div>
+          </template>
+        </v-gantt-chart>
+      </div>
       <div class="block">
         <el-tabs v-model="activeName">
           <el-tab-pane label="提测报告" name="first">
@@ -240,31 +272,6 @@
           </el-tab-pane>
         </el-tabs>
       </div>
-      <div class="block-end">
-        <div class="display-messege">
-          <div class="divide-twoparts"><span style="font-weight:bold;">需求评审时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.mrdTime }}</div>
-          <div class="divide-twoparts"><span style="font-weight:bold;">计划提测时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.launchTestPlanTime }}</div>
-        </div>
-        <div class="display-messege">
-          <div class="divide-twoparts"><span style="font-weight:bold;">计划开始开发时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.startDevPlanTime }}</div>
-          <div class="divide-twoparts"><span style="font-weight:bold;">计划测试完成时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.testFinishPlanTime }}</div>
-        </div>
-        <div class="display-messege">
-          <div class="divide-twoparts"><span style="font-weight:bold;">计划上线时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.onlinePlanTime }}</div>
-          <div class="divide-twoparts"><span style="font-weight:bold;">冒烟测试完成时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.smokeTestFinishTime }}</div>
-        </div>
-        <div class="display-messege">
-          <div class="divide-twoparts"><span style="font-weight:bold;">实际提测时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.launchTestRealTime }}</div>
-          <div class="divide-twoparts"><span style="font-weight:bold;">实际上线时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.onlineRealTime }}</div>
-        </div>
-        <div class="display-messege">
-          <div class="divide-twoparts"><span style="font-weight:bold;">实际开始开发时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.startDevRealTime }}</div>
-          <div class="divide-twoparts"><span style="font-weight:bold;">实际测试完成时间 :</span>&nbsp;&nbsp;&nbsp;{{ form.testFinishRealTime }}</div>
-        </div>
-        <div class="display-messege">
-          <div class="divide-fullparts"><span style="font-weight:bold;">备注 :</span>&nbsp;&nbsp;&nbsp;{{ form.remark }}</div>
-        </div>
-      </div>
     </div>
     <el-dialog title="请选择创建服务端还是客户端" :visible.sync="openDialogVisible" width="30%" center>
       <div style="text-align: center; margin-top: 5%;">
@@ -283,10 +290,30 @@
 <script>
 import { getTaskData, launchTestUpdate, updateTaskList } from '@/api/projectPage.js'
 import { bugGetEnum } from '@/api/defectManage' // 下拉菜单data
+// import vGanttChart from 'v-gantt-chart'
+import dayjs from 'dayjs'
+import GanttaLeft from './gantta-left.vue'
+import GanttaRight from './gantta-right.vue'
 
 export default {
+  components: {
+    // vGanttChart,
+    GanttaLeft,
+    GanttaRight
+  },
   data() {
     return {
+      timeDuring: [],
+      timeLines: [],
+      titleHeight: 50,
+      titleWidth: 250,
+      cellWidth: 60,
+      cellHeight: 30,
+      scale: 1440,
+      tableDataNone: [],
+      startTime: dayjs('2019-11-01').toString(),
+      endTime: dayjs('2020-12-01').toString(),
+      datas: [],
       form: {},
       CallBackTheReason: '',
       userInformation: localStorage.getItem('username'),
@@ -309,17 +336,73 @@ export default {
     }
   },
   created() {
+    // console.log(dayjs('2019-12-31T16:00:00.000Z').toString())
     this.bugListSelect()
     this.getList()
   },
   mounted() {
     document.getElementsByClassName('app-main')[0].style.cssText = 'overflow:auto'
+    history.pushState(null, null, document.URL)
+    // window.addEventListener('popstate', function() {
+    //   history.pushState(null, null, document.URL)
+    // })
   },
   methods: {
+    updateTimeLines(timeA, timeB) {
+      this.timeLines = [
+        {
+          time: timeA
+        },
+        {
+          time: timeB,
+          color: '#747e80'
+        }
+      ]
+    },
     // id get
     getList() {
       getTaskData(this.$route.query.id).then(res => {
         this.form = res.data
+        // if (this.form.schedule === null) {
+        //   this.form.schedule = {}
+        //   this.form.schedule.qaSchedulesList = []
+        //   this.form.schedule.rdSchedulesList = []
+        // }
+        if (this.form.schedule) {
+          if (this.form.schedule.qaSchedulesList === null) this.form.schedule.qaSchedulesList = []
+          if (this.form.schedule.rdSchedulesList === null) this.form.schedule.rdSchedulesList = []
+          // console.log(dayjs(this.form.schedule.qaSchedulesList.startCaseTime).format('YYYY-MM-DD HH:mm:ss'))
+          this.timeDuring = [...this.form.schedule.qaSchedulesList.map((eachData) => {
+            return eachData.startCaseTime
+          }), ...this.form.schedule.qaSchedulesList.map((eachData) => {
+            return eachData.endCaseTime
+          }), ...this.form.schedule.qaSchedulesList.map((eachData) => {
+            return eachData.startTestTime
+          }), ...this.form.schedule.qaSchedulesList.map((eachData) => {
+            return eachData.endTestTime
+          }), ...this.form.schedule.qaSchedulesList.map((eachData) => {
+            return eachData.onlimeTime
+          }), ...this.form.schedule.rdSchedulesList.map((eachDataRD) => {
+            return eachDataRD.devStartTime
+          }), ...this.form.schedule.rdSchedulesList.map((eachDataRD) => {
+            return eachDataRD.devEndTime
+          }), ...this.form.schedule.rdSchedulesList.map((eachDataRD) => {
+            return eachDataRD.joinStartTime
+          }), ...this.form.schedule.rdSchedulesList.map((eachDataRD) => {
+            return eachDataRD.joinEndTime
+          }), ...this.form.schedule.rdSchedulesList.map((eachDataRD) => {
+            return eachDataRD.launchTestTime
+          })].filter((eachTime) => eachTime).map((eachTime) => new Date(eachTime).getTime())
+          this.endTime = dayjs(Math.max.apply(this, this.timeDuring)).add(1, 'days').toString()
+          this.startTime = dayjs(Math.min.apply(this, this.timeDuring)).subtract(1, 'days').toString()
+          this.datas = [...this.form.schedule.rdSchedulesList.map((eachDataRD, index) => ({
+            ...eachDataRD,
+            gtArray: [{ start: eachDataRD.devStartTime ? dayjs(eachDataRD.devStartTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachDataRD.devEndTime ? dayjs(eachDataRD.devEndTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachDataRD.devStartTime ? '开发周期' : '', index: eachDataRD.devStartTime ? index : '' }, { start: eachDataRD.joinStartTime ? dayjs(eachDataRD.joinStartTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachDataRD.joinEndTime ? dayjs(eachDataRD.joinEndTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachDataRD.joinStartTime ? '联调时间' : '', index: eachDataRD.joinStartTime ? index : '' }, { start: eachDataRD.launchTestTime ? dayjs(eachDataRD.launchTestTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachDataRD.launchTestTime ? dayjs(eachDataRD.launchTestTime).add(1, 'days').format('YYYY-MM-DD HH:mm:ss') : '', name: eachDataRD.launchTestTime ? '提测时间' : '', index: eachDataRD.launchTestTime ? index : '' }].filter((eachTimeRD) => eachTimeRD.start)
+          })), ...this.form.schedule.qaSchedulesList.map((eachData, index) => ({
+            ...eachData,
+            gtArray: [{ start: eachData.startCaseTime ? dayjs(eachData.startCaseTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachData.endCaseTime ? dayjs(eachData.endCaseTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachData.startCaseTime ? '用例时间' : '', index: eachData.startCaseTime ? Math.abs((6 - index)) : '' }, { start: eachData.startTestTime ? dayjs(eachData.startTestTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachData.endTestTime ? dayjs(eachData.endTestTime).format('YYYY-MM-DD HH:mm:ss') : '', name: eachData.startTestTime ? '测试时间' : '', index: eachData.startTestTime ? Math.abs((6 - index)) : '' }, { start: eachData.onlimeTime ? dayjs(eachData.onlimeTime).format('YYYY-MM-DD HH:mm:ss') : '', end: eachData.onlimeTime ? dayjs(eachData.onlimeTime).add(1, 'days').format('YYYY-MM-DD HH:mm:ss') : '', name: eachData.onlimeTime ? '上线时间' : '', index: eachData.onlimeTime ? Math.abs((6 - index)) : '' }].filter((eachTime) => eachTime.start)
+          }))]
+        }
         this.listTaskDatas = res.data.launchTestInfoList
         // this.listTaskDatas.statusString = this.form.statusString
         this.projectTestReports = res.data.projectTestReports
@@ -371,7 +454,6 @@ export default {
         this.processStatusEnumList = res.data.processStatusEnumList
       })
     },
-
     create_code(e) {
       if (e.radio !== undefined) {
         if (e.radio === '1') {
@@ -458,12 +540,39 @@ export default {
 </script>
 
 <style lang="stylus" scoped>
-
+  .block >>> .gantt-leftbar-wrapper
+    z-index 8 !important
+  .block >>> .el-tabs__nav-wrap::after
+    background-color white
+  .block >>> th
+    background-color #F0F2F4 !important
+  .block >>> .el-dialog__header
+    padding 20px 20px 0px 20px
+    display flex
   .set-background
     background-color #F2F3F6
     display flex
     justify-content center
     min-width 900px
+    .block-gantta
+      background-color rgba(255,255,255,1)
+      box-shadow 0px 0px 11px 0px rgba(238,240,245,1)
+      border-radius 7px
+      width 100%
+      margin 20px 0
+      padding 20px 30px
+      .head
+        width 100%
+        height 100%
+        display flex
+        // border-top 1px solid #fff
+        // border-left 1px solid #fff
+        justify-content space-around
+        background-color white
+        span
+          font-size 14px
+          font-family MicrosoftYaHei
+          color rgba(111,124,147,1)
     .block
       background-color rgba(255,255,255,1)
       box-shadow 0px 0px 11px 0px rgba(238,240,245,1)
@@ -471,13 +580,6 @@ export default {
       width 100%
       margin 20px 0
       padding 10px 30px 20px 30px
-    .block >>> .el-tabs__nav-wrap::after
-      background-color white
-    .block >>> th
-      background-color #F0F2F4 !important
-    .block >>> .el-dialog__header
-      padding 20px 20px 0px 20px
-      display flex
     .block-end
       background-color rgba(255,255,255,1)
       box-shadow 0px 0px 11px 0px rgba(238,240,245,1)
@@ -495,7 +597,6 @@ export default {
       margin-right 10px
     .display-messege
       font-size 14px
-      font-family PingFangSC-Regular,PingFangSC
       font-weight 400
       color rgba(51,59,74,1)
       display flex
@@ -503,7 +604,6 @@ export default {
       margin-bottom 15px
     .display-messege-end-one
       font-size 14px
-      font-family PingFangSC-Regular,PingFangSC
       font-weight 400
       color rgba(51,59,74,1)
       display flex
@@ -511,7 +611,6 @@ export default {
       margin 30px 0 15px 0
     .display-messege-end
       font-size 14px
-      font-family PingFangSC-Regular,PingFangSC
       font-weight 400
       color rgba(51,59,74,1)
       display flex

+ 294 - 58
src/views/projectManage/taskList/taskUpdateCreate.vue

@@ -54,14 +54,6 @@
           </div>
           <el-form-item label="需求文档" label-width="112px"><el-input v-model="form.mrdUrl" placeholder="请填写" style="width:89.6%;" /></el-form-item>
           <el-form-item label="技术文档" label-width="112px"><el-input v-model="form.devUrl" placeholder="请填写" style="width:89.6%;" /></el-form-item>
-          <!-- <el-form-item class="submit">
-            <el-button type="danger" plain size="mini" @click="$router.go(-1)">取 消</el-button>
-            <el-button class="move-button" size="mini" type="primary" @click="createFormData(form)">保 存</el-button>
-          </el-form-item> -->
-        </div>
-      </div>
-      <div class="block">
-        <div class="block-flex">
           <div class="line-between">
             <el-form-item label="标签" label-width="124px">
               <el-input v-model="form.tag" autocomplete="off" placeholder="请填写" style="width:76%;" />
@@ -75,9 +67,10 @@
                 @change="handleChangeGroup"
                 @visible-change="realTimeGroupGet"
               />
+              <!-- <el-input v-model="form.group" autocomplete="off" placeholder="请填写" style="width:76%;" /> -->
             </el-form-item>
           </div>
-          <div class="line-between">
+          <div class="line-between-add">
             <el-form-item label="产品" label-width="124px">
               <el-select v-model="form.pm" multiple filterable placeholder="公司邮箱前缀" style="width:76%;" @visible-change="realTimeChange">
                 <el-option
@@ -88,7 +81,7 @@
                 />
               </el-select>
             </el-form-item>
-            <el-form-item label="开发" label-width="147px" prop="rd">
+            <!-- <el-form-item label="开发" label-width="147px" prop="rd">
               <el-select v-model="form.rd" multiple filterable placeholder="公司邮箱前缀" style="width:76%;" @visible-change="realTimeChange">
                 <el-option
                   v-for="item in optionsRD"
@@ -97,34 +90,127 @@
                   :value="item.email"
                 />
               </el-select>
-            </el-form-item>
+            </el-form-item> -->
           </div>
-          <div class="line-between-add">
-            <el-form-item label="测试" label-width="124px" prop="qa">
-              <el-select v-model="form.qa" multiple filterable placeholder="公司邮箱前缀" style="width:76%;" @visible-change="realTimeChange">
-                <el-option
-                  v-for="item in optionsQA"
-                  :key="item.id"
-                  :label="item.name"
-                  :value="item.email"
-                />
-              </el-select>
-            </el-form-item>
-          </div>
-          <el-form-item label="描述" label-width="112px"><el-input v-model="form.description" type="textarea" placeholder="任务描述" rows="3" style="width:89.6%;" /></el-form-item>
+          <!-- <el-form-item class="submit">
+            <el-button type="danger" plain size="mini" @click="$router.go(-1)">取 消</el-button>
+            <el-button class="move-button" size="mini" type="primary" @click="createFormData(form)">保 存</el-button>
+          </el-form-item> -->
         </div>
       </div>
-      <div class="block">
+      <div class="block" style="padding: 29px 20px;">
         <div class="block-flex">
-          <div class="line-between">
+          <div v-for="(itemMember, index) in developmentMember" :key="index" class="line-between-customize">
+            <span>开发</span>
+            <el-select v-model="itemMember.rd" filterable clearable placeholder="请选择" style="width: 9%;" @visible-change="realTimeChange">
+              <el-option
+                v-for="item in optionsRD"
+                :key="index + 999 + item.id"
+                :label="item.name"
+                :value="item.email"
+              />
+            </el-select>
+            <span>开发周期</span>
+            <el-tooltip :disabled="itemMember.developmentTimeValue === null || itemMember.developmentTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.developmentTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.developmentTimeValue"
+                :disabled="!itemMember.rd"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>联调时间</span>
+            <el-tooltip :disabled="itemMember.testTimeValue === null || itemMember.testTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.testTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.testTimeValue"
+                :disabled="!itemMember.rd"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>提测时间</span>
+            <el-tooltip :disabled="!itemMember.commitTimeValue" effect="dark" :content="getKonwTime(itemMember.commitTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.commitTimeValue"
+                :disabled="!itemMember.rd"
+                clearable
+                style="width:9%;"
+                type="date"
+                placeholder="提测"
+              />
+            </el-tooltip>
+            <span>工作量: {{ totalTimeCom(itemMember.developmentTimeValue, itemMember.testTimeValue) }}d</span>
+            <el-button v-if="index === 0" size="mini" type="primary" icon="el-icon-plus" circle @click="developmentMember.push({rd: '', developmentTimeValue: [], testTimeValue: [], commitTimeValue: ''})" />
+            <el-button v-else size="mini" type="info" icon="el-icon-minus" circle @click="developmentMember.splice(developmentMember.findIndex((itemDelete, indexDelete) => indexDelete === index), 1)" />
+          </div>
+          <div v-for="(itemMember, index) in testMember" :key="999 - index" class="line-between-customize">
+            <span>测试</span>
+            <el-select v-model="itemMember.qa" filterable clearable placeholder="请选择" style="width: 9%;" @visible-change="realTimeChange">
+              <el-option
+                v-for="item in optionsQA"
+                :key="index + 999 + item.id"
+                :label="item.name"
+                :value="item.email"
+              />
+            </el-select>
+            <span>用例时间</span>
+            <el-tooltip :disabled="itemMember.developmentTimeValue === null || itemMember.developmentTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.developmentTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.developmentTimeValue"
+                :disabled="!itemMember.qa"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>测试时间</span>
+            <el-tooltip :disabled="itemMember.testTimeValue === null || itemMember.testTimeValue.length === 0" effect="dark" :content="getKonwTime(itemMember.testTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.testTimeValue"
+                :disabled="!itemMember.qa"
+                clearable
+                type="datetimerange"
+                style="width:25%;"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </el-tooltip>
+            <span>上线时间</span>
+            <el-tooltip :disabled="!itemMember.commitTimeValue" effect="dark" :content="getKonwTime(itemMember.commitTimeValue)" placement="top">
+              <el-date-picker
+                v-model="itemMember.commitTimeValue"
+                :disabled="!itemMember.qa"
+                clearable
+                style="width:9%;"
+                type="date"
+                placeholder="提测"
+              />
+            </el-tooltip>
+            <span>工作量: {{ totalTimeCom(itemMember.developmentTimeValue, itemMember.testTimeValue) }}d</span>
+            <el-button v-if="index === 0" size="mini" type="primary" icon="el-icon-plus" circle @click="testMember.push({qa: '', developmentTimeValue: [], testTimeValue: [], commitTimeValue: ''})" />
+            <el-button v-else size="mini" type="info" icon="el-icon-minus" circle @click="testMember.splice(testMember.findIndex((itemDelete, indexDelete) => indexDelete === index), 1)" />
+          </div>
+          <!-- <div class="line-between">
             <el-form-item label="需求评审时间" label-width="166px">
               <el-date-picker v-model="form.mrdTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
             <el-form-item label="计划提测时间" label-width="189px">
               <el-date-picker v-model="form.launchTestPlanTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
-          </div>
-          <div class="line-between">
+          </div> -->
+          <!-- <div class="line-between">
             <el-form-item label="计划开始开发时间" label-width="166px">
               <el-date-picker v-model="form.startDevPlanTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
@@ -136,8 +222,13 @@
             <el-form-item label="计划上线时间" label-width="166px">
               <el-date-picker v-model="form.onlinePlanTime" align="left" size="medium" type="datetime" style="width:76%;" placeholder="选择日期" />
             </el-form-item>
+          </div> -->
+          <div class="line-between-customize-remark" style="margin-top:25px">
+            <span>描述</span><el-input v-model="form.description" style="width: 95.7%;" type="textarea" placeholder="任务描述" rows="3" />
+          </div>
+          <div class="line-between-customize-remark" style="margin-top:25px">
+            <span>备注</span><el-input v-model="form.remark" style="width: 95.7%;" type="textarea" placeholder="备注" rows="3" />
           </div>
-          <el-form-item label="备注" label-width="146px"><el-input v-model="form.remark" type="textarea" placeholder="项目描述" rows="3" style="width:90%;" /></el-form-item>
           <el-form-item class="submit">
             <el-button type="danger" plain size="mini" @click="$router.go(-1)">取 消</el-button>
             <el-button class="move-button" size="mini" type="primary" @click="createFormData(form)">确 定</el-button>
@@ -149,8 +240,8 @@
 </template>
 
 <script>
-import { projectGetTypeMap, updateTaskList, teamMembers } from '@/api/projectPage'
-import { taskListCreate } from '@/api/defectManage'
+import { projectGetTypeMap, updateTaskList, teamMembers, getTaskData } from '@/api/projectPage'
+// import { taskListCreate } from '@/api/defectManage'
 import { bugGetEnum } from '@/api/defectManage' // 下拉菜单data
 import { listComment } from '@/api/KanBan.js'
 import axios from 'axios'
@@ -159,12 +250,30 @@ export default {
   data() {
     return {
       form: {
+        bizId: '',
         type: '',
         clientType: '',
-        pm: '',
-        rd: '',
-        qa: ''
+        pm: [],
+        rd: [],
+        qa: [],
+        group: ''
       },
+      developmentMember: [
+        {
+          rd: '',
+          developmentTimeValue: [],
+          testTimeValue: [],
+          commitTimeValue: ''
+        }
+      ],
+      testMember: [
+        {
+          qa: '',
+          developmentTimeValue: [],
+          testTimeValue: [],
+          commitTimeValue: ''
+        }
+      ],
       optionsGroup: [],
       optionsPM: [],
       optionsRD: [],
@@ -193,10 +302,10 @@ export default {
         name: [{ required: true, message: '任务名称不能为空', trigger: 'change' }],
         priority: [{ required: true, message: '优先级不能为空', trigger: 'change' }],
         // stage: [{ required: true, message: '进展不能为空', trigger: 'change' }],
-        clientType: [{ required: true, message: '工程模块为空', trigger: 'change' }],
+        clientType: [{ required: true, message: '工程模块为空', trigger: 'change' }]
         // pm: [{ required: true, message: '产品人员不能为空', trigger: 'change' }],
-        rd: [{ required: true, message: '开发人员不能为空', trigger: 'change' }],
-        qa: [{ required: true, message: '测试人员不能为空', trigger: 'change' }]
+        // rd: [{ required: true, message: '开发人员不能为空', trigger: 'change' }],
+        // qa: [{ required: true, message: '测试人员不能为空', trigger: 'change' }]
       }
     }
   },
@@ -228,9 +337,9 @@ export default {
           this.errorFun(res.msg)
         }
       })
-      await taskListCreate({ id: this.$route.query.id }).then(res => {
+      await getTaskData(this.$route.query.id).then(res => {
         if (res.code === 200) {
-          this.form = res.data[0]
+          this.form = res.data
           this.typeString = this.bizIdEnumList.filter(value => value.code === this.form.bizId)[0].child
           this.getMember() // 保证bizId存在
           if (this.form.pm) {
@@ -242,6 +351,30 @@ export default {
           if (this.form.qa) {
             this.form.qa = this.form.qa.split(',')
           }
+          if (this.form.schedule && this.form.schedule.rdSchedulesList) {
+            this.developmentMember = this.form.schedule.rdSchedulesList.map((eachData) => ({
+              rd: eachData.ename ? eachData.ename : '',
+              developmentTimeValue: eachData.devStartTime ? [new Date(eachData.devStartTime), new Date(eachData.devEndTime)] : [],
+              testTimeValue: eachData.joinStartTime ? [new Date(eachData.joinStartTime), new Date(eachData.joinEndTime)] : [],
+              commitTimeValue: eachData.launchTestTime ? new Date(eachData.launchTestTime) : ''
+            }))
+          }
+          if (this.form.schedule && this.form.schedule.qaSchedulesList) {
+            this.testMember = this.form.schedule.qaSchedulesList.map((eachData) => ({
+              qa: eachData.ename ? eachData.ename : '',
+              developmentTimeValue: eachData.startCaseTime ? [new Date(eachData.startCaseTime), new Date(eachData.endCaseTime)] : [],
+              testTimeValue: eachData.startTestTime ? [new Date(eachData.startTestTime), new Date(eachData.endTestTime)] : [],
+              commitTimeValue: eachData.onlimeTime ? new Date(eachData.onlimeTime) : ''
+            }))
+          }
+          // if (this.form.schedule && this.form.schedule.rdSchedulesList) {
+          //   this.developmentMember = this.form.schedule.rdSchedulesList.map((eachData) => ({
+          //     rd: eachData.ename ? eachData.ename : '',
+          //     developmentTimeValue: eachData.devStartTime ? [eachData.devStartTime, eachData.devEndTime] : [],
+          //     testTimeValue: eachData.joinStartTime ? [eachData.joinStartTime, eachData.joinEndTime] : [],
+          //     commitTimeValue: eachData.launchTestTime ? eachData.launchTestTime : ''
+          //   }))
+          // }
           if (this.form.clientType) {
             this.businessTypeShow = true
             this.businessTypeStr = this.typeString.filter(value => value.code === this.form.type)[0].child
@@ -255,12 +388,50 @@ export default {
         this.form.group = this.groupArray(this.optionsGroup, this.form.group)
       }
     },
+    // 时间标签
+    getKonwTime(time) {
+      if (!time || time.length === 0) return
+      if (time.length === undefined) return time.getFullYear() + '-' + (time.getMonth() + 1) + '-' + time.getDate()
+      return time.map((eachTime) => {
+        return eachTime.getFullYear() + '-' + (eachTime.getMonth() + 1) + '-' + eachTime.getDate() + ' ' + eachTime.getHours() + ':' + eachTime.getMinutes() + ':' + eachTime.getSeconds()
+      }).join('至')
+    },
+    // 团队处理
+    groupArray(myArr, id) {
+      let res
+      // path,存储路径
+      const getPath = (arr, id, path = []) => {
+        return arr.reduce((a, c) => {
+          if (c.id === id) {
+            res = [...path, c.id]
+          }
+          if (c.child) {
+            return getPath(c.child, id, [...path, c.id])
+          }
+        }, [])
+      }
+      getPath(myArr, id)
+      return res
+    },
     // 业务线取子数据
     clickChangePlatform(e) {
       this.typeString = this.bizIdEnumList.filter(value => value.code === e)[0].child
       this.businessTypeStr = []
       this.$set(this.form, 'type', '')
       this.$set(this.form, 'clientType', '')
+      this.$set(this.form, 'pm', '')
+      this.$set(this.form, 'rd', '')
+      this.$set(this.form, 'qa', '')
+      this.$set(this.form, 'group', '')
+      this.developmentMember = this.developmentMember.map((eachData) => ({
+        ...eachData,
+        rd: ''
+      }))
+      this.testMember = this.testMember.map((eachData) => ({
+        ...eachData,
+        qa: ''
+      }))
+      this.realTimeGroupGet()
       this.getMember()
     },
     // 业务线取子数据
@@ -273,6 +444,23 @@ export default {
       }
       this.$set(this.form, 'clientType', '')
     },
+    // 相差时间计算
+    totalTimeCom(develop, test) {
+      if (develop === null || develop === undefined) develop = []
+      if (test === null || test === undefined) test = []
+      if (develop.length !== 0 && test.length === 0) {
+        return ((develop[1].getTime() - develop[0].getTime()) / (1000 * 3600 * 24)).toFixed(1)
+      }
+      if (develop.length === 0 && test.length !== 0) {
+        return ((test[1].getTime() - test[0].getTime()) / (1000 * 3600 * 24)).toFixed(1)
+      }
+      if (develop.length !== 0 && test.length !== 0) {
+        return ((test[1].getTime() - test[0].getTime() + develop[1].getTime() - develop[0].getTime()) / (1000 * 3600 * 24)).toFixed(1)
+      }
+      if (develop.length === 0 && test.length === 0) {
+        return 0
+      }
+    },
     // 点击一次更新一次
     realTimeChange(e) {
       if (e === true) this.getMember()
@@ -292,30 +480,12 @@ export default {
         }
       }))
     },
-    // 团队处理
-    groupArray(myArr, id) {
-      let res
-      // path,存储路径
-      const getPath = (arr, id, path = []) => {
-        return arr.reduce((a, c) => {
-          if (c.id === id) {
-            res = [...path, c.id]
-          }
-          if (c.child) {
-            return getPath(c.child, id, [...path, c.id])
-          }
-        }, [])
-      }
-      getPath(myArr, id)
-      return res
-    },
-
     // 分组选择
     handleChangeGroup(value) {
       console.log(value)
     },
     realTimeGroupGet() {
-      return listComment({ bizId: this.form.bizId, type: 1 }).then((res) => {
+      return listComment({ type: 1 }).then((res) => {
         const reduceNa = (arr) => {
           return arr.reduce((a, c) => {
             return c.child.length !== 0 ? [...a, { id: c.commentInfo.id, name: c.commentInfo.content, child: reduceNa(c.child) }] : [...a, { id: c.commentInfo.id, name: c.commentInfo.content }]
@@ -332,8 +502,20 @@ export default {
         }
       }
     },
+    // 处理开发时间提测时间是否为全空
+    filterNa(arr) {
+      return arr.filter((eachData) => {
+        return Object.values(eachData).some(item => item !== '' && (item === null ? item !== null : item.length !== 0))
+      })
+    },
     // 新建项目
     createFormData(form) {
+      if (!form.schedule) {
+        form.schedule = {
+          rdSchedulesList: [],
+          qaSchedulesList: []
+        }
+      }
       this.$refs.form.validate((valid) => {
         if (valid) {
           form.projectId = this.$route.query.projectId
@@ -350,6 +532,38 @@ export default {
             form.group = form.group[form.group.length - 1]
           }
           this.emptyJudge(form)
+          if (this.filterNa(this.developmentMember).length !== 0) {
+            const pauseDevelopmentMember = this.filterNa(this.developmentMember)
+            form.schedule.rdSchedulesList = pauseDevelopmentMember.map((eachData) => ({
+              ename: eachData.rd ? eachData.rd : '',
+              name: eachData.rd ? this.optionsRD.filter((eachMember) => eachMember.email === eachData.rd)[0].name : '',
+              role: 'rd',
+              devStartTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[0],
+              devEndTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[1],
+              joinStartTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[0],
+              joinEndTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[1],
+              launchTestTime: eachData.commitTimeValue ? eachData.commitTimeValue : '',
+              workTime: this.totalTimeCom(eachData.developmentTimeValue, eachData.testTimeValue)
+            }))
+          } else {
+            form.schedule.rdSchedulesList = null
+          }
+          if (this.filterNa(this.testMember).length !== 0) {
+            const pauseDevelopmentMember = this.filterNa(this.testMember)
+            form.schedule.qaSchedulesList = pauseDevelopmentMember.map((eachData) => ({
+              ename: eachData.qa ? eachData.qa : '',
+              name: eachData.qa ? this.optionsQA.filter((eachMember) => eachMember.email === eachData.qa)[0].name : '',
+              role: 'qa',
+              startCaseTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[0],
+              endCaseTime: eachData.developmentTimeValue === null || eachData.developmentTimeValue.length === 0 ? '' : eachData.developmentTimeValue[1],
+              startTestTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[0],
+              endTestTime: eachData.testTimeValue === null || eachData.testTimeValue.length === 0 ? '' : eachData.testTimeValue[1],
+              onlimeTime: eachData.commitTimeValue ? eachData.commitTimeValue : '',
+              workTime: this.totalTimeCom(eachData.developmentTimeValue, eachData.testTimeValue)
+            }))
+          } else {
+            form.schedule.qaSchedulesList = null
+          }
           this.userData = { id: '', ename: this.userInformation, name: this.userNames }
           this.objData = { taskInfo: form, user: this.userData }
           updateTaskList(this.objData).then(res => {
@@ -395,8 +609,30 @@ export default {
       display flex
     .block-flex >>> .el-form-item__content
       width 100%
+    .block-flex >>> .el-date-editor .el-range-separator
+      padding 0
+      width auto
     .force-height >>> .w-e-text-container
       height 80px !important
+    .line-between-customize-remark
+      width 100%
+      display flex
+      justify-content space-between
+      align-items center
+      span
+        font-size 14px
+        color rgba(51,59,74,1)
+        white-space nowrap
+    .line-between-customize
+      width 100%
+      height 50px
+      display flex
+      justify-content space-between
+      align-items center
+      span
+        font-size 14px
+        color rgba(51,59,74,1)
+        white-space nowrap
     .line-between
       width 100%
       display flex