From 1c856c34f916f047d56d5dd97b51209bb8b3a032 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Thu, 3 Jun 2021 00:34:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Api/ProjectController.php | 73 +++- app/Http/Middleware/VerifyCsrfToken.php | 3 +- app/Models/Project.php | 4 +- app/Models/ProjectTask.php | 188 ++++++++++ app/Models/ProjectTaskContent.php | 5 +- app/Models/ProjectTaskFile.php | 41 +++ app/Models/ProjectTaskMsg.php | 31 ++ app/Models/ProjectTaskTag.php | 34 ++ app/Module/Base.php | 7 +- resources/assets/js/components/UserInput.vue | 23 +- .../pages/manage/components/project-list.vue | 342 ++++++------------ .../js/pages/manage/components/task-add.vue | 54 ++- resources/assets/js/store/state.js | 1 + resources/assets/sass/iconfont.scss | 6 +- resources/assets/sass/main.scss | 33 +- .../statics/images/project-menu-blue.svg | 1 - .../statics/images/project-menu-gray.svg | 1 - .../statics/images/project-panel-blue.svg | 1 - .../statics/images/project-panel-gray.svg | 1 - 19 files changed, 587 insertions(+), 262 deletions(-) create mode 100644 app/Models/ProjectTaskFile.php create mode 100644 app/Models/ProjectTaskMsg.php create mode 100644 app/Models/ProjectTaskTag.php delete mode 100644 resources/assets/statics/images/project-menu-blue.svg delete mode 100644 resources/assets/statics/images/project-menu-gray.svg delete mode 100644 resources/assets/statics/images/project-panel-blue.svg delete mode 100644 resources/assets/statics/images/project-panel-gray.svg diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index b810eded..1c8d11c9 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -6,9 +6,11 @@ use App\Models\AbstractModel; use App\Models\Project; use App\Models\ProjectColumn; use App\Models\ProjectLog; +use App\Models\ProjectTask; use App\Models\ProjectUser; use App\Models\User; use App\Module\Base; +use Carbon\Carbon; use Request; /** @@ -68,7 +70,7 @@ class ProjectController extends AbstractController // $project = Project::with(['projectColumn' => function($query) { $query->with(['projectTask' => function($taskQuery) { - $taskQuery->where('parent_id', 0); + $taskQuery->with(['taskUser', 'taskTag'])->where('parent_id', 0); }]); }, 'projectUser']) ->select($this->projectSelect) @@ -155,4 +157,73 @@ class ProjectController extends AbstractController return Base::retSuccess('添加成功!'); }); } + + /** + * {post}【任务】添加任务 + * + * @apiParam {Number} project_id 项目ID + * @apiParam {Number} [column_id] 列表ID,留空取第一个 + * @apiParam {String} name 任务名称 + * @apiParam {String} [content] 任务描述 + * @apiParam {Array} [times] 计划时间(格式:开始时间,结束时间;如:2020-01-01 00:00,2020-01-01 23:59) + * @apiParam {Number} [owner] 负责人,留空为自己 + * @apiParam {Array} [subtasks] 子任务(格式:[{name,owner,times}]) + */ + public function task__add() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + $project_id = Base::getPostInt('project_id'); + $column_id = Base::getPostValue('column_id'); + $name = Base::getPostValue('name'); + $content = Base::getPostValue('content'); + $times = Base::getPostValue('times'); + $owner = Base::getPostValue('owner'); + $subtasks = Base::getPostValue('subtasks'); + // 项目 + $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('项目不存在或已被删除!'); + } + // 列表 + if (is_array($column_id)) { + $column_id = Base::arrayFirst($column_id); + } + if (empty($column_id)) { + $column = $project->projectColumn->first(); + } elseif (intval($column_id) > 0) { + $column = $project->projectColumn->where('id', $column_id)->first(); + } else { + $column = ProjectColumn::whereProjectId($project->id)->whereName($column_id)->first(); + if (empty($column)) { + $column = ProjectColumn::createInstance([ + 'project_id' => $project->id, + 'name' => $column_id, + ]); + $column->save(); + } + } + if (empty($column)) { + return Base::retError('任务列表不存在或已被删除!'); + } + // + return ProjectTask::addTask([ + 'parent_id' => 0, + 'project_id' => $project->id, + 'column_id' => $column->id, + 'name' => $name, + 'content' => $content, + 'times' => $times, + 'owner' => $owner, + 'subtasks' => $subtasks, + ]); + } } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 0c13b854..8ff89d06 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,7 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - // + // 添加任务 + 'api/project/task/add/', ]; } diff --git a/app/Models/Project.php b/app/Models/Project.php index e7c28a0d..85257cdd 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -45,7 +45,7 @@ class Project extends AbstractModel */ public function projectColumn(): \Illuminate\Database\Eloquent\Relations\HasMany { - return $this->hasMany(projectColumn::class, 'project_id', 'id')->orderByDesc('id'); + return $this->hasMany(projectColumn::class, 'project_id', 'id')->orderBy('id'); } /** @@ -61,6 +61,6 @@ class Project extends AbstractModel */ public function projectUser(): \Illuminate\Database\Eloquent\Relations\HasMany { - return $this->hasMany(projectUser::class, 'project_id', 'id')->orderByDesc('id'); + return $this->hasMany(projectUser::class, 'project_id', 'id')->orderBy('id'); } } diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php index f7d8dc9e..d3b020f7 100644 --- a/app/Models/ProjectTask.php +++ b/app/Models/ProjectTask.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Module\Base; +use Carbon\Carbon; + /** * Class ProjectTask * @@ -20,6 +23,13 @@ namespace App\Models; * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read int $file_num + * @property-read int $msg_num + * @property-read bool $overdue + * @property-read int $percent + * @property-read bool $today + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser + * @property-read int|null $task_user_count * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask query() @@ -42,4 +52,182 @@ namespace App\Models; class ProjectTask extends AbstractModel { + protected $appends = [ + 'file_num', + 'msg_num', + 'percent', + 'today', + 'overdue', + ]; + + /** + * 附件数量 + * @return int + */ + public function getFileNumAttribute() + { + if (!isset($this->attributes['file_num'])) { + $this->attributes['file_num'] = ProjectTaskFile::whereTaskId($this->id)->count(); + } + return $this->attributes['file_num']; + } + + /** + * 消息数量 + * @return int + */ + public function getMsgNumAttribute() + { + if (!isset($this->attributes['msg_num'])) { + $this->attributes['msg_num'] = ProjectTaskMsg::whereTaskId($this->id)->count(); + } + return $this->attributes['msg_num']; + } + + /** + * 进度(0-100) + * @return int + */ + public function getPercentAttribute() + { + $builder = self::whereParentId($this->id); + $subTaskTotal = $builder->count(); + if ($subTaskTotal == 0) { + return $this->complete_at ? 1 : 0; + } + $subTaskComplete = $builder->whereNotNull('complete_at')->count(); + if ($subTaskComplete == 0) { + return 0; + } + return intval($subTaskComplete / $subTaskTotal * 100); + } + + /** + * 是否今日任务 + * @return bool + */ + public function getTodayAttribute() + { + if ($this->end_at) { + $end_at = Carbon::parse($this->end_at); + if ($end_at->toDateString() == Carbon::now()->toDateString()) { + return true; + } + } + return false; + } + + /** + * 是否过期 + * @return bool + */ + public function getOverdueAttribute() + { + if ($this->end_at) { + if (Carbon::parse($this->end_at)->lt(Carbon::now())) { + return true; + } + } + return false; + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function taskUser(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(projectTaskUser::class, 'task_id', 'id')->orderByDesc('id'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function taskTag(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(projectTaskTag::class, 'task_id', 'id')->orderByDesc('id'); + } + + /** + * 添加任务 + * @param $params + * @return array|bool + */ + public static function addTask($params) + { + $parent_id = intval($params['parent_id']); + $project_id = intval($params['project_id']); + $column_id = intval($params['column_id']); + $name = $params['name']; + $content = $params['content']; + $times = $params['times']; + $owner = $params['owner']; + $subtasks = $params['subtasks']; + // + $retPre = $parent_id ? '子任务' : '任务'; + $task = self::createInstance(); + $task->parent_id = $parent_id; + $task->project_id = $project_id; + $task->column_id = $column_id; + if ($content) { + $task->desc = Base::getHtml($content); + } + // 标题 + if (empty($name)) { + return Base::retError($retPre . '名称不能为空!'); + } elseif (mb_strlen($name) > 255) { + return Base::retError($retPre . '名称最多只能设置255个字!'); + } + $task->name = $name; + // 时间 + if ($times) { + list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []); + if (Base::isDate($start) && Base::isDate($end)) { + if ($start != $end) { + $task->start_at = Carbon::parse($start); + $task->end_at = Carbon::parse($end); + } + } + } + // 负责人 + if (is_array($owner)) { + $owner = Base::arrayFirst($owner); + } + $owner = $owner ?: User::token2userid(); + if (!ProjectUser::whereProjectId($project_id)->whereUserid($owner)->exists()) { + return Base::retError($retPre . '负责人填写错误!'); + } + // 创建人 + $task->userid = User::token2userid(); + // + return AbstractModel::transaction(function() use ($subtasks, $content, $owner, $task) { + $task->save(); + if ($owner) { + ProjectTaskUser::createInstance([ + 'project_id' => $task->parent_id, + 'task_id' => $task->id, + 'userid' => $owner, + 'owner' => 1, + ])->save(); + } + if ($content) { + ProjectTaskContent::createInstance([ + 'project_id' => $task->parent_id, + 'task_id' => $task->id, + 'content' => $content, + ])->save(); + } + if ($task->parent_id == 0 && $subtasks && is_array($subtasks)) { + foreach ($subtasks as $subtask) { + $subtask['parent_id'] = $task->id; + $subtask['project_id'] = $task->project_id; + $subtask['column_id'] = $task->column_id; + $res = self::addTask($subtask); + if (Base::isError($res)) { + return $res; + } + } + } + return Base::retSuccess('添加成功'); + }); + } } diff --git a/app/Models/ProjectTaskContent.php b/app/Models/ProjectTaskContent.php index 8d086883..cbf13629 100644 --- a/app/Models/ProjectTaskContent.php +++ b/app/Models/ProjectTaskContent.php @@ -25,5 +25,8 @@ namespace App\Models; */ class ProjectTaskContent extends AbstractModel { - + protected $hidden = [ + 'created_at', + 'updated_at', + ]; } diff --git a/app/Models/ProjectTaskFile.php b/app/Models/ProjectTaskFile.php new file mode 100644 index 00000000..c2a824ba --- /dev/null +++ b/app/Models/ProjectTaskFile.php @@ -0,0 +1,41 @@ + -
+
+
+ + {{$L('任务名称')}} + {{$L('计划时间')}} + {{$L('负责人')}} + + + + + + + + + + + + +
+
@@ -65,6 +90,7 @@ export default { return { advanced: false, columns: [], + subName: '', taskPlugins: [ 'advlist autolink lists link image charmap print preview hr anchor pagebreak imagetools', @@ -101,11 +127,11 @@ export default { }, computed: { - ...mapState(['projectDetail']), + ...mapState(['userId', 'projectDetail']), }, watch: { projectDetail(detail) { - this.columns = detail.project_column; + this.columns = $A.cloneJSON(detail.project_column); } }, methods: { @@ -168,14 +194,24 @@ export default { }); } }, - taskTimeChange() { - let tempc = $A.date2string(this.value.times, "Y-m-d H:i"); + taskTimeChange(times) { + let tempc = $A.date2string(times, "Y-m-d H:i"); if (tempc[0] && tempc[1]) { if ($A.rightExists(tempc[0], '00:00') && $A.rightExists(tempc[1], '00:00')) { - this.$set(this.value.times, 1, tempc[1].replace("00:00", "23:59")); + this.$set(times, 1, tempc[1].replace("00:00", "23:59")); } } }, + addSubTask() { + if (this.subName.trim() !== '') { + this.value.subtasks.push({ + name: this.subName.trim(), + times: [], + owner: this.userId + }); + this.subName = ''; + } + } } } diff --git a/resources/assets/js/store/state.js b/resources/assets/js/store/state.js index 66298ebc..89b8eccd 100644 --- a/resources/assets/js/store/state.js +++ b/resources/assets/js/store/state.js @@ -179,6 +179,7 @@ export default Object.assign(stateCommon, { project_column: [], project_user: [] }, + projectMsgUnread: 0, cacheProject: {}, cacheUserBasic: {}, diff --git a/resources/assets/sass/iconfont.scss b/resources/assets/sass/iconfont.scss index b7cdb379..476bbb7d 100644 --- a/resources/assets/sass/iconfont.scss +++ b/resources/assets/sass/iconfont.scss @@ -1,8 +1,8 @@ @font-face { font-family: 'iconfont'; /* Project id 2583385 */ - src: url('//at.alicdn.com/t/font_2583385_eusvc29tdm5.woff2?t=1622518656789') format('woff2'), - url('//at.alicdn.com/t/font_2583385_eusvc29tdm5.woff?t=1622518656789') format('woff'), - url('//at.alicdn.com/t/font_2583385_eusvc29tdm5.ttf?t=1622518656789') format('truetype'); + src: url('//at.alicdn.com/t/font_2583385_jz78bezyc7o.woff2?t=1622640071039') format('woff2'), + url('//at.alicdn.com/t/font_2583385_jz78bezyc7o.woff?t=1622640071039') format('woff'), + url('//at.alicdn.com/t/font_2583385_jz78bezyc7o.ttf?t=1622640071039') format('truetype'); } .iconfont { diff --git a/resources/assets/sass/main.scss b/resources/assets/sass/main.scss index eb502888..7112dfc3 100755 --- a/resources/assets/sass/main.scss +++ b/resources/assets/sass/main.scss @@ -371,12 +371,25 @@ padding: 12px 16px; border-radius: 6px; background-color: #f8f8f8; - .ivu-input { - background: transparent; - border-color: transparent; - &:hover, - &:focus { - box-shadow: none; + .enter-input { + .ivu-input { + background: transparent; + border-color: transparent; + &:hover, + &:focus { + box-shadow: none; + } + } + } + .sublist { + .ivu-row { + margin-bottom: 12px; + > div { + padding-right: 7px; + &:last-child { + padding-right: 0; + } + } } } } @@ -411,6 +424,14 @@ height: 14px; } } + &.hidden-input { + .ivu-select-selection { + padding: 0 4px; + .ivu-select-input { + display: none; + } + } + } } .common-avatar { diff --git a/resources/assets/statics/images/project-menu-blue.svg b/resources/assets/statics/images/project-menu-blue.svg deleted file mode 100644 index 841f18a5..00000000 --- a/resources/assets/statics/images/project-menu-blue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/assets/statics/images/project-menu-gray.svg b/resources/assets/statics/images/project-menu-gray.svg deleted file mode 100644 index 738b3600..00000000 --- a/resources/assets/statics/images/project-menu-gray.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/assets/statics/images/project-panel-blue.svg b/resources/assets/statics/images/project-panel-blue.svg deleted file mode 100644 index d6ee61da..00000000 --- a/resources/assets/statics/images/project-panel-blue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/assets/statics/images/project-panel-gray.svg b/resources/assets/statics/images/project-panel-gray.svg deleted file mode 100644 index 88968cfe..00000000 --- a/resources/assets/statics/images/project-panel-gray.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file