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:
@@ -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,18 +105,21 @@ 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">
|
||||||
|
<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>
|
<p class="text-white text-2xl">此功能尚未开放,敬请期待。谢谢</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-h-screen bg-primary py-10">
|
<UPageBody>
|
||||||
<div class="max-w-3xl mx-auto p-8 bg-white rounded-2xl shadow-lg">
|
<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 class="text-3xl font-bold mb-6 text-center text-secondary">
|
||||||
永平中学校友会入会申请表
|
永平中学校友会入会申请表
|
||||||
@@ -127,7 +133,11 @@ const handleSubmit = () => {
|
|||||||
|
|
||||||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||||||
<!-- 中文姓名 -->
|
<!-- 中文姓名 -->
|
||||||
<FormField label="中文姓名" :error="errors.chineseName" for="chineseName">
|
<FormField
|
||||||
|
label="中文姓名"
|
||||||
|
:error="errors.chineseName"
|
||||||
|
for="chineseName"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
id="chineseName"
|
id="chineseName"
|
||||||
v-model="form.chineseName"
|
v-model="form.chineseName"
|
||||||
@@ -137,7 +147,11 @@ const handleSubmit = () => {
|
|||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- 英文姓名 -->
|
<!-- 英文姓名 -->
|
||||||
<FormField label="英文姓名" :error="errors.englishName" for="englishName">
|
<FormField
|
||||||
|
label="英文姓名"
|
||||||
|
:error="errors.englishName"
|
||||||
|
for="englishName"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
id="englishName"
|
id="englishName"
|
||||||
v-model="form.englishName"
|
v-model="form.englishName"
|
||||||
@@ -179,35 +193,50 @@ const handleSubmit = () => {
|
|||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- 毕业层次 (使用 Reka Radio primitives) -->
|
<!-- 毕业层次 (使用 Reka Radio primitives) -->
|
||||||
<FormField label="毕业层次" :error="errors.educationLevel" for="educationLevel">
|
<FormField
|
||||||
|
label="毕业层次"
|
||||||
|
:error="errors.educationLevel"
|
||||||
|
for="educationLevel"
|
||||||
|
>
|
||||||
<RadioGroupRoot
|
<RadioGroupRoot
|
||||||
v-model="form.educationLevel"
|
v-model="form.educationLevel"
|
||||||
class="flex flex-col gap-2"
|
class="flex flex-col gap-2"
|
||||||
name="educationLevel"
|
name="educationLevel"
|
||||||
>
|
>
|
||||||
<RadioGroupItem value="初中毕业" class="flex items-center gap-3">
|
<RadioGroupItem value="初中毕业" class="flex items-center gap-3">
|
||||||
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
|
<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">
|
<RadioGroupItem value="高中毕业" class="flex items-center gap-3">
|
||||||
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
|
<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">
|
<RadioGroupItem
|
||||||
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
|
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">
|
<RadioGroupItem value="不确定" class="flex items-center gap-3">
|
||||||
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
|
<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>
|
||||||
@@ -228,38 +257,57 @@ const handleSubmit = () => {
|
|||||||
class="w-32 border rounded px-3 py-2"
|
class="w-32 border rounded px-3 py-2"
|
||||||
/>
|
/>
|
||||||
<label class="flex items-center gap-2 select-none">
|
<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">
|
<CheckboxRoot
|
||||||
<CheckboxIndicator class="flex items-center justify-center w-full h-full">
|
v-model="form.unknownGradYear"
|
||||||
<Icon icon="radix-icons:check" class="h-4 w-4 text-secondary" />
|
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>
|
</CheckboxIndicator>
|
||||||
</CheckboxRoot>
|
</CheckboxRoot>
|
||||||
<span>毕业年份不详</span>
|
<span>毕业年份不详</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="text-sm text-gray-500" v-if="graduationBatch">
|
<span class="text-sm text-gray-500" v-if="graduationBatch">
|
||||||
您是第 <span class="font-bold">{{ graduationBatch }}</span> 届毕业生
|
您是第
|
||||||
|
<span class="font-bold">{{ graduationBatch }}</span> 届毕业生
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<!-- 婚姻状态 -->
|
<!-- 婚姻状态 -->
|
||||||
<FormField label="婚姻状态" :error="errors.maritalStatus" for="maritalStatus">
|
<FormField
|
||||||
|
label="婚姻状态"
|
||||||
|
:error="errors.maritalStatus"
|
||||||
|
for="maritalStatus"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<RadioGroupRoot v-model="form.maritalStatus" name="maritalStatus">
|
<RadioGroupRoot v-model="form.maritalStatus" name="maritalStatus">
|
||||||
<RadioGroupItem value="未婚" class="flex items-center gap-3">
|
<RadioGroupItem value="未婚" class="flex items-center gap-3">
|
||||||
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
|
<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">
|
<RadioGroupItem value="已婚" class="flex items-center gap-3">
|
||||||
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
|
<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">
|
<RadioGroupItem value="其他" class="flex items-center gap-3">
|
||||||
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
|
<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>
|
||||||
@@ -270,7 +318,11 @@ const handleSubmit = () => {
|
|||||||
|
|
||||||
<!-- 国家(原生 select,简单且稳定) -->
|
<!-- 国家(原生 select,简单且稳定) -->
|
||||||
<FormField label="国家" :error="errors.country" for="country">
|
<FormField label="国家" :error="errors.country" for="country">
|
||||||
<select id="country" v-model="form.country" class="w-full border rounded px-3 py-2">
|
<select
|
||||||
|
id="country"
|
||||||
|
v-model="form.country"
|
||||||
|
class="w-full border rounded px-3 py-2"
|
||||||
|
>
|
||||||
<option value="" disabled>请选择国家</option>
|
<option value="" disabled>请选择国家</option>
|
||||||
<option>马来西亚</option>
|
<option>马来西亚</option>
|
||||||
<option>新加坡</option>
|
<option>新加坡</option>
|
||||||
@@ -301,5 +353,6 @@ const handleSubmit = () => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</UPageBody>
|
||||||
|
</UPage>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user