Jelajahi Sumber

迭代详情页排期

qinzhipeng_v 5 tahun lalu
induk
melakukan
a235d69ccb

+ 2 - 2
package.json

@@ -25,8 +25,8 @@
     "@fullcalendar/vue": "^4.4.0",
     "@tinymce/tinymce-vue": "^3.2.2",
     "animate.css": "^3.7.2",
-    "ant-design-vue": "^1.6.2",
     "axios": "0.18.0",
+    "cnpm": "^6.1.1",
     "core-js": "^2.6.11",
     "crypto-js": "^4.0.0",
     "dayjs": "^1.8.17",
@@ -70,7 +70,7 @@
     "@vue/cli-plugin-unit-jest": "3.6.3",
     "@vue/cli-service": "3.6.0",
     "@vue/test-utils": "1.0.0-beta.29",
-    "ant-design-vue": "^1.6.1",
+    "ant-design-vue": "^1.6.2",
     "autoprefixer": "^9.5.1",
     "babel-core": "7.0.0-bridge.0",
     "babel-eslint": "10.0.1",

+ 254 - 0
src/views/projectManage/iteration/components/iterationTable.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="schedule-list">
+    <el-col align="right" class="add-schedule"><span @click="addSchedule()"><i class="el-icon-circle-plus-outline" />添加排期</span></el-col>
+    <el-table
+      :id="'schedule-'+id"
+      :data="scheduleList"
+      :header-cell-style="{'background-color': 'rgba(232,232,232,0.4)'}"
+      :row-style="{'background-color': 'transparent'}"
+      style="width: 100%"
+      show-overflow-tooltip="true"
+      row-key="id"
+    >
+      <el-table-column
+        width="80"
+        align="center"
+      >
+        <template>
+          <el-tooltip class="item" effect="dark" content="代表移动,鼠标选中区域可以向上移动" placement="bottom">
+            <div class="sortable-tip" />
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="type"
+        label="类型"
+        width="180"
+        align="center"
+      >
+        <template slot-scope="scope">
+          {{ getType(scope.row.type) }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="desc"
+        label="描述"
+        min-width="250"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        prop="seperateDaysNoHoliday"
+        label="排期"
+        min-width="250"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        prop="dayLength"
+        label="时长"
+        width="200"
+        align="center"
+      />
+      <el-table-column
+        prop="peopleList"
+        label="参与人员"
+        min-width="200"
+        align="center"
+      />
+      <el-table-column
+        label="操作"
+        width="200"
+        align="center"
+        show-overflow-tooltip
+      >
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="editSchedule(scope.row)">编辑</el-button>
+          <el-button type="text" size="small" @click="confirmDel(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+
+    </el-table>
+    <div class="bottom-detail">
+      <el-row>排期总汇:{{ scheduleDetail.startTime | handlerDate }} ~ {{ scheduleDetail.endTime | handlerDate }}</el-row>
+      <el-row v-if="scheduleDetail.preOnlineVersion && scheduleDetail.preOnlineVersion.length>0">预计上线版本:{{ scheduleDetail.preOnlineVersion }}</el-row>
+      <el-row v-else>预计上线版本:</el-row>
+    </div>
+    <modify-schedule
+      v-if="visibleSchedule"
+      :visible.sync="visibleSchedule"
+      :detail-data="detailData"
+      :title="DialogTitle"
+      @update="listByTask(id)"
+    />
+  </div>
+</template>
+<script>
+import Sortable from 'sortablejs'
+import moment from 'moment'
+import 'moment/locale/zh-cn'
+import { scheduleListByRequire } from '@/api/iteration.js'
+import { scheduleDelete, sortForTask } from '@/api/projectViewDetails'
+import modifySchedule from './modifySchedule'
+export default {
+  components: {
+    modifySchedule
+  },
+  filters: {
+    handlerDate(val) {
+      return val ? moment(val).format('YYYY-MM-DD') : ''
+    }
+  },
+  props: {
+    id: {
+      type: Number,
+      default: NaN,
+      required: true
+    },
+    typeList: {
+      type: Array,
+      default: () => [],
+      required: false
+    }
+  },
+  data() {
+    return {
+      scheduleList: [],
+      scheduleDetail: {},
+      visibleSchedule: false,
+      detailData: null,
+      taskScheduleEvent: [], // 排期类型
+      DialogTitle: '新建排期'
+    }
+  },
+  watch: {
+    id: {
+      handler(newV, oldV) {
+        this.listByTask(newV)
+      },
+      immediate: true
+    },
+    typeList: {
+      handler(newV, oldV) {
+        this.taskScheduleEvent = newV
+      },
+      immediate: true
+    }
+  },
+  mounted() {
+    this.rowDrop()
+  },
+  methods: {
+    rowDrop() {
+      const tbody = document.querySelector(`#schedule-${this.id} tbody`)
+      const _this = this
+      Sortable.create(tbody, {
+        onEnd({ newIndex, oldIndex }) {
+          const currRow = _this.scheduleList.splice(oldIndex, 1)[0]
+          _this.scheduleList.splice(newIndex, 0, currRow)
+          _this.sortForTask(_this.scheduleList.map(item => item.id))
+        }
+      })
+    },
+    async sortForTask(arr) {
+      const res = await sortForTask(this.id, arr)
+      if (res.code === 200) {
+        this.$message({ message: '移动成功', type: 'success', duration: 1000, offset: 150 })
+      }
+    },
+    getType(value) {
+      const res = this.taskScheduleEvent.find(item => item.code === value) || {}
+      return res.msg
+    },
+    async listByTask(id) { // 获取排期列表
+      const res = await scheduleListByRequire(id)
+      if (res.code === 200) {
+        this.scheduleList = res.data.schedulDetailResponses
+        this.scheduleDetail = res.data || {}
+        this.scheduleList = this.scheduleList.map(item => ({
+          ...item,
+          peopleList: item.peopleObjectList.map(item => item.name).join(',')
+        }))
+      }
+    },
+    addSchedule() {
+      this.detailData = null
+      this.DialogTitle = '新建排期'
+      this.visibleSchedule = true
+    },
+    confirmDel(row) {
+      this.$confirm('是否删除排期?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.deleteSchedule(row)
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消删除'
+        })
+      })
+    },
+    async deleteSchedule(row) { // 删除排期
+      const res = await scheduleDelete(row.id)
+      if (res.code === 200) {
+        this.listByTask(this.id)
+        this.$message({ message: '删除成功', type: 'success', duration: 1000, offset: 150 })
+      }
+    },
+    editSchedule(row) { // 编辑排期
+      this.DialogTitle = '编辑排期'
+      this.visibleSchedule = true
+      this.detailData = row
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.add-schedule {
+  cursor: pointer;
+  color: #409EFF;
+  font-size: 14px;
+  width: calc(100% - 40px);
+  margin: 0 20px;
+  padding: 20px 0;
+  i {
+    margin-right: 4px;
+  }
+  span {
+    margin-right: 20px;
+  }
+}
+.schedule-list {
+  width: calc(100% - 40px);
+  margin: 0 20px;
+  padding: 0;
+  background:rgba(248,248,248,0.6);
+}
+>>>.el-table, .el-table__expanded-cell{
+  background:rgba(248,248,248,0.6);
+}
+.bottom-detail {
+  font-size: 14px;
+  width: calc(100% - 40px);
+  margin: 0 20px;
+  padding: 20px 0;
+  :first-child {
+    margin-bottom: 10px;
+  }
+}
+.sortable-tip {
+  height: 26px;
+  width: 15px;
+  border:1px solid rgba(0,0,0,0.15);
+  border-radius:2px;
+  margin: auto;
+}
+</style>
+<style>
+.el-tooltip__popper.is-dark {
+  background:rgba(121,132,150,0.8);
+  color: #FFF;
+}
+</style>

+ 302 - 0
src/views/projectManage/iteration/components/modifySchedule.vue

@@ -0,0 +1,302 @@
+<template>
+  <normal-dialog :show-dialog.sync="show" :title="title" :is-default-close="false" @confirm="confirmForm()" @cancel="cancel()">
+    <article>
+      <el-form ref="form" :model="form" :rules="form_rules" label-width="100px" :label-position="'left'">
+        <el-form-item label="排期类型" prop="type">
+          <el-select v-model="form.type" clearable placeholder="请选择" style="width: 100%" :disabled="disabled">
+            <el-option v-for="item in taskScheduleEvent" :key="item.code" :label="item.msg" :value="item.code" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="参与人员" prop="peopleList">
+          <search-people :value.sync="form.peopleList" :multiple="true" style="width: 100%" :size="'medium'" :disabled="disabled" />
+        </el-form-item>
+        <el-form-item label="排期" prop="dayList">
+          <div v-show="form.dayList.length > 0 ">{{ schedule }}(用时<span class="blue">{{ detailDayList.length }}</span>天)</div>
+          <div v-show="form.dayList.length <= 0 " class="empty-schedule">添加排期</div>
+          <div v-if="!disabled" class="picker-hidden">
+            <sel-date-picker :start-end.sync="form.dayList" :detail-day-list="detailDayList" @getDetailDay="getDetailDay" />
+          </div>
+        </el-form-item>
+        <el-form-item label="问题描述">
+          <el-input v-model="form.desc" autocomplete="off" placeholder="请输入问题描述内容..." :disabled="disabled" />
+        </el-form-item>
+        <el-form-item label="关联任务" prop="taskList">
+          <el-select
+            v-model="selectTask"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请输入关键词"
+            :remote-method="remoteMethod"
+            :loading="loading"
+            style="width: 100%"
+            :disabled="disabled"
+            @change="tasksChange"
+          >
+            <el-option
+              v-for="item in tasksOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div class="task-list-show">
+        <div v-for="item in tasksDetailList" :key="'task'+item.id" class="task-list-item">
+          <div v-if="item.taskIdSting" class="item-id">{{ item.taskIdSting }}</div>
+          <div v-if="item.taskId" class="item-id">{{ item.taskId }}</div>
+          <div class="item-name">{{ item.name }}</div>
+          <div class="item-qa">测试负责人:{{ item.qaObject !== null?item.qaObject.name: '' }}</div>
+          <div class="item-rd">开发负责人:{{ item.rdObject !== null?item.rdObject.name: '' }}</div>
+          <i class="el-icon-circle-close item-cancel" @click="cancelTask(item.id)" />
+        </div>
+      </div>
+    </article>
+  </normal-dialog>
+</template>
+<script>
+const _ = require('lodash')
+import searchPeople from '@/components/select/searchPeople'
+import normalDialog from '@/components/dialog/normalDialog'
+import selDatePicker from '@/components/picker/SelDatePicker'
+import { taskList } from '@/api/projectIndex'
+import { taskList as allTaskList, configShowTaskEnum } from '@/api/taskIndex'
+import { getSeprateDayInfo, scheduleCreate, scheduleGet, scheduleUpdate } from '@/api/projectViewDetails'
+export default {
+  components: {
+    searchPeople,
+    normalDialog,
+    selDatePicker
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      default: false,
+      required: true
+    },
+    title: {
+      type: String,
+      default: '新建排期',
+      required: false
+    },
+    detailData: { // 详细日期
+      type: Object,
+      default: () => null,
+      required: false
+    },
+    disabled: {
+      type: Boolean,
+      default: false,
+      required: false
+    }
+  },
+  data() {
+    return {
+      form_rules: {
+        type: [{ required: true, message: '事件类型不能为空', trigger: 'blur' }],
+        peopleList: [{ required: true, message: '参与人员不能为空', trigger: 'blur' }],
+        dayList: [{ required: true, message: '排期不能为空', trigger: 'blur' }],
+        taskList: [{ required: true, message: '关联任务不能为空', trigger: 'blur' }]
+      },
+      show: this.visible,
+      taskScheduleEvent: [],
+      form: {
+        bizId: localStorage.getItem('bizId'),
+        type: '',
+        peopleList: [],
+        taskList: [],
+        desc: '',
+        dayList: [] // 日期列表
+      },
+      detailDayList: [], // 详细的日期
+      schedule: '',
+      selectTask: '', // 选择的任务id
+      tasksOptions: [], // 任务下拉选项
+      tasksDetailList: [], // 已有任务项目
+      loading: false
+    }
+  },
+  watch: {
+    visible: {
+      handler(newV, old) {
+        this.show = newV
+      },
+      immediate: true
+    },
+    detailData: {
+      handler(newV, old) {
+        if (newV) {
+          this.getScheduleData(newV.id)
+        }
+      },
+      immediate: true
+    },
+    title(newV, oldV) {
+      this.title = newV
+    }
+  },
+  created() {
+    this.getType()
+    this.getNowTask()
+  },
+  methods: {
+    async getScheduleData(id) { // 当有传入的详细信息的时候
+      const res = await scheduleGet(id)
+      let obj
+      if (res.code === 200) {
+        obj = res.data
+      } else {
+        return false
+      }
+      this.form = {
+        id: obj.id,
+        bizId: localStorage.getItem('bizId'),
+        type: obj.type,
+        peopleList: obj.peopleList || [],
+        taskList: obj.taskObjectList.map(item => item.id),
+        desc: obj.desc,
+        dayList: [obj.dayList[0], obj.dayList[obj.dayList.length - 1]] // 日期列表
+      }
+      console.log(this.form)
+      this.detailDayList = obj.dayList || []
+      this.schedule = obj.seperateDaysNoHoliday
+      this.tasksDetailList = obj.taskObjectList || []
+    },
+    async getType() {
+      const resEnum = await configShowTaskEnum()
+      if (resEnum.code === 200) {
+        this.taskScheduleEvent = resEnum.data.taskScheduleEvent
+      }
+    },
+    async getNowTask() { // 获取当前项目下任务
+      const res = await taskList({
+        projectId: this.$route.query.id
+      })
+      if (res.code === 200) {
+        this.tasksOptions = res.data
+      }
+    },
+    async remoteMethod(query) { // 远程搜索任务
+      this.loading = true
+      const res = await allTaskList({
+        bizId: Number(localStorage.getItem('bizId')),
+        name: query
+      })
+      if (res.code === 200) {
+        this.loading = false
+        this.tasksOptions = res.data
+      } else {
+        this.loading = false
+      }
+    },
+    tasksChange(id) { // 任务列表变动
+      const isEx = this.tasksDetailList.find(item => item.id === id)
+      if (isEx) {
+        this.$message({ message: '任务已存在', type: 'warning', duration: 1000, offset: 150 })
+        return false
+      }
+      const res = this.tasksOptions.find(item => {
+        return item.id === id
+      })
+      this.tasksDetailList.push(res)
+      this.form.taskList.push(res.id)
+    },
+    cancelTask(id) { // 删除单个关联任务
+      if (this.disabled) {
+        return false
+      }
+      this.tasksDetailList = this.tasksDetailList.filter(item => item.id !== id)
+      this.form.taskList = this.form.taskList.filter(item => item !== id)
+    },
+    getDetailDay(e) { // 获取详细排期日期
+      this.detailDayList = e
+      this.getSeprateDayInfo(e)
+    },
+    async getSeprateDayInfo(dataArr) { // 获取选中时间中的工作日时间
+      const res = await getSeprateDayInfo(dataArr)
+      if (res.code === 200) {
+        this.schedule = res.data.seperateDaysNoHoliday
+      }
+    },
+    async scheduleCreate(params) { // 创建排期
+      const res = await scheduleCreate(params)
+      if (res.code === 200) {
+        this.$message({ message: '添加成功', type: 'success', duration: 1000, offset: 150 })
+      }
+      this.$emit('update')
+      this.cancel()
+    },
+    async scheduleUpdate(params) { // 更新排期
+      const res = await scheduleUpdate(params)
+      if (res.code === 200) {
+        this.$message({ message: '更新成功', type: 'success', duration: 1000, offset: 150 })
+      }
+      this.$emit('update')
+      this.cancel()
+    },
+    confirmForm() { // 确认提交表单
+      this.$refs['form'].validate((valid) => {
+        if (valid) {
+          const params = _.cloneDeep(this.form)
+          params.dayList = this.detailDayList
+          params.id ? this.scheduleUpdate(params) : this.scheduleCreate(params)
+        }
+      })
+    },
+    cancel() { // 关闭弹框
+      this.show = false
+      this.$emit('update:visible', this.show)
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+article {
+  max-height: 60vh;
+  overflow: scroll;
+    .blue {
+    color: #409EFF;
+  }
+}
+>>>.el-form {
+  padding: 0 156px 0 60px;
+}
+.picker-hidden {
+  opacity: 0;
+  position: absolute;
+  top: 0;
+}
+.empty-schedule {
+  color:rgba(51,51,51,0.45);
+}
+.task-list-show {
+  width: 100%;
+  padding: 0 60px;
+  .task-list-item {
+    width: 100%;
+    display: flex;
+    margin: 10px 0;
+    .item-id {
+      width: 15%;
+    }
+    .item-name {
+      width: 40%;
+    }
+    .item-qa {
+      width: 20%;
+    }
+    .item-rd {
+      width: 20%;
+    }
+    .item-cancel {
+      font-size: 20px;
+    }
+    div {
+      overflow: hidden;
+      text-overflow:ellipsis;
+      white-space: nowrap;
+    }
+  }
+}
+</style>

+ 24 - 7
src/views/projectManage/iteration/components/requiredTable.vue

@@ -61,6 +61,7 @@
       v-loading="loading"
       :data="RequirementSet"
       row-key="id"
+      :expand-row-keys="expandArr"
       :show-header="showHeader"
       :header-cell-style="{ color: '#4A4A4A', fontSize: '14px', fontWeight: '550', background: '#F7F7F7', borderTop: '1px solid #eceef6' }"
       show-overflow-tooltip="true"
@@ -70,15 +71,15 @@
         <img style="display: inline-block; width: 8px; vertical-align: middle;" :src="image_role">
       </el-table-column>
       <el-table-column type="selection" min-width="40" align="center" />
-      <!-- <el-table-column type="expand" width="40">
+      <el-table-column type="expand" width="40">
         <template slot="header">
           <div class="expand"><i v-show="!allChange" class="el-icon-plus" @click="expandAll(true)" /></div>
           <div class="expand"><i v-show="allChange" class="el-icon-minus" @click="expandAll(false)" /></div>
         </template>
         <template slot-scope="props">
-          <ScheduleTable :required-id="props.row.id" />
+          <iterationTable :id="props.row.id" :type-list="taskScheduleEvent" />
         </template>
-      </el-table-column> -->
+      </el-table-column>
       <el-table-column label="优先级" min-width="80" align="center">
         <template v-slot="scope">
           <div align="center">
@@ -153,13 +154,16 @@
 <script>
 import '@/styles/PublicStyle/index.scss'
 import iterationDelete from '@/views/projectManage/iteration/components/delete.vue'
+import iterationTable from '@/views/projectManage/iteration/components/iterationTable.vue'
 import Sortable from 'sortablejs'
+import { configShowTaskEnum } from '@/api/taskIndex'
 import image_role from '@/assets/麻将@2x.png'
 import { iterationList, iterationRequire, iterationUpdateReqStatus, iterationOrder, iterationChangeIteration } from '@/api/iteration.js'
 import { showRequirementEnum, updateRequirementStatus } from '@/api/requirement.js'
 export default {
   components: {
-    iterationDelete
+    iterationDelete,
+    iterationTable
   },
   filters: {
     ellipsis(value, index) {
@@ -172,7 +176,9 @@ export default {
   },
   data() {
     return {
-      allChange: false,
+      allChange: false, // 是否全展开
+      expandArr: [], // 展开行数组
+      taskScheduleEvent: [], // 排期类型
       image_role: image_role,
       dialogVisibleDelete: false, // 移除迭代
       requiredArray: [], // 需求状态
@@ -189,6 +195,9 @@ export default {
       bizId: localStorage.getItem('bizId')
     }
   },
+  created() {
+    this.getTaskStatus()
+  },
   mounted() {
     this.iteratioFilter()
     this.SearchIteration()
@@ -209,6 +218,13 @@ export default {
       this.loading = false
     },
 
+    async getTaskStatus() { // 获取任务状态列表
+      const res = await configShowTaskEnum()
+      if (res.code === 200) {
+        this.taskScheduleEvent = res.data.taskScheduleEvent || []
+      }
+    },
+
     SearchIteration(e) { // 筛选change
       iterationList({ name: e, curIndex: 1, pageSize: 1000, bizId: this.bizId }).then(res => { // 调用查询迭代list
         this.searchResult = res.data.list
@@ -252,8 +268,9 @@ export default {
       this.$router.push({ name: '需求详情', params: { id: e + '' }})
     },
 
-    expandAll(e, ele) { // 展开表格
-      this.allChange = e
+    expandAll(isEx) { // 展开表格
+      this.allChange = isEx
+      isEx ? this.expandArr = this.RequirementSet.map(item => item.id) : this.expandArr = []
     },
 
     changeIteration(val) { // 变更迭代