panel.vue 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  1. <template>
  2. <div class="w-main project-panel">
  3. <v-title>{{$L('项目面板')}}</v-title>
  4. <div class="w-nav">
  5. <div class="nav-row">
  6. <div class="w-nav-left">
  7. <div class="page-nav-left">
  8. <span class="bold">{{projectDetail.title}}</span>
  9. <div v-if="loadIng > 0" class="page-nav-loading"><w-loading></w-loading></div>
  10. <div v-else class="page-nav-refresh"><em @click="getDetail(true)">{{$L('刷新')}}</em></div>
  11. </div>
  12. </div>
  13. <div class="w-nav-flex"></div>
  14. <div class="w-nav-right">
  15. <span class="ft hover" :class="{active:filtrTask!=''}">
  16. <Dropdown @on-click="(res)=>{filtrTask=res}" transfer>
  17. <Icon type="md-funnel" class="icon"/> {{$L('筛选')}}
  18. <DropdownMenu slot="list">
  19. <DropdownItem name="" :class="{'dropdown-active':filtrTask==''}">{{$L('全部任务')}}</DropdownItem>
  20. <DropdownItem name="persons" :class="{'dropdown-active':filtrTask=='persons'}">{{$L('我负责的任务')}}</DropdownItem>
  21. <DropdownItem name="follower" :class="{'dropdown-active':filtrTask=='follower'}">{{$L('我关注的任务')}}</DropdownItem>
  22. <DropdownItem name="create" :class="{'dropdown-active':filtrTask=='create'}">{{$L('我创建的任务')}}</DropdownItem>
  23. </DropdownMenu>
  24. </Dropdown>
  25. </span>
  26. <span class="m768-show-i">
  27. <Dropdown @on-click="openProjectDrawer" trigger="click" transfer>
  28. <Icon type="md-menu" size="18"/>
  29. <DropdownMenu slot="list">
  30. <DropdownItem name="lists">{{$L('列表')}}</DropdownItem>
  31. <DropdownItem name="projectGanttShow">{{$L('甘特图')}}</DropdownItem>
  32. <DropdownItem name="files">{{$L('文件')}}</DropdownItem>
  33. <DropdownItem name="logs">{{$L('动态')}}</DropdownItem>
  34. <DropdownItem name="openProjectSettingDrawer">{{$L('设置')}}</DropdownItem>
  35. </DropdownMenu>
  36. </Dropdown>
  37. </span>
  38. <span class="m768-hide-i">
  39. <span class="ft hover" @click="openProjectDrawer('lists')"><i class="ft icon">&#xE89E;</i> {{$L('列表')}}</span>
  40. <span class="ft hover" :class="{active:projectGanttShow}" @click="projectGanttShow=!projectGanttShow"><i class="ft icon">&#59141;</i> {{$L('甘特图')}}</span>
  41. <span class="ft hover" @click="openProjectDrawer('files')"><i class="ft icon">&#xE701;</i> {{$L('文件')}}</span>
  42. <span class="ft hover" @click="openProjectDrawer('logs')"><i class="ft icon">&#xE753;</i> {{$L('动态')}}</span>
  43. <span class="ft hover" @click="openProjectSettingDrawer('setting')"><i class="ft icon">&#xE7A7;</i> {{$L('设置')}}</span>
  44. </span>
  45. </div>
  46. </div>
  47. </div>
  48. <w-content>
  49. <draggable
  50. v-model="projectLabel"
  51. class="label-box"
  52. draggable=".label-draggable"
  53. :style="{visibility: projectGanttShow ? 'hidden' : 'visible'}"
  54. :animation="150"
  55. :disabled="projectSortDisabled || windowMax768"
  56. @sort="projectSort">
  57. <div
  58. v-if="projectLabel.length > 0"
  59. v-for="label in projectLabel"
  60. :key="label.id"
  61. :data-id="label.id"
  62. class="label-item label-draggable"
  63. :class="{'label-scroll': label.hasScroll === true && label.endScroll !== true}"
  64. @mouseenter="projectMouse(label)">
  65. <div class="label-body">
  66. <div class="title-box">
  67. <div v-if="label.loadIng === true" class="title-loading">
  68. <w-loading></w-loading>
  69. </div>
  70. <h2>{{label.title}}</h2>
  71. <Dropdown trigger="click" @on-click="handleLabel($event, label)" transfer>
  72. <Icon type="ios-more"/>
  73. <DropdownMenu slot="list">
  74. <Dropdown-item name="refresh">{{$L('刷新列表')}}</Dropdown-item>
  75. <Dropdown-item name="rename">{{$L('重命名')}}</Dropdown-item>
  76. <Dropdown-item name="delete">{{$L('删除')}}</Dropdown-item>
  77. </DropdownMenu>
  78. </Dropdown>
  79. </div>
  80. <ScrollerY
  81. :ref="'box_' + label.id"
  82. class="task-box"
  83. @on-scroll="projectBoxScroll($event, label)">
  84. <draggable
  85. v-model="label.taskLists"
  86. class="task-main"
  87. :class="[filtrTask ? 'filtr-' + filtrTask : '']"
  88. group="task"
  89. draggable=".task-draggable"
  90. :animation="150"
  91. :disabled="projectSortDisabled || windowMax768"
  92. :data-id="label.id"
  93. @sort="taskSort">
  94. <div v-for="task in label.taskLists"
  95. :key="task.id"
  96. :data-id="task.id"
  97. class="task-item task-draggable"
  98. :class="{'persons-item':isPersonsTask(task), 'follower-item': isFollowerTask(task), 'create-item': isCreateTask(task)}">
  99. <div class="task-shadow" :class="[
  100. 'p'+task.level,
  101. task.complete ? 'complete' : '',
  102. task.overdue ? 'overdue' : '',
  103. task.isNewtask === true ? 'newtask' : ''
  104. ]" @click="openTaskModal(task)">
  105. <div class="subtask-progress"><em :style="{width: subtaskProgress(task) + '%'}"></em></div>
  106. <div class="task-title">{{task.title}}<Icon v-if="task.desc" type="ios-list-box-outline" /></div>
  107. <div class="task-plantime"><span class="task-title-time">{{task.plantime}}</span></div>
  108. <div class="task-more">
  109. <div v-if="task.overdue" class="task-status">{{$L('已超期')}}</div>
  110. <div v-else-if="task.complete" class="task-status">{{$L('已完成')}}</div>
  111. <div v-else class="task-status">{{$L('未完成')}}</div>
  112. <div class="task-persons" :class="{'persons-more':task.persons.length > 1}">
  113. <Tooltip
  114. v-for="(person, iper) in task.persons"
  115. class="task-userimg"
  116. :key="iper"
  117. :content="person.nickname || person.username"
  118. transfer>
  119. <UserImg :info="person" class="avatar"/>
  120. </Tooltip>
  121. </div>
  122. </div>
  123. </div>
  124. </div>
  125. <div slot="footer">
  126. <project-add-task
  127. :ref="'add_' + label.id"
  128. :placeholder='`${$L("添加任务至")}"${label.title}"`'
  129. :projectid="label.projectid"
  130. :labelid="label.id"
  131. @on-add-success="addTaskSuccess($event, label)"></project-add-task>
  132. </div>
  133. </draggable>
  134. </ScrollerY>
  135. </div>
  136. <div class="label-bottom" @click="projectFocus(label)">
  137. <Icon class="label-bottom-icon" type="ios-add" />
  138. </div>
  139. </div>
  140. <div v-if="loadDetailed" slot="footer" class="label-item label-create" @click="addLabel">
  141. <div class="label-body">
  142. <div class="trigger-box ft hover"><i class="ft icon">&#xE8C8;</i>{{$L('添加一个新列表')}}</div>
  143. </div>
  144. </div>
  145. </draggable>
  146. <project-gantt v-if="projectGanttShow" @on-close="projectGanttShow=false" :projectLabel="projectLabel"></project-gantt>
  147. </w-content>
  148. <WDrawer v-model="projectDrawerShow" maxWidth="1080">
  149. <Tabs v-if="projectDrawerShow" v-model="projectDrawerTab">
  150. <TabPane :label="$L('任务列表')" name="lists">
  151. <project-task-lists :canload="projectDrawerShow && projectDrawerTab == 'lists'" :projectid="projectid" :labelLists="projectSimpleLabel"></project-task-lists>
  152. </TabPane>
  153. <TabPane :label="$L('文件列表')" name="files">
  154. <project-task-files :canload="projectDrawerShow && projectDrawerTab == 'files'" :projectid="projectid"></project-task-files>
  155. </TabPane>
  156. <TabPane :label="$L('项目动态')" name="logs">
  157. <project-task-logs :canload="projectDrawerShow && projectDrawerTab == 'logs'" :projectid="projectid"></project-task-logs>
  158. </TabPane>
  159. </Tabs>
  160. </WDrawer>
  161. <WDrawer v-model="projectSettingDrawerShow" maxWidth="1000">
  162. <Tabs v-if="projectSettingDrawerShow" v-model="projectSettingDrawerTab">
  163. <TabPane :label="$L('项目设置')" name="setting">
  164. <project-setting :canload="projectSettingDrawerShow && projectSettingDrawerTab == 'setting'" :projectid="projectid" @on-change="getDetail"></project-setting>
  165. </TabPane>
  166. <TabPane :label="$L('已归档任务')" name="archived">
  167. <project-archived :canload="projectSettingDrawerShow && projectSettingDrawerTab == 'archived'" :projectid="projectid"></project-archived>
  168. </TabPane>
  169. <TabPane :label="$L('项目统计')" name="statistics">
  170. <project-statistics ref="statistics" :canload="projectSettingDrawerShow && projectSettingDrawerTab == 'statistics'" :projectid="projectid"></project-statistics>
  171. </TabPane>
  172. <TabPane :label="$L('成员管理')" name="member">
  173. <project-users :canload="projectSettingDrawerShow && projectSettingDrawerTab == 'member'" :projectid="projectid"></project-users>
  174. </TabPane>
  175. </Tabs>
  176. </WDrawer>
  177. </div>
  178. </template>
  179. <style lang="scss">
  180. #project-panel-enter-textarea {
  181. background: transparent;
  182. background: none;
  183. outline: none;
  184. border: 0;
  185. resize: none;
  186. padding: 0;
  187. margin: 8px 0;
  188. line-height: 22px;
  189. border-radius: 0;
  190. color: rgba(0, 0, 0, 0.85);
  191. &:focus {
  192. border-color: transparent;
  193. box-shadow: none;
  194. }
  195. }
  196. </style>
  197. <style lang="scss" scoped>
  198. .project-panel {
  199. .label-box {
  200. display: flex;
  201. flex-direction: row;
  202. align-items: flex-start;
  203. justify-content: flex-start;
  204. flex-wrap: nowrap;
  205. overflow-x: auto;
  206. overflow-y: hidden;
  207. -webkit-overflow-scrolling: touch;
  208. width: 100%;
  209. height: 100%;
  210. padding: 15px;
  211. transform: translateZ(0);
  212. .label-item {
  213. flex-grow: 0;
  214. flex-shrink: 0;
  215. flex-basis: auto;
  216. position: relative;
  217. overflow: hidden;
  218. height: 100%;
  219. padding-right: 15px;
  220. &.label-create {
  221. cursor: pointer;
  222. &:hover {
  223. .trigger-box {
  224. transform: translate(0, -50%) scale(1.1);
  225. }
  226. }
  227. }
  228. &.label-scroll {
  229. &:hover {
  230. .label-bottom {
  231. transform: translate(-50%, 0);
  232. }
  233. }
  234. }
  235. .label-body {
  236. width: 300px;
  237. height: 100%;
  238. border-radius: 0.15rem;
  239. background-color: #ebecf0;
  240. overflow: hidden;
  241. position: relative;
  242. display: flex;
  243. flex-direction: column;
  244. .title-box {
  245. padding: 0 12px;
  246. font-weight: bold;
  247. color: #666666;
  248. position: relative;
  249. cursor: move;
  250. display: flex;
  251. align-items: center;
  252. width: 100%;
  253. height: 42px;
  254. .title-loading {
  255. width: 16px;
  256. height: 16px;
  257. margin-right: 6px;
  258. }
  259. h2 {
  260. flex: 1;
  261. font-size: 16px;
  262. overflow: hidden;
  263. text-overflow: ellipsis;
  264. white-space: nowrap;
  265. }
  266. i {
  267. font-weight: 500;
  268. font-size: 18px;
  269. height: 100%;
  270. line-height: 42px;
  271. width: 42px;
  272. text-align: right;
  273. cursor: pointer;
  274. }
  275. }
  276. .task-box {
  277. position: relative;
  278. flex: 1;
  279. width: 100%;
  280. padding: 0 12px 2px;
  281. transform: translateZ(0);
  282. .task-main {
  283. display: flex;
  284. flex-direction: column;
  285. &.filtr-persons {
  286. .task-item {
  287. display: none;
  288. &.persons-item {
  289. display: block;
  290. }
  291. }
  292. }
  293. &.filtr-follower {
  294. .task-item {
  295. display: none;
  296. &.follower-item {
  297. display: block;
  298. }
  299. }
  300. }
  301. &.filtr-create {
  302. .task-item {
  303. display: none;
  304. &.create-item {
  305. display: block;
  306. }
  307. }
  308. }
  309. }
  310. .task-item {
  311. width: 100%;
  312. &.task-draggable {
  313. .task-shadow {
  314. cursor: pointer;
  315. &:hover{
  316. box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.38);
  317. }
  318. }
  319. }
  320. .task-shadow {
  321. margin: 5px 0 4px;
  322. padding: 8px 10px 8px 8px;
  323. background-color: #ffffff;
  324. border-left: 2px solid #BF9F03;
  325. border-right: 0;
  326. color: #091e42;
  327. border-radius: 3px;
  328. box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
  329. transition: all 0.3s;
  330. transform: scale(1);
  331. &.p1 {
  332. border-left-color: #ff0000;
  333. }
  334. &.p2 {
  335. border-left-color: #BB9F35;
  336. }
  337. &.p3 {
  338. border-left-color: #449EDD;
  339. }
  340. &.p4 {
  341. border-left-color: #84A83B;
  342. }
  343. &.complete {
  344. border-left-color: #c1c1c1;
  345. .task-title {
  346. color: #666666;
  347. text-decoration: line-through;
  348. }
  349. .task-more {
  350. .task-status {
  351. color: #666666;
  352. }
  353. }
  354. }
  355. &.overdue {
  356. .task-title {
  357. font-weight: bold;
  358. }
  359. .task-more {
  360. .task-status {
  361. color: #ff0000;
  362. }
  363. }
  364. }
  365. &.newtask {
  366. transform: scale(1.5);
  367. }
  368. .task-title {
  369. font-size: 12px;
  370. color: #091e42;
  371. word-break: break-all;
  372. .ivu-icon {
  373. font-size: 14px;
  374. color: #afafaf;
  375. vertical-align: top;
  376. padding: 2px 4px;
  377. transform: scale(0.94);
  378. }
  379. }
  380. .task-more {
  381. min-height: 30px;
  382. display: flex;
  383. align-items: flex-end;
  384. .task-status {
  385. color: #19be6b;
  386. font-size: 12px;
  387. flex: 1;
  388. }
  389. .task-persons {
  390. max-width: 150px;
  391. &.persons-more {
  392. text-align: right;
  393. .task-userimg {
  394. width: 20px;
  395. height: 20px;
  396. margin-left: 4px;
  397. margin-top: 4px;
  398. .avatar {
  399. width: 20px;
  400. height: 20px;
  401. font-size: 12px;
  402. line-height: 20px;
  403. }
  404. }
  405. }
  406. .task-userimg {
  407. width: 26px;
  408. height: 26px;
  409. vertical-align: bottom;
  410. .avatar {
  411. width: 26px;
  412. height: 26px;
  413. font-size: 14px;
  414. line-height: 26px;
  415. border-radius: 13px;
  416. }
  417. }
  418. }
  419. }
  420. .subtask-progress {
  421. position: absolute;
  422. top: 0;
  423. left: 0;
  424. width: 100%;
  425. height: 100%;
  426. z-index: -1;
  427. border-radius: 0 3px 3px 0;
  428. overflow: hidden;
  429. pointer-events: none;
  430. em {
  431. display: block;
  432. height: 100%;
  433. background-color: rgba(3, 150, 242, 0.07);
  434. }
  435. }
  436. }
  437. }
  438. }
  439. .trigger-box {
  440. text-align: center;
  441. font-size: 16px;
  442. color: #666;
  443. width: 100%;
  444. position: absolute;
  445. top: 50%;
  446. transform: translate(0, -50%) scale(1);
  447. transition: all 0.3s;
  448. }
  449. }
  450. .label-bottom {
  451. position: absolute;
  452. left: 50%;
  453. bottom: 14px;
  454. z-index: 1;
  455. width: 36px;
  456. height: 36px;
  457. border-radius: 50%;
  458. background-color: #2db7f5;
  459. display: flex;
  460. align-items: center;
  461. justify-content: center;
  462. transition: transform 0.2s;
  463. transform: translate(-50%, 200%);
  464. cursor: pointer;
  465. .label-bottom-icon {
  466. color: #ffffff;
  467. font-size: 36px;
  468. }
  469. }
  470. }
  471. }
  472. }
  473. </style>
  474. <script>
  475. import draggable from 'vuedraggable'
  476. import WContent from "../../components/WContent";
  477. import ProjectAddTask from "../../components/project/task/add";
  478. import ProjectTaskLists from "../../components/project/task/lists";
  479. import ProjectTaskFiles from "../../components/project/task/files";
  480. import ProjectTaskLogs from "../../components/project/task/logs";
  481. import ProjectArchived from "../../components/project/archived";
  482. import ProjectUsers from "../../components/project/users";
  483. import ProjectStatistics from "../../components/project/statistics";
  484. import WDrawer from "../../components/iview/WDrawer";
  485. import ProjectGantt from "../../components/project/gantt/index";
  486. import ProjectSetting from "../../components/project/setting";
  487. import ScrollerY from "../../../_components/ScrollerY";
  488. import orderBy from "lodash/orderBy";
  489. export default {
  490. components: {
  491. ScrollerY,
  492. ProjectSetting,
  493. ProjectGantt,
  494. WDrawer,
  495. ProjectStatistics,
  496. ProjectUsers,
  497. ProjectArchived,
  498. ProjectTaskLogs,
  499. ProjectTaskFiles, ProjectTaskLists, ProjectAddTask, draggable, WContent},
  500. data () {
  501. return {
  502. loadIng: 0,
  503. loadDetailed: false,
  504. projectid: 0,
  505. projectDetail: {},
  506. projectLabel: [],
  507. projectSimpleLabel: [],
  508. projectSortData: '',
  509. projectSortDisabled: false,
  510. projectDrawerShow: false,
  511. projectDrawerTab: 'lists',
  512. projectSettingDrawerShow: false,
  513. projectSettingDrawerTab: 'setting',
  514. projectGanttShow: false,
  515. filtrTask: '',
  516. routeName: '',
  517. }
  518. },
  519. mounted() {
  520. this.routeName = this.$route.name;
  521. $A.setOnTaskInfoListener('pages/project-panel',(act, detail) => {
  522. if (detail.projectid != this.projectid) {
  523. return;
  524. }
  525. //
  526. switch (act) {
  527. case 'addlabel': // 添加分类
  528. let tempLists = this.projectLabel.filter((res) => { return res.id == detail.labelid });
  529. if (tempLists.length == 0) {
  530. this.projectLabel.push(Object.assign(detail, {id: detail.labelid}));
  531. this.projectSortData = this.getProjectSort();
  532. }
  533. return;
  534. case 'deletelabel': // 删除分类
  535. this.projectLabel.some((label, index) => {
  536. if (label.id == detail.labelid) {
  537. this.projectLabel.splice(index, 1);
  538. this.projectSortData = this.getProjectSort();
  539. return true;
  540. }
  541. });
  542. return;
  543. case 'deleteproject': // 删除项目
  544. return;
  545. case "labelsort": // 调整分类排序
  546. case "tasksort": // 调整任务排序
  547. if (detail.__modifyUsername != this.usrName) {
  548. if (this.routeName == this.$route.name) {
  549. this.$Modal.confirm({
  550. title: this.$L("更新提示"),
  551. content: this.$L('团队成员(%)调整了%,<br/>更新时间:%。<br/><br/>点击【确定】加载最新数据。', detail.nickname, this.$L(act == 'labelsort' ? '分类排序' : '任务排序'), $A.formatDate("Y-m-d H:i:s", detail.time)),
  552. onOk: () => {
  553. this.getDetail(true);
  554. }
  555. });
  556. } else {
  557. this.getDetail(true);
  558. }
  559. }
  560. return;
  561. }
  562. //
  563. this.projectLabel.forEach((label) => {
  564. label.taskLists.some((task, i) => {
  565. if (task.id == detail.id) {
  566. label.taskLists.splice(i, 1, detail);
  567. return true;
  568. }
  569. });
  570. });
  571. //
  572. switch (act) {
  573. case "delete": // 删除任务
  574. case "archived": // 归档
  575. this.projectLabel.forEach((label) => {
  576. label.taskLists.some((task, i) => {
  577. if (task.id == detail.id) {
  578. label.taskLists.splice(i, 1,);
  579. return true;
  580. }
  581. });
  582. });
  583. this.projectSortData = this.getProjectSort();
  584. break;
  585. case "create": // 创建任务
  586. this.projectLabel.some((label) => {
  587. if (label.id == detail.labelid) {
  588. let tempLists = label.taskLists.filter((res) => { return res.id == detail.id });
  589. if (tempLists.length == 0) {
  590. detail.isNewtask = true;
  591. if (detail.insertbottom) {
  592. label.taskLists.push(detail);
  593. } else {
  594. label.taskLists.unshift(detail);
  595. }
  596. this.$nextTick(() => {
  597. this.$set(detail, 'isNewtask', false);
  598. });
  599. }
  600. return true;
  601. }
  602. });
  603. break;
  604. case "unarchived": // 取消归档
  605. this.projectLabel.forEach((label) => {
  606. if (label.id == detail.labelid) {
  607. let index = label.taskLists.length;
  608. label.taskLists.some((task, i) => {
  609. if (detail.inorder > task.inorder || (detail.inorder == task.inorder && detail.id > task.id)) {
  610. index = i;
  611. return true;
  612. }
  613. });
  614. label.taskLists.splice(index, 0, detail);
  615. }
  616. });
  617. this.projectSortData = this.getProjectSort();
  618. break;
  619. case "complete": // 标记完成
  620. case "unfinished": // 标记未完成
  621. this.taskNewSort();
  622. break;
  623. }
  624. }, true);
  625. },
  626. activated() {
  627. this.projectid = this.$route.params.projectid;
  628. if (typeof this.$route.params.other === "object") {
  629. this.$set(this.projectDetail, 'title', $A.getObject(this.$route.params.other, 'title'));
  630. }
  631. if (this.$route.params.statistics === '已完成') {
  632. this.projectSettingDrawerTab = 'statistics';
  633. this.projectSettingDrawerShow = true;
  634. this.$nextTick(() => {
  635. this.$refs.statistics.setTaskType('已完成');
  636. });
  637. }
  638. },
  639. deactivated() {
  640. if ($A.getToken() === false) {
  641. this.projectid = 0;
  642. }
  643. this.projectGanttShow = false;
  644. this.projectDrawerShow = false;
  645. this.projectSettingDrawerShow = false;
  646. },
  647. watch: {
  648. projectid(val) {
  649. if ($A.runNum(val) <= 0) {
  650. return;
  651. }
  652. this.projectDetail = {};
  653. this.projectLabel = [];
  654. this.projectSimpleLabel = [];
  655. this.getDetail();
  656. },
  657. '$route' (To) {
  658. if (To.name == 'project-panel') {
  659. this.projectid = To.params.projectid;
  660. }
  661. }
  662. },
  663. methods: {
  664. getDetail(successTip) {
  665. this.loadIng++;
  666. $A.apiAjax({
  667. url: 'project/detail',
  668. data: {
  669. projectid: this.projectid,
  670. },
  671. complete: () => {
  672. this.loadIng--;
  673. this.loadDetailed = true;
  674. },
  675. error: () => {
  676. this.goBack({name:'project'});
  677. alert(this.$L('网络繁忙,请稍后再试!'));
  678. },
  679. success: (res) => {
  680. if (res.ret === 1) {
  681. this.projectLabel = res.data.label;
  682. this.taskNewSort();
  683. this.projectDetail = res.data.project;
  684. this.projectSimpleLabel = res.data.simpleLabel;
  685. this.projectSortData = this.getProjectSort();
  686. if (successTip === true) {
  687. this.$Message.success(this.$L('刷新成功!'));
  688. }
  689. } else {
  690. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  691. }
  692. }
  693. });
  694. },
  695. getProjectSort() {
  696. let sortData = "",
  697. taskData = "";
  698. this.projectLabel.forEach((label) => {
  699. taskData = "";
  700. label.taskLists.forEach((task) => {
  701. if (taskData) taskData+= "-";
  702. taskData+= task.id;
  703. });
  704. if (sortData) sortData+= ";";
  705. sortData+= label.id + ":" + taskData;
  706. });
  707. return sortData;
  708. },
  709. getLabelSort() {
  710. },
  711. handleLabel(event, labelDetail) {
  712. switch (event) {
  713. case 'refresh': {
  714. this.refreshLabel(labelDetail);
  715. break;
  716. }
  717. case 'rename': {
  718. this.renameLabel(labelDetail);
  719. break;
  720. }
  721. case 'delete': {
  722. this.deleteLabel(labelDetail);
  723. break;
  724. }
  725. }
  726. },
  727. refreshLabel(item) {
  728. this.$set(item, 'loadIng', true);
  729. $A.apiAjax({
  730. url: 'project/task/lists',
  731. data: {
  732. projectid: this.projectid,
  733. labelid: item.id,
  734. },
  735. complete: () => {
  736. this.$set(item, 'loadIng', false);
  737. },
  738. error: () => {
  739. window.location.reload();
  740. },
  741. success: (res) => {
  742. if (res.ret === 1) {
  743. this.$set(item, 'taskLists', res.data.lists);
  744. } else {
  745. window.location.reload();
  746. }
  747. }
  748. });
  749. },
  750. renameLabel(item) {
  751. this.renameValue = "";
  752. this.$Modal.confirm({
  753. render: (h) => {
  754. return h('div', [
  755. h('div', {
  756. style: {
  757. fontSize: '16px',
  758. fontWeight: '500',
  759. marginBottom: '20px',
  760. }
  761. }, this.$L('重命名列表')),
  762. h('Input', {
  763. props: {
  764. value: this.renameValue,
  765. autofocus: true,
  766. placeholder: this.$L('请输入新的列表名称')
  767. },
  768. on: {
  769. input: (val) => {
  770. this.renameValue = val;
  771. }
  772. }
  773. })
  774. ])
  775. },
  776. loading: true,
  777. onOk: () => {
  778. if (this.renameValue) {
  779. this.$set(item, 'loadIng', true);
  780. let title = this.renameValue;
  781. $A.apiAjax({
  782. url: 'project/label/rename',
  783. data: {
  784. projectid: this.projectid,
  785. labelid: item.id,
  786. title: title,
  787. },
  788. complete: () => {
  789. this.$set(item, 'loadIng', false);
  790. },
  791. error: () => {
  792. this.$Modal.remove();
  793. alert(this.$L('网络繁忙,请稍后再试!'));
  794. },
  795. success: (res) => {
  796. this.$Modal.remove();
  797. this.$set(item, 'title', title);
  798. setTimeout(() => {
  799. if (res.ret === 1) {
  800. this.$Message.success(res.msg);
  801. } else {
  802. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  803. }
  804. }, 350);
  805. }
  806. });
  807. } else {
  808. this.$Modal.remove();
  809. }
  810. },
  811. });
  812. },
  813. deleteLabel(item) {
  814. let redTip = item.taskLists.length > 0 ? ('<div style="color:red;font-weight:500">' + this.$L('注:将同时删除列表下所有任务') + '</div>') : '';
  815. this.$Modal.confirm({
  816. title: this.$L('删除列表'),
  817. content: '<div>' + this.$L('你确定要删除此列表吗?') + '</div>' + redTip,
  818. loading: true,
  819. onOk: () => {
  820. $A.apiAjax({
  821. url: 'project/label/delete',
  822. data: {
  823. projectid: this.projectid,
  824. labelid: item.id,
  825. },
  826. error: () => {
  827. this.$Modal.remove();
  828. alert(this.$L('网络繁忙,请稍后再试!'));
  829. },
  830. success: (res) => {
  831. this.$Modal.remove();
  832. this.projectLabel.some((label, index) => {
  833. if (label.id == item.id) {
  834. this.projectLabel.splice(index, 1);
  835. this.projectSortData = this.getProjectSort();
  836. return true;
  837. }
  838. });
  839. setTimeout(() => {
  840. if (res.ret === 1) {
  841. this.$Message.success(res.msg);
  842. $A.triggerTaskInfoListener('deletelabel', {labelid: item.id, projectid: item.projectid});
  843. } else {
  844. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg });
  845. }
  846. }, 350);
  847. }
  848. });
  849. }
  850. });
  851. },
  852. addLabel() {
  853. this.labelValue = "";
  854. this.$Modal.confirm({
  855. render: (h) => {
  856. return h('div', [
  857. h('div', {
  858. style: {
  859. fontSize: '16px',
  860. fontWeight: '500',
  861. marginBottom: '20px',
  862. }
  863. }, this.$L('添加列表')),
  864. h('Input', {
  865. props: {
  866. value: this.labelValue,
  867. autofocus: true,
  868. placeholder: this.$L('请输入列表名称')
  869. },
  870. on: {
  871. input: (val) => {
  872. this.labelValue = val;
  873. }
  874. }
  875. })
  876. ])
  877. },
  878. loading: true,
  879. onOk: () => {
  880. if (this.labelValue) {
  881. let data = {
  882. projectid: this.projectid,
  883. title: this.labelValue
  884. };
  885. $A.apiAjax({
  886. url: 'project/label/add',
  887. data: data,
  888. error: () => {
  889. this.$Modal.remove();
  890. alert(this.$L('网络繁忙,请稍后再试!'));
  891. },
  892. success: (res) => {
  893. this.$Modal.remove();
  894. this.projectLabel.push(res.data);
  895. this.projectSortData = this.getProjectSort();
  896. $A.triggerTaskInfoListener('addlabel', Object.assign(data, {labelid: res.data.id}));
  897. setTimeout(() => {
  898. if (res.ret === 1) {
  899. this.$Message.success(res.msg);
  900. } else {
  901. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  902. }
  903. }, 350);
  904. }
  905. });
  906. } else {
  907. this.$Modal.remove();
  908. }
  909. },
  910. });
  911. },
  912. addTaskSuccess(taskDetail, label) {
  913. if (label.taskLists instanceof Array) {
  914. taskDetail.isNewtask = true;
  915. if (taskDetail.insertbottom) {
  916. label.taskLists.push(taskDetail);
  917. } else {
  918. label.taskLists.unshift(taskDetail);
  919. }
  920. this.$nextTick(() => {
  921. this.$set(taskDetail, 'isNewtask', false);
  922. });
  923. } else {
  924. this.refreshLabel(label);
  925. }
  926. },
  927. openProjectDrawer(tab) {
  928. if (tab == 'projectGanttShow') {
  929. this.projectGanttShow = !this.projectGanttShow;
  930. return;
  931. } else if (tab == 'openProjectSettingDrawer') {
  932. this.openProjectSettingDrawer('setting')
  933. return;
  934. }
  935. this.projectDrawerTab = tab;
  936. this.projectDrawerShow = true;
  937. },
  938. openProjectSettingDrawer(tab) {
  939. this.projectSettingDrawerTab = tab;
  940. this.projectSettingDrawerShow = true;
  941. },
  942. projectSortUpdate(isLabel) {
  943. let oldSort = this.projectSortData;
  944. let newSort = this.getProjectSort();
  945. console.log(oldSort,newSort);
  946. if (oldSort == newSort) {
  947. return;
  948. }
  949. this.projectSortData = newSort;
  950. this.projectSortDisabled = true;
  951. this.loadIng++;
  952. $A.apiAjax({
  953. url: 'project/sort',
  954. data: {
  955. projectid: this.projectid,
  956. oldsort: oldSort,
  957. newsort: newSort,
  958. label: isLabel === true ? 1 : 0
  959. },
  960. complete: () => {
  961. this.projectSortDisabled = false;
  962. this.loadIng--;
  963. },
  964. error: () => {
  965. this.getDetail();
  966. alert(this.$L('网络繁忙,请稍后再试!'));
  967. },
  968. success: (res) => {
  969. if (res.ret === 1) {
  970. this.projectLabel.forEach((label) => {
  971. let length = label.taskLists.length;
  972. label.taskLists.forEach((task, index) => {
  973. task.inorder = length - index;
  974. });
  975. });
  976. this.taskNewSort();
  977. //
  978. this.$Message.success(res.msg);
  979. $A.triggerTaskInfoListener(isLabel ? 'labelsort' : 'tasksort', { projectid: this.projectid, nickname: $A.getNickName(), time: Math.round(new Date().getTime()/1000) });
  980. } else {
  981. this.getDetail();
  982. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  983. }
  984. }
  985. });
  986. },
  987. projectSort(e) {
  988. let newIndex = e.newIndex;
  989. let oldIndex = e.oldIndex;
  990. let id = e.item.dataset.id;
  991. this.projectSortDisabled = true;
  992. this.loadIng++;
  993. $A.apiAjax({
  994. url: 'project/labelsort',
  995. data: {
  996. projectid: this.projectid,
  997. newIndex: newIndex,
  998. oldIndex: oldIndex,
  999. id: id
  1000. },
  1001. complete: () => {
  1002. this.projectSortDisabled = false;
  1003. this.loadIng--;
  1004. },
  1005. error: () => {
  1006. this.getDetail();
  1007. alert(this.$L('网络繁忙,请稍后再试!'));
  1008. },
  1009. success: (res) => {
  1010. if (res.ret === 1) {
  1011. this.$Message.success(res.msg);
  1012. $A.triggerTaskInfoListener('labelsort', { projectid: this.projectid, nickname: $A.getNickName(), time: Math.round(new Date().getTime()/1000) });
  1013. } else {
  1014. this.getDetail();
  1015. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  1016. }
  1017. }
  1018. });
  1019. },
  1020. taskSort(e){
  1021. console.log(e)
  1022. let oldSort = this.projectSortData;
  1023. let newSort = this.getProjectSort();
  1024. let newIndex = e.newIndex;
  1025. let oldIndex = e.oldIndex;
  1026. let fromid = e.from.dataset.id;
  1027. let toid = e.to.dataset.id;
  1028. let id = e.item.dataset.id;
  1029. if (oldSort == newSort) {
  1030. return;
  1031. }
  1032. this.projectSortData = newSort;
  1033. this.projectSortDisabled = true;
  1034. this.loadIng++;
  1035. $A.apiAjax({
  1036. url: 'project/tasksort',
  1037. data: {
  1038. projectid: this.projectid,
  1039. newIndex: newIndex,
  1040. oldIndex: oldIndex,
  1041. fromid: fromid,
  1042. toid: toid,
  1043. id: id
  1044. },
  1045. complete: () => {
  1046. this.projectSortDisabled = false;
  1047. this.loadIng--;
  1048. },
  1049. error: () => {
  1050. this.getDetail();
  1051. alert(this.$L('网络繁忙,请稍后再试!'));
  1052. },
  1053. success: (res) => {
  1054. if (res.ret === 1) {
  1055. this.projectLabel.forEach((label) => {
  1056. if(label.id == res.data.fromid){
  1057. label.taskLists.forEach((task, index) => {
  1058. task.inorder = res.data.from_res[task.id].inorder;
  1059. });
  1060. }
  1061. if(label.id == res.data.toid){
  1062. label.taskLists.forEach((task, index) => {
  1063. task.inorder = res.data.to_res[task.id].inorder;
  1064. });
  1065. }
  1066. });
  1067. this.taskNewSort();
  1068. this.$Message.success(res.msg);
  1069. $A.triggerTaskInfoListener('tasksort', { projectid: this.projectid, nickname: $A.getNickName(), time: Math.round(new Date().getTime()/1000) });
  1070. } else {
  1071. this.getDetail();
  1072. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  1073. }
  1074. }
  1075. });
  1076. },
  1077. projectMouse(label) {
  1078. let hasScroll = false;
  1079. let el = this.$refs['box_' + label.id]
  1080. if (el && el.length > 0) {
  1081. el = el[0].$el;
  1082. hasScroll = el.scrollHeight > el.offsetHeight;
  1083. }
  1084. this.$set(label, 'hasScroll', hasScroll)
  1085. },
  1086. projectBoxScroll(e, label) {
  1087. this.$set(label, 'endScroll', e.scrollE < 50)
  1088. },
  1089. projectFocus(label) {
  1090. let el = this.$refs['add_' + label.id];
  1091. if (el && el.length > 0) {
  1092. el[0].setFocus();
  1093. }
  1094. el = this.$refs['box_' + label.id];
  1095. if (el && el.length > 0) {
  1096. el[0].scrollToBottom(false);
  1097. }
  1098. },
  1099. subtaskProgress(task) {
  1100. const {subtask, complete} = task;
  1101. if (!subtask || subtask.length === 0) {
  1102. return complete ? 100 : 0;
  1103. }
  1104. const completeLists = subtask.filter((item) => { return item.status == 'complete'});
  1105. return parseFloat(((completeLists.length / subtask.length) * 100).toFixed(2));
  1106. },
  1107. openTaskModal(taskDetail) {
  1108. this.taskDetail(taskDetail);
  1109. },
  1110. taskNewSort() {
  1111. this.$nextTick(() => {
  1112. this.projectLabel.forEach((item) => { item.taskLists = this.taskReturnNewSort(item.taskLists) });
  1113. })
  1114. },
  1115. taskReturnNewSort(lists) {
  1116. let tmpLists = orderBy(lists, ['complete', 'inorder'], ['asc', 'desc']);
  1117. let array = [];
  1118. array.unshift(...tmpLists.filter(({complete}) => complete ));
  1119. array.unshift(...tmpLists.filter(({complete}) => !complete ));
  1120. return array;
  1121. },
  1122. isPersonsTask(task) {
  1123. return task.persons && !!task.persons.find(({username}) => username == this.usrInfo.username);
  1124. },
  1125. isFollowerTask(task) {
  1126. return task.follower && task.follower.indexOf(this.usrInfo.username) !== -1;
  1127. },
  1128. isCreateTask(task) {
  1129. return task.createuser == this.usrInfo.username;
  1130. }
  1131. },
  1132. }
  1133. </script>