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.
95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
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);
|