|
@@ -2,39 +2,64 @@
|
|
|
import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
|
|
import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
|
|
|
import { Notify, Toast } from '@/platform';
|
|
import { Notify, Toast } from '@/platform';
|
|
|
|
|
|
|
|
-import {
|
|
|
|
|
- getCaptchaMethod,
|
|
|
|
|
- processMethod,
|
|
|
|
|
- registerAccountMethod,
|
|
|
|
|
- registerFieldsMethod,
|
|
|
|
|
- searchAccountMethod,
|
|
|
|
|
-} from '@/request/api';
|
|
|
|
|
-import type { Fields, RegisterModel } from '@/request/model';
|
|
|
|
|
-import { useRouteQuery } from '@vueuse/router';
|
|
|
|
|
|
|
+import { getCaptchaMethod, registerAccountMethod, registerFieldsMethod, dictionariesMethod, searchAccountMethod } from '@/request/api';
|
|
|
|
|
+import type { Fields, Option, RegisterModel } from '@/request/model';
|
|
|
|
|
+import { useRouteQuery } from '@vueuse/router';
|
|
|
|
|
|
|
|
|
|
+import { getRoutePath, useRouteNext } from '@/computable/useRouteNext';
|
|
|
import { useCaptcha, useRequest, useSerialRequest } from 'alova/client';
|
|
import { useCaptcha, useRequest, useSerialRequest } from 'alova/client';
|
|
|
|
|
|
|
|
-import type { FormInstance } from 'vant';
|
|
|
|
|
|
|
+import type { FormInstance } from 'vant';
|
|
|
import { RadioGroup as vanRadioGroup } from 'vant';
|
|
import { RadioGroup as vanRadioGroup } from 'vant';
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-const { data: fields, loading } = useRequest(registerFieldsMethod);
|
|
|
|
|
|
|
+import PickerDialog from '@/components/PickerDialog.vue';
|
|
|
|
|
|
|
|
const formRef = useTemplateRef<FormInstance>('register-form');
|
|
const formRef = useTemplateRef<FormInstance>('register-form');
|
|
|
const modelRef = ref<Partial<RegisterModel>>({ code: '' });
|
|
const modelRef = ref<Partial<RegisterModel>>({ code: '' });
|
|
|
|
|
+const modelValueRef = ref<Partial<RegisterModel>>({});
|
|
|
|
|
+const model = computed(() => ({ ...modelRef.value, ...modelValueRef.value }));
|
|
|
|
|
+
|
|
|
|
|
+const { data: fields, loading } = useSerialRequest([dictionariesMethod, (dictionaries) => registerFieldsMethod(dictionaries)]).onSuccess(({ data }) => {
|
|
|
|
|
+ const sex = data.find((field) => field.name === 'sex');
|
|
|
|
|
+ if (sex) {
|
|
|
|
|
+ const unknown = (<any>sex).component?.options?.find((option: any) => option.value === '2');
|
|
|
|
|
+ modelRef.value.sex = unknown?.value;
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
-const { loading: submitting, send: submit } = useSerialRequest([
|
|
|
|
|
- data => registerAccountMethod(data),
|
|
|
|
|
- () => processMethod(),
|
|
|
|
|
-], { immediate: false })
|
|
|
|
|
- .onSuccess(({ data }) => {router.replace(data);})
|
|
|
|
|
- .onError(({ error }) => Notify.warning(error.message));
|
|
|
|
|
|
|
+
|
|
|
|
|
+const { loading: submitting, send: submit } = useRequest(registerAccountMethod, { immediate: false }).onSuccess(({ data }) => {
|
|
|
|
|
+ submitting.value = true;
|
|
|
|
|
+ handle();
|
|
|
|
|
+});
|
|
|
|
|
+const { handle } = useRouteNext({
|
|
|
|
|
+ onSuccess(flow) { return router.push({ path: getRoutePath(flow.next), replace: true }).then() },
|
|
|
|
|
+ onError(error) { Notify.warning(error.message); },
|
|
|
|
|
+ onComplete() { submitting.value = false; },
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
const { loading: searching, send: search } = useRequest((data) => searchAccountMethod(data), {
|
|
const { loading: searching, send: search } = useRequest((data) => searchAccountMethod(data), {
|
|
|
immediate: false,
|
|
immediate: false,
|
|
|
}).onSuccess(({ data }) => {
|
|
}).onSuccess(({ data }) => {
|
|
|
- modelRef.value = { ...modelRef.value, ...data };
|
|
|
|
|
|
|
+ const modelLabel = {} as Record<string, any>;
|
|
|
|
|
+ const modelValue = {} as Record<string, any>;
|
|
|
|
|
+
|
|
|
|
|
+ for (const [key, value] of Object.entries(data)) {
|
|
|
|
|
+ const field = fields.value?.find((field) => field.name === key);
|
|
|
|
|
+ if (typeof value === 'string' && field?.component?.name === 'picker') {
|
|
|
|
|
+ const result = value.split(',').map((value) => {
|
|
|
|
|
+ const [v, l] = value.split(':');
|
|
|
|
|
+ return { value, label: l ?? (field.component as { options: Option[] })?.options?.find((option) => option.value === v)?.label ?? v };
|
|
|
|
|
+ });
|
|
|
|
|
+ modelValue[key] = result.map((t) => t.value).join(',');
|
|
|
|
|
+ modelLabel[key] = result.map((t) => t.label).join(',');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ modelLabel[key] = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ modelRef.value = { ...modelRef.value, ...modelLabel };
|
|
|
|
|
+ modelValueRef.value = { ...modelValueRef.value, ...modelValue };
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
let captchaLoaded = false;
|
|
let captchaLoaded = false;
|
|
@@ -43,11 +68,12 @@ const { loading: captchaLoading, countdown, send: getCaptcha } = useCaptcha(
|
|
|
{ initialCountdown: 60 },
|
|
{ initialCountdown: 60 },
|
|
|
).onSuccess(({ data }) => {
|
|
).onSuccess(({ data }) => {
|
|
|
captchaLoaded = true;
|
|
captchaLoaded = true;
|
|
|
- Toast.success(data ?? '获取成功')
|
|
|
|
|
|
|
+ Toast.success(data ?? '获取成功');
|
|
|
});
|
|
});
|
|
|
const getCaptchaHandle = async () => {
|
|
const getCaptchaHandle = async () => {
|
|
|
try {
|
|
try {
|
|
|
await formRef.value?.validate('phone');
|
|
await formRef.value?.validate('phone');
|
|
|
|
|
+ if ( !modelRef.value.phone ) throw { message: `请输入手机号码` };
|
|
|
await getCaptcha();
|
|
await getCaptcha();
|
|
|
const field = fields.value.find(field => field.name === 'code');
|
|
const field = fields.value.find(field => field.name === 'code');
|
|
|
if ( field?.keyboard ) { field.keyboard.show = true; }
|
|
if ( field?.keyboard ) { field.keyboard.show = true; }
|
|
@@ -67,12 +93,12 @@ const searchHandle = async (key: 'cardno' | 'code') => {
|
|
|
|
|
|
|
|
function onKeyboardBlur(field: Fields[number]) {
|
|
function onKeyboardBlur(field: Fields[number]) {
|
|
|
if ( field?.name === 'phone' && !captchaLoaded ) { getCaptchaHandle(); }
|
|
if ( field?.name === 'phone' && !captchaLoaded ) { getCaptchaHandle(); }
|
|
|
- if ( field?.name === 'cardno' ) { searchHandle('cardno'); }
|
|
|
|
|
- if ( field?.name === 'code' ) { searchHandle('code'); }
|
|
|
|
|
|
|
+ if ( field?.name === 'cardno' && modelRef.value.cardno ) { searchHandle('cardno'); }
|
|
|
|
|
+ if ( field?.name === 'code' && modelRef.value.phone ) { searchHandle('code'); }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function onSubmitHandle() {
|
|
function onSubmitHandle() {
|
|
|
- submit(toValue(modelRef));
|
|
|
|
|
|
|
+ submit(model.value);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function fix(key: string) {
|
|
function fix(key: string) {
|
|
@@ -97,6 +123,51 @@ onBeforeUnmount(() => {
|
|
|
if ( key?.startsWith('scan_') ) sessionStorage.removeItem(key);
|
|
if ( key?.startsWith('scan_') ) sessionStorage.removeItem(key);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+const keyboardProps = reactive({
|
|
|
|
|
+ key: '',
|
|
|
|
|
+ props: {},
|
|
|
|
|
+ show: false,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const pickerProps = reactive({
|
|
|
|
|
+ key: '',
|
|
|
|
|
+ props: { options: [], selected: [] },
|
|
|
|
|
+ show: false,
|
|
|
|
|
+ handle(options: Option[]) {
|
|
|
|
|
+ const key = (this ?? pickerProps).key;
|
|
|
|
|
+ (modelRef.value as Record<string, any>)[key] = options.map(option => option.label).join(',');
|
|
|
|
|
+ (modelValueRef.value as Record<string, any>)[key] = options.map(option => option.value).join(',');
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+function onFieldFocus(field: any) {
|
|
|
|
|
+ if (field.keyboard) {
|
|
|
|
|
+ keyboardProps.key = field.name;
|
|
|
|
|
+ keyboardProps.show = true;
|
|
|
|
|
+ keyboardProps.props = {
|
|
|
|
|
+ ...field.keyboard,
|
|
|
|
|
+ maxlength: field.control?.maxlength ?? Number.POSITIVE_INFINITY,
|
|
|
|
|
+ onBlur() {
|
|
|
|
|
+ keyboardProps.show = false;
|
|
|
|
|
+ onKeyboardBlur(field);
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (field.component?.name === 'picker') {
|
|
|
|
|
+ pickerProps.key = field.name;
|
|
|
|
|
+ pickerProps.show = true;
|
|
|
|
|
+ pickerProps.props = {
|
|
|
|
|
+ ...field.component.props,
|
|
|
|
|
+ title: field.control.label,
|
|
|
|
|
+ options: field.component.options,
|
|
|
|
|
+ selected: (modelValueRef.value as Record<string, string>)[field.name]?.split(','),
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+function onFieldBlur(field: any) {
|
|
|
|
|
+ keyboardProps.show = false;
|
|
|
|
|
+ pickerProps.show = false;
|
|
|
|
|
+}
|
|
|
</script>
|
|
</script>
|
|
|
<template>
|
|
<template>
|
|
|
<div>
|
|
<div>
|
|
@@ -120,55 +191,50 @@ onBeforeUnmount(() => {
|
|
|
>
|
|
>
|
|
|
<van-cell-group :border="false">
|
|
<van-cell-group :border="false">
|
|
|
<template v-for="field in fields" :key="field.name">
|
|
<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 v-if="!field.control?.hide || (typeof field.control?.hide === 'function' && !field.control.hide(model))">
|
|
|
|
|
+ <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}"
|
|
|
|
|
+ @focus="onFieldFocus(field)" @blur="onFieldBlur(field)"
|
|
|
|
|
+ :readonly="field.control?.readonly" @click="onFieldFocus(field)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <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>
|
|
|
|
|
+ </template>
|
|
|
</template>
|
|
</template>
|
|
|
</van-cell-group>
|
|
</van-cell-group>
|
|
|
</van-form>
|
|
</van-form>
|
|
|
<div class="m-4">
|
|
<div class="m-4">
|
|
|
<div class="m-auto size-16 cursor-pointer">
|
|
<div class="m-auto size-16 cursor-pointer">
|
|
|
<van-loading v-if="submitting || loading" type="spinner" size="64" color="#38ff6e" />
|
|
<van-loading v-if="submitting || loading" type="spinner" size="64" color="#38ff6e" />
|
|
|
- <img v-else class="size-full"
|
|
|
|
|
- src="@/assets/images/next-step.svg" alt="提交" @click="formRef?.submit()"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <img v-else class="size-full" src="@/assets/images/next-step.svg" alt="提交" @click="formRef?.submit()">
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <van-number-keyboard v-bind="keyboardProps.props" :show="keyboardProps.show" v-model="modelRef[keyboardProps.key]"></van-number-keyboard>
|
|
|
|
|
+ <PickerDialog v-bind="pickerProps.props" immediate v-model:show="pickerProps.show" @selected="pickerProps.handle($event)"></PickerDialog>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -251,4 +317,9 @@ onBeforeUnmount(() => {
|
|
|
.van-radio-group {
|
|
.van-radio-group {
|
|
|
height: 40px + 2px;
|
|
height: 40px + 2px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.sub-option.checked {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background-color: var(--primary-color);
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|