瀏覽代碼

需求详情页

PrinceLee 5 年之前
父節點
當前提交
9341cb4429

+ 8 - 0
src/api/requirement.js

@@ -123,3 +123,11 @@ export function settingGetBizList(data) {
   })
 }
 
+// 获取所属迭代
+export function iterationList(data) {
+  return request({
+    url: requestIp + `/iteration/list`,
+    method: 'post',
+    data
+  })
+}

+ 2 - 2
src/router/index.js

@@ -133,12 +133,12 @@ export const constantRoutes = [{
     meta: { title: '迭代详情' }
   },
   {
-    path: 'requirementDetails/:id',
+    path: 'requirementDetails',
     name: '需求详情',
     hidden: true,
     props: true,
     component: () =>
-      import('@/views/projectManage/requirement/details/index.vue'),
+      import('@/views/projectManage/requirement/requirementDetail.vue'),
     meta: { title: '需求详情' }
   },
   {

+ 17 - 2
src/views/projectManage/projectList/components/scheduleList.vue

@@ -64,14 +64,15 @@
       >
         <template slot-scope="scope">
           <el-button type="text" size="small" @click="editSchedule(scope.row)">编辑</el-button>
-          <el-button type="text" size="small" @click="deleteSchedule(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>预计上线版本:{{ scheduleDetail.preOnlineVersion || '' }}</el-row>
+      <el-row v-if="scheduleDetail.preOnlineVersion.length>0">预计上线版本:{{ scheduleDetail.preOnlineVersion }}</el-row>
+      <el-row v-else>预计上线版本:</el-row>
     </div>
     <modify-schedule
       v-if="visibleSchedule"
@@ -174,6 +175,20 @@ export default {
       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) {

+ 1 - 1
src/views/projectManage/projectList/projectViewDetails.vue

@@ -354,7 +354,7 @@ export default {
     reated_task(e) { // 新建任务
       this.task_open = true
       this.$nextTick(() => {
-        this.$refs.task_createdUpdata.init(2)
+        this.$refs.task_createdUpdata.init(3)
       })
     }
   }

+ 302 - 0
src/views/projectManage/requirement/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 { getTaskByRequireId } from '@/api/requirement'
+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 getTaskByRequireId({
+        id: this.$route.query.id
+      })
+      if (res.code === 200) {
+        this.tasksOptions = res.data.taskDetails
+      }
+    },
+    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>

+ 253 - 0
src/views/projectManage/requirement/components/scheduleList.vue

@@ -0,0 +1,253 @@
+<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.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 { listByTask, 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 listByTask(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>

+ 390 - 0
src/views/projectManage/requirement/components/taskList.vue

@@ -0,0 +1,390 @@
+<template>
+  <div>
+    <el-row v-if="!showHeader" class="select-main" type="flex" align="center">
+      <el-col :span="2" class="flex-align-center">
+        <el-checkbox v-model="planChecked" class="plan-checked" @change="changeCheck" />
+      </el-col>
+      <el-col :span="3" class="item-checked">已选择<span style="color: #409EFF">{{ curcentChecked }}</span>个</el-col>
+      <el-col :span="1" class="item-click">|</el-col>
+      <el-col :span="2" class="item-click click-blue" @click.native="handlePlan('test')">提测</el-col>
+      <el-col :span="2" class="item-click click-blue" @click.native="handlePlan('allow')">准出</el-col>
+      <el-col :span="4" class="item-click click-blue" @click.native="handlePlan('daily')">建立测试日报</el-col>
+      <el-col :span="4" class="item-click" @click.native="handlePlan('cancel')">取消选择</el-col>
+    </el-row>
+    <el-table
+      ref="planTable"
+      :data="all_task"
+      style="width: 100%;"
+      size="mini"
+      row-key="id"
+      :expand-row-keys="expandArr"
+      :header-cell-style="{ color: '#4A4A4A', fontSize: '14px', fontWeight: '550', textAlign: 'center' }"
+      show-overflow-tooltip="true"
+      :show-header="showHeader"
+      :header-row-style="{height: '50px'}"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <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">
+          <schedule-list :id="props.row.id" :type-list="taskScheduleEvent" />
+        </template>
+      </el-table-column>
+      <el-table-column label="优先级" prop="priority" width="90" sortable align="center">
+        <template slot-scope="scope" style="text-align: center;">
+          <span class="div_priority" :class="scope.row.priorityString">
+            {{ scope.row.priorityString }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="任务名称" width="200" align="center" show-overflow-tooltip>
+        <template slot-scope="scope"><span class="task-title" @click="link_task(scope.row.id)">{{ scope.row.name }}</span></template>
+      </el-table-column>
+      <el-table-column label="所属模块" width="150" align="center" show-overflow-tooltip>
+        <template slot-scope="scope">{{ scope.row.moduleInfoName }}</template>
+      </el-table-column>
+      <el-table-column label="状态" width="105" align="center">
+        <template slot-scope="scope">
+          <el-select
+            v-model="scope.row.status"
+            :class="['status'+scope.row.status]"
+            class="btns"
+            size="mini"
+            @change="changeStatus(scope.row)"
+          >
+            <el-option v-for="item in allStatus" :key="item.code" :label="item.msg" :value="item.code" />
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column label="所属需求" width="200" align="center" show-overflow-tooltip>
+        <template slot-scope="scope">{{ scope.row.requireName }}</template>
+      </el-table-column>
+      <el-table-column label="跟版客户端" width="120" align="center" show-overflow-tooltip>
+        <template slot-scope="scope">{{ scope.row.app }}</template>
+      </el-table-column>
+      <el-table-column label="开发负责人" width="100" align="center" show-overflow-tooltip>
+        <template slot-scope="scope">{{ scope.row.rdObject ? scope.row.rdObject.name : '' }}</template>
+      </el-table-column>
+      <el-table-column label="测试负责人" width="100" align="center" show-overflow-tooltip>
+        <template slot-scope="scope">{{ scope.row.qaObject ? scope.row.qaObject.name : '' }}</template>
+      </el-table-column>
+      <el-table-column label="任务进度" min-width="150" align="center">
+        <template slot-scope="scope">
+          <el-progress :percentage="Number(scope.row.rate && scope.row.rate.substring(0,4))" color="#409eff" />
+        </template>
+      </el-table-column>
+    </el-table>
+    <TestReport v-if="dialogTestReport" ref="TestReport" />
+    <DailyReport v-if="dialogDailyReport" ref="DailyReport" />
+    <ClientReport v-if="dialogClientReport" ref="ClientReport" />
+    <normal-dialog :show-dialog.sync="statusDialog" :title="'状态变更:已上线'" :width="'50%'" @confirm="confirmStatus()">
+      <div class="dialog-change-status">
+        <span>实际上线时间:</span>
+        <el-date-picker v-model="changeStatusDate" type="date" style="width:100%;" placeholder="选择日期" format="yyyy-MM-dd HH:mm:ss" />
+      </div>
+    </normal-dialog>
+  </div>
+</template>
+<script>
+import TestReport from '@/views/Platform/presentation/Templates/TestReport' // 提测
+import DailyReport from '@/views/Platform/presentation/Templates/DailyReport' // 日报
+import ClientReport from '@/views/Platform/presentation/Templates/ClientReport' // 准出
+import { getTaskByRequireId } from '@/api/requirement'
+import { taskUpdate } from '@/api/projectViewDetails'
+import { configShowTaskEnum } from '@/api/taskIndex'
+import scheduleList from './scheduleList'
+import normalDialog from '@/components/dialog/normalDialog'
+export default {
+  components: {
+    normalDialog,
+    TestReport,
+    DailyReport,
+    ClientReport,
+    scheduleList
+  },
+  data() {
+    return {
+      changeData: new Map(),
+      allChange: false, // 是否全展开
+      expandArr: [], // 展开行数组
+      inputValue: '',
+      all_task: [], // 任务列表
+      allStatus: [], // 任务所有状态
+      taskScheduleEvent: [], // 排期类型
+      showHeader: true, // 任务列表的表头是否显示
+      curcentList: [], // 当前已选择的列表
+      curcentChecked: 0, // 当前已选择的数量
+      planChecked: false,
+      planHandleType: '', // 任务列表操作类型
+      dialogTestReport: false, // 提测
+      dialogDailyReport: false, // 日报
+      dialogClientReport: false, // 准出
+      statusDialog: false, // 修改状态弹框
+      changeStatusDate: null, // 状态改变时间
+      nowChangeTask: null // 当前正在改变的任务对象
+    }
+  },
+  watch: {
+    value: {
+      handler(newV, oldV) {
+        this.inputValue = newV
+      },
+      immediate: true
+    }
+  },
+  created() {
+    this.getTaskStatus()
+    this.get_allTask()
+  },
+  methods: {
+    async get_allTask() { // 获取全部任务
+      const res = await getTaskByRequireId({
+        id: this.$route.query.id
+      })
+      if (res.code === 200) {
+        this.all_task = res.data.taskDetails
+        // for (const [key, value] of Object.entries(res.data[0])) {
+        //   console.log(`${key}: ${value}`)
+        // }
+      }
+    },
+    async getTaskStatus() { // 获取任务状态列表
+      const res = await configShowTaskEnum()
+      if (res.code === 200) {
+        this.allStatus = res.data.taskStatus
+        this.taskScheduleEvent = res.data.taskScheduleEvent || []
+      }
+    },
+    changeCheck(val) {
+      if (val) {
+        this.all_task.forEach(row => {
+          this.$refs.planTable.toggleRowSelection(row, true)
+        })
+      } else {
+        this.$refs.planTable.clearSelection()
+      }
+    },
+    expandAll(isEx) { // 全部展开
+      this.allChange = isEx
+      isEx ? this.expandArr = this.all_task.map(item => item.id) : this.expandArr = []
+    },
+    async changeStatus(e) { // 状态改变
+      if (e.status === 5) { // 已上线
+        this.statusDialog = true
+        this.nowChangeTask = e
+      } else {
+        const user = {
+          name: localStorage.getItem('username'),
+          ename: localStorage.getItem('realname'),
+          id: ''
+        }
+        const taskInfoDO = e
+        const resTask = await taskUpdate({ taskInfoDO, user })
+        if (resTask.code === 200) {
+          this.$message({ message: resTask.msg, type: 'success', offset: 150 })
+        }
+      }
+    },
+    async confirmStatus() { // 确认更改状态
+      const user = {
+        name: localStorage.getItem('username'),
+        ename: localStorage.getItem('realname'),
+        id: ''
+      }
+      const taskInfoDO = this.nowChangeTask
+      taskInfoDO.onlineRealTime = this.changeStatusDate
+      const resTask = await taskUpdate({ taskInfoDO, user })
+      if (resTask.code === 200) {
+        this.$message({ message: resTask.msg, type: 'success', offset: 150 })
+      }
+    },
+    handleSelectionChange(val) { // 任务列表删选操作
+      val.length > 0 ? this.showHeader = false : this.showHeader = true
+      this.curcentChecked = val.length
+      this.curcentList = val
+      if (val.length === this.all_task.length) {
+        this.planChecked = true
+      }
+    },
+    handlePlan(type) { // 任务列表操作
+      this.planHandleType = type
+      switch (type) {
+        case 'test':
+          this.filtrateTest()
+          break
+        case 'allow':
+          this.filtrateAllow()
+          break
+        case 'daily':
+          this.filtrateDaily()
+          break
+        case 'cancel':
+          this.$refs.planTable.clearSelection()
+          break
+      }
+    },
+    filtrateTest() { // 提测筛选
+      this.dialogTestReport = true
+      this.$nextTick(() => {
+        this.$refs.TestReport.init(7, this.curcentList.map(item => { return item.id }))
+      })
+    },
+    filtrateAllow() { // 准出筛选
+      this.dialogClientReport = true
+      this.$nextTick(() => {
+        this.$refs.ClientReport.init(7, this.curcentList.map(item => { return item.id }))
+      })
+    },
+    filtrateDaily() { // 建立日报
+      this.dialogDailyReport = true
+      this.$nextTick(() => {
+        this.$refs.DailyReport.init(7, this.curcentList.map(item => { return item.id }))
+      })
+    },
+    link_task(id) { // 跳转到任务详情页
+      this.$router.push({ name: '任务详情', query: { id: id }})
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+@mixin setStatus($color) {
+  input {
+    color:$color;
+    border: 1px solid $color;
+  }
+  >>> .el-select__caret{
+    color:$color;
+  }
+  >>> .el-input__inner{
+    color:$color;
+    border-color: $color;
+  }
+  >>> .el-input__inner:focus {
+    border-color: $color;
+  }
+}
+>>>.el-row .el-col {
+  margin: 10px 0;
+}
+.task-title {
+  cursor: pointer;
+}
+.P0 {
+  background-color: #F56C6C;
+}
+.P1 {
+  background-color: #FF8952;
+}
+.P2 {
+  background-color: #F5E300;
+}
+.P3 {
+  background-color: #7ED321;
+}
+.P4 {
+  background-color: #61D3B8;
+}
+.P5 {
+  background-color: #69B3FF;
+}
+.P6 {
+  background-color: #BDBDBD;
+}
+.status0 {
+  @include setStatus(#409EFF)
+}
+.status1 {
+  @include setStatus(#FF8952)
+}
+.status3 {
+  @include setStatus(#13C2C2)
+}
+.status5 {
+  @include setStatus(#7ED321)
+}
+.expand i {
+  border:1px solid #DCDFE6;
+}
+>>>.el-table__expand-icon{
+  font-size: 14px;
+  .el-icon{
+    margin: 0;
+    transform: translate(-50%, -50%);
+    border:1px solid #DCDFE6;
+  }
+}
+>>>.el-table__expand-icon .el-icon-arrow-right::before{
+  content: "\E6D9";
+}
+>>>.el-table__expand-icon--expanded .el-icon-arrow-right::before{
+  content: "\E6D8";
+}
+>>>.el-table__expand-icon--expanded {
+  transform: rotate(180deg);
+}
+>>>.el-table__expanded-cell {
+  background-color: #FFFFFF;
+  padding: 0;
+}
+>>>.el-table__expanded-cell:hover {
+  background-color: #FFFFFF !important;
+}
+.div_priority {
+  text-align: center;
+  color: #ffffff;
+  padding: inherit;
+  border-radius: 4px;
+  width: 40px;
+  display: inline-block;
+}
+.plan-checked {
+  padding-left: 21px;
+}
+.select-main {
+  height: 50px;
+  border-bottom: 1px solid #DCDFE6;
+  .flex-align-center {
+    display: flex;
+    align-items: center;
+  }
+  .item-checked {
+    color: #606266;
+    font-size: 14px;
+    display: flex;
+    align-items: center;
+    justify-content: left;
+    cursor: pointer;
+  }
+  .item-click{
+    color: #606266;
+    font-size: 14px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+  }
+  .click-blue {
+    color: #409EFF;
+  }
+}
+.descr {
+  display: flex;
+  justify-content: flex-start;
+}
+.planList >>> .el-table th>.cell {
+  padding-left: 14px;
+  padding-right: 14px;
+}
+.dialog-change-status {
+  margin: 2% 3%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  white-space:nowrap;
+}
+</style>

+ 1 - 1
src/views/projectManage/requirement/list/create.vue

@@ -165,7 +165,7 @@ export default {
       if (newVisible) {
         if (this.title === '编辑需求') {
           this.form = JSON.parse(JSON.stringify(this.data))
-          this.form.pm = this.form.pm[0].idap
+          this.form.pm = this.form.pm.idap
           this.init()
           this.form.modifier = localStorage.getItem('username')
           if (this.form.belongingProject !== '' && this.form.belongingProject !== -1) {

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

@@ -307,7 +307,7 @@ export default {
       return ret
     },
     getToRequirementDetails(id) {
-      this.$router.push({ name: '需求详情', params: { id: id + '' }})
+      this.$router.push({ name: '需求详情', query: { id: id }})
     }
   }
 }

+ 359 - 0
src/views/projectManage/requirement/requirementDetail.vue

@@ -0,0 +1,359 @@
+<template>
+  <div class="bg-project" @click="display = false">
+    <el-container>
+      <el-header class="main-header">
+        <div class="top-page-title">
+          <el-tooltip :disabled="form_query.name && form_query.name.length > 20 ? false : true" effect="dark" :content="form_query.name" placement="bottom">
+            <div class="header-title">需求 : {{ form_query.name | ellipsis(10) }}</div>
+          </el-tooltip>
+          <el-dropdown placement="bottom" @command="updateStatus">
+            <el-button size="mini" plainclass="el-dropdown-link drop_down">
+              {{ getStatus.msg }}
+              <i class="el-icon-arrow-down el-icon--right" />
+            </el-button>
+            <el-dropdown-menu slot="dropdown" align="center">
+              <el-dropdown-item
+                v-for="item in statusList"
+                :key="item.msg"
+                :command="{value:item.code,label:item.msg}"
+                :disabled="form_query.status === item.code? true: false"
+              >{{ item.msg }}</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </div>
+        <div class="top-tabs">
+          <el-tabs v-model="activeName">
+            <el-tab-pane label="概览" name="1" />
+            <el-tab-pane label="任务" name="2" />
+            <el-tab-pane label="缺陷" name="3" />
+            <el-tab-pane label="统计" name="4" />
+          </el-tabs>
+        </div>
+        <div class="top-control">
+          <el-dropdown placement="bottom">
+            <i class="el-icon-circle-plus icon-add" />
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item @click.native="createVisible = true">新建需求</el-dropdown-item>
+              <el-dropdown-item @click.native="reated_task()">新建任务</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+          <div class="line" />
+          <span style="vertical-align: bottom; cursor: pointer; color: #6F7C93;" @click.stop="setChild(), display = true"><img style="width: 24px; display: inline-block;" :src="image_url">&nbsp; {{ num }} &nbsp;</span>
+          <div class="line" />
+          <i class="el-icon-setting icon-delete" @click="updateVisible = true" />
+          <div class="line" />
+          <i class="el-icon-delete icon-delete" @click="deleteVisible = true" />
+        </div>
+      </el-header>
+      <!-- 概览 -->
+      <el-container v-show="activeName === '1'" class="is-vertical">
+        <section class="main-section">
+          <div class="el-main-title">
+            <div class="title-left-icon" />
+            <div class="title-left-name">基础信息</div>
+          </div>
+          <div class="detail-info">
+            <el-form :inline="true" :model="form_query" class="demo-form-inline" label-position="right" label-width="100px">
+              <el-form-item label="所属项目:">
+                <el-select v-model="form_query.belongingProject" placeholder="请选择">
+                  <el-option v-for="(item,index) in belongProjectList" :key="item.name + index" :label="item.name" :value="item.id" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="所属迭代:">
+                <el-select v-model="form_query.iterationId" placeholder="请选择">
+                  <el-option v-for="(item,index) in iterationList" :key="item.name + index" :label="item.name" :value="item.id" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="PM:">
+                <search-people v-if="form_query.pm" :value.sync="form_query.pm.idap" @change="changeArea" />
+              </el-form-item>
+            </el-form>
+            <el-form :inline="true" :model="form_query" class="demo-form-inline" label-position="right" label-width="100px">
+              <el-form-item label="需求来源:">
+                {{ form_query.sourceTypeName }}
+              </el-form-item>
+              <el-form-item label="优先级:">
+                <el-select v-model="form_query.priority" size="small" clearable filterable placeholder="请选择" @change="changeArea">
+                  <el-option v-for="item in priorityList" :key="item.msg" :label="item.msg" :value="item.code" />
+                </el-select>
+              </el-form-item>
+            </el-form>
+          </div>
+        </section>
+        <section class="main-section">
+          <div class="el-main-title">
+            <div class="title-left-icon" />
+            <div class="title-left-name">需求描述</div>
+          </div>
+          <div>
+            <text-area :id="'pro-desc'" :value.sync="form_query.description" :empty-text="'点击'" :input-button="'添加描述'" @change="changeArea" />
+          </div>
+        </section>
+        <section class="main-section">
+          <div class="el-main-title">
+            <div class="title-left-icon" />
+            <div class="title-left-name">评论</div>
+          </div>
+          <div>
+            <text-area :id="'pro-target'" :value.sync="form_query.target" :empty-text="'点击'" :input-button="'添加评论'" @change="changeArea" />
+          </div>
+        </section>
+      </el-container>
+      <!-- 概览 -->
+      <!-- 任务 -->
+      <el-container v-show="activeName === '2'" class="is-vertical">
+        <section class="main-section">
+          <tasks-list />
+        </section>
+      </el-container>
+      <!-- 任务 -->
+      <!-- 缺陷 -->
+      <el-container v-show="activeName === '3'" class="is-vertical">
+        <section class="main-section" />
+      </el-container>
+      <!-- 缺陷 -->
+      <!-- 统计 -->
+      <el-container v-show="activeName === '4'" class="is-vertical">
+        <section class="main-section" />
+      </el-container>
+      <!-- 统计 -->
+      <!-- 新建 -->
+      <create-requirement
+        title="新建需求"
+        :visible="createVisible"
+        @cancel="createVisible=false"
+        @confirm="getRequirementById();createVisible=false"
+      />
+      <!-- 新建 -->
+      <!-- 编辑 -->
+      <create-requirement
+        title="编辑需求"
+        :data="form_query"
+        :visible="updateVisible"
+        @cancel="updateVisible=false"
+        @confirm="getRequirementById();updateVisible=false"
+      />
+      <!-- 编辑 -->
+      <!-- 删除 -->
+      <el-dialog :visible.sync="deleteVisible" width="30%" center :close-on-click-modal="false">
+        <div align="center">确定要删除此 {{ form_query.name }} 需求吗?</div>
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="deleteVisible = false">关 闭</el-button>
+          <el-button type="primary" @click="deleteRequirement()">确 定</el-button>
+        </span>
+      </el-dialog>
+      <!-- 删除 -->
+      <openDialog v-if="task_open" ref="task_createdUpdata" />
+      <drawer
+        ref="drawer"
+        title="项目成员"
+        center
+        :display.sync="display"
+        width="28%"
+        :delete="form_query"
+        :types="false"
+        :inner="true"
+        :mask="false"
+        @childValInput="childVal"
+        @click.stop
+      />
+    </el-container>
+  </div>
+</template>
+<script>
+const _ = require('lodash')
+import {
+  updateRequirement,
+  getRequirementById,
+  updateRequirementStatus,
+  deleteRequirement,
+  showRequirementEnum,
+  projectListProject,
+  iterationList
+} from '@/api/requirement.js'
+import searchPeople from '@/components/select/searchPeople'
+import textArea from '@/components/input/textArea'
+import drawer from '@/views/projectManage/Drawer'
+import createRequirement from '@/views/projectManage/requirement/list/create.vue'
+import openDialog from '@/views/projectManage/dialog_vue'
+import image_url from '@/assets/home_images/home_u.png'
+import tasksList from './components/taskList'
+export default {
+  components: {
+    searchPeople,
+    textArea,
+    drawer,
+    createRequirement,
+    openDialog,
+    tasksList
+  },
+  filters: {
+    ellipsis(value, num) {
+      if (!value) return ''
+      if (value.length > num) {
+        return value.slice(0, num) + '...'
+      }
+      return value
+    }
+  },
+  data() {
+    return {
+      activeName: '1', // 顶部tab切换
+      userInformation: localStorage.getItem('username'),
+      userNames: localStorage.getItem('realname'),
+      textarea: '', // 评论
+      requirementId: Number(this.$route.query.id), // 需求id
+      statusList: [], // 状态列表
+      priorityList: [], // 优先级列表
+      form_query: {},
+      display: false, // 设置成员弹框
+      num: 0, // 成员数量
+      image_url: image_url, // 成员icon
+      createVisible: false, // 新建需求弹框
+      updateVisible: false, // 编辑需求弹框
+      deleteVisible: false, // 删除需求弹框
+      task_open: false, // 新建任务弹框
+      belongProjectList: [], // 所属项目列表
+      iterationList: [] // 所属迭代列表
+    }
+  },
+  computed: {
+    getStatus() {
+      return this.statusList.find(item => item.code === this.form_query.status) || { msg: null }
+    }
+  },
+  created() {
+    this.showRequirementEnum()
+    this.getRequirementById()
+    this.getBelongProject()
+    this.getIterationList()
+    this.$store.state.data.status = true
+    this.$store.state.data.bizId = true
+  },
+  destroyed() {
+    this.$store.state.data.status = false
+    this.$store.state.data.bizId = false
+  },
+  methods: {
+    async changeArea(e) { // area修改
+      const requirementInfo = _.cloneDeep(this.form_query)
+      requirementInfo.pm = requirementInfo.pm.idap
+      const res = await updateRequirement(requirementInfo)
+      if (res.code === 200) {
+        this.$message({ message: res.msg, type: 'success', duration: 1000, offset: 150 })
+      }
+    },
+    async getBelongProject() { // 获取所属项目列表
+      const res = await projectListProject({ bizId: Number(localStorage.getItem('bizId')) })
+      if (res.code === 200) {
+        this.belongProjectList = res.data.filter(item => item.id !== -1)
+      }
+    },
+    async getIterationList() { // 获取所属迭代列表
+      const res = await iterationList({
+        bizId: Number(localStorage.getItem('bizId')),
+        curIndex: 1,
+        pageSize: 999
+      })
+      if (res.code === 200) {
+        this.iterationList = res.data.list
+      }
+      this.iterationList.unshift({
+        id: -1,
+        name: '无'
+      })
+    },
+    async showRequirementEnum() { // 获取需求状态列表,优先级列表
+      const res = await showRequirementEnum()
+      if (res.code === 200) {
+        this.statusList = res.data.requirementStatus
+        this.priorityList = res.data.priority
+      }
+    },
+    async getRequirementById() { // 获取需求详情
+      const res = await getRequirementById({ id: this.$route.query.id })
+      if (res.code === 200) {
+        this.form_query = res.data
+      }
+    },
+    async updateStatus(status) { // 修改状态
+      const res = await updateRequirementStatus({
+        id: this.$route.query.id,
+        status: status.code,
+        modifier: localStorage.getItem('username')
+      })
+      if (res.code === 200) {
+        this.$message({ message: '状态修改成功', type: 'success', duration: 1000, offset: 150 })
+      }
+    },
+    childVal(val) {
+      this.num = val
+    },
+    setChild() { // 设置成员
+      this.$refs.drawer.getRoleList()
+    },
+    async deleteRequirement() { // 删除需求
+      const res = await deleteRequirement({
+        id: this.$route.query.id,
+        modifier: localStorage.getItem('username')
+      })
+      if (res.code === 200) {
+        this.$message({ message: '删除成功', type: 'warning', duration: 1000, offset: 150 })
+      }
+    },
+    reated_task(e) { // 新建任务
+      this.task_open = true
+      this.$nextTick(() => {
+        this.$refs.task_createdUpdata.init(2)
+      })
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+@import '@/styles/detail-pages.scss';
+/deep/.el-button {
+  cursor: pointer;
+}
+@include hide-open-header;
+.bg-project {
+  @include bg-project;
+}
+.main-header {
+  @include main-header;
+}
+.main-header::after {
+  @include main-header-after;
+}
+.main-section {
+  @include main-section;
+  .detail-info {
+    padding: 20px 34px;
+    /deep/.el-input__inner{
+      border: 1px solid rgba(220,223,230,0)
+    }
+    /deep/.el-input__inner:hover{
+      border: 1px solid rgba(220,223,230,1)
+    }
+    /deep/.is-focus .el-input__inner {
+      border: 1px solid #409EFF;
+    }
+    /deep/.el-select{
+      .el-input__suffix-inner {
+        visibility: hidden;
+      }
+    }
+    /deep/.el-select:hover{
+      .el-input__suffix-inner {
+        visibility: visible;
+      }
+    }
+    .demo-form-inline {
+      .el-form-item {
+        width: 33%;
+        margin-right: 0;
+      }
+    }
+  }
+}
+</style>

+ 3 - 2
src/views/workbench/person/index.vue

@@ -300,15 +300,16 @@ export default {
           startTime: moment(view.activeStart).subtract(1, 'month').format('YYYY.MM.DD'),
           endTime: moment(view.activeEnd).add(1, 'month').format('YYYY.MM.DD')
         },
-        teamSearchInfo: { bizId: localStorage.getItem('bizId') || null },
+        teamSearchInfo: null,
         searchScheduleInfo: { origin: [0, 1] }
       }
       const res = await queryWorkListByTime(params)
       if (res.code === 200) {
         this.calendarEvents = res.data.map(item => {
+          const title = item.origin ? '日程' : '排期'
           return {
             id: item.id,
-            title: `${item.peopleObject.name}:${item.name}`,
+            title: `${title}:${item.peopleObject.name}:${item.name}`,
             start: moment(item.startTime).toDate(),
             end: moment(item.endTime).add(1, 'day').toDate(),
             detailData: item

+ 2 - 2
src/views/workbench/team/index.vue

@@ -298,10 +298,10 @@ export default {
       const res = await queryTeamWorkListByTime(params)
       if (res.code === 200) {
         this.calendarEvents = res.data.map(item => {
+          const title = item.origin ? '日程' : '排期'
           return {
             id: item.id,
-            cssClass: ['el-icon-s-order'],
-            title: `${item.peopleObject.name}:${item.name}`,
+            title: `${title}:${item.peopleObject.name}:${item.name}`,
             start: moment(item.startTime).toDate(),
             end: moment(item.endTime).add(1, 'day').toDate(),
             detailData: item