no message

This commit is contained in:
kuaifan 2021-06-09 15:26:09 +08:00
parent e6794bce8c
commit dd3df81c63
5 changed files with 528 additions and 271 deletions

View File

@ -16,6 +16,7 @@ use App\Module\Base;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Request; use Request;
use function Swoole\Coroutine\Http\get;
/** /**
* @apiDefine project * @apiDefine project
@ -440,87 +441,6 @@ class ProjectController extends AbstractController
return Base::retError('删除失败'); 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('删除失败'); 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} 添加任务 * {post} 添加任务
* *

View File

@ -35,6 +35,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read int $msg_num * @property-read int $msg_num
* @property-read bool $overdue * @property-read bool $overdue
* @property-read int $percent * @property-read int $percent
* @property-read int $sub_complete
* @property-read int $sub_num * @property-read int $sub_num
* @property-read bool $today * @property-read bool $today
* @property-read \App\Models\Project|null $project * @property-read \App\Models\Project|null $project
@ -77,10 +78,11 @@ class ProjectTask extends AbstractModel
'file_num', 'file_num',
'msg_num', 'msg_num',
'sub_num', 'sub_num',
'dialog_id', 'sub_complete',
'percent', 'percent',
'today', 'today',
'overdue', 'overdue',
'dialog_id',
]; ];
/** /**
@ -107,31 +109,50 @@ class ProjectTask extends AbstractModel
return $this->attributes['msg_num']; 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 * @return int
*/ */
public function getSubNumAttribute() public function getSubNumAttribute()
{ {
if ($this->parent_id > 0) { $this->generateSubTaskData();
return 0;
}
if (!isset($this->attributes['sub_num'])) {
$this->attributes['sub_num'] = self::whereParentId($this->id)->count();
}
return $this->attributes['sub_num']; return $this->attributes['sub_num'];
} }
/** /**
* 对话ID * 子任务已完成数量
* @return int * @return int
*/ */
public function getDialogIdAttribute() public function getSubCompleteAttribute()
{ {
if (!isset($this->attributes['dialog_id'])) { $this->generateSubTaskData();
$this->attributes['dialog_id'] = intval(Project::whereId($this->project_id)->value('dialog_id')); return $this->attributes['sub_complete'];
}
return $this->attributes['dialog_id'];
} }
/** /**
@ -140,22 +161,8 @@ class ProjectTask extends AbstractModel
*/ */
public function getPercentAttribute() public function getPercentAttribute()
{ {
if ($this->parent_id > 0) { $this->generateSubTaskData();
return 0; return $this->attributes['percent'];
}
$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);
} }
/** /**
@ -187,6 +194,18 @@ class ProjectTask extends AbstractModel
return false; 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 * @return \Illuminate\Database\Eloquent\Relations\HasOne
*/ */

View File

@ -43,7 +43,6 @@
<EDropdownItem command="exit">{{$L('退出项目')}}</EDropdownItem> <EDropdownItem command="exit">{{$L('退出项目')}}</EDropdownItem>
</EDropdownMenu> </EDropdownMenu>
</EDropdown> </EDropdown>
</li> </li>
</ul> </ul>
<div class="project-switch"> <div class="project-switch">
@ -66,7 +65,7 @@
<li v-for="column in projectDetail.project_column" class="column-item"> <li v-for="column in projectDetail.project_column" class="column-item">
<div <div
:class="['column-head', column.color ? 'custom-color' : '']" :class="['column-head', column.color ? 'custom-color' : '']"
:style="column.color ? {backgroundColor: column.color} : null"> :style="column.color ? {backgroundColor: column.color} : {}">
<div class="column-head-title"> <div class="column-head-title">
<AutoTip>{{column.name}}</AutoTip> <AutoTip>{{column.name}}</AutoTip>
<em>({{column.project_task.length}})</em> <em>({{column.project_task.length}})</em>
@ -90,7 +89,7 @@
</div> </div>
</EDropdownItem> </EDropdownItem>
<EDropdownItem divided disabled>{{$L('颜色')}}</EDropdownItem> <EDropdownItem divided disabled>{{$L('颜色')}}</EDropdownItem>
<EDropdownItem v-for="(c, k) in columnList" :key="k" :command="c"> <EDropdownItem v-for="(c, k) in columnColorList" :key="k" :command="c">
<div class="item"> <div class="item">
<i class="iconfont" :style="{color:c.color}" v-html="c.color == column.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}} <i class="iconfont" :style="{color:c.color}" v-html="c.color == column.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div> </div>
@ -123,13 +122,14 @@
<div <div
v-for="item in panelTask(column.project_task)" v-for="item in panelTask(column.project_task)"
:class="['task-item task-draggable', item.complete_at ? 'complete' : '']" :class="['task-item task-draggable', item.complete_at ? 'complete' : '']"
:style="item.color ? {backgroundColor: item.color} : null"> :style="item.color ? {backgroundColor: item.color} : {}">
<div :class="['task-head', item.desc ? 'has-desc' : '']"> <div :class="['task-head', item.desc ? 'has-desc' : '']">
<div class="task-title"><pre>{{item.name}}</pre></div> <div class="task-title"><pre>{{item.name}}</pre></div>
<div v-if="item.loading === true" class="loading"><Loading /></div> <div v-if="item.loading === true" class="loading"><Loading /></div>
<EDropdown <EDropdown
v-else v-else
trigger="click" trigger="click"
size="small"
@command="dropTask(item, $event)"> @command="dropTask(item, $event)">
<Icon type="ios-more" /> <Icon type="ios-more" />
<EDropdownMenu slot="dropdown" class="project-list-more-dropdown-menu"> <EDropdownMenu slot="dropdown" class="project-list-more-dropdown-menu">
@ -154,9 +154,9 @@
</div> </div>
</EDropdownItem> </EDropdownItem>
<EDropdownItem divided disabled>{{$L('背景色')}}</EDropdownItem> <EDropdownItem divided disabled>{{$L('背景色')}}</EDropdownItem>
<EDropdownItem v-for="(c, k) in taskList" :key="k" :command="c"> <EDropdownItem v-for="(c, k) in taskColorList" :key="k" :command="c">
<div class="item"> <div class="item">
<i class="iconfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == column.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}} <i class="iconfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == item.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div> </div>
</EDropdownItem> </EDropdownItem>
</EDropdownMenu> </EDropdownMenu>
@ -176,6 +176,7 @@
<div v-if="item.msg_num > 0" class="task-icon">{{item.msg_num}}<Icon type="ios-chatbubbles-outline" /></div> <div v-if="item.msg_num > 0" class="task-icon">{{item.msg_num}}<Icon type="ios-chatbubbles-outline" /></div>
</div> </div>
<div class="task-progress"> <div class="task-progress">
<div v-if="item.sub_num > 0" class="task-sub-num">{{item.sub_complete}}/{{item.sub_num}}</div>
<Progress :percent="item.percent" :stroke-width="6" /> <Progress :percent="item.percent" :stroke-width="6" />
<ETooltip <ETooltip
v-if="item.end_at" v-if="item.end_at"
@ -216,7 +217,7 @@
</div> </div>
<div v-else class="project-table"> <div v-else class="project-table">
<div class="project-table-head"> <div class="project-table-head">
<Row class="project-row"> <Row class="task-row">
<Col span="12"># {{$L('任务名称')}}</Col> <Col span="12"># {{$L('任务名称')}}</Col>
<Col span="3">{{$L('列表')}}</Col> <Col span="3">{{$L('列表')}}</Col>
<Col span="3">{{$L('优先级')}}</Col> <Col span="3">{{$L('优先级')}}</Col>
@ -226,51 +227,20 @@
</div> </div>
<!--我的任务--> <!--我的任务-->
<div :class="['project-table-body', !taskMyShow ? 'project-table-hide' : '']"> <div :class="['project-table-body', !taskMyShow ? 'project-table-hide' : '']">
<div @click="toggleBoolean('taskMyShow')"> <Row class="task-row">
<Row class="project-row"> <Col span="12" class="row-title">
<Col span="12" class="row-title"> <i class="iconfont" @click="toggleBoolean('taskMyShow')">&#xe689;</i>
<i class="iconfont">&#xe689;</i> <div class="row-h1">{{$L('我的任务')}}</div>
<div class="row-h1">{{$L('我的任务')}}</div> <div class="row-num">({{myList.length}})</div>
<div class="row-num">({{myList.length}})</div> </Col>
</Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> </Row>
</Row> <TaskRow :list="myList" :color-list="taskColorList" @command="dropTask"/>
</div>
<div class="project-rows">
<Row v-for="(item, key) in myList" :key="key" class="project-row">
<Col span="12" class="row-item">
<Icon v-if="item.complete_at" class="completed" type="md-checkmark-circle" />
<Icon v-else type="md-radio-button-off" />
<div class="item-title">{{item.name}}</div>
<div v-if="item.file_num > 0" class="item-icon">{{item.file_num}}<Icon type="ios-link-outline" /></div>
<div v-if="item.msg_num > 0" class="item-icon">{{item.msg_num}}<Icon type="ios-chatbubbles-outline" /></div>
</Col>
<Col span="3">{{item.column_name}}</Col>
<Col span="3"><TaskPriority v-if="item.p_name" :backgroundColor="item.p_color">{{item.p_name}}</TaskPriority></Col>
<Col span="3" class="row-member">
<ul>
<li v-for="(user, keyu) in item.task_user" :key="keyu">
<UserAvatar :userid="user.userid" size="28"/>
</li>
</ul>
</Col>
<Col span="3">
<ETooltip
v-if="item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:open-delay="600"
:content="item.end_at">
<div>{{item.end_at ? expiresFormat(item.end_at) : ''}}</div>
</ETooltip>
</Col>
<em v-if="item.p_name" class="priority-color" :style="{backgroundColor:item.p_color}"></em>
</Row>
</div>
<div @click="addTaskOpen(0)"> <div @click="addTaskOpen(0)">
<Row class="project-row"> <Row class="task-row">
<Col span="12" class="row-add"> <Col span="12" class="row-add">
<Icon type="ios-add" /> {{$L('添加任务')}} <Icon type="ios-add" /> {{$L('添加任务')}}
</Col> </Col>
@ -283,95 +253,33 @@
</div> </div>
<!--未完成任务--> <!--未完成任务-->
<div :class="['project-table-body', !taskUndoneShow ? 'project-table-hide' : '']"> <div :class="['project-table-body', !taskUndoneShow ? 'project-table-hide' : '']">
<div @click="toggleBoolean('taskUndoneShow')"> <Row class="task-row">
<Row class="project-row"> <Col span="12" class="row-title">
<Col span="12" class="row-title"> <i class="iconfont" @click="toggleBoolean('taskUndoneShow')">&#xe689;</i>
<i class="iconfont">&#xe689;</i> <div class="row-h1">{{$L('未完成任务')}}</div>
<div class="row-h1">{{$L('未完成任务')}}</div> <div class="row-num">({{undoneList.length}})</div>
<div class="row-num">({{undoneList.length}})</div> </Col>
</Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> </Row>
</Row> <TaskRow :list="undoneList" :color-list="taskColorList" @command="dropTask"/>
</div>
<div class="project-rows">
<Row v-for="(item, key) in undoneList" :key="key" class="project-row">
<Col span="12" class="row-item">
<Icon v-if="item.complete_at" class="completed" type="md-checkmark-circle" />
<Icon v-else type="md-radio-button-off" />
<div class="item-title">{{item.name}}</div>
<div v-if="item.file_num > 0" class="item-icon">{{item.file_num}}<Icon type="ios-link-outline" /></div>
<div v-if="item.msg_num > 0" class="item-icon">{{item.msg_num}}<Icon type="ios-chatbubbles-outline" /></div>
</Col>
<Col span="3">{{item.column_name}}</Col>
<Col span="3"><TaskPriority v-if="item.p_name" :backgroundColor="item.p_color">{{item.p_name}}</TaskPriority></Col>
<Col span="3" class="row-member">
<ul>
<li v-for="(user, keyu) in item.task_user" :key="keyu">
<UserAvatar :userid="user.userid" size="28"/>
</li>
</ul>
</Col>
<Col span="3">
<ETooltip
v-if="item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:open-delay="600"
:content="item.end_at">
<div>{{item.end_at ? expiresFormat(item.end_at) : ''}}</div>
</ETooltip>
</Col>
<em v-if="item.p_name" class="priority-color" :style="{backgroundColor:item.p_color}"></em>
</Row>
</div>
</div> </div>
<!--已完成任务--> <!--已完成任务-->
<div :class="['project-table-body', !taskCompletedShow ? 'project-table-hide' : '']"> <div :class="['project-table-body', !taskCompletedShow ? 'project-table-hide' : '']">
<div @click="toggleBoolean('taskCompletedShow')"> <Row class="task-row">
<Row class="project-row"> <Col span="12" class="row-title">
<Col span="12" class="row-title"> <i class="iconfont" @click="toggleBoolean('taskCompletedShow')">&#xe689;</i>
<i class="iconfont">&#xe689;</i> <div class="row-h1">{{$L('已完成任务')}}</div>
<div class="row-h1">{{$L('已完成任务')}}</div> <div class="row-num">({{completedList.length}})</div>
<div class="row-num">({{completedList.length}})</div> </Col>
</Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> <Col span="3"></Col>
<Col span="3"></Col> </Row>
</Row> <TaskRow :list="completedList" :color-list="taskColorList" @command="dropTask"/>
</div>
<div class="project-rows">
<Row v-for="(item, key) in completedList" :key="key" class="project-row">
<Col span="12" class="row-item">
<Icon v-if="item.complete_at" class="completed" type="md-checkmark-circle" />
<Icon v-else type="md-radio-button-off" />
<div class="item-title">{{item.name}}</div>
<div v-if="item.file_num > 0" class="item-icon">{{item.file_num}}<Icon type="ios-link-outline" /></div>
<div v-if="item.msg_num > 0" class="item-icon">{{item.msg_num}}<Icon type="ios-chatbubbles-outline" /></div>
</Col>
<Col span="3">{{item.column_name}}</Col>
<Col span="3"><TaskPriority v-if="item.p_name" :backgroundColor="item.p_color">{{item.p_name}}</TaskPriority></Col>
<Col span="3" class="row-member">
<ul>
<li v-for="(user, keyu) in item.task_user" :key="keyu">
<UserAvatar :userid="user.userid" size="28"/>
</li>
</ul>
</Col>
<Col span="3">
<ETooltip
v-if="item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:open-delay="600"
:content="item.end_at">
<div>{{item.end_at ? expiresFormat(item.end_at) : ''}}</div>
</ETooltip>
</Col>
<em v-if="item.p_name" class="priority-color" :style="{backgroundColor:item.p_color}"></em>
</Row>
</div>
</div> </div>
</div> </div>
@ -455,9 +363,10 @@ import TaskAdd from "./TaskAdd";
import {mapState} from "vuex"; import {mapState} from "vuex";
import UserInput from "../../../components/UserInput"; import UserInput from "../../../components/UserInput";
import TaskAddSimple from "./TaskAddSimple"; import TaskAddSimple from "./TaskAddSimple";
import TaskRow from "./TaskRow";
export default { export default {
name: "ProjectList", name: "ProjectList",
components: {Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority}, components: {TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority},
data() { data() {
return { return {
nowTime: Math.round(new Date().getTime() / 1000), nowTime: Math.round(new Date().getTime() / 1000),
@ -495,7 +404,7 @@ export default {
transferData: {}, transferData: {},
transferLoad: 0, transferLoad: 0,
columnList: [ columnColorList: [
{name: '默认', color: ''}, {name: '默认', color: ''},
{name: '灰色', color: '#6C6F71'}, {name: '灰色', color: '#6C6F71'},
{name: '棕色', color: '#695C56'}, {name: '棕色', color: '#695C56'},
@ -508,7 +417,7 @@ export default {
{name: '红色', color: '#9D6058'}, {name: '红色', color: '#9D6058'},
], ],
taskList: [ taskColorList: [
{name: '默认', color: ''}, {name: '默认', color: ''},
{name: '黄色', color: '#FCF4A7'}, {name: '黄色', color: '#FCF4A7'},
{name: '蓝色', color: '#BCF2FD'}, {name: '蓝色', color: '#BCF2FD'},
@ -698,7 +607,9 @@ export default {
success: ({ret, data, msg}) => { success: ({ret, data, msg}) => {
if (ret === 1) { if (ret === 1) {
$A.messageSuccess(msg); $A.messageSuccess(msg);
this.addTaskSuccess(data) this.addTaskSuccess(Object.assign(data, {
top: !!this.addData.top
}))
this.addShow = false; this.addShow = false;
this.addData = { this.addData = {
owner: 0, owner: 0,
@ -723,7 +634,7 @@ export default {
addTaskOpen(column_id) { addTaskOpen(column_id) {
if ($A.isJson(column_id)) { if ($A.isJson(column_id)) {
this.addData = Object.assign(this.addData, column_id); this.addData = Object.assign({}, this.addData, column_id);
} else { } else {
this.$set(this.addData, 'owner', this.userId); this.$set(this.addData, 'owner', this.userId);
this.$set(this.addData, 'column_id', column_id); this.$set(this.addData, 'column_id', column_id);
@ -962,6 +873,7 @@ export default {
Object.keys(data).forEach(key => { Object.keys(data).forEach(key => {
this.$set(task, key, data[key]); this.$set(task, key, data[key]);
}); });
if (data.parent_id) this.getTaskOne(data.parent_id);
} else { } else {
Object.keys(updata).forEach(key => { Object.keys(updata).forEach(key => {
this.$set(task, key, backup[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) { archivedOrRemoveTask(task, type) {
if (task.loading === true) { if (task.loading === true) {
return; return;

View File

@ -0,0 +1,220 @@
<template>
<div class="task-rows">
<div v-for="(item, key) in list" :key="key">
<Row class="task-row" :style="item.color ? {backgroundColor: item.color, borderBottomColor: item.color} : {}">
<em v-if="item.p_name && item.parent_id === 0" class="priority-color" :style="{backgroundColor:item.p_color}"></em>
<Col span="12" :class="['row-item', item.complete_at ? 'complete' : '']">
<Icon
v-if="item.sub_num > 0"
:class="['sub-icon', item.sub_open ? 'active' : '']"
type="ios-arrow-forward"
@click="getSublist(item)"/>
<div v-if="item.loading === true" class="loading"><Loading /></div>
<EDropdown
v-else
trigger="click"
size="small"
@command="dropTask(item, $event)">
<div>
<Icon v-if="item.complete_at" class="completed" type="md-checkmark-circle" />
<Icon v-else type="md-radio-button-off" />
</div>
<EDropdownMenu slot="dropdown" class="project-list-more-dropdown-menu">
<EDropdownItem v-if="item.complete_at" command="uncomplete">
<div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
</div>
</EDropdownItem>
<EDropdownItem v-else command="complete">
<div class="item">
<Icon type="md-radio-button-off" />{{$L('完成')}}
</div>
</EDropdownItem>
<EDropdownItem command="archived">
<div class="item">
<Icon type="ios-filing" />{{$L('归档')}}
</div>
</EDropdownItem>
<EDropdownItem command="delete">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
<template v-if="item.parent_id === 0">
<EDropdownItem v-if="item.parent_id === 0" divided disabled>{{$L('背景色')}}</EDropdownItem>
<EDropdownItem v-for="(c, k) in colorList" :key="k" :command="c">
<div class="item">
<i class="iconfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == item.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div>
</EDropdownItem>
</template>
</EDropdownMenu>
</EDropdown>
<div class="item-title">{{item.name}}</div>
<div v-if="item.sub_num > 0" class="item-sub-num" @click="getSublist(item)">
<Icon type="md-git-merge" />
{{item.sub_complete}}/{{item.sub_num}}
</div>
<div class="item-icons">
<div v-if="item.file_num > 0" class="item-icon">{{item.file_num}}<Icon type="ios-link-outline" /></div>
<div v-if="item.msg_num > 0" class="item-icon">{{item.msg_num}}<Icon type="ios-chatbubbles-outline" /></div>
</div>
</Col>
<Col span="3">
<div v-if="item.parent_id === 0">{{item.column_name}}</div>
</Col>
<Col span="3">
<TaskPriority v-if="item.p_name && item.parent_id === 0" :backgroundColor="item.p_color">{{item.p_name}}</TaskPriority>
</Col>
<Col span="3" class="row-member">
<ul>
<li v-for="(user, keyu) in item.task_user" :key="keyu" v-if="keyu < 3">
<UserAvatar :userid="user.userid" size="28"/>
</li>
</ul>
</Col>
<Col span="3">
<ETooltip
v-if="!item.complete_at && item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:open-delay="600"
:content="item.end_at">
<div>{{expiresFormat(item.end_at)}}</div>
</ETooltip>
</Col>
</Row>
<TaskRow
v-if="item.sub_open===true"
:list="item.sub_list"
:color-list="colorList"
@command="dropTask"/>
</div>
</div>
</template>
<script>
import TaskPriority from "./TaskPriority";
export default {
name: "TaskRow",
components: {TaskPriority},
props: {
list: {
type: Array,
default: () => {
return [];
}
},
colorList: {
type: Array,
default: () => {
return [];
}
},
},
data() {
return {
nowTime: Math.round(new Date().getTime() / 1000),
nowInterval: null,
}
},
mounted() {
this.nowInterval = setInterval(() => {
this.nowTime = Math.round(new Date().getTime() / 1000);
}, 1000)
},
destroyed() {
clearInterval(this.nowInterval)
},
computed: {
expiresFormat() {
const {nowTime} = this;
return function (date) {
let time = Math.round(new Date(date).getTime() / 1000) - nowTime;
if (time < 86400 * 4 && time > 0 ) {
return this.formatSeconds(time);
} else if (time <= 0) {
return '-' + this.formatSeconds(time * -1);
}
return this.formatTime(date)
}
},
},
watch: {
},
methods: {
dropTask(task, command) {
this.$emit("command", task, command)
},
getSublist(task) {
if (task.sub_open === true) {
this.$set(task, 'sub_open', false);
return;
}
if (task.loading === true) {
return;
}
this.$set(task, 'loading', true);
$A.apiAjax({
url: 'project/task/sublist',
data: {
task_id: task.id,
},
complete: () => {
this.$set(task, 'loading', false);
},
error: () => {
$A.modalAlert('网络繁忙,请稍后再试!');
},
success: ({ret, data, msg}) => {
if (ret === 1) {
this.$set(task, 'sub_list', data);
this.$set(task, 'sub_open', true);
} else {
$A.modalError(msg);
}
}
});
},
formatTime(date) {
let time = Math.round(new Date(date).getTime() / 1000),
string = '';
if ($A.formatDate('Ymd') === $A.formatDate('Ymd', time)) {
string = $A.formatDate('H:i', time)
} else if ($A.formatDate('Y') === $A.formatDate('Y', time)) {
string = $A.formatDate('m-d', time)
} else {
string = $A.formatDate('Y-m-d', time)
}
return string || '';
},
formatBit(val) {
val = +val
return val > 9 ? val : '0' + val
},
formatSeconds(second) {
let duration
let days = Math.floor(second / 86400);
let hours = Math.floor((second % 86400) / 3600);
let minutes = Math.floor(((second % 86400) % 3600) / 60);
let seconds = Math.floor(((second % 86400) % 3600) % 60);
if (days > 0) {
if (hours > 0) duration = days + "d," + this.formatBit(hours) + "h";
else if (minutes > 0) duration = days + "d," + this.formatBit(minutes) + "min";
else if (seconds > 0) duration = days + "d," + this.formatBit(seconds) + "s";
else duration = days + "d";
}
else if (hours > 0) duration = this.formatBit(hours) + ":" + this.formatBit(minutes) + ":" + this.formatBit(seconds);
else if (minutes > 0) duration = this.formatBit(minutes) + ":" + this.formatBit(seconds);
else if (seconds > 0) duration = this.formatBit(seconds) + "s";
return duration;
},
}
}
</script>

View File

@ -389,6 +389,11 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
.task-sub-num {
font-size: 12px;
margin-right: 8px;
color: #777777;
}
.task-time { .task-time {
flex-shrink: 0; flex-shrink: 0;
color: #777777; color: #777777;
@ -448,10 +453,10 @@
} }
.project-table { .project-table {
height: 100%; height: 100%;
padding-top: 18px; margin-top: 18px;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
.project-row { .task-row {
background-color: #ffffff; background-color: #ffffff;
border-bottom: 1px solid #F4F4F5; border-bottom: 1px solid #F4F4F5;
position: relative; position: relative;
@ -466,6 +471,12 @@
&:last-child { &:last-child {
border-right: 0; border-right: 0;
} }
&.complete {
.item-title {
color: #aaaaaa;
text-decoration: line-through;
}
}
} }
.priority-color { .priority-color {
position: absolute; position: absolute;
@ -483,7 +494,7 @@
border-bottom: 0; border-bottom: 0;
overflow: hidden; overflow: hidden;
&.project-table-hide { &.project-table-hide {
.project-rows { .task-rows {
display: none; display: none;
} }
.row-title { .row-title {
@ -494,7 +505,7 @@
} }
} }
.project-table-head { .project-table-head {
.project-row { .task-row {
> div { > div {
color: #888888; color: #888888;
font-size: 13px; font-size: 13px;
@ -507,7 +518,33 @@
&:hover { &:hover {
box-shadow: 0 0 10px #e6ecfa; 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 { > div {
padding: 10px 12px; padding: 10px 12px;
.task-time { .task-time {
@ -546,29 +583,82 @@
} }
} }
&.row-item { &.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 { .ivu-icon {
font-size: 16px; font-size: 16px;
color: #dddddd; color: #cccccc;
margin-right: 8px;
&.completed { &.completed {
color: #87d068; 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 { .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; font-size: 12px;
margin-right: 8px; .ivu-icon {
color: #777777; transform: scale(0.9);
.ivu-icon, font-size: 12px;
.iconfont { color: #333333;
margin-left: 1px; margin-right: 4px;
font-size: 14px;
color: #666666;
} }
.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 { > ul {
display: flex; display: flex;
align-items: center; align-items: center;
overflow: auto;
margin-left: -4px; margin-left: -4px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
> li { > li {
list-style: none; list-style: none;
margin-left: -6px; margin-left: -6px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
&:first-child { &:first-child {
margin-left: 0; margin-left: 0;
} }
@ -600,6 +695,7 @@
} }
} }
} }
} }
} }
} }