Files
bid-setup.tootaio.com/app/components/item/EditModal.vue
xiaomai 802c4460a7 feat(export): implement export page for bidding items
Introduces a new export page for creating and managing bidding lists. Key features include: selecting items from a
master list, adding them to a bidding list, editing start price/remarks, batch price updates, and drag-and-drop
reordering. The final list can be previewed and exported as a CSV. This change adds the `useBiddingItems` composable and
the `sortablejs` dependency. Also refactors `imageUrl` to be a non-nullable string for type consistency.
2025-10-20 17:33:48 +08:00

202 lines
5.5 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>
<UModal
v-model:open="open"
:title="isEditMode ? '编辑物品' : '新增物品'"
:description="isEditMode ? '修改现有物品信息' : '新增一件新的物品'"
>
<UButton label="新增" icon="lucide:plus" @click="openModal()" />
<template #body>
<UForm
ref="itemForm"
:schema="formSchema"
:state="itemState"
@submit="onSubmit"
class="space-y-4"
>
<UFormField label="品牌名称" name="brand">
<USelectMenu
v-model="itemState.brand"
:items="utils.allBrands.value"
placeholder="请输入物品品牌名(可选)"
class="w-full"
create-item
@create="(brand: string) => utils.allBrands.value.push(brand)"
/>
</UFormField>
<UFormField label="标品名称" required name="name">
<UInput
v-model="itemState.name"
placeholder="请输入物品名称"
class="w-full"
/>
</UFormField>
<UFormField label="图片 URL">
<UInput
v-model="itemState.imageUrl"
placeholder="https://img.tootaio.com/i/2025/09/26/gxuf17.jpeg"
class="w-full"
/>
<template #hint>
<div class="text-xs text-gray-500">请粘贴您的图床图片链接</div>
</template>
</UFormField>
<UFormField label="描述">
<UTextarea
v-model="itemState.description"
placeholder="请输入物品描述(可选)"
class="w-full"
/>
</UFormField>
<UFormField label="标签">
<UInput
v-model="itemState.tags"
placeholder="Johnnie Walker,Whiskey"
class="w-full"
/>
<template #hint>
<div class="text-xs text-gray-500">用英文逗号隔开</div>
</template>
</UFormField>
</UForm>
</template>
<template #footer>
<div class="flex justify-end w-full gap-2">
<UButton
label="取消"
color="neutral"
variant="subtle"
@click="open = false"
/>
<UButton
:label="isEditMode ? '保存修改' : '创建'"
color="primary"
variant="solid"
@click="itemForm.submit()"
/>
</div>
</template>
</UModal>
</template>
<script lang="ts" setup>
import type { FormSubmitEvent } from "@nuxt/ui";
import * as z from "zod";
const itemForm = ref();
const open = ref<boolean>(false);
const isEditMode = ref<boolean>(false);
const currentId = ref<number | null>(null);
const toast = useToast();
const { utils, isNameAvailable, addItem, editItem, items } = useItemsStore();
// 🧩 基础校验(动态 schema
const baseSchema = z.object({
brand: z.string().optional(),
name: z.string().min(1, "名称不能为空"),
imageUrl: z.string().optional(),
description: z.string().nullable().optional(),
tags: z.string().nullable().optional(),
});
// ⚙️ 根据模式动态生成 Schema编辑模式允许当前名称
const formSchema = computed(() =>
baseSchema.refine(
(data) =>
isEditMode.value
? isNameAvailable(currentId.value, data.name, data.brand)
: isNameAvailable(null, data.name, data.brand),
"该名称已被占用,请更换一个"
)
);
type ItemSchema = z.output<typeof baseSchema>;
const itemState = reactive<ItemSchema>({
brand: undefined,
name: "",
imageUrl: "",
description: null,
tags: null,
});
/** 🔧 打开 Modal新增 / 编辑) */
const openModal = (item?: Item) => {
if (item) {
// 编辑模式
isEditMode.value = true;
currentId.value = item.id;
itemState.brand = item.brand;
itemState.name = item.name;
itemState.imageUrl = item.imageUrl;
itemState.description = item.description ?? null;
itemState.tags = item.tags?.join(", ") ?? null;
} else {
// 新增模式
isEditMode.value = false;
itemState.brand = undefined;
currentId.value = null;
itemState.name = "";
itemState.imageUrl = "";
itemState.description = null;
itemState.tags = null;
}
open.value = true;
};
const onSubmit = async (event: FormSubmitEvent<ItemSchema>) => {
if (isEditMode.value && currentId.value !== null) {
// ✅ 编辑模式
const updatedItem: Item = {
id: currentId.value,
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:
items.value.find((i) => i.id === currentId.value)?.createdAt ??
new Date(),
updatedAt: new Date(),
};
editItem(updatedItem);
toast.add({
title: "已保存修改",
description: `${event.data.name} 的信息已更新`,
color: "success",
});
} else {
// ✅ 新增模式
const newItem: Item = {
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()),
};
addItem(newItem);
toast.add({
title: "成功",
description: `${event.data.name} 已添加到数据库中`,
color: "success",
});
}
open.value = false;
};
// expose method so parent can call modal.openModal(item)
defineExpose({ openModal });
</script>
<style></style>