待办内容支持选择或新增 (#631)
This commit is contained in:
parent
eda75f96a9
commit
38a765fa47
@ -146,6 +146,7 @@ class SystemizationTodos extends Controller
|
||||
'todoPositionGroups' => $positionGroups,
|
||||
'todoFilters' => $filters,
|
||||
'todoCategories' => $categories,
|
||||
'todoContentOptions' => $this->contentOptions(),
|
||||
'todoOwners' => $this->peopleOptions('owner'),
|
||||
'todoAssignees' => $this->peopleOptions('assignee'),
|
||||
'todoStatuses' => SystemizationTodo::STATUSES,
|
||||
@ -636,6 +637,30 @@ class SystemizationTodos extends Controller
|
||||
->values();
|
||||
}
|
||||
|
||||
private function contentOptions(): array
|
||||
{
|
||||
$options = [];
|
||||
$todos = SystemizationTodo::query()
|
||||
->whereNotNull('section')
|
||||
->where('section', '<>', '')
|
||||
->get(['category', 'section']);
|
||||
foreach ($todos as $todo) {
|
||||
$category = trim((string) ($todo->category ?? ''));
|
||||
$section = $this->hierarchyLabel($todo->section);
|
||||
if ($section !== '') {
|
||||
$options[$category][$section] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($options as $category => $sections) {
|
||||
$labels = array_keys($sections);
|
||||
usort($labels, 'strnatcasecmp');
|
||||
$options[$category] = $labels;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function normalizeDateFilter(mixed $value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
@ -486,7 +486,20 @@
|
||||
<input type="text" maxlength="100" placeholder="输入新类型" data-sys-todo-new-category-input>
|
||||
</div>
|
||||
</div>
|
||||
<label class="sys-todo-field sys-todo-field--4">内容<textarea name="section" maxlength="255" rows="2" data-sys-todo-auto-grow></textarea></label>
|
||||
<div class="sys-todo-field sys-todo-field--4">
|
||||
<label for="sysTodoSectionSelect">内容</label>
|
||||
<select id="sysTodoSectionSelect" data-sys-todo-section-select>
|
||||
<option value="">请选择内容</option>
|
||||
@foreach (collect($todoContentOptions)->flatten()->unique()->sort()->values() as $section)
|
||||
<option value="{{ $section }}">{{ $section }}</option>
|
||||
@endforeach
|
||||
<option value="__new__">+ 新增内容</option>
|
||||
</select>
|
||||
<input type="hidden" name="section" data-sys-todo-section-value>
|
||||
<div class="sys-todo-new-category" data-sys-todo-new-section hidden>
|
||||
<input type="text" maxlength="255" placeholder="输入新内容" data-sys-todo-new-section-input>
|
||||
</div>
|
||||
</div>
|
||||
<label class="sys-todo-field sys-todo-field--2 sys-todo-position-field" data-sys-todo-position-field>待办事项序号
|
||||
<select name="position" data-sys-todo-position></select>
|
||||
</label>
|
||||
@ -601,7 +614,10 @@
|
||||
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 sectionInput = dialog.querySelector('[name="section"]');
|
||||
var sectionSelect = dialog.querySelector('[data-sys-todo-section-select]');
|
||||
var sectionValue = dialog.querySelector('[data-sys-todo-section-value]');
|
||||
var newSectionWrap = dialog.querySelector('[data-sys-todo-new-section]');
|
||||
var newSectionInput = dialog.querySelector('[data-sys-todo-new-section-input]');
|
||||
var positionField = dialog.querySelector('[data-sys-todo-position-field]');
|
||||
var positionSelect = dialog.querySelector('[data-sys-todo-position]');
|
||||
var ownerValue = dialog.querySelector('[data-sys-todo-owner-value]');
|
||||
@ -620,6 +636,7 @@
|
||||
var groupNote = groupDialog.querySelector('[data-sys-todo-group-note]');
|
||||
var routes = @json($todoRoutes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
var positionGroups = @json($todoPositionGroups, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
var contentOptions = @json($todoContentOptions, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
var currentTodoId = null;
|
||||
var currentGroupTodoId = null;
|
||||
var currentTodoTitle = '';
|
||||
@ -664,7 +681,7 @@
|
||||
|
||||
function currentPositionGroup() {
|
||||
var category = normalizedGroupText(categoryValue.value);
|
||||
var section = normalizedGroupText(sectionInput.value);
|
||||
var section = normalizedGroupText(sectionValue.value);
|
||||
return positionGroups.find(function (group) {
|
||||
return normalizedGroupText(group.category) === category
|
||||
&& normalizedGroupText(group.section) === section;
|
||||
@ -794,6 +811,76 @@
|
||||
: categorySelect.value;
|
||||
}
|
||||
|
||||
function availableSectionLabels() {
|
||||
var category = categoryValue.value;
|
||||
if (category !== '' && Array.isArray(contentOptions[category])) {
|
||||
return contentOptions[category].slice();
|
||||
}
|
||||
|
||||
var labels = Object.values(contentOptions || {}).flat();
|
||||
return Array.from(new Set(labels)).sort(function (left, right) {
|
||||
return String(left).localeCompare(String(right), 'zh-CN');
|
||||
});
|
||||
}
|
||||
|
||||
function setSectionSelection(value) {
|
||||
var normalized = String(value || '').trim();
|
||||
var hasOption = Array.prototype.some.call(
|
||||
sectionSelect.options,
|
||||
function (option) { return option.value === normalized && normalized !== '__new__'; }
|
||||
);
|
||||
|
||||
if (normalized !== '' && !hasOption) {
|
||||
var custom = document.createElement('option');
|
||||
custom.value = normalized;
|
||||
custom.textContent = normalized;
|
||||
sectionSelect.insertBefore(custom, sectionSelect.lastElementChild);
|
||||
hasOption = true;
|
||||
}
|
||||
|
||||
sectionSelect.value = normalized;
|
||||
newSectionWrap.hidden = true;
|
||||
newSectionInput.required = false;
|
||||
newSectionInput.value = '';
|
||||
sectionValue.value = normalized;
|
||||
}
|
||||
|
||||
function refreshSectionOptions(selectedValue) {
|
||||
var normalized = String(selectedValue || '').trim();
|
||||
var labels = availableSectionLabels();
|
||||
sectionSelect.replaceChildren();
|
||||
var placeholder = document.createElement('option');
|
||||
placeholder.value = '';
|
||||
placeholder.textContent = '请选择内容';
|
||||
sectionSelect.appendChild(placeholder);
|
||||
labels.forEach(function (label) {
|
||||
var option = document.createElement('option');
|
||||
option.value = label;
|
||||
option.textContent = label;
|
||||
sectionSelect.appendChild(option);
|
||||
});
|
||||
if (normalized !== '' && labels.indexOf(normalized) === -1) {
|
||||
var current = document.createElement('option');
|
||||
current.value = normalized;
|
||||
current.textContent = normalized;
|
||||
sectionSelect.appendChild(current);
|
||||
}
|
||||
var create = document.createElement('option');
|
||||
create.value = '__new__';
|
||||
create.textContent = '+ 新增内容';
|
||||
sectionSelect.appendChild(create);
|
||||
setSectionSelection(normalized);
|
||||
}
|
||||
|
||||
function syncSectionValue() {
|
||||
var creating = sectionSelect.value === '__new__';
|
||||
newSectionWrap.hidden = !creating;
|
||||
newSectionInput.required = creating;
|
||||
sectionValue.value = creating
|
||||
? newSectionInput.value.trim()
|
||||
: sectionSelect.value;
|
||||
}
|
||||
|
||||
function appendScreenshotFiles(files) {
|
||||
var images = Array.prototype.slice.call(files || []).filter(function (file) {
|
||||
return /^image\/(jpeg|png|webp)$/.test(file.type)
|
||||
@ -866,7 +953,7 @@
|
||||
deleteButton.hidden = !currentTodoId;
|
||||
if (payload) {
|
||||
setCategoryValue(payload.category);
|
||||
setValue('section', payload.section);
|
||||
refreshSectionOptions(payload.section);
|
||||
setValue('title', payload.title);
|
||||
setOwnerValue(payload.owner);
|
||||
setValue('assignee', payload.assignee);
|
||||
@ -878,6 +965,7 @@
|
||||
setValue('notes', payload.notes);
|
||||
} else {
|
||||
setCategoryValue('');
|
||||
refreshSectionOptions('');
|
||||
setOwnerValue('');
|
||||
}
|
||||
if (!currentTodoId && !(payload && payload.status)) {
|
||||
@ -1016,18 +1104,26 @@
|
||||
});
|
||||
categorySelect.addEventListener('change', function () {
|
||||
syncCategoryValue();
|
||||
refreshSectionOptions(sectionValue.value);
|
||||
populatePositionOptions();
|
||||
});
|
||||
newCategoryInput.addEventListener('input', function () {
|
||||
if (categorySelect.value === '__new__') {
|
||||
syncCategoryValue();
|
||||
refreshSectionOptions(sectionValue.value);
|
||||
populatePositionOptions();
|
||||
}
|
||||
});
|
||||
sectionInput.addEventListener('input', function () {
|
||||
resizeTextarea(sectionInput);
|
||||
sectionSelect.addEventListener('change', function () {
|
||||
syncSectionValue();
|
||||
populatePositionOptions();
|
||||
});
|
||||
newSectionInput.addEventListener('input', function () {
|
||||
if (sectionSelect.value === '__new__') {
|
||||
syncSectionValue();
|
||||
populatePositionOptions();
|
||||
}
|
||||
});
|
||||
dialog.addEventListener('input', function (event) {
|
||||
if (event.target.matches('[data-sys-todo-auto-grow]')) resizeTextarea(event.target);
|
||||
});
|
||||
|
||||
@ -40,6 +40,8 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
->assertSee('name="assignee[]"', false)
|
||||
->assertSee('data-sys-todo-category-select', false)
|
||||
->assertSee('data-sys-todo-new-category-input', false)
|
||||
->assertSee('data-sys-todo-section-select', false)
|
||||
->assertSee('data-sys-todo-new-section-input', false)
|
||||
->assertSee('data-sys-todo-position', false)
|
||||
->assertSeeText('待办事项序号')
|
||||
->assertDontSee('name="type_sequence"', false)
|
||||
@ -49,6 +51,7 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
->assertSee('id="sysTodoGroupDialog"', false)
|
||||
->assertDontSee('sys-todo-table__clamp', false)
|
||||
->assertSeeText('+ 新增类型')
|
||||
->assertSeeText('+ 新增内容')
|
||||
->assertSee('data-sys-todo-choose-files', false)
|
||||
->assertSee('data-sys-todo-open-delete', false)
|
||||
->assertSee('id="sysTodoDeleteConfirm"', false)
|
||||
@ -320,6 +323,7 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
->assertSee('收入事项 A')
|
||||
->assertSee('收入事项 B');
|
||||
$pageContent = $pageResponse->viewData('content');
|
||||
$this->assertContains('平台收入核对', $pageContent['todoContentOptions']['收入']);
|
||||
$this->assertSame(
|
||||
['__overdue__', '待办'],
|
||||
array_column($pageContent['todoStatusOptions']->all(), 'value'),
|
||||
@ -455,7 +459,10 @@ class SystemizationTodosTest extends FeatureTestCase
|
||||
);
|
||||
$this->assertSame(
|
||||
1,
|
||||
substr_count((string) $response->getContent(), '>' . $mergedSection . '<'),
|
||||
substr_count(
|
||||
(string) $response->getContent(),
|
||||
'sys-todo-table__group-text">' . $mergedSection . '</span>',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user