diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index e0d1b181..cee42db7 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -16,6 +16,7 @@ use App\Module\Base; use Carbon\Carbon; use Illuminate\Support\Arr; use Request; +use function Swoole\Coroutine\Http\get; /** * @apiDefine project @@ -440,87 +441,6 @@ class ProjectController extends AbstractController return Base::retError('删除失败'); } - /** - * 消息列表 - * - * @apiParam {Number} project_id 项目ID - * @apiParam {Number} [task_id] 任务ID - * - * @apiParam {Number} [page] 当前页,默认:1 - * @apiParam {Number} [pagesize] 每页显示数量,默认:30,最大:100 - */ - public function msg__lists() - { - $user = User::authE(); - if (Base::isError($user)) { - return $user; - } else { - $user = User::IDE($user['data']); - } - // - $project_id = intval(Request::input('project_id')); - $task_id = intval(Request::input('task_id')); - // - $project = Project::select($this->projectSelect) - ->join('project_users', 'projects.id', '=', 'project_users.project_id') - ->where('projects.id', $project_id) - ->where('project_users.userid', $user->userid) - ->first(); - if (empty($project)) { - return Base::retError('项目不存在或不在成员列表内'); - } - // - $builder = WebSocketDialogMsg::whereDialogId($project->dialog_id); - if ($task_id > 0) { - $builder->whereExtraInt($task_id); - } - $list = $builder->orderByDesc('id')->paginate(Base::getPaginate(100, 30)); - // - return Base::retSuccess('success', $list); - } - - /** - * 发送消息 - * - * @apiParam {Number} project_id 项目ID - * @apiParam {Number} [task_id] 任务ID - * @apiParam {String} text 消息内容 - */ - public function msg__sendtext() - { - $user = User::authE(); - if (Base::isError($user)) { - return $user; - } else { - $user = User::IDE($user['data']); - } - // - $project_id = intval(Request::input('project_id')); - $task_id = intval(Request::input('task_id')); - $text = trim(Request::input('text')); - // - if (mb_strlen($text) < 1) { - return Base::retError('消息内容不能为空'); - } elseif (mb_strlen($text) > 20000) { - return Base::retError('消息内容最大不能超过20000字'); - } - // - $project = Project::select($this->projectSelect) - ->join('project_users', 'projects.id', '=', 'project_users.project_id') - ->where('projects.id', $project_id) - ->where('project_users.userid', $user->userid) - ->first(); - if (empty($project)) { - return Base::retError('项目不存在或不在成员列表内'); - } - // - $msg = [ - 'text' => $text - ]; - // - return WebSocketDialogMsg::addGroupMsg($project->dialog_id, 'text', $msg, $user->userid, $task_id); - } - /** * 添加任务列表 * @@ -638,6 +558,73 @@ class ProjectController extends AbstractController return Base::retError('删除失败'); } + /** + * 获取任务 + * + * @apiParam {Number} task_id 任务ID + */ + public function task__one() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $task_id = intval(Request::input('task_id')); + // 任务 + $task = ProjectTask::with(['taskUser', 'taskTag'])->whereId($task_id)->first(); + if (empty($task)) { + return Base::retError('任务不存在'); + } + // 项目 + $project = Project::select($this->projectSelect) + ->join('project_users', 'projects.id', '=', 'project_users.project_id') + ->where('projects.id', $task->project_id) + ->where('project_users.userid', $user->userid) + ->first(); + if (empty($project)) { + return Base::retError('项目不存在或不在成员列表内'); + } + // + return Base::retSuccess('success', $task); + } + + /** + * 获取子任务 + * + * @apiParam {Number} task_id 任务ID + */ + public function task__sublist() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $task_id = intval(Request::input('task_id')); + // 任务 + $task = ProjectTask::whereId($task_id)->first(); + if (empty($task)) { + return Base::retError('任务不存在'); + } + // 项目 + $project = Project::select($this->projectSelect) + ->join('project_users', 'projects.id', '=', 'project_users.project_id') + ->where('projects.id', $task->project_id) + ->where('project_users.userid', $user->userid) + ->first(); + if (empty($project)) { + return Base::retError('项目不存在或不在成员列表内'); + } + // + $data = ProjectTask::with(['taskUser', 'taskTag'])->where('parent_id', $task->id)->get(); + return Base::retSuccess('success', $data); + } + /** * {post} 添加任务 * diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php index 8213d0f7..f9ad4a32 100644 --- a/app/Models/ProjectTask.php +++ b/app/Models/ProjectTask.php @@ -35,6 +35,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read int $msg_num * @property-read bool $overdue * @property-read int $percent + * @property-read int $sub_complete * @property-read int $sub_num * @property-read bool $today * @property-read \App\Models\Project|null $project @@ -77,10 +78,11 @@ class ProjectTask extends AbstractModel 'file_num', 'msg_num', 'sub_num', - 'dialog_id', + 'sub_complete', 'percent', 'today', 'overdue', + 'dialog_id', ]; /** @@ -107,31 +109,50 @@ class ProjectTask extends AbstractModel return $this->attributes['msg_num']; } + /** + * 生成子任务数据 + */ + private function generateSubTaskData() + { + if ($this->parent_id > 0) { + $this->attributes['sub_num'] = 0; + $this->attributes['sub_complete'] = 0; + $this->attributes['percent'] = 0; + return; + } + if (!isset($this->attributes['sub_num'])) { + $builder = self::whereParentId($this->id); + $this->attributes['sub_num'] = $builder->count(); + $this->attributes['sub_complete'] = $builder->whereNotNull('complete_at')->count(); + // + if ($this->complete_at) { + $this->attributes['percent'] = 100; + } elseif ($this->attributes['sub_complete'] == 0) { + $this->attributes['percent'] = 0; + } else { + $this->attributes['percent'] = intval($this->attributes['sub_complete'] / $this->attributes['sub_num'] * 100); + } + } + } + /** * 子任务数量 * @return int */ public function getSubNumAttribute() { - if ($this->parent_id > 0) { - return 0; - } - if (!isset($this->attributes['sub_num'])) { - $this->attributes['sub_num'] = self::whereParentId($this->id)->count(); - } + $this->generateSubTaskData(); return $this->attributes['sub_num']; } /** - * 对话ID + * 子任务已完成数量 * @return int */ - public function getDialogIdAttribute() + public function getSubCompleteAttribute() { - if (!isset($this->attributes['dialog_id'])) { - $this->attributes['dialog_id'] = intval(Project::whereId($this->project_id)->value('dialog_id')); - } - return $this->attributes['dialog_id']; + $this->generateSubTaskData(); + return $this->attributes['sub_complete']; } /** @@ -140,22 +161,8 @@ class ProjectTask extends AbstractModel */ public function getPercentAttribute() { - if ($this->parent_id > 0) { - return 0; - } - $builder = self::whereParentId($this->id); - if (!isset($this->attributes['sub_num'])) { - $this->attributes['sub_num'] = $builder->count(); - } - $subTaskTotal = $this->attributes['sub_num']; - if ($subTaskTotal == 0) { - return $this->complete_at ? 100 : 0; - } - $subTaskComplete = $builder->whereNotNull('complete_at')->count(); - if ($subTaskComplete == 0) { - return 0; - } - return intval($subTaskComplete / $subTaskTotal * 100); + $this->generateSubTaskData(); + return $this->attributes['percent']; } /** @@ -187,6 +194,18 @@ class ProjectTask extends AbstractModel return false; } + /** + * 对话ID + * @return int + */ + public function getDialogIdAttribute() + { + if (!isset($this->attributes['dialog_id'])) { + $this->attributes['dialog_id'] = intval(Project::whereId($this->project_id)->value('dialog_id')); + } + return $this->attributes['dialog_id']; + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasOne */ diff --git a/resources/assets/js/pages/manage/components/ProjectList.vue b/resources/assets/js/pages/manage/components/ProjectList.vue index 8f0d5fc3..16037cce 100644 --- a/resources/assets/js/pages/manage/components/ProjectList.vue +++ b/resources/assets/js/pages/manage/components/ProjectList.vue @@ -43,7 +43,6 @@ {{$L('退出项目')}} -
@@ -66,7 +65,7 @@
  • + :style="column.color ? {backgroundColor: column.color} : {}">
    {{column.name}} ({{column.project_task.length}}) @@ -90,7 +89,7 @@
    {{$L('颜色')}} - +
    {{$L(c.name)}}
    @@ -123,13 +122,14 @@
    + :style="item.color ? {backgroundColor: item.color} : {}">
    {{item.name}}
    @@ -154,9 +154,9 @@
    {{$L('背景色')}} - +
    - {{$L(c.name)}} + {{$L(c.name)}}
    @@ -176,6 +176,7 @@
    {{item.msg_num}}
    +
    {{item.sub_complete}}/{{item.sub_num}}
    - + # {{$L('任务名称')}} {{$L('列表')}} {{$L('优先级')}} @@ -226,51 +227,20 @@
    -
    - - - -
    {{$L('我的任务')}}
    -
    ({{myList.length}})
    - - - - - -
    -
    -
    - - - - -
    {{item.name}}
    -
    {{item.file_num}}
    -
    {{item.msg_num}}
    - - {{item.column_name}} - {{item.p_name}} - -
      -
    • - -
    • -
    - - - -
    {{item.end_at ? expiresFormat(item.end_at) : ''}}
    -
    - - -
    -
    + + + +
    {{$L('我的任务')}}
    +
    ({{myList.length}})
    + + + + + +
    +
    - + {{$L('添加任务')}} @@ -283,95 +253,33 @@
    -
    - - - -
    {{$L('未完成任务')}}
    -
    ({{undoneList.length}})
    - - - - - -
    -
    -
    - - - - -
    {{item.name}}
    -
    {{item.file_num}}
    -
    {{item.msg_num}}
    - - {{item.column_name}} - {{item.p_name}} - -
      -
    • - -
    • -
    - - - -
    {{item.end_at ? expiresFormat(item.end_at) : ''}}
    -
    - - -
    -
    + + + +
    {{$L('未完成任务')}}
    +
    ({{undoneList.length}})
    + + + + + +
    +
    -
    - - - -
    {{$L('已完成任务')}}
    -
    ({{completedList.length}})
    - - - - - -
    -
    -
    - - - - -
    {{item.name}}
    -
    {{item.file_num}}
    -
    {{item.msg_num}}
    - - {{item.column_name}} - {{item.p_name}} - -
      -
    • - -
    • -
    - - - -
    {{item.end_at ? expiresFormat(item.end_at) : ''}}
    -
    - - -
    -
    + + + +
    {{$L('已完成任务')}}
    +
    ({{completedList.length}})
    + + + + + +
    +
    @@ -455,9 +363,10 @@ import TaskAdd from "./TaskAdd"; import {mapState} from "vuex"; import UserInput from "../../../components/UserInput"; import TaskAddSimple from "./TaskAddSimple"; +import TaskRow from "./TaskRow"; export default { name: "ProjectList", - components: {Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority}, + components: {TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority}, data() { return { nowTime: Math.round(new Date().getTime() / 1000), @@ -495,7 +404,7 @@ export default { transferData: {}, transferLoad: 0, - columnList: [ + columnColorList: [ {name: '默认', color: ''}, {name: '灰色', color: '#6C6F71'}, {name: '棕色', color: '#695C56'}, @@ -508,7 +417,7 @@ export default { {name: '红色', color: '#9D6058'}, ], - taskList: [ + taskColorList: [ {name: '默认', color: ''}, {name: '黄色', color: '#FCF4A7'}, {name: '蓝色', color: '#BCF2FD'}, @@ -698,7 +607,9 @@ export default { success: ({ret, data, msg}) => { if (ret === 1) { $A.messageSuccess(msg); - this.addTaskSuccess(data) + this.addTaskSuccess(Object.assign(data, { + top: !!this.addData.top + })) this.addShow = false; this.addData = { owner: 0, @@ -723,7 +634,7 @@ export default { addTaskOpen(column_id) { if ($A.isJson(column_id)) { - this.addData = Object.assign(this.addData, column_id); + this.addData = Object.assign({}, this.addData, column_id); } else { this.$set(this.addData, 'owner', this.userId); this.$set(this.addData, 'column_id', column_id); @@ -962,6 +873,7 @@ export default { Object.keys(data).forEach(key => { this.$set(task, key, data[key]); }); + if (data.parent_id) this.getTaskOne(data.parent_id); } else { Object.keys(updata).forEach(key => { this.$set(task, key, backup[key]); @@ -972,6 +884,29 @@ export default { }); }, + getTaskOne(task_id) { + let task = null; + this.projectDetail.project_column.some(({project_task}) => { + task = project_task.find(({id}) => id === task_id); + if (task) return true; + }); + if (!task) return; + // + $A.apiAjax({ + url: 'project/task/one', + data: { + task_id: task.id + }, + success: ({ret, data, msg}) => { + if (ret === 1) { + Object.keys(data).forEach(key => { + this.$set(task, key, data[key]); + }); + } + } + }); + }, + archivedOrRemoveTask(task, type) { if (task.loading === true) { return; diff --git a/resources/assets/js/pages/manage/components/TaskRow.vue b/resources/assets/js/pages/manage/components/TaskRow.vue new file mode 100644 index 00000000..3d4c1963 --- /dev/null +++ b/resources/assets/js/pages/manage/components/TaskRow.vue @@ -0,0 +1,220 @@ + + + diff --git a/resources/assets/sass/project-list.scss b/resources/assets/sass/project-list.scss index 5df8d0d8..ad60b3a9 100644 --- a/resources/assets/sass/project-list.scss +++ b/resources/assets/sass/project-list.scss @@ -389,6 +389,11 @@ display: flex; align-items: center; justify-content: flex-end; + .task-sub-num { + font-size: 12px; + margin-right: 8px; + color: #777777; + } .task-time { flex-shrink: 0; color: #777777; @@ -448,10 +453,10 @@ } .project-table { height: 100%; - padding-top: 18px; + margin-top: 18px; overflow-x: hidden; overflow-y: auto; - .project-row { + .task-row { background-color: #ffffff; border-bottom: 1px solid #F4F4F5; position: relative; @@ -466,6 +471,12 @@ &:last-child { border-right: 0; } + &.complete { + .item-title { + color: #aaaaaa; + text-decoration: line-through; + } + } } .priority-color { position: absolute; @@ -483,7 +494,7 @@ border-bottom: 0; overflow: hidden; &.project-table-hide { - .project-rows { + .task-rows { display: none; } .row-title { @@ -494,7 +505,7 @@ } } .project-table-head { - .project-row { + .task-row { > div { color: #888888; font-size: 13px; @@ -507,7 +518,33 @@ &:hover { box-shadow: 0 0 10px #e6ecfa; } - .project-row { + .task-rows { + .task-rows { + position: relative; + overflow: hidden; + &:before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + box-shadow: rgb(0 0 0 / 8%) 0 0 8px 1px; + z-index: 1; + } + .task-row { + background-color: #fcfcfd; + > div { + &.row-item { + padding-left: 56px; + .item-title { + color: #6C7D8C; + } + } + } + } + } + } + .task-row { > div { padding: 10px 12px; .task-time { @@ -546,29 +583,82 @@ } } &.row-item { - padding-left: 24px; + padding-left: 34px; + .loading { + width: 24px; + height: 14px; + display: flex; + align-items: center; + justify-content: flex-start; + .common-loading { + margin: 0; + width: 14px; + height: 14px; + } + } .ivu-icon { font-size: 16px; - color: #dddddd; + color: #cccccc; + margin-right: 8px; &.completed { color: #87d068; } + &.sub-icon { + font-size: 16px; + width: 16px; + height: 16px; + margin-left: -20px; + margin-right: 4px; + color: #cfcfcf; + transition: transform 0.2s; + &.active { + transform: rotate(90deg); + } + } } .item-title { - padding: 0 22px 0 10px; + flex: 1; + padding: 0 22px 0 0; } - .item-icon { + .item-sub-num { + flex-shrink: 0; + cursor: pointer; + display: flex; + align-items: center; + margin-left: 8px; + border: 1px solid #e8e8e8; + background-color: #ffffff; + padding: 0 6px 0 5px; + border-radius: 4px; + height: 20px; + line-height: 18px; font-size: 12px; - margin-right: 8px; - color: #777777; - .ivu-icon, - .iconfont { - margin-left: 1px; - font-size: 14px; - color: #666666; + .ivu-icon { + transform: scale(0.9); + font-size: 12px; + color: #333333; + margin-right: 4px; } - .iconfont { - color: #999999; + } + .item-icons { + display: flex; + align-items: center; + margin-left: 4px; + flex-shrink: 0; + .item-icon { + font-size: 12px; + margin-left: 8px; + color: #777777; + .ivu-icon, + .iconfont { + margin-left: 2px; + margin-right: 0; + font-size: 14px; + color: #666666; + } + .iconfont { + color: #999999; + } } } } @@ -576,11 +666,16 @@ > ul { display: flex; align-items: center; - overflow: auto; margin-left: -4px; + overflow: hidden; + text-overflow:ellipsis; + white-space: nowrap; > li { list-style: none; margin-left: -6px; + overflow: hidden; + text-overflow:ellipsis; + white-space: nowrap; &:first-child { margin-left: 0; } @@ -600,6 +695,7 @@ } } } + } } }