detail.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. <template>
  2. <div class="project-task-detail-window" :class="{'task-detail-show': visible}">
  3. <div class="task-detail-main">
  4. <div class="detail-left">
  5. <div class="detail-title-box detail-icon">
  6. <input v-model="detail.title" :disabled="!!loadData.title" type="text" maxlength="60" @keydown.enter="(e)=>{e.target.blur()}" @blur="handleTask('title')">
  7. <div class="time">
  8. <span class="z-nick">{{detail.createuser}}</span>
  9. 创建于:
  10. <span>{{$A.formatDate("Y-m-d H:i:s", detail.indate)}}</span>
  11. </div>
  12. </div>
  13. <div class="detail-desc-box detail-icon">
  14. <div class="detail-h2"><strong class="active">描述</strong></div>
  15. <textarea v-model="detail.desc" placeholder="添加详细描述..." @keydown="descKeydown" @blur="handleTask('desc')"></textarea>
  16. </div>
  17. <ul class="detail-text-box">
  18. <li v-if="detail.startdate > 0 && detail.enddate > 0" class="text-time detail-icon">
  19. 计划时间:
  20. <em>{{$A.formatDate("Y-m-d H:i", detail.startdate)}} 至 {{$A.formatDate("Y-m-d H:i", detail.enddate)}}</em>
  21. <em v-if="detail.overdue" class="overdue">[已超期]</em>
  22. </li>
  23. <li class="text-username detail-icon">
  24. 负责人:
  25. <em>{{detail.username}}</em>
  26. </li>
  27. <li class="text-level detail-icon">
  28. 优先级:
  29. <em :class="`p${detail.level}`">{{levelFormt(detail.level)}}</em>
  30. </li>
  31. <li class="text-status detail-icon">
  32. 任务状态:
  33. <em v-if="detail.complete" class="complete">已完成</em>
  34. <em v-else class="unfinished">未完成</em>
  35. </li>
  36. </ul>
  37. <div :style="`${detail.filenum>0?'':'display:none'}`">
  38. <div class="detail-h2 detail-file-box detail-icon"><strong class="active">附件</strong></div>
  39. <project-task-files ref="upload" :taskid="taskid" :simple="true" @change="handleTask('filechange', $event)"></project-task-files>
  40. </div>
  41. <div class="detail-h2 detail-comment-box detail-icon"><strong class="link" :class="{active:logType=='评论'}" @click="logType='评论'">评论</strong><em></em><strong class="link" :class="{active:logType=='日志'}" @click="logType='日志'">操作记录</strong></div>
  42. <div class="detail-log-box">
  43. <project-task-logs ref="log" :logtype="logType" :projectid="detail.projectid" :taskid="taskid" :pagesize="5"></project-task-logs>
  44. </div>
  45. <div class="detail-footer-box">
  46. <Input class="comment-input" v-model="commentText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="commentKeydown" placeholder="输入评论,Enter发表评论,Shift+Enter换行" />
  47. <Button :loading="!!loadData.comment" :disabled="!commentText" type="primary" @click="handleTask('comment')">评 论</Button>
  48. </div>
  49. </div>
  50. <div class="detail-right">
  51. <div class="cancel"><em @click="visible=false"></em></div>
  52. <Dropdown trigger="click" class="block" @on-click="handleTask">
  53. <Button :loading="!!loadData.unfinished || !!loadData.complete" icon="md-checkmark-circle-outline" class="btn">标记{{detail.complete?'未完成':'已完成'}}</Button>
  54. <DropdownMenu slot="list">
  55. <DropdownItem name="unfinished">标记未完成<Icon v-if="!detail.complete" type="md-checkmark" class="checkmark"/></DropdownItem>
  56. <DropdownItem name="complete">标记已完成<Icon v-if="detail.complete" type="md-checkmark" class="checkmark"/></DropdownItem>
  57. <DropdownItem name="archived2">完成并归档<Icon v-if="detail.complete && detail.archived" type="md-checkmark" class="checkmark"/></DropdownItem>
  58. </DropdownMenu>
  59. </Dropdown>
  60. <Dropdown trigger="click" class="block" @on-click="handleTask">
  61. <Button :loading="!!loadData.level" icon="md-funnel" class="btn">优先级</Button>
  62. <DropdownMenu slot="list">
  63. <DropdownItem v-for="level in [1,2,3,4]" :key="level" :name="`level-${level}`" :class="`p${level}`">{{levelFormt(level)}}<Icon v-if="detail.level==level" type="md-checkmark" class="checkmark"/></DropdownItem>
  64. </DropdownMenu>
  65. </Dropdown>
  66. <Poptip placement="bottom" class="block">
  67. <Button :loading="!!loadData.username" icon="md-person" class="btn">负责人</Button>
  68. <div slot="content">
  69. <div style="width:240px">
  70. 选择负责人
  71. <UseridInput :projectid="detail.projectid" @change="handleTask('username', $event)" placeholder="输入关键词搜索" style="margin:5px 0 3px"></UseridInput>
  72. </div>
  73. </div>
  74. </Poptip>
  75. <Poptip ref="timeRef" placement="bottom" class="block" @on-popper-show="handleTask('opentime')">
  76. <Button :loading="!!loadData.plannedtime || !!loadData.unplannedtime" icon="md-calendar" class="btn">计划时间</Button>
  77. <div slot="content">
  78. <div style="width:280px">
  79. 选择日期范围
  80. <Date-picker
  81. v-model="timeValue"
  82. :options="timeOptions"
  83. :placeholder="$L('日期范围')"
  84. format="yyyy-MM-dd HH:mm"
  85. type="datetimerange"
  86. placement="bottom"
  87. @on-ok="handleTask('plannedtime')"
  88. @on-clear="handleTask('unplannedtime')"
  89. style="display:block;margin:5px 0 3px"></Date-picker>
  90. </div>
  91. </div>
  92. </Poptip>
  93. <Button icon="md-attach" class="btn" @click="handleTask('fileupload')">添加附件</Button>
  94. <Button v-if="!detail.archived" :loading="!!loadData.archived" icon="md-filing" class="btn" @click="handleTask('archived')">归档</Button>
  95. <Button v-else :loading="!!loadData.unarchived" icon="md-filing" class="btn" @click="handleTask('unarchived')">取消归档</Button>
  96. <Button :loading="!!loadData.delete" icon="md-trash" class="btn" type="error" ghost @click="handleTask('deleteb')">删除</Button>
  97. </div>
  98. </div>
  99. </div>
  100. </template>
  101. <script>
  102. import ProjectTaskLogs from "../logs";
  103. import ProjectTaskFiles from "../files";
  104. export default {
  105. components: {ProjectTaskFiles, ProjectTaskLogs},
  106. data() {
  107. return {
  108. taskid: 0,
  109. detail: {},
  110. callback: null,
  111. visible: false,
  112. bakData: {},
  113. loadData: {},
  114. commentText: '',
  115. logType: '评论',
  116. timeValue: [],
  117. timeOptions: {
  118. shortcuts: [{
  119. text: '今天',
  120. value() {
  121. return [new Date(), new Date()];
  122. }
  123. }, {
  124. text: '明天',
  125. value() {
  126. let e = new Date();
  127. e.setDate(e.getDate() + 1);
  128. return [new Date(), e];
  129. }
  130. }, {
  131. text: '本周',
  132. value() {
  133. return [$A.getData('今天', true), $A.getData('本周结束', true)];
  134. }
  135. }, {
  136. text: '本月',
  137. value() {
  138. return [$A.getData('今天', true), $A.getData('本月结束', true)];
  139. }
  140. }, {
  141. text: '3天',
  142. value() {
  143. let e = new Date();
  144. e.setDate(e.getDate() + 3);
  145. return [new Date(), e];
  146. }
  147. }, {
  148. text: '5天',
  149. value() {
  150. let e = new Date();
  151. e.setDate(e.getDate() + 5);
  152. return [new Date(), e];
  153. }
  154. }, {
  155. text: '7天',
  156. value() {
  157. let e = new Date();
  158. e.setDate(e.getDate() + 7);
  159. return [new Date(), e];
  160. }
  161. }]
  162. },
  163. }
  164. },
  165. beforeCreate() {
  166. let doms = document.querySelectorAll('.project-task-detail-window');
  167. for (let i = 0; i < doms.length; ++i) {
  168. if (doms[i].parentNode != null) doms[i].parentNode.removeChild(doms[i]);
  169. }
  170. },
  171. mounted() {
  172. this.$nextTick(() => {
  173. let dom = this.$el;
  174. if (parseInt(this.taskid) === 0) {
  175. if (dom.parentNode != null) dom.parentNode.removeChild(dom);
  176. return;
  177. }
  178. //
  179. dom.addEventListener('transitionend', () => {
  180. if (dom !== null && dom.parentNode !== null && !this.visible) {
  181. dom.parentNode.removeChild(dom);
  182. }
  183. }, false);
  184. //
  185. setTimeout(() => {
  186. this.visible = true;
  187. }, 0)
  188. });
  189. this.bakData = $A.cloneData(this.detail);
  190. this.getTaskDetail();
  191. },
  192. watch: {
  193. taskid() {
  194. this.bakData = $A.cloneData(this.detail);
  195. this.getTaskDetail();
  196. }
  197. },
  198. methods: {
  199. levelFormt(p) {
  200. switch (parseInt(p)) {
  201. case 1:
  202. return "重要且紧急 (P1)";
  203. case 2:
  204. return "重要不紧急 (P2)";
  205. case 3:
  206. return "紧急不重要 (P3)";
  207. case 4:
  208. return "不重要不紧急 (P4)";
  209. }
  210. },
  211. descKeydown(e) {
  212. e = e || event;
  213. if (e.keyCode == 13) {
  214. if (e.shiftKey) {
  215. return;
  216. }
  217. e.preventDefault();
  218. e.target.blur();
  219. }
  220. },
  221. commentKeydown(e) {
  222. e = e || event;
  223. if (e.keyCode == 13) {
  224. if (e.shiftKey) {
  225. return;
  226. }
  227. e.preventDefault();
  228. this.handleTask('comment');
  229. }
  230. },
  231. getTaskDetail() {
  232. $A.aAjax({
  233. url: 'project/task/lists',
  234. data: {
  235. taskid: this.taskid
  236. },
  237. error: () => {
  238. alert(this.$L('网络繁忙,请稍后再试!'));
  239. this.visible = false;
  240. },
  241. success: (res) => {
  242. if (res.ret === 1) {
  243. this.detail = res.data;
  244. this.bakData = $A.cloneData(this.detail);
  245. } else {
  246. this.$Modal.error({
  247. title: this.$L('温馨提示'),
  248. content: res.msg,
  249. onOk: () => {
  250. this.visible = false;
  251. }
  252. });
  253. }
  254. }
  255. });
  256. },
  257. handleTask(act, eve) {
  258. if (!!this.loadData[act]) {
  259. this.$Message.info(this.$L('请稍候...'));
  260. return;
  261. }
  262. //
  263. let ajaxData = {
  264. act: act,
  265. taskid: this.taskid,
  266. };
  267. let ajaxCallback = () => {};
  268. //
  269. switch (act) {
  270. case 'title':
  271. case 'desc':
  272. if (this.detail[act] == this.bakData[act]) {
  273. return;
  274. }
  275. if (!this.detail[act]) {
  276. this.$set(this.detail, act, this.bakData[act]);
  277. return;
  278. }
  279. ajaxData.content = this.detail[act];
  280. ajaxCallback = (res) => {
  281. if (res !== 1) {
  282. this.$set(this.detail, act, this.bakData[act]);
  283. }
  284. };
  285. break;
  286. case 'fileupload':
  287. this.$refs.upload.uploadHandleClick();
  288. return;
  289. case 'filechange':
  290. let filenum = $A.runNum(this.detail.filenum);
  291. switch (eve) {
  292. case 'up':
  293. this.$set(this.detail, 'filenum', filenum + 1);
  294. break;
  295. case 'error':
  296. case 'delete':
  297. this.$set(this.detail, 'filenum', filenum - 1);
  298. break;
  299. }
  300. if (eve == 'add' || eve == 'delete') {
  301. this.logType == '日志' && this.$refs.log.getLists(true, true);
  302. }
  303. return;
  304. case 'complete':
  305. case 'unfinished':
  306. case 'archived':
  307. case 'unarchived':
  308. break;
  309. case 'archived2':
  310. ajaxData.act = 'complete';
  311. ajaxCallback = (res) => {
  312. if (res === 1 && !this.detail.archived) {
  313. this.handleTask('archived');
  314. return false;
  315. }
  316. };
  317. break;
  318. case 'level-1':
  319. case 'level-2':
  320. case 'level-3':
  321. case 'level-4':
  322. ajaxData.act = 'level';
  323. ajaxData.content = act.substring(6);
  324. break;
  325. case 'username':
  326. if (!eve.username) {
  327. return;
  328. }
  329. ajaxData.content = eve.username;
  330. ajaxCallback = (res) => {
  331. if (res === 1) {
  332. this.$Modal.info({
  333. title: '温馨提示',
  334. content: '任务负责人已改变,点击确定关闭窗口。',
  335. onOk: () => {
  336. this.visible = false;
  337. }
  338. });
  339. }
  340. };
  341. break;
  342. case 'opentime':
  343. if (this.detail.startdate > 0 && this.detail.enddate > 0) {
  344. this.timeValue = [$A.formatDate("Y-m-d H:i", this.detail.startdate), $A.formatDate("Y-m-d H:i", this.detail.enddate)]
  345. } else {
  346. this.timeValue = [];
  347. }
  348. return;
  349. case 'plannedtime':
  350. this.timeValue = $A.date2string(this.timeValue);
  351. ajaxData.content = this.timeValue[0] + "," + this.timeValue[1];
  352. this.$refs.timeRef.handleClose();
  353. break;
  354. case 'unplannedtime':
  355. this.$refs.timeRef.handleClose();
  356. break;
  357. case 'deleteb':
  358. this.$Modal.confirm({
  359. title: this.$L('删除提示'),
  360. content: this.$L('您确定要删除此任务吗?'),
  361. onOk: () => {
  362. this.handleTask('delete');
  363. },
  364. });
  365. return;
  366. case 'delete':
  367. ajaxCallback = (res) => {
  368. if (res === 1) {
  369. this.$Modal.info({
  370. title: '温馨提示',
  371. content: '任务已删除,点击确定关闭窗口。',
  372. onOk: () => {
  373. this.visible = false;
  374. }
  375. });
  376. }
  377. };
  378. break;
  379. case 'comment':
  380. if (!this.commentText) {
  381. return;
  382. }
  383. ajaxData.content = this.commentText;
  384. ajaxCallback = (res) => {
  385. if (res === 1) {
  386. this.commentText = "";
  387. this.logType == '评论' && this.$refs.log.getLists(true, true);
  388. }
  389. };
  390. break;
  391. default: {
  392. return;
  393. }
  394. }
  395. //
  396. this.$set(this.loadData, ajaxData.act, true);
  397. $A.aAjax({
  398. url: 'project/task/edit',
  399. data: ajaxData,
  400. complete: () => {
  401. this.$set(this.loadData, ajaxData.act, false);
  402. },
  403. error: () => {
  404. ajaxCallback(-1);
  405. alert(this.$L('网络繁忙,请稍后再试!'));
  406. },
  407. success: (res) => {
  408. if (res.ret === 1) {
  409. this.detail = res.data;
  410. this.bakData = $A.cloneData(this.detail);
  411. if (ajaxCallback(1) !== false) {
  412. this.logType == '日志' && this.$refs.log.getLists(true, true);
  413. this.$Message.success(res.msg);
  414. }
  415. if (typeof this.callback === "function") {
  416. this.callback(ajaxData.act, res.data, ajaxData);
  417. }
  418. } else {
  419. ajaxCallback(0);
  420. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  421. }
  422. }
  423. });
  424. }
  425. }
  426. }
  427. </script>
  428. <style lang="scss" scoped>
  429. .project-task-detail-window {
  430. position: fixed;
  431. z-index: 1001;
  432. top: 0;
  433. left: 0;
  434. height: 100%;
  435. width: 100%;
  436. background-color: rgba(0, 0, 0, 0.5);
  437. transition: all .3s;
  438. opacity: 0;
  439. pointer-events: unset;
  440. display: flex;
  441. flex-direction: column;
  442. align-items: center;
  443. justify-content: center;
  444. &.task-detail-show {
  445. opacity: 1;
  446. }
  447. .task-detail-main {
  448. display: flex;
  449. flex-direction: row;
  450. width: 92%;
  451. max-width: 800px;
  452. max-height: 92%;
  453. background: #ffffff;
  454. overflow: visible;
  455. border-radius: 4px;
  456. padding: 10px 20px 2px;
  457. transform: translateZ(0);
  458. .detail-left {
  459. flex: 1;
  460. padding-left: 8px;
  461. padding-right: 20px;
  462. overflow: auto;
  463. .detail-h2 {
  464. color: #172b4d;
  465. font-size: 16px;
  466. display: flex;
  467. align-items: center;
  468. line-height: 26px;
  469. strong {
  470. font-size: 14px;
  471. font-weight: normal;
  472. &.link {
  473. cursor: pointer;
  474. }
  475. &.active {
  476. font-size: 16px;
  477. font-weight: bold;
  478. }
  479. }
  480. em {
  481. margin: 0 9px;
  482. width: 1px;
  483. height: 10px;
  484. background: #cccccc;
  485. }
  486. }
  487. .detail-icon {
  488. position: relative;
  489. padding-left: 26px;
  490. &:before {
  491. font-family: zenicon;
  492. font-size: 20px;
  493. color: #42526e;
  494. font-weight: 600;
  495. position: absolute;
  496. top: 0;
  497. left: 0;
  498. width: 26px;
  499. height: 26px;
  500. line-height: 26px;
  501. }
  502. }
  503. .detail-title-box {
  504. margin-top: 12px;
  505. margin-bottom: 12px;
  506. &:before {
  507. content: "\E740";
  508. }
  509. .time {
  510. font-size: 12px;
  511. color: #606266;
  512. }
  513. input {
  514. margin: -10px 0 0 -8px;
  515. font-size: 20px;
  516. font-weight: 600;
  517. border: 2px solid #ffffff;
  518. padding: 5px 8px;
  519. cursor: pointer;
  520. color: #172b4d;
  521. background: #ffffff;
  522. width: 100%;
  523. border-radius: 3px;
  524. }
  525. input:focus {
  526. outline: 0;
  527. background: #fff;
  528. border-color: #0396f2;
  529. }
  530. }
  531. .detail-desc-box {
  532. &:before {
  533. content: "\E75E";
  534. }
  535. textarea {
  536. border: 2px solid #F4F5F7;
  537. padding: 5px 8px;
  538. cursor: pointer;
  539. color: #172b4d;
  540. background: rgba(9, 30, 66, 0.04);
  541. width: 100%;
  542. border-radius: 3px;
  543. resize: none;
  544. margin-top: 10px;
  545. &:focus {
  546. outline: 0;
  547. background: #fff;
  548. border-color: #0396f2;
  549. }
  550. }
  551. }
  552. .detail-text-box {
  553. margin-bottom: 12px;
  554. li {
  555. color: #606266;
  556. font-size: 14px;
  557. line-height: 32px;
  558. word-break: break-all;
  559. &:before {
  560. font-weight: normal;
  561. color: #606266;
  562. font-size: 14px;
  563. padding-left: 4px;
  564. line-height: 32px;
  565. }
  566. &.text-time {
  567. &:before {
  568. content: "\E706";
  569. }
  570. }
  571. &.text-username {
  572. &:before {
  573. content: "\E903";
  574. }
  575. }
  576. &.text-level {
  577. &:before {
  578. content: "\E725";
  579. }
  580. }
  581. &.text-status {
  582. &:before {
  583. content: "\E6AF";
  584. }
  585. }
  586. em {
  587. margin-left: 4px;
  588. &.p1 {
  589. color: #ed3f14;
  590. }
  591. &.p2 {
  592. color: #ff9900;
  593. }
  594. &.p3 {
  595. color: #19be6b;
  596. }
  597. &.p4 {
  598. color: #666666;
  599. }
  600. &.complete {
  601. color: #666666;
  602. }
  603. &.overdue {
  604. color: #ff0000;
  605. }
  606. &.unfinished {
  607. color: #19be6b;
  608. }
  609. }
  610. }
  611. }
  612. .detail-file-box {
  613. &:before {
  614. content: "\E8B9";
  615. font-size: 16px;
  616. padding-left: 2px;
  617. }
  618. }
  619. .detail-comment-box {
  620. &:before {
  621. content: "\E753";
  622. }
  623. }
  624. .detail-footer-box {
  625. border-top: 1px solid #e5e5e5;
  626. display: flex;
  627. flex-direction: row;
  628. padding-top: 20px;
  629. padding-bottom: 16px;
  630. .comment-input {
  631. margin-right: 12px;
  632. }
  633. }
  634. }
  635. .detail-right {
  636. .cancel {
  637. text-align: right;
  638. width: auto;
  639. height: 38px;
  640. em {
  641. display: inline-block;
  642. width: 38px;
  643. height: 38px;
  644. cursor: pointer;
  645. border-radius: 50%;
  646. transform: scale(0.92);
  647. &:after,
  648. &:before {
  649. position: absolute;
  650. content: "";
  651. top: 50%;
  652. left: 50%;
  653. width: 2px;
  654. height: 20px;
  655. background-color: #EE2321;
  656. transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
  657. transition: all .2s;
  658. }
  659. &:before {
  660. position: absolute;
  661. transform: translate(-50%, -50%) rotate(-45deg) scale(0.6, 1);
  662. }
  663. &:hover {
  664. &:after,
  665. &:before {
  666. background-color: #ff0000;
  667. transform: translate(-50%, -50%) rotate(135deg) scale(0.6, 1);
  668. }
  669. &:before {
  670. background-color: #ff0000;
  671. transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
  672. }
  673. }
  674. }
  675. }
  676. .block {
  677. display: block;
  678. .p1 {
  679. color: #ed3f14;
  680. }
  681. .p2 {
  682. color: #ff9900;
  683. }
  684. .p3 {
  685. color: #19be6b;
  686. }
  687. .p4 {
  688. color: #666666;
  689. }
  690. .checkmark {
  691. margin-left: 8px;
  692. margin-right: -8px;
  693. }
  694. }
  695. .btn {
  696. display: block;
  697. width: 118px;
  698. text-align: left;
  699. margin-top: 8px;
  700. padding-left: 10px;
  701. padding-right: 10px;
  702. }
  703. }
  704. }
  705. }
  706. </style>