From 83bc9c631523f3edf8b0d8a6e8aaf8b7d0264814 Mon Sep 17 00:00:00 2001 From: yueyang <2495544247@qq.com> Date: Mon, 26 Oct 2026 22:55:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=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=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SystemizationTodoWorkbookImporter.php | 14 +- .../Business/SystemizationTodos.php | 133 ++++++-- app/Models/Business/SystemizationTodo.php | 51 ++- .../pages/systemization-todos.blade.php | 306 ++++++++++++++---- .../Business/SystemizationTodosTest.php | 125 ++++++- 5 files changed, 514 insertions(+), 115 deletions(-) diff --git a/app/Business/SystemizationTodoWorkbookImporter.php b/app/Business/SystemizationTodoWorkbookImporter.php index e6c2b611..34659c38 100644 --- a/app/Business/SystemizationTodoWorkbookImporter.php +++ b/app/Business/SystemizationTodoWorkbookImporter.php @@ -295,7 +295,7 @@ class SystemizationTodoWorkbookImporter [$finishDate, $finishNote] = $this->dateParts($finishText); $progress = $this->cell($cells, $columns['progress'] ?? null); $notes = $this->cell($cells, $columns['notes'] ?? null); - $status = $this->status($progress, $startText, $finishText); + $status = $this->status($progress); foreach ($titles as $offset => $title) { $titleIndex = $offset + 1; @@ -674,20 +674,12 @@ class SystemizationTodoWorkbookImporter return [null, mb_substr($value, 0, 100)]; } - private function status(string $progress, string $startNote, string $finishNote): string + private function status(string $progress): string { if (str_contains($progress, '已完成')) { return SystemizationTodo::STATUS_COMPLETED; } - if ($progress !== '') { - return str_contains($progress, '延期') - ? SystemizationTodo::STATUS_DELAYED - : SystemizationTodo::STATUS_IN_PROGRESS; - } - if (str_contains($startNote, '延期') || str_contains($finishNote, '延期')) { - return SystemizationTodo::STATUS_DELAYED; - } - return SystemizationTodo::STATUS_PENDING; + return SystemizationTodo::STATUS_INCOMPLETE; } } diff --git a/app/Http/Controllers/Business/SystemizationTodos.php b/app/Http/Controllers/Business/SystemizationTodos.php index a3b2f732..baa6e38c 100644 --- a/app/Http/Controllers/Business/SystemizationTodos.php +++ b/app/Http/Controllers/Business/SystemizationTodos.php @@ -28,10 +28,12 @@ class SystemizationTodos extends Controller ->withCount('attachments'); if ($filters['status'] === self::OVERDUE_FILTER) { - $query->whereNotIn('status', [SystemizationTodo::STATUS_COMPLETED]) + $query->where('status', '<>', SystemizationTodo::STATUS_COMPLETED) ->whereNotNull('due_date') ->whereDate('due_date', '<', today()); - } elseif ($filters['status'] !== '') { + } elseif ($filters['status'] === SystemizationTodo::STATUS_INCOMPLETE) { + $query->where('status', '<>', SystemizationTodo::STATUS_COMPLETED); + } elseif ($filters['status'] === SystemizationTodo::STATUS_COMPLETED) { $query->where('status', $filters['status']); } if ($filters['category'] !== '') { @@ -103,23 +105,28 @@ class SystemizationTodos extends Controller $todo->setAttribute('section_display', $sequence['section_display']); $todo->setAttribute('type_count', $sequence['type_count']); $todo->setAttribute('content_count', $sequence['content_count']); + $todo->setAttribute('status_label', $todo->statusLabel()); + $reminder = $todo->reminder(); + $todo->setAttribute('reminder_label', $reminder['label']); + $todo->setAttribute('reminder_tone', $reminder['tone']); }); $items = $items->sortBy('hierarchy_order')->values(); $positionGroups = $this->positionGroups($sequenceMap); $base = SystemizationTodo::query(); + $totalCount = (clone $base)->count(); + $completedCount = (clone $base) + ->where('status', SystemizationTodo::STATUS_COMPLETED) + ->count(); + $incompleteCount = max(0, $totalCount - $completedCount); $categories = SystemizationTodo::query() ->whereNotNull('category') ->where('category', '<>', '') ->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]) + ->where('status', '<>', SystemizationTodo::STATUS_COMPLETED) ->whereNotNull('due_date') ->whereDate('due_date', '<', today()) ->count(); @@ -131,12 +138,10 @@ class SystemizationTodos extends Controller ]); } foreach (SystemizationTodo::STATUSES as $status) { - if ((int) $statusCounts->get($status, 0) > 0) { - $statusOptions->push([ - 'value' => $status, - 'label' => $status, - ]); - } + $statusOptions->push([ + 'value' => $status, + 'label' => $status, + ]); } return [ @@ -151,11 +156,14 @@ class SystemizationTodos extends Controller 'todoAssignees' => $this->peopleOptions('assignee'), 'todoStatuses' => SystemizationTodo::STATUSES, 'todoStatusOptions' => $statusOptions, + 'todoReturnFilters' => base64_encode(json_encode( + $filters, + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + )), 'todoSummary' => [ - 'total' => (clone $base)->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), + 'total' => $totalCount, + 'incomplete' => $incompleteCount, + 'completed' => $completedCount, 'overdue' => $overdueCount, ], 'todoRoutes' => [ @@ -333,6 +341,7 @@ class SystemizationTodos extends Controller public function store(Request $request): RedirectResponse { $data = $this->validated($request); + $this->normalizePendingDates($request, $data); $position = max(1, (int) ($data['position'] ?? 1)); unset($data['position']); $data['company_id'] = (int) company_id(); @@ -506,6 +515,7 @@ class SystemizationTodos extends Controller public function update(Request $request, SystemizationTodo $todo): RedirectResponse { $data = $this->validated($request); + $this->normalizePendingDates($request, $data); $targetPosition = max(0, (int) ($data['position'] ?? 0)); unset($data['position']); $data['updated_by'] = user_id(); @@ -596,22 +606,30 @@ class SystemizationTodos extends Controller private function filters(): array { - $status = (string) request('status', ''); - return [ - 'status' => $status === self::OVERDUE_FILTER - || in_array($status, SystemizationTodo::STATUSES, true) - ? $status - : '', + 'status' => $this->normalizeStatusValue(request('status')), 'category' => trim((string) request('category', '')), 'owners' => $this->peopleFilter('owner'), 'assignees' => $this->peopleFilter('assignee'), - 'start_date' => $this->normalizeDateFilter(request('start_date')), - 'due_date' => $this->normalizeDateFilter(request('due_date')), + 'start_date' => $this->normalizeDateValue(request('start_date')), + 'due_date' => $this->normalizeDateValue(request('due_date')), 'keyword' => trim((string) request('keyword', '')), ]; } + private function normalizeStatusValue(mixed $value): string + { + $status = trim((string) $value); + if (in_array($status, SystemizationTodo::LEGACY_INCOMPLETE_STATUSES, true)) { + return SystemizationTodo::STATUS_INCOMPLETE; + } + + return $status === self::OVERDUE_FILTER + || in_array($status, SystemizationTodo::STATUSES, true) + ? $status + : ''; + } + private function peopleFilter(string $key): array { return collect((array) request($key, [])) @@ -661,7 +679,7 @@ class SystemizationTodos extends Controller return $options; } - private function normalizeDateFilter(mixed $value): string + private function normalizeDateValue(mixed $value): string { $value = trim((string) $value); if ($value === '') { @@ -686,8 +704,10 @@ class SystemizationTodos extends Controller 'assignee' => ['nullable', 'string', 'max:255'], 'start_date' => ['nullable', 'date_format:Y-m-d'], 'start_note' => ['nullable', 'string', 'max:50'], + 'start_pending' => ['nullable', 'boolean'], 'due_date' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:start_date'], 'due_note' => ['nullable', 'string', 'max:50'], + 'due_pending' => ['nullable', 'boolean'], 'status' => ['required', Rule::in(SystemizationTodo::STATUSES)], 'completed_at' => ['nullable', 'date_format:Y-m-d'], 'progress' => ['nullable', 'string', 'max:10000'], @@ -699,6 +719,19 @@ class SystemizationTodos extends Controller ]); } + private function normalizePendingDates(Request $request, array &$data): void + { + foreach (['start', 'due'] as $prefix) { + if ($request->boolean($prefix . '_pending')) { + $data[$prefix . '_date'] = null; + $data[$prefix . '_note'] = '待定'; + } elseif (!empty($data[$prefix . '_date'])) { + $data[$prefix . '_note'] = null; + } + unset($data[$prefix . '_pending']); + } + } + private function normalizeCompletionDates(array &$data): void { $completed = ($data['status'] ?? '') === SystemizationTodo::STATUS_COMPLETED; @@ -743,6 +776,7 @@ class SystemizationTodos extends Controller 'section' => 'other', 'page' => 'systemization-todos', ]; + $parameters = array_merge($parameters, $this->returnedFilters()); if ($focus) { $parameters['highlight'] = $focus->id; } @@ -751,4 +785,51 @@ class SystemizationTodos extends Controller ->route('business.pages.show', $parameters) ->with($type === 'success' ? 'success' : 'error', $message); } + + private function returnedFilters(): array + { + $encoded = request('return_filters'); + if (!is_string($encoded) || $encoded === '') { + return []; + } + + $json = base64_decode($encoded, true); + $filters = $json === false ? null : json_decode($json, true); + if (!is_array($filters)) { + return []; + } + + $category = is_string($filters['category'] ?? null) + ? trim($filters['category']) + : ''; + $keyword = is_string($filters['keyword'] ?? null) + ? trim($filters['keyword']) + : ''; + $owners = collect((array) ($filters['owners'] ?? [])) + ->filter(static fn (mixed $value): bool => is_string($value)) + ->map(static fn (string $value): string => trim($value)) + ->filter() + ->unique() + ->take(50) + ->values() + ->all(); + $assignees = collect((array) ($filters['assignees'] ?? [])) + ->filter(static fn (mixed $value): bool => is_string($value)) + ->map(static fn (string $value): string => trim($value)) + ->filter() + ->unique() + ->take(50) + ->values() + ->all(); + + return array_filter([ + 'status' => $this->normalizeStatusValue($filters['status'] ?? null), + 'category' => $category, + 'owner' => $owners, + 'assignee' => $assignees, + 'start_date' => $this->normalizeDateValue($filters['start_date'] ?? null), + 'due_date' => $this->normalizeDateValue($filters['due_date'] ?? null), + 'keyword' => $keyword, + ], static fn (mixed $value): bool => $value !== '' && $value !== []); + } } diff --git a/app/Models/Business/SystemizationTodo.php b/app/Models/Business/SystemizationTodo.php index b73b540f..74c10b7a 100644 --- a/app/Models/Business/SystemizationTodo.php +++ b/app/Models/Business/SystemizationTodo.php @@ -7,21 +7,24 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class SystemizationTodo extends Model { - public const STATUS_PENDING = '待办'; - - public const STATUS_IN_PROGRESS = '进行中'; + public const STATUS_INCOMPLETE = '未完成'; public const STATUS_COMPLETED = '已完成'; - public const STATUS_DELAYED = '已延期'; - public const STATUSES = [ - self::STATUS_PENDING, - self::STATUS_IN_PROGRESS, + self::STATUS_INCOMPLETE, self::STATUS_COMPLETED, - self::STATUS_DELAYED, ]; + public const LEGACY_INCOMPLETE_STATUSES = ['待办', '进行中', '已延期']; + + // Keep old constant names for imports and existing callers without rewriting data. + public const STATUS_PENDING = self::STATUS_INCOMPLETE; + + public const STATUS_IN_PROGRESS = self::STATUS_INCOMPLETE; + + public const STATUS_DELAYED = self::STATUS_INCOMPLETE; + protected $table = 'business_systemization_todos'; protected $fillable = [ @@ -65,4 +68,36 @@ class SystemizationTodo extends Model { return $this->status === self::STATUS_COMPLETED; } + + public function statusLabel(): string + { + return $this->isCompleted() ? self::STATUS_COMPLETED : self::STATUS_INCOMPLETE; + } + + public function reminder(): array + { + if ($this->isCompleted()) { + return ['label' => '已完成', 'tone' => 'muted']; + } + if ($this->due_date === null) { + return ['label' => '时间待定', 'tone' => 'muted']; + } + + $today = today()->startOfDay(); + $dueDate = $this->due_date->copy()->startOfDay(); + if ($dueDate->lt($today)) { + return [ + 'label' => '逾期 ' . $dueDate->diffInDays($today) . ' 天', + 'tone' => 'danger', + ]; + } + if ($dueDate->equalTo($today)) { + return ['label' => '今天到期', 'tone' => 'warning']; + } + + return [ + 'label' => '剩余 ' . $today->diffInDays($dueDate) . ' 天', + 'tone' => 'info', + ]; + } } diff --git a/resources/views/business/pages/systemization-todos.blade.php b/resources/views/business/pages/systemization-todos.blade.php index 10880aa0..243f61de 100644 --- a/resources/views/business/pages/systemization-todos.blade.php +++ b/resources/views/business/pages/systemization-todos.blade.php @@ -11,7 +11,7 @@ .sys-todo-page__button--primary:hover { background:#6a5129; color:#fff; } .sys-todo-page__button .material-icons-outlined { font-size:1rem; } .sys-todo-page__intro { margin:.35rem 0 0; color:var(--sys-muted); font-size:.75rem; line-height:1.6; } - .sys-todo-summary { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:.55rem; margin:.8rem 0; } + .sys-todo-summary { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:.55rem; margin:.8rem 0; } .sys-todo-summary__item { display:flex; align-items:center; justify-content:space-between; gap:.6rem; padding:.5rem .7rem; border:1px solid #e6dfd2; border-radius:.5rem; background:#fffdf9; } .sys-todo-summary__label { color:var(--sys-muted); font-size:.72rem; } .sys-todo-summary__value { color:#171717; font-size:1rem; font-weight:700; font-variant-numeric:tabular-nums; } @@ -38,42 +38,60 @@ .sys-todo-filter__actions { display:flex; flex:0 0 auto; gap:.45rem; } .sys-todo-table-wrap { overflow-x:auto; overflow-y:visible; border:1px solid var(--sys-border); border-radius:.65rem; background:#fff; scrollbar-width:none; } .sys-todo-table-wrap::-webkit-scrollbar { height:0; } - .sys-todo-table { width:100%; min-width:74rem; border-collapse:separate; border-spacing:0; font-size:.75rem; } - .sys-todo-table th { position:sticky; top:0; z-index:4; padding:.55rem .48rem; border-bottom:1px solid var(--sys-border); background:#f3ece0; color:#3d3a35; text-align:left; white-space:nowrap; } - .sys-todo-table th:first-child { left:0; z-index:8; width:5.4rem; min-width:5.4rem; text-align:center; box-shadow:1px 0 0 #e5ddcf; } - .sys-todo-table td { padding:.72rem .6rem; border-bottom:1px solid #eee7da; background:#fff; color:#2f2a23; line-height:1.6; vertical-align:top; } + .sys-todo-table { width:108rem; min-width:108rem; table-layout:fixed; border-collapse:separate; border-spacing:0; font-size:.75rem; } + .sys-todo-table th { position:sticky; top:0; z-index:4; padding:.55rem .7rem; border-bottom:1px solid var(--sys-border); background:#f3ece0; color:#3d3a35; text-align:left; white-space:nowrap; } + .sys-todo-table th:first-child { left:0; z-index:8; width:var(--sys-todo-type-width,5.4rem); text-align:center; box-shadow:1px 0 0 #e5ddcf; } + .sys-todo-table td { height:3.5rem; padding:.72rem .7rem; border-bottom:1px solid #eee7da; background:#fff; color:#2f2a23; line-height:1.5; vertical-align:middle; } + .sys-todo-table tbody tr { height:3.5rem; } .sys-todo-table tbody tr:hover td { background:#fdfbf6; } .sys-todo-table tbody tr.is-highlighted td { background:#fff4cf !important; } .sys-todo-table [data-sys-todo-row-edit], .sys-todo-table [data-sys-todo-group-edit] { cursor:pointer; } .sys-todo-table [data-sys-todo-row-edit]:hover, .sys-todo-table [data-sys-todo-group-edit]:hover { background:#fff4cf; } .sys-todo-table [data-sys-todo-row-edit]:focus-visible, .sys-todo-table [data-sys-todo-group-edit]:focus-visible { outline:2px solid var(--sys-gold); outline-offset:-2px; } - .sys-todo-table__index-cell { width:4.8rem; min-width:4.8rem; text-align:center; } + .sys-todo-table__index-cell { text-align:center; } .sys-todo-table__index-actions { display:inline-flex; align-items:center; justify-content:center; gap:.32rem; } .sys-todo-table__index { color:#6d5630; font-weight:600; font-variant-numeric:tabular-nums; white-space:nowrap; } .sys-todo-table__insert-after { display:inline-flex; align-items:center; justify-content:center; width:1.45rem; height:1.45rem; padding:0; border:1px solid #d8c9aa; border-radius:999px; background:#fffaf0; color:var(--sys-gold); cursor:pointer; } .sys-todo-table__insert-after:hover { border-color:var(--sys-gold); background:var(--sys-gold-soft); color:#6a5129; } .sys-todo-table__insert-after:focus-visible { outline:2px solid var(--sys-gold); outline-offset:2px; } .sys-todo-table__insert-after .material-icons-outlined { font-size:.95rem; line-height:1; } - .sys-todo-table__group { padding-top:.9rem; padding-bottom:.9rem; background:#fcfaf5; font-weight:600; line-height:1.7; vertical-align:middle; } + .sys-todo-table__group { padding-top:.9rem; padding-bottom:.9rem; background:#fcfaf5; font-weight:600; line-height:1.5; vertical-align:middle; } .sys-todo-table__group-content { display:inline-flex; align-items:flex-start; gap:.38rem; min-width:0; } .sys-todo-table__group-text { min-width:0; white-space:pre-line; overflow-wrap:anywhere; } .sys-todo-table__level { display:inline-flex; flex:0 0 auto; align-items:center; justify-content:center; min-width:1.35rem; height:1.35rem; margin-top:.05rem; border:1px solid #d8c9aa; border-radius:999px; background:#fffaf0; color:var(--sys-gold); font-size:.68rem; font-weight:700; font-variant-numeric:tabular-nums; } - .sys-todo-table__type { position:sticky; left:0; z-index:3; width:5.4rem; min-width:5.4rem; text-align:center; white-space:nowrap; box-shadow:1px 0 0 #e5ddcf; } + .sys-todo-table__type { position:sticky; left:0; z-index:3; width:var(--sys-todo-type-width,5.4rem); text-align:center; white-space:normal; box-shadow:1px 0 0 #e5ddcf; } .sys-todo-table__type .sys-todo-table__group-text { white-space:normal; } - .sys-todo-table__section { min-width:11rem; max-width:17rem; } - .sys-todo-table__title, .sys-todo-table__progress, .sys-todo-table__notes { line-height:1.45; white-space:pre-line; overflow-wrap:anywhere; } - .sys-todo-table__title { min-width:17rem; max-width:24rem; font-weight:600; } - .sys-todo-table__progress { min-width:12rem; max-width:20rem; } - .sys-todo-table__notes { min-width:9rem; max-width:16rem; } + .sys-todo-table__title, .sys-todo-table__progress-notes { line-height:1.5; white-space:pre-line; overflow-wrap:anywhere; } + .sys-todo-table__title { font-weight:600; } + .sys-todo-table__inline-add { display:inline-flex; align-items:center; justify-content:center; gap:.15rem; min-height:1.65rem; margin-top:.45rem; padding:0 .45rem; border:1px solid #d8c9aa; border-radius:999px; background:#fffaf0; color:#6d5630; font:inherit; font-size:.68rem; font-weight:500; white-space:nowrap; cursor:pointer; } + .sys-todo-table__inline-add:hover { border-color:var(--sys-gold); background:var(--sys-gold-soft); color:#6a5129; } + .sys-todo-table__inline-add:focus-visible { outline:2px solid var(--sys-gold); outline-offset:2px; } + .sys-todo-table__inline-actions { display:flex; flex-wrap:wrap; justify-content:center; gap:.25rem; } + .sys-todo-table__inline-actions .sys-todo-table__inline-add { margin-top:.3rem; } + .sys-todo-table__progress-notes { display:grid; gap:.35rem; } + .sys-todo-table__detail { display:grid; gap:.15rem; } + .sys-todo-table__detail-label { color:#856632; font-size:.66rem; font-weight:600; } + .sys-todo-table__reminder { display:inline-flex; align-items:center; padding:.1rem .45rem; border:1px solid #dfd6c5; border-radius:999px; background:#faf6ee; color:#51483c; white-space:nowrap; } + .sys-todo-table__reminder.is-danger { border-color:#e7c3c3; background:#fff3f3; color:#b80000; } + .sys-todo-table__reminder.is-warning { border-color:#e5c98f; background:#fff8e8; color:#935f07; } + .sys-todo-table__reminder.is-info { border-color:#b5cad8; background:#f3f8fb; color:#006395; } + .sys-todo-table__reminder.is-muted { color:#68625a; } + .sys-todo-table__col-resizer { position:absolute; top:0; right:-4px; z-index:12; width:9px; height:100%; padding:0; border:0; background:transparent; cursor:col-resize; touch-action:none; } + .sys-todo-table__col-resizer::after { position:absolute; top:20%; right:4px; width:1px; height:60%; background:#bfb3a0; content:""; } + .sys-todo-table__col-resizer:hover::after, .sys-todo-table__col-resizer:focus-visible::after { top:0; width:2px; height:100%; background:var(--sys-gold); } + .sys-todo-table__col-resizer:focus-visible { outline:none; } .sys-todo-badge { display:inline-flex; align-items:center; padding:.1rem .45rem; border:1px solid #dfd6c5; border-radius:999px; background:#faf6ee; color:#51483c; white-space:nowrap; } - .sys-todo-badge--progress { border-color:#dbbf7a; background:#fff7df; color:#935f07; } + .sys-todo-badge--incomplete { border-color:#dbbf7a; background:#fff7df; color:#935f07; } .sys-todo-badge--completed { border-color:#bad0ac; background:#f2f8ed; color:#53793e; } - .sys-todo-badge--delayed { border-color:#e7c3c3; background:#fff3f3; color:#b80000; } - .sys-todo-badge--overdue { border-color:#e7c3c3; color:#b80000; } .sys-todo-shots { display:flex; flex-wrap:wrap; gap:.3rem; max-width:13rem; } .sys-todo-shots button { display:block; width:4.25rem; height:3.2rem; padding:0; border:1px solid #ddd4c4; border-radius:.35rem; overflow:hidden; background:#faf8f3; cursor:zoom-in; } .sys-todo-shots img { width:100%; height:100%; object-fit:cover; } - .sys-todo-table__add { margin-top:1rem; padding:.55rem .7rem; border:1px dashed #d8cdb8; border-radius:.55rem; background:#fcfaf5; color:#666; font-size:.72rem; } + .sys-todo-table__add { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:.5rem .75rem; margin-top:1rem; padding:.55rem .7rem; border:1px dashed #d8cdb8; border-radius:.55rem; background:#fcfaf5; color:#666; font-size:.72rem; } + .sys-todo-table__add-copy { min-width:16rem; flex:1 1 30rem; } + .sys-todo-table__reset-widths { min-height:1.8rem; padding:0 .55rem; border:1px solid var(--sys-border); border-radius:.4rem; background:#fff; color:#51483c; font:inherit; font-size:.7rem; cursor:pointer; } + .sys-todo-table__reset-widths:hover { border-color:var(--sys-gold); background:var(--sys-gold-soft); } + .sys-todo-table__footer-add { background:#fcfaf5; text-align:center; } + .sys-todo-table__footer-note { padding-left:1rem; color:#666; font-size:.7rem; font-weight:400; text-align:left; } .sys-todo-scrollrail { position:fixed; bottom:.85rem; z-index:35; height:1.15rem; padding:2px; border:1px solid #d8cdb8; border-radius:999px; background:rgba(243,236,224,.96); box-shadow:0 5px 18px rgba(23,23,23,.16); opacity:0; pointer-events:none; overflow-x:scroll; overflow-y:hidden; transition:opacity 150ms ease; } .sys-todo-scrollrail.is-visible { opacity:1; pointer-events:auto; } .sys-todo-scrollrail__spacer { height:1px; } @@ -96,6 +114,8 @@ .sys-todo-dialog__body { max-height:calc(90vh - 9rem); overflow:auto; padding:1rem 1.25rem; } .sys-todo-form-grid { display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:.75rem 1rem; } .sys-todo-field { display:grid; gap:.3rem; min-width:0; color:#595959; font-size:.75rem; } + .sys-todo-combined-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.65rem; padding:.65rem; border:1px solid var(--sys-border); border-radius:.55rem; background:#fcfaf5; } + .sys-todo-combined-grid label { display:grid; gap:.3rem; min-width:0; } .sys-todo-field--2 { grid-column:span 2; } .sys-todo-field--3 { grid-column:span 3; } .sys-todo-field--4 { grid-column:span 4; } @@ -165,9 +185,9 @@ .sys-todo-image-dialog__body img { max-width:100%; max-height:100%; object-fit:contain; } .sys-todo-image-dialog__foot { display:flex; justify-content:flex-end; padding:.55rem .85rem; background:#1d1d1f; } .sys-todo-image-dialog__foot a { color:#f0e4bf; font-size:.75rem; } - @media (max-width:1300px) { .sys-todo-summary { grid-template-columns:repeat(3,minmax(0,1fr)); } } + @media (max-width:1300px) { .sys-todo-summary { grid-template-columns:repeat(2,minmax(0,1fr)); } } @media (max-width:900px) { .sys-todo-filter label, .sys-todo-filter label.sys-todo-filter__keyword { flex-basis:9rem; } .sys-todo-filter__actions { width:100%; } .sys-todo-filter__actions .sys-todo-page__button { flex:1; } } - @media (max-width:720px) { .sys-todo-summary { grid-template-columns:repeat(2,minmax(0,1fr)); } .sys-todo-filter label, .sys-todo-filter label.sys-todo-filter__keyword { flex-basis:10rem; } .sys-todo-field--2,.sys-todo-field--3,.sys-todo-field--4,.sys-todo-field--6 { grid-column:span 12; } } + @media (max-width:720px) { .sys-todo-summary { grid-template-columns:repeat(2,minmax(0,1fr)); } .sys-todo-filter label, .sys-todo-filter label.sys-todo-filter__keyword { flex-basis:10rem; } .sys-todo-field--2,.sys-todo-field--3,.sys-todo-field--4,.sys-todo-field--6 { grid-column:span 12; } .sys-todo-combined-grid { grid-column:span 12; grid-template-columns:1fr; } }
日常过会使用。新增事项、跟进责任人和完成情况,截图直接附在该事项下。
- -日常过会使用。在类型、内容或序号旁直接添加,筛选修改后仍保持当前筛选结果。
| 类型 | 内容 | 待办事项序号 | 待办事项 | 总负责人 | 责任人 | -开始时间 | 完成时间 | 状态 | 完成情况 | 完成截图 | 备注 | +类型 | +内容 | +待办事项序号 | +待办事项 | +总负责人 | +责任人 | +开始时间 | +完成时间 | +提醒 | +状态 | +完成情况 / 备注 | +完成截图 | @@ -442,12 +492,27 @@ | {{ $item->owner ?: '—' }} | {{ $item->assignee ?: '—' }} | {{ $item->start_date?->format('Y-m-d') ?: ($item->start_note ?: '—') }} | -
- {{ $item->due_date?->format('Y-m-d') ?: ($item->due_note ?: '—') }}
- @if ($isOverdue) 已逾期@endif + | {{ $item->due_date?->format('Y-m-d') ?: ($item->due_note ?: '—') }} | +{{ $item->reminder_label }} | +{{ $item->status_label }} | +
+ @if ($item->progress || $item->notes)
+ @if ($item->progress)
+
+ 完成情况
+ {{ $item->progress }}
+
+ @endif
+ @if ($item->notes)
+
+ 备注
+ {{ $item->notes }}
+
+ @endif
+ @else
+ —
+ @endif
|
- {{ $item->status }} | -{{ $item->progress ?: '—' }} |
@if ($item->attachments->isNotEmpty())
@@ -468,12 +533,21 @@
—
@endif
|
- {{ $item->notes ?: '—' }} | @empty
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
当前筛选条件下没有待办事项。 | |||||||||||||||||||||||
| + + | +也可在现有类型下方新增内容,在内容下方新增待办。 | +||||||||||||||||||||||