detail.vue 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. <template>
  2. <div class="project-task-detail-window" :class="{'task-detail-show': visible}" @click="$nextTick(()=>{visible=false})">
  3. <div class="task-detail-main" @click.stop="">
  4. <div class="detail-left">
  5. <div class="detail-title-box detail-icon">
  6. <Input v-model="detail.title"
  7. :disabled="!!loadData.title"
  8. type="textarea"
  9. class="detail-title-input"
  10. ref="titleInput"
  11. :rows="1"
  12. :autosize="{minRows:1,maxRows:5}"
  13. maxlength="60"
  14. @on-keydown="titleKeydown"
  15. @on-blur="handleTask('title')"/>
  16. <div v-if="detail.projectTitle && urlProjectid != detail.projectid" class="subtitle">
  17. {{$L('所属项目:')}}
  18. <span class="project-title" @click="openProject(detail.projectid)">{{detail.projectTitle}}</span>
  19. </div>
  20. <div class="subtitle">
  21. <span class="z-nick"><UserView :username="detail.createuser"/></span>
  22. {{$L('创建于:')}}
  23. <span>{{$A.formatDate("Y-m-d H:i:s", detail.indate)}}</span>
  24. </div>
  25. </div>
  26. <div class="detail-desc-box detail-icon">
  27. <div class="detail-h2"><strong class="active">{{$L('描述')}}</strong></div>
  28. <Input v-model="detail.desc"
  29. type="textarea"
  30. class="detail-desc-input"
  31. ref="descInput"
  32. :rows="2"
  33. :autosize="{minRows:2,maxRows:8}"
  34. maxlength="500"
  35. :placeholder="$L('添加详细描述...')"
  36. @on-keydown="descKeydown"
  37. @on-blur="handleTask('desc')"/>
  38. </div>
  39. <ul class="detail-text-box">
  40. <li v-if="detail.startdate > 0 && detail.enddate > 0" class="text-time detail-icon">
  41. <span>{{$L('计划时间:')}}</span>
  42. <em>{{$A.formatDate("Y-m-d H:i", detail.startdate)}} {{$L('至')}} {{$A.formatDate("Y-m-d H:i", detail.enddate)}}</em>
  43. <em v-if="detail.overdue" class="overdue">[{{$L('已超期')}}]</em>
  44. </li>
  45. <li class="text-username detail-icon">
  46. <span>{{$L('负责人:')}}</span>
  47. <em><UserView :username="detail.username"/></em>
  48. </li>
  49. <li v-if="followerLength(detail.follower) > 0" class="text-follower detail-icon">
  50. <span>{{$L('关注者:')}}</span>
  51. <em>
  52. <Tag v-for="(fname, findex) in detail.follower" :key="findex" closable @on-close="handleTask('unattention', {username:fname,uisynch:true})"><UserView :username="fname"/></Tag>
  53. </em>
  54. </li>
  55. <li class="text-level detail-icon">
  56. <span>{{$L('优先级:')}}</span>
  57. <em :class="`p${detail.level}`">{{levelFormt(detail.level)}}</em>
  58. </li>
  59. <li class="text-status detail-icon">
  60. <span>{{$L('任务状态:')}}</span>
  61. <em v-if="detail.complete" class="complete">{{$L('已完成')}}<span class="completedate">({{$A.formatDate("Y-m-d H:i", detail.completedate)}})</span></em>
  62. <em v-else class="unfinished">{{$L('未完成')}}</em>
  63. </li>
  64. </ul>
  65. <div class="detail-h2 detail-subtask-icon detail-icon">
  66. <strong class="active">{{$L('子任务')}}</strong>
  67. <Button class="detail-button" size="small" @click="handleTask('subtaskAdd')">{{$L('添加子任务')}}</Button>
  68. </div>
  69. <div class="detail-subtask-box">
  70. <div v-if="detail.subtask.length == 0" class="detail-subtask-none" @click="handleTask('subtaskAdd')">{{$L('暂无子任务')}}</div>
  71. <div v-else>
  72. <Progress class="detail-subtask-progress" :percent="subtaskProgress" :stroke-width="5" status="active" />
  73. <div v-for="(subitem, subindex) in detail.subtask" :key="subindex" class="detail-subtask-item">
  74. <Checkbox v-model="subitem.status"
  75. true-value="complete"
  76. false-value="unfinished"
  77. @on-change="handleTask('subtaskBlur')"></Checkbox>
  78. <Input v-model="subitem.detail"
  79. type="textarea"
  80. class="detail-subtask-input"
  81. :ref="`subtaskInput_${subindex}`"
  82. :class="{'subtask-complete':subitem.status=='complete'}"
  83. :rows="1"
  84. :autosize="{minRows:1,maxRows:5}"
  85. maxlength="255"
  86. :placeholder="$L('子任务描述...')"
  87. @on-keydown="subtaskKeydown(subindex, $event)"
  88. @on-blur="handleTask('subtaskBlur')"/>
  89. <div v-if="subitem.detail==''" class="detail-subtask-delete">
  90. <Icon type="md-trash" @click="handleTask('subtaskDelete', subindex)"/>
  91. </div>
  92. <Poptip
  93. v-else
  94. class="detail-subtask-delete"
  95. transfer
  96. confirm
  97. :title="$L('你确定你要删除这个子任务吗?')"
  98. :style="subitem.showPoptip===true?{opacity:1}:{}"
  99. @on-ok="handleTask('subtaskDelete', subindex)"
  100. @on-popper-show="$set(subitem, 'showPoptip', true)"
  101. @on-popper-hide="$set(subitem, 'showPoptip', false)"><Icon type="md-trash" /></Poptip>
  102. </div>
  103. </div>
  104. </div>
  105. <div :style="`${detail.filenum>0?'':'display:none'}`">
  106. <div class="detail-h2 detail-file-box detail-icon">
  107. <strong class="active">{{$L('附件')}}</strong>
  108. <Button class="detail-button" size="small" @click="handleTask('fileupload')">{{$L('添加附件')}}</Button>
  109. </div>
  110. <project-task-files ref="upload" :taskid="taskid" :projectid="detail.projectid" :simple="true" @change="handleTask('filechange', $event)"></project-task-files>
  111. </div>
  112. <div class="detail-h2 detail-comment-box detail-icon"><strong class="link" :class="{active:logType=='评论'}" @click="logType='评论'">{{$L('评论')}}</strong><em></em><strong class="link" :class="{active:logType=='日志'}" @click="logType='日志'">{{$L('操作记录')}}</strong></div>
  113. <div class="detail-log-box">
  114. <project-task-logs ref="log" :logtype="logType" :projectid="detail.projectid" :taskid="taskid" :pagesize="5"></project-task-logs>
  115. </div>
  116. <div class="detail-footer-box">
  117. <Input class="comment-input" v-model="commentText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="commentKeydown" :placeholder="$L('输入评论,Enter发表评论,Shift+Enter换行')" />
  118. <Button :loading="!!loadData.comment" :disabled="!commentText" type="primary" @click="handleTask('comment')">评 论</Button>
  119. </div>
  120. </div>
  121. <div class="detail-right">
  122. <div class="cancel"><em @click="visible=false"></em></div>
  123. <Dropdown trigger="click" class="block" @on-click="handleTask">
  124. <Button :loading="!!loadData.unfinished || !!loadData.complete" :icon="detail.complete?'md-checkmark-circle-outline':'md-radio-button-off'" class="btn">{{$L('标记')}}{{$L(detail.complete?'未完成':'已完成')}}</Button>
  125. <DropdownMenu slot="list">
  126. <DropdownItem name="unfinished">{{$L('标记未完成')}}<Icon v-if="!detail.complete" type="md-checkmark" class="checkmark"/></DropdownItem>
  127. <DropdownItem name="complete">{{$L('标记已完成')}}<Icon v-if="detail.complete" type="md-checkmark" class="checkmark"/></DropdownItem>
  128. <DropdownItem name="archived2">{{$L('完成并归档')}}<Icon v-if="detail.complete && detail.archived" type="md-checkmark" class="checkmark"/></DropdownItem>
  129. </DropdownMenu>
  130. </Dropdown>
  131. <Dropdown trigger="click" class="block" @on-click="handleTask">
  132. <Button :loading="!!loadData.level" icon="md-funnel" class="btn">{{$L('优先级')}}</Button>
  133. <DropdownMenu slot="list">
  134. <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>
  135. </DropdownMenu>
  136. </Dropdown>
  137. <Poptip placement="bottom" class="block">
  138. <Button :loading="!!loadData.username" icon="md-person" class="btn">{{$L('负责人')}}</Button>
  139. <div slot="content">
  140. <div style="width:280px">
  141. {{$L('选择负责人')}}
  142. <UserInput :projectid="detail.projectid" :nousername="detail.username" :transfer="false" @change="handleTask('usernameb', $event)" :placeholder="$L('输入关键词搜索')" style="margin:5px 0 3px"></UserInput>
  143. </div>
  144. </div>
  145. </Poptip>
  146. <Poptip ref="timeRef" placement="bottom" class="block" @on-popper-show="handleTask('inittime')">
  147. <Button :loading="!!loadData.plannedtime || !!loadData.unplannedtime" icon="md-calendar" class="btn">{{$L('计划时间')}}</Button>
  148. <div slot="content">
  149. <div style="width:280px">
  150. {{$L('选择日期范围')}}
  151. <Date-picker
  152. v-model="timeValue"
  153. :options="timeOptions"
  154. :placeholder="$L('日期范围')"
  155. format="yyyy-MM-dd HH:mm"
  156. type="datetimerange"
  157. placement="bottom"
  158. @on-ok="handleTask('plannedtimeb')"
  159. @on-clear="handleTask('unplannedtimeb')"
  160. style="display:block;margin:5px 0 3px"></Date-picker>
  161. </div>
  162. </div>
  163. </Poptip>
  164. <Button icon="md-attach" class="btn" @click="handleTask('fileupload')">{{$L('添加附件')}}</Button>
  165. <Poptip ref="attentionRef" v-if="detail.username == myUsername" placement="bottom" class="block" @on-popper-show="() => {$set(detail, 'attentionLists', followerToStr(detail.follower))}">
  166. <Button :loading="!!loadData.attention" icon="md-at" class="btn">{{$L('关注人')}}</Button>
  167. <div slot="content">
  168. <div style="width:280px">
  169. {{$L('选择关注人')}}
  170. <UserInput :projectid="detail.projectid" :multiple="true" :transfer="false" v-model="detail.attentionLists" :placeholder="$L('输入关键词搜索')" style="margin:5px 0 3px"></UserInput>
  171. <Button :loading="!!loadData.attention" :disabled="!detail.attentionLists" class="btn" type="primary" style="text-align:center;width:72px;height:28px;font-size:13px" @click="handleTask('attention')">确 定</Button>
  172. </div>
  173. </div>
  174. </Poptip>
  175. <Button v-else-if="haveAttention(detail.follower)" :loading="!!loadData.unattention" icon="md-at" class="btn" @click="handleTask('unattention', {username:myUsername})">{{$L('取消关注')}}</Button>
  176. <Button v-else :loading="!!loadData.attention" icon="md-at" class="btn" @click="handleTask('attentiona')">{{$L('关注任务')}}</Button>
  177. <Button v-if="!detail.archived" :loading="!!loadData.archived" icon="md-filing" class="btn" @click="handleTask('archived')">{{$L('归档')}}</Button>
  178. <Button v-else :loading="!!loadData.unarchived" icon="md-filing" class="btn" @click="handleTask('unarchived')">{{$L('取消归档')}}</Button>
  179. <Button :loading="!!loadData.delete" icon="md-trash" class="btn" type="error" ghost @click="handleTask('deleteb')">{{$L('删除')}}</Button>
  180. </div>
  181. </div>
  182. </div>
  183. </template>
  184. <script>
  185. import ProjectTaskLogs from "../logs";
  186. import ProjectTaskFiles from "../files";
  187. import cloneDeep from "lodash/cloneDeep";
  188. export default {
  189. components: {ProjectTaskFiles, ProjectTaskLogs},
  190. data() {
  191. return {
  192. taskid: 0,
  193. detail: {},
  194. visible: false,
  195. urlProjectid: 0,
  196. bakData: {},
  197. loadData: {},
  198. commentText: '',
  199. logType: '评论',
  200. timeValue: [],
  201. timeOptions: {},
  202. myUsername: '',
  203. }
  204. },
  205. beforeCreate() {
  206. let doms = document.querySelectorAll('.project-task-detail-window');
  207. for (let i = 0; i < doms.length; ++i) {
  208. if (doms[i].parentNode != null) doms[i].parentNode.removeChild(doms[i]);
  209. }
  210. },
  211. created() {
  212. let lastSecond = (e) => {
  213. return new Date($A.formatDate("Y-m-d 23:59:29", Math.round(e / 1000)))
  214. };
  215. this.timeOptions = {
  216. shortcuts: [{
  217. text: this.$L('今天'),
  218. value() {
  219. return [new Date(), lastSecond(new Date().getTime())];
  220. }
  221. }, {
  222. text: this.$L('明天'),
  223. value() {
  224. let e = new Date();
  225. e.setDate(e.getDate() + 1);
  226. return [new Date(), lastSecond(e.getTime())];
  227. }
  228. }, {
  229. text: this.$L('本周'),
  230. value() {
  231. return [$A.getData('今天', true), lastSecond($A.getData('本周结束2', true))];
  232. }
  233. }, {
  234. text: this.$L('本月'),
  235. value() {
  236. return [$A.getData('今天', true), lastSecond($A.getData('本月结束', true))];
  237. }
  238. }, {
  239. text: this.$L('3天'),
  240. value() {
  241. let e = new Date();
  242. e.setDate(e.getDate() + 3);
  243. return [new Date(), lastSecond(e.getTime())];
  244. }
  245. }, {
  246. text: this.$L('5天'),
  247. value() {
  248. let e = new Date();
  249. e.setDate(e.getDate() + 5);
  250. return [new Date(), lastSecond(e.getTime())];
  251. }
  252. }, {
  253. text: this.$L('7天'),
  254. value() {
  255. let e = new Date();
  256. e.setDate(e.getDate() + 7);
  257. return [new Date(), lastSecond(e.getTime())];
  258. }
  259. }]
  260. };
  261. },
  262. mounted() {
  263. let match = (window.location.pathname + "").match(/\/project\/panel\/(\d+)$/i);
  264. this.urlProjectid = match ? match[1] : 0;
  265. //
  266. this.$nextTick(() => {
  267. let dom = this.$el;
  268. if (parseInt(this.taskid) === 0) {
  269. if (dom.parentNode != null) dom.parentNode.removeChild(dom);
  270. return;
  271. }
  272. //
  273. dom.addEventListener('transitionend', () => {
  274. if (dom !== null && dom.parentNode !== null && !this.visible) {
  275. dom.parentNode.removeChild(dom);
  276. }
  277. }, false);
  278. //
  279. setTimeout(() => {
  280. this.visible = true;
  281. }, 0)
  282. });
  283. this.bakData = cloneDeep(this.detail);
  284. this.myUsername = $A.getUserName();
  285. this.getTaskDetail();
  286. //
  287. $A.setOnUserInfoListener("components/project/task/detail", () => {
  288. this.myUsername = $A.getUserName();
  289. });
  290. $A.setOnTaskInfoListener('components/project/task/detail',(act, detail) => {
  291. if (detail.id != this.taskid) {
  292. return;
  293. }
  294. if (detail.__modifyUsername == this.myUsername) {
  295. return;
  296. }
  297. this.getTaskDetail();
  298. }, true);
  299. },
  300. watch: {
  301. taskid() {
  302. this.bakData = cloneDeep(this.detail);
  303. this.getTaskDetail();
  304. }
  305. },
  306. computed: {
  307. subtaskProgress() {
  308. const countLists = this.detail.subtask;
  309. if (countLists.length === 0) {
  310. return 0;
  311. }
  312. const completeLists = countLists.filter((item) => { return item.status == 'complete'});
  313. return parseFloat(((completeLists.length / countLists.length) * 100).toFixed(2));
  314. }
  315. },
  316. methods: {
  317. levelFormt(p) {
  318. switch (parseInt(p)) {
  319. case 1:
  320. return this.$L("重要且紧急") + " (P1)";
  321. case 2:
  322. return this.$L("重要不紧急") + " (P2)";
  323. case 3:
  324. return this.$L("紧急不重要") + " (P3)";
  325. case 4:
  326. return this.$L("不重要不紧急") + " (P4)";
  327. }
  328. },
  329. titleKeydown(e) {
  330. e = e || event;
  331. if (e.keyCode == 13) {
  332. e.preventDefault();
  333. e.target.blur();
  334. }
  335. },
  336. descKeydown(e) {
  337. e = e || event;
  338. if (e.keyCode == 13) {
  339. if (e.shiftKey) {
  340. return;
  341. }
  342. e.preventDefault();
  343. e.target.blur();
  344. }
  345. },
  346. commentKeydown(e) {
  347. e = e || event;
  348. if (e.keyCode == 13) {
  349. if (e.shiftKey) {
  350. return;
  351. }
  352. e.preventDefault();
  353. this.handleTask('comment');
  354. }
  355. },
  356. subtaskKeydown(subindex, e) {
  357. e = e || event;
  358. if (e.keyCode == 13) {
  359. if (e.shiftKey) {
  360. return;
  361. }
  362. e.preventDefault();
  363. this.handleTask('subtaskEnter', subindex);
  364. }
  365. },
  366. followerLength(follower) {
  367. if (follower instanceof Array) {
  368. return follower.length;
  369. } else {
  370. return 0;
  371. }
  372. },
  373. followerToStr(follower) {
  374. if (follower instanceof Array) {
  375. return follower.join(",");
  376. } else {
  377. return '';
  378. }
  379. },
  380. haveAttention(follower) {
  381. if (follower instanceof Array) {
  382. return follower.filter((uname) => { return uname == this.myUsername }).length > 0
  383. } else {
  384. return 0;
  385. }
  386. },
  387. getTaskDetail() {
  388. $A.apiAjax({
  389. url: 'project/task/lists',
  390. data: {
  391. taskid: this.taskid,
  392. archived: '全部'
  393. },
  394. error: () => {
  395. alert(this.$L('网络繁忙,请稍后再试!'));
  396. this.visible = false;
  397. },
  398. success: (res) => {
  399. if (res.ret === 1) {
  400. this.detail = res.data;
  401. this.bakData = cloneDeep(this.detail);
  402. this.$nextTick(() => {
  403. this.$refs.titleInput.resizeTextarea();
  404. this.$refs.descInput.resizeTextarea();
  405. this.detail.subtask.forEach((temp, index) => {
  406. this.$refs['subtaskInput_' + (index)][0].resizeTextarea();
  407. })
  408. });
  409. } else {
  410. this.$Modal.error({
  411. title: this.$L('温馨提示'),
  412. content: res.msg,
  413. onOk: () => {
  414. this.visible = false;
  415. }
  416. });
  417. }
  418. }
  419. });
  420. },
  421. handleTask(act, eve) {
  422. if (!!this.loadData[act]) {
  423. this.$Message.info(this.$L('请稍候...'));
  424. return;
  425. }
  426. //
  427. let ajaxData = {
  428. act: act,
  429. taskid: this.taskid,
  430. };
  431. let ajaxCallback = () => {};
  432. //
  433. switch (act) {
  434. case 'title':
  435. case 'desc':
  436. if (this.detail[act] == this.bakData[act]) {
  437. return;
  438. }
  439. if (act == 'title' && !this.detail[act]) {
  440. this.$set(this.detail, act, this.bakData[act]);
  441. return;
  442. }
  443. ajaxData.content = this.detail[act];
  444. ajaxCallback = (res) => {
  445. if (res !== 1) {
  446. this.$set(this.detail, act, this.bakData[act]);
  447. }
  448. };
  449. break;
  450. case 'subtaskAdd':
  451. if (!$A.isArray(this.detail.subtask)) {
  452. this.detail.subtask = [];
  453. }
  454. this.detail.subtask.push({status: 'unfinished', detail: '' });
  455. this.$nextTick(() => {
  456. this.$refs['subtaskInput_' + (this.detail.subtask.length - 1)][0].focus();
  457. });
  458. return;
  459. case 'subtaskDelete':
  460. this.detail.subtask.splice(eve, 1);
  461. this.handleTask('subtaskBlur');
  462. return;
  463. case 'subtaskEnter':
  464. if (!$A.isArray(this.detail.subtask)) {
  465. this.detail.subtask = [];
  466. }
  467. if (eve + 1 >= this.detail.subtask.length) {
  468. this.handleTask('subtaskAdd');
  469. return;
  470. }
  471. this.$refs['subtaskInput_' + (eve + 1)][0].focus();
  472. return;
  473. case 'subtaskBlur':
  474. this.handleTask('subtask');
  475. return;
  476. case 'subtask':
  477. let tempArray = cloneDeep(this.detail[act]);
  478. while (tempArray.length > 0 && tempArray[tempArray.length - 1].detail == '') {
  479. tempArray.splice(tempArray.length - 1, 1);
  480. }
  481. if ($A.jsonStringify(tempArray) === $A.jsonStringify(this.bakData[act])) {
  482. return;
  483. }
  484. ajaxData.content = tempArray;
  485. ajaxCallback = (res) => {
  486. if (res !== 1) {
  487. this.$set(this.detail, act, this.bakData[act]);
  488. }
  489. };
  490. break;
  491. case 'fileupload':
  492. this.$refs.upload.uploadHandleClick();
  493. return;
  494. case 'filechange':
  495. let filenum = $A.runNum(this.detail.filenum);
  496. switch (eve) {
  497. case 'up':
  498. this.$set(this.detail, 'filenum', filenum + 1);
  499. break;
  500. case 'error':
  501. case 'delete':
  502. this.$set(this.detail, 'filenum', filenum - 1);
  503. break;
  504. }
  505. if (eve == 'add' || eve == 'delete') {
  506. this.logType == '日志' && this.$refs.log.getLists(true, true);
  507. $A.triggerTaskInfoChange(ajaxData.taskid);
  508. }
  509. return;
  510. case 'complete':
  511. case 'unfinished':
  512. case 'archived':
  513. case 'unarchived':
  514. break;
  515. case 'archived2':
  516. ajaxData.act = 'complete';
  517. ajaxCallback = (res) => {
  518. if (res === 1 && !this.detail.archived) {
  519. this.handleTask('archived');
  520. return false;
  521. }
  522. };
  523. break;
  524. case 'level-1':
  525. case 'level-2':
  526. case 'level-3':
  527. case 'level-4':
  528. ajaxData.act = 'level';
  529. ajaxData.content = act.substring(6);
  530. break;
  531. case 'usernameb':
  532. if (!eve.username) {
  533. return;
  534. }
  535. this.$Modal.confirm({
  536. title: this.$L('修改负责人'),
  537. content: this.$L('你确定修改负责人设置为“%”吗?', (eve.nickname || eve.username)),
  538. onOk: () => {
  539. this.handleTask('username', eve);
  540. }
  541. });
  542. return;
  543. case 'username':
  544. if (!eve.username) {
  545. return;
  546. }
  547. ajaxData.content = eve.username;
  548. break;
  549. case 'inittime':
  550. if (this.detail.startdate > 0 && this.detail.enddate > 0) {
  551. this.timeValue = [$A.formatDate("Y-m-d H:i", this.detail.startdate), $A.formatDate("Y-m-d H:i", this.detail.enddate)]
  552. } else {
  553. this.timeValue = [];
  554. }
  555. return;
  556. case 'plannedtimeb':
  557. let temp = $A.date2string(this.timeValue, "Y-m-d H:i");
  558. this.$Modal.confirm({
  559. title: this.$L('修改计划时间'),
  560. content: this.$L('你确定将任务计划时间设置为“%”吗?', temp[0] + "~" + temp[1]),
  561. onOk: () => {
  562. this.handleTask('plannedtime');
  563. }
  564. });
  565. return;
  566. case 'plannedtime':
  567. this.timeValue = $A.date2string(this.timeValue, "Y-m-d H:i");
  568. ajaxData.content = this.timeValue[0] + "," + this.timeValue[1];
  569. this.$refs.timeRef.handleClose();
  570. break;
  571. case 'unplannedtimeb':
  572. this.$Modal.confirm({
  573. title: this.$L('取消计划时间'),
  574. content: this.$L('你确定将任务计划时间取消吗?'),
  575. onOk: () => {
  576. this.handleTask('unplannedtime');
  577. }
  578. });
  579. return;
  580. case 'unplannedtime':
  581. this.$refs.timeRef.handleClose();
  582. break;
  583. case 'attentiona':
  584. ajaxData.act = "attention";
  585. ajaxData.content = this.myUsername;
  586. break;
  587. case 'attention':
  588. if (!this.detail.attentionLists) {
  589. return;
  590. }
  591. ajaxData.content = this.detail.attentionLists;
  592. this.$refs.attentionRef.handleClose();
  593. break;
  594. case 'unattention':
  595. ajaxData.content = eve.username;
  596. if (eve.uisynch === true) {
  597. let bakFollower = cloneDeep(this.detail.follower);
  598. this.$set(this.detail, 'follower', this.detail.follower.filter((uname) => { return uname != eve }));
  599. ajaxCallback = (res) => {
  600. if (res !== 1) {
  601. this.$set(this.detail, 'follower', bakFollower);
  602. }
  603. };
  604. }
  605. break;
  606. case 'deleteb':
  607. this.$Modal.confirm({
  608. title: this.$L('删除提示'),
  609. content: this.$L('您确定要删除此任务吗?'),
  610. onOk: () => {
  611. this.handleTask('delete');
  612. },
  613. });
  614. return;
  615. case 'delete':
  616. ajaxCallback = (res) => {
  617. if (res === 1) {
  618. this.$Modal.info({
  619. title: this.$L('温馨提示'),
  620. content: this.$L('任务已删除,点击确定关闭窗口。'),
  621. onOk: () => {
  622. this.visible = false;
  623. }
  624. });
  625. return false;
  626. }
  627. };
  628. break;
  629. case 'comment':
  630. if (!this.commentText) {
  631. return;
  632. }
  633. ajaxData.content = this.commentText;
  634. ajaxCallback = (res) => {
  635. if (res === 1) {
  636. this.commentText = "";
  637. this.logType == '评论' && this.$refs.log.getLists(true, true);
  638. }
  639. };
  640. break;
  641. default: {
  642. return;
  643. }
  644. }
  645. //
  646. this.$set(this.loadData, ajaxData.act, true);
  647. let runTime = Math.round(new Date().getTime());
  648. $A.apiAjax({
  649. url: 'project/task/edit',
  650. data: ajaxData,
  651. complete: () => {
  652. this.$set(this.loadData, ajaxData.act, false);
  653. },
  654. error: () => {
  655. ajaxCallback(-1);
  656. alert(this.$L('网络繁忙,请稍后再试!'));
  657. },
  658. success: (res) => {
  659. runTime = Math.round(new Date().getTime()) - runTime;
  660. if (res.ret === 1) {
  661. let tempArray = cloneDeep(this.detail.subtask);
  662. this.detail = res.data;
  663. this.bakData = cloneDeep(this.detail);
  664. while (tempArray.length > 0 && tempArray[tempArray.length - 1].detail == '') {
  665. tempArray.splice(tempArray.length - 1, 1);
  666. this.detail.subtask.push({status: 'unfinished', detail: '' })
  667. }
  668. $A.triggerTaskInfoListener(ajaxData.act, res.data);
  669. $A.triggerTaskInfoChange(ajaxData.taskid);
  670. setTimeout(() => {
  671. if (ajaxCallback(1) !== false) {
  672. this.logType == '日志' && this.$refs.log.getLists(true, true);
  673. this.$Message.success(res.msg);
  674. }
  675. }, Math.max(0, 350 - runTime));
  676. } else {
  677. setTimeout(() => {
  678. ajaxCallback(0);
  679. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  680. }, Math.max(0, 350 - runTime));
  681. }
  682. }
  683. });
  684. },
  685. openProject(projectid) {
  686. try {
  687. this.visible = false;
  688. $A.app.$router.push({
  689. name: 'project-panel',
  690. params: {projectid: projectid, statistics: '', other: {}}
  691. });
  692. } catch (e) {
  693. this.visible = true;
  694. }
  695. }
  696. }
  697. }
  698. </script>
  699. <style lang="scss">
  700. .project-task-detail-window {
  701. .detail-title-box {
  702. .detail-title-input {
  703. textarea {
  704. margin: -7px 0 3px -2px;
  705. font-size: 20px;
  706. font-weight: 600;
  707. border: 2px solid #ffffff;
  708. padding: 2px;
  709. cursor: pointer;
  710. color: #172b4d;
  711. background: #ffffff;
  712. width: 100%;
  713. border-radius: 3px;
  714. resize: none;
  715. }
  716. }
  717. }
  718. .detail-desc-box {
  719. .detail-desc-input {
  720. margin: 10px 0 6px;
  721. textarea {
  722. border: 2px solid #F4F5F7;
  723. padding: 5px 8px;
  724. color: #172b4d;
  725. background: rgba(9, 30, 66, 0.04);
  726. resize: none;
  727. }
  728. }
  729. }
  730. .detail-subtask-input {
  731. flex: 1;
  732. border: 0;
  733. background: #ffffff;
  734. margin-left: 2px;
  735. border-bottom: 1px solid #f6f6f6;
  736. textarea {
  737. border: 0;
  738. box-shadow: none;
  739. outline: none;
  740. resize: none;
  741. min-height: auto;
  742. padding-left: 0;
  743. padding-right: 0;
  744. &:focus {
  745. color: #333333;
  746. }
  747. }
  748. &.subtask-complete {
  749. textarea {
  750. text-decoration: line-through;
  751. color: #999;
  752. }
  753. }
  754. }
  755. }
  756. </style>
  757. <style lang="scss" scoped>
  758. .project-task-detail-window {
  759. position: fixed;
  760. z-index: 1001;
  761. top: 0;
  762. left: 0;
  763. height: 100%;
  764. width: 100%;
  765. background-color: rgba(0, 0, 0, 0.5);
  766. transition: all .3s;
  767. opacity: 0;
  768. pointer-events: unset;
  769. display: flex;
  770. flex-direction: column;
  771. align-items: center;
  772. justify-content: center;
  773. &.task-detail-show {
  774. opacity: 1;
  775. }
  776. .task-detail-main {
  777. display: flex;
  778. flex-direction: row;
  779. width: 92%;
  780. max-width: 800px;
  781. max-height: 92%;
  782. background: #ffffff;
  783. overflow: visible;
  784. border-radius: 4px;
  785. padding: 10px 20px 2px;
  786. transform: translateZ(0);
  787. .detail-left {
  788. flex: 1;
  789. padding-left: 8px;
  790. padding-right: 20px;
  791. overflow: auto;
  792. .detail-h2 {
  793. color: #172b4d;
  794. font-size: 16px;
  795. display: flex;
  796. align-items: center;
  797. line-height: 26px;
  798. strong {
  799. font-size: 14px;
  800. font-weight: normal;
  801. &.link {
  802. cursor: pointer;
  803. }
  804. &.active {
  805. font-size: 16px;
  806. font-weight: bold;
  807. }
  808. }
  809. em {
  810. margin: 0 9px;
  811. width: 1px;
  812. height: 10px;
  813. background: #cccccc;
  814. }
  815. .detail-button {
  816. position: absolute;
  817. right: 12px;
  818. top: 50%;
  819. font-size: 12px;
  820. transform: translate(0, -50%);
  821. opacity: 0.5;
  822. transition: all 0.3s;
  823. &:hover {
  824. opacity: 1;
  825. }
  826. }
  827. }
  828. .detail-icon {
  829. position: relative;
  830. padding-left: 26px;
  831. &:before {
  832. font-family: zenicon;
  833. font-size: 20px;
  834. color: #42526e;
  835. font-weight: 600;
  836. position: absolute;
  837. top: 0;
  838. left: 0;
  839. width: 26px;
  840. height: 26px;
  841. line-height: 26px;
  842. }
  843. }
  844. .detail-title-box {
  845. margin-top: 12px;
  846. margin-bottom: 12px;
  847. &:before {
  848. content: "\E740";
  849. }
  850. .subtitle {
  851. padding-top: 3px;
  852. font-size: 12px;
  853. color: #606266;
  854. .project-title {
  855. cursor: pointer;
  856. &:hover {
  857. color: #57a3f3;
  858. text-decoration: underline;
  859. }
  860. }
  861. }
  862. }
  863. .detail-desc-box {
  864. &:before {
  865. content: "\E75E";
  866. }
  867. }
  868. .detail-text-box {
  869. margin-bottom: 12px;
  870. li {
  871. color: #606266;
  872. font-size: 14px;
  873. line-height: 32px;
  874. word-break: break-all;
  875. display: flex;
  876. &:before {
  877. font-weight: normal;
  878. color: #606266;
  879. font-size: 14px;
  880. padding-left: 4px;
  881. line-height: 32px;
  882. }
  883. &.text-time {
  884. &:before {
  885. content: "\E706";
  886. }
  887. }
  888. &.text-username {
  889. &:before {
  890. content: "\E903";
  891. }
  892. }
  893. &.text-follower {
  894. &:before {
  895. content: "\E90D";
  896. }
  897. }
  898. &.text-level {
  899. &:before {
  900. content: "\E725";
  901. }
  902. }
  903. &.text-status {
  904. &:before {
  905. content: "\E6AF";
  906. }
  907. }
  908. > span {
  909. white-space: nowrap;
  910. }
  911. > em {
  912. margin-left: 4px;
  913. padding-top: 5px;
  914. line-height: 22px;
  915. &.p1 {
  916. color: #ed3f14;
  917. }
  918. &.p2 {
  919. color: #ff9900;
  920. }
  921. &.p3 {
  922. color: #19be6b;
  923. }
  924. &.p4 {
  925. color: #666666;
  926. }
  927. &.complete {
  928. color: #666666;
  929. .completedate {
  930. font-size: 12px;
  931. padding-left: 4px;
  932. opacity: 0.6;
  933. }
  934. }
  935. &.overdue {
  936. color: #ff0000;
  937. }
  938. &.unfinished {
  939. color: #19be6b;
  940. }
  941. }
  942. }
  943. }
  944. .detail-file-box {
  945. &:before {
  946. content: "\E8B9";
  947. font-size: 16px;
  948. padding-left: 2px;
  949. }
  950. }
  951. .detail-subtask-icon {
  952. &:before {
  953. content: "\E819";
  954. font-size: 16px;
  955. padding-left: 2px;
  956. }
  957. }
  958. .detail-subtask-box {
  959. padding: 12px;
  960. .detail-subtask-progress {
  961. margin: 2px 0 6px;
  962. }
  963. .detail-subtask-item {
  964. display: flex;
  965. flex-direction: row;
  966. align-items: center;
  967. margin: 4px 2px;
  968. position: relative;
  969. &:hover {
  970. .detail-subtask-delete {
  971. opacity: 1;
  972. }
  973. }
  974. .detail-subtask-delete {
  975. opacity: 0;
  976. position: absolute;
  977. top: 50%;
  978. right: 0;
  979. transform: translate(0, -50%);
  980. width: 28px;
  981. height: 28px;
  982. line-height: 28px;
  983. font-size: 16px;
  984. text-align: center;
  985. cursor: pointer;
  986. background: #ffffff;
  987. border-radius: 2px 0 0 2px;
  988. transition: all 0.3s;
  989. }
  990. }
  991. .detail-subtask-none {
  992. color: #666666;
  993. cursor: pointer;
  994. padding: 0 12px;
  995. }
  996. }
  997. .detail-comment-box {
  998. &:before {
  999. content: "\E753";
  1000. }
  1001. }
  1002. .detail-footer-box {
  1003. border-top: 1px solid #e5e5e5;
  1004. display: flex;
  1005. flex-direction: row;
  1006. padding-top: 20px;
  1007. padding-bottom: 16px;
  1008. .comment-input {
  1009. margin-right: 12px;
  1010. }
  1011. }
  1012. }
  1013. .detail-right {
  1014. .cancel {
  1015. text-align: right;
  1016. width: auto;
  1017. height: 38px;
  1018. em {
  1019. display: inline-block;
  1020. width: 38px;
  1021. height: 38px;
  1022. cursor: pointer;
  1023. border-radius: 50%;
  1024. transform: scale(0.92);
  1025. &:after,
  1026. &:before {
  1027. position: absolute;
  1028. content: "";
  1029. top: 50%;
  1030. left: 50%;
  1031. width: 2px;
  1032. height: 20px;
  1033. background-color: #EE2321;
  1034. transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
  1035. transition: all .2s;
  1036. }
  1037. &:before {
  1038. position: absolute;
  1039. transform: translate(-50%, -50%) rotate(-45deg) scale(0.6, 1);
  1040. }
  1041. &:hover {
  1042. &:after,
  1043. &:before {
  1044. background-color: #ff0000;
  1045. transform: translate(-50%, -50%) rotate(135deg) scale(0.6, 1);
  1046. }
  1047. &:before {
  1048. background-color: #ff0000;
  1049. transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
  1050. }
  1051. }
  1052. }
  1053. }
  1054. .block {
  1055. display: block;
  1056. .p1 {
  1057. color: #ed3f14;
  1058. }
  1059. .p2 {
  1060. color: #ff9900;
  1061. }
  1062. .p3 {
  1063. color: #19be6b;
  1064. }
  1065. .p4 {
  1066. color: #666666;
  1067. }
  1068. .checkmark {
  1069. margin-left: 8px;
  1070. margin-right: -8px;
  1071. }
  1072. }
  1073. .btn {
  1074. display: block;
  1075. width: 118px;
  1076. text-align: left;
  1077. margin-top: 8px;
  1078. padding-left: 10px;
  1079. padding-right: 10px;
  1080. overflow: hidden;
  1081. white-space: nowrap;
  1082. text-overflow: ellipsis;
  1083. }
  1084. }
  1085. }
  1086. }
  1087. </style>