kuaifan 5 vuotta sitten
vanhempi
commit
b0822d337a

+ 104 - 5
app/Http/Controllers/Api/ProjectController.php

@@ -109,6 +109,8 @@ class ProjectController extends Controller
             foreach ($task AS $info) {
                 if ($temp['id'] == $info['labelid']) {
                     $info['overdue'] = Project::taskIsOverdue($info);
+                    $info['subtask'] = Base::string2array($info['subtask']);
+                    $info['follower'] = Base::string2array($info['follower']);
                     $taskLists[] = array_merge($info, Users::username2basic($info['username']));
                 }
             }
@@ -1062,6 +1064,7 @@ class ProjectController extends Controller
             }
         }
         //
+        $selectArray = ['project_task.*'];
         $whereRaw = null;
         $whereFunc = null;
         $whereArray = [];
@@ -1095,10 +1098,6 @@ class ProjectController extends Controller
         if (intval(Request::input('level')) > 0) {
             $whereArray[] = ['project_task.level', '=', intval(Request::input('level'))];
         }
-        if (intval(Request::input('attention')) === 1) {
-            $whereRaw.= $whereRaw ? ' AND ' : '';
-            $whereRaw.= "`username` in (select username from `" . env('DB_PREFIX') . "project_users` where `type`='关注' AND `username`='" . $user['username'] . "')";
-        }
         $archived = trim(Request::input('archived'));
         if (empty($archived)) $archived = "未归档";
         switch ($archived) {
@@ -1138,10 +1137,18 @@ class ProjectController extends Controller
         if ($projectid > 0) {
             $builder->join('project_lists', 'project_lists.id', '=', 'project_task.projectid');
         }
+        if (intval(Request::input('attention')) === 1) {
+            $builder->join('project_users', 'project_users.taskid', '=', 'project_task.id');
+            $builder->where([
+                ['project_users.type', '=', '关注'],
+                ['project_users.username', '=', $user['username']],
+            ]);
+            $selectArray[] = 'project_users.indate AS attentiondate';
+        }
         if ($whereRaw) {
             $builder->whereRaw($whereRaw);
         }
-        $lists = $builder->select(['project_task.*'])
+        $lists = $builder->select($selectArray)
             ->where($whereArray)
             ->orderByRaw($orderBy)
             ->paginate(Min(Max(Base::nullShow(Request::input('pagesize'), 10), 1), 100));
@@ -1156,6 +1163,8 @@ class ProjectController extends Controller
         }
         foreach ($lists['lists'] AS $key => $info) {
             $info['overdue'] = Project::taskIsOverdue($info);
+            $info['subtask'] = Base::string2array($info['subtask']);
+            $info['follower'] = Base::string2array($info['follower']);
             $lists['lists'][$key] = array_merge($info, Users::username2basic($info['username']));
         }
         if ($taskid > 0) {
@@ -1264,6 +1273,8 @@ class ProjectController extends Controller
             //
             $task = Base::DBC2A(DB::table('project_task')->where('id', $taskid)->first());
             $task['overdue'] = Project::taskIsOverdue($task);
+            $task['subtask'] = Base::string2array($task['subtask']);
+            $task['follower'] = Base::string2array($task['follower']);
             $task = array_merge($task, Users::username2basic($task['username']));
             return Base::retSuccess('添加成功!', $task);
         });
@@ -1286,7 +1297,10 @@ class ProjectController extends Controller
      * - unarchived: 取消归档
      * - delete: 删除任务
      * - comment: 评论
+     * - attention: 添加关注
      * @apiParam {String} [content]         内容数据
+     *
+     * @throws \Throwable
      */
     public function task__edit()
     {
@@ -1647,6 +1661,89 @@ class ProjectController extends Controller
                 break;
             }
 
+            /**
+             * 添加关注
+             */
+            case 'attention': {
+                $userArray  = explode(",", $content);
+                DB::transaction(function () use ($user, $task, $userArray) {
+                    foreach ($userArray AS $uname) {
+                        $uid = Users::username2id($uname);
+                        if (empty($uid)) {
+                            continue;
+                        }
+                        $row = Base::DBC2A(DB::table('project_users')->where([
+                            'type' => '关注',
+                            'taskid' => $task['id'],
+                            'username' => $uname,
+                        ])->lockForUpdate()->first());
+                        if (empty($row)) {
+                            DB::table('project_users')->insert([
+                                'type' => '关注',
+                                'projectid' => $task['projectid'],
+                                'taskid' => $task['id'],
+                                'isowner' => $task['username'] == $uname ? 1 : 0,
+                                'username' => $uname,
+                                'indate' => Base::time()
+                            ]);
+                            DB::table('project_log')->insert([
+                                'type' => '日志',
+                                'projectid' => $task['projectid'],
+                                'taskid' => $task['id'],
+                                'username' => $uname,
+                                'detail' => $uname == $user['username'] ? '关注任务' : '加入关注',
+                                'indate' => Base::time(),
+                                'other' => Base::array2string([
+                                    'type' => 'task',
+                                    'id' => $task['id'],
+                                    'title' => $task['title'],
+                                    'operator' => $user['username'],
+                                ])
+                            ]);
+                        }
+                    }
+                });
+                $tempRow = Base::DBC2A(DB::table('project_users')->select(['username'])->where([ 'type' => '关注', 'taskid' => $task['id'] ])->get()->pluck('username'));
+                $upArray['follower'] = Base::array2string($tempRow);
+                $message = "保存成功!";
+                break;
+            }
+
+            /**
+             * 取消关注
+             */
+            case 'unattention': {
+                $userArray  = explode(",", $content);
+                DB::transaction(function () use ($user, $task, $userArray) {
+                    foreach ($userArray AS $uname) {
+                        if (DB::table('project_users')->where([
+                            'type' => '关注',
+                            'taskid' => $task['id'],
+                            'username' => $uname,
+                        ])->delete()) {
+                            DB::table('project_log')->insert([
+                                'type' => '日志',
+                                'projectid' => $task['projectid'],
+                                'taskid' => $task['id'],
+                                'username' => $uname,
+                                'detail' => $uname == $user['username'] ? '取消关注' : '移出关注',
+                                'indate' => Base::time(),
+                                'other' => Base::array2string([
+                                    'type' => 'task',
+                                    'id' => $task['id'],
+                                    'title' => $task['title'],
+                                    'operator' => $user['username'],
+                                ])
+                            ]);
+                        }
+                    }
+                });
+                $tempRow = Base::DBC2A(DB::table('project_users')->select(['username'])->where([ 'type' => '关注', 'taskid' => $task['id'] ])->get()->pluck('username'));
+                $upArray['follower'] = Base::array2string($tempRow);
+                $message = "保存成功!";
+                break;
+            }
+
             default: {
                 return Base::retError('参数错误!');
                 break;
@@ -1666,6 +1763,8 @@ class ProjectController extends Controller
         //
         $task = array_merge($task, $upArray);
         $task['overdue'] = Project::taskIsOverdue($task);
+        $task['subtask'] = Base::string2array($task['subtask']);
+        $task['follower'] = Base::string2array($task['follower']);
         $task = array_merge($task, Users::username2basic($task['username']));
         return Base::retSuccess($message ?: '修改成功!', $task);
     }

+ 24 - 0
app/Module/Project.php

@@ -36,6 +36,30 @@ class Project
     }
 
     /**
+     * 是否在关注列表里
+     * @param int $taskid
+     * @param string $username
+     * @return array
+     */
+    public static function inAttention($taskid, $username)
+    {
+        $whereArray = [
+            'type' => '关注',
+            'projectid' => $projectid,
+            'username' => $username,
+        ];
+        if ($isowner) {
+            $whereArray['isowner'] = 1;
+        }
+        $row = Base::DBC2A(DB::table('project_users')->select(['isowner', 'indate'])->where($whereArray)->first());
+        if (empty($row)) {
+            return Base::retError('你不在项目成员内!');
+        } else {
+            return Base::retSuccess('你在项目内', $row);
+        }
+    }
+
+    /**
      * 更新项目(complete、unfinished)
      * @param int $projectid
      */

+ 16 - 2
resources/assets/js/main/components/UseridInput.vue

@@ -36,6 +36,7 @@
 <style lang="scss" scoped>
     .user-id-multiple {
         margin-bottom: 4px;
+        overflow: auto;
     }
     .user-id-input {
         display: inline-block;
@@ -153,7 +154,7 @@
             multiple: {
                 type: Boolean,
                 default: false
-            },
+            }
         },
         data () {
             return {
@@ -177,7 +178,17 @@
                         "ellipsis": true,
                         "tooltip": true,
                         render: (h, params) => {
-                            return h('span', params.row.nickname || '-');
+                            let arr = [];
+                            let username = params.row.username;
+                            let mLists = this.multipleLists.filter((item) => { return item.username == username; });
+                            if (mLists.length > 0) {
+                                arr.push(h('Icon', {
+                                    props: { type: 'md-checkmark' },
+                                    style: { marginRight: '6px', fontSize: '16px', color: '#FF5722' },
+                                }));
+                            }
+                            arr.push(h('span', params.row.nickname || '-'));
+                            return h('div', arr);
                         }
                     }, {
                         "title": "用户名",
@@ -194,6 +205,9 @@
         watch: {
             value (val) {
                 if (this.multiple) {
+                    if (!val) {
+                        this.multipleLists = [];
+                    }
                     return;
                 }
                 this.userName = $A.cloneData(val)

+ 110 - 10
resources/assets/js/main/components/project/task/detail/detail.vue

@@ -16,20 +16,26 @@
                 </div>
                 <ul class="detail-text-box">
                     <li v-if="detail.startdate > 0 && detail.enddate > 0" class="text-time detail-icon">
-                        计划时间:
+                        <span>计划时间:</span>
                         <em>{{$A.formatDate("Y-m-d H:i", detail.startdate)}} 至 {{$A.formatDate("Y-m-d H:i", detail.enddate)}}</em>
                         <em v-if="detail.overdue" class="overdue">[已超期]</em>
                     </li>
                     <li class="text-username detail-icon">
-                        负责人:
+                        <span>负责人:</span>
                         <em>{{detail.username}}</em>
                     </li>
+                    <li v-if="detail.follower.length > 0" class="text-follower detail-icon">
+                        <span>关注者:</span>
+                        <em>
+                            <Tag v-for="(fname, findex) in detail.follower" :key="findex" closable @on-close="handleTask('unattention', {username:fname,uisynch:true})">{{fname}}</Tag>
+                        </em>
+                    </li>
                     <li class="text-level detail-icon">
-                        优先级:
+                        <span>优先级:</span>
                         <em :class="`p${detail.level}`">{{levelFormt(detail.level)}}</em>
                     </li>
                     <li class="text-status detail-icon">
-                        任务状态:
+                        <span>任务状态:</span>
                         <em v-if="detail.complete" class="complete">已完成</em>
                         <em v-else class="unfinished">未完成</em>
                     </li>
@@ -68,11 +74,11 @@
                     <div slot="content">
                         <div style="width:240px">
                             选择负责人
-                            <UseridInput :projectid="detail.projectid" :nousername="detail.username" @change="handleTask('username', $event)" placeholder="输入关键词搜索" style="margin:5px 0 3px"></UseridInput>
+                            <UseridInput :projectid="detail.projectid" :nousername="detail.username" :transfer="false" @change="handleTask('usernameb', $event)" placeholder="输入关键词搜索" style="margin:5px 0 3px"></UseridInput>
                         </div>
                     </div>
                 </Poptip>
-                <Poptip ref="timeRef" placement="bottom" class="block" @on-popper-show="handleTask('opentime')">
+                <Poptip ref="timeRef" placement="bottom" class="block" @on-popper-show="handleTask('inittime')">
                     <Button :loading="!!loadData.plannedtime || !!loadData.unplannedtime" icon="md-calendar" class="btn">计划时间</Button>
                     <div slot="content">
                         <div style="width:280px">
@@ -84,13 +90,25 @@
                                 format="yyyy-MM-dd HH:mm"
                                 type="datetimerange"
                                 placement="bottom"
-                                @on-ok="handleTask('plannedtime')"
-                                @on-clear="handleTask('unplannedtime')"
+                                @on-ok="handleTask('plannedtimeb')"
+                                @on-clear="handleTask('unplannedtimeb')"
                                 style="display:block;margin:5px 0 3px"></Date-picker>
                         </div>
                     </div>
                 </Poptip>
                 <Button icon="md-attach" class="btn" @click="handleTask('fileupload')">添加附件</Button>
+                <Poptip ref="attentionRef" v-if="detail.username == myUsername" placement="bottom" class="block" @on-popper-show="() => {$set(detail, 'attentionLists', '')}">
+                    <Button :loading="!!loadData.attention" icon="md-at" class="btn">关注人</Button>
+                    <div slot="content">
+                        <div style="width:240px">
+                            选择关注人
+                            <UseridInput :multiple="true" :transfer="false" v-model="detail.attentionLists" placeholder="输入关键词搜索" style="margin:5px 0 3px"></UseridInput>
+                            <Button :loading="!!loadData.attention" :disabled="!detail.attentionLists" class="btn" type="primary" style="text-align:center;width:72px;height:28px" @click="handleTask('attention')">确 定</Button>
+                        </div>
+                    </div>
+                </Poptip>
+                <Button v-else-if="haveAttention(detail.follower)" :loading="!!loadData.unattention" icon="md-at" class="btn" @click="handleTask('unattention', {username:myUsername})">取消关注</Button>
+                <Button v-else :loading="!!loadData.attention" icon="md-at" class="btn" @click="handleTask('attentiona')">关注任务</Button>
                 <Button v-if="!detail.archived" :loading="!!loadData.archived" icon="md-filing" class="btn" @click="handleTask('archived')">归档</Button>
                 <Button v-else :loading="!!loadData.unarchived" icon="md-filing" class="btn" @click="handleTask('unarchived')">取消归档</Button>
                 <Button :loading="!!loadData.delete" icon="md-trash" class="btn" type="error" ghost @click="handleTask('deleteb')">删除</Button>
@@ -164,6 +182,8 @@
                         }
                     }]
                 },
+
+                myUsername: '',
             }
         },
         beforeCreate() {
@@ -191,7 +211,12 @@
                 }, 0)
             });
             this.bakData = $A.cloneData(this.detail);
+            this.myUsername = $A.getUserName();
             this.getTaskDetail();
+            //
+            $A.setOnUserInfoListener("components/project/task/detail", () => {
+                this.myUsername = $A.getUserName();
+            });
         },
         watch: {
             taskid() {
@@ -235,6 +260,10 @@
                 }
             },
 
+            haveAttention(follower) {
+                return follower.filter((uname) => { return uname == this.myUsername }).length > 0
+            },
+
             getTaskDetail() {
                 $A.aAjax({
                     url: 'project/task/lists',
@@ -337,6 +366,19 @@
                         ajaxData.content = act.substring(6);
                         break;
 
+                    case 'usernameb':
+                        if (!eve.username) {
+                            return;
+                        }
+                        this.$Modal.confirm({
+                            title: '修改负责人',
+                            content: '你确定修改负责人设置为“' + (eve.nickname || eve.username) + '”吗?',
+                            onOk: () => {
+                                this.handleTask('username', eve);
+                            }
+                        });
+                        return;
+
                     case 'username':
                         if (!eve.username) {
                             return;
@@ -355,7 +397,7 @@
                         };
                         break;
 
-                    case 'opentime':
+                    case 'inittime':
                         if (this.detail.startdate > 0 && this.detail.enddate > 0) {
                             this.timeValue = [$A.formatDate("Y-m-d H:i", this.detail.startdate), $A.formatDate("Y-m-d H:i", this.detail.enddate)]
                         } else {
@@ -363,16 +405,63 @@
                         }
                         return;
 
+                    case 'plannedtimeb':
+                        let temp = $A.date2string(this.timeValue, "Y-m-d H:i");
+                        this.$Modal.confirm({
+                            title: '修改计划时间',
+                            content: '你确定将任务计划时间设置为“' + temp[0] + "~" + temp[1] + '”吗?',
+                            onOk: () => {
+                                this.handleTask('plannedtime');
+                            }
+                        });
+                        return;
+
                     case 'plannedtime':
                         this.timeValue = $A.date2string(this.timeValue);
                         ajaxData.content = this.timeValue[0] + "," + this.timeValue[1];
                         this.$refs.timeRef.handleClose();
                         break;
 
+                    case 'unplannedtimeb':
+                        this.$Modal.confirm({
+                            title: '取消计划时间',
+                            content: '你确定将任务计划时间取消吗?',
+                            onOk: () => {
+                                this.handleTask('unplannedtime');
+                            }
+                        });
+                        return;
+
                     case 'unplannedtime':
                         this.$refs.timeRef.handleClose();
                         break;
 
+                    case 'attentiona':
+                        ajaxData.act = "attention";
+                        ajaxData.content = this.myUsername;
+                        break;
+
+                    case 'attention':
+                        if (!this.detail.attentionLists) {
+                            return;
+                        }
+                        ajaxData.content = this.detail.attentionLists;
+                        this.$refs.attentionRef.handleClose();
+                        break;
+
+                    case 'unattention':
+                        ajaxData.content = eve.username;
+                        if (eve.uisynch === true) {
+                            let bakFollower = $A.cloneData(this.detail.follower);
+                            this.$set(this.detail, 'follower', this.detail.follower.filter((uname) => { return uname != eve }));
+                            ajaxCallback = (res) => {
+                                if (res !== 1) {
+                                    this.$set(this.detail, 'follower', bakFollower);
+                                }
+                            };
+                        }
+                        break;
+
                     case 'deleteb':
                         this.$Modal.confirm({
                             title: this.$L('删除提示'),
@@ -579,6 +668,7 @@
                         font-size: 14px;
                         line-height: 32px;
                         word-break: break-all;
+                        display: flex;
                         &:before {
                             font-weight: normal;
                             color: #606266;
@@ -596,6 +686,11 @@
                                 content: "\E903";
                             }
                         }
+                        &.text-follower {
+                            &:before {
+                                content: "\E90D";
+                            }
+                        }
                         &.text-level {
                             &:before {
                                 content: "\E725";
@@ -606,8 +701,13 @@
                                 content: "\E6AF";
                             }
                         }
-                        em {
+                        > span {
+                            white-space: nowrap;
+                        }
+                        > em {
                             margin-left: 4px;
+                            padding-top: 5px;
+                            line-height: 22px;
                             &.p1 {
                                 color: #ed3f14;
                             }

+ 21 - 3
resources/assets/js/main/components/project/todo/attention.vue

@@ -65,20 +65,22 @@
             }, {
                 "title": "完成",
                 "minWidth": 70,
+                "align": "center",
                 render: (h, params) => {
                     return h('span', params.row.complete ? '√' : '-');
                 }
             }, {
                 "title": "归档",
                 "minWidth": 70,
+                "align": "center",
                 render: (h, params) => {
                     return h('span', params.row.archived ? '√' : '-');
                 }
             }, {
-                "title": "添加时间",
+                "title": "关注时间",
                 "width": 160,
                 render: (h, params) => {
-                    return h('span', $A.formatDate("Y-m-d H:i:s", params.row.inorder));
+                    return h('span', $A.formatDate("Y-m-d H:i:s", params.row.attentiondate));
                 }
             }];
         },
@@ -96,7 +98,8 @@
                 });
                 //
                 switch (act) {
-                    case "delete":      // 删除任务
+                    case "unattention":     // 取消关注
+                    case "delete":          // 删除任务
                         this.lists.some((task, i) => {
                             if (task.id == detail.id) {
                                 this.lists.splice(i, 1);
@@ -104,6 +107,21 @@
                             }
                         });
                         break;
+
+                    case "attention":       // 添加关注
+                        let username = $A.getUserName();
+                        if (detail.follower.filter((uname) => { return uname == username }).length > 0) {
+                            let has = false;
+                            this.lists.some((task) => {
+                                if (task.id == detail.id) {
+                                    return has = true;
+                                }
+                            });
+                            if (!has) {
+                                this.lists.unshift(detail);
+                            }
+                        }
+                        break;
                 }
             });
         },