feat: initialize project with event order form

This commit establishes the initial structure of the Nuxt application, centered around a new event order form.

Key changes include:
- Setting up the main app layout with a header, footer, and color mode support using @nuxt/ui.
- Creating a multi-part event order form on the index page.
- Introducing a `useEventOrder` composable to manage form state and validation with Zod.
- Adding modular form components under `app/components/eventOrder`.
- Including project configuration files for pnpm, VSCode, and global CSS.
This commit is contained in:
xiaomai
2025-10-16 15:22:29 +08:00
parent 9341e2ef0f
commit eb69f6c48e
15 changed files with 551 additions and 4 deletions

View File

@@ -0,0 +1,94 @@
import * as z from "zod";
import { createSharedComposable } from "@vueuse/core";
export const _useEventOrder = () => {
const sectionIndex = ref(0);
function formatLocalDate(date: Date): string {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
}
const EVENT_LOCATIONS = [
"永平区",
"周边城市Batu Pahat / Kluang",
"柔佛州境内",
"柔佛州境外",
] as const;
const eventLocationItems = ref<string[]>([...EVENT_LOCATIONS]);
const orderSchema = z.object({
contactName: z.string().min(1, "姓名不能为空"),
// 用户可以选择是否使用同一联系方式
isSameContact: z.boolean().default(true),
contactNumber: z.string().optional(),
eventName: z.string().min(1, "活动名称不能为空"),
eventDate: z
.string()
.refine((date) => {
return !isNaN(Date.parse(date));
}, "无效的日期")
.refine((date) => {
const eventDate = new Date(date);
const today = new Date();
// 仅按日期比较(忽略时间)
eventDate.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
// 活动日期必须是今天或未来的日期
return eventDate.getTime() >= today.getTime();
}, "活动日期必须是今天或未来的日期"),
eventLocation: z.enum(EVENT_LOCATIONS),
// 服务:竞标系统
biddingSystem: z.boolean().default(false),
biddingSystemProvideImage: z.boolean().default(false).optional(),
estimatedBidItemCount: z.string().optional(),
// 服务:背景设计
backgroundDesign: z.boolean().default(false),
backgroundType: z.enum(["static", "dynamic"]).optional(),
backgroundWidthOverride: z.number().optional(),
backgroundHeightOverride: z.number().optional(),
// 服务:流程 PPT 设计
flowBackgroundDesign: z.boolean().default(false),
backgroundSourceProvided: z.boolean().default(false), // 如果自己设计的,那么就没有这个额外收费
pptDesignQty: z
.number()
.min(1, "最少都要一张")
.max(20, "太多了我来不及做设计"),
// 服务:赞助商征信录设计
sponsorListDesign: z.boolean().default(false),
});
type OrderForm = z.infer<typeof orderSchema>;
const orderState = reactive<OrderForm>({
contactName: "",
isSameContact: true,
contactNumber: "",
eventName: "",
eventDate: formatLocalDate(new Date()),
eventLocation: EVENT_LOCATIONS[0],
biddingSystem: false,
biddingSystemProvideImage: false,
estimatedBidItemCount: "30-40",
backgroundDesign: false,
backgroundType: "static",
backgroundWidthOverride: 1920,
backgroundHeightOverride: 1080,
flowBackgroundDesign: false,
backgroundSourceProvided: false,
pptDesignQty: 1,
sponsorListDesign: false,
});
return {
sectionIndex,
eventLocationItems,
orderSchema,
orderState,
};
};
export const useEventOrder = createSharedComposable(_useEventOrder);