feat: 添加工作流
This commit is contained in:
parent
8a2571f514
commit
bbd394272f
@ -89,6 +89,7 @@ git pull
|
|||||||
./cmd uninstall
|
./cmd uninstall
|
||||||
./cmd install
|
./cmd install
|
||||||
./cmd mysql recovery
|
./cmd mysql recovery
|
||||||
|
./cmd artisan migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
## Uninstall
|
## Uninstall
|
||||||
|
@ -89,6 +89,7 @@ git pull
|
|||||||
./cmd uninstall
|
./cmd uninstall
|
||||||
./cmd install
|
./cmd install
|
||||||
./cmd mysql recovery
|
./cmd mysql recovery
|
||||||
|
./cmd artisan migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
## 卸载项目
|
## 卸载项目
|
||||||
|
@ -6,6 +6,8 @@ use App\Exceptions\ApiException;
|
|||||||
use App\Models\AbstractModel;
|
use App\Models\AbstractModel;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\ProjectColumn;
|
use App\Models\ProjectColumn;
|
||||||
|
use App\Models\ProjectFlow;
|
||||||
|
use App\Models\ProjectFlowItem;
|
||||||
use App\Models\ProjectInvite;
|
use App\Models\ProjectInvite;
|
||||||
use App\Models\ProjectLog;
|
use App\Models\ProjectLog;
|
||||||
use App\Models\ProjectTask;
|
use App\Models\ProjectTask;
|
||||||
@ -1406,6 +1408,163 @@ class ProjectController extends AbstractController
|
|||||||
return Base::retSuccess('删除成功', ['id' => $task->id]);
|
return Base::retSuccess('删除成功', ['id' => $task->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/project/flow/list 29. 工作流列表
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份(限:项目负责人)
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup project
|
||||||
|
* @apiName flow__list
|
||||||
|
*
|
||||||
|
* @apiParam {Number} project_id 项目ID
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function flow__list()
|
||||||
|
{
|
||||||
|
User::auth();
|
||||||
|
//
|
||||||
|
$project_id = intval(Request::input('project_id'));
|
||||||
|
//
|
||||||
|
$project = Project::userProject($project_id, true, true);
|
||||||
|
//
|
||||||
|
$list = ProjectFlow::with(['ProjectFlowItem'])->whereProjectId($project->id)->get();
|
||||||
|
return Base::retSuccess('success', $list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} api/project/flow/save 29. 保存工作流
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份(限:项目负责人)
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup project
|
||||||
|
* @apiName flow__save
|
||||||
|
*
|
||||||
|
* @apiParam {Number} project_id 项目ID
|
||||||
|
* @apiParam {Array} flows 工作流数据
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function flow__save()
|
||||||
|
{
|
||||||
|
User::auth();
|
||||||
|
//
|
||||||
|
$project_id = intval(Base::getContentValue('project_id'));
|
||||||
|
$flows = Base::getContentValue('flows');
|
||||||
|
//
|
||||||
|
if (!is_array($flows)) {
|
||||||
|
return Base::retError('参数错误');
|
||||||
|
}
|
||||||
|
//
|
||||||
|
$project = Project::userProject($project_id, true, true);
|
||||||
|
//
|
||||||
|
return AbstractModel::transaction(function() use ($project, $flows) {
|
||||||
|
$projectFlow = ProjectFlow::whereProjectId($project->id)->first();
|
||||||
|
if (empty($projectFlow)) {
|
||||||
|
$projectFlow = ProjectFlow::createInstance([
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'name' => 'Default'
|
||||||
|
]);
|
||||||
|
if (!$projectFlow->save()) {
|
||||||
|
throw new ApiException('工作流创建失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
$ids = [];
|
||||||
|
$idc = [];
|
||||||
|
$hasStart = false;
|
||||||
|
$hasEnd = false;
|
||||||
|
foreach ($flows as $item) {
|
||||||
|
$id = intval($item['id']);
|
||||||
|
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||||
|
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
|
||||||
|
$flow = ProjectFlowItem::updateInsert([
|
||||||
|
'id' => $id,
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'flow_id' => $projectFlow->id,
|
||||||
|
], [
|
||||||
|
'name' => trim($item['name']),
|
||||||
|
'status' => trim($item['status']),
|
||||||
|
'sort' => intval($item['sort']),
|
||||||
|
'turns' => $turns,
|
||||||
|
'userids' => $userids,
|
||||||
|
]);
|
||||||
|
if ($flow) {
|
||||||
|
$ids[] = $flow->id;
|
||||||
|
if ($flow->id != $id) {
|
||||||
|
$idc[$id] = $flow->id;
|
||||||
|
}
|
||||||
|
if ($flow->status == 'start') {
|
||||||
|
$hasStart = true;
|
||||||
|
}
|
||||||
|
if ($flow->status == 'end') {
|
||||||
|
$hasEnd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$hasStart) {
|
||||||
|
throw new ApiException('至少需要1个开始状态');
|
||||||
|
}
|
||||||
|
if (!$hasEnd) {
|
||||||
|
throw new ApiException('至少需要1个结束状态');
|
||||||
|
}
|
||||||
|
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->delete();
|
||||||
|
//
|
||||||
|
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($project->id)->find($projectFlow->id);
|
||||||
|
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
|
||||||
|
foreach ($projectFlow->projectFlowItem as $item) {
|
||||||
|
$turns = $item->turns;
|
||||||
|
foreach ($idc as $oid => $nid) {
|
||||||
|
if (in_array($oid, $turns)) {
|
||||||
|
$turns = array_diff($turns, [$oid]);
|
||||||
|
$turns[] = $nid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!in_array($item->id, $turns)) {
|
||||||
|
$turns[] = $item->id;
|
||||||
|
}
|
||||||
|
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
|
||||||
|
sort($turns);
|
||||||
|
$item->turns = $turns;
|
||||||
|
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
|
||||||
|
}
|
||||||
|
return Base::retSuccess('保存成功', $projectFlow);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/project/flow/delete 29. 删除工作流
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份(限:项目负责人)
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup project
|
||||||
|
* @apiName flow__delete
|
||||||
|
*
|
||||||
|
* @apiParam {Number} project_id 项目ID
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function flow__delete()
|
||||||
|
{
|
||||||
|
User::auth();
|
||||||
|
//
|
||||||
|
$project_id = intval(Request::input('project_id'));
|
||||||
|
//
|
||||||
|
$project = Project::userProject($project_id, true, true);
|
||||||
|
//
|
||||||
|
return AbstractModel::transaction(function() use ($project) {
|
||||||
|
ProjectFlow::whereProjectId($project->id)->delete();
|
||||||
|
ProjectFlowItem::whereProjectId($project->id)->delete();
|
||||||
|
return Base::retSuccess('删除成功');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/project/log/lists 30. 获取项目、任务日志
|
* @api {get} api/project/log/lists 30. 获取项目、任务日志
|
||||||
*
|
*
|
||||||
|
@ -24,6 +24,9 @@ class VerifyCsrfToken extends Middleware
|
|||||||
// 添加任务
|
// 添加任务
|
||||||
'api/project/task/add/',
|
'api/project/task/add/',
|
||||||
|
|
||||||
|
// 保存工作流
|
||||||
|
'api/project/flow/save/',
|
||||||
|
|
||||||
// 修改任务
|
// 修改任务
|
||||||
'api/project/task/update/',
|
'api/project/task/update/',
|
||||||
|
|
||||||
|
@ -140,7 +140,11 @@ class AbstractModel extends Model
|
|||||||
$row = static::where($where)->first();
|
$row = static::where($where)->first();
|
||||||
if (empty($row)) {
|
if (empty($row)) {
|
||||||
$row = new static;
|
$row = new static;
|
||||||
$row->updateInstance(array_merge($where, $insert ?: $update));
|
$array = array_merge($where, $insert ?: $update);
|
||||||
|
if (isset($array[$row->primaryKey])) {
|
||||||
|
unset($array[$row->primaryKey]);
|
||||||
|
}
|
||||||
|
$row->updateInstance($array);
|
||||||
} elseif ($update) {
|
} elseif ($update) {
|
||||||
$row->updateInstance($update);
|
$row->updateInstance($update);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ use Request;
|
|||||||
* @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
|
||||||
* @property-read int|null $project_user_count
|
* @property-read int|null $project_user_count
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectFlow[] $projectFlowItem
|
||||||
|
* @property-read int|null $project_flow_item_count
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null)
|
* @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null)
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null)
|
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null)
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery()
|
* @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery()
|
||||||
@ -99,6 +101,14 @@ 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
|
||||||
|
93
app/Models/ProjectFlow.php
Normal file
93
app/Models/ProjectFlow.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Module\Base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App\Models\ProjectFlow
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int|null $project_id 项目ID
|
||||||
|
* @property string|null $name 流程名称
|
||||||
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectFlowItem[] $projectFlowItem
|
||||||
|
* @property-read int|null $project_flow_item_count
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereCreatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereName($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereProjectId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereUpdatedAt($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class ProjectFlow extends AbstractModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
*/
|
||||||
|
public function projectFlowItem(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ProjectFlowItem::class, 'flow_id', 'id')->orderBy('sort');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function addFlow()
|
||||||
|
{
|
||||||
|
AbstractModel::transaction(function() {
|
||||||
|
$projectFlow = ProjectFlow::whereProjectId($project->id)->first();
|
||||||
|
if (empty($projectFlow)) {
|
||||||
|
$projectFlow = ProjectFlow::createInstance([
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'name' => 'Default'
|
||||||
|
]);
|
||||||
|
if (!$projectFlow->save()) {
|
||||||
|
return Base::retError('工作流创建失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
$ids = [];
|
||||||
|
$idc = [];
|
||||||
|
$hasStart = false;
|
||||||
|
$hasEnd = false;
|
||||||
|
foreach ($flows as $item) {
|
||||||
|
$id = intval($item['id']);
|
||||||
|
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||||
|
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
|
||||||
|
$flow = ProjectFlowItem::updateInsert([
|
||||||
|
'id' => $id,
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'flow_id' => $projectFlow->id,
|
||||||
|
], [
|
||||||
|
'name' => trim($item['name']),
|
||||||
|
'status' => trim($item['status']),
|
||||||
|
'sort' => intval($item['sort']),
|
||||||
|
'turns' => $turns,
|
||||||
|
'userids' => $userids,
|
||||||
|
]);
|
||||||
|
if ($flow) {
|
||||||
|
$ids[] = $flow->id;
|
||||||
|
if ($flow->id != $id) {
|
||||||
|
$idc[$id] = $flow->id;
|
||||||
|
}
|
||||||
|
if ($flow->status == 'start') {
|
||||||
|
$hasStart = true;
|
||||||
|
}
|
||||||
|
if ($flow->status == 'end') {
|
||||||
|
$hasEnd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$hasStart) {
|
||||||
|
return Base::retError('至少需要1个开始状态');
|
||||||
|
}
|
||||||
|
if (!$hasEnd) {
|
||||||
|
return Base::retError('至少需要1个结束状态');
|
||||||
|
}
|
||||||
|
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->delete();
|
||||||
|
});
|
||||||
|
return Base::retSuccess("success");
|
||||||
|
}
|
||||||
|
}
|
60
app/Models/ProjectFlowItem.php
Normal file
60
app/Models/ProjectFlowItem.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Module\Base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App\Models\ProjectFlowItem
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int|null $project_id 项目ID
|
||||||
|
* @property int|null $flow_id 流程ID
|
||||||
|
* @property string|null $name 名称
|
||||||
|
* @property string|null $status 状态
|
||||||
|
* @property array $turns 可流转
|
||||||
|
* @property array $userids 自动负责人ID
|
||||||
|
* @property int|null $sort 排序
|
||||||
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereCreatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereFlowId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereName($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereProjectId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereSort($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereStatus($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereTurns($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUpdatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUserids($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class ProjectFlowItem extends AbstractModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param $value
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getTurnsAttribute($value)
|
||||||
|
{
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
return Base::json2array($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $value
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getUseridsAttribute($value)
|
||||||
|
{
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
return Base::json2array($value);
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,8 @@ use Carbon\Carbon;
|
|||||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserid($value)
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserid($value)
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserimg($value)
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserimg($value)
|
||||||
* @mixin \Eloquent
|
* @mixin \Eloquent
|
||||||
|
* @property string|null $disable_at 禁用时间
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|User whereDisableAt($value)
|
||||||
*/
|
*/
|
||||||
class User extends AbstractModel
|
class User extends AbstractModel
|
||||||
{
|
{
|
||||||
|
@ -830,13 +830,16 @@ class Base
|
|||||||
/**
|
/**
|
||||||
* 数组只保留数字的
|
* 数组只保留数字的
|
||||||
* @param $array
|
* @param $array
|
||||||
|
* @param bool $int 是否格式化值
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function arrayRetainInt($array)
|
public static function arrayRetainInt($array, $int = false)
|
||||||
{
|
{
|
||||||
foreach ($array as $k => $v) {
|
foreach ($array as $k => $v) {
|
||||||
if (!is_numeric($v)) {
|
if (!is_numeric($v)) {
|
||||||
unset($array[$k]);
|
unset($array[$k]);
|
||||||
|
} elseif ($int === true) {
|
||||||
|
$array[$k] = intval($v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return array_values($array);
|
return array_values($array);
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateProjectFlowItemsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('project_flow_items', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
|
||||||
|
$table->bigInteger('flow_id')->nullable()->default(0)->comment('流程ID');
|
||||||
|
$table->string('name', 50)->nullable()->default('')->comment('名称');
|
||||||
|
$table->string('status', 20)->nullable()->default('')->comment('状态');
|
||||||
|
$table->string('turns')->nullable()->default('')->comment('可流转');
|
||||||
|
$table->string('userids')->nullable()->default('')->comment('自动负责人ID');
|
||||||
|
$table->integer('sort')->nullable()->comment('排序');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('project_flow_items');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateProjectFlowsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('project_flows', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
|
||||||
|
$table->string('name', 50)->nullable()->default('')->comment('流程名称');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('project_flows');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class ProjectTasksAddFlowItemId extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('project_tasks', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('project_tasks', 'flow_item_id')) {
|
||||||
|
$table->bigInteger('flow_item_id')->nullable()->default(0)->after('dialog_id')->comment('工作流状态ID');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('project_tasks', function (Blueprint $table) {
|
||||||
|
$table->dropColumn("flow_item_id");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -51,7 +51,8 @@
|
|||||||
<Icon class="menu-icon" type="ios-more" />
|
<Icon class="menu-icon" type="ios-more" />
|
||||||
<EDropdownMenu v-if="projectData.owner_userid === userId" slot="dropdown">
|
<EDropdownMenu v-if="projectData.owner_userid === userId" slot="dropdown">
|
||||||
<EDropdownItem command="setting">{{$L('项目设置')}}</EDropdownItem>
|
<EDropdownItem command="setting">{{$L('项目设置')}}</EDropdownItem>
|
||||||
<EDropdownItem command="user">{{$L('成员管理')}}</EDropdownItem>
|
<EDropdownItem command="workflow">{{$L('工作流设置')}}</EDropdownItem>
|
||||||
|
<EDropdownItem command="user" divided>{{$L('成员管理')}}</EDropdownItem>
|
||||||
<EDropdownItem command="invite">{{$L('邀请链接')}}</EDropdownItem>
|
<EDropdownItem command="invite">{{$L('邀请链接')}}</EDropdownItem>
|
||||||
<EDropdownItem command="log" divided>{{$L('项目动态')}}</EDropdownItem>
|
<EDropdownItem command="log" divided>{{$L('项目动态')}}</EDropdownItem>
|
||||||
<EDropdownItem command="archived_task">{{$L('已归档任务')}}</EDropdownItem>
|
<EDropdownItem command="archived_task">{{$L('已归档任务')}}</EDropdownItem>
|
||||||
@ -431,6 +432,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<!--工作流程设置-->
|
||||||
|
<DrawerOverlay
|
||||||
|
v-model="workflowShow"
|
||||||
|
placement="right"
|
||||||
|
:size="1200">
|
||||||
|
<ProjectWorkflow v-if="workflowShow" :project-id="projectId"/>
|
||||||
|
</DrawerOverlay>
|
||||||
|
|
||||||
<!--查看项目动态-->
|
<!--查看项目动态-->
|
||||||
<DrawerOverlay
|
<DrawerOverlay
|
||||||
v-model="logShow"
|
v-model="logShow"
|
||||||
@ -465,10 +474,12 @@ import TaskRow from "./TaskRow";
|
|||||||
import TaskArchived from "./TaskArchived";
|
import TaskArchived from "./TaskArchived";
|
||||||
import ProjectLog from "./ProjectLog";
|
import ProjectLog from "./ProjectLog";
|
||||||
import DrawerOverlay from "../../../components/DrawerOverlay";
|
import DrawerOverlay from "../../../components/DrawerOverlay";
|
||||||
|
import ProjectWorkflow from "./ProjectWorkflow";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ProjectList",
|
name: "ProjectList",
|
||||||
components: {
|
components: {
|
||||||
|
ProjectWorkflow,
|
||||||
DrawerOverlay,
|
DrawerOverlay,
|
||||||
ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority},
|
ProjectLog, TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority},
|
||||||
data() {
|
data() {
|
||||||
@ -510,6 +521,7 @@ export default {
|
|||||||
transferData: {},
|
transferData: {},
|
||||||
transferLoad: 0,
|
transferLoad: 0,
|
||||||
|
|
||||||
|
workflowShow: false,
|
||||||
logShow: false,
|
logShow: false,
|
||||||
archivedTaskShow: false,
|
archivedTaskShow: false,
|
||||||
|
|
||||||
@ -1201,6 +1213,10 @@ export default {
|
|||||||
this.inviteGet()
|
this.inviteGet()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "workflow":
|
||||||
|
this.workflowShow = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case "log":
|
case "log":
|
||||||
this.logShow = true;
|
this.logShow = true;
|
||||||
break;
|
break;
|
||||||
|
438
resources/assets/js/pages/manage/components/ProjectWorkflow.vue
Normal file
438
resources/assets/js/pages/manage/components/ProjectWorkflow.vue
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
<template>
|
||||||
|
<div class="project-workflow">
|
||||||
|
<div class="workflow-title">
|
||||||
|
{{$L('工作流设置')}}
|
||||||
|
<div class="title-icon">
|
||||||
|
<Loading v-if="loadIng > 0"/>
|
||||||
|
<Icon v-else type="ios-refresh" @click="getData"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="list.length > 0" class="workflow-content">
|
||||||
|
<Collapse v-model="openIndex" accordion>
|
||||||
|
<Panel v-for="data in list" :key="data.id" :name="'index_' + data.id">
|
||||||
|
<div class="workflow-item">
|
||||||
|
<div class="workflow-name">{{data.name}}</div>
|
||||||
|
<div class="workflow-status">
|
||||||
|
<div v-for="item in data.project_flow_item" :class="item.status">{{item.name}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="workflow-save" @click.stop="">
|
||||||
|
<template v-if="contrast(data.project_flow_item, data.project_flow_bak)">
|
||||||
|
<Button :loading="loadIng > 0" type="primary" @click="onSave(data)">{{$L('保存')}}</Button>
|
||||||
|
<Button v-if="data.id > 0" :disabled="loadIng > 0" type="primary" ghost @click="onReduction(data, $event)">{{$L('还原')}}</Button>
|
||||||
|
</template>
|
||||||
|
<Button :disabled="loadIng > 0" type="error" ghost @click="onDelete(data)">{{$L('删除')}}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div slot="content" class="taskflow-config">
|
||||||
|
<div class="taskflow-config-table">
|
||||||
|
<div class="taskflow-config-table-left-container">
|
||||||
|
<div class="taskflow-config-table-column-header left-header">{{$L('配置项')}}</div>
|
||||||
|
<div :ref="`overlay_${data.id}`" class="taskflow-config-table-column-body overlay-y">
|
||||||
|
<div class="taskflow-config-table-block">
|
||||||
|
<div class="taskflow-config-table-block-title">{{$L('设置状态为')}}</div>
|
||||||
|
<div class="taskflow-config-table-block-item">
|
||||||
|
<div>
|
||||||
|
<div class="title">{{$L('开始状态')}}</div>
|
||||||
|
<div class="subtitle">{{$L('新建任务默认状态')}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="taskflow-config-table-block-item">
|
||||||
|
<div>
|
||||||
|
<div class="title">{{$L('进行中')}}</div>
|
||||||
|
<div class="subtitle">{{$L('可设置多个状态为进行中')}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="taskflow-config-table-block-item">
|
||||||
|
<div>
|
||||||
|
<div class="title">{{$L('结束状态')}}</div>
|
||||||
|
<div class="subtitle">{{$L('该状态下任务自动标记完成')}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="taskflow-config-table-block hr">
|
||||||
|
<div class="taskflow-config-table-block-title">{{$L('可流转到')}}</div>
|
||||||
|
<div v-for="item in data.project_flow_item" class="taskflow-config-table-block-item">
|
||||||
|
<span class="transform-status-name">{{item.name}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="taskflow-config-table-right-container">
|
||||||
|
<Draggable
|
||||||
|
:list="data.project_flow_item"
|
||||||
|
:animation="150"
|
||||||
|
class="taskflow-config-table-list-wrapper"
|
||||||
|
tag="div"
|
||||||
|
draggable=".column-border"
|
||||||
|
@sort="">
|
||||||
|
<div v-for="item in data.project_flow_item" class="taskflow-config-table-status-column column-border" :class="item.status">
|
||||||
|
<div
|
||||||
|
class="taskflow-config-table-status-item taskflow-config-table-column-header">
|
||||||
|
<div class="status-label-with-menu" :class="item.status">
|
||||||
|
<div class="name">{{$L(item.name)}}</div>
|
||||||
|
<EDropdown
|
||||||
|
trigger="click"
|
||||||
|
class="more"
|
||||||
|
@command="onMore($event, item)">
|
||||||
|
<Icon type="ios-more" />
|
||||||
|
<EDropdownMenu slot="dropdown" class="taskflow-config-more-dropdown-menu">
|
||||||
|
<EDropdownItem v-if="item.userids.length > 0" command="user">
|
||||||
|
<div class="users">
|
||||||
|
<UserAvatar v-for="(uid, ukey) in item.userids" :key="ukey" :userid="uid" :size="28" :borderWitdh="1" :showName="item.userids.length === 1" tooltipDisabled/>
|
||||||
|
</div>
|
||||||
|
</EDropdownItem>
|
||||||
|
<EDropdownItem command="user">
|
||||||
|
<div class="item">
|
||||||
|
<Icon type="md-person" />
|
||||||
|
{{$L('自动添加负责人')}}
|
||||||
|
</div>
|
||||||
|
</EDropdownItem>
|
||||||
|
<EDropdownItem command="name">
|
||||||
|
<div class="item">
|
||||||
|
<Icon type="md-create" />{{$L('修改名称')}}
|
||||||
|
</div>
|
||||||
|
</EDropdownItem>
|
||||||
|
<EDropdownItem command="remove">
|
||||||
|
<div class="item delete">
|
||||||
|
<Icon type="md-trash" />{{$L('删除')}}
|
||||||
|
</div>
|
||||||
|
</EDropdownItem>
|
||||||
|
</EDropdownMenu>
|
||||||
|
</EDropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :ref="`overlay_${data.id}`" class="taskflow-config-table-column-body overlay-y">
|
||||||
|
<div class="taskflow-config-table-block">
|
||||||
|
<div class="taskflow-config-table-block-title"></div>
|
||||||
|
<RadioGroup v-model="item.status">
|
||||||
|
<Radio label="start"><span></span></Radio>
|
||||||
|
<Radio label="progress"><span></span></Radio>
|
||||||
|
<Radio label="end"><span></span></Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<div class="taskflow-config-table-block">
|
||||||
|
<div class="taskflow-config-table-block-title"></div>
|
||||||
|
<CheckboxGroup v-model="item.turns" @on-change="onTurns(item)">
|
||||||
|
<Checkbox v-for="v in data.project_flow_item" :key="v.id" :label="v.id" :disabled="v.id==item.id"><span></span></Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="taskflow-config-table-status-column addnew" @click="onAdd(data)">{{$L('添加状态')}}</div>
|
||||||
|
</Draggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="loadIng == 0" class="workflow-no">
|
||||||
|
{{$L('当前项目还没有创建工作流')}}
|
||||||
|
<Button type="primary" @click="onCreate">{{$L('创建工作流')}}</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--成员管理-->
|
||||||
|
<Modal
|
||||||
|
v-model="userShow"
|
||||||
|
:title="$L('自动负责人')"
|
||||||
|
:mask-closable="false">
|
||||||
|
<Form :model="userData" label-width="auto" @submit.native.prevent>
|
||||||
|
<FormItem prop="userids" :label="userData.name">
|
||||||
|
<UserInput v-if="userShow" v-model="userData.userids" :project-id="projectId" :multiple-max="5" :placeholder="$L('选择成员')"/>
|
||||||
|
<div class="form-tip">{{$L('任务流转到此流程时自动添加负责人')}}</div>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
<div slot="footer" class="adaption">
|
||||||
|
<Button type="default" @click="userShow=false">{{$L('取消')}}</Button>
|
||||||
|
<Button type="primary" @click="onUser">{{$L('保存')}}</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Draggable from "vuedraggable";
|
||||||
|
import UserInput from "../../../components/UserInput";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProjectWorkflow",
|
||||||
|
components: {UserInput, Draggable},
|
||||||
|
props: {
|
||||||
|
projectId: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loadIng: 0,
|
||||||
|
|
||||||
|
list: [],
|
||||||
|
openIndex: "",
|
||||||
|
|
||||||
|
userShow: false,
|
||||||
|
userData: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
projectId: {
|
||||||
|
handler(val) {
|
||||||
|
if (val) {
|
||||||
|
this.getData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
getData() {
|
||||||
|
this.loadIng++;
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'project/flow/list',
|
||||||
|
data: {
|
||||||
|
project_id: this.projectId,
|
||||||
|
},
|
||||||
|
}).then(({data}) => {
|
||||||
|
this.loadIng--;
|
||||||
|
this.list = data.map(item => {
|
||||||
|
item.project_flow_bak = JSON.stringify(item.project_flow_item)
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.openIndex = this.list.length === 1 ? ("index_" + this.list[0].id) : ""
|
||||||
|
this.$nextTick(this.syncScroller);
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
this.loadIng--;
|
||||||
|
$A.modalError(msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
syncScroller() {
|
||||||
|
this.list.some(data => {
|
||||||
|
this.$refs[`overlay_${data.id}`] && this.$refs[`overlay_${data.id}`].some(el => {
|
||||||
|
if (!Object.keys(el.attributes).includes("sync-scroller")) {
|
||||||
|
el.setAttribute("sync-scroller", true);
|
||||||
|
el.addEventListener('scroll', ({target}) => {
|
||||||
|
let top = target.scrollTop;
|
||||||
|
let left = target.scrollLeft;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs[`overlay_${data.id}`].some(node => {
|
||||||
|
if (node != el) {
|
||||||
|
node.scrollTo(left, top)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
contrast(project_flow_item, project_flow_bak) {
|
||||||
|
return JSON.stringify(project_flow_item) != project_flow_bak
|
||||||
|
},
|
||||||
|
|
||||||
|
onCreate() {
|
||||||
|
let id = -1 * $A.randNum(1000, 10000);
|
||||||
|
this.list.push({
|
||||||
|
"id": id,
|
||||||
|
"name": "Default workflow",
|
||||||
|
"project_flow_item": [
|
||||||
|
{
|
||||||
|
"id": -10,
|
||||||
|
"name": "待处理",
|
||||||
|
"status": "start",
|
||||||
|
"turns": [-10, -11, -12, -13],
|
||||||
|
"userids": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": -11,
|
||||||
|
"name": "进行中",
|
||||||
|
"status": "progress",
|
||||||
|
"turns": [-10, -11, -12, -13],
|
||||||
|
"userids": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": -12,
|
||||||
|
"name": "已完成",
|
||||||
|
"status": "end",
|
||||||
|
"turns": [-10, -11, -12, -13],
|
||||||
|
"userids": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": -13,
|
||||||
|
"name": "已取消",
|
||||||
|
"status": "end",
|
||||||
|
"turns": [-10, -11, -12, -13],
|
||||||
|
"userids": [],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
this.openIndex = "index_" + id;
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete(data) {
|
||||||
|
$A.modalConfirm({
|
||||||
|
title: '删除工作流',
|
||||||
|
content: '你确定要删除工作流吗?',
|
||||||
|
loading: true,
|
||||||
|
onOk: () => {
|
||||||
|
if (data.id > 0) {
|
||||||
|
this.loadIng++;
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'project/flow/delete',
|
||||||
|
data: {
|
||||||
|
project_id: this.projectId,
|
||||||
|
},
|
||||||
|
}).then(({msg}) => {
|
||||||
|
this.loadIng--;
|
||||||
|
$.messageSuccess(msg);
|
||||||
|
this.$Modal.remove();
|
||||||
|
//
|
||||||
|
let index = this.list.findIndex(({id}) => id == data.id)
|
||||||
|
if (index > -1) {
|
||||||
|
this.list.splice(index, 1)
|
||||||
|
}
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
this.loadIng--;
|
||||||
|
$A.modalError(msg, 301);
|
||||||
|
this.$Modal.remove();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let index = this.list.findIndex(({id}) => id == data.id)
|
||||||
|
if (index > -1) {
|
||||||
|
this.list.splice(index, 1)
|
||||||
|
}
|
||||||
|
this.$Modal.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onMore(name, item) {
|
||||||
|
switch (name) {
|
||||||
|
case "user":
|
||||||
|
this.$set(this.userData, 'id', item.id);
|
||||||
|
this.$set(this.userData, 'name', item.name);
|
||||||
|
this.$set(this.userData, 'userids', item.userids);
|
||||||
|
this.userShow = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "name":
|
||||||
|
this.onName(item);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "remove":
|
||||||
|
this.onRemove(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onUser() {
|
||||||
|
this.userShow = false;
|
||||||
|
this.list.some(data => {
|
||||||
|
let item = data.project_flow_item.find(item => item.id == this.userData.id)
|
||||||
|
if (item) {
|
||||||
|
this.$set(item, 'userids', this.userData.userids)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onName(item) {
|
||||||
|
$A.modalInput({
|
||||||
|
value: item.name,
|
||||||
|
title: "修改名称",
|
||||||
|
placeholder: "输入流程名称",
|
||||||
|
onOk: (name) => {
|
||||||
|
if (name) {
|
||||||
|
this.$set(item, 'name', name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemove(item) {
|
||||||
|
this.list.some(data => {
|
||||||
|
let index = data.project_flow_item.findIndex(({id}) => id == item.id)
|
||||||
|
if (index > -1) {
|
||||||
|
data.project_flow_item.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onTurns(item) {
|
||||||
|
this.$set(item, 'turns', item.turns.sort())
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd(data) {
|
||||||
|
$A.modalInput({
|
||||||
|
title: "添加状态",
|
||||||
|
placeholder: "输入状态名称",
|
||||||
|
onOk: (name) => {
|
||||||
|
if (name) {
|
||||||
|
let id = $A.randNum(100000, 999999) * -1;
|
||||||
|
let turns = data.project_flow_item.map(({id}) => id)
|
||||||
|
data.project_flow_item.push({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
status: 'end',
|
||||||
|
turns,
|
||||||
|
userids: [],
|
||||||
|
})
|
||||||
|
data.project_flow_item.some(item => {
|
||||||
|
item.turns.push(id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onReduction(data) {
|
||||||
|
this.$set(data, 'project_flow_item', JSON.parse(data.project_flow_bak))
|
||||||
|
},
|
||||||
|
|
||||||
|
onSave(formData) {
|
||||||
|
let sort = 0;
|
||||||
|
formData.project_flow_item.some(item => {
|
||||||
|
item.sort = sort++
|
||||||
|
})
|
||||||
|
this.loadIng++;
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'project/flow/save',
|
||||||
|
data: {
|
||||||
|
project_id: this.projectId,
|
||||||
|
flows: formData.project_flow_item,
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
}).then(({data, msg}) => {
|
||||||
|
this.loadIng--;
|
||||||
|
$.messageSuccess(msg)
|
||||||
|
//
|
||||||
|
data.project_flow_bak = JSON.stringify(data.project_flow_item)
|
||||||
|
let index = this.list.findIndex(({id}) => id == formData.id)
|
||||||
|
if (index > -1) {
|
||||||
|
this.list.splice(index, 1, data)
|
||||||
|
} else {
|
||||||
|
this.list.push(data)
|
||||||
|
}
|
||||||
|
this.openIndex = "index_" + data.id;
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
this.loadIng--;
|
||||||
|
$A.modalError(msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -5,6 +5,7 @@
|
|||||||
@import "project-list";
|
@import "project-list";
|
||||||
@import "project-log";
|
@import "project-log";
|
||||||
@import "project-management";
|
@import "project-management";
|
||||||
|
@import "project-workflow";
|
||||||
@import "task-add";
|
@import "task-add";
|
||||||
@import "task-add-simple";
|
@import "task-add-simple";
|
||||||
@import "task-archived";
|
@import "task-archived";
|
||||||
|
431
resources/assets/sass/pages/components/project-workflow.scss
vendored
Normal file
431
resources/assets/sass/pages/components/project-workflow.scss
vendored
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
.project-workflow {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.workflow-title {
|
||||||
|
color: #333333;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 20px 20px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.title-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-top: 2px;
|
||||||
|
> i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.workflow-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 20px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.ivu-collapse-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.workflow-name {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
.workflow-status {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
> div {
|
||||||
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
margin-right: 8px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border: 1px solid #e8eaec;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #f7f7f7;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
overflow: hidden;
|
||||||
|
&.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.workflow-save {
|
||||||
|
margin: 0 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
> button {
|
||||||
|
height: 26px;
|
||||||
|
line-height: 24px;
|
||||||
|
padding: 0 13px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-no {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #999;
|
||||||
|
> button {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config {
|
||||||
|
display: flex;
|
||||||
|
max-height: 580px;
|
||||||
|
|
||||||
|
.taskflow-config-table {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 1px 0 3px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.taskflow-config-table-left-container {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 246px;
|
||||||
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
border-right: 1px solid #f4f4f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-column-header {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 58px;
|
||||||
|
padding: 0 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 58px;
|
||||||
|
|
||||||
|
&.left-header {
|
||||||
|
top: 16px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-column-body {
|
||||||
|
margin-top: 58px;
|
||||||
|
height: calc(100% - 58px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-block {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 0;
|
||||||
|
|
||||||
|
|
||||||
|
&.hr {
|
||||||
|
position: relative;
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
border-top: 1px solid #f4f4f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-block-title {
|
||||||
|
padding: 0 20px;
|
||||||
|
height: 40px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
line-height: 40px
|
||||||
|
}
|
||||||
|
|
||||||
|
.ivu-radio-group {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
.ivu-radio-group-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 20px;
|
||||||
|
height: 58px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ivu-checkbox-group {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
.ivu-checkbox-group-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 20px;
|
||||||
|
height: 58px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-block-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 20px;
|
||||||
|
height: 58px;
|
||||||
|
|
||||||
|
&.with-indicator:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 20px;
|
||||||
|
width: 4px;
|
||||||
|
border-radius: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 700;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
align-items: center
|
||||||
|
}
|
||||||
|
|
||||||
|
&.radio-item>span {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.transform-status-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-right-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-list-wrapper,
|
||||||
|
.taskflow-config-table-right-container {
|
||||||
|
display: flex
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.taskflow-config-table-status-column {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 210px;
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-right: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.status-label-with-menu {
|
||||||
|
.more {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.addnew {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #8c8c8c;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #777777;
|
||||||
|
border-color: #bfbfbf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.column-border {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid
|
||||||
|
}
|
||||||
|
|
||||||
|
&.start {
|
||||||
|
border-color: #e5e5e5;
|
||||||
|
&:hover {
|
||||||
|
border-color: #bfbfbf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.progress {
|
||||||
|
border-color: #ccecff;
|
||||||
|
&:hover {
|
||||||
|
border-color: #87d2ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.end {
|
||||||
|
border-color: #cafac8;
|
||||||
|
&:hover {
|
||||||
|
border-color: #64d16d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskflow-config-table-status-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
height: 58px;
|
||||||
|
line-height: 58px;
|
||||||
|
cursor: move
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label-with-menu {
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 32px;
|
||||||
|
width: 200px;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.start {
|
||||||
|
background: rgba(38,38,38,0.05);
|
||||||
|
color: #595959
|
||||||
|
}
|
||||||
|
|
||||||
|
&.progress {
|
||||||
|
background: rgba(27,154,238,0.1);
|
||||||
|
color: #0171c2
|
||||||
|
}
|
||||||
|
|
||||||
|
&.end {
|
||||||
|
background: rgba(21,173,49,0.1);
|
||||||
|
color: #038a24
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
||||||
|
|
||||||
|
.more {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
&.opacity {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.taskflow-config-more-dropdown-menu {
|
||||||
|
.users {
|
||||||
|
display: flex;
|
||||||
|
padding: 6px 0;
|
||||||
|
margin: 0 -8px;
|
||||||
|
overflow: auto;
|
||||||
|
.common-avatar {
|
||||||
|
max-width: 100%;
|
||||||
|
margin-right: -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.delete {
|
||||||
|
color: #f00;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user