detail.vue 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459
  1. <template>
  2. <div v-if="detail.isassign!==true" class="project-task-detail-window" :class="{'task-detail-show': visible}">
  3. <div class="task-detail-bg" @click="handleBgClose"></div>
  4. <div class="task-detail-main"
  5. @drop.prevent="commentPasteDrag($event, 'drag')"
  6. @dragover.prevent="commentDragOver(true)"
  7. @dragleave.prevent="commentDragOver(false)">
  8. <div class="detail-left">
  9. <div class="detail-title-box detail-icon">
  10. <Input v-model="detail.title"
  11. :disabled="!!loadData.title"
  12. type="textarea"
  13. class="detail-title-input"
  14. ref="titleInput"
  15. :rows="1"
  16. :autosize="{minRows:1,maxRows:5}"
  17. maxlength="60"
  18. @on-keydown="titleKeydown"
  19. @on-blur="handleTask('title')"/>
  20. <div v-if="detail.projectTitle && urlProjectid != detail.projectid" class="subtitle">
  21. {{$L('所属项目:')}}
  22. <span class="project-title" @click="openProject(detail.projectid)">{{detail.projectTitle}}</span>
  23. </div>
  24. <div class="subtitle">
  25. <span class="z-nick"><UserView :username="detail.createuser"/></span>
  26. {{$L('创建于:')}}
  27. <span>{{$A.formatDate("Y-m-d H:i:s", detail.indate)}}</span>
  28. </div>
  29. </div>
  30. <div class="detail-desc-box detail-icon">
  31. <div class="detail-h2"><strong class="active">{{$L('描述')}}</strong></div>
  32. <DescEditor :taskid="detail.id" :desc="detail.desc" :placeholder="$L('添加详细描述...')" @save-success="handleTask('desc')"/>
  33. </div>
  34. <ul class="detail-text-box">
  35. <li v-if="detail.startdate > 0 && detail.enddate > 0" class="text-time detail-icon">
  36. <span>{{$L('计划时间:')}}</span>
  37. <em>
  38. {{$A.formatDate("Y-m-d H:i", detail.startdate)}} {{$L('至')}} {{$A.formatDate("Y-m-d H:i", detail.enddate)}}
  39. <em v-if="detail.overdue" class="overdue">[{{$L('已超期')}}]</em>
  40. </em>
  41. </li>
  42. <li class="text-username detail-icon">
  43. <span>{{$L('负责人:')}}</span>
  44. <template v-if="typeof detail.username!=='undefined'">
  45. <em v-if="detail.username"><UserView :username="detail.username" showimg/></em>
  46. <em v-else>
  47. <div class="uname-no">{{$L('暂无负责人')}}</div>
  48. <Button :loading="!!loadData.claim" class="uname-button" type="primary" size="small" @click="handleTask('claimb')">{{$L('认领任务')}}</Button>
  49. </em>
  50. <em v-if="detail.type=='assign' && !detail.reassign">
  51. <Button v-if="detail.username==usrName" class="uname-button" type="success" size="small" @click="handleTask('reassign')">{{$L('确认接收')}}</Button>
  52. <div v-else class="uname-text">[{{$L('等待确认')}}]</div>
  53. </em>
  54. </template>
  55. </li>
  56. <li v-if="followerLength(detail.follower) > 0" class="text-follower detail-icon">
  57. <span>{{$L('关注者:')}}</span>
  58. <em>
  59. <Tag v-for="(fname, findex) in detail.follower" :key="findex" closable @on-close="handleTask('unattention', {username:fname,uisynch:true})"><UserView :username="fname" showimg/></Tag>
  60. </em>
  61. </li>
  62. <li class="text-level detail-icon">
  63. <span>{{$L('优先级:')}}</span>
  64. <em :class="`p${detail.level}`">{{levelFormt(detail.level)}}</em>
  65. </li>
  66. <li class="text-status detail-icon">
  67. <span>{{$L('任务状态:')}}</span>
  68. <em v-if="detail.complete" class="complete">{{$L('已完成')}}<span class="completedate">({{$A.formatDate("Y-m-d H:i", detail.completedate)}})</span></em>
  69. <em v-else class="unfinished">{{$L('未完成')}}</em>
  70. </li>
  71. </ul>
  72. <div class="detail-h2 detail-subtask-icon detail-icon">
  73. <strong class="active">{{$L('子任务')}}</strong>
  74. <div class="detail-button">
  75. <Button class="detail-button-batch" size="small" @click="subtaskBatchAdd">{{$L('批量添加子任务')}}</Button>
  76. <Button class="detail-button-btn" size="small" @click="handleTask('subtaskAdd')">{{$L('添加子任务')}}</Button>
  77. </div>
  78. </div>
  79. <div class="detail-subtask-box">
  80. <div v-if="detail.subtask.length == 0" class="detail-subtask-none">{{$L('暂无子任务')}}</div>
  81. <div v-else>
  82. <Progress class="detail-subtask-progress" :percent="subtaskProgress" :stroke-width="5" status="active" />
  83. <draggable
  84. v-model="detail.subtask"
  85. draggable=".detail-subtask-item"
  86. handle=".detail-subtask-rmenu"
  87. :animation="150"
  88. @sort="handleTask('subtaskBlur')">
  89. <div v-for="(subitem, subindex) in detail.subtask" :key="subindex" :data-id="subitem.id" class="detail-subtask-item">
  90. <Checkbox v-model="subitem.status"
  91. true-value="complete"
  92. false-value="unfinished"
  93. @on-change="handleTask('subtaskBlur')"></Checkbox>
  94. <UserView v-if="subitem.uname"
  95. :username="subitem.uname"
  96. imgsize="20"
  97. imgfontsize="14"
  98. :showname="false"
  99. showimg/>
  100. <Input v-model="subitem.detail"
  101. type="textarea"
  102. class="detail-subtask-input"
  103. :readonly="subitem.status=='complete'"
  104. :ref="`subtaskInput_${subindex}`"
  105. :class="{'subtask-complete':subitem.status=='complete'}"
  106. :rows="1"
  107. :autosize="{minRows:1,maxRows:5}"
  108. maxlength="255"
  109. :placeholder="$L('子任务描述...')"
  110. @on-keydown="subtaskKeydown(subindex, $event)"
  111. @on-blur="handleTask('subtaskBlur')"/>
  112. <div class="detail-subtask-right" :style="subitem.stip==='show'?{opacity:1}:{}">
  113. <Icon type="md-menu" class="detail-subtask-ricon detail-subtask-rmenu"/>
  114. <Poptip
  115. class="detail-subtask-ricon"
  116. transfer
  117. @on-popper-show="$set(subitem, 'stip', 'show')"
  118. @on-popper-hide="[$set(subitem, 'stip', ''), handleTask('subtaskBlur')]">
  119. <Icon type="md-person" />
  120. <div slot="content">
  121. <div style="width:280px">
  122. {{$L('子任务负责人')}}
  123. <UserInput
  124. v-model="subitem.uname"
  125. :projectid="detail.projectid"
  126. :transfer="false"
  127. :placeholder="$L('输入关键词搜索')"
  128. style="margin:5px 0 3px"></UserInput>
  129. </div>
  130. </div>
  131. </Poptip>
  132. <div v-if="subitem.detail==''" class="detail-subtask-ricon">
  133. <Icon type="md-trash" @click="handleTask('subtaskDelete', subindex)"/>
  134. </div>
  135. <Poptip v-else
  136. class="detail-subtask-ricon"
  137. transfer
  138. confirm
  139. :title="$L('你确定你要删除这个子任务吗?')"
  140. @on-ok="handleTask('subtaskDelete', subindex)"
  141. @on-popper-show="$set(subitem, 'stip', 'show')"
  142. @on-popper-hide="$set(subitem, 'stip', '')"><Icon type="md-trash" /></Poptip>
  143. </div>
  144. </div>
  145. </draggable>
  146. </div>
  147. </div>
  148. <div :style="`${detail.filenum>0?'':'display:none'}`">
  149. <div class="detail-h2 detail-file-box detail-icon">
  150. <strong class="active">{{$L('附件')}}</strong>
  151. <div class="detail-button">
  152. <Button class="detail-button-btn" size="small" @click="handleTask('fileupload')">{{$L('添加附件')}}</Button>
  153. </div>
  154. </div>
  155. <project-task-files ref="projectUpload" :taskid="taskid" :projectid="detail.projectid" :simple="true" @change="handleTask('filechange', $event)"></project-task-files>
  156. </div>
  157. <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>
  158. <div class="detail-log-box">
  159. <project-task-logs ref="log" :logtype="logType" :projectid="detail.projectid" :taskid="taskid" :pagesize="5"></project-task-logs>
  160. </div>
  161. <div class="detail-footer-box">
  162. <WInput class="comment-input" v-model="commentText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="commentKeydown" @on-input-paste="commentPasteDrag" :placeholder="$L('输入评论,Enter发表评论,Shift+Enter换行')" />
  163. <Button :loading="!!loadData.comment" :disabled="!commentText" type="primary" @click="handleTask('comment')">评 论</Button>
  164. </div>
  165. </div>
  166. <div v-if="detail.username" class="detail-right" :class="{'open-menu':openMenu}">
  167. <Button v-if="detail.complete" :loading="!!loadData.unfinished" icon="md-checkmark-circle-outline" class="btn" @click="handleTask('unfinished')">{{$L('标记未完成')}}</Button>
  168. <Button v-else :loading="!!loadData.complete" icon="md-radio-button-off" class="btn" @click="handleTask('complete')">{{$L('标记已完成')}}</Button>
  169. <Dropdown trigger="click" class="block" @on-click="handleTask" @on-visible-change="handleSubwinToggle">
  170. <Button :loading="!!loadData.level" icon="md-funnel" class="btn">{{$L('优先级')}}</Button>
  171. <DropdownMenu slot="list">
  172. <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>
  173. </DropdownMenu>
  174. </Dropdown>
  175. <Poptip placement="bottom" class="block" @on-popper-show="[handleUsernameShow(),handleSubwinToggle(true)]" @on-popper-hide="handleSubwinToggle(false)" transfer>
  176. <Button :loading="!!loadData.username" icon="md-person" class="btn">{{$L('负责人')}}</Button>
  177. <div slot="content">
  178. <div style="width:280px">
  179. {{$L('选择负责人')}}
  180. <UserInput v-model="detail.newusername" :projectid="detail.projectid" :nousername="detail.username" :transfer="false" @change="handleTask('usernameb', $event)" :placeholder="$L('输入关键词搜索')" style="margin:5px 0 3px"></UserInput>
  181. </div>
  182. </div>
  183. </Poptip>
  184. <Poptip ref="timeRef" placement="bottom" class="block" @on-popper-show="[handleTask('inittime'),handleSubwinToggle(true)]" @on-popper-hide="handleSubwinToggle(false)" transfer>
  185. <Button :loading="!!loadData.plannedtime || !!loadData.unplannedtime" icon="md-calendar" class="btn">{{$L('计划时间')}}</Button>
  186. <div slot="content">
  187. <div style="width:280px">
  188. {{$L('选择日期范围')}}
  189. <Date-picker
  190. v-model="timeValue"
  191. :options="timeOptions"
  192. :placeholder="$L('日期范围')"
  193. format="yyyy-MM-dd HH:mm"
  194. type="datetimerange"
  195. placement="bottom"
  196. @on-ok="handleTask('plannedtimeb')"
  197. @on-clear="handleTask('unplannedtimeb')"
  198. style="display:block;margin:5px 0 3px"></Date-picker>
  199. </div>
  200. </div>
  201. </Poptip>
  202. <Button icon="md-attach" class="btn" @click="handleTask('fileupload')">{{$L('添加附件')}}</Button>
  203. <Poptip ref="attentionRef" v-if="detail.username == usrName" placement="bottom" class="block" @on-popper-show="[handleAttentionShow(),handleSubwinToggle(true)]" @on-popper-hide="handleSubwinToggle(false)" transfer>
  204. <Button :loading="!!loadData.attention" icon="md-at" class="btn">{{$L('关注人')}}</Button>
  205. <div slot="content">
  206. <div style="width:280px">
  207. {{$L('选择关注人')}}
  208. <UserInput :projectid="detail.projectid" :multiple="true" :transfer="false" v-model="detail.attentionLists" :placeholder="$L('输入关键词搜索')" style="margin:5px 0 3px" @on-confirm="handleTask('attention', true)"></UserInput>
  209. </div>
  210. </div>
  211. </Poptip>
  212. <Button v-else-if="haveAttention(detail.follower)" :loading="!!loadData.unattention" icon="md-at" class="btn" @click="handleTask('unattention', {username:usrName})">{{$L('取消关注')}}</Button>
  213. <Button v-else :loading="!!loadData.attention" icon="md-at" class="btn" @click="handleTask('attentiona')">{{$L('关注任务')}}</Button>
  214. <Button v-if="!detail.archived" :loading="!!loadData.archived" icon="md-filing" class="btn" @click="handleTask('archived')">{{$L('归档')}}</Button>
  215. <Button v-else :loading="!!loadData.unarchived" icon="md-filing" class="btn" @click="handleTask('unarchived')">{{$L('取消归档')}}</Button>
  216. <Button :loading="!!loadData.delete" icon="md-trash" class="btn" type="error" ghost @click="handleTask('deleteb')">{{$L('删除')}}</Button>
  217. </div>
  218. <div v-if="detail.complete" class="detail-complete"><Icon type="md-checkmark-circle-outline" /></div>
  219. <div class="detail-menu" @click="openMenu=!openMenu"><Icon type="md-menu" size="24"/></div>
  220. <div class="detail-cancel"><em @click="visible=false"></em></div>
  221. <div v-if="detailDragOver" class="detail-drag-over"><div class="detail-drag-text">{{$L('拖动到这里添加附件至 %', detail.title)}}</div></div>
  222. </div>
  223. </div>
  224. </template>
  225. <script>
  226. import ProjectTaskLogs from "../logs";
  227. import ProjectTaskFiles from "../files";
  228. import draggable from 'vuedraggable'
  229. import cloneDeep from "lodash/cloneDeep";
  230. import WInput from "../../../iview/WInput";
  231. import DescEditor from "./DescEditor";
  232. export default {
  233. components: {DescEditor, WInput, ProjectTaskFiles, ProjectTaskLogs, draggable},
  234. data() {
  235. return {
  236. taskid: 0,
  237. detail: {},
  238. detailDragOver: false,
  239. visible: false,
  240. subwinVisible: 0,
  241. urlProjectid: 0,
  242. bakData: {},
  243. loadData: {},
  244. loadRand: {},
  245. commentText: '',
  246. logType: '评论',
  247. timeValue: [],
  248. timeOptions: {},
  249. openMenu: false,
  250. }
  251. },
  252. beforeCreate() {
  253. let doms = document.querySelectorAll('.project-task-detail-window');
  254. for (let i = 0; i < doms.length; ++i) {
  255. if (doms[i].parentNode != null) doms[i].parentNode.removeChild(doms[i]);
  256. }
  257. },
  258. mounted() {
  259. let match = (window.location.pathname + "").match(/\/project\/panel\/(\d+)$/i);
  260. this.urlProjectid = match ? match[1] : 0;
  261. //
  262. this.$nextTick(() => {
  263. let dom = this.$el;
  264. if (parseInt(this.taskid) === 0) {
  265. if (dom.parentNode != null) dom.parentNode.removeChild(dom);
  266. return;
  267. }
  268. //
  269. dom.addEventListener('transitionend', () => {
  270. if (dom !== null && dom.parentNode !== null && !this.visible) {
  271. dom.parentNode.removeChild(dom);
  272. }
  273. }, false);
  274. //
  275. setTimeout(() => {
  276. this.visible = true;
  277. }, 0)
  278. });
  279. this.bakData = cloneDeep(this.detail);
  280. this.getTaskDetail();
  281. //
  282. $A.setOnTaskInfoListener('components/project/task/detail',(act, detail) => {
  283. if (detail.id != this.taskid) {
  284. return;
  285. }
  286. if (detail.__modifyUsername == this.usrName) {
  287. return;
  288. }
  289. this.getTaskDetail();
  290. }, true);
  291. },
  292. watch: {
  293. taskid() {
  294. this.bakData = cloneDeep(this.detail);
  295. this.getTaskDetail();
  296. }
  297. },
  298. computed: {
  299. subtaskProgress() {
  300. const countLists = this.detail.subtask;
  301. if (countLists.length === 0) {
  302. return 0;
  303. }
  304. const completeLists = countLists.filter((item) => { return item.status == 'complete'});
  305. return parseFloat(((completeLists.length / countLists.length) * 100).toFixed(2));
  306. }
  307. },
  308. methods: {
  309. initLanguage() {
  310. let lastSecond = (e) => {
  311. return new Date($A.formatDate("Y-m-d 23:59:29", Math.round(e / 1000)))
  312. };
  313. this.timeOptions = {
  314. shortcuts: [{
  315. text: this.$L('今天'),
  316. value() {
  317. return [new Date(), lastSecond(new Date().getTime())];
  318. }
  319. }, {
  320. text: this.$L('明天'),
  321. value() {
  322. let e = new Date();
  323. e.setDate(e.getDate() + 1);
  324. return [new Date(), lastSecond(e.getTime())];
  325. }
  326. }, {
  327. text: this.$L('本周'),
  328. value() {
  329. return [$A.getData('今天', true), lastSecond($A.getData('本周结束2', true))];
  330. }
  331. }, {
  332. text: this.$L('本月'),
  333. value() {
  334. return [$A.getData('今天', true), lastSecond($A.getData('本月结束', true))];
  335. }
  336. }, {
  337. text: this.$L('3天'),
  338. value() {
  339. let e = new Date();
  340. e.setDate(e.getDate() + 3);
  341. return [new Date(), lastSecond(e.getTime())];
  342. }
  343. }, {
  344. text: this.$L('5天'),
  345. value() {
  346. let e = new Date();
  347. e.setDate(e.getDate() + 5);
  348. return [new Date(), lastSecond(e.getTime())];
  349. }
  350. }, {
  351. text: this.$L('7天'),
  352. value() {
  353. let e = new Date();
  354. e.setDate(e.getDate() + 7);
  355. return [new Date(), lastSecond(e.getTime())];
  356. }
  357. }]
  358. };
  359. },
  360. levelFormt(p) {
  361. switch (parseInt(p)) {
  362. case 1:
  363. return this.$L("重要且紧急") + " (P1)";
  364. case 2:
  365. return this.$L("重要不紧急") + " (P2)";
  366. case 3:
  367. return this.$L("紧急不重要") + " (P3)";
  368. case 4:
  369. return this.$L("不重要不紧急") + " (P4)";
  370. }
  371. },
  372. titleKeydown(e) {
  373. if (e.keyCode == 13) {
  374. e.preventDefault();
  375. e.target.blur();
  376. }
  377. },
  378. descKeydown(e) {
  379. if (e.keyCode == 13) {
  380. if (e.shiftKey) {
  381. return;
  382. }
  383. e.preventDefault();
  384. e.target.blur();
  385. }
  386. },
  387. commentKeydown(e) {
  388. if (e.keyCode == 13) {
  389. if (e.shiftKey) {
  390. return;
  391. }
  392. e.preventDefault();
  393. this.handleTask('comment');
  394. }
  395. },
  396. commentDragOver(show) {
  397. let random = (this.__detailDragOver = $A.randomString(8));
  398. if (!show) {
  399. setTimeout(() => {
  400. if (random === this.__detailDragOver) {
  401. this.detailDragOver = show;
  402. }
  403. }, 150);
  404. } else {
  405. this.detailDragOver = show;
  406. }
  407. },
  408. commentPasteDrag(e, type) {
  409. this.detailDragOver = false;
  410. const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
  411. const postFiles = Array.prototype.slice.call(files);
  412. if (postFiles.length > 0) {
  413. e.preventDefault();
  414. postFiles.forEach((file) => {
  415. this.$refs.projectUpload.upload(file);
  416. });
  417. }
  418. },
  419. subtaskKeydown(subindex, e) {
  420. if (e.keyCode == 13) {
  421. if (e.shiftKey) {
  422. return;
  423. }
  424. e.preventDefault();
  425. this.handleTask('subtaskEnter', subindex);
  426. }
  427. },
  428. followerLength(follower) {
  429. if (follower instanceof Array) {
  430. return follower.length;
  431. } else {
  432. return 0;
  433. }
  434. },
  435. followerToStr(follower) {
  436. if (follower instanceof Array) {
  437. return follower.join(",");
  438. } else {
  439. return '';
  440. }
  441. },
  442. haveAttention(follower) {
  443. if (follower instanceof Array) {
  444. return follower.filter((uname) => { return uname == this.usrName }).length > 0
  445. } else {
  446. return 0;
  447. }
  448. },
  449. getTaskDetail() {
  450. $A.apiAjax({
  451. url: 'project/task/detail',
  452. data: {
  453. taskid: this.taskid,
  454. },
  455. error: () => {
  456. alert(this.$L('网络繁忙,请稍后再试!'));
  457. this.visible = false;
  458. },
  459. success: (res) => {
  460. if (res.ret === 1) {
  461. this.detail = res.data;
  462. this.bakData = cloneDeep(this.detail);
  463. this.$nextTick(() => {
  464. this.$refs.titleInput.resizeTextarea();
  465. this.detail.subtask.forEach((temp, index) => {
  466. this.$refs['subtaskInput_' + (index)][0].resizeTextarea();
  467. })
  468. });
  469. } else {
  470. this.$Modal.error({
  471. title: this.$L('温馨提示'),
  472. content: res.msg,
  473. onOk: () => {
  474. this.visible = false;
  475. }
  476. });
  477. }
  478. }
  479. });
  480. },
  481. subtaskBatchAdd() {
  482. this.inputValue = "";
  483. this.$Modal.confirm({
  484. width: 560,
  485. render: (h) => {
  486. return h('div', [
  487. h('div', {
  488. style: {
  489. fontSize: '16px',
  490. fontWeight: '500',
  491. marginBottom: '20px',
  492. }
  493. }, this.$L('批量添加子任务')),
  494. h('Input', {
  495. props: {
  496. type: 'textarea',
  497. rows: 4,
  498. autosize: {minRows: 4, maxRows: 30},
  499. value: this.inputValue,
  500. placeholder: this.$L('使用换行添加多个子任务')
  501. },
  502. on: {
  503. input: (val) => {
  504. this.inputValue = val;
  505. }
  506. }
  507. })
  508. ])
  509. },
  510. loading: true,
  511. onOk: () => {
  512. if (this.inputValue) {
  513. let tempArray = this.inputValue.split(/\n/);
  514. tempArray.forEach((detail) => {
  515. detail = detail.trim();
  516. detail && this.detail.subtask.push({
  517. id: $A.randomString(6),
  518. uname: '',
  519. time: Math.round(new Date().getTime()/1000),
  520. status: 'unfinished',
  521. detail: detail,
  522. stip: ''
  523. });
  524. });
  525. this.handleTask('subtask', () => {
  526. this.$Modal.remove();
  527. });
  528. } else {
  529. this.$Modal.remove();
  530. }
  531. },
  532. });
  533. },
  534. handleUsernameShow() {
  535. this.$set(this.detail, 'newusername', '')
  536. },
  537. handleAttentionShow() {
  538. this.$set(this.detail, 'attentionLists', this.followerToStr(this.detail.follower))
  539. },
  540. handleBgClose() {
  541. if (this.subwinVisible > 0) {
  542. return;
  543. }
  544. this.visible = false;
  545. },
  546. handleSubwinToggle(visible) {
  547. if (visible) {
  548. this.subwinVisible++;
  549. } else {
  550. this.subwinVisible--;
  551. }
  552. },
  553. handleTask(act, eve) {
  554. let ajaxData = {
  555. act: act,
  556. taskid: this.taskid,
  557. };
  558. let ajaxCallback = () => {};
  559. //
  560. switch (act) {
  561. case 'title':
  562. if (this.detail[act] == this.bakData[act]) {
  563. return;
  564. }
  565. if (act == 'title' && !this.detail[act]) {
  566. this.$set(this.detail, act, this.bakData[act]);
  567. return;
  568. }
  569. ajaxData.content = this.detail[act];
  570. ajaxCallback = (res) => {
  571. if (res !== 1) {
  572. this.$set(this.detail, act, this.bakData[act]);
  573. }
  574. };
  575. break;
  576. case 'desc':
  577. this.logType == '日志' && this.$refs.log.getLists(true, true);
  578. return;
  579. case 'subtaskAdd':
  580. if (!$A.isArray(this.detail.subtask)) {
  581. this.detail.subtask = [];
  582. }
  583. this.detail.subtask.push({
  584. id: $A.randomString(6),
  585. uname: '',
  586. time: Math.round(new Date().getTime()/1000),
  587. status: 'unfinished',
  588. detail: '',
  589. stip: ''
  590. });
  591. this.$nextTick(() => {
  592. this.$refs['subtaskInput_' + (this.detail.subtask.length - 1)][0].focus();
  593. });
  594. return;
  595. case 'subtaskDelete':
  596. this.detail.subtask.splice(eve, 1);
  597. this.handleTask('subtaskBlur');
  598. return;
  599. case 'subtaskEnter':
  600. if (!$A.isArray(this.detail.subtask)) {
  601. this.detail.subtask = [];
  602. }
  603. if (eve + 1 >= this.detail.subtask.length) {
  604. this.handleTask('subtaskAdd');
  605. return;
  606. }
  607. this.$refs['subtaskInput_' + (eve + 1)][0].focus();
  608. return;
  609. case 'subtaskBlur':
  610. this.handleTask('subtask');
  611. return;
  612. case 'subtask':
  613. let tempArray = cloneDeep(this.detail[act]);
  614. while (tempArray.length > 0 && tempArray[tempArray.length - 1].detail == '') {
  615. tempArray.splice(tempArray.length - 1, 1);
  616. }
  617. tempArray.forEach((item) => {
  618. if (typeof item.stip !== "undefined") {
  619. delete item.stip;
  620. }
  621. });
  622. if ($A.jsonStringify(tempArray) === $A.jsonStringify(this.bakData[act])) {
  623. return;
  624. }
  625. ajaxData.content = tempArray;
  626. ajaxCallback = (res) => {
  627. if (res !== 1) {
  628. this.$set(this.detail, act, cloneDeep(this.bakData[act]));
  629. }
  630. typeof eve === "function" && eve(res);
  631. };
  632. break;
  633. case 'fileupload':
  634. this.$refs.projectUpload.uploadHandleClick();
  635. return;
  636. case 'filechange':
  637. let filenum = $A.runNum(this.detail.filenum);
  638. switch (eve) {
  639. case 'up':
  640. this.$set(this.detail, 'filenum', filenum + 1);
  641. break;
  642. case 'error':
  643. case 'delete':
  644. this.$set(this.detail, 'filenum', filenum - 1);
  645. break;
  646. }
  647. if (eve == 'add' || eve == 'delete') {
  648. this.logType == '日志' && this.$refs.log.getLists(true, true);
  649. $A.triggerTaskInfoChange(ajaxData.taskid);
  650. }
  651. return;
  652. case 'claimb':
  653. this.$Modal.confirm({
  654. title: this.$L('认领任务'),
  655. content: this.$L('你确定认领任务“%”吗?', this.detail.title),
  656. onOk: () => {
  657. this.handleTask('claim', eve);
  658. }
  659. });
  660. return;
  661. case 'claim':
  662. case 'reassign':
  663. case 'complete':
  664. case 'unfinished':
  665. case 'archived':
  666. case 'unarchived':
  667. break;
  668. case 'archived2':
  669. ajaxData.act = 'complete';
  670. ajaxCallback = (res) => {
  671. if (res === 1 && !this.detail.archived) {
  672. this.handleTask('archived');
  673. return false;
  674. }
  675. };
  676. break;
  677. case 'level-1':
  678. case 'level-2':
  679. case 'level-3':
  680. case 'level-4':
  681. ajaxData.act = 'level';
  682. ajaxData.content = act.substring(6);
  683. break;
  684. case 'usernameb':
  685. if (!eve.username) {
  686. return;
  687. }
  688. this.$Modal.confirm({
  689. title: this.$L('修改负责人'),
  690. content: this.$L('你确定修改负责人设置为“%”吗?', (eve.nickname || eve.username)),
  691. onOk: () => {
  692. this.handleTask('username', eve);
  693. }
  694. });
  695. return;
  696. case 'username':
  697. if (!eve.username) {
  698. return;
  699. }
  700. ajaxData.content = eve.username;
  701. break;
  702. case 'inittime':
  703. if (this.detail.startdate > 0 && this.detail.enddate > 0) {
  704. this.timeValue = [$A.formatDate("Y-m-d H:i", this.detail.startdate), $A.formatDate("Y-m-d H:i", this.detail.enddate)]
  705. } else {
  706. this.timeValue = [];
  707. }
  708. return;
  709. case 'plannedtimeb':
  710. let temp = $A.date2string(this.timeValue, "Y-m-d H:i");
  711. if (!temp[0] || !temp[1]) {
  712. this.$Modal.error({title: this.$L('温馨提示'), content: this.$L('请选择一个有效时间!')});
  713. return;
  714. }
  715. this.$Modal.confirm({
  716. title: this.$L('修改计划时间'),
  717. content: this.$L('你确定将任务计划时间设置为“%”吗?', temp[0] + "~" + temp[1]),
  718. onOk: () => {
  719. this.handleTask('plannedtime');
  720. }
  721. });
  722. return;
  723. case 'plannedtime':
  724. this.timeValue = $A.date2string(this.timeValue, "Y-m-d H:i");
  725. ajaxData.content = this.timeValue[0] + "," + this.timeValue[1];
  726. this.$refs.timeRef.handleClose();
  727. break;
  728. case 'unplannedtimeb':
  729. this.$Modal.confirm({
  730. title: this.$L('取消计划时间'),
  731. content: this.$L('你确定将任务计划时间取消吗?'),
  732. onOk: () => {
  733. this.handleTask('unplannedtime');
  734. }
  735. });
  736. return;
  737. case 'unplannedtime':
  738. this.$refs.timeRef.handleClose();
  739. break;
  740. case 'attentiona':
  741. ajaxData.act = "attention";
  742. ajaxData.content = this.usrName;
  743. break;
  744. case 'attention':
  745. if (!this.detail.attentionLists) {
  746. return;
  747. }
  748. ajaxData.mode = eve ? 'clean' : '';
  749. ajaxData.content = this.detail.attentionLists;
  750. this.$refs.attentionRef.handleClose();
  751. break;
  752. case 'unattention':
  753. ajaxData.content = eve.username;
  754. if (eve.uisynch === true) {
  755. let bakFollower = cloneDeep(this.detail.follower);
  756. this.$set(this.detail, 'follower', this.detail.follower.filter((uname) => { return uname != eve }));
  757. ajaxCallback = (res) => {
  758. if (res !== 1) {
  759. this.$set(this.detail, 'follower', bakFollower);
  760. }
  761. };
  762. }
  763. break;
  764. case 'deleteb':
  765. this.$Modal.confirm({
  766. title: this.$L('删除提示'),
  767. content: this.$L('您确定要删除此任务吗?'),
  768. onOk: () => {
  769. this.handleTask('delete');
  770. },
  771. });
  772. return;
  773. case 'delete':
  774. ajaxCallback = (res) => {
  775. if (res === 1) {
  776. this.$Modal.info({
  777. title: this.$L('温馨提示'),
  778. content: this.$L('任务已删除,点击确定关闭窗口。'),
  779. onOk: () => {
  780. this.visible = false;
  781. }
  782. });
  783. return false;
  784. }
  785. };
  786. break;
  787. case 'comment':
  788. if (!this.commentText) {
  789. return;
  790. }
  791. ajaxData.content = this.commentText;
  792. ajaxCallback = (res) => {
  793. if (res === 1) {
  794. this.commentText = "";
  795. this.logType == '评论' && this.$refs.log.getLists(true, true);
  796. }
  797. };
  798. break;
  799. default: {
  800. return;
  801. }
  802. }
  803. //
  804. let loadRand = $A.randomString(6);
  805. this.$set(this.loadRand, ajaxData.act, loadRand);
  806. this.$set(this.loadData, ajaxData.act, true);
  807. let runTime = Math.round(new Date().getTime());
  808. $A.apiAjax({
  809. url: 'project/task/edit',
  810. method: 'post',
  811. data: ajaxData,
  812. complete: () => {
  813. if (this.loadRand[ajaxData.act] !== loadRand) {
  814. return;
  815. }
  816. this.$set(this.loadData, ajaxData.act, false);
  817. },
  818. error: () => {
  819. if (this.loadRand[ajaxData.act] !== loadRand) {
  820. return;
  821. }
  822. ajaxCallback(-1);
  823. alert(this.$L('网络繁忙,请稍后再试!'));
  824. },
  825. success: (res) => {
  826. if (this.loadRand[ajaxData.act] !== loadRand) {
  827. return;
  828. }
  829. runTime = Math.round(new Date().getTime()) - runTime;
  830. if (res.ret === 1) {
  831. let tempArray = cloneDeep(this.detail.subtask);
  832. this.detail = res.data;
  833. this.bakData = cloneDeep(this.detail);
  834. while (tempArray.length > 0 && tempArray[tempArray.length - 1].detail == '') {
  835. tempArray.splice(tempArray.length - 1, 1);
  836. this.detail.subtask.push({
  837. id: $A.randomString(6),
  838. uname: '',
  839. time: Math.round(new Date().getTime()/1000),
  840. status: 'unfinished',
  841. detail: '',
  842. stip: ''
  843. });
  844. }
  845. $A.triggerTaskInfoListener(ajaxData.act, res.data);
  846. $A.triggerTaskInfoChange(ajaxData.taskid);
  847. setTimeout(() => {
  848. if (ajaxCallback(1) !== false) {
  849. this.logType == '日志' && this.$refs.log.getLists(true, true);
  850. this.$Message.success(res.msg);
  851. }
  852. }, Math.max(0, 350 - runTime));
  853. } else {
  854. setTimeout(() => {
  855. ajaxCallback(0);
  856. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  857. }, Math.max(0, 350 - runTime));
  858. }
  859. }
  860. });
  861. },
  862. openProject(projectid) {
  863. try {
  864. this.visible = false;
  865. $A.app.$router.push({
  866. name: 'project-panel',
  867. params: {projectid: projectid, statistics: '', other: {}}
  868. });
  869. } catch (e) {
  870. this.visible = true;
  871. }
  872. }
  873. }
  874. }
  875. </script>
  876. <style lang="scss">
  877. .project-task-detail-window {
  878. .detail-title-box {
  879. .detail-title-input {
  880. textarea {
  881. margin: -7px 0 3px -2px;
  882. font-size: 20px;
  883. font-weight: 600;
  884. border: 2px solid #ffffff;
  885. padding: 2px;
  886. cursor: pointer;
  887. color: #172b4d;
  888. background: #ffffff;
  889. width: 100%;
  890. border-radius: 3px;
  891. resize: none;
  892. }
  893. }
  894. }
  895. .detail-subtask-input {
  896. flex: 1;
  897. border: 0;
  898. background: #ffffff;
  899. margin-left: 2px;
  900. border-bottom: 1px solid #f6f6f6;
  901. textarea {
  902. border: 0;
  903. box-shadow: none;
  904. outline: none;
  905. resize: none;
  906. min-height: auto;
  907. padding-left: 0;
  908. padding-right: 0;
  909. &:focus {
  910. color: #333333;
  911. }
  912. }
  913. &.subtask-complete {
  914. textarea {
  915. text-decoration: line-through;
  916. color: #999;
  917. }
  918. }
  919. }
  920. }
  921. </style>
  922. <style lang="scss" scoped>
  923. .project-task-detail-window {
  924. position: fixed;
  925. z-index: 1001;
  926. top: 0;
  927. left: 0;
  928. height: 100%;
  929. width: 100%;
  930. background-color: rgba(0, 0, 0, 0.5);
  931. transition: all .3s;
  932. opacity: 0;
  933. pointer-events: unset;
  934. display: flex;
  935. flex-direction: column;
  936. align-items: center;
  937. justify-content: center;
  938. &.task-detail-show {
  939. opacity: 1;
  940. }
  941. .task-detail-bg {
  942. position: absolute;
  943. top: 0;
  944. left: 0;
  945. right: 0;
  946. bottom: 0;
  947. z-index: -1;
  948. }
  949. .task-detail-main {
  950. position: relative;
  951. z-index: 1;
  952. display: flex;
  953. flex-direction: row;
  954. width: 92%;
  955. max-width: 800px;
  956. max-height: 92%;
  957. background: #ffffff;
  958. overflow: visible;
  959. border-radius: 4px;
  960. padding: 10px 20px 2px;
  961. transform: translateZ(0);
  962. .detail-left {
  963. flex: 1;
  964. padding: 0 8px;
  965. overflow: auto;
  966. .detail-h2 {
  967. color: #172b4d;
  968. font-size: 16px;
  969. display: flex;
  970. align-items: center;
  971. line-height: 26px;
  972. strong {
  973. font-size: 14px;
  974. font-weight: normal;
  975. &.link {
  976. cursor: pointer;
  977. }
  978. &.active {
  979. font-size: 16px;
  980. font-weight: bold;
  981. }
  982. }
  983. em {
  984. margin: 0 9px;
  985. width: 1px;
  986. height: 10px;
  987. background: #cccccc;
  988. }
  989. .detail-button {
  990. display: flex;
  991. flex-direction: row;
  992. align-items: center;
  993. position: absolute;
  994. right: 12px;
  995. top: 50%;
  996. transform: translate(0, -50%);
  997. &:hover {
  998. .detail-button-batch {
  999. display: inline-block;
  1000. }
  1001. }
  1002. .detail-button-btn,
  1003. .detail-button-batch {
  1004. font-size: 12px;
  1005. opacity: 0.9;
  1006. transition: all 0.3s;
  1007. margin-left: 5px;
  1008. &:hover {
  1009. opacity: 1;
  1010. }
  1011. }
  1012. .detail-button-batch {
  1013. display: none;
  1014. }
  1015. }
  1016. }
  1017. .detail-icon {
  1018. position: relative;
  1019. padding-left: 26px;
  1020. &:before {
  1021. font-family: zenicon;
  1022. font-size: 20px;
  1023. color: #42526e;
  1024. font-weight: 600;
  1025. position: absolute;
  1026. top: 0;
  1027. left: 0;
  1028. width: 26px;
  1029. height: 26px;
  1030. line-height: 26px;
  1031. }
  1032. }
  1033. .detail-title-box {
  1034. margin-top: 12px;
  1035. margin-bottom: 12px;
  1036. &:before {
  1037. content: "\E740";
  1038. }
  1039. .subtitle {
  1040. padding-top: 3px;
  1041. font-size: 12px;
  1042. color: #606266;
  1043. .project-title {
  1044. cursor: pointer;
  1045. &:hover {
  1046. color: #57a3f3;
  1047. text-decoration: underline;
  1048. }
  1049. }
  1050. }
  1051. }
  1052. .detail-desc-box {
  1053. &:before {
  1054. content: "\E75E";
  1055. }
  1056. }
  1057. .detail-text-box {
  1058. margin-bottom: 12px;
  1059. li {
  1060. color: #606266;
  1061. font-size: 14px;
  1062. line-height: 32px;
  1063. word-break: break-all;
  1064. display: flex;
  1065. &:before {
  1066. font-weight: normal;
  1067. color: #606266;
  1068. font-size: 14px;
  1069. padding-left: 4px;
  1070. line-height: 32px;
  1071. }
  1072. &.text-time {
  1073. &:before {
  1074. content: "\E706";
  1075. }
  1076. }
  1077. &.text-username {
  1078. &:before {
  1079. content: "\E903";
  1080. }
  1081. .uname-no {
  1082. display: inline-block;
  1083. color: #888888;
  1084. }
  1085. .uname-button {
  1086. font-size: 12px;
  1087. margin-left: 6px;
  1088. }
  1089. .uname-text {
  1090. line-height: 24px;
  1091. color: #666666;
  1092. font-size: 12px;
  1093. margin-left: 6px;
  1094. }
  1095. }
  1096. &.text-follower {
  1097. &:before {
  1098. content: "\E90D";
  1099. }
  1100. .ivu-tag {
  1101. padding: 0 6px;
  1102. }
  1103. .user-view-inline {
  1104. height: 20px;
  1105. line-height: 20px;
  1106. vertical-align: top;
  1107. }
  1108. }
  1109. &.text-level {
  1110. &:before {
  1111. content: "\E725";
  1112. }
  1113. }
  1114. &.text-status {
  1115. &:before {
  1116. content: "\E6AF";
  1117. }
  1118. }
  1119. > span {
  1120. white-space: nowrap;
  1121. }
  1122. > em {
  1123. margin-left: 4px;
  1124. padding-top: 5px;
  1125. line-height: 22px;
  1126. &.p1 {
  1127. color: #ed3f14;
  1128. }
  1129. &.p2 {
  1130. color: #ff9900;
  1131. }
  1132. &.p3 {
  1133. color: #19be6b;
  1134. }
  1135. &.p4 {
  1136. color: #666666;
  1137. }
  1138. &.complete {
  1139. color: #666666;
  1140. .completedate {
  1141. font-size: 12px;
  1142. padding-left: 4px;
  1143. opacity: 0.6;
  1144. }
  1145. }
  1146. &.overdue,
  1147. > em.overdue{
  1148. color: #ff0000;
  1149. }
  1150. &.unfinished {
  1151. color: #19be6b;
  1152. }
  1153. }
  1154. }
  1155. }
  1156. .detail-file-box {
  1157. &:before {
  1158. content: "\E8B9";
  1159. font-size: 16px;
  1160. padding-left: 2px;
  1161. }
  1162. }
  1163. .detail-subtask-icon {
  1164. &:before {
  1165. content: "\E819";
  1166. font-size: 16px;
  1167. padding-left: 2px;
  1168. }
  1169. }
  1170. .detail-subtask-box {
  1171. padding: 12px;
  1172. margin-bottom: 4px;
  1173. .detail-subtask-progress {
  1174. margin: 2px 0 6px;
  1175. }
  1176. .detail-subtask-item {
  1177. display: flex;
  1178. flex-direction: row;
  1179. align-items: center;
  1180. margin: 0 2px 0 -6px;
  1181. padding-top: 4px;
  1182. padding-left: 8px;
  1183. position: relative;
  1184. background-color: #ffffff;
  1185. &:hover {
  1186. .detail-subtask-right {
  1187. opacity: 1;
  1188. }
  1189. }
  1190. .detail-subtask-right {
  1191. opacity: 0;
  1192. position: absolute;
  1193. top: 50%;
  1194. right: 0;
  1195. padding: 0 6px;
  1196. transform: translate(0, -50%);
  1197. background: #ffffff;
  1198. border-radius: 3px 0 0 3px;
  1199. transition: all 0.3s;
  1200. cursor: pointer;
  1201. box-shadow: -3px 0px 3px 0px rgba(45, 45, 45, 0.1);
  1202. .detail-subtask-ricon {
  1203. &:hover {
  1204. opacity: 1;
  1205. }
  1206. display: inline-block;
  1207. opacity: 0.9;
  1208. width: 18px;
  1209. height: 26px;
  1210. line-height: 26px;
  1211. font-size: 16px;
  1212. text-align: center;
  1213. }
  1214. }
  1215. }
  1216. .detail-subtask-none {
  1217. color: #666666;
  1218. padding: 0 12px;
  1219. }
  1220. }
  1221. .detail-comment-box {
  1222. &:before {
  1223. content: "\E753";
  1224. }
  1225. }
  1226. .detail-footer-box {
  1227. border-top: 1px solid #e5e5e5;
  1228. display: flex;
  1229. flex-direction: row;
  1230. padding-top: 20px;
  1231. padding-bottom: 16px;
  1232. .comment-input {
  1233. margin-right: 12px;
  1234. }
  1235. }
  1236. }
  1237. .detail-right {
  1238. margin: 38px 0 6px;
  1239. padding-left: 12px;
  1240. overflow-x: hidden;
  1241. overflow-y: auto;
  1242. .block {
  1243. display: block;
  1244. .p1 {
  1245. color: #ed3f14;
  1246. }
  1247. .p2 {
  1248. color: #ff9900;
  1249. }
  1250. .p3 {
  1251. color: #19be6b;
  1252. }
  1253. .p4 {
  1254. color: #666666;
  1255. }
  1256. .checkmark {
  1257. margin-left: 8px;
  1258. margin-right: -8px;
  1259. }
  1260. }
  1261. .btn {
  1262. display: block;
  1263. width: 118px;
  1264. text-align: left;
  1265. margin-top: 8px;
  1266. padding-left: 10px;
  1267. padding-right: 10px;
  1268. overflow: hidden;
  1269. white-space: nowrap;
  1270. text-overflow: ellipsis;
  1271. }
  1272. }
  1273. .detail-complete {
  1274. display: inline-block;
  1275. pointer-events: none;
  1276. position: absolute;
  1277. top: 6px;
  1278. right: 23%;
  1279. font-size: 72px;
  1280. color: #19be6b;
  1281. opacity: 0.2;
  1282. z-index: 1;
  1283. }
  1284. .detail-menu {
  1285. display: none;
  1286. position: absolute;
  1287. top: 10px;
  1288. right: 64px;
  1289. text-align: right;
  1290. width: auto;
  1291. height: 38px;
  1292. z-index: 5;
  1293. align-items: center;
  1294. }
  1295. .detail-cancel {
  1296. position: absolute;
  1297. top: 10px;
  1298. right: 20px;
  1299. text-align: right;
  1300. width: auto;
  1301. height: 38px;
  1302. z-index: 5;
  1303. em {
  1304. display: inline-block;
  1305. width: 38px;
  1306. height: 38px;
  1307. cursor: pointer;
  1308. border-radius: 50%;
  1309. transform: scale(0.92);
  1310. &:after,
  1311. &:before {
  1312. position: absolute;
  1313. content: "";
  1314. top: 50%;
  1315. left: 50%;
  1316. width: 2px;
  1317. height: 20px;
  1318. background-color: #EE2321;
  1319. transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
  1320. transition: all .2s;
  1321. }
  1322. &:before {
  1323. position: absolute;
  1324. transform: translate(-50%, -50%) rotate(-45deg) scale(0.6, 1);
  1325. }
  1326. &:hover {
  1327. &:after,
  1328. &:before {
  1329. background-color: #ff0000;
  1330. transform: translate(-50%, -50%) rotate(135deg) scale(0.6, 1);
  1331. }
  1332. &:before {
  1333. background-color: #ff0000;
  1334. transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
  1335. }
  1336. }
  1337. }
  1338. }
  1339. .detail-drag-over {
  1340. position: absolute;
  1341. top: 0;
  1342. left: 0;
  1343. right: 0;
  1344. bottom: 0;
  1345. z-index: 6;
  1346. background-color: rgba(255, 255, 255, 0.78);
  1347. display: flex;
  1348. align-items: center;
  1349. justify-content: center;
  1350. border-radius: 4px;
  1351. &:before {
  1352. content: "";
  1353. position: absolute;
  1354. top: 16px;
  1355. left: 16px;
  1356. right: 16px;
  1357. bottom: 16px;
  1358. border: 2px dashed #7b7b7b;
  1359. border-radius: 12px;
  1360. }
  1361. .detail-drag-text {
  1362. padding: 12px;
  1363. font-size: 18px;
  1364. color: #666666;
  1365. }
  1366. }
  1367. }
  1368. @media (max-width: 768px) {
  1369. .task-detail-main {
  1370. padding: 10px 12px 2px;
  1371. .detail-left {
  1372. margin-top: 32px;
  1373. .detail-icon {
  1374. padding-left: 22px;
  1375. }
  1376. }
  1377. .detail-right {
  1378. transform: translate(200%, 0);
  1379. position: absolute;
  1380. top: 0;
  1381. right: 0;
  1382. bottom: 0;
  1383. margin: 0;
  1384. padding: 48px 18px;
  1385. background: #ffffff;
  1386. box-shadow: 0 1px 6px 0 rgba(32, 33, 36, 0.28);
  1387. z-index: 4;
  1388. transition: all 0.3s;
  1389. border-top-right-radius: 4px;
  1390. border-bottom-right-radius: 4px;
  1391. &.open-menu {
  1392. transform: translate(0, 0);
  1393. }
  1394. }
  1395. .detail-menu {
  1396. display: flex;
  1397. }
  1398. }
  1399. }
  1400. }
  1401. </style>