feat: 限制系统化待办删除权限 (#1055)

This commit is contained in:
chenxi 2026-11-10 17:47:13 +08:00
parent 14adc83014
commit d803d97c19
3 changed files with 72 additions and 10 deletions

View File

@ -45,6 +45,7 @@ class SystemizationTodos extends Controller
'秋香',
'待定',
];
private const TODO_DELETE_ACCOUNTS = ['江陵', 'jiangling'];
private const COLUMN_WIDTHS_SETTING = 'business.systemization_todos.column_widths';
private const COLUMN_WIDTH_KEYS = [
'type',
@ -156,6 +157,7 @@ class SystemizationTodos extends Controller
)),
'todoColumnWidths' => $this->columnWidths(),
'todoSummary' => $summary,
'canDeleteTodos' => $this->canDeleteTodos(),
'todoRoutes' => [
'store' => route('business.systemization-todos.store'),
'update' => route('business.systemization-todos.update', ['todo' => '__TODO__']),
@ -800,6 +802,8 @@ class SystemizationTodos extends Controller
public function destroy(SystemizationTodo $todo): RedirectResponse
{
abort_unless($this->canDeleteTodos(), 403, '只有江陵可以删除待办事项。');
DB::transaction(function () use ($todo): void {
$before = $this->audit->snapshot($todo, true);
$todo->update(['updated_by' => user_id()]);
@ -819,6 +823,13 @@ class SystemizationTodos extends Controller
return $this->backToPage('待办事项已删除。');
}
private function canDeleteTodos(): bool
{
$accountName = mb_strtolower(trim((string) (user()?->name ?? '')));
return in_array($accountName, self::TODO_DELETE_ACCOUNTS, true);
}
public function batchUpdateCells(Request $request): JsonResponse
{
$data = $request->validate([

View File

@ -728,14 +728,16 @@
@disabled((int) $item->item_sequence >= (int) $item->content_count)>
<span class="material-icons-outlined" aria-hidden="true">arrow_downward</span>
</button>
<button type="button" class="sys-todo-table__row-action sys-todo-table__row-action--danger"
data-sys-todo-grid-delete
data-sys-todo-grid-delete-url="{{ route('business.systemization-todos.destroy', ['todo' => $item->id]) }}"
data-sys-todo-grid-delete-title="{{ $titleLabels[$loop->index] }}"
title="删除这一行"
aria-label="删除第 {{ (int) $item->item_sequence }} 条待办">
<span class="material-icons-outlined" aria-hidden="true">delete_outline</span>
</button>
@if ($canDeleteTodos)
<button type="button" class="sys-todo-table__row-action sys-todo-table__row-action--danger"
data-sys-todo-grid-delete
data-sys-todo-grid-delete-url="{{ route('business.systemization-todos.destroy', ['todo' => $item->id]) }}"
data-sys-todo-grid-delete-title="{{ $titleLabels[$loop->index] }}"
title="删除这一行"
aria-label="删除第 {{ (int) $item->item_sequence }} 条待办">
<span class="material-icons-outlined" aria-hidden="true">delete_outline</span>
</button>
@endif
</span>
</td>
<td class="sys-todo-table__title" title="点击待办文字直接编辑">
@ -1022,7 +1024,9 @@
</div>
</div>
<div class="sys-todo-dialog__foot">
<button type="button" class="sys-todo-dialog__delete" data-sys-todo-open-delete hidden>删除待办</button>
@if ($canDeleteTodos)
<button type="button" class="sys-todo-dialog__delete" data-sys-todo-open-delete hidden>删除待办</button>
@endif
<button type="button" data-sys-todo-close>取消</button>
<button type="submit">保存</button>
</div>
@ -2032,7 +2036,7 @@
title.textContent = currentTodoId
? '编辑待办'
: (payload && payload.afterSequence ? '在第 ' + payload.afterSequence + ' 条后新增待办' : '新增待办');
deleteButton.hidden = !currentTodoId;
if (deleteButton) deleteButton.hidden = !currentTodoId;
if (!currentTodoId) {
payload = Object.assign({}, payload || {});
if (!payload.owner && activeTodoFilters.owners.length) {

View File

@ -18,6 +18,7 @@ class SystemizationTodosTest extends FeatureTestCase
{
Storage::fake('uploads');
$this->loginAsSystemizationTodoDeleter();
$pageResponse = $this->loginAs()
->get(route('business.pages.show', [
'section' => 'other',
@ -1965,6 +1966,7 @@ class SystemizationTodosTest extends FeatureTestCase
public function test_manual_changes_are_listed_in_operation_history_and_csv(): void
{
$this->loginAsSystemizationTodoDeleter();
$this->loginAs();
$this->post(route('business.systemization-todos.store'), [
@ -2014,6 +2016,45 @@ class SystemizationTodosTest extends FeatureTestCase
$this->assertStringNotContainsString('新增:', $csv);
}
public function test_only_jiangling_can_see_or_use_todo_delete_entry_points(): void
{
$this->loginAs();
$todo = $this->createTodo('收入', '删除权限测试', '普通账号不得删除', '2026-11-30', 1);
$this->get(route('business.pages.show', [
'section' => 'other',
'page' => 'systemization-todos',
]))->assertOk()
->assertDontSee('data-sys-todo-grid-delete-url="', false)
->assertDontSee('data-sys-todo-open-delete hidden', false);
$this->withExceptionHandling()
->delete(route('business.systemization-todos.destroy', ['todo' => $todo->id]))
->assertForbidden();
$this->assertNotSoftDeleted('business_systemization_todos', ['id' => $todo->id]);
}
public function test_jiangling_can_delete_todos(): void
{
$this->user->update(['name' => '江陵']);
$this->user->refresh();
$this->loginAs();
$todo = $this->createTodo('收入', '删除权限测试', '江陵可以删除', '2026-11-30', 1);
$this->get(route('business.pages.show', [
'section' => 'other',
'page' => 'systemization-todos',
]))->assertOk()
->assertSee('data-sys-todo-grid-delete-url="', false)
->assertSee('data-sys-todo-open-delete hidden', false);
$this->delete(route('business.systemization-todos.destroy', ['todo' => $todo->id]))
->assertRedirect();
$this->assertSoftDeleted('business_systemization_todos', ['id' => $todo->id]);
}
public function test_operation_history_starts_from_today(): void
{
$this->loginAs();
@ -2280,6 +2321,12 @@ class SystemizationTodosTest extends FeatureTestCase
]);
}
private function loginAsSystemizationTodoDeleter(): void
{
$this->user->update(['name' => '江陵']);
$this->user->refresh();
}
private function writeWorkbook(string $path, array $rows): void
{
$spreadsheet = new Spreadsheet();