From 4c30f2e628d491364b2f71fba0e4f4e23a44113c Mon Sep 17 00:00:00 2001 From: yueyang <2495544247@qq.com> Date: Sun, 25 Oct 2026 23:20:42 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=B3=BB=E7=BB=9F=E5=8C=96?= =?UTF-8?q?=E5=BE=85=E5=8A=9E=E7=AD=9B=E9=80=89=E4=B8=8E=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=80=89=E9=A1=B9=20(#569)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/SystemizationTodos.php | 74 ++++-- .../pages/systemization-todos.blade.php | 226 ++++++++++++++---- .../Business/SystemizationTodosTest.php | 25 +- 3 files changed, 254 insertions(+), 71 deletions(-) diff --git a/app/Http/Controllers/Business/SystemizationTodos.php b/app/Http/Controllers/Business/SystemizationTodos.php index 2b300249..478a9679 100644 --- a/app/Http/Controllers/Business/SystemizationTodos.php +++ b/app/Http/Controllers/Business/SystemizationTodos.php @@ -36,8 +36,16 @@ class SystemizationTodos extends Controller if ($filters['category'] !== '') { $query->where('category', $filters['category']); } - if ($filters['owner'] !== '') { - $query->where('owner', 'like', '%' . addcslashes($filters['owner'], '%_\\') . '%'); + if ($filters['owners'] !== []) { + $query->where(function ($builder) use ($filters): void { + foreach ($filters['owners'] as $owner) { + $builder->orWhere( + 'owner', + 'like', + '%' . addcslashes($owner, '%_\\') . '%', + ); + } + }); } if ($filters['assignees'] !== []) { $query->where(function ($builder) use ($filters): void { @@ -103,6 +111,30 @@ class SystemizationTodos extends Controller ->distinct() ->orderBy('category') ->pluck('category'); + $statusCounts = (clone $base) + ->selectRaw('status, COUNT(*) AS aggregate') + ->groupBy('status') + ->pluck('aggregate', 'status'); + $overdueCount = (clone $base) + ->whereNotIn('status', [SystemizationTodo::STATUS_COMPLETED]) + ->whereNotNull('due_date') + ->whereDate('due_date', '<', today()) + ->count(); + $statusOptions = collect(); + if ($overdueCount > 0) { + $statusOptions->push([ + 'value' => self::OVERDUE_FILTER, + 'label' => '已逾期', + ]); + } + foreach (SystemizationTodo::STATUSES as $status) { + if ((int) $statusCounts->get($status, 0) > 0) { + $statusOptions->push([ + 'value' => $status, + 'label' => $status, + ]); + } + } return [ 'view' => 'business.pages.systemization-todos', @@ -113,17 +145,13 @@ class SystemizationTodos extends Controller 'todoOwners' => $this->peopleOptions('owner'), 'todoAssignees' => $this->peopleOptions('assignee'), 'todoStatuses' => SystemizationTodo::STATUSES, - 'todoOverdueFilter' => self::OVERDUE_FILTER, + 'todoStatusOptions' => $statusOptions, 'todoSummary' => [ 'total' => (clone $base)->count(), - 'pending' => (clone $base)->where('status', SystemizationTodo::STATUS_PENDING)->count(), - 'in_progress' => (clone $base)->where('status', SystemizationTodo::STATUS_IN_PROGRESS)->count(), - 'completed' => (clone $base)->where('status', SystemizationTodo::STATUS_COMPLETED)->count(), - 'overdue' => (clone $base) - ->whereNotIn('status', [SystemizationTodo::STATUS_COMPLETED]) - ->whereNotNull('due_date') - ->whereDate('due_date', '<', today()) - ->count(), + 'pending' => (int) $statusCounts->get(SystemizationTodo::STATUS_PENDING, 0), + 'in_progress' => (int) $statusCounts->get(SystemizationTodo::STATUS_IN_PROGRESS, 0), + 'completed' => (int) $statusCounts->get(SystemizationTodo::STATUS_COMPLETED, 0), + 'overdue' => $overdueCount, ], 'todoRoutes' => [ 'store' => route('business.systemization-todos.store'), @@ -234,14 +262,6 @@ class SystemizationTodos extends Controller private function filters(): array { $status = (string) request('status', ''); - $assignees = collect((array) request('assignee', [])) - ->filter(static fn (mixed $value): bool => is_string($value)) - ->map(static fn (string $value): string => trim($value)) - ->filter() - ->unique() - ->take(50) - ->values() - ->all(); return [ 'status' => $status === self::OVERDUE_FILTER @@ -249,14 +269,26 @@ class SystemizationTodos extends Controller ? $status : '', 'category' => trim((string) request('category', '')), - 'owner' => trim((string) request('owner', '')), - 'assignees' => $assignees, + 'owners' => $this->peopleFilter('owner'), + 'assignees' => $this->peopleFilter('assignee'), 'start_date' => $this->normalizeDateFilter(request('start_date')), 'due_date' => $this->normalizeDateFilter(request('due_date')), 'keyword' => trim((string) request('keyword', '')), ]; } + private function peopleFilter(string $key): array + { + return collect((array) request($key, [])) + ->filter(static fn (mixed $value): bool => is_string($value)) + ->map(static fn (string $value): string => trim($value)) + ->filter() + ->unique() + ->take(50) + ->values() + ->all(); + } + private function peopleOptions(string $column): Collection { return SystemizationTodo::query() diff --git a/resources/views/business/pages/systemization-todos.blade.php b/resources/views/business/pages/systemization-todos.blade.php index ef6d4feb..6f7d3fd6 100644 --- a/resources/views/business/pages/systemization-todos.blade.php +++ b/resources/views/business/pages/systemization-todos.blade.php @@ -21,12 +21,13 @@ .sys-todo-filter label { display:grid; flex:1 1 7.4rem; gap:.25rem; min-width:0; color:#595959; font-size:.72rem; } .sys-todo-filter label.sys-todo-filter__keyword { flex:2.2 1 16rem; } .sys-todo-filter input, .sys-todo-filter select { width:100%; height:2.125rem; padding:0 .6rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fff; color:#171717; font:inherit; font-size:.8125rem; } - .sys-todo-filter__multi { position:relative; flex:1 1 8.2rem; min-width:0; color:#595959; font-size:.72rem; } - .sys-todo-filter__multi > summary { position:relative; display:grid; align-content:center; gap:.18rem; min-height:2.95rem; padding:.28rem 1.8rem .28rem .6rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fff; color:#171717; cursor:pointer; list-style:none; } - .sys-todo-filter__multi > summary::-webkit-details-marker { display:none; } - .sys-todo-filter__multi > summary .material-icons-outlined { position:absolute; top:50%; right:.55rem; color:#777; font-size:1.05rem; transform:translateY(-50%); transition:transform 150ms ease; } - .sys-todo-filter__multi[open] > summary { border-color:var(--sys-gold); box-shadow:0 0 0 3px rgba(133,102,50,.14); } - .sys-todo-filter__multi[open] > summary .material-icons-outlined { transform:translateY(-50%) rotate(180deg); } + .sys-todo-filter__multi { display:grid; flex:1 1 8.2rem; gap:.25rem; min-width:0; color:#595959; font-size:.72rem; } + .sys-todo-filter__multi details { position:relative; min-width:0; } + .sys-todo-filter__multi > details > summary { position:relative; display:flex; align-items:center; min-height:2.125rem; padding:0 1.8rem 0 .6rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fff; color:#171717; cursor:pointer; list-style:none; } + .sys-todo-filter__multi > details > summary::-webkit-details-marker { display:none; } + .sys-todo-filter__multi > details > summary .material-icons-outlined { position:absolute; top:50%; right:.55rem; color:#777; font-size:1.05rem; transform:translateY(-50%); transition:transform 150ms ease; } + .sys-todo-filter__multi details[open] > summary { border-color:var(--sys-gold); box-shadow:0 0 0 3px rgba(133,102,50,.14); } + .sys-todo-filter__multi details[open] > summary .material-icons-outlined { transform:translateY(-50%) rotate(180deg); } .sys-todo-filter__multi-label { color:#595959; font-size:.72rem; line-height:1; } .sys-todo-filter__multi-value { overflow:hidden; font-size:.8125rem; line-height:1.2; text-overflow:ellipsis; white-space:nowrap; } .sys-todo-filter__multi-menu { position:absolute; top:calc(100% + .3rem); left:0; z-index:20; display:grid; width:max(100%,13rem); max-height:17rem; padding:.35rem; border:1px solid var(--sys-border); border-radius:.55rem; background:#fff; box-shadow:0 8px 22px rgba(23,23,23,.16); overflow:auto; } @@ -97,6 +98,12 @@ .sys-todo-field input, .sys-todo-field select, .sys-todo-field textarea { width:100%; padding:.5rem .6rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fff; color:#171717; font:inherit; font-size:.8125rem; } .sys-todo-field input, .sys-todo-field select { min-height:2.15rem; } .sys-todo-field textarea { min-height:5.5rem; resize:vertical; line-height:1.55; } + .sys-todo-new-category { margin-top:.35rem; } + .sys-todo-new-category[hidden] { display:none; } + .sys-todo-owner-checks { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.2rem .45rem; max-height:7rem; padding:.4rem; border:1px solid var(--sys-border); border-radius:.45rem; background:#fff; overflow:auto; } + .sys-todo-owner-checks label { display:flex; align-items:center; gap:.35rem; min-height:1.6rem; color:#333; font-size:.76rem; cursor:pointer; } + .sys-todo-owner-checks label:hover { color:var(--sys-gold); } + .sys-todo-owner-checks input[type="checkbox"] { flex:0 0 auto; width:1rem; height:1rem; min-height:0; padding:0; accent-color:var(--sys-gold); } .sys-todo-upload-zone { display:flex; flex-wrap:wrap; align-items:center; gap:.45rem .65rem; min-height:4.5rem; padding:.7rem; border:1px dashed #cfc3ad; border-radius:.55rem; background:#fcfaf5; } .sys-todo-upload-zone input[type="file"] { position:absolute; width:1px; height:1px; padding:0; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; } .sys-todo-upload-button { display:inline-flex; align-items:center; justify-content:center; min-height:2rem; padding:0 .75rem; border:1px solid var(--sys-gold); border-radius:.4rem; background:var(--sys-gold); color:#fff; font-size:.78rem; cursor:pointer; } @@ -169,9 +176,8 @@