diff --git a/app/components/item/EditModal.vue b/app/components/item/EditModal.vue index 2aa15a5..af121e0 100644 --- a/app/components/item/EditModal.vue +++ b/app/components/item/EditModal.vue @@ -4,11 +4,7 @@ :title="isEditMode ? '编辑物品' : '新增物品'" :description="isEditMode ? '修改现有物品信息' : '新增一件新的物品'" > - + + + utils.allBrands.value.push(brand)" + /> + + (false); const currentId = ref(null); const toast = useToast(); -const { stats, isNameAvailable, addItem, editItem, items } = useItemsStore(); +const { utils, isNameAvailable, addItem, editItem, items } = useItemsStore(); // 🧩 基础校验(动态 schema) const baseSchema = z.object({ + brand: z.string().optional(), name: z.string().min(1, "名称不能为空"), imageUrl: z.string().nullable().optional(), description: z.string().nullable().optional(), @@ -102,10 +110,8 @@ const formSchema = computed(() => baseSchema.refine( (data) => isEditMode.value - ? !items.value.some( - (i) => i.name === data.name && i.id !== currentId.value - ) - : isNameAvailable(data.name), + ? isNameAvailable(currentId.value, data.name, data.brand) + : isNameAvailable(null, data.name, data.brand), "该名称已被占用,请更换一个" ) ); @@ -113,6 +119,7 @@ const formSchema = computed(() => type ItemSchema = z.output; const itemState = reactive({ + brand: undefined, name: "", imageUrl: null, description: null, @@ -125,6 +132,7 @@ const openModal = (item?: Item) => { // 编辑模式 isEditMode.value = true; currentId.value = item.id; + itemState.brand = item.brand; itemState.name = item.name; itemState.imageUrl = item.imageUrl ?? null; itemState.description = item.description ?? null; @@ -132,6 +140,7 @@ const openModal = (item?: Item) => { } else { // 新增模式 isEditMode.value = false; + itemState.brand = undefined; currentId.value = null; itemState.name = ""; itemState.imageUrl = null; @@ -146,6 +155,7 @@ const onSubmit = async (event: FormSubmitEvent) => { // ✅ 编辑模式 const updatedItem: Item = { id: currentId.value, + brand: event.data.brand, name: event.data.name, imageUrl: event.data.imageUrl, description: event.data.description, @@ -165,13 +175,12 @@ const onSubmit = async (event: FormSubmitEvent) => { } else { // ✅ 新增模式 const newItem: Item = { - id: stats.latestId.value, + id: 0, // 由添加函数处理 + brand: event.data.brand, name: event.data.name, imageUrl: event.data.imageUrl, description: event.data.description, tags: event.data.tags?.split(",").map((tag) => tag.trim()), - createdAt: new Date(), - updatedAt: new Date(), }; addItem(newItem); diff --git a/app/composables/items.ts b/app/composables/items.ts index 9abd26a..f2fa5e9 100644 --- a/app/composables/items.ts +++ b/app/composables/items.ts @@ -2,12 +2,13 @@ import { useLocalStorage, createSharedComposable } from "@vueuse/core"; export type Item = { id: number; + brand?: string; name: string; imageUrl?: string | null; description?: string | null; tags?: string[] | null; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; }; const _useItems = () => { @@ -56,21 +57,64 @@ const _useItems = () => { const thisYear = now.getFullYear(); return (items.value ?? []).filter( (item) => - item.createdAt.getMonth() === thisMonth && - item.createdAt.getFullYear() === thisYear + item.createdAt?.getMonth() === thisMonth && + item.createdAt?.getFullYear() === thisYear ).length; }), - latestId: computed(() => { - const arr = items.value ?? []; - if (arr.length === 0) return 1; - return Math.max(...arr.map((i) => i.id)) + 1; - }), }; - const isNameAvailable = (name: string) => - !(items.value ?? []).some((item) => item.name === name); + const utils = { + // Get all brandings as array of strings + allBrands: ref( + (() => { + const brands = new Set(); + (items.value ?? []).forEach((item) => { + if (item.brand) { + brands.add(item.brand); + } + }); + return Array.from(brands).sort((a, b) => a.localeCompare(b)); + })() + ), + // Get all tags as array of strings + getAllTags: () => { + const tags = new Set(); + (items.value ?? []).forEach((item) => { + if (item.tags) { + item.tags.forEach((tag) => tags.add(tag)); + } + }); + return Array.from(tags).sort((a, b) => a.localeCompare(b)); + }, + }; + + const latestId = computed(() => { + const arr = items.value ?? []; + if (arr.length === 0) return 1; + return Math.max(...arr.map((i) => i.id)) + 1; + }); + + const isNameAvailable = (id: number | null, name: string, brand?: string) => { + const list = items.value ?? []; + + return !list.some((item) => { + // 跳过自己(编辑时) + if (id && item.id === id) return false; + + // 有品牌时需同时匹配品牌 + if (brand) { + return item.name === name && item.brand === brand; + } + + // 没品牌时只比较名字 + return item.name === name; + }); + }; const addItem = (item: Item) => { + item.id = latestId.value; + item.createdAt = new Date(); + item.updatedAt = new Date(); items.value = [...(items.value ?? []), item]; console.debug("[addItem] new length:", (items.value ?? []).length); }; @@ -98,7 +142,15 @@ const _useItems = () => { ); }; - return { items, stats, isNameAvailable, addItem, editItem, removeItem }; + return { + items, + stats, + utils, + isNameAvailable, + addItem, + editItem, + removeItem, + }; }; export const useItemsStore = createSharedComposable(_useItems); diff --git a/app/pages/index.vue b/app/pages/index.vue index 3543d3c..f07dc6a 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -69,7 +69,7 @@ /> - {{ row.original.name }} + {{ row.original.brand }} {{ row.original.name }}
- {{ row.original.name }} + {{ row.original.brand }} {{ row.original.name }}