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:
94
app/composables/eventOrder.ts
Normal file
94
app/composables/eventOrder.ts
Normal 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);
|
||||
Reference in New Issue
Block a user