From e792ab7b4da9907f7dcc864ba526db198883a02a Mon Sep 17 00:00:00 2001 From: kuaifan Date: Fri, 1 Aug 2025 11:27:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B7=A5=E4=BD=9C=E6=B5=81=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Api/ProjectController.php | 4 +- app/Models/Project.php | 14 +-- app/Models/ProjectFlowItem.php | 2 + app/Models/ProjectTask.php | 6 +- app/Tasks/LoopTask.php | 2 +- ..._add_color_to_project_flow_items_table.php | 34 +++++++ resources/assets/js/functions/web.js | 65 ++++++++++++ resources/assets/js/pages/manage.vue | 1 + .../pages/manage/components/ProjectPanel.vue | 7 +- .../manage/components/ProjectWorkflow.vue | 83 ++++++++++------ .../pages/manage/components/TaskArchived.vue | 10 +- .../pages/manage/components/TaskDeleted.vue | 10 +- .../js/pages/manage/components/TaskDetail.vue | 10 +- .../js/pages/manage/components/TaskMove.vue | 2 +- .../pages/manage/components/TaskOperation.vue | 6 +- .../js/pages/manage/components/TaskRow.vue | 2 +- .../assets/js/pages/manage/dashboard.vue | 2 +- resources/assets/js/store/actions.js | 8 +- resources/assets/js/store/state.js | 17 ++-- resources/assets/sass/pages/common.scss | 11 ++- .../pages/components/general-operation.scss | 24 ++--- .../sass/pages/components/project-panel.scss | 99 ++++++++----------- .../pages/components/project-workflow.scss | 95 +++++++++--------- .../sass/pages/components/task-detail.scss | 54 +++++----- .../sass/pages/components/task-move.scss | 24 ++--- .../assets/sass/pages/page-dashboard.scss | 24 ++--- resources/assets/sass/pages/page-manage.scss | 24 ++--- 27 files changed, 397 insertions(+), 243 deletions(-) create mode 100644 database/migrations/2025_07_31_231557_add_color_to_project_flow_items_table.php diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 84515e6c7..dfde08bee 100755 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -2367,7 +2367,7 @@ class ProjectController extends AbstractController $task->updateTask($data, $updateMarking); // $data = ProjectTask::oneTask($task->id)->toArray(); - $data["flow_item_name"] = $newFlowItem->status . "|" . $newFlowItem->name; + $data["flow_item_name"] = $newFlowItem->status . "|" . $newFlowItem->name . "|" . $newFlowItem->color; $data['update_marking'] = $updateMarking ?: json_decode('{}'); $task->pushMsg('update', $data); // @@ -2424,7 +2424,7 @@ class ProjectController extends AbstractController ]); } // - $turns = ProjectFlowItem::select(['id', 'name', 'status', 'turns'])->whereFlowId($projectFlow->id)->orderBy('sort')->get(); + $turns = ProjectFlowItem::select(['id', 'name', 'status', 'turns', 'color'])->whereFlowId($projectFlow->id)->orderBy('sort')->get(); if (empty($projectFlowItem)) { $data = [ 'task_id' => $projectTask->id, diff --git a/app/Models/Project.php b/app/Models/Project.php index 52ae60aa2..2de3fc75f 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -423,24 +423,25 @@ class Project extends AbstractModel $projectUserids = $this->relationUserids(); foreach ($flows as $item) { $id = intval($item['id']); + $name = trim(str_replace('|', '·', $item['name'])); $turns = Base::arrayRetainInt($item['turns'] ?: [], true); $userids = Base::arrayRetainInt($item['userids'] ?: [], true); $usertype = trim($item['usertype']); $userlimit = intval($item['userlimit']); $columnid = intval($item['columnid']); if ($usertype == 'replace' && empty($userids)) { - throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人"); + throw new ApiException("状态[{$name}]设置错误,设置流转模式时必须填写状态负责人"); } if ($usertype == 'merge' && empty($userids)) { - throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人"); + throw new ApiException("状态[{$name}]设置错误,设置剔除模式时必须填写状态负责人"); } if ($userlimit && empty($userids)) { - throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人"); + throw new ApiException("状态[{$name}]设置错误,设置限制负责人时必须填写状态负责人"); } foreach ($userids as $userid) { if (!in_array($userid, $projectUserids)) { $nickname = User::userid2nickname($userid); - throw new ApiException("状态[{$item['name']}]设置错误,状态负责人[{$nickname}]不在项目成员内"); + throw new ApiException("状态[{$name}]设置错误,状态负责人[{$nickname}]不在项目成员内"); } } $flow = ProjectFlowItem::updateInsert([ @@ -448,8 +449,9 @@ class Project extends AbstractModel 'project_id' => $this->id, 'flow_id' => $projectFlow->id, ], [ - 'name' => trim($item['name']), + 'name' => $name, 'status' => trim($item['status']), + 'color' => trim($item['color']), 'sort' => intval($item['sort']), 'turns' => $turns, 'userids' => $userids, @@ -469,7 +471,7 @@ class Project extends AbstractModel $hasEnd = true; } if (!$isInsert) { - $upTaskList[$flow->id] = $flow->status . "|" . $flow->name; + $upTaskList[$flow->id] = $flow->status . "|" . $flow->name . "|" . $flow->color; } } } diff --git a/app/Models/ProjectFlowItem.php b/app/Models/ProjectFlowItem.php index 04ad22996..0fa10b9ac 100644 --- a/app/Models/ProjectFlowItem.php +++ b/app/Models/ProjectFlowItem.php @@ -12,6 +12,7 @@ use App\Module\Base; * @property int|null $flow_id 流程ID * @property string|null $name 名称 * @property string|null $status 状态 + * @property string|null $color 自定义颜色 * @property array $turns 可流转 * @property array $userids 状态负责人ID * @property string|null $usertype 流转模式 @@ -38,6 +39,7 @@ use App\Module\Base; * @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 whereColor($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) diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php index 0a986a9db..d06b771e4 100644 --- a/app/Models/ProjectTask.php +++ b/app/Models/ProjectTask.php @@ -485,7 +485,7 @@ class ProjectTask extends AbstractModel foreach ($projectFlowItem as $item) { if ($item->status == 'start') { $task->flow_item_id = $item->id; - $task->flow_item_name = $item->status . "|" . $item->name; + $task->flow_item_name = $item->status . "|" . $item->name . "|" . $item->color; $owner = array_merge($owner, $item->userids); break; } @@ -649,7 +649,7 @@ class ProjectTask extends AbstractModel $data['column_id'] = $newFlowItem->columnid; } $this->flow_item_id = $newFlowItem->id; - $this->flow_item_name = $newFlowItem->status . "|" . $newFlowItem->name; + $this->flow_item_name = $newFlowItem->status . "|" . $newFlowItem->name . "|" . $newFlowItem->color; $this->addLog("修改{任务}状态", [ 'flow' => $flowData, 'change' => [$currentFlowItem?->name, $newFlowItem->name] @@ -1907,7 +1907,7 @@ class ProjectTask extends AbstractModel // 更新任务流程 $flowItem = projectFlowItem::whereProjectId($projectId)->whereId($flowItemId)->first(); $this->flow_item_id = $flowItemId; - $this->flow_item_name = $flowItem->status . "|" . $flowItem->name; + $this->flow_item_name = $flowItem->status . "|" . $flowItem->name . "|" . $flowItem->color; if ($flowItem->status == 'end') { $this->completeTask(Carbon::now(), $flowItem->name); } else { diff --git a/app/Tasks/LoopTask.php b/app/Tasks/LoopTask.php index 1bc4e3789..dde40f3e7 100644 --- a/app/Tasks/LoopTask.php +++ b/app/Tasks/LoopTask.php @@ -40,7 +40,7 @@ class LoopTask extends AbstractTask foreach ($projectFlowItem as $flowItem) { if ($flowItem->status == 'start') { $task->flow_item_id = $flowItem->id; - $task->flow_item_name = $flowItem->status . "|" . $flowItem->name; + $task->flow_item_name = $flowItem->status . "|" . $flowItem->name . "|" . $flowItem->color; if ($flowItem->userids) { $userids = array_values(array_unique($flowItem->userids)); foreach ($userids as $uid) { diff --git a/database/migrations/2025_07_31_231557_add_color_to_project_flow_items_table.php b/database/migrations/2025_07_31_231557_add_color_to_project_flow_items_table.php new file mode 100644 index 000000000..be994287d --- /dev/null +++ b/database/migrations/2025_07_31_231557_add_color_to_project_flow_items_table.php @@ -0,0 +1,34 @@ +string('color', 20)->nullable()->default('')->after('status')->comment('自定义颜色'); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('project_flow_items', function (Blueprint $table) { + $table->dropColumn('color'); + }); + } +} diff --git a/resources/assets/js/functions/web.js b/resources/assets/js/functions/web.js index 7b2e995f3..71e604b2b 100755 --- a/resources/assets/js/functions/web.js +++ b/resources/assets/js/functions/web.js @@ -692,6 +692,71 @@ import {convertLocalResourcePath} from "../components/Replace/utils"; return secondLast; } return ""; + }, + + /** + * 根据十六进制颜色生成通用 CSS 变量样式 + * @param {string} hexColor - 颜色值,格式如 "#RRGGBB" + * @param {number[]} levels - 需要生成的透明度等级数组(如 [10, 20, 70],代表 10%、20%、70%) + * @param {string} prefix - 生成的 CSS 变量前缀,默认为 'custom-color' + * @param {Object|null} styles - 可选的样式对象,如果未传入则会创建一个新的对象 + * @returns {Object|null} 返回包含 CSS 变量的对象,若未传入颜色则返回 null + */ + generateColorVarStyle(hexColor, levels = [], prefix = 'custom-color', styles = null) { + if (typeof hexColor !== 'string' || !/^#([0-9a-fA-F]{6})$/.test(hexColor)) { + return styles; + } + // 解析十六进制颜色为 RGB + const r = parseInt(hexColor.substring(1, 3), 16); + const g = parseInt(hexColor.substring(3, 5), 16); + const b = parseInt(hexColor.substring(5, 7), 16); + + // 初始化样式对象 + if (!$A.isJson(styles)) { + styles = {}; + } + + // 遍历 levels,生成对应透明度的 rgba 变量 + levels.forEach(level => { + // 只处理有效的数字 + if (typeof level === 'number' && level >= 0 && level <= 100) { + const alpha = Math.round((level / 100) * 100) / 100; // 保留两位小数 + styles[`--${prefix}-${level}`] = `rgba(${r}, ${g}, ${b}, ${alpha})`; + } + }); + + // 加上 100% 不透明度直接用 hexColor + styles[`--${prefix}-100`] = hexColor + + return styles; + }, + + /** + * 转换工作流状态 + * @param {string|{flow_item_name, complete_at}} item + * @returns {{status: null, name: string, color: null}} + */ + convertWorkflow(item) { + let status = null, + name = item, + color = null; + if ($A.isJson(item)) { + name = item.flow_item_name + if (name.indexOf("|") === -1) { + if (name.complete_at) { + name = $A.L('已完成'); + } else { + name = $A.L('未完成'); + } + } + } + if (name && name.indexOf("|") !== -1) { + const arr = `${name}||`.split("|") + status = arr[0] + name = arr[1] + color = arr[2] + } + return {status, name, color} } }); diff --git a/resources/assets/js/pages/manage.vue b/resources/assets/js/pages/manage.vue index 251f2a11e..824c30150 100644 --- a/resources/assets/js/pages/manage.vue +++ b/resources/assets/js/pages/manage.vue @@ -36,6 +36,7 @@ v-for="(item, key) in taskBrowseLists" v-if="item.id > 0 && key < 10" :key="key" + :style="$A.generateColorVarStyle(item.flow_item_color, [10], 'flow-item-custom-color')" class="task-title" @click.native="openTask(item)" :name="item.name"> diff --git a/resources/assets/js/pages/manage/components/ProjectPanel.vue b/resources/assets/js/pages/manage/components/ProjectPanel.vue index fd7a1fd3e..7a171ddb2 100644 --- a/resources/assets/js/pages/manage/components/ProjectPanel.vue +++ b/resources/assets/js/pages/manage/components/ProjectPanel.vue @@ -128,7 +128,6 @@ @@ -181,9 +180,10 @@ @remove="sortUpdate">