Files
bid-setup.tootaio.com/app/pages/index.vue
xiaomai 1d2ce32ab9 feat(item): implement item editing functionality
This commit introduces the capability to edit existing items. The `ItemEditModal` has been refactored to support both creation and editing modes.

- The modal UI (title, buttons) and submission logic now adapt based on whether an item is being added or edited.
- A new `editItem` function is added to the `useItemsStore` to handle the update logic.
- Form validation for the item name is enhanced to correctly check for uniqueness during edits.
- The table's row action menu now opens the modal in edit mode, pre-populating the form.
- The `latestId` generation logic is improved for robustness.
- Additionally, this commit adds row selection checkboxes to the items table.
2025-10-14 13:40:49 +08:00

244 lines
6.1 KiB
Vue
Raw 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>
<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>
<ItemEditModal ref="itemEditModal" />
</div>
</div>
<UTable
v-model:global-filter="globalFilter"
:data="items"
:columns="itemColumns"
>
<template #select-cell="{ row }">
<UCheckbox
:model-value="row.getIsSelected()"
@update:model-value="(value: boolean | 'indeterminate') => row.toggleSelected(!!value)"
aria-label="Select row"
/>
</template>
<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>
<ItemDeleteModal
v-model:open="deleteModal.open"
:item="deleteModal.selectedItem"
/>
</main>
</div>
</template>
<script lang="ts" setup>
import type { TableColumn, DropdownMenuItem } from "@nuxt/ui";
import type { Row } from "@tanstack/vue-table";
const UCheckbox = resolveComponent("UCheckbox");
const UBadge = resolveComponent("UBadge");
const UAvatar = resolveComponent("UAvatar");
const toast = useToast();
const { items, stats } = useItemsStore();
const itemEditModal = ref<any>(null);
const deleteModal = reactive<{
open: boolean;
selectedItem: Item | null;
}>({
open: false,
selectedItem: null,
});
const globalFilter = ref<string>("");
// 统计
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 itemColumns: TableColumn<Item>[] = [
{
id: "select",
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? "indeterminate"
: table.getIsAllPageRowsSelected(),
"onUpdate:modelValue": (value: boolean | "indeterminate") =>
table.toggleAllPageRowsSelected(!!value),
ariaLabel: "Select all",
}),
},
{
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() {
itemEditModal.value?.openModal(row.original);
toast.add({
title: `编辑 ${row.original.name}`,
description: "已打开编辑模态框",
color: "warning",
icon: "lucide:pencil-line",
});
},
},
{ type: "separator" },
{
label: "删除",
color: "error",
icon: "lucide:trash-2",
onSelect() {
deleteModal.selectedItem = row.original;
deleteModal.open = true; // 👈 打开 Modal
toast.add({
title: `Deleting ${row.original.name}...`,
color: "error",
icon: "lucide:trash-2",
});
},
},
] as const;
}
// Define Shortcut
defineShortcuts({
meta_i: () => {
console.log("Shortcut works!");
},
});
</script>
<style></style>