初步完成工作流

This commit is contained in:
kuaifan 2022-01-09 17:52:46 +08:00
parent 1fe4e80f82
commit b895eec69c
19 changed files with 562 additions and 120 deletions

View File

@ -170,19 +170,6 @@ class ProjectController extends AbstractController
} }
], ],
"project_flow_item": [ // 工作流
{
"id": 9,
"project_id": 2,
"flow_id": 3,
"name": "待处理",
"status": "start",
"turns": [9,10,11,12],
"userids": [],
"sort": 0
}
],
"task_num": 9, "task_num": 9,
"task_complete": 0, "task_complete": 0,
"task_percent": 0, "task_percent": 0,
@ -200,7 +187,6 @@ class ProjectController extends AbstractController
$project = Project::userProject($project_id); $project = Project::userProject($project_id);
$data = array_merge($project->toArray(), $project->getTaskStatistics($user->userid), [ $data = array_merge($project->toArray(), $project->getTaskStatistics($user->userid), [
'project_user' => $project->projectUser, 'project_user' => $project->projectUser,
'project_flow_item' => $project->projectFlowItem
]); ]);
// //
return Base::retSuccess('success', $data); return Base::retSuccess('success', $data);
@ -1216,6 +1202,7 @@ class ProjectController extends AbstractController
* @apiParam {String} [p_name] 优先级相关(子任务不支持) * @apiParam {String} [p_name] 优先级相关(子任务不支持)
* @apiParam {String} [p_color] 优先级相关(子任务不支持) * @apiParam {String} [p_color] 优先级相关(子任务不支持)
* *
* @apiParam {Number} [flow_item_id] 任务状态工作流状态ID
* @apiParam {String|false} [complete_at] 完成时间2020-01-01 00:00false表示未完成 * @apiParam {String|false} [complete_at] 完成时间2020-01-01 00:00false表示未完成
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
@ -1442,26 +1429,73 @@ class ProjectController extends AbstractController
// //
$task_id = intval(Request::input('task_id')); $task_id = intval(Request::input('task_id'));
// //
$projectTask = ProjectTask::select(['id', 'flow_item_id', 'flow_item_name', 'project_id'])->find($task_id); $projectTask = ProjectTask::select(['id', 'project_id', 'complete_at', 'flow_item_id', 'flow_item_name'])->find($task_id);
if (empty($projectTask)) { if (empty($projectTask)) {
return Base::retError('任务不存在', [ 'task_id' => $task_id ], -4002); return Base::retError('任务不存在', [ 'task_id' => $task_id ], -4002);
} }
// //
$projectFlowItem = []; $projectFlowItem = $projectTask->flow_item_id ? ProjectFlowItem::with(['projectFlow'])->find($projectTask->flow_item_id) : null;
$turnBuilder = ProjectFlowItem::select(['id', 'name']); if ($projectFlowItem?->projectFlow) {
if ($projectTask->flow_item_id) { $projectFlow = $projectFlowItem->projectFlow;
$projectFlowItem = ProjectFlowItem::select(['id', 'name', 'turns'])->find($projectTask->flow_item_id); } else {
$projectFlow = ProjectFlow::whereProjectId($projectTask->project_id)->first();
} }
if (empty($projectFlow)) {
return Base::retSuccess('success', [
'task_id' => $projectTask->id,
'flow_item_id' => 0,
'flow_item_name' => '',
'turns' => [],
]);
}
//
$turns = ProjectFlowItem::select(['id', 'name', 'status', 'turns'])->whereFlowId($projectFlow->id)->orderBy('sort')->get();
if (empty($projectFlowItem)) { if (empty($projectFlowItem)) {
$data = [ $data = [
'id' => 0, 'task_id' => $projectTask->id,
'name' => '', 'flow_item_id' => 0,
'turns' => $turnBuilder->whereProjectId($projectTask->project_id)->get() 'flow_item_name' => '',
'turns' => $turns,
]; ];
} else { $assign = null;
$data = $projectFlowItem->toArray(); if ($projectTask->complete_at) {
$data['turns'] = $turnBuilder->whereIn('id', $data['turns'])->get(); // 赋一个结束状态
foreach ($turns as $turn) {
if ($turn->status == 'end' || preg_match("/complete|done|完成/i", $turn->name)) {
$assign = $turn;
break;
} }
}
if (empty($data['flow_item_id'])) {
foreach ($turns as $turn) {
if ($turn->status == 'end') {
$assign = $turn;
break;
}
}
}
} else {
// 赋一个开始状态
foreach ($turns as $turn) {
if ($turn->status == 'start') {
$assign = $turn;
break;
}
}
}
if ($assign) {
$data['flow_item_id'] = $assign->id;
$data['flow_item_name'] = $assign->name;
}
} else {
$data = [
'task_id' => $projectTask->id,
'flow_item_id' => $projectFlowItem->id,
'flow_item_name' => $projectFlowItem->name,
'turns' => $turns,
];
}
//
return Base::retSuccess('success', $data); return Base::retSuccess('success', $data);
} }

View File

@ -26,8 +26,6 @@ use Request;
* @property-read int $owner_userid * @property-read int $owner_userid
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectColumn[] $projectColumn * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectColumn[] $projectColumn
* @property-read int|null $project_column_count * @property-read int|null $project_column_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectFlowItem[] $projectFlowItem
* @property-read int|null $project_flow_item_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectLog[] $projectLog * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectLog[] $projectLog
* @property-read int|null $project_log_count * @property-read int|null $project_log_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser
@ -101,14 +99,6 @@ class Project extends AbstractModel
return $this->hasMany(ProjectUser::class, 'project_id', 'id')->orderBy('id'); return $this->hasMany(ProjectUser::class, 'project_id', 'id')->orderBy('id');
} }
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function projectFlowItem(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(projectFlowItem::class, 'project_id', 'id')->orderBy('sort');
}
/** /**
* 查询所有项目与正常查询多返回owner字段 * 查询所有项目与正常查询多返回owner字段
* @param self $query * @param self $query

View File

@ -17,6 +17,7 @@ use App\Module\Base;
* @property int|null $sort 排序 * @property int|null $sort 排序
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\ProjectFlow|null $projectFlow
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem query() * @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem query()
@ -63,6 +64,14 @@ class ProjectFlowItem extends AbstractModel
return Base::json2array($value); return Base::json2array($value);
} }
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function projectFlow(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(ProjectFlow::class, 'id', 'flow_id');
}
/** /**
* @return bool|null * @return bool|null
*/ */

View File

@ -473,6 +473,45 @@ class ProjectTask extends AbstractModel
public function updateTask($data, &$updateProject = false, &$updateContent = false, &$updateSubTask = false) public function updateTask($data, &$updateProject = false, &$updateContent = false, &$updateSubTask = false)
{ {
AbstractModel::transaction(function () use ($data, &$updateProject, &$updateContent, &$updateSubTask) { AbstractModel::transaction(function () use ($data, &$updateProject, &$updateContent, &$updateSubTask) {
// 工作流
if (Arr::exists($data, 'flow_item_id')) {
if ($this->flow_item_id == $data['flow_item_id']) {
throw new ApiException('任务状态未发生改变');
}
$currentFlowItem = null;
$newFlowItem = ProjectFlowItem::whereProjectId($this->project_id)->find(intval($data['flow_item_id']));
if (empty($newFlowItem) || empty($newFlowItem->projectFlow)) {
throw new ApiException('任务状态不存在');
}
if ($this->flow_item_id) {
// 判断符合流转
$currentFlowItem = ProjectFlowItem::find($this->flow_item_id);
if ($currentFlowItem && !in_array($currentFlowItem->id, $newFlowItem->turns)) {
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
}
}
if ($newFlowItem->status == 'end') {
// 判断自动完成
if (!$this->complete_at) {
$data['complete_at'] = date("Y-m-d H:i");
}
} else {
// 判断自动打开
if ($this->complete_at) {
$data['complete_at'] = false;
}
}
if ($newFlowItem->userids) {
// 判断自动添加负责人
if (!Arr::exists($data, 'owner')) {
$data['owner'] = $this->taskUser->pluck('userid')->toArray();
}
$data['owner'] = array_values(array_unique(array_merge($data['owner'], $newFlowItem->userids)));
}
$this->flow_item_id = $newFlowItem->id;
$this->flow_item_name = $newFlowItem->status . "|" . $newFlowItem->name;
$this->addLog("修改{任务}状态:{$currentFlowItem?->name} => {$newFlowItem->name}");
}
// 状态 // 状态
if (Arr::exists($data, 'complete_at')) { if (Arr::exists($data, 'complete_at')) {
if (Base::isDate($data['complete_at'])) { if (Base::isDate($data['complete_at'])) {

View File

@ -151,7 +151,7 @@
:mask-closable="false" :mask-closable="false"
:styles="{ :styles="{
width: '90%', width: '90%',
maxWidth: taskData.dialog_id ? '1200px' : '640px' maxWidth: taskData.dialog_id ? '1200px' : '700px'
}" }"
@on-visible-change="taskVisibleChange" @on-visible-change="taskVisibleChange"
footer-hide> footer-hide>

View File

@ -125,6 +125,9 @@ export default {
if (data.sub_top === true) { if (data.sub_top === true) {
task.title = `[${this.$L('子任务')}] ${task.title}` task.title = `[${this.$L('子任务')}] ${task.title}`
} }
if (data.flow_item_name) {
task.title = `[${data.flow_item_name}] ${task.title}`
}
if (data.overdue) { if (data.overdue) {
task.title = `[${this.$L('超期')}] ${task.title}` task.title = `[${this.$L('超期')}] ${task.title}`
task.color = "#f56c6c" task.color = "#f56c6c"

View File

@ -151,9 +151,14 @@
:style="item.color ? {backgroundColor: item.color} : {}" :style="item.color ? {backgroundColor: item.color} : {}"
@click="openTask(item)"> @click="openTask(item)">
<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">
<!--工作流状态-->
<span v-if="item.flow_item_name" :class="item.flow_item_status" @click.stop="openMenu(item)">{{item.flow_item_name}}</span>
<!--任务描述-->
<pre>{{item.name}}</pre>
</div>
<div class="task-menu" @click.stop=""> <div class="task-menu" @click.stop="">
<TaskMenu :task="item" icon="ios-more"/> <TaskMenu :ref="`taskMenu_${item.id}`" :task="item" icon="ios-more"/>
</div> </div>
</div> </div>
<div v-if="item.desc" class="task-desc"><pre v-html="item.desc"></pre></div> <div v-if="item.desc" class="task-desc"><pre v-html="item.desc"></pre></div>
@ -1108,6 +1113,13 @@ export default {
} }
}, },
openMenu(task) {
const el = this.$refs[`taskMenu_${task.id}`];
if (el) {
el[0].show()
}
},
taskIsHidden(task) { taskIsHidden(task) {
const {name, desc, complete_at} = task; const {name, desc, complete_at} = task;
const {searchText} = this; const {searchText} = this;

View File

@ -2,7 +2,10 @@
<!--子任务--> <!--子任务-->
<li v-if="ready && taskDetail.parent_id > 0"> <li v-if="ready && taskDetail.parent_id > 0">
<div class="subtask-icon"> <div class="subtask-icon">
<TaskMenu :task="taskDetail" :load-status="taskDetail.loading === true"/> <TaskMenu :ref="`taskMenu_${taskDetail.id}`" :task="taskDetail" :load-status="taskDetail.loading === true"/>
</div>
<div v-if="taskDetail.flow_item_name" class="subtask-flow">
<span :class="taskDetail.flow_item_status" @click.stop="openMenu(taskDetail)">{{taskDetail.flow_item_name}}</span>
</div> </div>
<div class="subtask-name"> <div class="subtask-name">
<Input <Input
@ -61,7 +64,10 @@
<div v-else-if="ready" v-show="taskDetail.id > 0" :class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}"> <div v-else-if="ready" v-show="taskDetail.id > 0" :class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}">
<div class="task-info"> <div class="task-info">
<div class="head"> <div class="head">
<TaskMenu :task="taskDetail" class="icon" size="medium" :color-show="false" quick-completed/> <TaskMenu :ref="`taskMenu_${taskDetail.id}`" :task="taskDetail" class="icon" size="medium" :color-show="false"/>
<div v-if="taskDetail.flow_item_name" class="flow">
<span :class="taskDetail.flow_item_status" @click.stop="openMenu(taskDetail)">{{taskDetail.flow_item_name}}</span>
</div>
<div class="nav"> <div class="nav">
<p v-if="projectName"><span>{{projectName}}</span></p> <p v-if="projectName"><span>{{projectName}}</span></p>
<p v-if="columnName"><span>{{columnName}}</span></p> <p v-if="columnName"><span>{{columnName}}</span></p>
@ -1096,6 +1102,13 @@ export default {
}); });
}, },
openMenu(task) {
const el = this.$refs[`taskMenu_${task.id}`];
if (el) {
el.show()
}
},
openNewWin() { openNewWin() {
let config = { let config = {
parent: null, parent: null,

View File

@ -4,7 +4,8 @@
trigger="click" trigger="click"
:size="size" :size="size"
placement="bottom" placement="bottom"
@command="dropTask"> @command="dropTask"
@visible-change="visibleChange">
<slot name="icon"> <slot name="icon">
<div class="task-menu-icon"> <div class="task-menu-icon">
<div v-if="loadIng" class="loading"><Loading/></div> <div v-if="loadIng" class="loading"><Loading/></div>
@ -15,6 +16,23 @@
</div> </div>
</slot> </slot>
<EDropdownMenu slot="dropdown" class="task-menu-more-dropdown"> <EDropdownMenu slot="dropdown" class="task-menu-more-dropdown">
<li class="task-menu-more-warp" :class="size">
<ul>
<EDropdownItem v-if="!flow" class="load-flow" disabled>
<div class="load-flow-warp">
<Loading/>
</div>
</EDropdownItem>
<template v-else-if="turns.length > 0">
<EDropdownItem v-for="item in turns" :key="item.id" :command="`turn::${item.id}`">
<div class="item flow">
<Icon v-if="item.id == task.flow_item_id && flow.auto_assign !== true" class="check" type="md-checkmark-circle-outline" />
<Icon v-else type="md-radio-button-off" />
<div class="flow-name" :class="item.status">{{item.name}}</div>
</div>
</EDropdownItem>
</template>
<template v-else>
<EDropdownItem v-if="task.complete_at" command="uncomplete"> <EDropdownItem v-if="task.complete_at" command="uncomplete">
<div class="item red"> <div class="item red">
<Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}} <Icon type="md-checkmark-circle-outline" />{{$L('标记未完成')}}
@ -25,7 +43,10 @@
<Icon type="md-radio-button-off" />{{$L('完成')}} <Icon type="md-radio-button-off" />{{$L('完成')}}
</div> </div>
</EDropdownItem> </EDropdownItem>
<EDropdownItem v-if="task.parent_id === 0" command="archived"> </template>
<template v-if="task.parent_id === 0">
<EDropdownItem :divided="turns.length > 0" command="archived">
<div class="item"> <div class="item">
<Icon type="ios-filing" />{{$L('归档')}} <Icon type="ios-filing" />{{$L('归档')}}
</div> </div>
@ -35,13 +56,21 @@
<Icon type="md-trash" />{{$L('删除')}} <Icon type="md-trash" />{{$L('删除')}}
</div> </div>
</EDropdownItem> </EDropdownItem>
<template v-if="task.parent_id === 0 && colorShow"> <template v-if="colorShow">
<EDropdownItem v-for="(c, k) in $store.state.taskColorList" :key="k" :divided="k==0" :command="c"> <EDropdownItem v-for="(c, k) in taskColorList" :key="k" :divided="k==0" :command="c">
<div class="item"> <div class="item">
<i class="taskfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == task.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}} <i class="taskfont" :style="{color:c.color||'#f9f9f9'}" v-html="c.color == task.color ? '&#xe61d;' : '&#xe61c;'"></i>{{$L(c.name)}}
</div> </div>
</EDropdownItem> </EDropdownItem>
</template> </template>
</template>
<EDropdownItem v-else command="remove" :divided="turns.length > 0">
<div class="item">
<Icon type="md-trash" />{{$L('删除')}}
</div>
</EDropdownItem>
</ul>
</li>
</EDropdownMenu> </EDropdownMenu>
</EDropdown> </EDropdown>
</template> </template>
@ -66,10 +95,6 @@ export default {
type: Boolean, type: Boolean,
default: true default: true
}, },
quickCompleted: { //
type: Boolean,
default: false
},
size: { size: {
type: String, type: String,
default: 'small' default: 'small'
@ -89,7 +114,7 @@ export default {
} }
}, },
computed: { computed: {
...mapState(['taskLoading']), ...mapState(['taskColorList', 'taskLoading', 'taskFlows', 'taskFlowItems']),
loadIng() { loadIng() {
if (this.loadStatus) { if (this.loadStatus) {
@ -97,7 +122,22 @@ export default {
} }
const load = this.taskLoading.find(({id}) => id == this.task.id); const load = this.taskLoading.find(({id}) => id == this.task.id);
return load && load.num > 0 return load && load.num > 0
},
flow() {
return this.taskFlows.find(({task_id}) => task_id == this.task.id);
},
turns() {
if (!this.flow) {
return [];
} }
let item = this.taskFlowItems.find(({id}) => id == this.flow.flow_item_id);
if (!item) {
return [];
}
return this.taskFlowItems.filter(({id}) => item.turns.includes(id))
},
}, },
methods: { methods: {
show() { show() {
@ -118,13 +158,20 @@ export default {
} }
return; return;
} }
if ($A.leftExists(command, 'turn::')) {
//
let flow_item_id = $A.leftDelete(command, 'turn::');
if (flow_item_id == this.task.flow_item_id) return;
this.updateTask({
flow_item_id
})
return;
}
switch (command) { switch (command) {
case 'complete': case 'complete':
if (this.task.complete_at) return; if (this.task.complete_at) return;
this.updateTask({ this.updateTask({
complete_at: $A.formatDate("Y-m-d H:i:s") complete_at: $A.formatDate("Y-m-d H:i:s")
}).then(() => {
//
}) })
break; break;
@ -132,8 +179,6 @@ export default {
if (!this.task.complete_at) return; if (!this.task.complete_at) return;
this.updateTask({ this.updateTask({
complete_at: false complete_at: false
}).then(() => {
//
}) })
break; break;
@ -144,6 +189,12 @@ export default {
} }
}, },
visibleChange(visible) {
if (visible) {
this.$store.dispatch("getTaskFlow", this.task.id);
}
},
updateTask(updata) { updateTask(updata) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.loadIng) { if (this.loadIng) {

View File

@ -11,8 +11,13 @@
@click="getSublist(item)"/> @click="getSublist(item)"/>
<TaskMenu :ref="`taskMenu_${item.id}`" :task="item"/> <TaskMenu :ref="`taskMenu_${item.id}`" :task="item"/>
<div class="item-title" @click="openTask(item)"> <div class="item-title" @click="openTask(item)">
<!--工作流状态-->
<span v-if="item.flow_item_name" :class="item.flow_item_status" @click.stop="openMenu(item)">{{item.flow_item_name}}</span>
<!--是否子任务-->
<span v-if="item.sub_top === true">{{$L('子任务')}}</span> <span v-if="item.sub_top === true">{{$L('子任务')}}</span>
<!--有多少个子任务-->
<span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span> <span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span>
<!--任务描述-->
{{item.name}} {{item.name}}
</div> </div>
<div class="item-icons" @click="openTask(item)"> <div class="item-icons" @click="openTask(item)">
@ -240,6 +245,13 @@ export default {
} }
}, },
openMenu(task) {
const el = this.$refs[`taskMenu_${task.id}`];
if (el) {
el[0].show()
}
},
ownerUser(list) { ownerUser(list) {
return list.filter(({owner}) => owner == 1).sort((a, b) => { return list.filter(({owner}) => owner == 1).sort((a, b) => {
return a.id - b.id; return a.id - b.id;

View File

@ -40,14 +40,19 @@
v-if="item.p_name" v-if="item.p_name"
class="priority-color" class="priority-color"
:style="{backgroundColor:item.p_color}"></em> :style="{backgroundColor:item.p_color}"></em>
<TaskMenu :task="item"> <TaskMenu :ref="`taskMenu_${item.id}`" :task="item">
<div slot="icon" class="drop-icon" @click.stop=""> <div slot="icon" class="drop-icon" @click.stop="">
<i class="taskfont" v-html="item.complete_at ? '&#xe627;' : '&#xe625;'"></i> <i class="taskfont" v-html="item.complete_at ? '&#xe627;' : '&#xe625;'"></i>
</div> </div>
</TaskMenu> </TaskMenu>
<div class="item-title"> <div class="item-title">
<!--工作流状态-->
<span v-if="item.flow_item_name" :class="item.flow_item_status" @click.stop="openMenu(item)">{{item.flow_item_name}}</span>
<!--是否子任务-->
<span v-if="item.sub_top === true">{{$L('子任务')}}</span> <span v-if="item.sub_top === true">{{$L('子任务')}}</span>
<!--有多少个子任务-->
<span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span> <span v-if="item.sub_my && item.sub_my.length > 0">+{{item.sub_my.length}}</span>
<!--任务描述-->
{{item.name}} {{item.name}}
</div> </div>
<div v-if="item.desc" class="item-icon"> <div v-if="item.desc" class="item-icon">
@ -140,6 +145,13 @@ export default {
this.$store.dispatch("openTask", task) this.$store.dispatch("openTask", task)
}, },
openMenu(task) {
const el = this.$refs[`taskMenu_${task.id}`];
if (el) {
el[0].show()
}
},
expiresFormat(date) { expiresFormat(date) {
return $A.countDownFormat(date, this.nowTime) return $A.countDownFormat(date, this.nowTime)
}, },

View File

@ -524,9 +524,6 @@ export default {
if (typeof data.project_user === "undefined") { if (typeof data.project_user === "undefined") {
data.project_user = [] data.project_user = []
} }
if (typeof data.project_flow_item === "undefined") {
data.project_flow_item = []
}
state.cacheProjects.push(data); state.cacheProjects.push(data);
} }
setTimeout(() => { setTimeout(() => {
@ -857,6 +854,9 @@ export default {
}); });
} else if ($A.isJson(data)) { } else if ($A.isJson(data)) {
data._time = $A.Time(); data._time = $A.Time();
if (data.flow_item_name && data.flow_item_name.indexOf("|") !== -1) {
[data.flow_item_status, data.flow_item_name] = data.flow_item_name.split("|")
}
let index = state.cacheTasks.findIndex(({id}) => id == data.id); let index = state.cacheTasks.findIndex(({id}) => id == data.id);
if (index > -1) { if (index > -1) {
state.cacheTasks.splice(index, 1, Object.assign({}, state.cacheTasks[index], data)); state.cacheTasks.splice(index, 1, Object.assign({}, state.cacheTasks[index], data));
@ -1135,7 +1135,7 @@ export default {
reject({msg: 'Parameter error'}); reject({msg: 'Parameter error'});
return; return;
} }
dispatch("taskLoadAdd", task_id) dispatch("taskLoadStart", task_id)
dispatch("call", { dispatch("call", {
url: 'project/task/remove', url: 'project/task/remove',
data: { data: {
@ -1143,12 +1143,12 @@ export default {
}, },
}).then(result => { }).then(result => {
dispatch("forgetTask", task_id) dispatch("forgetTask", task_id)
dispatch("taskLoadSub", task_id) dispatch("taskLoadEnd", task_id)
resolve(result) resolve(result)
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
dispatch("getTaskOne", task_id); dispatch("getTaskOne", task_id);
dispatch("taskLoadSub", task_id) dispatch("taskLoadEnd", task_id)
reject(e) reject(e)
}); });
}); });
@ -1167,7 +1167,7 @@ export default {
reject({msg: 'Parameter error'}); reject({msg: 'Parameter error'});
return; return;
} }
dispatch("taskLoadAdd", task_id) dispatch("taskLoadStart", task_id)
dispatch("call", { dispatch("call", {
url: 'project/task/archived', url: 'project/task/archived',
data: { data: {
@ -1175,12 +1175,12 @@ export default {
}, },
}).then(result => { }).then(result => {
dispatch("forgetTask", task_id) dispatch("forgetTask", task_id)
dispatch("taskLoadSub", task_id) dispatch("taskLoadEnd", task_id)
resolve(result) resolve(result)
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
dispatch("getTaskOne", task_id) dispatch("getTaskOne", task_id)
dispatch("taskLoadSub", task_id) dispatch("taskLoadEnd", task_id)
reject(e) reject(e)
}); });
}); });
@ -1378,18 +1378,18 @@ export default {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const post = $A.cloneJSON($A.date2string(data)); const post = $A.cloneJSON($A.date2string(data));
// //
dispatch("taskLoadAdd", post.task_id) dispatch("taskLoadStart", post.task_id)
dispatch("call", { dispatch("call", {
url: 'project/task/update', url: 'project/task/update',
data: post, data: post,
method: 'post', method: 'post',
}).then(result => { }).then(result => {
dispatch("taskLoadSub", post.task_id) dispatch("taskLoadEnd", post.task_id)
dispatch("saveTask", result.data) dispatch("saveTask", result.data)
resolve(result) resolve(result)
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
dispatch("taskLoadSub", post.task_id) dispatch("taskLoadEnd", post.task_id)
dispatch("getTaskOne", post.task_id); dispatch("getTaskOne", post.task_id);
reject(e) reject(e)
}); });
@ -1401,7 +1401,7 @@ export default {
* @param state * @param state
* @param task_id * @param task_id
*/ */
taskLoadAdd({state}, task_id) { taskLoadStart({state}, task_id) {
setTimeout(() => { setTimeout(() => {
const load = state.taskLoading.find(({id}) => id == task_id) const load = state.taskLoading.find(({id}) => id == task_id)
if (!load) { if (!load) {
@ -1420,7 +1420,7 @@ export default {
* @param state * @param state
* @param task_id * @param task_id
*/ */
taskLoadSub({state}, task_id) { taskLoadEnd({state}, task_id) {
const load = state.taskLoading.find(({id}) => id == task_id) const load = state.taskLoading.find(({id}) => id == task_id)
if (!load) { if (!load) {
state.taskLoading.push({ state.taskLoading.push({
@ -1432,6 +1432,46 @@ export default {
} }
}, },
/**
* 获取任务流程信息
* @param state
* @param dispatch
* @param task_id
* @returns {Promise<unknown>}
*/
getTaskFlow({state, dispatch}, task_id) {
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'project/task/flow',
data: {
task_id: task_id
},
}).then(result => {
let {data} = result
data.turns.some(item => {
let index = state.taskFlowItems.findIndex(({id}) => id == item.id);
if (index > -1) {
state.taskFlowItems.splice(index, 1, item);
} else {
state.taskFlowItems.push(item);
}
})
//
delete data.turns;
let index = state.taskFlows.findIndex(({task_id}) => task_id == data.task_id);
if (index > -1) {
state.taskFlows.splice(index, 1, data);
} else {
state.taskFlows.push(data);
}
resolve(result)
}).catch(e => {
console.error(e);
reject(e);
});
});
},
/** /**
* 获取任务优先级预设数据 * 获取任务优先级预设数据
* @param state * @param state

View File

@ -84,6 +84,10 @@ export default {
end_at: task.end_at, end_at: task.end_at,
complete_at: task.complete_at, complete_at: task.complete_at,
flow_item_id: task.flow_item_id,
flow_item_name: task.flow_item_name,
flow_item_status: task.flow_item_status,
sub_top: true, sub_top: true,
sub_my: [], sub_my: [],
}); });

View File

@ -67,8 +67,14 @@ state.taskId = 0;
state.taskContents = []; state.taskContents = [];
state.taskFiles = []; state.taskFiles = [];
state.taskLogs = []; state.taskLogs = [];
// 任务等待状态
state.taskLoading = []; state.taskLoading = [];
// 任务流程信息
state.taskFlows = [];
state.taskFlowItems = [];
// 任务优先级 // 任务优先级
state.taskPriority = []; state.taskPriority = [];

View File

@ -31,12 +31,6 @@ $--dropdown-menuItem-hover-color: #606266;
color: #f00; color: #f00;
} }
} }
&.hover-del {
color: #f00;
> i {
color: #f00;
}
}
} }
} }
} }

View File

@ -352,7 +352,35 @@
.task-title { .task-title {
flex: 1; flex: 1;
padding-top: 1px; padding-top: 1px;
> span {
float: left;
font-size: 12px;
height: 20px;
line-height: 18px;
padding: 0 3px;
border-radius: 3px;
color: #8bcf70;
border: 1px solid #8bcf70;
margin-right: 4px;
text-align: center;
&.start {
background-color: rgba(38, 38, 38, 0.05);
border-color: rgba(38, 38, 38, 0.05);
color: #595959;
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
border-color: rgba(27, 154, 238, 0.1);
color: #0171c2;
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
border-color: rgba(21, 173, 49, 0.1);
color: #038a24;
}
}
> pre { > pre {
display: block;
margin: 0; margin: 0;
padding: 0; padding: 0;
line-height: 1.5; line-height: 1.5;
@ -682,16 +710,32 @@
> span { > span {
font-size: 12px; font-size: 12px;
height: 18px; height: 18px;
min-width: 20px;
line-height: 16px; line-height: 16px;
padding: 0 3px; padding: 0 2px;
border-radius: 3px; border-radius: 3px;
color: #8bcf70; color: #8bcf70;
background-color: rgba(139, 207, 112, 0);
border: 1px solid #8bcf70; border: 1px solid #8bcf70;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin-top: 3px; margin-top: 3px;
margin-right: 3px; margin-right: 3px;
text-align: center;
&.start {
background-color: rgba(38, 38, 38, 0.05);
border-color: rgba(38, 38, 38, 0.05);
color: #595959;
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
border-color: rgba(27, 154, 238, 0.1);
color: #0171c2;
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
border-color: rgba(21, 173, 49, 0.1);
color: #038a24;
}
} }
} }
.item-icons { .item-icons {

View File

@ -45,6 +45,37 @@
} }
} }
} }
.flow {
margin-left: 18px;
margin-right: -3px;
> span {
font-size: 14px;
height: 26px;
line-height: 24px;
padding: 0 8px;
border-radius: 4px;
color: #8bcf70;
border: 1px solid #8bcf70;
display: inline-block;
text-align: center;
cursor: pointer;
&.start {
background-color: rgba(38, 38, 38, 0.05);
border-color: rgba(38, 38, 38, 0.05);
color: #595959;
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
border-color: rgba(27, 154, 238, 0.1);
color: #0171c2;
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
border-color: rgba(21, 173, 49, 0.1);
color: #038a24;
}
}
}
.nav { .nav {
flex: 1; flex: 1;
display: flex; display: flex;
@ -54,6 +85,9 @@
width: 0; width: 0;
height: 40px; height: 40px;
overflow: auto; overflow: auto;
&::-webkit-scrollbar {
display: none
}
> p { > p {
display: flex; display: flex;
align-items: center; align-items: center;
@ -263,11 +297,12 @@
.subtask-time { .subtask-time {
.clock { .clock {
transform: translateX(0); transform: translateX(0);
opacity: 0.8; opacity: 0.7;
} }
} }
} }
.subtask-icon { .subtask-icon {
padding-top: 1px;
width: 16px; width: 16px;
height: 26px; height: 26px;
line-height: 26px; line-height: 26px;
@ -276,9 +311,39 @@
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
} }
.subtask-flow {
> span {
font-size: 12px;
height: 18px;
min-width: 20px;
line-height: 16px;
padding: 0 2px;
border-radius: 3px;
color: #8bcf70;
border: 1px solid #8bcf70;
display: inline-block;
margin-right: 3px;
text-align: center;
&.start {
background-color: rgba(38, 38, 38, 0.05);
border-color: rgba(38, 38, 38, 0.05);
color: #595959;
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
border-color: rgba(27, 154, 238, 0.1);
color: #0171c2;
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
border-color: rgba(21, 173, 49, 0.1);
color: #038a24;
}
}
}
.subtask-name { .subtask-name {
flex: 1; flex: 1;
margin-right: 16px; margin-right: 8px;
display: flex; display: flex;
.ivu-input { .ivu-input {
margin: -2px 0; margin: -2px 0;

View File

@ -27,12 +27,25 @@
} }
.task-menu-more-dropdown { .task-menu-more-dropdown {
> li {
&.task-menu-more-warp {
list-style: none;
> ul {
max-height: 320px;
overflow: auto;
&::-webkit-scrollbar {
display: none
}
> li { > li {
.item { .item {
display: flex; display: flex;
align-items: center; align-items: center;
> i { > i {
flex-shrink: 0;
width: 18px; width: 18px;
height: 18px; height: 18px;
line-height: 18px; line-height: 18px;
@ -46,5 +59,90 @@
} }
} }
} }
.flow {
padding: 4px 0;
> i {
margin-right: 3px;
&.check {
color: $primary-color;
}
}
.flow-name {
border-radius: 4px;
white-space: nowrap;
padding: 0 5px;
height: 20px;
line-height: 20px;
font-size: 12px;
background: #f4f4f4;
color: #595959;
&.start {
background-color: rgba(38, 38, 38, 0.05);
border-color: rgba(38, 38, 38, 0.05);
color: #595959;
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
border-color: rgba(27, 154, 238, 0.1);
color: #0171c2;
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
border-color: rgba(21, 173, 49, 0.1);
color: #038a24;
}
}
}
&.load-flow {
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
.load-flow-warp {
width: 20px;
height: 20px;
}
}
}
}
&.medium {
> ul {
> li {
.flow {
.flow-name {
height: 24px;
line-height: 24px;
padding: 0 7px;
}
}
}
}
}
&.large {
> ul {
> li {
.flow {
.flow-name {
font-size: 13px;
height: 30px;
line-height: 30px;
padding: 0 8px;
}
}
}
}
}
}
} }
} }

View File

@ -136,16 +136,32 @@
> span { > span {
font-size: 12px; font-size: 12px;
height: 18px; height: 18px;
min-width: 20px;
line-height: 16px; line-height: 16px;
padding: 0 3px; padding: 0 2px;
border-radius: 3px; border-radius: 3px;
color: #8bcf70; color: #8bcf70;
background-color: rgba(139, 207, 112, 0);
border: 1px solid #8bcf70; border: 1px solid #8bcf70;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin-top: 2px; margin-top: 3px;
margin-right: 2px; margin-right: 3px;
text-align: center;
&.start {
background-color: rgba(38, 38, 38, 0.05);
border-color: rgba(38, 38, 38, 0.05);
color: #595959;
}
&.progress {
background-color: rgba(27, 154, 238, 0.1);
border-color: rgba(27, 154, 238, 0.1);
color: #0171c2;
}
&.end {
background-color: rgba(21, 173, 49, 0.1);
border-color: rgba(21, 173, 49, 0.1);
color: #038a24;
}
} }
} }
.item-icon { .item-icon {