Files
kanban/components/TaskModal.vue
xiaomai 2384e42933 refactor(app): rewrite application with Nuxt 4, Pinia, and TailwindCSS
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.
2025-10-22 17:08:31 +08:00

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>