create-rue 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle.js +2 -2
- package/package.json +1 -1
- package/template/base/app/pages/TodoApp.tsx +148 -249
package/bundle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! create-rue v0.1.
|
|
2
|
+
/*! create-rue v0.1.4 | MIT */
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import "node:fs";
|
|
@@ -2037,7 +2037,7 @@ function inferPackageManager() {
|
|
|
2037
2037
|
//#endregion
|
|
2038
2038
|
//#region package.json
|
|
2039
2039
|
var name = "create-rue";
|
|
2040
|
-
var version = "0.1.
|
|
2040
|
+
var version = "0.1.4";
|
|
2041
2041
|
//#endregion
|
|
2042
2042
|
//#region index.ts
|
|
2043
2043
|
const language = await getLanguage(fileURLToPath(new URL("./locales", import.meta.url)));
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, type FC, ref, watchEffect } from '@rue-js/rue'
|
|
1
|
+
import { computed, type FC, ref, useState, watchEffect } from '@rue-js/rue'
|
|
2
2
|
import { RouterLink } from '@rue-js/router'
|
|
3
3
|
|
|
4
4
|
const todoStorageKey = 'rue.base.todos'
|
|
@@ -211,18 +211,6 @@ const getTodoCreatedAtTime = (item: TodoItem) => {
|
|
|
211
211
|
return parsed.getTime()
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
const getClosestTodoActionElement = (target: EventTarget | null) => {
|
|
215
|
-
if (target instanceof Element) {
|
|
216
|
-
return target.closest<HTMLElement>('[data-todo-action]')
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (target instanceof Node) {
|
|
220
|
-
return target.parentElement?.closest<HTMLElement>('[data-todo-action]') ?? null
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return null
|
|
224
|
-
}
|
|
225
|
-
|
|
226
214
|
const getTodoStorage = () => {
|
|
227
215
|
if (typeof globalThis === 'undefined' || !('localStorage' in globalThis)) {
|
|
228
216
|
return null
|
|
@@ -364,47 +352,41 @@ const statusMeta: Record<
|
|
|
364
352
|
const TodoApp: FC = () => {
|
|
365
353
|
const persistedState = loadPersistedTodoState()
|
|
366
354
|
const initialStateTodos = persistedState?.todos ?? initialTodos
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
const
|
|
355
|
+
const initialActiveFilter: TodoFilter = persistedState?.activeFilter ?? 'all'
|
|
356
|
+
const [todos, setTodos] = useState<TodoItem[]>(initialStateTodos)
|
|
357
|
+
const [draft, setDraft] = useState('')
|
|
358
|
+
const [search, setSearch] = useState(persistedState?.search ?? '')
|
|
359
|
+
const activeFilter = ref<TodoFilter>(initialActiveFilter)
|
|
360
|
+
const setActiveFilter = (nextFilter: TodoFilter) => {
|
|
361
|
+
activeFilter.value = nextFilter
|
|
362
|
+
}
|
|
363
|
+
const [editingId, setEditingId] = useState<number | null>(null)
|
|
364
|
+
const [editingTitle, setEditingTitle] = useState('')
|
|
373
365
|
const nextId = ref(getNextTodoId(initialStateTodos))
|
|
374
366
|
const nextCreatedOrder = ref(getNextCreatedOrder(initialStateTodos))
|
|
375
|
-
const todosVersion = ref(0)
|
|
376
|
-
|
|
377
|
-
const syncTodos = (nextTodos: TodoItem[]) => {
|
|
378
|
-
todos.value = nextTodos
|
|
379
|
-
todosVersion.value += 1
|
|
380
|
-
}
|
|
381
367
|
|
|
382
368
|
watchEffect(() => {
|
|
383
|
-
void todosVersion.value
|
|
384
369
|
persistTodoState({
|
|
385
|
-
todos
|
|
370
|
+
todos,
|
|
386
371
|
search: search.value,
|
|
387
372
|
activeFilter: activeFilter.value,
|
|
388
373
|
})
|
|
389
374
|
})
|
|
390
375
|
|
|
391
376
|
const counts = computed(() => {
|
|
392
|
-
void todosVersion.value
|
|
393
|
-
|
|
394
377
|
return {
|
|
395
|
-
total: todos.
|
|
396
|
-
todo: todos.
|
|
397
|
-
doing: todos.
|
|
398
|
-
done: todos.
|
|
399
|
-
archived: todos.
|
|
378
|
+
total: todos.filter((item) => !item.archived).length,
|
|
379
|
+
todo: todos.filter((item) => !item.archived && item.status === 'todo').length,
|
|
380
|
+
doing: todos.filter((item) => !item.archived && item.status === 'doing').length,
|
|
381
|
+
done: todos.filter((item) => !item.archived && item.status === 'done').length,
|
|
382
|
+
archived: todos.filter((item) => item.archived).length,
|
|
400
383
|
}
|
|
401
384
|
})
|
|
402
385
|
|
|
403
386
|
const visibleTodos = computed(() => {
|
|
404
|
-
void todosVersion.value
|
|
405
387
|
const keyword = search.value.trim().toLowerCase()
|
|
406
388
|
|
|
407
|
-
return todos
|
|
389
|
+
return todos
|
|
408
390
|
.filter((item) => {
|
|
409
391
|
const matchesKeyword = !keyword || item.title.toLowerCase().includes(keyword)
|
|
410
392
|
if (!matchesKeyword) {
|
|
@@ -435,163 +417,65 @@ const TodoApp: FC = () => {
|
|
|
435
417
|
})
|
|
436
418
|
})
|
|
437
419
|
|
|
438
|
-
const visibleTodoCards = computed(() => {
|
|
439
|
-
const currentEditingId = editingId.value
|
|
440
|
-
|
|
441
|
-
return visibleTodos.get().map((item) => ({
|
|
442
|
-
item,
|
|
443
|
-
meta: statusMeta[item.status],
|
|
444
|
-
isEditing: currentEditingId === item.id,
|
|
445
|
-
}))
|
|
446
|
-
})
|
|
447
|
-
|
|
448
420
|
const addTodo = () => {
|
|
449
421
|
const title = draft.value.trim()
|
|
450
422
|
if (!title) {
|
|
451
423
|
return
|
|
452
424
|
}
|
|
453
425
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
draft.value = ''
|
|
426
|
+
const nextTodo: TodoItem = {
|
|
427
|
+
id: nextId.value++,
|
|
428
|
+
title,
|
|
429
|
+
status: 'todo',
|
|
430
|
+
archived: false,
|
|
431
|
+
createdAt: new Date().toISOString(),
|
|
432
|
+
createdOrder: nextCreatedOrder.value++,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
setTodos((current) => [nextTodo, ...current])
|
|
436
|
+
setDraft('')
|
|
466
437
|
}
|
|
467
438
|
|
|
468
439
|
const removeTodo = (id: number) => {
|
|
469
|
-
|
|
440
|
+
setTodos((current) => current.filter((item) => item.id !== id))
|
|
470
441
|
if (editingId.value === id) {
|
|
471
|
-
|
|
442
|
+
setEditingId(null)
|
|
443
|
+
setEditingTitle('')
|
|
472
444
|
}
|
|
473
445
|
}
|
|
474
446
|
|
|
475
447
|
const updateStatus = (id: number, status: TodoStatus) => {
|
|
476
|
-
|
|
477
|
-
|
|
448
|
+
setTodos((current) =>
|
|
449
|
+
current.map((item) => (item.id === id ? { ...item, status, archived: false } : item)),
|
|
478
450
|
)
|
|
479
451
|
}
|
|
480
452
|
|
|
481
453
|
const toggleArchived = (id: number) => {
|
|
482
|
-
|
|
483
|
-
|
|
454
|
+
setTodos((current) =>
|
|
455
|
+
current.map((item) => (item.id === id ? { ...item, archived: !item.archived } : item)),
|
|
484
456
|
)
|
|
485
457
|
}
|
|
486
458
|
|
|
487
459
|
const startEditing = (item: TodoItem) => {
|
|
488
|
-
|
|
489
|
-
|
|
460
|
+
setEditingId(item.id)
|
|
461
|
+
setEditingTitle(item.title)
|
|
490
462
|
}
|
|
491
463
|
|
|
492
464
|
const cancelEditing = () => {
|
|
493
|
-
|
|
494
|
-
|
|
465
|
+
setEditingId(null)
|
|
466
|
+
setEditingTitle('')
|
|
495
467
|
}
|
|
496
468
|
|
|
497
|
-
const saveEditing = (id: number) => {
|
|
498
|
-
const title = editingTitle.value.trim()
|
|
469
|
+
const saveEditing = (id: number, titleOverride?: string) => {
|
|
470
|
+
const title = (titleOverride ?? editingTitle.value).trim()
|
|
499
471
|
if (!title) {
|
|
500
472
|
return
|
|
501
473
|
}
|
|
502
474
|
|
|
503
|
-
|
|
475
|
+
setTodos((current) => current.map((item) => (item.id === id ? { ...item, title } : item)))
|
|
504
476
|
cancelEditing()
|
|
505
477
|
}
|
|
506
478
|
|
|
507
|
-
const handleTodoListClick = (event: MouseEvent) => {
|
|
508
|
-
const actionElement = getClosestTodoActionElement(event.target)
|
|
509
|
-
|
|
510
|
-
if (!actionElement) {
|
|
511
|
-
return
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const action = actionElement.dataset.todoAction
|
|
515
|
-
const id = Number(actionElement.dataset.todoId)
|
|
516
|
-
|
|
517
|
-
if (!Number.isFinite(id)) {
|
|
518
|
-
return
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (action === 'status') {
|
|
522
|
-
const status = actionElement.dataset.todoStatus
|
|
523
|
-
if (isTodoStatus(status)) {
|
|
524
|
-
updateStatus(id, status)
|
|
525
|
-
}
|
|
526
|
-
return
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (action === 'start-editing') {
|
|
530
|
-
const item = todos.value.find((candidate) => candidate.id === id)
|
|
531
|
-
if (item) {
|
|
532
|
-
startEditing(item)
|
|
533
|
-
}
|
|
534
|
-
return
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (action === 'save-editing') {
|
|
538
|
-
saveEditing(id)
|
|
539
|
-
return
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
if (action === 'cancel-editing') {
|
|
543
|
-
cancelEditing()
|
|
544
|
-
return
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (action === 'toggle-archived') {
|
|
548
|
-
toggleArchived(id)
|
|
549
|
-
return
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (action === 'remove') {
|
|
553
|
-
removeTodo(id)
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const handleTodoListInput = (event: Event) => {
|
|
558
|
-
if (!(event.target instanceof HTMLInputElement)) {
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const target = event.target
|
|
563
|
-
if (!target.matches('[data-todo-edit-input]')) {
|
|
564
|
-
return
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
editingTitle.value = target.value
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const handleTodoListKeydown = (event: KeyboardEvent) => {
|
|
571
|
-
if (!(event.target instanceof HTMLInputElement)) {
|
|
572
|
-
return
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const target = event.target
|
|
576
|
-
if (!target.matches('[data-todo-edit-input]')) {
|
|
577
|
-
return
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const id = Number(target.dataset.todoId)
|
|
581
|
-
if (!Number.isFinite(id)) {
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (event.key === 'Enter' && !event.isComposing) {
|
|
586
|
-
event.preventDefault()
|
|
587
|
-
saveEditing(id)
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if (event.key === 'Escape') {
|
|
591
|
-
cancelEditing()
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
479
|
return (
|
|
596
480
|
<div className="space-y-6 pb-4">
|
|
597
481
|
<section className="hero border border-base-300 bg-base-100 shadow-xl">
|
|
@@ -637,7 +521,7 @@ const TodoApp: FC = () => {
|
|
|
637
521
|
value={draft.value}
|
|
638
522
|
placeholder="例如:把日报页接入真实 API"
|
|
639
523
|
onInput={(event: any) => {
|
|
640
|
-
|
|
524
|
+
setDraft((event.target as HTMLInputElement).value)
|
|
641
525
|
}}
|
|
642
526
|
onKeydown={(event: KeyboardEvent) => {
|
|
643
527
|
if (event.key === 'Enter' && !event.isComposing) {
|
|
@@ -661,7 +545,7 @@ const TodoApp: FC = () => {
|
|
|
661
545
|
value={search.value}
|
|
662
546
|
placeholder="按标题筛选任务"
|
|
663
547
|
onInput={(event: any) => {
|
|
664
|
-
|
|
548
|
+
setSearch((event.target as HTMLInputElement).value)
|
|
665
549
|
}}
|
|
666
550
|
/>
|
|
667
551
|
</label>
|
|
@@ -677,7 +561,7 @@ const TodoApp: FC = () => {
|
|
|
677
561
|
: 'btn-ghost border border-base-300'
|
|
678
562
|
}`}
|
|
679
563
|
onClick={() => {
|
|
680
|
-
|
|
564
|
+
setActiveFilter(filter.key)
|
|
681
565
|
}}
|
|
682
566
|
>
|
|
683
567
|
{filter.label}
|
|
@@ -702,130 +586,145 @@ const TodoApp: FC = () => {
|
|
|
702
586
|
</div>
|
|
703
587
|
</section>
|
|
704
588
|
|
|
705
|
-
<section
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
<span
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
589
|
+
<section className="grid gap-4">
|
|
590
|
+
{visibleTodos.get().map((item) => {
|
|
591
|
+
const isEditing = editingId.value === item.id
|
|
592
|
+
const editingValue = isEditing ? editingTitle.value : item.title
|
|
593
|
+
const meta = statusMeta[item.status]
|
|
594
|
+
|
|
595
|
+
const commitEditing = (latestValue: string) => {
|
|
596
|
+
setEditingTitle(latestValue)
|
|
597
|
+
saveEditing(item.id, latestValue)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<article
|
|
602
|
+
key={`${item.id}-${item.title}-${item.status}-${item.archived ? 'archived' : 'active'}-${isEditing ? 'editing' : 'view'}`}
|
|
603
|
+
className={`card border bg-base-100 shadow-sm transition-all ${meta.cardClass} ${
|
|
604
|
+
item.archived ? 'opacity-75' : 'hover:-translate-y-0.5 hover:shadow-md'
|
|
605
|
+
}`}
|
|
606
|
+
>
|
|
607
|
+
<div className="card-body gap-4">
|
|
608
|
+
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
609
|
+
<div className="flex-1 space-y-3">
|
|
610
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
611
|
+
<span
|
|
612
|
+
className={`inline-block h-2.5 w-2.5 rounded-full ${meta.dotClass}`}
|
|
613
|
+
></span>
|
|
614
|
+
<span className={meta.badgeClass}>{meta.label}</span>
|
|
615
|
+
{item.archived && (
|
|
616
|
+
<span className="badge badge-secondary badge-outline">已归档</span>
|
|
617
|
+
)}
|
|
618
|
+
<span className="text-xs text-base-content/50">
|
|
619
|
+
创建于 {formatTodoCreatedAt(item.createdAt)}
|
|
620
|
+
</span>
|
|
621
|
+
</div>
|
|
733
622
|
|
|
734
|
-
{!card.isEditing && (
|
|
735
623
|
<h2
|
|
736
624
|
className={`text-xl font-semibold ${
|
|
737
|
-
|
|
738
|
-
? '
|
|
739
|
-
: '
|
|
625
|
+
isEditing
|
|
626
|
+
? 'hidden'
|
|
627
|
+
: item.status === 'done'
|
|
628
|
+
? 'text-base-content/50 line-through'
|
|
629
|
+
: 'text-base-content'
|
|
740
630
|
}`}
|
|
741
631
|
>
|
|
742
|
-
{
|
|
632
|
+
{item.title}
|
|
743
633
|
</h2>
|
|
744
|
-
)}
|
|
745
634
|
|
|
746
|
-
|
|
747
|
-
|
|
635
|
+
<div
|
|
636
|
+
data-todo-edit-row="true"
|
|
637
|
+
className={`flex flex-col gap-3 sm:flex-row ${isEditing ? '' : 'hidden'}`}
|
|
638
|
+
>
|
|
748
639
|
<input
|
|
749
640
|
className="input input-bordered w-full"
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
641
|
+
value={editingValue}
|
|
642
|
+
onInput={(event: any) => {
|
|
643
|
+
setEditingTitle((event.target as HTMLInputElement).value)
|
|
644
|
+
}}
|
|
645
|
+
onKeydown={(event: KeyboardEvent) => {
|
|
646
|
+
if (event.key === 'Enter') {
|
|
647
|
+
commitEditing((event.target as HTMLInputElement).value)
|
|
648
|
+
}
|
|
649
|
+
if (event.key === 'Escape') {
|
|
650
|
+
cancelEditing()
|
|
651
|
+
}
|
|
652
|
+
}}
|
|
753
653
|
/>
|
|
754
654
|
<div className="flex gap-2">
|
|
755
655
|
<button
|
|
756
656
|
className="btn btn-primary btn-sm"
|
|
757
|
-
|
|
758
|
-
|
|
657
|
+
onClick={(event: any) => {
|
|
658
|
+
const editRow = (event.currentTarget as HTMLElement).closest(
|
|
659
|
+
'[data-todo-edit-row="true"]',
|
|
660
|
+
) as HTMLElement | null
|
|
661
|
+
const input = editRow?.querySelector('input') as HTMLInputElement | null
|
|
662
|
+
commitEditing(input?.value ?? editingValue)
|
|
663
|
+
}}
|
|
759
664
|
type="button"
|
|
760
665
|
>
|
|
761
666
|
保存
|
|
762
667
|
</button>
|
|
763
668
|
<button
|
|
764
669
|
className="btn btn-ghost btn-sm"
|
|
765
|
-
|
|
766
|
-
data-todo-id={String(card.item.id)}
|
|
670
|
+
onClick={cancelEditing}
|
|
767
671
|
type="button"
|
|
768
672
|
>
|
|
769
673
|
取消
|
|
770
674
|
</button>
|
|
771
675
|
</div>
|
|
772
676
|
</div>
|
|
773
|
-
)}
|
|
774
677
|
|
|
775
|
-
|
|
776
|
-
|
|
678
|
+
<div className="flex flex-wrap gap-2">
|
|
679
|
+
{statusOptions.map((option) => (
|
|
680
|
+
<button
|
|
681
|
+
key={option.key}
|
|
682
|
+
className={`btn btn-xs ${
|
|
683
|
+
item.status === option.key
|
|
684
|
+
? 'btn-neutral'
|
|
685
|
+
: 'btn-ghost border border-base-300'
|
|
686
|
+
}`}
|
|
687
|
+
onClick={() => updateStatus(item.id, option.key)}
|
|
688
|
+
type="button"
|
|
689
|
+
>
|
|
690
|
+
{option.actionLabel}
|
|
691
|
+
</button>
|
|
692
|
+
))}
|
|
693
|
+
</div>
|
|
694
|
+
</div>
|
|
695
|
+
|
|
696
|
+
<div className="flex flex-wrap gap-2 lg:justify-end">
|
|
697
|
+
{!isEditing && (
|
|
777
698
|
<button
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
card.item.status === option.key
|
|
781
|
-
? 'btn-neutral'
|
|
782
|
-
: 'btn-ghost border border-base-300'
|
|
783
|
-
}`}
|
|
784
|
-
data-todo-action="status"
|
|
785
|
-
data-todo-id={String(card.item.id)}
|
|
786
|
-
data-todo-status={option.key}
|
|
699
|
+
className="btn btn-sm btn-outline"
|
|
700
|
+
onClick={() => startEditing(item)}
|
|
787
701
|
type="button"
|
|
788
702
|
>
|
|
789
|
-
|
|
703
|
+
改名
|
|
790
704
|
</button>
|
|
791
|
-
)
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
705
|
+
)}
|
|
706
|
+
<button
|
|
707
|
+
className="btn btn-sm btn-outline btn-secondary"
|
|
708
|
+
onClick={() => toggleArchived(item.id)}
|
|
709
|
+
type="button"
|
|
710
|
+
>
|
|
711
|
+
{item.archived ? '恢复' : '归档'}
|
|
712
|
+
</button>
|
|
797
713
|
<button
|
|
798
|
-
className="btn btn-sm btn-outline"
|
|
799
|
-
|
|
800
|
-
data-todo-id={String(card.item.id)}
|
|
714
|
+
className="btn btn-sm btn-outline btn-error"
|
|
715
|
+
onClick={() => removeTodo(item.id)}
|
|
801
716
|
type="button"
|
|
802
717
|
>
|
|
803
|
-
|
|
718
|
+
删除
|
|
804
719
|
</button>
|
|
805
|
-
|
|
806
|
-
<button
|
|
807
|
-
className="btn btn-sm btn-outline btn-secondary"
|
|
808
|
-
data-todo-action="toggle-archived"
|
|
809
|
-
data-todo-id={String(card.item.id)}
|
|
810
|
-
type="button"
|
|
811
|
-
>
|
|
812
|
-
{card.item.archived ? '恢复' : '归档'}
|
|
813
|
-
</button>
|
|
814
|
-
<button
|
|
815
|
-
className="btn btn-sm btn-outline btn-error"
|
|
816
|
-
data-todo-action="remove"
|
|
817
|
-
data-todo-id={String(card.item.id)}
|
|
818
|
-
type="button"
|
|
819
|
-
>
|
|
820
|
-
删除
|
|
821
|
-
</button>
|
|
720
|
+
</div>
|
|
822
721
|
</div>
|
|
823
722
|
</div>
|
|
824
|
-
</
|
|
825
|
-
|
|
826
|
-
)
|
|
723
|
+
</article>
|
|
724
|
+
)
|
|
725
|
+
})}
|
|
827
726
|
|
|
828
|
-
{!
|
|
727
|
+
{!visibleTodos.get().length && (
|
|
829
728
|
<div className="card border border-dashed border-base-300 bg-base-100 shadow-sm">
|
|
830
729
|
<div className="card-body items-center py-14 text-center">
|
|
831
730
|
<h2 className="text-xl font-semibold">当前筛选下没有任务</h2>
|