kuaifan пре 5 година
родитељ
комит
ed473dda4b

+ 22 - 8
app/Http/Controllers/Api/ProjectController.php

@@ -1584,8 +1584,8 @@ class ProjectController extends Controller
     /**
      * 项目动态-列表
      *
-     * @apiParam {Number} projectid             项目ID
-     * @apiParam {Number} [taskid]              任务ID
+     * @apiParam {Number} [projectid]           项目ID
+     * @apiParam {Number} [taskid]              任务ID(如果项目ID为空时此参必须赋值且任务必须是自己负责人)
      * @apiParam {String} [username]            用户名
      * @apiParam {Number} [page]                当前页,默认:1
      * @apiParam {Number} [pagesize]            每页显示数量,默认:20,最大:100
@@ -1600,15 +1600,29 @@ class ProjectController extends Controller
         }
         //
         $projectid = intval(Request::input('projectid'));
-        $inRes = Project::inThe($projectid, $user['username']);
-        if (Base::isError($inRes)) {
-            return $inRes;
+        if ($projectid > 0) {
+            $inRes = Project::inThe($projectid, $user['username']);
+            if (Base::isError($inRes)) {
+                return $inRes;
+            }
         }
         //
+        $taskid = intval(Request::input('taskid'));
         $whereArray = [];
-        $whereArray[] = ['projectid', '=', $projectid];
-        if (intval(Request::input('taskid')) > 0) {
-            $whereArray[] = ['taskid', '=', intval(Request::input('taskid'))];
+        if ($projectid > 0) {
+            $whereArray[] = ['projectid', '=', $projectid];
+            if ($taskid > 0) {
+                $whereArray[] = ['taskid', '=', $taskid];
+            }
+        } else {
+            if ($taskid < 0) {
+                return Base::retError('参数错误!');
+            }
+            $count = DB::table('project_task')->where([ 'id' => $taskid, 'username' => $user['username']])->count();
+            if ($count <= 0) {
+                return Base::retError('你不是任务负责人!');
+            }
+            $whereArray[] = ['taskid', '=', $taskid];
         }
         if (trim(Request::input('username'))) {
             $whereArray[] = ['username', '=', trim(Request::input('username'))];

+ 3 - 0
resources/assets/js/main/app.js

@@ -20,6 +20,9 @@ Vue.component('VTitle', Title);
 Vue.component('UseridInput', UseridInput);
 Vue.component('sreachTitle', sreachTitle);
 
+import TaskDetail from './components/project/task/detail'
+Vue.prototype.taskDetail = TaskDetail;
+
 const router = new VueRouter({routes});
 
 //进度条配置

+ 4 - 1
resources/assets/js/main/components/DrawerTabsContainer.vue

@@ -50,7 +50,10 @@
         },
         computed: {
             myStyle() {
-                const {calculateHeight} = this;
+                const {calculateHeight, idDrawerTabs} = this;
+                if (!idDrawerTabs) {
+                    return {};
+                }
                 return {
                     height: Math.max(0, calculateHeight - 16) + 'px'
                 }

+ 18 - 3
resources/assets/js/main/components/project/task/add.vue

@@ -1,7 +1,7 @@
 <template>
     <div class="task-input-box">
         <div v-if="!addText" class="input-placeholder">
-            <Icon type="md-create" size="18"/>&nbsp;{{placeholder}}
+            <Icon type="md-create" size="18"/>&nbsp;{{addFocus?`输入任务,回车即可保存`:placeholder}}
         </div>
         <div class="input-enter">
             <Input
@@ -13,7 +13,8 @@
                 @on-focus="addFocus=true"
                 @on-blur="addFocus=false"
                 :autosize="{ minRows: 1, maxRows: 6 }"
-                :maxlength="255"/>
+                :maxlength="255"
+                @on-keydown="addKeydown"/>
             <div v-if="!!addText" class="input-enter-module">
                 <Tooltip content="重要且紧急" placement="bottom" transfer><div @click="addLevel=1" class="enter-module-icon p1"><Icon v-if="addLevel=='1'" type="md-checkmark" /></div></Tooltip>
                 <Tooltip content="重要不紧急" placement="bottom" transfer><div @click="addLevel=2" class="enter-module-icon p2"><Icon v-if="addLevel=='2'" type="md-checkmark" /></div></Tooltip>
@@ -195,13 +196,17 @@
                 this.addUserimg = user.userimg;
             },
             clickAdd() {
+                let addText = this.addText.trim();
+                if ($A.count(addText) == 0 || this.loadIng > 0) {
+                    return;
+                }
                 this.loadIng++;
                 $A.aAjax({
                     url: 'project/task/add',
                     data: {
                         projectid: this.projectid,
                         labelid: this.labelid,
-                        title: this.addText,
+                        title: addText,
                         level: this.addLevel,
                         username: this.addUsername,
                     },
@@ -222,6 +227,16 @@
                         }
                     }
                 });
+            },
+            addKeydown(e) {
+                e = e || event;
+                if (e.keyCode == 13) {
+                    if (e.shiftKey) {
+                        return;
+                    }
+                    this.clickAdd();
+                    e.preventDefault();
+                }
             }
         }
     }

+ 366 - 0
resources/assets/js/main/components/project/task/detail/detail.vue

@@ -0,0 +1,366 @@
+<template>
+    <div class="project-task-detail-window" :class="{'task-detail-show': visible}">
+        <div class="task-detail-main">
+            <div class="detail-left">
+                <div class="detail-title-box detail-icon">
+                    <input v-model="detail.title" type="text" maxlength="60">
+                    <div class="time">
+                        <span class="z-nick">{{detail.createuser}}</span>
+                        创建于:
+                        <span>{{$A.formatDate("Y-m-d H:i:s", detail.indate)}}</span>
+                    </div>
+                </div>
+                <div class="detail-desc-box detail-icon">
+                    <div class="detail-h2"><strong>描述</strong></div>
+                    <textarea placeholder="添加详细描述..."></textarea>
+                </div>
+                <ul class="detail-text-box">
+                    <li class="text-username detail-icon">
+                        负责人:
+                        <em>{{detail.username}}</em>
+                    </li>
+                    <li class="text-level detail-icon">
+                        优先级:
+                        <em :class="`p${detail.level}`">P{{detail.level}}</em>
+                    </li>
+                    <li class="text-status detail-icon">
+                        任务状态:
+                        <em v-if="detail.overdue" class="overdue">已超期</em>
+                        <em v-else-if="detail.complete" class="complete">已完成</em>
+                        <em v-else class="unfinished">未完成</em>
+                    </li>
+                </ul>
+                <div class="detail-h2 detail-comment-box detail-icon"><strong>评论</strong><em></em><strong>操作记录</strong></div>
+                <div class="detail-log-box">
+                    <project-task-logs :projectid="detail.projectid" :taskid="detail.taskid" :pagesize="5"></project-task-logs>
+                </div>
+                <div class="detail-footer-box">
+                    <Input class="comment-input" v-model="commentText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="commentKeydown" placeholder="输入评论,Enter发表评论,Shift+Enter换行" />
+                    <Button type="primary">评 论</Button>
+                </div>
+            </div>
+            <div class="detail-right">
+                <div class="cancel"><em @click="visible=false"></em></div>
+                <div class="btn"><i class="ft icon">&#xE6CB;</i>标记已完成</div>
+                <div class="btn"><i class="ft icon">&#xE7AD;</i>优先级</div>
+                <div class="btn"><i class="ft icon">&#xE6FD;</i>负责人</div>
+                <div class="btn"><i class="ft icon">&#xE706;</i>计划时间</div>
+                <div class="btn"><i class="ft icon">&#xE701;</i>附件</div>
+                <div class="btn"><i class="ft icon">&#xE85F;</i>归档</div>
+                <div class="btn remove"><i class="ft icon">&#xE6FB;</i>删除</div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    import ProjectTaskLogs from "../logs";
+    export default {
+        components: {ProjectTaskLogs},
+        data() {
+            return {
+                visible: false,
+                taskid: 0,
+                detail: {},
+                commentText: '',
+            }
+        },
+        beforeCreate() {
+            let doms = document.querySelectorAll('.project-task-detail-window');
+            for (let i = 0; i < doms.length; ++i) {
+                if (doms[i].parentNode != null) doms[i].parentNode.removeChild(doms[i]);
+            }
+        },
+        mounted() {
+            this.$nextTick(() => {
+                let dom = this.$el;
+                if (parseInt(this.taskid) === 0) {
+                    if (dom.parentNode != null) dom.parentNode.removeChild(dom);
+                    return;
+                }
+                //
+                dom.addEventListener('transitionend', () => {
+                    if (dom !== null && dom.parentNode !== null && !this.visible) {
+                        dom.parentNode.removeChild(dom);
+                    }
+                }, false);
+                //
+                setTimeout(() => {
+                    this.visible = true;
+                }, 0)
+            });
+        },
+        methods: {
+            commentKeydown(e) {
+                e = e || event;
+                if (e.keyCode == 13) {
+                    if (e.shiftKey) {
+                        console.log("换行");
+                        return;
+                    }
+                    console.log("发表");
+                    e.preventDefault();
+                }
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .project-task-detail-window {
+        position: fixed;
+        z-index: 100000;
+        top: 0;
+        left: 0;
+        height: 100%;
+        width: 100%;
+        background-color: rgba(0, 0, 0, 0.5);
+        transition: all .3s;
+        opacity: 0;
+        pointer-events: unset;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+
+        &.task-detail-show {
+            opacity: 1;
+        }
+
+        .task-detail-main {
+            display: flex;
+            flex-direction: row;
+            width: 96%;
+            max-width: 800px;
+            max-height: 96%;
+            background: #ffffff;
+            overflow: hidden;
+            border-radius: 4px;
+            padding: 10px 20px 2px;
+            transform: translateZ(0);
+            .detail-left {
+                flex: 1;
+                padding-left: 8px;
+                padding-right: 20px;
+                overflow: auto;
+                .detail-h2 {
+                    color: #172b4d;
+                    font-size: 16px;
+                    display: flex;
+                    align-items: center;
+                    line-height: 26px;
+                    em {
+                        margin: 0 11px;
+                        width: 1px;
+                        height: 10px;
+                        background: #cccccc;
+                    }
+                }
+                .detail-icon {
+                    position: relative;
+                    padding-left: 26px;
+                    &:before {
+                        font-family: zenicon;
+                        font-size: 20px;
+                        color: #42526e;
+                        font-weight: 600;
+                        position: absolute;
+                        top: 0;
+                        left: 0;
+                        width: 26px;
+                        height: 26px;
+                        line-height: 26px;
+                    }
+                }
+                .detail-title-box {
+                    margin-top: 12px;
+                    margin-bottom: 12px;
+                    &:before {
+                        content: "\E740";
+                    }
+                    .time {
+                        font-size: 12px;
+                        color: #606266;
+                    }
+                    input {
+                        margin: -10px 0 0 -8px;
+                        font-size: 20px;
+                        font-weight: 600;
+                        border: 2px solid #ffffff;
+                        padding: 5px 8px;
+                        cursor: pointer;
+                        color: #172b4d;
+                        background: #ffffff;
+                        width: 100%;
+                        border-radius: 3px;
+                    }
+                    input:focus {
+                        outline: 0;
+                        background: #fff;
+                        border-color: #0396f2;
+                    }
+                }
+                .detail-desc-box {
+                    &:before {
+                        content: "\E75E";
+                    }
+                    textarea {
+                        border: 2px solid #F4F5F7;
+                        padding: 5px 8px;
+                        cursor: pointer;
+                        color: #172b4d;
+                        background: rgba(9, 30, 66, 0.04);
+                        width: 100%;
+                        border-radius: 3px;
+                        resize: none;
+                        margin-top: 10px;
+                    }
+                    textarea:focus {
+                        outline: 0;
+                        background: #fff;
+                        height: 100px;
+                        border-color: #0396f2;
+                    }
+                }
+                .detail-text-box {
+                    margin-bottom: 12px;
+                    li {
+                        color: #606266;
+                        font-size: 14px;
+                        line-height: 30px;
+                        word-break: break-all;
+                        &:before {
+                            font-weight: normal;
+                            color: #606266;
+                            font-size: 14px;
+                            padding-left: 4px;
+                            line-height: 30px;
+                        }
+                        &.text-username {
+                            &:before {
+                                content: "\E903";
+                            }
+                        }
+                        &.text-level {
+                            &:before {
+                                content: "\E725";
+                            }
+                        }
+                        &.text-status {
+                            &:before {
+                                content: "\E6AF";
+                            }
+                        }
+                        em {
+                            margin-left: 4px;
+                            &.p1 {
+                                color: #ed3f14;
+                            }
+                            &.p2 {
+                                color: #ff9900;
+                            }
+                            &.p3 {
+                                color: #19be6b;
+                            }
+                            &.p4 {
+                                color: #666666;
+                            }
+                            &.complete {
+                                color: #666666;
+                            }
+                            &.overdue {
+                                color: #ff0000;
+                            }
+                            &.unfinished {
+                                color: #19be6b;
+                            }
+                        }
+                    }
+                }
+                .detail-comment-box {
+                    &:before {
+                        content: "\E753";
+                    }
+                }
+                .detail-footer-box {
+                    border-top: 1px solid #e5e5e5;
+                    display: flex;
+                    flex-direction: row;
+                    padding-top: 20px;
+                    padding-bottom: 16px;
+                    .comment-input {
+                        margin-right: 12px;
+                    }
+                }
+            }
+            .detail-right {
+                .cancel {
+                    text-align: right;
+                    width: auto;
+                    height: 38px;
+                    em {
+                        display: inline-block;
+                        width: 38px;
+                        height: 38px;
+                        cursor: pointer;
+                        border-radius: 50%;
+                        transform: scale(0.92);
+                        &:after,
+                        &:before {
+                            position: absolute;
+                            content: "";
+                            top: 50%;
+                            left: 50%;
+                            width: 2px;
+                            height: 20px;
+                            background-color: #EE2321;
+                            transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
+                            transition: all .2s;
+                        }
+                        &:before {
+                            position: absolute;
+                            transform: translate(-50%, -50%) rotate(-45deg) scale(0.6, 1);
+                        }
+                        &:hover {
+                            &:after,
+                            &:before {
+                                background-color: #ff0000;
+                                transform: translate(-50%, -50%) rotate(135deg) scale(0.6, 1);
+                            }
+                            &:before {
+                                background-color: #ff0000;
+                                transform: translate(-50%, -50%) rotate(45deg) scale(0.6, 1);
+                            }
+                        }
+                    }
+                }
+                .btn {
+                    background: rgba(9, 30, 66, 0.04);
+                    border: 1px solid #E8EAEE;
+                    border-radius: 4px;
+                    padding: 4px 8px;
+                    margin-top: 6px;
+                    color: #172b4d;
+                    cursor: pointer;
+                    display: block;
+                    &:hover {
+                        border-color: #409EFF;
+                        background: #409EFF;
+                        color: #fff;
+                    }
+                    &.remove {
+                        color: rgba(248, 14, 21, 0.5);
+                        &:hover {
+                            border-color: #ffa5a3;
+                            background: #ffa5a3;
+                            color: #ffffff;
+                        }
+                    }
+                    .icon {
+                        margin-right: 4px;
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 51 - 0
resources/assets/js/main/components/project/task/detail/index.js

@@ -0,0 +1,51 @@
+import Vue from 'vue';
+import component from './detail.vue'
+
+const detailElement = (taskid, detail = {}) => {
+    let cloneData = (myObj) => {
+        if (typeof (myObj) !== 'object') return myObj;
+        if (myObj === null) return myObj;
+        //
+        if (typeof myObj.length === 'number') {
+            let [...myNewObj] = myObj;
+            return myNewObj;
+        } else {
+            let {...myNewObj} = myObj;
+            return myNewObj;
+        }
+    };
+    return new Promise(() => {
+        let custom = Vue.extend(component);
+
+        if (typeof taskid === 'object' && taskid !== null) {
+            detail = cloneData(taskid);
+            taskid = parseInt(taskid.id);
+            if (isNaN(taskid)) {
+                taskid = 0;
+            }
+        }
+
+        if (typeof detail !== 'object' || detail === null) {
+            detail = {}
+        }
+
+        if (typeof taskid === "number") {
+            detail.taskid = taskid;
+        }
+
+        let data = {
+            taskid: taskid,
+            detail: detail,
+        };
+
+        let instance = new custom({
+            data: data
+        });
+
+        instance.$mount();
+        document.body.appendChild(instance.$el);
+    })
+};
+
+export default detailElement
+

+ 28 - 12
resources/assets/js/main/components/project/task/logs.vue

@@ -1,7 +1,7 @@
 <template>
     <drawer-tabs-container>
         <div class="project-task-logs">
-            <ul class="logs-activity">
+            <ul class="logs-activity" :class="`${taskid>0?'istaskid':''}`">
                 <li v-for="(items, key) in lists">
                     <div class="logs-date">{{key}}</div>
                     <div class="logs-section">
@@ -13,14 +13,15 @@
                                 <div class="log-summary">
                                     <span class="log-creator">{{item.username}}</span>
                                     <span class="log-text-secondary">{{item.detail}}</span>
-                                    <span v-if="item.other.type=='task'" class="log-text-link">{{item.other.title}}</span>
+                                    <span v-if="item.other.type=='task' && taskid == 0" class="log-text-link">{{item.other.title}}</span>
                                     <a v-if="item.other.type=='file'" class="log-text-link" target="_blank" :href="fileDownUrl(item.other.id)">{{item.other.name}}</a>
                                     <span class="log-text-info">{{item.timeData.ymd}} {{item.timeData.segment}} {{item.timeData.hi}}</span></div>
                             </TimelineItem>
                         </Timeline>
                     </div>
                 </li>
-                <li v-if="hasMorePages" class="logs-more" @click="getMore">加载更多</li>
+                <li v-if="loadIng > 0" class="logs-loading"><w-loading></w-loading></li>
+                <li v-else-if="hasMorePages" class="logs-more" @click="getMore">加载更多</li>
             </ul>
         </div>
     </drawer-tabs-container>
@@ -32,8 +33,19 @@
             position: relative;
             word-break: break-all;
             padding: 12px 12px;
+            &.istaskid {
+                > li {
+                    padding-top: 0;
+                }
+            }
             > li {
                 padding-top: 22px;
+                &.logs-loading {
+                    margin: 8px 0;
+                    width: 22px;
+                    height: 22px;
+                    display: flex;
+                }
                 &.logs-more {
                     cursor: pointer;
                     &:hover {
@@ -43,6 +55,11 @@
                 &:first-child {
                     padding-top: 0;
                 }
+                &:last-child {
+                    .logs-section {
+                        margin-bottom: -20px;
+                    }
+                }
                 .logs-date {
                     color: rgba(0, 0, 0, .36);
                     padding-bottom: 14px;
@@ -86,9 +103,11 @@
 <script>
 
     import DrawerTabsContainer from "../../DrawerTabsContainer";
+    import WLoading from '../../WLoading'
+
     export default {
         name: 'ProjectTaskLogs',
-        components: {DrawerTabsContainer},
+        components: {DrawerTabsContainer, WLoading},
         props: {
             projectid: {
                 default: 0
@@ -96,6 +115,9 @@
             taskid: {
                 default: 0
             },
+            pagesize: {
+                default: 100
+            },
             canload: {
                 type: Boolean,
                 default: true
@@ -148,11 +170,6 @@
                 if (resetLoad === true) {
                     this.listPage = 1;
                 }
-                if (this.projectid == 0) {
-                    this.lists = {};
-                    this.hasMorePages = false;
-                    return;
-                }
                 this.loadIng++;
                 $A.aAjax({
                     url: 'project/log/lists',
@@ -160,7 +177,7 @@
                         projectid: this.projectid,
                         taskid: this.taskid,
                         page: Math.max(this.listPage, 1),
-                        pagesize: 100,
+                        pagesize: this.pagesize,
                     },
                     complete: () => {
                         this.loadIng--;
@@ -177,10 +194,9 @@
                                 }
                                 this.lists[key].push(item);
                             });
-                            console.log(this.lists);
                             this.hasMorePages = res.data.hasMorePages;
                         } else {
-                            this.lists = [];
+                            this.lists = {};
                             this.hasMorePages = false;
                         }
                     }

+ 1 - 1
resources/assets/js/main/pages/todo.vue

@@ -50,7 +50,7 @@
                                     :disabled="taskSortDisabled"
                                     @sort="taskSortUpdate"
                                     @remove="taskSortUpdate">
-                                    <div v-for="task in taskDatas[index].lists" class="content-li task-draggable" :key="task.id" :class="{complete:task.complete}">
+                                    <div v-for="task in taskDatas[index].lists" class="content-li task-draggable" :key="task.id" :class="{complete:task.complete}" @click="taskDetail(task)">
                                         <Icon v-if="task.complete" class="task-check" type="md-checkbox-outline" />
                                         <Icon v-else class="task-check" type="md-square-outline" />
                                         <div v-if="task.overdue" class="task-overdue">[超期]</div>