This commit replaces the original vanilla JavaScript implementation with a modern frontend stack. The entire application has been rewritten to improve maintainability, developer experience, and leverage modern web technologies. Key changes include: - Replaced plain HTML, CSS, and JS with a Nuxt 4 project structure. - Migrated state management from a global state object to a Pinia store for better organization and reactivity. - Rebuilt the UI with Vue 3 components and styled with TailwindCSS. - Changed the primary persistence mechanism from manual file I/O to automatic LocalStorage saving, while retaining import/export functionality. - All core features, including drag-and-drop, task management, and the import/merge diffing logic, have been ported to the new architecture.
69 lines
3.4 KiB
Vue
69 lines
3.4 KiB
Vue
<template>
|
|
<div class="fixed inset-0 bg-black/60 flex items-center justify-center p-4">
|
|
<div class="w-[720px] max-w-[95vw] max-h-[90vh] rounded-lg border border-border bg-panel flex flex-col">
|
|
<div class="flex items-center justify-between px-3 py-2 border-b border-border">
|
|
<h2 class="font-semibold">编辑任务</h2>
|
|
<button class="px-2 py-1 rounded bg-slate-800/60 border border-border hover:bg-slate-800" @click="$emit('close')">✕</button>
|
|
</div>
|
|
<div class="p-3 overflow-auto space-y-3">
|
|
<div class="grid grid-cols-[90px_1fr] gap-3 items-start">
|
|
<label class="pt-1 text-sm text-slate-400">标题</label>
|
|
<input v-model="title" class="px-2 py-1 rounded bg-slate-900 border border-border" />
|
|
</div>
|
|
<div class="grid grid-cols-[90px_1fr] gap-3 items-start">
|
|
<label class="pt-1 text-sm text-slate-400">类别</label>
|
|
<select v-model="category" class="px-2 py-1 rounded bg-slate-900 border border-border">
|
|
<option value="">未分类</option>
|
|
<option v-for="c in store.board.categories" :key="c.uuid" :value="c.uuid">{{ c.title }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="grid grid-cols-[90px_1fr] gap-3 items-start">
|
|
<label class="pt-1 text-sm text-slate-400">描述</label>
|
|
<textarea v-model="desc" rows="6" class="px-2 py-1 rounded bg-slate-900 border border-border"></textarea>
|
|
</div>
|
|
<div class="grid grid-cols-[90px_1fr] gap-3 items-start">
|
|
<label class="pt-1 text-sm text-slate-400">步骤</label>
|
|
<div class="space-y-2">
|
|
<div v-for="(s, i) in steps" :key="i" class="grid grid-cols-[24px_1fr_24px] items-center gap-2">
|
|
<input type="checkbox" v-model="s.done" />
|
|
<input v-model="s.details" class="px-2 py-1 rounded bg-slate-900 border border-border" />
|
|
<button class="text-sm" @click="steps.splice(i,1)">🗑</button>
|
|
</div>
|
|
<button class="px-2 py-1 rounded bg-slate-800/60 border border-border hover:bg-slate-800" @click="steps.push({details:'新步骤', done:false})">添加步骤</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-2 px-3 py-2 border-t border-border">
|
|
<button class="px-3 py-1 rounded bg-red-500/90 text-white hover:brightness-110" @click="onDelete">删除</button>
|
|
<div class="flex-1" />
|
|
<button class="px-3 py-1 rounded bg-accent text-slate-900 font-medium hover:brightness-110" @click="onSave">保存</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useBoardStore } from '~/stores/board'
|
|
|
|
const store = useBoardStore()
|
|
const props = defineProps<{ taskId: string }>()
|
|
const t = computed(() => store.taskById(props.taskId))
|
|
const title = ref(t.value?.title || '')
|
|
const desc = ref(t.value?.description || '')
|
|
const category = ref<string | ''>((t.value?.category as string) || '')
|
|
const steps = ref(JSON.parse(JSON.stringify(t.value?.steps || [])))
|
|
|
|
function onSave() {
|
|
store.editTask(props.taskId, { title: title.value.trim() || t.value?.title, description: desc.value, category: category.value || null, steps: steps.value })
|
|
emit('close')
|
|
}
|
|
function onDelete() {
|
|
if (!confirm('删除该任务?此操作不可撤销')) return
|
|
store.removeTask(props.taskId)
|
|
emit('close')
|
|
}
|
|
const emit = defineEmits(['close'])
|
|
</script>
|
|
|