create-rue 0.1.1 → 0.1.3
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 +138 -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.3 | 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.3";
|
|
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
|
-
{
|
|
426
|
+
const nextTodo: TodoItem = {
|
|
456
427
|
id: nextId.value++,
|
|
457
428
|
title,
|
|
458
429
|
status: 'todo',
|
|
459
430
|
archived: false,
|
|
460
431
|
createdAt: new Date().toISOString(),
|
|
461
432
|
createdOrder: nextCreatedOrder.value++,
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
])
|
|
465
|
-
|
|
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,135 @@ 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 className=
|
|
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 className={`inline-block h-2.5 w-2.5 rounded-full ${meta.dotClass}`}></span>
|
|
612
|
+
<span className={meta.badgeClass}>{meta.label}</span>
|
|
613
|
+
{item.archived && (
|
|
614
|
+
<span className="badge badge-secondary badge-outline">已归档</span>
|
|
615
|
+
)}
|
|
616
|
+
<span className="text-xs text-base-content/50">
|
|
617
|
+
创建于 {formatTodoCreatedAt(item.createdAt)}
|
|
618
|
+
</span>
|
|
619
|
+
</div>
|
|
733
620
|
|
|
734
|
-
{!card.isEditing && (
|
|
735
621
|
<h2
|
|
736
622
|
className={`text-xl font-semibold ${
|
|
737
|
-
|
|
738
|
-
? '
|
|
739
|
-
: '
|
|
623
|
+
isEditing
|
|
624
|
+
? 'hidden'
|
|
625
|
+
: item.status === 'done'
|
|
626
|
+
? 'text-base-content/50 line-through'
|
|
627
|
+
: 'text-base-content'
|
|
740
628
|
}`}
|
|
741
629
|
>
|
|
742
|
-
{
|
|
630
|
+
{item.title}
|
|
743
631
|
</h2>
|
|
744
|
-
)}
|
|
745
632
|
|
|
746
|
-
|
|
747
|
-
|
|
633
|
+
<div
|
|
634
|
+
data-todo-edit-row="true"
|
|
635
|
+
className={`flex flex-col gap-3 sm:flex-row ${isEditing ? '' : 'hidden'}`}
|
|
636
|
+
>
|
|
748
637
|
<input
|
|
749
638
|
className="input input-bordered w-full"
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
639
|
+
value={editingValue}
|
|
640
|
+
onInput={(event: any) => {
|
|
641
|
+
setEditingTitle((event.target as HTMLInputElement).value)
|
|
642
|
+
}}
|
|
643
|
+
onKeydown={(event: KeyboardEvent) => {
|
|
644
|
+
if (event.key === 'Enter') {
|
|
645
|
+
commitEditing((event.target as HTMLInputElement).value)
|
|
646
|
+
}
|
|
647
|
+
if (event.key === 'Escape') {
|
|
648
|
+
cancelEditing()
|
|
649
|
+
}
|
|
650
|
+
}}
|
|
753
651
|
/>
|
|
754
652
|
<div className="flex gap-2">
|
|
755
653
|
<button
|
|
756
654
|
className="btn btn-primary btn-sm"
|
|
757
|
-
|
|
758
|
-
|
|
655
|
+
onClick={(event: any) => {
|
|
656
|
+
const editRow = (event.currentTarget as HTMLElement).closest(
|
|
657
|
+
'[data-todo-edit-row="true"]',
|
|
658
|
+
) as HTMLElement | null
|
|
659
|
+
const input = editRow?.querySelector('input') as HTMLInputElement | null
|
|
660
|
+
commitEditing(input?.value ?? editingValue)
|
|
661
|
+
}}
|
|
759
662
|
type="button"
|
|
760
663
|
>
|
|
761
664
|
保存
|
|
762
665
|
</button>
|
|
666
|
+
<button className="btn btn-ghost btn-sm" onClick={cancelEditing} type="button">
|
|
667
|
+
取消
|
|
668
|
+
</button>
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
|
|
672
|
+
<div className="flex flex-wrap gap-2">
|
|
673
|
+
{statusOptions.map((option) => (
|
|
763
674
|
<button
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
675
|
+
key={option.key}
|
|
676
|
+
className={`btn btn-xs ${
|
|
677
|
+
item.status === option.key
|
|
678
|
+
? 'btn-neutral'
|
|
679
|
+
: 'btn-ghost border border-base-300'
|
|
680
|
+
}`}
|
|
681
|
+
onClick={() => updateStatus(item.id, option.key)}
|
|
767
682
|
type="button"
|
|
768
683
|
>
|
|
769
|
-
|
|
684
|
+
{option.actionLabel}
|
|
770
685
|
</button>
|
|
771
|
-
|
|
686
|
+
))}
|
|
772
687
|
</div>
|
|
773
|
-
)}
|
|
774
|
-
|
|
775
|
-
<div className="flex flex-wrap gap-2">
|
|
776
|
-
{statusOptions.map((option) => (
|
|
777
|
-
<button
|
|
778
|
-
key={option.key}
|
|
779
|
-
className={`btn btn-xs ${
|
|
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}
|
|
787
|
-
type="button"
|
|
788
|
-
>
|
|
789
|
-
{option.actionLabel}
|
|
790
|
-
</button>
|
|
791
|
-
))}
|
|
792
688
|
</div>
|
|
793
|
-
</div>
|
|
794
689
|
|
|
795
|
-
|
|
796
|
-
|
|
690
|
+
<div className="flex flex-wrap gap-2 lg:justify-end">
|
|
691
|
+
{!isEditing && (
|
|
692
|
+
<button className="btn btn-sm btn-outline" onClick={() => startEditing(item)} type="button">
|
|
693
|
+
改名
|
|
694
|
+
</button>
|
|
695
|
+
)}
|
|
696
|
+
<button
|
|
697
|
+
className="btn btn-sm btn-outline btn-secondary"
|
|
698
|
+
onClick={() => toggleArchived(item.id)}
|
|
699
|
+
type="button"
|
|
700
|
+
>
|
|
701
|
+
{item.archived ? '恢复' : '归档'}
|
|
702
|
+
</button>
|
|
797
703
|
<button
|
|
798
|
-
className="btn btn-sm btn-outline"
|
|
799
|
-
|
|
800
|
-
data-todo-id={String(card.item.id)}
|
|
704
|
+
className="btn btn-sm btn-outline btn-error"
|
|
705
|
+
onClick={() => removeTodo(item.id)}
|
|
801
706
|
type="button"
|
|
802
707
|
>
|
|
803
|
-
|
|
708
|
+
删除
|
|
804
709
|
</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>
|
|
710
|
+
</div>
|
|
822
711
|
</div>
|
|
823
712
|
</div>
|
|
824
|
-
</
|
|
825
|
-
|
|
826
|
-
)
|
|
713
|
+
</article>
|
|
714
|
+
)
|
|
715
|
+
})}
|
|
827
716
|
|
|
828
|
-
{!
|
|
717
|
+
{!visibleTodos.get().length && (
|
|
829
718
|
<div className="card border border-dashed border-base-300 bg-base-100 shadow-sm">
|
|
830
719
|
<div className="card-body items-center py-14 text-center">
|
|
831
720
|
<h2 className="text-xl font-semibold">当前筛选下没有任务</h2>
|