Files
kanban/components/TaskModal.vue
xiaomai 485d75820b feat(ui): overhaul interface with Nuxt UI
Integrate the Nuxt UI component library and completely revamp the application's user interface to improve usability, aesthetics, and maintainability.

- Replace all custom components and native browser dialogs (`alert`, `prompt`, `confirm`) with Nuxt UI components like `UCard`, `UButton`, `UModal`, and `UNotifications`.
- Refactor the main page by extracting the sidebar into dedicated panel components: `CategoryPanel`, `BoardSummaryPanel`, and `HistoryPanel`.
- Redesign all major components (Toolbar, Board, Stage, Task) for a cleaner layout and improved information hierarchy.
- Implement user-friendly modals for all creation, editing, and deletion flows, providing a more consistent user experience.
- Add toast notifications to provide immediate feedback for user actions.
2025-10-22 17:52:17 +08:00

212 lines
6.1 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<UModal v-model="open" :ui="{ width: 'max-w-2xl' }">
<UCard>
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-pencil-square-20-solid" class="h-5 w-5 text-sky-400" />
<span class="font-semibold">编辑任务</span>
</div>
</template>
<form class="space-y-5" @submit.prevent="save">
<UFormGroup label="标题" name="title">
<UInput v-model="form.title" placeholder="任务标题" autofocus />
</UFormGroup>
<div class="grid gap-4 sm:grid-cols-2">
<UFormGroup label="类别" name="category">
<USelectMenu
v-model="form.category"
:options="categoryOptions"
value-attribute="value"
option-attribute="label"
searchable
placeholder="选择类别"
/>
</UFormGroup>
<UFormGroup label="摘要" name="summary">
<UInput v-model="summary" placeholder="用于步骤的快速概览(可选)" />
</UFormGroup>
</div>
<UFormGroup label="详细描述" name="description">
<UTextarea
v-model="form.description"
placeholder="补充任务背景、验收标准等……"
:rows="6"
/>
</UFormGroup>
<div class="space-y-3">
<div class="flex items-center justify-between">
<span class="text-sm font-medium text-slate-200">步骤清单</span>
<UButton size="xs" color="primary" variant="soft" icon="i-heroicons-plus-20-solid" @click="addStep">
添加步骤
</UButton>
</div>
<div v-if="!form.steps.length" class="rounded border border-slate-800 bg-slate-900/70 px-3 py-4 text-sm text-slate-400">
暂无步骤添加步骤帮助团队追踪完成情况
</div>
<div v-else class="space-y-3">
<div
v-for="(step, index) in form.steps"
:key="step.id"
class="flex items-start gap-3 rounded border border-slate-800 bg-slate-900/80 p-3"
>
<UCheckbox v-model="step.done" class="mt-1.5" />
<div class="min-w-0 flex-1 space-y-2">
<UInput
v-model="step.details"
placeholder="步骤内容"
/>
<p class="text-[11px] text-slate-500">步骤 {{ index + 1 }}</p>
</div>
<UTooltip text="删除步骤">
<UButton
size="xs"
color="neutral"
variant="ghost"
icon="i-heroicons-trash-20-solid"
@click="removeStep(index)"
/>
</UTooltip>
</div>
</div>
</div>
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<UButton
color="rose"
variant="soft"
icon="i-heroicons-trash-20-solid"
@click.prevent="deleteOpen = true"
>
删除任务
</UButton>
<div class="flex items-center gap-2">
<UButton color="neutral" variant="ghost" @click="open = false">取消</UButton>
<UButton type="submit" color="primary" icon="i-heroicons-check-20-solid">保存</UButton>
</div>
</div>
</form>
</UCard>
</UModal>
<UModal v-model="deleteOpen">
<UCard>
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-exclamation-triangle-20-solid" class="h-5 w-5 text-amber-400" />
<span class="font-semibold">删除任务</span>
</div>
</template>
<p class="text-sm text-slate-300">
确定要删除该任务吗此操作不可撤销
</p>
<div class="mt-6 flex justify-end gap-2">
<UButton color="neutral" variant="ghost" @click="deleteOpen = false">取消</UButton>
<UButton color="rose" @click="removeTask">永久删除</UButton>
</div>
</UCard>
</UModal>
</template>
<script setup lang="ts">
import { useBoardStore } from '~/stores/board'
import { uuid } from '~/utils/uuid'
const props = defineProps<{ taskId: string }>()
const open = defineModel<boolean>('open', { default: false })
const store = useBoardStore()
const toast = useToast()
interface StepDraft {
id: string
details: string
done: boolean
}
const form = reactive({
title: '',
category: '' as string | '',
description: '',
steps: [] as StepDraft[]
})
const summary = ref('')
const categoryOptions = computed(() => [
{ label: '未分类', value: '' },
...store.board.categories.map((c) => ({ label: c.title, value: c.uuid }))
])
watch(
() => open.value,
(value) => {
if (value) hydrate()
}
)
watch(
() => props.taskId,
() => {
if (open.value) hydrate()
}
)
function hydrate() {
const task = store.taskById(props.taskId)
if (!task) return
form.title = task.title
form.category = (task.category as string) || ''
form.description = task.description || ''
summary.value = ''
form.steps = (task.steps || []).map((step) => ({
id: uuid(),
details: step.details,
done: step.done
}))
}
function addStep() {
form.steps.push({
id: uuid(),
details: summary.value ? summary.value : `步骤 ${form.steps.length + 1}`,
done: false
})
summary.value = ''
}
function removeStep(index: number) {
form.steps.splice(index, 1)
}
function save() {
if (!form.title.trim()) {
toast.add({ color: 'rose', title: '任务标题不能为空' })
return
}
store.editTask(props.taskId, {
title: form.title.trim(),
description: form.description.trim(),
category: form.category || null,
steps: form.steps.map((step) => ({
details: step.details.trim(),
done: step.done
}))
})
toast.add({ color: 'primary', title: '任务已保存' })
open.value = false
}
const deleteOpen = ref(false)
function removeTask() {
store.removeTask(props.taskId)
toast.add({ color: 'rose', title: '任务已删除' })
deleteOpen.value = false
open.value = false
}
</script>