From 74fa8ff9a1e2025a6da37822bbc147cf93e78e28 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Sun, 13 Jun 2021 23:10:49 +0800 Subject: [PATCH] no message --- app/Http/Controllers/Api/DialogController.php | 39 +- .../Controllers/Api/ProjectController.php | 81 ++- app/Http/Middleware/VerifyCsrfToken.php | 3 + app/Models/ProjectTask.php | 59 ++- app/Models/ProjectTaskFile.php | 20 + app/Models/WebSocketDialogMsg.php | 4 +- app/Module/Base.php | 44 ++ resources/assets/js/pages/manage.vue | 2 +- .../js/pages/manage/components/DialogView.vue | 2 +- .../pages/manage/components/ProjectList.vue | 29 +- .../pages/manage/components/TaskAddSimple.vue | 10 +- .../js/pages/manage/components/TaskDetail.vue | 490 ++++++++++++++---- .../js/pages/manage/components/TaskUpload.vue | 107 ++++ resources/assets/js/store/actions.js | 81 ++- .../sass/pages/components/task-detail.scss | 116 +++-- 15 files changed, 835 insertions(+), 252 deletions(-) create mode 100644 resources/assets/js/pages/manage/components/TaskUpload.vue diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index ff3029ac..e669e98b 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -247,43 +247,8 @@ class DialogController extends AbstractController if (Base::isError($data)) { return Base::retError($data['msg']); } else { - $fileData = $data['data']; - $fileData['thumb'] = $fileData['thumb'] ?: 'images/ext/file.png'; - switch ($fileData['ext']) { - case "docx": - $fileData['thumb'] = 'images/ext/doc.png'; - break; - case "xlsx": - $fileData['thumb'] = 'images/ext/xls.png'; - break; - case "pptx": - $fileData['thumb'] = 'images/ext/ppt.png'; - break; - case "ai": - case "avi": - case "bmp": - case "cdr": - case "doc": - case "eps": - case "gif": - case "mov": - case "mp3": - case "mp4": - case "pdf": - case "ppt": - case "pr": - case "psd": - case "rar": - case "svg": - case "tif": - case "txt": - case "xls": - case "zip": - $fileData['thumb'] = 'images/ext/' . $fileData['ext'] . '.png'; - break; - } - // - $msg = $fileData; + $msg = $data['data']; + $msg['thumb'] = Base::unFillUrl($msg['thumb']); $msg['size'] *= 1024; // return WebSocketDialogMsg::sendMsg($dialog_id, 'file', $msg, $user->userid, $extra_int, $extra_str); diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index d4d76fca..654af2b5 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -7,6 +7,7 @@ use App\Models\Project; use App\Models\ProjectColumn; use App\Models\ProjectLog; use App\Models\ProjectTask; +use App\Models\ProjectTaskFile; use App\Models\ProjectUser; use App\Models\User; use App\Module\Base; @@ -723,7 +724,7 @@ class ProjectController extends AbstractController * @apiParam {String} name 任务描述 * @apiParam {String} [content] 任务详情 * @apiParam {Array} [times] 计划时间(格式:开始时间,结束时间;如:2020-01-01 00:00,2020-01-01 23:59) - * @apiParam {mixed} [owner] 负责人,留空为自己 + * @apiParam {Number} [owner] 负责人,留空为自己 * @apiParam {Array} [subtasks] 子任务(格式:[{name,owner,times}]) * @apiParam {Number} [top] 添加的任务排到列表最前面 */ @@ -750,9 +751,6 @@ class ProjectController extends AbstractController // 列表 $column = null; $newColumn = null; - if (is_array($column_id)) { - $column_id = Base::arrayFirst($column_id); - } if ($column_id) { if (intval($column_id) > 0) { $column = $project->projectColumn->find($column_id); @@ -760,6 +758,8 @@ class ProjectController extends AbstractController if (empty($column)) { $column = ProjectColumn::whereProjectId($project->id)->whereName($column_id)->first(); } + } else { + $column = ProjectColumn::whereProjectId($project->id)->orderBy('id')->first(); } if (empty($column)) { $column = ProjectColumn::createInstance([ @@ -798,7 +798,8 @@ class ProjectController extends AbstractController * @apiParam {String} [color] 任务描述(子任务不支持) * @apiParam {String} [content] 任务详情(子任务不支持) * @apiParam {Array} [times] 计划时间(格式:开始时间,结束时间;如:2020-01-01 00:00,2020-01-01 23:59) - * @apiParam {mixed} [owner] 修改负责人 + * @apiParam {Number} [owner] 修改负责人 + * @apiParam {Array} [assist] 修改协助人员 * * @apiParam {String|false} [complete_at] 完成时间(如:2020-01-01 00:00,false表示未完成) */ @@ -850,6 +851,76 @@ class ProjectController extends AbstractController return $result; } + /** + * {post} 上传文件 + * + * @apiParam {Number} task_id 任务ID + * @apiParam {String} [filename] post-文件名称 + * @apiParam {String} [image64] post-base64图片(二选一) + * @apiParam {File} [files] post-文件对象(二选一) + */ + public function task__upload() + { + $user = User::authE(); + if (Base::isError($user)) { + return $user; + } else { + $user = User::IDE($user['data']); + } + // + $task_id = Base::getPostInt('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('项目不存在或不在成员列表内'); + } + // + $path = "uploads/task/" . $task->id . "/"; + $image64 = Base::getPostValue('image64'); + $fileName = Base::getPostValue('filename'); + if ($image64) { + $data = Base::image64save([ + "image64" => $image64, + "path" => $path, + "fileName" => $fileName, + ]); + } else { + $data = Base::upload([ + "file" => Request::file('files'), + "type" => 'file', + "path" => $path, + "fileName" => $fileName, + ]); + } + // + if (Base::isError($data)) { + return Base::retError($data['msg']); + } else { + $fileData = $data['data']; + $file = ProjectTaskFile::createInstance([ + 'project_id' => $task->project_id, + 'task_id' => $task->id, + 'name' => $fileData['name'], + 'size' => $fileData['size'] * 1024, + 'ext' => $fileData['ext'], + 'path' => $fileData['path'], + 'thumb' => Base::unFillUrl($fileData['thumb']), + 'userid' => $user->userid, + ]); + $file->save(); + return Base::retSuccess("上传成功", $file->find($file->id)); + } + } + /** * 归档任务 * diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 638a74c3..1074486d 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -27,6 +27,9 @@ class VerifyCsrfToken extends Middleware // 修改任务 'api/project/task/update/', + // 上传任务问题 + 'api/project/task/upload/', + // 聊天发文件 'api/dialog/msg/sendfile/', ]; diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php index 4fee6f20..642a3907 100644 --- a/app/Models/ProjectTask.php +++ b/app/Models/ProjectTask.php @@ -299,10 +299,7 @@ class ProjectTask extends AbstractModel } } // 负责人 - if (is_array($owner)) { - $owner = Base::arrayFirst($owner); - } - $owner = $owner ?: User::token2userid(); + $owner = intval($owner) ?: User::token2userid(); if (!ProjectUser::whereProjectId($project_id)->whereUserid($owner)->exists()) { return Base::retError($retPre . '负责人填写错误'); } @@ -360,9 +357,6 @@ class ProjectTask extends AbstractModel public function updateTask($data) { return AbstractModel::transaction(function () use ($data) { - $content = $data['content']; - $times = $data['times']; - $owner = $data['owner']; // 标题 if (Arr::exists($data, 'name')) { if (empty($data['name'])) { @@ -373,25 +367,48 @@ class ProjectTask extends AbstractModel $this->name = $data['name']; } // 负责人 - if ($owner) { - if (is_array($owner)) { - $owner = Base::arrayFirst($owner); - } - $ownerUser = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->first(); - if ($ownerUser->userid != $owner) { - $ownerUser->owner = 0; - $ownerUser->save(); + if (Arr::exists($data, 'owner')) { + $row = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->first(); + if ($row->userid != $data['owner']) { + if (!User::find(intval($data['owner']))) { + return Base::retError('请选择正确的负责人'); + } + $row->owner = 0; + $row->save(); ProjectTaskUser::updateInsert([ 'project_id' => $this->parent_id, 'task_id' => $this->id, - 'userid' => $owner, + 'userid' => $data['owner'], ], [ 'owner' => 1, ]); } } + // 协助人员 + if (Arr::exists($data, 'assist')) { + $array = []; + $assist = is_array($data['assist']) ? $data['assist'] : [$data['assist']]; + foreach ($assist as $uid) { + if (intval($uid) == 0) continue; + if (ProjectTaskUser::whereTaskId($this->id)->whereUserid($uid)->whereOwner(1)->exists()) continue; + // + if (!ProjectTaskUser::whereTaskId($this->id)->whereUserid($uid)->where('owner', '!=', 1)->exists()) { + ProjectTaskUser::createInstance([ + 'project_id' => $this->parent_id, + 'task_id' => $this->id, + 'userid' => $uid, + 'owner' => 0, + ])->save(); + } + $array[] = $uid; + } + ProjectTaskUser::whereTaskId($this->id)->where('owner', '!=', 1)->whereNotIn('userid', $array)->delete(); + } // 计划时间 - if ($times) { + if (Arr::exists($data, 'times')) { + $this->start_at = null; + $this->end_at = null; + $times = $data['times']; list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []); if (Base::isDate($start) && Base::isDate($end)) { if ($start != $end) { @@ -407,14 +424,14 @@ class ProjectTask extends AbstractModel $this->color = $data['color']; } // 内容 - if ($content && $this->parent_id === 0) { + if (Arr::exists($data, 'content')) { ProjectTaskContent::updateInsert([ 'project_id' => $this->parent_id, 'task_id' => $this->id, ], [ - 'content' => $content, + 'content' => $data['content'], ]); - $this->desc = Base::getHtml($content); + $this->desc = Base::getHtml($data['content']); } // 优先级 if (Arr::exists($data, 'p_level')) { @@ -428,6 +445,8 @@ class ProjectTask extends AbstractModel } } $this->save(); + if ($this->start_at instanceof \DateTimeInterface) $this->start_at = $this->start_at->format('Y-m-d H:i:s'); + if ($this->end_at instanceof \DateTimeInterface) $this->end_at = $this->end_at->format('Y-m-d H:i:s'); return Base::retSuccess('修改成功'); }); } diff --git a/app/Models/ProjectTaskFile.php b/app/Models/ProjectTaskFile.php index c2a824ba..7b3307b3 100644 --- a/app/Models/ProjectTaskFile.php +++ b/app/Models/ProjectTaskFile.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Module\Base; + /** * Class ProjectTaskFile * @@ -37,5 +39,23 @@ namespace App\Models; */ class ProjectTaskFile extends AbstractModel { + /** + * 地址 + * @param $value + * @return string + */ + public function getPathAttribute($value) + { + return Base::fillUrl($value); + } + /** + * 缩略图 + * @param $value + * @return string + */ + public function getThumbAttribute($value) + { + return Base::fillUrl($value ?: Base::extIcon($this->ext)); + } } diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php index 70bc9cfb..bf1ac009 100644 --- a/app/Models/WebSocketDialogMsg.php +++ b/app/Models/WebSocketDialogMsg.php @@ -79,8 +79,8 @@ class WebSocketDialogMsg extends AbstractModel $value = Base::json2array($value); if ($this->type === 'file') { $value['type'] = in_array($value['ext'], ['jpg', 'jpeg', 'png', 'gif']) ? 'img' : 'file'; - $value['url'] = Base::fillUrl($value['path']); - $value['thumb'] = Base::fillUrl($value['thumb']); + $value['path'] = Base::fillUrl($value['path']); + $value['thumb'] = Base::fillUrl($value['thumb'] ?: Base::extIcon($value['ext'])); } return $value; } diff --git a/app/Module/Base.php b/app/Module/Base.php index 39afa96b..50b3a055 100755 --- a/app/Module/Base.php +++ b/app/Module/Base.php @@ -2486,6 +2486,50 @@ class Base return true; } + /** + * 获取后缀名图标相对地址 + * @param $ext + * @return string + */ + public static function extIcon($ext) + { + $value = 'images/ext/file.png'; + switch ($ext) { + case "docx": + $value = 'images/ext/doc.png'; + break; + case "xlsx": + $value = 'images/ext/xls.png'; + break; + case "pptx": + $value = 'images/ext/ppt.png'; + break; + case "ai": + case "avi": + case "bmp": + case "cdr": + case "doc": + case "eps": + case "gif": + case "mov": + case "mp3": + case "mp4": + case "pdf": + case "ppt": + case "pr": + case "psd": + case "rar": + case "svg": + case "tif": + case "txt": + case "xls": + case "zip": + $value = 'images/ext/' . $ext . '.png'; + break; + } + return $value; + } + /** * 排列组合(无重复) * @param $arr diff --git a/resources/assets/js/pages/manage.vue b/resources/assets/js/pages/manage.vue index ad1d3836..dcc40c3a 100644 --- a/resources/assets/js/pages/manage.vue +++ b/resources/assets/js/pages/manage.vue @@ -110,7 +110,7 @@ maxWidth: projectOpenTask._dialog || projectOpenTask._msgText ? '1200px' : '640px' }" footer-hide> - + diff --git a/resources/assets/js/pages/manage/components/DialogView.vue b/resources/assets/js/pages/manage/components/DialogView.vue index 328a580e..b922f871 100644 --- a/resources/assets/js/pages/manage/components/DialogView.vue +++ b/resources/assets/js/pages/manage/components/DialogView.vue @@ -7,7 +7,7 @@
- +
diff --git a/resources/assets/js/pages/manage/components/ProjectList.vue b/resources/assets/js/pages/manage/components/ProjectList.vue index eb97eae3..13014971 100644 --- a/resources/assets/js/pages/manage/components/ProjectList.vue +++ b/resources/assets/js/pages/manage/components/ProjectList.vue @@ -107,7 +107,6 @@ :add-top="true" @on-close="column.addTopShow=false" @on-priority="addTaskOpen" - @on-success="addTaskSuccess" auto-active/>
+ @on-priority="addTaskOpen"/>
@@ -589,13 +587,8 @@ export default { onAddTask() { this.taskLoad++; - this.$store.dispatch("call", { - url: 'project/task/add', - data: this.addData, - method: 'post', - }).then(({data, msg}) => { + this.$store.dispatch("taskAdd", this.addData).then(({msg}) => { this.taskLoad--; - $A.messageSuccess(msg); this.addShow = false; this.addData = { owner: 0, @@ -606,8 +599,7 @@ export default { p_name: '', p_color: '', }; - this.$store.dispatch('projectOne', data.project_id); - this.addTaskSuccess(data) + $A.messageSuccess(msg); }).catch(({msg}) => { this.taskLoad--; $A.modalError(msg); @@ -630,21 +622,6 @@ export default { this.addShow = true; }, - addTaskSuccess(data) { - const {task, in_top, new_column} = data; - if (new_column) { - this.projectDetail.project_column.push(new_column) - } - const column = this.projectDetail.project_column.find(({id}) => id === task.column_id); - if (column) { - if (in_top) { - column.project_task.unshift(task); - } else { - column.project_task.push(task); - } - } - }, - addColumnOpen() { this.addColumnShow = true; this.$nextTick(() => { diff --git a/resources/assets/js/pages/manage/components/TaskAddSimple.vue b/resources/assets/js/pages/manage/components/TaskAddSimple.vue index 43dd29a7..71d5df3b 100644 --- a/resources/assets/js/pages/manage/components/TaskAddSimple.vue +++ b/resources/assets/js/pages/manage/components/TaskAddSimple.vue @@ -151,13 +151,8 @@ export default { return; } this.loadIng++; - this.$store.dispatch("call", { - url: 'project/task/add', - data: this.getData(), - method: 'post', - }).then(({data, msg}) => { + this.$store.dispatch("taskAdd", this.getData()).then(({msg}) => { this.loadIng--; - $A.messageSuccess(msg); this.active = false; this.addData = { owner: 0, @@ -168,8 +163,7 @@ export default { p_name: '', p_color: '', } - this.$store.dispatch('projectOne', data.project_id); - this.$emit("on-success", data) + $A.messageSuccess(msg); }).catch(({msg}) => { this.loadIng--; $A.modalError(msg); diff --git a/resources/assets/js/pages/manage/components/TaskDetail.vue b/resources/assets/js/pages/manage/components/TaskDetail.vue index d9ea270d..b051c6a4 100644 --- a/resources/assets/js/pages/manage/components/TaskDetail.vue +++ b/resources/assets/js/pages/manage/components/TaskDetail.vue @@ -1,5 +1,90 @@ @@ -222,17 +312,26 @@ import {mapState} from "vuex"; import TEditor from "../../../components/TEditor"; import TaskPriority from "./TaskPriority"; import UserInput from "../../../components/UserInput"; +import TaskUpload from "./TaskUpload"; export default { name: "TaskDetail", - components: {UserInput, TaskPriority, TEditor}, + components: {TaskUpload, UserInput, TaskPriority, TEditor}, + props: { + openTask: { + type: Object, + default: () => { + return {}; + } + }, + }, data() { return { taskDetail: {}, - transferShow: false, - transferData: {}, - transferLoad: 0, + ownerShow: false, + ownerData: {}, + ownerLoad: 0, assistShow: false, assistData: {}, @@ -268,6 +367,12 @@ export default { valid_elements : 'a[href|target=_blank],em,strong/b,div[align],span[style],a,br,img[src|alt|witdh|height],pre[class],code', toolbar: 'uploadImages | uploadFiles | bold italic underline forecolor backcolor | codesample | preview screenload' }, + + timeOpen: false, + timeValue: [], + timeOptions: { + shortcuts: [] + }, } }, @@ -285,7 +390,7 @@ export default { }, computed: { - ...mapState(['userId', 'projectOpenTask', 'taskPriority']), + ...mapState(['userId', 'taskPriority']), scrollerStyle() { const {innerHeight, taskDetail} = this; @@ -327,6 +432,18 @@ export default { } }, + cutTime() { + const {nowTime, taskDetail} = this; + let string = ""; + let start_at = Math.round(new Date(taskDetail.start_at).getTime() / 1000); + if (start_at > nowTime) { + string = $A.formatDate('Y/m/d H:i', start_at) + " ~ " + } + let end_at = Math.round(new Date(taskDetail.end_at).getTime() / 1000); + string+= $A.formatDate('Y/m/d H:i', end_at); + return string; + }, + hasFile() { const {taskDetail} = this; return $A.isArray(taskDetail.files) && taskDetail.files.length > 0; @@ -338,15 +455,11 @@ export default { }, getOwner() { - return function (task) { - if (task === undefined) { - task = this.taskDetail; - } - if (!$A.isArray(task.task_user)) { - return null; - } - return task.task_user.find(({owner}) => owner === 1); + const {taskDetail} = this; + if (!$A.isArray(taskDetail.task_user)) { + return null; } + return taskDetail.task_user.find(({owner}) => owner === 1); }, getAssist() { @@ -407,13 +520,75 @@ export default { }, watch: { - projectOpenTask(data) { - this.taskDetail = $A.cloneJSON(data); - if (data._show) this.$nextTick(this.$refs.input.focus) + openTask: { + handler(data) { + this.taskDetail = $A.cloneJSON(data); + }, + immediate: true, + deep: true }, + 'openTask._show' (v) { + if (v) { + this.$nextTick(this.$refs.input.focus) + } else { + this.timeOpen = false; + } + } }, methods: { + initLanguage() { + const lastSecond = (e) => { + return new Date($A.formatDate("Y-m-d 23:59:29", Math.round(e / 1000))) + }; + this.timeOptions = { + shortcuts: [{ + text: this.$L('今天'), + value() { + return [new Date(), lastSecond(new Date().getTime())]; + } + }, { + text: this.$L('明天'), + value() { + let e = new Date(); + e.setDate(e.getDate() + 1); + return [new Date(), lastSecond(e.getTime())]; + } + }, { + text: this.$L('本周'), + value() { + return [$A.getData('今天', true), lastSecond($A.getData('本周结束2', true))]; + } + }, { + text: this.$L('本月'), + value() { + return [$A.getData('今天', true), lastSecond($A.getData('本月结束', true))]; + } + }, { + text: this.$L('3天'), + value() { + let e = new Date(); + e.setDate(e.getDate() + 3); + return [new Date(), lastSecond(e.getTime())]; + } + }, { + text: this.$L('5天'), + value() { + let e = new Date(); + e.setDate(e.getDate() + 5); + return [new Date(), lastSecond(e.getTime())]; + } + }, { + text: this.$L('7天'), + value() { + let e = new Date(); + e.setDate(e.getDate() + 7); + return [new Date(), lastSecond(e.getTime())]; + } + }] + }; + }, + innerHeightListener() { this.innerHeight = window.innerHeight; }, @@ -454,6 +629,28 @@ export default { return duration; }, + onNameKeydown(e) { + if (e.keyCode === 13) { + if (e.shiftKey) { + return; + } + e.preventDefault(); + this.updateData('name'); + } + }, + + dropTask(command) { + if (command === 'complete') { + this.updateData('complete') + } + else if (command === 'uncomplete') { + this.updateData('uncomplete') + } + else if (command === 'delete') { + this.archivedOrRemoveTask('delete'); + } + }, + updateData(action, params) { switch (action) { case 'complete': @@ -470,46 +667,77 @@ export default { this.$set(this.taskDetail, 'p_color', params.color) action = ['p_level', 'p_name', 'p_color']; break; + case 'times': + this.$set(this.taskDetail, 'times', [params.start_at, params.end_at]) + break; } // let dataJson = {task_id: this.taskDetail.id}; ($A.isArray(action) ? action : [action]).forEach(key => { let newData = this.taskDetail[key]; - let originalData = this.projectOpenTask[key]; + let originalData = this.openTask[key]; if ($A.jsonStringify(newData) != $A.jsonStringify(originalData)) { dataJson[key] = newData; } }) if (Object.keys(dataJson).length <= 1) return; // - this.$store.dispatch("taskUpdate", dataJson).then(() => { + this.$store.dispatch("taskUpdate", dataJson).then(({msg}) => { // 更新成功 - }).catch(() => { + $A.messageSuccess(msg); + }).catch(({msg}) => { // 更新失败 + $A.modalError(msg); }) }, - openTransfer() { - this.$set(this.taskDetail, 'owner_userid', [this.getOwner().userid]) - this.$set(this.transferData, 'owner_userid', [this.getOwner().userid]); - this.transferShow = true; + archivedOrRemoveTask(type) { + let typeTitle = this.taskDetail.parent_id > 0 ? '子任务' : '任务'; + $A.modalConfirm({ + title: '删除' + typeTitle, + content: '你确定要删除' + typeTitle + '【' + this.taskDetail.name + '】吗?', + loading: true, + onOk: () => { + if (this.taskDetail.loading === true) { + return; + } + this.$set(this.taskDetail, 'loading', true); + this.$store.dispatch("taskArchivedOrRemove", { + task_id: this.taskDetail.id, + type: type, + }).then(({msg}) => { + this.$Modal.remove(); + $A.messageSuccess(msg); + }).catch(({msg}) => { + this.$Modal.remove(); + $A.modalError(msg, 301); + }); + } + }); }, - onTransfer() { - if ($A.jsonStringify(this.taskDetail.owner_userid) === $A.jsonStringify(this.transferData.owner_userid)) { + openOwner() { + this.$set(this.taskDetail, 'owner_userid', [this.getOwner.userid]) + this.$set(this.ownerData, 'owner_userid', [this.getOwner.userid]); + this.ownerShow = true; + }, + + onOwner() { + if ($A.jsonStringify(this.taskDetail.owner_userid) === $A.jsonStringify(this.ownerData.owner_userid)) { return; } - this.transferLoad++; + this.ownerLoad++; this.$store.dispatch("taskUpdate", { task_id: this.taskDetail.id, - owner: this.transferData.owner_userid - }).then(() => { - this.transferLoad--; - this.transferShow = false; + owner: this.ownerData.owner_userid + }).then(({msg}) => { + this.ownerLoad--; + this.ownerShow = false; this.$store.dispatch("taskOne", this.taskDetail.id); + $A.messageSuccess(msg); }).catch(({msg}) => { - this.transferLoad--; - this.transferShow = false; + this.ownerLoad--; + this.ownerShow = false; $A.modalError(msg); }) }, @@ -518,7 +746,7 @@ export default { const list = this.getAssist.map(({userid}) => userid) this.$set(this.taskDetail, 'assist_userid', list) this.$set(this.assistData, 'assist_userid', list); - this.$set(this.assistData, 'disabled', [this.getOwner().userid]); + this.$set(this.assistData, 'disabled', [this.getOwner.userid]); this.assistShow = true; }, @@ -532,16 +760,58 @@ export default { this.$store.dispatch("taskUpdate", { task_id: this.taskDetail.id, assist, - }).then(() => { + }).then(({msg}) => { this.assistLoad--; this.assistShow = false; this.$store.dispatch("taskOne", this.taskDetail.id); + $A.messageSuccess(msg); }).catch(({msg}) => { this.assistLoad--; this.assistShow = false; $A.modalError(msg); }) - } + }, + + openTime() { + this.timeOpen = !this.timeOpen; + if (this.timeOpen) { + this.timeValue = this.taskDetail.end_at ? [this.taskDetail.start_at, this.taskDetail.end_at] : []; + } + }, + + timeChange(open) { + if (!open) { + this.timeOpen = false; + } + }, + + timeClear() { + $A.modalConfirm({ + content: '你确定要取消任务时间吗?', + cancelText: '不是', + onOk: () => { + this.updateData('times', { + start_at: false, + end_at: false, + }); + this.timeOpen = false; + } + }); + }, + + timeOk() { + let times = $A.date2string(this.timeValue, "Y-m-d H:i"); + if (times[0] && times[1]) { + if ($A.rightExists(times[0], '00:00') && $A.rightExists(times[1], '00:00')) { + times[1] = times[1].replace("00:00", "23:59"); + } + } + this.updateData('times', { + start_at: times[0], + end_at: times[1], + }); + this.timeOpen = false; + }, } } diff --git a/resources/assets/js/pages/manage/components/TaskUpload.vue b/resources/assets/js/pages/manage/components/TaskUpload.vue new file mode 100644 index 00000000..aeacf98a --- /dev/null +++ b/resources/assets/js/pages/manage/components/TaskUpload.vue @@ -0,0 +1,107 @@ + + + diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index e5e4fe46..6ec86655 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -389,6 +389,11 @@ export default { }); if (data.id == state.projectOpenTask.id) { state.projectOpenTask = Object.assign({}, state.projectOpenTask, data); + } else if (data.parent_id == state.projectOpenTask.id) { + let index = state.projectOpenTask.sub_task.findIndex(({id}) => id === data.id); + if (index > -1) { + state.projectOpenTask.sub_task.splice(index, 1, Object.assign(state.projectOpenTask.sub_task[index], data)) + } } }, @@ -522,28 +527,77 @@ export default { }, /** - * 更新任务 + * 添加任务 + * @param state * @param dispatch * @param data * @returns {Promise} */ - taskUpdate({dispatch}, data) { + taskAdd({state, dispatch}, data) { return new Promise(function (resolve, reject) { + const post = state.method.cloneJSON(data); + if (state.method.isArray(post.column_id)) { + post.column_id = post.column_id.find((val) => val) + } + if (state.method.isArray(post.owner)) { + post.owner = post.owner.find((val) => val) + } + // + dispatch("call", { + url: 'project/task/add', + data: post, + method: 'post', + }).then(result => { + const {task, in_top, new_column} = result.data; + if (state.projectDetail.id == task.project_id) { + if (new_column) { + state.projectDetail.project_column.push(new_column); + } + const column = state.projectDetail.project_column.find(({id}) => id === task.column_id); + if (column) { + if (in_top) { + column.project_task.unshift(task); + } else { + column.project_task.push(task); + } + } + } + dispatch('projectOne', task.project_id); + resolve(result) + }).catch(result => { + reject(result) + }); + }); + }, + + /** + * 更新任务 + * @param state + * @param dispatch + * @param data + * @returns {Promise} + */ + taskUpdate({state, dispatch}, data) { + return new Promise(function (resolve, reject) { + const post = state.method.cloneJSON(data); + if (state.method.isArray(post.owner)) { + post.owner = post.owner.find((id) => id) + } dispatch("call", { url: 'project/task/update', - data: data, + data: post, method: 'post', }).then(result => { if (result.data.parent_id) { dispatch('taskOne', result.data.parent_id); } - if (typeof data.complete_at !== "undefined") { + if (typeof post.complete_at !== "undefined") { dispatch('projectOne', result.data.project_id); } dispatch("taskData", result.data); resolve(result) }).catch(result => { - dispatch('taskOne', data.task_id); + dispatch('taskOne', post.task_id); reject(result) }); }); @@ -553,7 +607,7 @@ export default { * 删除或归档任务 * @param state * @param dispatch - * @param data + * @param data {task_id, type} * @returns {Promise} */ taskArchivedOrRemove({state, dispatch}, data) { @@ -565,14 +619,23 @@ export default { task_id, }, }).then(result => { - const column = state.projectDetail.project_column.find(({id}) => id === result.data.column_id); + const {data} = result; + const column = state.projectDetail.project_column.find(({id}) => id === data.column_id); if (column) { - let index = column.project_task.findIndex(({id}) => id === result.data.id); + let index = column.project_task.findIndex(({id}) => id === data.id); if (index > -1) { column.project_task.splice(index, 1); } } - dispatch('projectDetail', result.data.project_id); + if (data.id == state.projectOpenTask.id) { + state.projectOpenTask = Object.assign({}, state.projectOpenTask, {_show: false}); + } else if (data.parent_id == state.projectOpenTask.id) { + let index = state.projectOpenTask.sub_task.findIndex(({id}) => id === data.id); + if (index > -1) { + state.projectOpenTask.sub_task.splice(index, 1) + } + } + dispatch('projectDetail', data.project_id); resolve(result); }).catch(result => { reject(result) diff --git a/resources/assets/sass/pages/components/task-detail.scss b/resources/assets/sass/pages/components/task-detail.scss index a93cd47f..5e843545 100644 --- a/resources/assets/sass/pages/components/task-detail.scss +++ b/resources/assets/sass/pages/components/task-detail.scss @@ -109,18 +109,49 @@ line-height: 26px; .el-dropdown { display: flex; + cursor: pointer; + } + .picker-time { + display: flex; + align-items: center; + line-height: 26px; + .time { + cursor: pointer; + } + .ivu-tag { + margin-left: 10px; + padding: 0 4px; + height: 20px; + line-height: 18px; + .ivu-tag-text { + display: flex; + align-items: center; + > i { + padding-right: 2px; + } + } + } } } &.user { - margin-top: 0; - > li { - display: inline-block; - cursor: pointer; + margin-top: 1px; + cursor: pointer; + .user-list { + > div { + display: inline-block; + margin-right: 6px; + } } } &.file { > li { margin-bottom: 2px; + .file-load { + margin: 0; + padding: 2px; + width: 16px; + height: 16px; + } .file-ext { width: 16px; } @@ -138,17 +169,25 @@ &.subtask { > li { align-items: flex-start; - margin-bottom: 2px; + margin-bottom: 4px; .subtask-icon { width: 16px; height: 26px; line-height: 26px; - font-size: 16px; - color: #cccccc; - margin-right: 8px; + margin-right: 6px; cursor: pointer; - &.completed { - color: #87d068; + .loading { + width: 16px; + height: 16px; + margin: 0; + padding: 2px; + } + .ivu-icon { + font-size: 16px; + color: #cccccc; + &.completed { + color: #87d068; + } } &.sub-icon { font-size: 16px; @@ -168,7 +207,7 @@ margin-right: 16px; display: flex; .ivu-input { - margin: -1px 0; + margin: -2px 0; padding: 4px 0; resize: none; border-color: transparent; @@ -179,31 +218,28 @@ } } .subtask-time { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-right: 6px; - font-size: 13px; - height: 26px; - line-height: 26px; - &.overdue { - font-weight: 600; - color: #ed4014; - } - &.today { - font-weight: 500; - color: #ff9900; + margin-right: 8px; + .time { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 13px; + height: 26px; + line-height: 26px; + cursor: pointer; + &.overdue { + font-weight: 600; + color: #ed4014; + } + &.today { + font-weight: 500; + color: #ff9900; + } } } .subtask-avatar { height: 26px; - line-height: 1; - .avatar-box { - > em { - right: -2px; - bottom: -1px; - } - } + cursor: pointer; } } } @@ -240,6 +276,9 @@ } } } + .upload { + display: none; + } } .task-dialog { flex: 1; @@ -388,4 +427,15 @@ } } - +.task-detail-avatar-buttons { + margin-top: 12px; + margin-bottom: 4px; + text-align: right; + position: absolute; + top: 5px; + right: 14px; + > button { + font-size: 12px; + transform: scale(0.9); + } +}