refactor(join-us): adopt UPage layout components

The 'Join Us' page layout has been refactored to use the `<UPage>` and `<UPageBody>` components from Nuxt UI. This change ensures a consistent page structure with the rest of the application. Additionally,
quote styles in the script section have been standardized to double quotes.
This commit is contained in:
xiaomai
2025-10-26 23:44:04 +08:00
parent 567c9ef9c9
commit 0a46c3e591

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, defineComponent, h } from 'vue'; import { ref, reactive, computed, defineComponent, h } from "vue";
import { Icon } from '@iconify/vue'; import { Icon } from "@iconify/vue";
import { vMaska } from "maska/vue"; import { vMaska } from "maska/vue";
// Reka primitive parts we actually need // Reka primitive parts we actually need
@@ -11,7 +11,7 @@ import {
RadioGroupRoot, RadioGroupRoot,
RadioGroupItem, RadioGroupItem,
RadioGroupIndicator, RadioGroupIndicator,
} from 'reka-ui'; } from "reka-ui";
/** /**
* Local lightweight FormField wrapper: * Local lightweight FormField wrapper:
@@ -19,7 +19,7 @@ import {
* - renders: <Label for="..."> + slot(default) + error paragraph * - renders: <Label for="..."> + slot(default) + error paragraph
*/ */
const FormField = defineComponent({ const FormField = defineComponent({
name: 'FormField', name: "FormField",
props: { props: {
label: { type: String, required: false }, label: { type: String, required: false },
error: { type: String, required: false }, error: { type: String, required: false },
@@ -28,13 +28,13 @@ const FormField = defineComponent({
setup(props, { slots }) { setup(props, { slots }) {
return () => return () =>
h( h(
'div', "div",
{ class: 'grid gap-2' }, { class: "grid gap-2" },
[ [
props.label ? h(Label, { for: props.for }, () => props.label) : null, props.label ? h(Label, { for: props.for }, () => props.label) : null,
slots.default ? slots.default() : null, slots.default ? slots.default() : null,
props.error props.error
? h('p', { class: 'text-sm text-red-600 mt-1' }, () => props.error) ? h("p", { class: "text-sm text-red-600 mt-1" }, () => props.error)
: null, : null,
].filter(Boolean) ].filter(Boolean)
); );
@@ -45,30 +45,30 @@ const FormField = defineComponent({
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const form = reactive({ const form = reactive({
chineseName: '', chineseName: "",
englishName: '', englishName: "",
ic: '', ic: "",
email: '', email: "",
phone: '', phone: "",
gradYear: null as number | null, gradYear: null as number | null,
unknownGradYear: false, unknownGradYear: false,
educationLevel: '', educationLevel: "",
maritalStatus: '', maritalStatus: "",
country: '', country: "",
address: '', address: "",
}); });
const errors = reactive<Record<string, string>>({}); const errors = reactive<Record<string, string>>({});
const toUpperCaseEnglish = () => { const toUpperCaseEnglish = () => {
form.englishName = (form.englishName || '').toUpperCase(); form.englishName = (form.englishName || "").toUpperCase();
}; };
const graduationBatch = computed(() => { const graduationBatch = computed(() => {
if (form.gradYear) { if (form.gradYear) {
if (form.educationLevel === '高中毕业') { if (form.educationLevel === "高中毕业") {
return form.gradYear - 1965; return form.gradYear - 1965;
} else if (form.educationLevel === '初中毕业') { } else if (form.educationLevel === "初中毕业") {
return form.gradYear - 1958; return form.gradYear - 1958;
} }
} }
@@ -76,25 +76,28 @@ const graduationBatch = computed(() => {
}); });
const validate = () => { const validate = () => {
errors.chineseName = !form.chineseName ? '请输入中文姓名' : ''; errors.chineseName = !form.chineseName ? "请输入中文姓名" : "";
errors.englishName = !form.englishName ? '请输入英文姓名' : ''; errors.englishName = !form.englishName ? "请输入英文姓名" : "";
errors.ic = /^\d{6}-\d{2}-\d{4}$/.test(form.ic) ? '' : '格式应为 000000-00-0000'; errors.ic = /^\d{6}-\d{2}-\d{4}$/.test(form.ic)
? ""
: "格式应为 000000-00-0000";
errors.email = errors.email =
(!form.email && form.country !== '马来西亚') !form.email && form.country !== "马来西亚"
? '国外居住必须填写电邮' ? "国外居住必须填写电邮"
: form.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email) : form.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)
? '请输入有效的电邮地址' ? "请输入有效的电邮地址"
: ''; : "";
errors.phone = errors.phone =
!/^01\d{1}-\d{7,8}$/.test(form.phone) && !/^\+\d{1,3}\s?\d+$/.test(form.phone) !/^01\d{1}-\d{7,8}$/.test(form.phone) &&
? '请输入马来西亚号 (01x-xxxxxxx) 或带区号的号码' !/^\+\d{1,3}\s?\d+$/.test(form.phone)
: ''; ? "请输入马来西亚号 (01x-xxxxxxx) 或带区号的号码"
errors.educationLevel = !form.educationLevel ? '请选择毕业层次' : ''; : "";
errors.educationLevel = !form.educationLevel ? "请选择毕业层次" : "";
errors.gradYear = errors.gradYear =
!form.unknownGradYear && !form.gradYear ? '请输入毕业年份或勾选“不详”' : ''; !form.unknownGradYear && !form.gradYear ? "请输入毕业年份或勾选“不详”" : "";
errors.maritalStatus = !form.maritalStatus ? '请选择婚姻状态' : ''; errors.maritalStatus = !form.maritalStatus ? "请选择婚姻状态" : "";
errors.country = !form.country ? '请选择国家' : ''; errors.country = !form.country ? "请选择国家" : "";
errors.address = !form.address ? '请输入详细地址' : ''; errors.address = !form.address ? "请输入详细地址" : "";
return Object.values(errors).every((e) => !e); return Object.values(errors).every((e) => !e);
}; };
@@ -102,204 +105,254 @@ const validate = () => {
const handleSubmit = () => { const handleSubmit = () => {
if (validate()) { if (validate()) {
// 如果你已在根组件挂载 Reka 的 ToastProvider + useToast可替换下面 alert 的实现(见备注) // 如果你已在根组件挂载 Reka 的 ToastProvider + useToast可替换下面 alert 的实现(见备注)
alert('提交成功!理事会将尽快联系您。'); alert("提交成功!理事会将尽快联系您。");
} else { } else {
alert('请完善表单信息'); alert("请完善表单信息");
} }
}; };
</script> </script>
<template> <template>
<div class="cursor-not-allowed fixed flex items-center justify-center min-h-screen min-w-screen bg-black opacity-50"> <UPage class="bg-primary">
<p class="text-white text-2xl">此功能尚未开放敬请期待谢谢</p> <div
</div> class="cursor-not-allowed fixed flex items-center justify-center min-h-screen min-w-screen bg-black opacity-50"
<div class="min-h-screen bg-primary py-10"> >
<div class="max-w-3xl mx-auto p-8 bg-white rounded-2xl shadow-lg"> <p class="text-white text-2xl">此功能尚未开放敬请期待谢谢</p>
<h1 class="text-3xl font-bold mb-6 text-center text-secondary"> </div>
永平中学校友会入会申请表 <UPageBody>
</h1> <div class="max-w-3xl mx-auto p-8 bg-white rounded-2xl shadow-lg">
<h1 class="text-3xl font-bold mb-6 text-center text-secondary">
永平中学校友会入会申请表
</h1>
<p class="text-sm text-gray-600 my-6 text-center leading-relaxed"> <p class="text-sm text-gray-600 my-6 text-center leading-relaxed">
兹申请加入成为永平中学校友会会员愿遵守会规及议决案并填此表为据<br /> 兹申请加入成为永平中学校友会会员愿遵守会规及议决案并填此表为据<br />
入会费 <span class="font-bold text-secondary">RM60 / </span><br /> 入会费 <span class="font-bold text-secondary">RM60 / </span><br />
填写此表格之后会有理事联系您协商入会费事宜 填写此表格之后会有理事联系您协商入会费事宜
</p> </p>
<form @submit.prevent="handleSubmit" class="space-y-6"> <form @submit.prevent="handleSubmit" class="space-y-6">
<!-- 中文姓名 --> <!-- 中文姓名 -->
<FormField label="中文姓名" :error="errors.chineseName" for="chineseName"> <FormField
<input label="中文姓名"
id="chineseName" :error="errors.chineseName"
v-model="form.chineseName" for="chineseName"
class="w-full border rounded px-3 py-2"
placeholder="请输入中文姓名"
/>
</FormField>
<!-- 英文姓名 -->
<FormField label="英文姓名" :error="errors.englishName" for="englishName">
<input
id="englishName"
v-model="form.englishName"
@input="toUpperCaseEnglish"
class="w-full border rounded px-3 py-2"
placeholder="请输入英文姓名"
/>
</FormField>
<!-- IC -->
<FormField label="IC" :error="errors.ic" for="ic">
<input
id="ic"
v-model="form.ic"
class="w-full border rounded px-3 py-2"
placeholder="000000-00-0000"
v-maska="'######-##-####'"
/>
</FormField>
<!-- 电邮 -->
<FormField label="电邮" :error="errors.email" for="email">
<input
id="email"
v-model="form.email"
class="w-full border rounded px-3 py-2"
placeholder="选填 / 国外必填"
/>
</FormField>
<!-- 电话 -->
<FormField label="电话" :error="errors.phone" for="phone">
<input
id="phone"
v-model="form.phone"
class="w-full border rounded px-3 py-2"
placeholder="请输入电话WhatsApp 号码为佳)"
/>
</FormField>
<!-- 毕业层次 (使用 Reka Radio primitives) -->
<FormField label="毕业层次" :error="errors.educationLevel" for="educationLevel">
<RadioGroupRoot
v-model="form.educationLevel"
class="flex flex-col gap-2"
name="educationLevel"
> >
<RadioGroupItem value="初中毕业" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>初中毕业</span>
</RadioGroupItem>
<RadioGroupItem value="高中毕业" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>高中毕业</span>
</RadioGroupItem>
<RadioGroupItem value="辍学/转学肄业" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>辍学/转学肄业</span>
</RadioGroupItem>
<RadioGroupItem value="不确定" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>不确定</span>
</RadioGroupItem>
</RadioGroupRoot>
</FormField>
<!-- 毕业年份 -->
<FormField label="毕业年份" :error="errors.gradYear" for="gradYear">
<div class="flex items-center gap-3">
<input <input
id="gradYear" id="chineseName"
type="number" v-model="form.chineseName"
v-model="form.gradYear" class="w-full border rounded px-3 py-2"
:min="1957" placeholder="请输入中文姓名"
:max="currentYear"
:disabled="form.unknownGradYear"
class="w-32 border rounded px-3 py-2"
/> />
<label class="flex items-center gap-2 select-none"> </FormField>
<CheckboxRoot v-model="form.unknownGradYear" class="w-5 h-5 rounded border flex items-center justify-center">
<CheckboxIndicator class="flex items-center justify-center w-full h-full">
<Icon icon="radix-icons:check" class="h-4 w-4 text-secondary" />
</CheckboxIndicator>
</CheckboxRoot>
<span>毕业年份不详</span>
</label>
<span class="text-sm text-gray-500" v-if="graduationBatch"> <!-- 英文姓名 -->
您是第 <span class="font-bold">{{ graduationBatch }}</span> 届毕业生 <FormField
</span> label="英文姓名"
</div> :error="errors.englishName"
</FormField> for="englishName"
>
<input
id="englishName"
v-model="form.englishName"
@input="toUpperCaseEnglish"
class="w-full border rounded px-3 py-2"
placeholder="请输入英文姓名"
/>
</FormField>
<!-- 婚姻状态 --> <!-- IC -->
<FormField label="婚姻状态" :error="errors.maritalStatus" for="maritalStatus"> <FormField label="IC" :error="errors.ic" for="ic">
<div class="flex flex-col gap-2"> <input
<RadioGroupRoot v-model="form.maritalStatus" name="maritalStatus"> id="ic"
<RadioGroupItem value="未婚" class="flex items-center gap-3"> v-model="form.ic"
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center"> class="w-full border rounded px-3 py-2"
placeholder="000000-00-0000"
v-maska="'######-##-####'"
/>
</FormField>
<!-- 电邮 -->
<FormField label="电邮" :error="errors.email" for="email">
<input
id="email"
v-model="form.email"
class="w-full border rounded px-3 py-2"
placeholder="选填 / 国外必填"
/>
</FormField>
<!-- 电话 -->
<FormField label="电话" :error="errors.phone" for="phone">
<input
id="phone"
v-model="form.phone"
class="w-full border rounded px-3 py-2"
placeholder="请输入电话WhatsApp 号码为佳)"
/>
</FormField>
<!-- 毕业层次 (使用 Reka Radio primitives) -->
<FormField
label="毕业层次"
:error="errors.educationLevel"
for="educationLevel"
>
<RadioGroupRoot
v-model="form.educationLevel"
class="flex flex-col gap-2"
name="educationLevel"
>
<RadioGroupItem value="初中毕业" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" /> <span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator> </RadioGroupIndicator>
<span>未婚</span> <span>初中毕业</span>
</RadioGroupItem> </RadioGroupItem>
<RadioGroupItem value="已婚" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center"> <RadioGroupItem value="高中毕业" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" /> <span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator> </RadioGroupIndicator>
<span>已婚</span> <span>高中毕业</span>
</RadioGroupItem> </RadioGroupItem>
<RadioGroupItem value="其他" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center"> <RadioGroupItem
value="辍学/转学肄业"
class="flex items-center gap-3"
>
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" /> <span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator> </RadioGroupIndicator>
<span>其他</span> <span>辍学/转学肄业</span>
</RadioGroupItem>
<RadioGroupItem value="不确定" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>不确定</span>
</RadioGroupItem> </RadioGroupItem>
</RadioGroupRoot> </RadioGroupRoot>
</div> </FormField>
</FormField>
<!-- 国家原生 select简单且稳定 --> <!-- 毕业年份 -->
<FormField label="国家" :error="errors.country" for="country"> <FormField label="毕业年份" :error="errors.gradYear" for="gradYear">
<select id="country" v-model="form.country" class="w-full border rounded px-3 py-2"> <div class="flex items-center gap-3">
<option value="" disabled>请选择国家</option> <input
<option>马来西亚</option> id="gradYear"
<option>新加坡</option> type="number"
<option>中国</option> v-model="form.gradYear"
<option>美国</option> :min="1957"
<option>其他</option> :max="currentYear"
</select> :disabled="form.unknownGradYear"
</FormField> class="w-32 border rounded px-3 py-2"
/>
<label class="flex items-center gap-2 select-none">
<CheckboxRoot
v-model="form.unknownGradYear"
class="w-5 h-5 rounded border flex items-center justify-center"
>
<CheckboxIndicator
class="flex items-center justify-center w-full h-full"
>
<Icon
icon="radix-icons:check"
class="h-4 w-4 text-secondary"
/>
</CheckboxIndicator>
</CheckboxRoot>
<span>毕业年份不详</span>
</label>
<!-- 详细地址 --> <span class="text-sm text-gray-500" v-if="graduationBatch">
<FormField label="详细地址" :error="errors.address" for="address"> 您是第
<textarea <span class="font-bold">{{ graduationBatch }}</span> 届毕业生
id="address" </span>
v-model="form.address" </div>
class="w-full border rounded px-3 py-2" </FormField>
placeholder="请输入现居详细地址"
rows="4"
/>
</FormField>
<div class="text-center mt-8"> <!-- 婚姻状态 -->
<button <FormField
type="submit" label="婚姻状态"
class="bg-secondary text-white font-bold px-8 py-2 rounded-xl shadow hover:scale-105 transition" :error="errors.maritalStatus"
for="maritalStatus"
> >
提交申请 <div class="flex flex-col gap-2">
</button> <RadioGroupRoot v-model="form.maritalStatus" name="maritalStatus">
</div> <RadioGroupItem value="未婚" class="flex items-center gap-3">
</form> <RadioGroupIndicator
</div> class="w-4 h-4 rounded-full border flex items-center justify-center"
</div> >
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>未婚</span>
</RadioGroupItem>
<RadioGroupItem value="已婚" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>已婚</span>
</RadioGroupItem>
<RadioGroupItem value="其他" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>其他</span>
</RadioGroupItem>
</RadioGroupRoot>
</div>
</FormField>
<!-- 国家原生 select简单且稳定 -->
<FormField label="国家" :error="errors.country" for="country">
<select
id="country"
v-model="form.country"
class="w-full border rounded px-3 py-2"
>
<option value="" disabled>请选择国家</option>
<option>马来西亚</option>
<option>新加坡</option>
<option>中国</option>
<option>美国</option>
<option>其他</option>
</select>
</FormField>
<!-- 详细地址 -->
<FormField label="详细地址" :error="errors.address" for="address">
<textarea
id="address"
v-model="form.address"
class="w-full border rounded px-3 py-2"
placeholder="请输入现居详细地址"
rows="4"
/>
</FormField>
<div class="text-center mt-8">
<button
type="submit"
class="bg-secondary text-white font-bold px-8 py-2 rounded-xl shadow hover:scale-105 transition"
>
提交申请
</button>
</div>
</form>
</div>
</UPageBody>
</UPage>
</template> </template>