refactor(ui): standardize page layouts and component styling
Introduce structural CSS classes for page shells, headers, and surface cards Update primary theme color to red and neutral to zinc across the application
This commit is contained in:
@@ -234,30 +234,58 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer class="py-8">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<div class="mb-8 text-center">
|
||||
<h1 class="text-3xl font-extrabold tracking-tight text-highlighted sm:text-4xl">
|
||||
{{ bookingConfig.event.title }}
|
||||
</h1>
|
||||
</div>
|
||||
<UContainer class="page-shell-narrow">
|
||||
<div class="grid gap-8 xl:grid-cols-[minmax(0,1fr)_34rem] xl:items-start">
|
||||
<section class="space-y-6 xl:sticky xl:top-6">
|
||||
<div class="page-header">
|
||||
<UBadge :label="t('layout.brand')" color="primary" variant="soft" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
{{ bookingConfig.event.title }}
|
||||
</h1>
|
||||
<p class="page-description">
|
||||
{{ t('booking.seatGeneration', { count: seatCount, seatLabel: locale === 'zh' ? '座位' : `seat${seatCount === 1 ? '' : 's'}` }) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<UCard id="booking-form" class="border border-default bg-default" :ui="{
|
||||
header: 'space-y-4',
|
||||
body: 'space-y-6'
|
||||
}">
|
||||
<template #header>
|
||||
<div class="space-y-3">
|
||||
<div v-for="detail in eventDetails" :key="detail.label"
|
||||
class="flex items-center gap-3 text-sm text-default">
|
||||
<UIcon :name="detail.icon" class="size-4 text-muted" />
|
||||
<span>{{ detail.value }}</span>
|
||||
<div class="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
|
||||
<div
|
||||
v-for="detail in eventDetails"
|
||||
:key="detail.label"
|
||||
class="surface-card rounded-lg p-4"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<UIcon :name="detail.icon" class="size-5" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-xs font-semibold uppercase text-muted">
|
||||
{{ detail.label }}
|
||||
</p>
|
||||
<p class="mt-1 text-sm font-semibold leading-6 text-highlighted break-words">
|
||||
{{ detail.value }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<UCard
|
||||
id="booking-form"
|
||||
class="surface-card overflow-hidden rounded-lg"
|
||||
:ui="{ body: 'space-y-6 p-5 sm:p-6' }"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<p class="text-sm font-semibold text-primary">
|
||||
{{ t('booking.bookNow') }}
|
||||
</p>
|
||||
<h2 class="text-xl font-semibold text-highlighted">
|
||||
{{ t('booking.ticketCategory') }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<UForm :state="form" :validate="validateBooking" class="space-y-6" @submit="bookTicket">
|
||||
<div class="space-y-5">
|
||||
<div class="grid gap-5 sm:grid-cols-2">
|
||||
<UFormField name="name" :label="t('booking.name')" required>
|
||||
<UInput v-model="form.name" size="xl" class="w-full" :placeholder="t('booking.namePlaceholder')" />
|
||||
</UFormField>
|
||||
@@ -268,47 +296,68 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
|
||||
</div>
|
||||
|
||||
<UFormField :label="t('booking.bookingMode')" name="bookingMode">
|
||||
<URadioGroup v-model="form.bookingMode" orientation="horizontal" variant="card" indicator="hidden"
|
||||
:items="bookingModeOptions" :ui="{
|
||||
<URadioGroup
|
||||
v-model="form.bookingMode"
|
||||
orientation="horizontal"
|
||||
variant="card"
|
||||
indicator="hidden"
|
||||
:items="bookingModeOptions"
|
||||
:ui="{
|
||||
fieldset: 'grid grid-cols-2 gap-3',
|
||||
item: 'rounded-xl border border-default bg-default p-3 data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
|
||||
}" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField :label="quantityLabel" name="quantity">
|
||||
<UInputNumber v-model="form.quantity" size="xl" class="w-full" :min="1" :step="1" />
|
||||
<template #help>
|
||||
{{ t('booking.seatGeneration', { count: seatCount, seatLabel: locale === 'zh' ? '座位' : `seat${seatCount === 1 ? '' : 's'}` }) }}
|
||||
</template>
|
||||
</UFormField>
|
||||
|
||||
<UFormField :label="t('booking.ticketCategory')" name="ticketType">
|
||||
<URadioGroup v-model="form.ticketType" orientation="horizontal" variant="card" indicator="hidden"
|
||||
:items="ticketCatalogOptions" :ui="{
|
||||
fieldset: 'grid grid-cols-2 gap-3',
|
||||
item: 'rounded-xl border border-default bg-default p-3 data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
|
||||
}" />
|
||||
</UFormField>
|
||||
|
||||
<div class="rounded-xl border border-default bg-muted px-4 py-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="text-sm font-medium text-muted">{{ t('common.totalPrice') }}</span>
|
||||
<span class="text-2xl font-bold text-highlighted">{{ totalFormatted }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UFormField :label="t('booking.personInCharge')">
|
||||
<USelect
|
||||
v-model="selectedPersonInCharge"
|
||||
size="xl"
|
||||
class="w-full"
|
||||
:items="personInCharge"
|
||||
:disabled="!personInCharge.length"
|
||||
item: 'rounded-lg border border-default bg-default p-3 transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
|
||||
}"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<UButton id="getTicketBtn" type="submit" :label="t('booking.bookNow')" size="xl"
|
||||
class="w-full justify-center" :disabled="!selectedPersonInCharge || !selectedBookingMode || !selectedTicket" :loading="submittingBooking" />
|
||||
<div class="grid gap-5 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]">
|
||||
<UFormField :label="quantityLabel" name="quantity">
|
||||
<UInputNumber v-model="form.quantity" size="xl" class="w-full" :min="1" :step="1" />
|
||||
<template #help>
|
||||
{{ t('booking.seatGeneration', { count: seatCount, seatLabel: locale === 'zh' ? '座位' : `seat${seatCount === 1 ? '' : 's'}` }) }}
|
||||
</template>
|
||||
</UFormField>
|
||||
|
||||
<UFormField :label="t('booking.personInCharge')">
|
||||
<USelect
|
||||
v-model="selectedPersonInCharge"
|
||||
size="xl"
|
||||
class="w-full"
|
||||
:items="personInCharge"
|
||||
:disabled="!personInCharge.length"
|
||||
/>
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<UFormField :label="t('booking.ticketCategory')" name="ticketType">
|
||||
<URadioGroup
|
||||
v-model="form.ticketType"
|
||||
orientation="horizontal"
|
||||
variant="card"
|
||||
indicator="hidden"
|
||||
:items="ticketCatalogOptions"
|
||||
:ui="{
|
||||
fieldset: 'grid grid-cols-1 gap-3 sm:grid-cols-2',
|
||||
item: 'rounded-lg border border-default bg-default p-3 transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
|
||||
}"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<div class="surface-panel rounded-lg px-4 py-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="text-sm font-medium text-muted">{{ t('common.totalPrice') }}</span>
|
||||
<span class="text-2xl font-bold tabular-nums text-highlighted">{{ totalFormatted }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
id="getTicketBtn"
|
||||
type="submit"
|
||||
:label="t('booking.bookNow')"
|
||||
size="xl"
|
||||
class="w-full justify-center"
|
||||
:disabled="!selectedPersonInCharge || !selectedBookingMode || !selectedTicket"
|
||||
:loading="submittingBooking"
|
||||
/>
|
||||
</UForm>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user