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.
359 lines
12 KiB
Vue
359 lines
12 KiB
Vue
<script setup lang="ts">
|
||
import { ref, reactive, computed, defineComponent, h } from "vue";
|
||
import { Icon } from "@iconify/vue";
|
||
import { vMaska } from "maska/vue";
|
||
|
||
// Reka primitive parts we actually need
|
||
import {
|
||
Label,
|
||
CheckboxRoot,
|
||
CheckboxIndicator,
|
||
RadioGroupRoot,
|
||
RadioGroupItem,
|
||
RadioGroupIndicator,
|
||
} from "reka-ui";
|
||
|
||
/**
|
||
* Local lightweight FormField wrapper:
|
||
* - props: label, error, for
|
||
* - renders: <Label for="..."> + slot(default) + error paragraph
|
||
*/
|
||
const FormField = defineComponent({
|
||
name: "FormField",
|
||
props: {
|
||
label: { type: String, required: false },
|
||
error: { type: String, required: false },
|
||
for: { type: String, required: false },
|
||
},
|
||
setup(props, { slots }) {
|
||
return () =>
|
||
h(
|
||
"div",
|
||
{ class: "grid gap-2" },
|
||
[
|
||
props.label ? h(Label, { for: props.for }, () => props.label) : null,
|
||
slots.default ? slots.default() : null,
|
||
props.error
|
||
? h("p", { class: "text-sm text-red-600 mt-1" }, () => props.error)
|
||
: null,
|
||
].filter(Boolean)
|
||
);
|
||
},
|
||
});
|
||
|
||
// --- form state & helpers ---
|
||
const currentYear = new Date().getFullYear();
|
||
|
||
const form = reactive({
|
||
chineseName: "",
|
||
englishName: "",
|
||
ic: "",
|
||
email: "",
|
||
phone: "",
|
||
gradYear: null as number | null,
|
||
unknownGradYear: false,
|
||
educationLevel: "",
|
||
maritalStatus: "",
|
||
country: "",
|
||
address: "",
|
||
});
|
||
|
||
const errors = reactive<Record<string, string>>({});
|
||
|
||
const toUpperCaseEnglish = () => {
|
||
form.englishName = (form.englishName || "").toUpperCase();
|
||
};
|
||
|
||
const graduationBatch = computed(() => {
|
||
if (form.gradYear) {
|
||
if (form.educationLevel === "高中毕业") {
|
||
return form.gradYear - 1965;
|
||
} else if (form.educationLevel === "初中毕业") {
|
||
return form.gradYear - 1958;
|
||
}
|
||
}
|
||
return null;
|
||
});
|
||
|
||
const validate = () => {
|
||
errors.chineseName = !form.chineseName ? "请输入中文姓名" : "";
|
||
errors.englishName = !form.englishName ? "请输入英文姓名" : "";
|
||
errors.ic = /^\d{6}-\d{2}-\d{4}$/.test(form.ic)
|
||
? ""
|
||
: "格式应为 000000-00-0000";
|
||
errors.email =
|
||
!form.email && form.country !== "马来西亚"
|
||
? "国外居住必须填写电邮"
|
||
: form.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)
|
||
? "请输入有效的电邮地址"
|
||
: "";
|
||
errors.phone =
|
||
!/^01\d{1}-\d{7,8}$/.test(form.phone) &&
|
||
!/^\+\d{1,3}\s?\d+$/.test(form.phone)
|
||
? "请输入马来西亚号 (01x-xxxxxxx) 或带区号的号码"
|
||
: "";
|
||
errors.educationLevel = !form.educationLevel ? "请选择毕业层次" : "";
|
||
errors.gradYear =
|
||
!form.unknownGradYear && !form.gradYear ? "请输入毕业年份或勾选“不详”" : "";
|
||
errors.maritalStatus = !form.maritalStatus ? "请选择婚姻状态" : "";
|
||
errors.country = !form.country ? "请选择国家" : "";
|
||
errors.address = !form.address ? "请输入详细地址" : "";
|
||
|
||
return Object.values(errors).every((e) => !e);
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
if (validate()) {
|
||
// 如果你已在根组件挂载 Reka 的 ToastProvider + useToast,可替换下面 alert 的实现(见备注)
|
||
alert("提交成功!理事会将尽快联系您。");
|
||
} else {
|
||
alert("请完善表单信息");
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<UPage class="bg-primary">
|
||
<div
|
||
class="cursor-not-allowed fixed flex items-center justify-center min-h-screen min-w-screen bg-black opacity-50"
|
||
>
|
||
<p class="text-white text-2xl">此功能尚未开放,敬请期待。谢谢</p>
|
||
</div>
|
||
<UPageBody>
|
||
<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">
|
||
兹申请加入成为永平中学校友会会员,愿遵守会规及议决案,并填此表为据。<br />
|
||
入会费 <span class="font-bold text-secondary">RM60 / 年</span>。<br />
|
||
填写此表格之后,会有理事联系您协商入会费事宜。
|
||
</p>
|
||
|
||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||
<!-- 中文姓名 -->
|
||
<FormField
|
||
label="中文姓名"
|
||
:error="errors.chineseName"
|
||
for="chineseName"
|
||
>
|
||
<input
|
||
id="chineseName"
|
||
v-model="form.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
|
||
id="gradYear"
|
||
type="number"
|
||
v-model="form.gradYear"
|
||
:min="1957"
|
||
:max="currentYear"
|
||
:disabled="form.unknownGradYear"
|
||
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">
|
||
您是第
|
||
<span class="font-bold">{{ graduationBatch }}</span> 届毕业生
|
||
</span>
|
||
</div>
|
||
</FormField>
|
||
|
||
<!-- 婚姻状态 -->
|
||
<FormField
|
||
label="婚姻状态"
|
||
:error="errors.maritalStatus"
|
||
for="maritalStatus"
|
||
>
|
||
<div class="flex flex-col gap-2">
|
||
<RadioGroupRoot v-model="form.maritalStatus" name="maritalStatus">
|
||
<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>
|
||
</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>
|