|
@@ -0,0 +1,336 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { Notify, Toast } from '@/platform';
|
|
|
+
|
|
|
+import {
|
|
|
+ type FieldKey,
|
|
|
+ getCaptchaMethod,
|
|
|
+ processMethod,
|
|
|
+ registerAccountMethod,
|
|
|
+ registerFieldsMethod,
|
|
|
+ searchAccountMethod,
|
|
|
+} from '@/request/api';
|
|
|
+
|
|
|
+import { useVisitor } from '@/stores';
|
|
|
+
|
|
|
+import { useCaptcha, useForm, useRequest } from 'alova/client';
|
|
|
+
|
|
|
+import type { FieldRule, FormInstance, NumberKeyboardProps, PasswordInputProps } from 'vant';
|
|
|
+import { RadioGroup as vanRadioGroup } from 'vant';
|
|
|
+
|
|
|
+
|
|
|
+interface Field {
|
|
|
+ control: {
|
|
|
+ label: string; placeholder?: string;
|
|
|
+ type?: string; min?: number; max?: number; minlength?: number; maxlength?: number;
|
|
|
+ clearable?: boolean; border?: boolean;
|
|
|
+ };
|
|
|
+ component?: |
|
|
|
+ { name: 'radio', options: { label: string; value: string; }[] } |
|
|
|
+ { name: 'code', props?: Partial<PasswordInputProps> };
|
|
|
+ keyboard?: { show: boolean; } & Partial<NumberKeyboardProps>;
|
|
|
+ suffix?: string;
|
|
|
+ rules?: FieldRule | FieldRule[];
|
|
|
+}
|
|
|
+
|
|
|
+const Fields: Record<FieldKey, Field> = {
|
|
|
+ height: {
|
|
|
+ control: {
|
|
|
+ label: '身高', placeholder: '请输入身高',
|
|
|
+ type: 'number', min: 1, max: 300, clearable: true,
|
|
|
+ },
|
|
|
+ suffix: 'cm',
|
|
|
+ },
|
|
|
+ weight: {
|
|
|
+ control: {
|
|
|
+ label: '体重', placeholder: '请输入体重',
|
|
|
+ type: 'number', min: 1, max: 300, clearable: true,
|
|
|
+ },
|
|
|
+ suffix: 'kg',
|
|
|
+ },
|
|
|
+ sex: {
|
|
|
+ control: { label: '性别', border: false },
|
|
|
+ component: {
|
|
|
+ name: 'radio' as const,
|
|
|
+ options: [
|
|
|
+ { label: '男', value: '0' },
|
|
|
+ { label: '女', value: '1' },
|
|
|
+ { label: '未知', value: '2' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ isEasyAllergy: {
|
|
|
+ control: { label: '容易过敏', border: false },
|
|
|
+ component: {
|
|
|
+ name: 'radio' as const,
|
|
|
+ options: [
|
|
|
+ { label: '是', value: 'Y' },
|
|
|
+ { label: '否', value: 'N' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ name: {
|
|
|
+ control: {
|
|
|
+ label: '姓名', placeholder: '请输入姓名',
|
|
|
+ type: 'text', maxlength: 10, clearable: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ cardno: {
|
|
|
+ control: {
|
|
|
+ label: '身份证号', placeholder: '请输入身份证号',
|
|
|
+ type: 'text', maxlength: 18, minlength: 18, clearable: true,
|
|
|
+ },
|
|
|
+ keyboard: { show: false, title: '身份证号', extraKey: 'X', closeButtonText: '完成' },
|
|
|
+ rules: [
|
|
|
+ { required: true, message: '请输入身份证号' },
|
|
|
+ {
|
|
|
+ validator: (value: string) => value && value.length === 18,
|
|
|
+ message: '请输入正确的身份证',
|
|
|
+ trigger: 'onBlur',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ phone: {
|
|
|
+ control: {
|
|
|
+ label: '手机号码', placeholder: '请输入手机号码',
|
|
|
+ type: 'tel', maxlength: 11, minlength: 11, clearable: true,
|
|
|
+ },
|
|
|
+ keyboard: { show: false, title: '手机号码', closeButtonText: '完成' },
|
|
|
+ rules: [
|
|
|
+ { required: true, message: '请输入手机号码' },
|
|
|
+ {
|
|
|
+ validator: (value: string) => value && value.length === 11,
|
|
|
+ message: '请输入正确的手机号码',
|
|
|
+ trigger: 'onBlur',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ code: {
|
|
|
+ control: {
|
|
|
+ label: '验证码', placeholder: '请输入验证码',
|
|
|
+ type: 'digit', maxlength: 6, minlength: 6, clearable: true,
|
|
|
+ border: false,
|
|
|
+ },
|
|
|
+ component: {
|
|
|
+ name: 'code' as const,
|
|
|
+ props: { mask: false },
|
|
|
+ },
|
|
|
+ keyboard: { show: false, title: '验证码', closeButtonText: '完成' },
|
|
|
+ rules: [
|
|
|
+ { required: true, message: '请输入验证码' },
|
|
|
+ {
|
|
|
+ validator: (value: string) => value && value.length === 6,
|
|
|
+ message: '请输入验证码',
|
|
|
+ trigger: [ 'onChange', 'onBlur' ],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+const fields = ref<( Field & { name: FieldKey } )[]>([]);
|
|
|
+const { loading } = useRequest(registerFieldsMethod).onSuccess(({ data }) => {
|
|
|
+ fields.value = data.map(name => {return { ...Fields[ name ], name };});
|
|
|
+});
|
|
|
+
|
|
|
+const Visitor = useVisitor();
|
|
|
+const formRef = useTemplateRef<FormInstance>('register-form');
|
|
|
+const { form: modelRef, loading: submitting, send: submit } = useForm(data => registerAccountMethod(data), {
|
|
|
+ initialForm: { code: '', sex: '2' } as Record<string, any>,
|
|
|
+}).onSuccess(async ({ data }) => {
|
|
|
+ Visitor.patientId = data;
|
|
|
+ Toast.success(`操作成功`);
|
|
|
+ try {
|
|
|
+ submitting.value = true;
|
|
|
+ await handle();
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const { loading: searching, send: search } = useRequest((data) => searchAccountMethod(data), {
|
|
|
+ immediate: false,
|
|
|
+}).onSuccess(({ data }) => {
|
|
|
+ modelRef.value = { ...modelRef.value, ...data };
|
|
|
+});
|
|
|
+
|
|
|
+const { loading: captchaLoading, countdown, send: getCaptcha } = useCaptcha(
|
|
|
+ () => getCaptchaMethod(modelRef.value.phone!),
|
|
|
+ { initialCountdown: 60 },
|
|
|
+);
|
|
|
+const getCaptchaHandle = async () => {
|
|
|
+ try {
|
|
|
+ await formRef.value?.validate('phone');
|
|
|
+ await getCaptcha();
|
|
|
+ const field = fields.value.find(field => field.name === 'code');
|
|
|
+ if ( field?.keyboard ) { field.keyboard.show = true; }
|
|
|
+ } catch ( e: any ) {
|
|
|
+ Toast.warning(e?.message);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const searchHandle = async (key: 'cardno' | 'code') => {
|
|
|
+ try {
|
|
|
+ await formRef.value?.validate(key);
|
|
|
+ await search(modelRef.value).catch();
|
|
|
+ } catch ( e: any ) {
|
|
|
+ Toast.warning(e?.message);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+function onKeyboardBlur(field: Field & { name: FieldKey }) {
|
|
|
+ if ( field?.name === 'phone' ) { getCaptchaHandle(); }
|
|
|
+ if ( field?.name === 'cardno' ) { searchHandle('cardno'); }
|
|
|
+ if ( field?.name === 'code' ) { searchHandle('code'); }
|
|
|
+}
|
|
|
+
|
|
|
+function onSubmitHandle() {
|
|
|
+ submit(toValue(modelRef));
|
|
|
+}
|
|
|
+
|
|
|
+const router = useRouter();
|
|
|
+const { send: handle } = useRequest(
|
|
|
+ () => processMethod('/register'),
|
|
|
+ { immediate: false },
|
|
|
+).onSuccess(
|
|
|
+ ({ data }) => {
|
|
|
+ if ( data ) {
|
|
|
+ router.replace(data);
|
|
|
+ } else {
|
|
|
+ Notify.warning(`[路由] 配置异常无法解析正确路径,请联系管理员`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <div class="p-6">
|
|
|
+ <van-form class="register-form" ref="register-form" colon required="auto"
|
|
|
+ scroll-to-error scroll-to-error-position="center"
|
|
|
+ @submit="onSubmitHandle()"
|
|
|
+ >
|
|
|
+ <van-cell-group :border="false">
|
|
|
+ <template v-for="field in fields" :key="field.name">
|
|
|
+ <van-field v-model="modelRef[field.name]" :name="field.name" :id="field.name"
|
|
|
+ :rules="field.rules" v-bind="field.control"
|
|
|
+ :class="{'no-border': field.control?.border === false}"
|
|
|
+ :focused="field.keyboard?.show" @focus="field.keyboard && (field.keyboard.show = true)"
|
|
|
+ @blur="field.keyboard && (field.keyboard.show = false)"
|
|
|
+ >
|
|
|
+ <template #input v-if="field.component?.name === 'radio'">
|
|
|
+ <van-radio-group v-model="modelRef[field.name]" direction="horizontal" shape="dot">
|
|
|
+ <van-radio v-for="option in field.component?.options" :key="option.value" :name="option.value">
|
|
|
+ {{ option.label }}
|
|
|
+ </van-radio>
|
|
|
+ </van-radio-group>
|
|
|
+ </template>
|
|
|
+ <template #input v-else-if="field.component?.name === 'code'">
|
|
|
+ <van-password-input
|
|
|
+ style="width: 100%;"
|
|
|
+ v-model:value="modelRef[field.name]" v-bind="field.component.props"
|
|
|
+ :focused="field.keyboard?.show" @focus="field.keyboard && (field.keyboard.show = true)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template #button>
|
|
|
+ <div class="text-primary cursor-pointer">
|
|
|
+ <template v-if="field.component?.name === 'code'">
|
|
|
+ <div class="text-primary cursor-pointer" @click="getCaptchaHandle()">
|
|
|
+ {{ captchaLoading ? '发送中...' : countdown > 0 ? `${ countdown }后可重发` : '获取验证码' }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else>{{ field.suffix }}</template>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </van-field>
|
|
|
+ <van-number-keyboard
|
|
|
+ v-if="field.keyboard"
|
|
|
+ v-model="modelRef[field.name]"
|
|
|
+ v-bind="field.keyboard" :maxlength="field.control.maxlength"
|
|
|
+ @blur="field.keyboard.show = false; onKeyboardBlur(field)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </van-cell-group>
|
|
|
+ </van-form>
|
|
|
+ <div class="m-4">
|
|
|
+ <div class="m-auto size-16 cursor-pointer">
|
|
|
+ <van-loading v-if="submitting" type="spinner" size="64" color="#38ff6e" />
|
|
|
+ <img v-else class="size-full"
|
|
|
+ src="@/assets/images/next-step.svg" alt="提交" @click="formRef?.submit()"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<style scoped lang="scss">
|
|
|
+.register-form {
|
|
|
+ .van-field {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .van-field.no-border {
|
|
|
+ :deep(.van-field__control) {
|
|
|
+ padding: 0;
|
|
|
+ border: none;
|
|
|
+ border-radius: 8px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-field__label) {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ padding: 8px 0;
|
|
|
+ min-width: 100px;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-field__control) {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ padding: 8px;
|
|
|
+ border: 1px solid #38ff6e;
|
|
|
+ border-radius: 8px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-field__clear) {
|
|
|
+ align-self: flex-start;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 42px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-field__button) {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ padding: 8px var(--van-padding-xs);
|
|
|
+ min-width: 100px;
|
|
|
+ font-size: 18px;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-field__error-message) {
|
|
|
+ position: absolute;
|
|
|
+ top: 40px + 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-password-input) {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-password-input__security) {
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ text-align: center;
|
|
|
+ $size: 40px;
|
|
|
+ height: $size + 2px;
|
|
|
+
|
|
|
+ li {
|
|
|
+ height: $size;
|
|
|
+ width: $size;
|
|
|
+ flex: none;
|
|
|
+ border: 1px solid #38ff6e;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.van-radio-group {
|
|
|
+ height: 40px + 2px;
|
|
|
+}
|
|
|
+</style>
|