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.
This commit is contained in:
xiaomai
2025-10-22 17:52:17 +08:00
parent 2384e42933
commit 485d75820b
19 changed files with 1823 additions and 318 deletions

View File

@@ -1,55 +1,84 @@
<template>
<div v-if="open" class="fixed inset-0 bg-black/60 flex items-center justify-center p-4">
<div class="w-[1000px] 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="close"></button>
</div>
<div class="p-3 overflow-auto space-y-3">
<div class="flex items-center gap-3">
<div>
<label class="text-sm text-slate-400 mr-2">策略</label>
<select v-model="policy" class="px-2 py-1 rounded bg-slate-900 border border-border">
<option value="prefer-import">冲突优先导入文件</option>
<option value="prefer-current">冲突优先当前看板</option>
</select>
<UModal v-model="open" :ui="{ width: 'max-w-3xl' }">
<UCard>
<template #header>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-arrow-down-tray-20-solid" class="h-5 w-5 text-sky-400" />
<span class="font-semibold">导入/合并预览</span>
</div>
<label class="text-sm flex items-center gap-2">
<input type="checkbox" v-model="removeMissing" /> 同步删除在导入文件中不存在的任务/阶段
</label>
<UBadge color="neutral" variant="soft">来源文件{{ name || '未命名' }}</UBadge>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="rounded border border-border bg-slate-900 p-3">
<div class="text-sm text-slate-400">任务新增</div>
<div class="text-2xl font-bold">{{ diff?.tasks?.added?.length || 0 }}</div>
</div>
<div class="rounded border border-border bg-slate-900 p-3">
<div class="text-sm text-slate-400">任务删除</div>
<div class="text-2xl font-bold">{{ diff?.tasks?.removed?.length || 0 }}</div>
</div>
<div class="rounded border border-border bg-slate-900 p-3">
<div class="text-sm text-slate-400">任务修改</div>
<div class="text-2xl font-bold">{{ diff?.tasks?.modified?.length || 0 }}</div>
</div>
</template>
<div class="space-y-6">
<UAlert
color="primary"
variant="soft"
title="合并前请确认策略"
description="导入文件将与当前看板数据对比。请选择冲突策略及是否删除缺失项目。"
/>
<div class="flex flex-wrap items-center gap-4">
<UFormGroup label="冲突策略" name="policy" class="w-64">
<USelectMenu
v-model="policy"
:options="policies"
value-attribute="value"
option-attribute="label"
/>
</UFormGroup>
<UCheckbox v-model="removeMissing">
同步删除在导入文件中不存在的任务/阶段
</UCheckbox>
</div>
<div class="rounded border border-border bg-slate-900 p-3 text-xs whitespace-pre-wrap">
<div>文件{{ name }}</div>
<div>Tasks: +{{ diff?.tasks?.added?.length || 0 }} -{{ diff?.tasks?.removed?.length || 0 }} ~{{ diff?.tasks?.modified?.length || 0 }}</div>
<div>Stages: +{{ diff?.stages?.added?.length || 0 }} -{{ diff?.stages?.removed?.length || 0 }} ~{{ diff?.stages?.modified?.length || 0 }}</div>
<div>Layout changed: {{ diff?.layout?.changed ? 'Yes' : 'No' }}</div>
<div class="grid gap-3 md:grid-cols-3">
<UCard variant="soft" class="border border-emerald-500/20 bg-emerald-500/5 text-emerald-200">
<p class="text-xs uppercase tracking-wide opacity-70">任务新增</p>
<p class="text-3xl font-semibold">{{ diff?.tasks?.added?.length || 0 }}</p>
</UCard>
<UCard variant="soft" class="border border-rose-500/20 bg-rose-500/5 text-rose-200">
<p class="text-xs uppercase tracking-wide opacity-70">任务删除</p>
<p class="text-3xl font-semibold">{{ diff?.tasks?.removed?.length || 0 }}</p>
</UCard>
<UCard variant="soft" class="border border-amber-500/20 bg-amber-500/5 text-amber-200">
<p class="text-xs uppercase tracking-wide opacity-70">任务修改</p>
<p class="text-3xl font-semibold">{{ diff?.tasks?.modified?.length || 0 }}</p>
</UCard>
</div>
<UCard variant="soft" class="border border-slate-800 bg-slate-900/70 text-xs text-slate-300">
<div class="space-y-1">
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-document-duplicate-20-solid" class="h-4 w-4 text-slate-500" />
<span>Tasks: +{{ diff?.tasks?.added?.length || 0 }} / -{{ diff?.tasks?.removed?.length || 0 }} / ~{{ diff?.tasks?.modified?.length || 0 }}</span>
</div>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-queue-list-20-solid" class="h-4 w-4 text-slate-500" />
<span>Stages: +{{ diff?.stages?.added?.length || 0 }} / -{{ diff?.stages?.removed?.length || 0 }} / ~{{ diff?.stages?.modified?.length || 0 }}</span>
</div>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-view-columns-20-solid" class="h-4 w-4 text-slate-500" />
<span>布局是否变化{{ diff?.layout?.changed ? '是' : '否' }}</span>
</div>
</div>
</UCard>
<div class="flex justify-end gap-2">
<UButton color="neutral" variant="ghost" @click="open = false">取消</UButton>
<UButton color="primary" icon="i-heroicons-arrow-up-tray-20-solid" @click="apply">应用合并</UButton>
</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-accent text-slate-900 font-medium hover:brightness-110" @click="apply">应用合并</button>
</div>
</div>
</div>
</UCard>
</UModal>
</template>
<script setup lang="ts">
import { useBoardStore } from '~/stores/board'
const store = useBoardStore()
const open = defineModel<boolean>('open', { required: true })
const imported = defineModel<any>('imported', { required: true })
const diff = defineModel<any>('diff', { required: true })
@@ -57,10 +86,14 @@ const name = defineModel<string>('name', { required: true })
const policy = ref<'prefer-import' | 'prefer-current'>('prefer-import')
const removeMissing = ref(false)
function close(){ open.value = false }
function apply(){
const policies = [
{ label: '冲突优先:导入文件', value: 'prefer-import' },
{ label: '冲突优先:当前看板', value: 'prefer-current' }
]
function apply() {
store.applyMerge(imported.value, policy.value, removeMissing.value)
close()
open.value = false
}
</script>