From bbd394272f75b2036a3aa86958030f86d5e73fff Mon Sep 17 00:00:00 2001 From: kuaifan Date: Sat, 8 Jan 2022 16:51:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + README_CN.md | 1 + .../Controllers/Api/ProjectController.php | 159 +++++++ app/Http/Middleware/VerifyCsrfToken.php | 3 + app/Models/AbstractModel.php | 6 +- app/Models/Project.php | 10 + app/Models/ProjectFlow.php | 93 ++++ app/Models/ProjectFlowItem.php | 60 +++ app/Models/User.php | 2 + app/Module/Base.php | 5 +- ...171816_create_project_flow_items_table.php | 38 ++ ...1_08_171816_create_project_flows_table.php | 33 ++ ..._172053_project_tasks_add_flow_item_id.php | 34 ++ .../pages/manage/components/ProjectList.vue | 18 +- .../manage/components/ProjectWorkflow.vue | 438 ++++++++++++++++++ resources/assets/sass/pages/components/_.scss | 1 + .../pages/components/project-workflow.scss | 431 +++++++++++++++++ 17 files changed, 1330 insertions(+), 3 deletions(-) create mode 100644 app/Models/ProjectFlow.php create mode 100644 app/Models/ProjectFlowItem.php create mode 100644 database/migrations/2022_01_08_171816_create_project_flow_items_table.php create mode 100644 database/migrations/2022_01_08_171816_create_project_flows_table.php create mode 100644 database/migrations/2022_01_08_172053_project_tasks_add_flow_item_id.php create mode 100644 resources/assets/js/pages/manage/components/ProjectWorkflow.vue create mode 100644 resources/assets/sass/pages/components/project-workflow.scss diff --git a/README.md b/README.md index 9fd3357c..4364c692 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ git pull ./cmd uninstall ./cmd install ./cmd mysql recovery +./cmd artisan migrate ``` ## Uninstall diff --git a/README_CN.md b/README_CN.md index c39d5beb..271cc4ea 100644 --- a/README_CN.md +++ b/README_CN.md @@ -89,6 +89,7 @@ git pull ./cmd uninstall ./cmd install ./cmd mysql recovery +./cmd artisan migrate ``` ## 卸载项目 diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 26df1edc..a8f32574 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -6,6 +6,8 @@ use App\Exceptions\ApiException; use App\Models\AbstractModel; use App\Models\Project; use App\Models\ProjectColumn; +use App\Models\ProjectFlow; +use App\Models\ProjectFlowItem; use App\Models\ProjectInvite; use App\Models\ProjectLog; use App\Models\ProjectTask; @@ -1406,6 +1408,163 @@ class ProjectController extends AbstractController 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. 获取项目、任务日志 * diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 8729d40a..d2ec036e 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -24,6 +24,9 @@ class VerifyCsrfToken extends Middleware // 添加任务 'api/project/task/add/', + // 保存工作流 + 'api/project/flow/save/', + // 修改任务 'api/project/task/update/', diff --git a/app/Models/AbstractModel.php b/app/Models/AbstractModel.php index 25b7ab42..c18e5424 100644 --- a/app/Models/AbstractModel.php +++ b/app/Models/AbstractModel.php @@ -140,7 +140,11 @@ class AbstractModel extends Model $row = static::where($where)->first(); if (empty($row)) { $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) { $row->updateInstance($update); } diff --git a/app/Models/Project.php b/app/Models/Project.php index 33046da3..40452e9b 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -30,6 +30,8 @@ use Request; * @property-read int|null $project_log_count * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser * @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 authData($userid = null, $owner = null) * @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 \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function projectFlowItem(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(projectFlowItem::class, 'project_id', 'id')->orderBy('sort'); + } + /** * 查询所有项目(与正常查询多返回owner字段) * @param self $query diff --git a/app/Models/ProjectFlow.php b/app/Models/ProjectFlow.php new file mode 100644 index 00000000..670de9b9 --- /dev/null +++ b/app/Models/ProjectFlow.php @@ -0,0 +1,93 @@ +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"); + } +} diff --git a/app/Models/ProjectFlowItem.php b/app/Models/ProjectFlowItem.php new file mode 100644 index 00000000..73ddd50f --- /dev/null +++ b/app/Models/ProjectFlowItem.php @@ -0,0 +1,60 @@ + $v) { if (!is_numeric($v)) { unset($array[$k]); + } elseif ($int === true) { + $array[$k] = intval($v); } } return array_values($array); diff --git a/database/migrations/2022_01_08_171816_create_project_flow_items_table.php b/database/migrations/2022_01_08_171816_create_project_flow_items_table.php new file mode 100644 index 00000000..a681a290 --- /dev/null +++ b/database/migrations/2022_01_08_171816_create_project_flow_items_table.php @@ -0,0 +1,38 @@ +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'); + } +} diff --git a/database/migrations/2022_01_08_171816_create_project_flows_table.php b/database/migrations/2022_01_08_171816_create_project_flows_table.php new file mode 100644 index 00000000..f14d8a65 --- /dev/null +++ b/database/migrations/2022_01_08_171816_create_project_flows_table.php @@ -0,0 +1,33 @@ +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'); + } +} diff --git a/database/migrations/2022_01_08_172053_project_tasks_add_flow_item_id.php b/database/migrations/2022_01_08_172053_project_tasks_add_flow_item_id.php new file mode 100644 index 00000000..ae3c1d67 --- /dev/null +++ b/database/migrations/2022_01_08_172053_project_tasks_add_flow_item_id.php @@ -0,0 +1,34 @@ +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"); + }); + } +} diff --git a/resources/assets/js/pages/manage/components/ProjectList.vue b/resources/assets/js/pages/manage/components/ProjectList.vue index f15d7ecb..0b860038 100644 --- a/resources/assets/js/pages/manage/components/ProjectList.vue +++ b/resources/assets/js/pages/manage/components/ProjectList.vue @@ -51,7 +51,8 @@ {{$L('项目设置')}} - {{$L('成员管理')}} + {{$L('工作流设置')}} + {{$L('成员管理')}} {{$L('邀请链接')}} {{$L('项目动态')}} {{$L('已归档任务')}} @@ -431,6 +432,14 @@ + + + + + +
+
+ {{$L('工作流设置')}} +
+ + +
+
+
+ + +
+
{{data.name}}
+
+
{{item.name}}
+
+
+ + +
+
+
+
+
+
{{$L('配置项')}}
+
+
+
{{$L('设置状态为')}}
+
+
+
{{$L('开始状态')}}
+
{{$L('新建任务默认状态')}}
+
+
+
+
+
{{$L('进行中')}}
+
{{$L('可设置多个状态为进行中')}}
+
+
+
+
+
{{$L('结束状态')}}
+
{{$L('该状态下任务自动标记完成')}}
+
+
+
+
+
{{$L('可流转到')}}
+
+ {{item.name}} +
+
+
+
+
+ +
+
+
+
{{$L(item.name)}}
+ + + + +
+ +
+
+ +
+ + {{$L('自动添加负责人')}} +
+
+ +
+ {{$L('修改名称')}} +
+
+ +
+ {{$L('删除')}} +
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+ + + +
+
+
+
{{$L('添加状态')}}
+
+
+
+
+
+
+
+
+ {{$L('当前项目还没有创建工作流')}} + +
+ + + +
+ + +
{{$L('任务流转到此流程时自动添加负责人')}}
+
+
+
+ + +
+
+
+ + + diff --git a/resources/assets/sass/pages/components/_.scss b/resources/assets/sass/pages/components/_.scss index 1950e62b..2217113b 100755 --- a/resources/assets/sass/pages/components/_.scss +++ b/resources/assets/sass/pages/components/_.scss @@ -5,6 +5,7 @@ @import "project-list"; @import "project-log"; @import "project-management"; +@import "project-workflow"; @import "task-add"; @import "task-add-simple"; @import "task-archived"; diff --git a/resources/assets/sass/pages/components/project-workflow.scss b/resources/assets/sass/pages/components/project-workflow.scss new file mode 100644 index 00000000..6c27fe37 --- /dev/null +++ b/resources/assets/sass/pages/components/project-workflow.scss @@ -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; + } +}