feat(items): implement initial item management system

This commit introduces the foundational structure and core features for the item management application.

Key features implemented:
- **Item Listing:** A main page displaying all items in a searchable UTable.
- **Item Creation:** An "Add Item" modal with a UForm for input and validation using Zod.
- **State Management:** A `useItemsStore` composable built with `@vueuse/core`'s `useLocalStorage` to persist item data
in the browser.
- **UI Framework:** Integrated Nuxt UI for components like tables, modals, forms, and the overall layout.
- **Application Shell:** Established a default layout with a header, navigation menu, and placeholders for Export and
History pages.
- **Project Setup:** Configured pnpm workspace, Tailwind CSS, and VSCode editor settings for an improved development
experience.
This commit is contained in:
xiaomai
2025-10-14 09:36:59 +08:00
parent c0ba7ac0ff
commit e05c41eb07
13 changed files with 493 additions and 4 deletions

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

195
app/pages/index.vue Normal file
View File

@@ -0,0 +1,195 @@
<template>
<div>
<UPageHero
title="智能物品管理系统"
description="专业的物品数据库管理工具支持图片记录、CSV导出和历史价格追踪"
headline="New release"
/>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- 统计 -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div
v-for="stat in statistics"
:key="stat.label"
class="bg-white rounded-xl p-6 shadow-sm border border-gray-100"
>
<div class="flex items-center">
<div
:class="`p-3 rounded-lg flex bg-${stat.color}-100 items-center`"
>
<Icon
class="w-6 h-6"
:name="stat.icon"
:style="`color: ${stat.color}`"
/>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600">{{ stat.label }}</p>
<p class="text-2xl font-bold text-gray-900">{{ stat.value }}</p>
</div>
</div>
</div>
</div>
<!-- 列表 -->
<div class="flex flex-col flex-1 w-full">
<div
class="flex justify-between items-center gap-2 overflow-x-auto px-4 py-3.5 border-b border-accented"
>
<div>
<UInput
v-model="globalFilter"
class="max-w-sm"
placeholder="Search..."
icon="lucide:search"
/>
</div>
<div>
<ItemAddModal />
</div>
</div>
<UTable
v-model:global-filter="globalFilter"
:data="items"
:columns="itemColumns"
>
<template #name-cell="{ row }">
<div class="flex items-center gap-3">
<UAvatar
:src="row.original.imageUrl"
size="lg"
:alt="row.original.name"
/>
<div>
<p class="font-medium text-highlighted">
{{ row.original.name }}
</p>
</div>
</div>
</template>
<template #tags-cell="{ row }">
<div class="flex gap-2">
<UBadge v-for="tag in row.original.tags" variant="subtle">{{
tag
}}</UBadge>
</div>
</template>
<template #actions-cell="{ row }">
<div class="text-right">
<UDropdownMenu
:items="getRowItems(row)"
:content="{ align: 'end' }"
aria-label="Actions dropdown"
>
<UButton
icon="i-lucide-ellipsis-vertical"
color="neutral"
variant="ghost"
class="ml-auto"
aria-label="Actions dropdown"
/>
</UDropdownMenu>
</div>
</template>
</UTable>
</div>
</main>
</div>
</template>
<script lang="ts" setup>
import type { TableColumn, DropdownMenuItem } from "@nuxt/ui";
import type { Row } from "@tanstack/vue-table";
import { useClipboard } from "@vueuse/core";
const UBadge = resolveComponent("UBadge");
const UAvatar = resolveComponent("UAvatar");
const toast = useToast();
const { copy } = useClipboard();
const { items, stats } = useItemsStore();
// 统计
const statistics = [
{
icon: "lucide:box",
label: "总物品数",
value: stats.totalItems,
color: "blue",
},
{
icon: "lucide:package-plus",
label: "本月新增",
value: stats.addedThisMonth,
color: "green",
},
{
icon: "lucide:history",
label: "历史记录",
value: computed(() => 0),
color: "orange",
},
{
icon: "lucide:image",
label: "图片总数",
value: computed(() => 0),
color: "purple",
},
];
const globalFilter = ref<string>("");
const itemColumns: TableColumn<Item>[] = [
{
accessorKey: "id",
header: "#",
cell: ({ row }) => `#${row.getValue("id")}`,
},
{
accessorKey: "name",
header: "标品名称",
},
{
accessorKey: "tags",
header: "标签",
},
{
id: "actions",
header: "", // 不需要标题
},
];
function getRowItems(row: Row<Item>): DropdownMenuItem[] {
return [
{ type: "label", label: "操作" },
{
label: "编辑",
color: "primary",
icon: "lucide:pencil-line",
onSelect() {
copy(row.original.id.toString());
toast.add({
title: "Payment ID copied to clipboard!",
color: "success",
icon: "i-lucide-circle-check",
});
},
},
{ type: "separator" },
{ label: "删除", color: "error", icon: "lucide:trash-2" },
] as const;
}
// Define Shortcut
defineShortcuts({
meta_i: () => {
console.log("Shortcut works!");
},
});
</script>
<style></style>