requirementDetail.vue 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. <template>
  2. <div class="bg-project" @click="display = false">
  3. <el-container>
  4. <el-header class="main-header" :class="{'paddingLeft': form_query.type === 1}">
  5. <div class="top-page-title">
  6. <img v-if="form_query.type === 1" :src="urgent" style="height: 32px;padding: 0 10px;">
  7. <div class="header-title">
  8. <span class="title-id">REQUIREMENT-{{ form_query.id }}</span>
  9. <el-tooltip class="item" effect="dark" :content="form_query.name" placement="bottom">
  10. <span
  11. v-clipboard:copy="form_query.name"
  12. v-clipboard:success="copyName"
  13. class="title-name"
  14. >{{ form_query.name }}</span>
  15. </el-tooltip>
  16. </div>
  17. <el-button v-show="form_query.status === -2" disabled plain size="mini">Hold</el-button>
  18. <el-dropdown v-show="form_query.status !== -2" placement="bottom" @command="updateStatus">
  19. <el-button size="mini" plainclass="el-dropdown-link drop_down">
  20. {{ getStatus.name }}
  21. <i class="el-icon-arrow-down el-icon--right" />
  22. </el-button>
  23. <el-dropdown-menu slot="dropdown" align="center">
  24. <el-dropdown-item
  25. v-for="item in form_query.availableStatusList"
  26. :key="item.name"
  27. :command="{value:item.code,label:item.name}"
  28. :disabled="form_query.status === item.code? true: false"
  29. >{{ item.name }}</el-dropdown-item>
  30. </el-dropdown-menu>
  31. </el-dropdown>
  32. </div>
  33. <div class="top-tabs">
  34. <el-tabs v-model="activeName">
  35. <el-tab-pane label="概览" name="1" />
  36. <el-tab-pane label="任务" name="2" />
  37. <el-tab-pane label="缺陷" name="3" />
  38. <el-tab-pane label="统计" name="4" />
  39. </el-tabs>
  40. </div>
  41. <div class="top-control">
  42. <el-dropdown placement="bottom">
  43. <i class="el-icon-circle-plus icon-add" />
  44. <el-dropdown-menu slot="dropdown">
  45. <el-dropdown-item @click.native="reated_task()">新建任务</el-dropdown-item>
  46. <el-dropdown-item @click.native="created_bug()">新建缺陷</el-dropdown-item>
  47. </el-dropdown-menu>
  48. </el-dropdown>
  49. <div class="line" />
  50. <span style="vertical-align: bottom; cursor: pointer; color: #6F7C93;" @click.stop="setChild(), display = true"><img style="width: 20px; display: inline-block;" :src="image_url">&nbsp; {{ num }} &nbsp;</span>
  51. <div class="line" />
  52. <i class="el-icon-setting icon-delete" @click="updateVisible = true" />
  53. <div class="line" />
  54. <i class="el-icon-delete icon-delete" @click="deleteVisible = true" />
  55. </div>
  56. </el-header>
  57. <el-container v-show="activeName === '1'">
  58. <section class="main-section">
  59. <div class="Layout_space_between">
  60. <div class="el-main-title">
  61. <div class="title-left-icon" />
  62. <div class="title-left-name">工作流</div>
  63. </div>
  64. <el-popover
  65. v-model="visible"
  66. placement="bottom-start"
  67. width="300px"
  68. :visible-arrow="false"
  69. trigger="manual"
  70. >
  71. <el-input
  72. v-model="textarea2"
  73. type="textarea"
  74. rows="5"
  75. style="width:300px"
  76. placeholder="请输入Hold原因(选填)"
  77. />
  78. <div style="text-align: right; margin-top: 10px;">
  79. <el-button size="mini" type="text" @click="visible = false">取消</el-button>
  80. <el-button type="primary" size="mini" @click="requirementHold(textarea2)">确定</el-button>
  81. </div>
  82. <el-button slot="reference" class="el-btn-size" size="mini" @click="changeBtn">{{ form_query.status === -2 ? HoldTask = '解除 Hold' : HoldTask = 'Hold 需求' }}</el-button>
  83. </el-popover>
  84. </div>
  85. <timeLine :id="requirementId" ref="timeLine1" :name="'需求'" />
  86. </section>
  87. </el-container>
  88. <!-- 概览 -->
  89. <el-container v-show="activeName === '1'" class="is-vertical">
  90. <section class="main-section">
  91. <div class="el-main-title">
  92. <div class="title-left-icon" />
  93. <div class="title-left-name">基础信息</div>
  94. </div>
  95. <div class="detail-info">
  96. <el-form :inline="true" :model="form_query" class="demo-form-inline" label-position="left" label-width="100px">
  97. <el-form-item label="所属项目:" class="module">
  98. <div v-if="form_query.belongingProject !== -1" @click="jump('项目详情',form_query.belongingProject)">{{ form_query.belongingProjectName }}</div>
  99. <template v-else>{{ form_query.belongingProjectName }}</template>
  100. </el-form-item>
  101. <el-form-item label="所属迭代:">
  102. <el-select v-model="form_query.iterationId" placeholder="请选择" @change="changeArea">
  103. <el-option v-for="(item,index) in iterationList" :key="item.name + index" :label="item.name" :value="item.id" />
  104. </el-select>
  105. </el-form-item>
  106. <el-form-item label="PM:">
  107. <search-people :value.sync="form_query.pm" :clearable="false" @change="changeArea" />
  108. </el-form-item>
  109. </el-form>
  110. <el-form :inline="true" :model="form_query" class="demo-form-inline" label-position="left" label-width="100px">
  111. <el-form-item label="需求方向:">
  112. <el-cascader v-model="form_query.rqmtOrntIds" size="medium" collapse-tags :props="props" :options="demandDirection" placeholder="请选择" @change="changeArea" />
  113. </el-form-item>
  114. <el-form-item label="需求来源:">
  115. <el-select v-model="form_query.sourceType" size="small" filterable placeholder="请选择" @change="changeArea">
  116. <el-option v-for="(item,index) in sourceTypeList" :key="item.msg + index" :label="item.msg" :value="item.code" />
  117. </el-select>
  118. </el-form-item>
  119. <el-form-item label="需求提出人:">
  120. <search-people :value.sync="form_query.rqmtProposer" :clearable="false" :multiple="true" @change="changeArea" />
  121. </el-form-item>
  122. </el-form>
  123. <el-form :inline="true" :model="form_query" class="demo-form-inline" label-position="left" label-width="100px">
  124. <el-form-item label="优先级:">
  125. <el-select v-model="form_query.priority" size="small" filterable placeholder="请选择" @change="changeArea">
  126. <el-option v-for="(item,index) in priorityList" :key="item.msg + index" :label="item.msg" :value="item.code" />
  127. </el-select>
  128. </el-form-item>
  129. <el-form-item label="是否跟版:">
  130. <el-select v-model="form_query.dependOnRelease" size="small" filterable placeholder="请选择" @change="changeArea">
  131. <el-option v-for="(item,index) in dependList" :key="item.msg + index" :label="item.msg" :value="item.code" />
  132. </el-select>
  133. </el-form-item>
  134. <el-form-item v-if="form_query.dependOnRelease === 1" label="跟版客户端:">
  135. <el-select v-model="form_query.referredClientType" size="small" multiple filterable placeholder="请选择" @change="changeArea">
  136. <el-option v-for="(item,index) in appClient" :key="item.msg + index" :label="item.msg" :value="item.code" />
  137. </el-select>
  138. </el-form-item>
  139. </el-form>
  140. <el-form :model="form_query" class="demo-form-inline" label-position="left" label-width="100px">
  141. <el-form-item label="BRD链接:" class="PRD">
  142. <span class="PRD-link"><a :href="form_query.brdUrl" target="_blank">{{ form_query.brdUrl }}</a></span>
  143. </el-form-item>
  144. <el-form-item label="PRD链接:" prop="mrdUrl" class="PRD">
  145. <span class="PRD-link"><a :href="form_query.mrdUrl" target="_blank">{{ form_query.mrdUrl }}</a></span>
  146. </el-form-item>
  147. </el-form>
  148. </div>
  149. </section>
  150. <section class="main-section">
  151. <div class="el-main-title">
  152. <div class="title-left-icon" />
  153. <div class="title-left-name">需求描述</div>
  154. </div>
  155. <div>
  156. <text-area :id="'pro-desc'" :value.sync="form_query.description" :empty-text="'点击'" :input-button="'添加描述'" @change="changeArea" />
  157. </div>
  158. </section>
  159. <section class="main-section">
  160. <div class="el-main-title">
  161. <div class="title-left-icon" />
  162. <div class="title-left-name">
  163. <div>需求计划
  164. <el-tooltip class="item" effect="dark" :content="isScheduleLocked === 1? '点击解锁排期' : '点击锁定排期'" placement="top">
  165. <span
  166. v-if="!form_query.needGrey || isScheduleLocked === 1"
  167. class="titleStatus"
  168. :class="isScheduleLocked === 1 ? 'el-icon-lock' : 'el-icon-unlock'"
  169. @click="changeSchedule"
  170. >
  171. {{ isScheduleLocked === 1 ? '已锁定' : '未锁定' }}
  172. </span>
  173. </el-tooltip>
  174. </div>
  175. </div><br>
  176. </div>
  177. <section class="main-section">
  178. <div class="allTips">
  179. <el-radio-group v-model="listOrGannt" size="small" style="margin-left: 10px">
  180. <el-radio-button label="列表" />
  181. <el-radio-button label="甘特图" />
  182. </el-radio-group>
  183. <div v-show="listOrGannt === '列表'" class="allTips">
  184. <div v-if="BackToTheLatest" class="Scheduling" @click="GetRequireScheduleHistory"><i class="el-icon-refresh" /> 回到最新</div>
  185. <div v-if="Latest" align="left" class="Scheduling" @click="scheduleHiHide"><div class="el-icon-document" /> 排期变更记录</div>
  186. <download :id="requirementId" :name="'需求'" />
  187. </div>
  188. </div>
  189. </section>
  190. <el-container v-show="listOrGannt === '列表'" class="allTips">
  191. <el-main style="padding: 0;">
  192. <!-- <schedule-list :id="requirementId" ref="ScheduleEvent" :showunlock="showunlock" :type-list="taskScheduleEvent" :required-list="taskScheduleList" class-name="white" :all="true" :no-move="false" /> -->
  193. <demand :id="requirementId" ref="ScheduleEvent" :showunlock="showunlock" :type-list="taskScheduleEvent" :required-list="taskScheduleList" />
  194. </el-main>
  195. <el-aside v-if="lockHide" class="SchedulingAside">
  196. <div v-for="(item, index) in SchedulingContent" :key="index" class="SchedulingDiv" @click="clickScheduling(item)">
  197. <i v-show="index < SchedulingContent.length - 1" />
  198. <div class="timeline">
  199. <div class="SchedulingTow" :class="{'vss' : item.id === ScheduId}">
  200. <div v-if="item.operationType === 0" class="el-icon-unlock image" />
  201. <div v-if="item.operationType === 1" class="el-icon-lock image" />
  202. </div>
  203. <div class="modifyTime">{{ item.modifyTime }}</div>
  204. <div>{{ item.operatorObject.name !== null ? item.operatorObject.name : '' }} <span class="btn">{{ item.operation }}</span></div>
  205. <div v-if="item.remarkTypeName"><span class="modifyTime">{{ '解锁原因 : ' }}</span>{{ item.remarkTypeName }}</div>
  206. <div v-if="item.remark"><span class="modifyTime">{{ '具体描述 : ' }}</span>{{ item.remark }}</div>
  207. </div>
  208. </div>
  209. <div v-if="SchedulingContent.length === 0" style="width: 270px; margin: 50% 20px; text-align: center;"> 暂无排期变更记录!</div>
  210. </el-aside>
  211. </el-container>
  212. <gannt-views v-if="listOrGannt === '甘特图'" />
  213. <div class="detail-info border-top">
  214. <el-divider />
  215. <el-form ref="form_query" :inline="true" :model="form_query" class="Layout_space_start" label-position="left" label-width="140px">
  216. <el-form-item
  217. v-if="brdPassRealTime"
  218. label="BRD评审通过时间:"
  219. >
  220. <el-date-picker v-model="form_query.brdPassRealTime" type="date" :clearable="false" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" size="small" @change="setChangeArea" />
  221. </el-form-item>
  222. <el-form-item
  223. v-if="prdPassRealTime"
  224. label="PRD评审通过时间:"
  225. >
  226. <el-date-picker v-model="form_query.prdPassRealTime" type="date" :clearable="false" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" size="small" @change="setChangeArea" />
  227. </el-form-item>
  228. <el-form-item
  229. v-if="techInRealTime"
  230. label="技术准入时间:"
  231. >
  232. <el-date-picker v-model="form_query.techInRealTime" type="date" :clearable="false" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" size="small" @change="setChangeArea" />
  233. </el-form-item>
  234. <el-form-item
  235. v-if="onlineRealTime"
  236. label="实际上线时间:"
  237. >
  238. <el-date-picker v-model="form_query.onlineRealTime" type="date" :clearable="false" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" size="small" @change="setChangeArea" />
  239. </el-form-item>
  240. </el-form>
  241. </div>
  242. </section>
  243. <section class="main-section">
  244. <div class="el-main-title">
  245. <div class="title-left-icon" />
  246. <div class="title-left-name">动态</div>
  247. </div>
  248. <el-tabs v-model="optionName" class="sign-tabs">
  249. <el-tab-pane label="评论" name="first">
  250. <div class="detail-info">
  251. <ul class="comment-main">
  252. <li v-for="(item,index) in comments" :key="'comment'+index">
  253. <span class="comment-name">{{ item.commentInfo.name }}</span>
  254. <span class="comment-gmtCreater">{{ item.commentInfo.gmtCreater }}</span><br>
  255. <span class="comment-content">{{ item.commentInfo.content }}</span>
  256. </li>
  257. </ul>
  258. <el-input
  259. v-model="commentContent"
  260. type="textarea"
  261. placeholder="请输入评论内容"
  262. maxlength="300"
  263. show-word-limit
  264. :autosize="{ minRows: 3, maxRows: 5}"
  265. style="margin-bottom: 20px"
  266. />
  267. <el-row>
  268. <el-col :span="2" :offset="22"><el-button type="primary" size="small" @click="addComment">发表评论</el-button></el-col>
  269. </el-row>
  270. </div>
  271. </el-tab-pane>
  272. <el-tab-pane label="变更记录" name="second">
  273. <record :id="requirementId" ref="record" :name="'需求'" />
  274. </el-tab-pane>
  275. </el-tabs>
  276. </section>
  277. </el-container>
  278. <!-- 概览 -->
  279. <!-- 任务 -->
  280. <el-container v-if="activeName === '2'" class="is-vertical">
  281. <section class="main-section contain">
  282. <tasks-list ref="tasks-list" @update="getRequirementById" />
  283. </section>
  284. </el-container>
  285. <!-- 任务 -->
  286. <!-- 缺陷 -->
  287. <el-container v-if="activeName === '3'" class="is-vertical">
  288. <section class="main-section contain">
  289. <bugTableDialog ref="bugTableDialog" :obj-id="{ requireId: Number(this.$route.query.id) }" />
  290. </section>
  291. </el-container>
  292. <!-- 缺陷 -->
  293. <!-- 统计 -->
  294. <el-container v-if="activeName === '4'" class="is-vertical">
  295. <section class="main-section contain">
  296. <data-statistics ref="data-statistics" @change="getRequirementById" />
  297. </section>
  298. </el-container>
  299. <!-- 统计 -->
  300. <!-- 编辑 -->
  301. <create-requirement
  302. title="编辑需求"
  303. :data="form_query"
  304. :visible="updateVisible"
  305. @cancel="updateVisible=false"
  306. @confirm="getRequirementById();updateVisible=false"
  307. />
  308. <!-- 编辑 -->
  309. <!-- 删除 -->
  310. <el-dialog :visible.sync="deleteVisible" class="public_task" title="删除确认" width="30%" :close-on-click-modal="false">
  311. <div class="blueStripe" />
  312. <div align="center">确定要删除此 {{ form_query.name }} 需求吗?</div>
  313. <span slot="footer" class="dialog-footer">
  314. <el-button size="mini" @click="deleteVisible = false">关 闭</el-button>
  315. <el-button size="mini" type="primary" @click="deleteRequirement()">确 定</el-button>
  316. </span>
  317. </el-dialog>
  318. <!-- 删除 -->
  319. <openDialog v-if="task_open" ref="task_createdUpdata" :no-jump="true" @change="reloadList" />
  320. <createdBug v-if="bug_open" ref="createdBug" :required="requirementId" @reloadList="reloadList" />
  321. <!-- 排期锁定 -->
  322. <schedule :visible.sync="scheduleVisble" :name="'需求'" :is-schedule-locked="isScheduleLocked" :require-id="requirementId" @updataData="GetRequireScheduleHistory" />
  323. <!-- 排期锁定 -->
  324. <drawer
  325. ref="drawer"
  326. title="需求成员"
  327. center
  328. :display.sync="display"
  329. width="28%"
  330. :delete="form_query"
  331. :types="false"
  332. :inner="true"
  333. :mask="false"
  334. @childValInput="childVal"
  335. @click.stop
  336. />
  337. <el-dialog
  338. title="状态变更"
  339. :visible.sync="dialogStatusVisible"
  340. width="30%"
  341. class="public_task"
  342. >
  343. <div class="blueStripe" />
  344. <div align="center">
  345. <el-form ref="form_query" :inline="true" :model="form_query" :rules="rules" label-position="left" label-width="158px">
  346. <el-form-item v-if="statusName === 'BRD评审通过'" :label="statusName + '时间:'" prop="brdPassRealTime">
  347. <el-date-picker v-model="form_query.brdPassRealTime" type="date" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" />
  348. </el-form-item>
  349. <el-form-item v-if="statusName === 'PRD评审通过'" :label="statusName + '时间:'" prop="prdPassRealTime">
  350. <el-date-picker v-model="form_query.prdPassRealTime" type="date" :clearable="false" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" />
  351. </el-form-item>
  352. <el-form-item v-if="statusName === '技术准入'" :label="statusName + '时间:'" prop="techInRealTime">
  353. <el-date-picker v-model="form_query.techInRealTime" type="date" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" />
  354. </el-form-item>
  355. <el-form-item v-if="statusName === '已上线'" :label="statusName + '时间:'" prop="onlineRealTime">
  356. <el-date-picker v-model="form_query.onlineRealTime" type="date" placeholder="请选择" format="yyyy.MM.dd" value-format="yyyy.MM.dd" style="width: 100%;" />
  357. </el-form-item>
  358. </el-form>
  359. </div>
  360. <span slot="footer" class="dialog-footer">
  361. <el-button @click="dialogStatusVisible = false">取 消</el-button>
  362. <el-button type="primary" @click="setChangeArea">确 定</el-button>
  363. </span>
  364. </el-dialog>
  365. </el-container>
  366. </div>
  367. </template>
  368. <script>
  369. const _ = require('lodash')
  370. import Vue from 'vue'
  371. import VueClipboard from 'vue-clipboard2'
  372. Vue.use(VueClipboard)
  373. import {
  374. updateRequirement,
  375. getRequirementById,
  376. updateRequirementStatus,
  377. deleteRequirement,
  378. showRequirementEnum,
  379. projectListProject,
  380. iterationList,
  381. getCommentList,
  382. addComment,
  383. listByRequire,
  384. requirementHold,
  385. requirementUnhold,
  386. configShowRequireStatusEnum,
  387. scheduleGetRequireScheduleHistory,
  388. scheduleGetHistoryRequireScheduleById,
  389. // scheduleGetHistoryScheduleById,
  390. settingQueryBizRqmtOrntList
  391. } from '@/api/requirement.js'
  392. import { projectGetMemberList } from '@/api/drewer'
  393. import { configShowTaskEnum } from '@/api/taskIndex'
  394. import searchPeople from '@/components/select/searchPeople'
  395. import textArea from '@/components/input/textArea'
  396. import drawer from '@/views/projectManage/Drawer'
  397. import createRequirement from '@/views/projectManage/requirement/list/create.vue'
  398. import openDialog from '@/views/projectManage/dialog_vue'
  399. import image_url from '@/assets/home_images/home_u.png'
  400. import createdBug from '@/views/projectManage/bugList/file/createdBug'
  401. import tasksList from './components/taskList'
  402. import dataStatistics from './components/dataStatistics'
  403. import moment from 'moment'
  404. // import scheduleList from './components/scheduleList'
  405. import bugTableDialog from '@/views/projectManage/bugList/details/bugTableDialog' // 缺陷表格
  406. import schedule from '@/views/projectManage/schedule' // 排期锁定弹窗
  407. import urgent from '@/assets/urgent.png'
  408. import download from '@/views/projectManage/components/export.vue'
  409. import demand from '@/views/projectManage/components/demand.vue'
  410. import record from '@/views/projectManage/components/record.vue'
  411. import timeLine from '@/views/projectManage/components/timeLine.vue'
  412. import ganntViews from './components/ganntViews'
  413. import '@/styles/PublicStyle/index.scss'
  414. export default {
  415. components: {
  416. searchPeople,
  417. textArea,
  418. drawer,
  419. createRequirement,
  420. openDialog,
  421. createdBug,
  422. tasksList,
  423. dataStatistics,
  424. // scheduleList,
  425. bugTableDialog,
  426. schedule,
  427. download,
  428. record,
  429. timeLine,
  430. demand,
  431. ganntViews
  432. },
  433. filters: {
  434. ellipsis(value, num) {
  435. if (!value) return ''
  436. if (value.length > num) {
  437. return value.slice(0, num) + '...'
  438. }
  439. return value
  440. }
  441. },
  442. data() {
  443. return {
  444. urgent: urgent,
  445. showunlock: true,
  446. show2: true,
  447. activeTitle: '1', // 默认展示列表视图
  448. taskShow: 1, // 默认展示仅任务
  449. taskArray: [{ code: 1, name: '仅任务' }, { code: 2, name: '仅子任务' }, { code: 3, name: '所有任务' }], // 需求下任务展示
  450. textarea2: '',
  451. HoldTask: '',
  452. props: {
  453. value: 'id',
  454. label: 'rqmtOrntName',
  455. children: 'childRqmtOrnts',
  456. multiple: true
  457. },
  458. rules: {
  459. brdPassRealTime: [{ required: true, message: '请输入BRD评审通过时间', trigger: 'change' }],
  460. prdPassRealTime: [{ required: true, message: '请输入PRD评审通过时间', trigger: 'change' }],
  461. techInRealTime: [{ required: true, message: '请输入技术准入时间', trigger: 'change' }],
  462. onlineRealTime: [{ required: true, message: '请输入实际上线时间', trigger: 'change' }]
  463. },
  464. Latest: true,
  465. statusName: '',
  466. statusValue: '',
  467. dialogStatusVisible: false,
  468. demandDirection: [], // 需求方向option
  469. brdPassRealTime: false, // BRD评审通过时间
  470. prdPassRealTime: false, // PRD评审通过时间
  471. techInRealTime: false, // 技术准入
  472. onlineRealTime: false, // 实际上线
  473. optionName: 'first',
  474. visible: false, // Hold任务
  475. ScheduId: '', // 排期ID
  476. BackToTheLatest: false, // 回到最新
  477. LockState: {}, // 锁定状态
  478. scheduleVisble: false, // 排期锁定
  479. activeName: '1', // 顶部tab切换
  480. userInformation: localStorage.getItem('username'),
  481. userNames: localStorage.getItem('realname'),
  482. textarea: '', // 评论
  483. requirementId: Number(this.$route.query.id), // 需求id
  484. statusList: [], // 状态列表
  485. priorityList: [], // 优先级列表
  486. sourceTypeList: [], // 需求来源列表
  487. appClient: [], // 跟版客户端列表
  488. taskScheduleEvent: [], // 排期类型列表
  489. dependList: [{ msg: '否', code: 0 }, { msg: '是', code: 1 }], // 是否跟版
  490. form_query: { pm: {}, rqmtOrntIds: [] },
  491. display: false, // 设置成员弹框
  492. num: 0, // 成员数量
  493. availableStatusList: [], // 状态
  494. image_url: image_url, // 成员icon
  495. updateVisible: false, // 编辑需求弹框
  496. deleteVisible: false, // 删除需求弹框
  497. task_open: false, // 新建任务弹框
  498. bug_open: false, // 新建缺陷弹框
  499. belongProjectList: [], // 所属项目列表
  500. iterationList: [], // 所属迭代列表
  501. commentContent: null, // 评论内容
  502. comments: [], // 评论列表
  503. taskScheduleList: [], // 排期数据
  504. lockHide: false, // 隐藏排期变更记录
  505. isScheduleLocked: '', // 锁定状态1锁定0未锁定
  506. SchedulingContent: [], // 排期历史变更记录
  507. listOrGannt: '列表'
  508. }
  509. },
  510. computed: {
  511. getStatus() {
  512. return this.availableStatusList.find(item => item.code === this.form_query.status) || { name: null }
  513. }
  514. },
  515. created() {
  516. this.showRequirementEnum()
  517. this.getRequirementById()
  518. this.getBelongProject()
  519. this.getIterationList()
  520. this.getCommentList()
  521. this.getTaskStatus()
  522. this.GetRequireScheduleHistory()
  523. this.$store.state.data.status = true
  524. // this.$store.state.data.bizId = true
  525. },
  526. destroyed() {
  527. this.$store.state.data.status = false
  528. // this.$store.state.data.bizId = false
  529. },
  530. methods: {
  531. async GetRequireScheduleHistory() {
  532. this.scheduleVisble = false
  533. const res = await scheduleGetRequireScheduleHistory(this.requirementId)
  534. const res1 = await listByRequire(this.requirementId)
  535. this.isScheduleLocked = res1.data.isScheduleLocked
  536. this.SchedulingContent = res.data
  537. this.BackToTheLatest = false // 回到最新
  538. this.Latest = true
  539. this.lockHide = false // 隐藏排期变更记录
  540. this.showunlock = true
  541. this.ScheduId = 0
  542. this.$refs.ScheduleEvent.listByTask(this.requirementId)
  543. this.$refs.ScheduleEvent.lockingchange()
  544. this.getRequirementById()
  545. },
  546. async clickScheduling(ele) {
  547. this.showunlock = false
  548. this.Latest = false
  549. this.ScheduId = ele.id
  550. const res = await scheduleGetHistoryRequireScheduleById(ele.id)
  551. this.taskScheduleList = res.data
  552. this.BackToTheLatest = true
  553. },
  554. async changeSchedule() { // 修改锁定状态
  555. if (this.isScheduleLocked === 1) {
  556. const res = await projectGetMemberList({ projectId: this.form_query.belongingProject, requireId: this.requirementId })
  557. if (res.code === 200) {
  558. const data = res.data.PM
  559. data.map(item => {
  560. if (item.memberInfoResponse.idap === localStorage.getItem('username')) {
  561. this.scheduleVisble = true
  562. }
  563. })
  564. if (!this.scheduleVisble) {
  565. this.$message({ message: '没有权限,请联系PM执行解锁!', type: 'error', duration: 2000, offset: 150 })
  566. }
  567. }
  568. } else {
  569. this.scheduleVisble = true
  570. }
  571. },
  572. // clickBackToTheLatest() {
  573. // this.$refs.ScheduleEvent.rowDrop()
  574. // },
  575. setChangeArea() {
  576. this.$refs.form_query.validate((valid) => {
  577. if (valid) {
  578. this.changeArea()
  579. } else {
  580. this.$message({ message: '还有必填项未填写', type: 'error', duration: 1000, offset: 150 })
  581. }
  582. })
  583. },
  584. async changeArea(e) { // area修改
  585. const requirementInfo = _.cloneDeep(this.form_query)
  586. requirementInfo.rqmtProposer = requirementInfo.rqmtProposer ? requirementInfo.rqmtProposer.join() : null
  587. if (requirementInfo.dependOnRelease === 1 && requirementInfo.referredClientType === null) {
  588. this.$message({ message: '跟版客户端不能为空', type: 'error', duration: 1000, offset: 150 })
  589. return false
  590. } else if (requirementInfo.dependOnRelease === 0) {
  591. requirementInfo.referredClientType = null
  592. }
  593. if (requirementInfo.referredClientType !== null) {
  594. requirementInfo.referredClientType = requirementInfo.referredClientType.join()
  595. }
  596. requirementInfo.status = this.statusValue
  597. const res = await updateRequirement(requirementInfo)
  598. if (res.code === 200) {
  599. this.dialogStatusVisible = false
  600. this.getRequirementById()
  601. this.$message({ message: '修改成功', type: 'success', duration: 1000, offset: 150 })
  602. }
  603. this.getRequirementById()
  604. },
  605. async getBelongProject() { // 获取所属项目列表
  606. const res = await projectListProject({ bizId: Number(localStorage.getItem('bizId')) })
  607. if (res.code === 200) {
  608. this.belongProjectList = res.data
  609. }
  610. },
  611. async getIterationList() { // 获取所属迭代列表
  612. const res = await iterationList({
  613. bizId: Number(localStorage.getItem('bizId')),
  614. curIndex: 1,
  615. pageSize: 999
  616. })
  617. if (res.code === 200) {
  618. this.iterationList = res.data.list
  619. }
  620. this.iterationList.unshift({ id: -1, name: '无' })
  621. },
  622. async showRequirementEnum() { // 获取需求状态列表,优先级列表,需求来源
  623. const res1 = await configShowRequireStatusEnum(localStorage.getItem('bizId'))
  624. if (res1.code === 200) {
  625. this.statusList = []
  626. this.statusList = res1.data.requirementStatus
  627. }
  628. const res = await showRequirementEnum()
  629. if (res.code === 200) {
  630. this.priorityList = res.data.priority
  631. this.sourceTypeList = res.data.sourceType
  632. this.appClient = res.data.appClient
  633. }
  634. const res3 = await settingQueryBizRqmtOrntList(localStorage.getItem('bizId'))
  635. if (res3.code === 200) { // 需求方向
  636. this.demandDirection = this.getTreeData(res3.data)
  637. }
  638. },
  639. getTreeData(data) {
  640. for (var i = 0; i < data.length; i++) {
  641. if (data[i].childRqmtOrnts.length < 1) {
  642. // children若为空数组,则将children设为undefined
  643. data[i].childRqmtOrnts = undefined
  644. } else {
  645. // children若不为空数组,则继续 递归调用 本方法
  646. this.getTreeData(data[i].childRqmtOrnts)
  647. }
  648. }
  649. return data
  650. },
  651. changeBtn() {
  652. if (this.HoldTask === 'Hold 需求') {
  653. this.visible = !this.visible
  654. this.textarea2 = ''
  655. }
  656. if (this.HoldTask === '解除 Hold') {
  657. this.requirementHold()
  658. }
  659. },
  660. async requirementHold(val) { // 锁定Hold
  661. if (this.HoldTask === 'Hold 需求') {
  662. const res = await requirementHold(this.requirementId, { 'remark': val })
  663. if (res.code === 200) {
  664. this.getRequirementById()
  665. this.showRequirementEnum()
  666. this.$refs.timeLine1.taskGetWorkFlow()
  667. this.visible = false
  668. this.$message({ message: '已修改状态为 Hold', type: 'success', duration: 1000, offset: 150 })
  669. }
  670. }
  671. if (this.HoldTask === '解除 Hold') {
  672. const res = await requirementUnhold(this.requirementId)
  673. if (res.code === 200) {
  674. this.getRequirementById()
  675. this.showRequirementEnum()
  676. this.$refs.timeLine1.taskGetWorkFlow()
  677. this.$message({ message: 'Hold 状态已解除', type: 'success', duration: 1000, offset: 150 })
  678. }
  679. }
  680. },
  681. async getTaskStatus() { // 获取排期类型
  682. const res = await configShowTaskEnum()
  683. if (res.code === 200) {
  684. this.taskScheduleEvent = res.data.taskScheduleEvent || []
  685. }
  686. },
  687. async getRequirementById() { // 获取需求详情
  688. const res = await getRequirementById({ id: this.$route.query.id })
  689. if (res.code === 200) {
  690. this.form_query = res.data
  691. if (this.form_query.referredClientType !== null) {
  692. this.form_query.referredClientType = this.form_query.referredClientTypes
  693. }
  694. this.availableStatusList = res.data.availableStatusList
  695. if (this.form_query.rqmtProposer === null || this.form_query.rqmtProposer === '') {
  696. this.form_query.rqmtProposer = null
  697. } else {
  698. this.form_query.rqmtProposer = this.form_query.rqmtProposer.split(',')
  699. }
  700. }
  701. this.availableStatusList.map(item => {
  702. if (item.name === 'BRD评审通过') {
  703. if (this.form_query.status >= item.code) {
  704. this.brdPassRealTime = true
  705. }
  706. }
  707. if (item.name === 'PRD评审通过') {
  708. if (this.form_query.status >= item.code) {
  709. this.prdPassRealTime = true
  710. }
  711. }
  712. if (item.name === '技术准入') {
  713. if (this.form_query.status >= item.code) {
  714. this.techInRealTime = true
  715. }
  716. }
  717. if (item.name === '已上线') {
  718. if (this.form_query.status >= item.code) {
  719. this.onlineRealTime = true
  720. }
  721. }
  722. })
  723. },
  724. async getCommentList() { // 获取需求评论
  725. const res = await getCommentList({ type: 4, joinId: this.$route.query.id })
  726. if (res.code === 200) {
  727. this.comments = res.data
  728. this.commentContent = ''
  729. }
  730. },
  731. async addComment() { // 发表需求评论
  732. if (this.commentContent.replace(/\s+/g, '') === '' || this.commentContent === null) {
  733. this.$message.warning('评论不能为空')
  734. return
  735. }
  736. const user = localStorage.getItem('username')
  737. const res = await addComment({
  738. commentInfo: { joinId: this.$route.query.id, type: 4, content: this.commentContent },
  739. user: { ename: user }
  740. })
  741. if (res.code === 200) {
  742. this.getCommentList()
  743. } else {
  744. this.$message.warning(res.msg)
  745. }
  746. },
  747. async updateStatus(status) { // 修改状态
  748. if (status.label === 'PRD评审通过' || status.label === 'BRD评审通过' || status.label === '技术准入' || status.label === '已上线') {
  749. this.statusName = status.label
  750. this.statusValue = status.value
  751. this.dialogStatusVisible = true
  752. status.label === 'BRD评审通过' ? this.form_query.brdPassRealTime = moment().locale('zh-cn').format('YYYY.MM.DD') : '' // BRD评审通过时间
  753. status.label === 'PRD评审通过' ? this.form_query.prdPassRealTime = moment().locale('zh-cn').format('YYYY.MM.DD') : '' // PRD评审通过时间
  754. status.label === '技术准入' ? this.form_query.techInRealTime = moment().locale('zh-cn').format('YYYY.MM.DD') : '' // 技术准入
  755. status.label === '已上线' ? this.form_query.onlineRealTime = moment().locale('zh-cn').format('YYYY.MM.DD') : '' // 实际上线
  756. return false
  757. }
  758. const res = await updateRequirementStatus({
  759. id: this.$route.query.id,
  760. status: status.value,
  761. modifier: localStorage.getItem('username')
  762. })
  763. if (res.code === 200) {
  764. this.$refs.timeLine1.taskGetWorkFlow()
  765. this.$refs.record.operationLogTask()
  766. this.$message({ message: '修改成功', type: 'success', duration: 1000, offset: 150 })
  767. }
  768. this.getRequirementById()
  769. },
  770. childVal(val) {
  771. this.num = val
  772. },
  773. setChild() { // 设置成员
  774. this.$refs.drawer.getRoleList()
  775. },
  776. async deleteRequirement() { // 删除需求
  777. const res = await deleteRequirement({
  778. id: this.$route.query.id,
  779. modifier: localStorage.getItem('username')
  780. })
  781. if (res.code === 200) {
  782. this.$message({ message: '删除成功', type: 'success', duration: 1000, offset: 150 })
  783. this.$router.push({ name: '需求', query: {}})
  784. }
  785. },
  786. reated_task() { // 新建任务
  787. this.task_open = true
  788. this.$nextTick(() => {
  789. this.$refs.task_createdUpdata.init(4, this.$route.query.id,
  790. {
  791. requirementId: this.requirementId,
  792. name: `${this.form_query.name}的任务`,
  793. priority: this.form_query.priority
  794. }
  795. )
  796. })
  797. },
  798. created_bug() { // 缺陷创建
  799. this.bug_open = true
  800. this.$nextTick(() => {
  801. this.$refs.createdBug.init(1)
  802. })
  803. },
  804. scheduleHiHide() { // 排期变更显示隐藏
  805. this.lockHide = !this.lockHide
  806. this.lockHide === false ? this.BackToTheLatest = false : ''
  807. this.showunlock = true
  808. },
  809. jump(page, id) { // 跳转
  810. this.$router.push({ name: page, query: { id: id }})
  811. },
  812. reloadList() {
  813. this.GetRequireScheduleHistory()
  814. this.getRequirementById()
  815. if (this.$refs['bugTableDialog']) {
  816. this.$refs['bugTableDialog'].bugGetTableList()
  817. }
  818. if (this.$refs['data-statistics']) {
  819. this.$refs['data-statistics'].getRequireSumData()
  820. }
  821. if (this.$refs['tasks-list']) {
  822. this.$refs['tasks-list'].get_allTask()
  823. }
  824. },
  825. copyName(name) { // 复制名字
  826. this.$message({ message: '复制成功', type: 'success', duration: 1000, offset: 150 })
  827. }
  828. }
  829. }
  830. </script>
  831. <style scoped lang="scss">
  832. @import '@/styles/detail-pages.scss';
  833. /deep/.el-button {
  834. cursor: pointer;
  835. }
  836. @include hide-open-header;
  837. .bg-project {
  838. @include bg-project;
  839. }
  840. .main-header {
  841. @include main-header;
  842. .title-name {
  843. cursor: pointer;
  844. }
  845. }
  846. .main-header::after {
  847. @include main-header-after;
  848. }
  849. .contain {
  850. height: calc(100vh - 140px);
  851. overflow: scroll;
  852. }
  853. .main-section {
  854. @include main-section;
  855. line-height: 20px;
  856. .detail-info {
  857. padding: 0 34px 20px 34px;
  858. /deep/.el-input__inner{
  859. border: 1px solid rgba(220,223,230,0)
  860. }
  861. /deep/.el-input__inner:hover{
  862. border: 1px solid rgba(220,223,230,1)
  863. }
  864. /deep/.is-focus .el-input__inner {
  865. border: 1px solid #409EFF;
  866. }
  867. /deep/.el-select{
  868. .el-input__suffix-inner {
  869. visibility: hidden;
  870. }
  871. }
  872. /deep/.el-select:hover{
  873. .el-input__suffix-inner {
  874. visibility: visible;
  875. }
  876. }
  877. .demo-form-inline {
  878. .el-form-item {
  879. width: 20%;
  880. margin-right: 0;
  881. }
  882. }
  883. .comment-main {
  884. list-style: none;
  885. padding: 15px 0 0 0;
  886. margin: 0 0 20px 0;
  887. li {
  888. list-style: none;
  889. padding: 0px;
  890. margin: 0px 0px 25px 0;
  891. }
  892. .comment-name {
  893. font-size:14px;
  894. color:#333B4A;
  895. }
  896. .comment-gmtCreater {
  897. margin-left:20px;
  898. color: #9B9B9B;
  899. font-size:12px
  900. }
  901. .comment-content {
  902. font-size:14px;
  903. color:#333B4A;
  904. margin-top: 10px;
  905. white-space: pre-line;
  906. }
  907. }
  908. .PRD-link {
  909. width: 50%;
  910. overflow: hidden;
  911. text-overflow:ellipsis;
  912. white-space: nowrap;
  913. padding-left: 15px;
  914. }
  915. .PRD-link:hover{
  916. color:#409EFF;
  917. }
  918. }
  919. }
  920. >>>.module .el-form-item__content {
  921. display: inline-block;
  922. width: calc(100% - 100px);
  923. div {
  924. line-height: 18px;
  925. padding-top: 12px;
  926. cursor: pointer;
  927. }
  928. div:hover{
  929. color: #409EFF;
  930. }
  931. }
  932. >>>.el-input--small {
  933. font-size: 14px;
  934. }
  935. .vss {
  936. color: #409EFF !important;
  937. border:1px solid #409EFF !important;
  938. }
  939. .paddingLeft {
  940. padding-left: 0px;
  941. }
  942. .el-tabs-spacing {
  943. padding: 20px 30px;
  944. }
  945. .sign-tabs {
  946. padding: 0 30px;
  947. }
  948. .el-btn-size {
  949. margin: 10px 30px;
  950. }
  951. .border-top {
  952. padding: 0 20px 10px !important;
  953. >>>.el-divider--horizontal {
  954. display: block;
  955. height: 1px;
  956. width: 100%;
  957. margin: 10px 0;
  958. }
  959. }
  960. </style>