| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- <script setup lang="ts">
- import { Notify, Toast } from '@/platform';
- import {
- type FieldKey,
- getCaptchaMethod,
- processMethod,
- registerAccountMethod,
- registerFieldsMethod,
- scanAccountMethod, searchAccountMethod,
- } from '@/request/api';
- import { useVisitor } from '@/stores';
- import { useRouteQuery } from '@vueuse/router';
- import { useCaptcha, useForm, useRequest, useWatcher } from 'alova/client';
- import type { FieldRule, FormInstance, NumberKeyboardProps, PasswordInputProps } from 'vant';
- import { RadioGroup as vanRadioGroup } from 'vant';
- import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
- interface Field {
- control: {
- label: string; placeholder?: string;
- type?: string; min?: number; max?: number; minlength?: number; maxlength?: number;
- clearable?: boolean; border?: boolean; readonly?:boolean;
- };
- component?: |
- { name: 'radio', options: { label: string; value: string; }[] } |
- { name: 'code', props?: Partial<PasswordInputProps> };
- keyboard?: { show: boolean; } & Partial<NumberKeyboardProps>;
- suffix?: string;
- rules?: FieldRule[];
- }
- const Fields: Record<FieldKey, Field> = {
- height: {
- control: {
- label: '身高', placeholder: '请输入身高',
- type: 'number', min: 1, max: 300, clearable: true, readonly: true,
- maxlength: 5,
- },
- keyboard: { show: false, title: '身高', extraKey:'.', closeButtonText: '完成' },
- suffix: 'cm',
- },
- weight: {
- control: {
- label: '体重', placeholder: '请输入体重',
- type: 'number', min: 1, max: 300, clearable: true, readonly: true,
- maxlength: 5,
- },
- keyboard: { show: false, title: '体重', extraKey:'.', closeButtonText: '完成' },
- suffix: 'kg',
- },
- age: {
- control: {
- label: '年龄', placeholder: '请输入年龄',
- type: 'digit', min: 0, max: 300, clearable: true, readonly: true,
- maxlength: 3,
- },
- keyboard: { show: false, title: '年龄', closeButtonText: '完成' },
- suffix: '岁',
- },
- sex: {
- control: { label: '性别', border: false },
- component: {
- name: 'radio' as const,
- options: [
- { label: '男', value: '0' },
- { label: '女', value: '1' },
- ],
- },
- },
- 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, readonly: 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, readonly: 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: '' } 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 };
- });
- let captchaLoaded = false;
- const { loading: captchaLoading, countdown, send: getCaptcha } = useCaptcha(
- () => getCaptchaMethod(modelRef.value.phone!),
- { initialCountdown: 60 },
- ).onSuccess(({ data }) => {
- captchaLoaded = true;
- Toast.success(data ?? '获取成功')
- });
- 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' && !captchaLoaded ) { 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(`[路由] 配置异常无法解析正确路径,请联系管理员`);
- }
- });
- function fix(key: FieldKey) {
- for ( const field of fields.value ) {
- if (field.keyboard?.show && field.name !== key ) field.keyboard.show = false;
- }
- }
- const scan = useRouteQuery<string>('scan');
- useWatcher(
- () => scanAccountMethod(sessionStorage.getItem(`scan_${ scan.value }`)!),
- [ scan ],
- {
- immediate: true,
- async middleware(_, next) {
- if ( scan.value ) {
- let scanToastRef: any;
- try {
- scanToastRef = Toast.loading(100, { message: '加载中' });
- await next();
- } catch ( error ) {} finally {
- scanToastRef?.close?.();
- }
- }
- },
- },
- ).onSuccess(({ data }) => {
- Toast.success('扫码成功');
- modelRef.value = { ...modelRef.value, ...data };
- });
- onBeforeUnmount(() => {
- for ( let i = 0; i < sessionStorage.length; i++ ) {
- const key = sessionStorage.key(i);
- if ( key?.startsWith('scan_') ) sessionStorage.removeItem(key);
- }
- });
- </script>
- <template>
- <div>
- <div class="page-header flex py-4 px-4">
- <div class="grow shrink-0 h-full min-w-16"></div>
- <div class="grow-[3] shrink mx-2 flex flex-col justify-center overflow-hidden">
- <div class="font-bold text-3xl text-nowrap text-center tracking-wide overflow-ellipsis overflow-hidden">
- 建档
- </div>
- </div>
- <div class="grow shrink-0 h-full min-w-16 flex items-center justify-end overflow-hidden">
- <router-link :to="{ path: '/screen' }" replace>
- <img class="size-8 object-scale-down" :src="NavHomeSelect" alt="返回首页">
- </router-link>
- </div>
- </div>
- <div class="page-content 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)"
- :readonly="field.control.readonly" @click="field.keyboard && (field.keyboard.show = true)"
- >
- <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 as any)!.props"
- :focused="field.keyboard?.show" @focus="field.keyboard && (field.keyboard.show = true);fix('code')"
- />
- </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>
- </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;
- &::after {
- display: none;
- }
- li {
- height: $size;
- width: $size;
- flex: none;
- border: 1px solid #38ff6e;
- border-radius: 8px;
- }
- }
- }
- .van-radio-group {
- height: 40px + 2px;
- }
- </style>
|