统一系统化待办筛选与新增选项 (#569)
This commit is contained in:
parent
3c7757d848
commit
4c30f2e628
@ -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()
|
||||
|
||||
@ -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 @@
|
||||
<form class="sys-todo-filter" method="GET" action="{{ request()->url() }}">
|
||||
<label>状态<select name="status">
|
||||
<option value="">全部状态</option>
|
||||
<option value="{{ $todoOverdueFilter }}" @selected($todoFilters['status'] === $todoOverdueFilter)>已逾期</option>
|
||||
@foreach ($todoStatuses as $status)
|
||||
<option value="{{ $status }}" @selected($todoFilters['status'] === $status)>{{ $status }}</option>
|
||||
@foreach ($todoStatusOptions as $statusOption)
|
||||
<option value="{{ $statusOption['value'] }}" @selected($todoFilters['status'] === $statusOption['value'])>{{ $statusOption['label'] }}</option>
|
||||
@endforeach
|
||||
</select></label>
|
||||
<label>类型<select name="category">
|
||||
@ -180,37 +186,54 @@
|
||||
<option value="{{ $category }}" @selected($todoFilters['category'] === $category)>{{ $category }}</option>
|
||||
@endforeach
|
||||
</select></label>
|
||||
<label>总负责人<select name="owner">
|
||||
<option value="">全部总负责人</option>
|
||||
@foreach ($todoOwners as $owner)
|
||||
<option value="{{ $owner }}" @selected($todoFilters['owner'] === $owner)>{{ $owner }}</option>
|
||||
@endforeach
|
||||
</select></label>
|
||||
@php
|
||||
$selectedOwners = $todoOwners->filter(
|
||||
static fn (string $owner): bool => in_array($owner, $todoFilters['owners'], true)
|
||||
);
|
||||
$selectedAssignees = $todoAssignees->filter(
|
||||
static fn (string $assignee): bool => in_array($assignee, $todoFilters['assignees'], true)
|
||||
);
|
||||
$assigneeSummary = $selectedAssignees->isEmpty()
|
||||
? '全部责任人'
|
||||
: '已选 ' . $selectedAssignees->count() . ' 人:' . $selectedAssignees->implode('、');
|
||||
$ownerSummary = $selectedOwners->isEmpty() ? '全部总负责人' : '已选 ' . $selectedOwners->count() . ' 人';
|
||||
$assigneeSummary = $selectedAssignees->isEmpty() ? '全部责任人' : '已选 ' . $selectedAssignees->count() . ' 人';
|
||||
@endphp
|
||||
<details class="sys-todo-filter__multi" data-sys-todo-assignee-filter>
|
||||
<summary>
|
||||
<span class="sys-todo-filter__multi-label">责任人</span>
|
||||
<span class="sys-todo-filter__multi-value" data-sys-todo-assignee-summary>{{ $assigneeSummary }}</span>
|
||||
<span class="material-icons-outlined" aria-hidden="true">expand_more</span>
|
||||
</summary>
|
||||
<div class="sys-todo-filter__multi-menu">
|
||||
@forelse ($todoAssignees as $assignee)
|
||||
<label>
|
||||
<input type="checkbox" name="assignee[]" value="{{ $assignee }}" @checked(in_array($assignee, $todoFilters['assignees'], true))>
|
||||
<span>{{ $assignee }}</span>
|
||||
</label>
|
||||
@empty
|
||||
<span class="sys-todo-filter__multi-empty">暂无可选责任人</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</details>
|
||||
<div class="sys-todo-filter__multi">
|
||||
<span class="sys-todo-filter__multi-label">总负责人</span>
|
||||
<details data-sys-todo-owner-filter>
|
||||
<summary>
|
||||
<span class="sys-todo-filter__multi-value" data-sys-todo-owner-summary title="{{ $selectedOwners->implode('、') }}">{{ $ownerSummary }}</span>
|
||||
<span class="material-icons-outlined" aria-hidden="true">expand_more</span>
|
||||
</summary>
|
||||
<div class="sys-todo-filter__multi-menu">
|
||||
@forelse ($todoOwners as $owner)
|
||||
<label>
|
||||
<input type="checkbox" name="owner[]" value="{{ $owner }}" @checked(in_array($owner, $todoFilters['owners'], true))>
|
||||
<span>{{ $owner }}</span>
|
||||
</label>
|
||||
@empty
|
||||
<span class="sys-todo-filter__multi-empty">暂无可选总负责人</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="sys-todo-filter__multi">
|
||||
<span class="sys-todo-filter__multi-label">责任人</span>
|
||||
<details data-sys-todo-assignee-filter>
|
||||
<summary>
|
||||
<span class="sys-todo-filter__multi-value" data-sys-todo-assignee-summary title="{{ $selectedAssignees->implode('、') }}">{{ $assigneeSummary }}</span>
|
||||
<span class="material-icons-outlined" aria-hidden="true">expand_more</span>
|
||||
</summary>
|
||||
<div class="sys-todo-filter__multi-menu">
|
||||
@forelse ($todoAssignees as $assignee)
|
||||
<label>
|
||||
<input type="checkbox" name="assignee[]" value="{{ $assignee }}" @checked(in_array($assignee, $todoFilters['assignees'], true))>
|
||||
<span>{{ $assignee }}</span>
|
||||
</label>
|
||||
@empty
|
||||
<span class="sys-todo-filter__multi-empty">暂无可选责任人</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<label>开始时间<input type="date" name="start_date" value="{{ $todoFilters['start_date'] }}"></label>
|
||||
<label>完成时间<input type="date" name="due_date" value="{{ $todoFilters['due_date'] }}"></label>
|
||||
<label class="sys-todo-filter__keyword">关键词<input type="search" name="keyword" value="{{ $todoFilters['keyword'] }}" placeholder="事项、内容、责任人、进展或备注"></label>
|
||||
@ -418,11 +441,34 @@
|
||||
</div>
|
||||
<div class="sys-todo-dialog__body">
|
||||
<div class="sys-todo-form-grid">
|
||||
<label class="sys-todo-field sys-todo-field--3">类型<input name="category" maxlength="100" list="sysTodoCategories"></label>
|
||||
<div class="sys-todo-field sys-todo-field--3">
|
||||
<label for="sysTodoCategorySelect">类型</label>
|
||||
<select id="sysTodoCategorySelect" data-sys-todo-category-select>
|
||||
<option value="">请选择类型</option>
|
||||
@foreach ($todoCategories as $category)
|
||||
<option value="{{ $category }}">{{ $category }}</option>
|
||||
@endforeach
|
||||
<option value="__new__">+ 新增类型</option>
|
||||
</select>
|
||||
<input type="hidden" name="category" data-sys-todo-category-value>
|
||||
<div class="sys-todo-new-category" data-sys-todo-new-category hidden>
|
||||
<input type="text" maxlength="100" placeholder="输入新类型" data-sys-todo-new-category-input>
|
||||
</div>
|
||||
</div>
|
||||
<label class="sys-todo-field sys-todo-field--4">内容<input name="section" maxlength="255"></label>
|
||||
<label class="sys-todo-field sys-todo-field--3">状态<select name="status" required>@foreach ($todoStatuses as $status)<option value="{{ $status }}">{{ $status }}</option>@endforeach</select></label>
|
||||
<label class="sys-todo-field sys-todo-field--3">总负责人<input name="owner" maxlength="255" list="sysTodoOwners"></label>
|
||||
<label class="sys-todo-field sys-todo-field--3">责任人<input name="assignee" maxlength="255" list="sysTodoOwners"></label>
|
||||
<div class="sys-todo-field sys-todo-field--3">
|
||||
<span>总负责人</span>
|
||||
<div class="sys-todo-owner-checks" data-sys-todo-owner-options>
|
||||
@forelse ($todoOwners as $owner)
|
||||
<label><input type="checkbox" value="{{ $owner }}"><span>{{ $owner }}</span></label>
|
||||
@empty
|
||||
<span class="sys-todo-filter__multi-empty">暂无可选总负责人</span>
|
||||
@endforelse
|
||||
</div>
|
||||
<input type="hidden" name="owner" data-sys-todo-owner-value>
|
||||
</div>
|
||||
<label class="sys-todo-field sys-todo-field--3">责任人<input name="assignee" maxlength="255" list="sysTodoAssignees"></label>
|
||||
<label class="sys-todo-field sys-todo-field--3">开始时间<input type="date" name="start_date"></label>
|
||||
<label class="sys-todo-field sys-todo-field--3">完成时间<input type="date" name="due_date"></label>
|
||||
<label class="sys-todo-field sys-todo-field--6">待办事项<textarea name="title" maxlength="5000" required></textarea></label>
|
||||
@ -459,8 +505,7 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<datalist id="sysTodoCategories">@foreach ($todoCategories as $category)<option value="{{ $category }}">@endforeach</datalist>
|
||||
<datalist id="sysTodoOwners">@foreach ($todoOwners->merge($todoAssignees)->unique()->sort() as $person)<option value="{{ $person }}">@endforeach</datalist>
|
||||
<datalist id="sysTodoAssignees">@foreach ($todoAssignees as $assignee)<option value="{{ $assignee }}">@endforeach</datalist>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
@ -476,9 +521,19 @@
|
||||
var imagePreview = imageViewer.querySelector('[data-sys-todo-image-preview]');
|
||||
var imageTitle = imageViewer.querySelector('[data-sys-todo-image-title]');
|
||||
var imageOpen = imageViewer.querySelector('[data-sys-todo-image-open]');
|
||||
var categorySelect = dialog.querySelector('[data-sys-todo-category-select]');
|
||||
var categoryValue = dialog.querySelector('[data-sys-todo-category-value]');
|
||||
var newCategoryWrap = dialog.querySelector('[data-sys-todo-new-category]');
|
||||
var newCategoryInput = dialog.querySelector('[data-sys-todo-new-category-input]');
|
||||
var ownerValue = dialog.querySelector('[data-sys-todo-owner-value]');
|
||||
var ownerOptionInputs = Array.prototype.slice.call(
|
||||
dialog.querySelectorAll('[data-sys-todo-owner-options] input[type="checkbox"]')
|
||||
);
|
||||
var routes = @json($todoRoutes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
var currentTodoId = null;
|
||||
var uploadPrompt = '点击选择、拖入此区域或直接粘贴,支持多张,每张不超过 10 MB。';
|
||||
var ownerFilter = document.querySelector('[data-sys-todo-owner-filter]');
|
||||
var ownerSummary = ownerFilter.querySelector('[data-sys-todo-owner-summary]');
|
||||
var assigneeFilter = document.querySelector('[data-sys-todo-assignee-filter]');
|
||||
var assigneeSummary = assigneeFilter.querySelector('[data-sys-todo-assignee-summary]');
|
||||
|
||||
@ -500,13 +555,68 @@
|
||||
.join('、');
|
||||
}
|
||||
|
||||
function updateAssigneeSummary() {
|
||||
function updatePeopleFilterSummary(filter, summary) {
|
||||
var selected = Array.prototype.slice
|
||||
.call(assigneeFilter.querySelectorAll('input[name="assignee[]"]:checked'))
|
||||
.call(filter.querySelectorAll('input[type="checkbox"]:checked'))
|
||||
.map(function (input) { return input.value; });
|
||||
assigneeSummary.textContent = selected.length
|
||||
? '已选 ' + selected.length + ' 人:' + selected.join('、')
|
||||
: '全部责任人';
|
||||
summary.textContent = selected.length
|
||||
? '已选 ' + selected.length + ' 人'
|
||||
: (filter === ownerFilter ? '全部总负责人' : '全部责任人');
|
||||
summary.title = selected.join('、');
|
||||
}
|
||||
|
||||
function splitPeople(value) {
|
||||
return String(value || '')
|
||||
.split(/[、,,]/)
|
||||
.map(function (person) { return person.trim(); })
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function updateOwnerValue() {
|
||||
ownerValue.value = ownerOptionInputs
|
||||
.filter(function (input) { return input.checked; })
|
||||
.map(function (input) { return input.value; })
|
||||
.join('、');
|
||||
}
|
||||
|
||||
function setOwnerValue(value) {
|
||||
var selected = splitPeople(value);
|
||||
ownerOptionInputs.forEach(function (input) {
|
||||
input.checked = selected.indexOf(input.value) !== -1;
|
||||
});
|
||||
updateOwnerValue();
|
||||
}
|
||||
|
||||
function setCategoryValue(value) {
|
||||
var normalized = String(value || '').trim();
|
||||
var hasOption = Array.prototype.some.call(
|
||||
categorySelect.options,
|
||||
function (option) { return option.value === normalized && normalized !== '__new__'; }
|
||||
);
|
||||
|
||||
if (normalized !== '' && !hasOption) {
|
||||
categorySelect.value = '__new__';
|
||||
newCategoryWrap.hidden = false;
|
||||
newCategoryInput.required = true;
|
||||
newCategoryInput.value = normalized;
|
||||
categoryValue.value = normalized;
|
||||
return;
|
||||
}
|
||||
|
||||
categorySelect.value = normalized;
|
||||
newCategoryWrap.hidden = true;
|
||||
newCategoryInput.required = false;
|
||||
newCategoryInput.value = '';
|
||||
categoryValue.value = normalized;
|
||||
}
|
||||
|
||||
function syncCategoryValue() {
|
||||
var creating = categorySelect.value === '__new__';
|
||||
newCategoryWrap.hidden = !creating;
|
||||
newCategoryInput.required = creating;
|
||||
categoryValue.value = creating
|
||||
? newCategoryInput.value.trim()
|
||||
: categorySelect.value;
|
||||
}
|
||||
|
||||
function appendScreenshotFiles(files) {
|
||||
@ -556,10 +666,10 @@
|
||||
form.action = currentTodoId ? routes.update.replace('__TODO__', currentTodoId) : routes.store;
|
||||
title.textContent = currentTodoId ? '编辑待办' : '新增待办';
|
||||
if (payload) {
|
||||
setValue('category', payload.category);
|
||||
setCategoryValue(payload.category);
|
||||
setValue('section', payload.section);
|
||||
setValue('title', payload.title);
|
||||
setValue('owner', payload.owner);
|
||||
setOwnerValue(payload.owner);
|
||||
setValue('assignee', payload.assignee);
|
||||
setValue('start_date', payload.start_date);
|
||||
setValue('due_date', payload.due_date);
|
||||
@ -567,6 +677,9 @@
|
||||
setValue('completed_at', payload.completed_at);
|
||||
setValue('progress', payload.progress);
|
||||
setValue('notes', payload.notes);
|
||||
} else {
|
||||
setCategoryValue('');
|
||||
setOwnerValue('');
|
||||
}
|
||||
if (!currentTodoId && !(payload && payload.status)) {
|
||||
setValue('status', '待办');
|
||||
@ -659,13 +772,30 @@
|
||||
imageViewer.addEventListener('click', function (event) {
|
||||
if (event.target === imageViewer) closeImageViewer();
|
||||
});
|
||||
categorySelect.addEventListener('change', syncCategoryValue);
|
||||
newCategoryInput.addEventListener('input', function () {
|
||||
if (categorySelect.value === '__new__') syncCategoryValue();
|
||||
});
|
||||
ownerOptionInputs.forEach(function (input) {
|
||||
input.addEventListener('change', updateOwnerValue);
|
||||
});
|
||||
ownerFilter.querySelectorAll('input[name="owner[]"]').forEach(function (input) {
|
||||
input.addEventListener('change', function () {
|
||||
updatePeopleFilterSummary(ownerFilter, ownerSummary);
|
||||
});
|
||||
});
|
||||
assigneeFilter.querySelectorAll('input[name="assignee[]"]').forEach(function (input) {
|
||||
input.addEventListener('change', updateAssigneeSummary);
|
||||
input.addEventListener('change', function () {
|
||||
updatePeopleFilterSummary(assigneeFilter, assigneeSummary);
|
||||
});
|
||||
});
|
||||
document.addEventListener('click', function (event) {
|
||||
if (!event.target.closest('[data-sys-todo-assignee-filter]')) {
|
||||
assigneeFilter.removeAttribute('open');
|
||||
}
|
||||
if (!event.target.closest('[data-sys-todo-owner-filter]')) {
|
||||
ownerFilter.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.target.matches('[data-sys-todo-image-close]') && (event.key === 'Enter' || event.key === ' ')) {
|
||||
|
||||
@ -33,8 +33,13 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
->assertSeeText('完成时间')
|
||||
->assertSeeText('在列表底部新增一条')
|
||||
->assertSee('data-sys-todo-create', false)
|
||||
->assertSee('data-sys-todo-owner-filter', false)
|
||||
->assertSee('data-sys-todo-assignee-filter', false)
|
||||
->assertSee('name="owner[]"', false)
|
||||
->assertSee('name="assignee[]"', false)
|
||||
->assertSee('data-sys-todo-category-select', false)
|
||||
->assertSee('data-sys-todo-new-category-input', false)
|
||||
->assertSeeText('+ 新增类型')
|
||||
->assertSee('data-sys-todo-choose-files', false)
|
||||
->assertSee('id="sysTodoImageViewer"', false);
|
||||
|
||||
@ -42,7 +47,7 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
'category' => '收入',
|
||||
'section' => '平台收入核对',
|
||||
'title' => '核对淘系收入与平台账单差异',
|
||||
'owner' => '晨曦',
|
||||
'owner' => '晨曦、人参',
|
||||
'assignee' => '栎阳',
|
||||
'start_date' => '2026-10-25',
|
||||
'due_date' => '2026-10-28',
|
||||
@ -60,6 +65,7 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
|
||||
$todo = SystemizationTodo::query()->firstOrFail();
|
||||
$this->assertSame('收入', $todo->category);
|
||||
$this->assertSame('晨曦、人参', $todo->owner);
|
||||
$this->assertSame(SystemizationTodo::STATUS_IN_PROGRESS, $todo->status);
|
||||
$this->assertSame(2, $todo->attachments()->count());
|
||||
$this->get(route('business.pages.show', [
|
||||
@ -184,7 +190,12 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
->assertDontSee('3、成本事项')
|
||||
->assertSee('收入事项 A')
|
||||
->assertSee('收入事项 B');
|
||||
$pageItems = $pageResponse->viewData('content')['items']->getCollection();
|
||||
$pageContent = $pageResponse->viewData('content');
|
||||
$this->assertSame(
|
||||
['__overdue__', '待办'],
|
||||
array_column($pageContent['todoStatusOptions']->all(), 'value'),
|
||||
);
|
||||
$pageItems = $pageContent['items']->getCollection();
|
||||
$this->assertSame(
|
||||
[1, 2],
|
||||
$pageItems->where('category', '收入')
|
||||
@ -226,6 +237,16 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
$peopleContent['items']->pluck('title')->all(),
|
||||
);
|
||||
|
||||
$ownerResponse = $this->get(route('business.pages.show', [
|
||||
'section' => 'other',
|
||||
'page' => 'systemization-todos',
|
||||
'owner' => ['常仪', '人参'],
|
||||
]))->assertOk();
|
||||
$this->assertEqualsCanonicalizing(
|
||||
['3、成本事项', '4、逾期事项'],
|
||||
$ownerResponse->viewData('content')['items']->pluck('title')->all(),
|
||||
);
|
||||
|
||||
$overdueResponse = $this->get(route('business.pages.show', [
|
||||
'section' => 'other',
|
||||
'page' => 'systemization-todos',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user