|
@@ -1,8 +1,9 @@
|
|
|
import UIButton from "@/components/ui/UIButton";
|
|
import UIButton from "@/components/ui/UIButton";
|
|
|
import { Colors } from "@/constants/theme";
|
|
import { Colors } from "@/constants/theme";
|
|
|
-import { DatePicker, Input, Modal, Picker, Toast } from "@ant-design/react-native";
|
|
|
|
|
|
|
+import api from "@/utils/api";
|
|
|
|
|
+import { DatePicker, Input, Modal, Radio, Toast } from "@ant-design/react-native";
|
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
|
-import { usePreventRemove } from "@react-navigation/native";
|
|
|
|
|
|
|
+import { usePreventRemove, useRoute } from "@react-navigation/native";
|
|
|
import { NavigationAction } from "@react-navigation/routers";
|
|
import { NavigationAction } from "@react-navigation/routers";
|
|
|
import { Link, Stack, useNavigation } from "expo-router";
|
|
import { Link, Stack, useNavigation } from "expo-router";
|
|
|
import React, { useCallback, useRef, useState } from "react";
|
|
import React, { useCallback, useRef, useState } from "react";
|
|
@@ -13,25 +14,27 @@ import {
|
|
|
ScrollView,
|
|
ScrollView,
|
|
|
StyleSheet,
|
|
StyleSheet,
|
|
|
Text,
|
|
Text,
|
|
|
|
|
+ TextInput,
|
|
|
View,
|
|
View,
|
|
|
} from "react-native";
|
|
} from "react-native";
|
|
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
-import UploadScreen from "../credit/upload";
|
|
|
|
|
|
|
+import { UploadScreen } from "../credit/upload";
|
|
|
|
|
|
|
|
type CustomerForm = {
|
|
type CustomerForm = {
|
|
|
name: string;
|
|
name: string;
|
|
|
mobile: string;
|
|
mobile: string;
|
|
|
gender: string;
|
|
gender: string;
|
|
|
- birthDate?: Date;
|
|
|
|
|
- idCard: string;
|
|
|
|
|
- maritalStatus: string;
|
|
|
|
|
- hometown: string;
|
|
|
|
|
- residence: string;
|
|
|
|
|
|
|
+ birthday?: Date;
|
|
|
|
|
+ idcard: string;
|
|
|
|
|
+ nation: string;
|
|
|
|
|
+ registered: string;
|
|
|
|
|
+ residential: string;
|
|
|
|
|
+ photo: string;
|
|
|
occupation: string;
|
|
occupation: string;
|
|
|
- others: string;
|
|
|
|
|
|
|
+ other: string;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-type FieldKey = "name" | "mobile" | "idCard";
|
|
|
|
|
|
|
+type FieldKey = "name" | "mobile" | "idcard";
|
|
|
type FieldErrors = Partial<Record<FieldKey, string>>;
|
|
type FieldErrors = Partial<Record<FieldKey, string>>;
|
|
|
type BirthDateSource = "unset" | "manual" | "id-card";
|
|
type BirthDateSource = "unset" | "manual" | "id-card";
|
|
|
|
|
|
|
@@ -41,20 +44,46 @@ type Option = {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const GENDER_OPTIONS: Option[] = [
|
|
const GENDER_OPTIONS: Option[] = [
|
|
|
- { label: "男", value: "male" },
|
|
|
|
|
- { label: "女", value: "female" },
|
|
|
|
|
-];
|
|
|
|
|
-
|
|
|
|
|
-const MARITAL_STATUS_OPTIONS: Option[] = [
|
|
|
|
|
- { label: "未婚", value: "single" },
|
|
|
|
|
- { label: "已婚", value: "married" },
|
|
|
|
|
- { label: "离异", value: "divorced" },
|
|
|
|
|
- { label: "丧偶", value: "widowed" },
|
|
|
|
|
|
|
+ { label: "男", value: "1" },
|
|
|
|
|
+ { label: "女", value: "2" },
|
|
|
|
|
+ { label: "其它", value: "3" }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
const MOBILE_PATTERN = /^1\d{10}$/;
|
|
const MOBILE_PATTERN = /^1\d{10}$/;
|
|
|
const IDCARD_PATTERN = /^(?:\d{15}|\d{17}[\dX])$/;
|
|
const IDCARD_PATTERN = /^(?:\d{15}|\d{17}[\dX])$/;
|
|
|
|
|
|
|
|
|
|
+const EMPTY_CUSTOMER_FORM: CustomerForm = {
|
|
|
|
|
+ name: "",
|
|
|
|
|
+ mobile: "",
|
|
|
|
|
+ gender: "",
|
|
|
|
|
+ birthday: undefined,
|
|
|
|
|
+ idcard: "",
|
|
|
|
|
+ nation: "",
|
|
|
|
|
+ registered: "",
|
|
|
|
|
+ residential: "",
|
|
|
|
|
+ photo: "",
|
|
|
|
|
+ occupation: "",
|
|
|
|
|
+ other: "",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+type CustomerExtPayload = {
|
|
|
|
|
+ nation: string;
|
|
|
|
|
+ registered: string | null;
|
|
|
|
|
+ residential: string | null;
|
|
|
|
|
+ photo: string | null;
|
|
|
|
|
+ occupation: string | null;
|
|
|
|
|
+ other: string | null;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+type CustomerPayload = {
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ mobile: string;
|
|
|
|
|
+ gender: number;
|
|
|
|
|
+ birthday: string | null;
|
|
|
|
|
+ idcard: string;
|
|
|
|
|
+ ext: CustomerExtPayload;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
function sanitizePhone(value: string) {
|
|
function sanitizePhone(value: string) {
|
|
|
return value.replace(/\D/g, "").slice(0, 11);
|
|
return value.replace(/\D/g, "").slice(0, 11);
|
|
|
}
|
|
}
|
|
@@ -111,22 +140,23 @@ function inferBirthDateFromIdCard(idCard: string) {
|
|
|
return new Date(year, month - 1, day);
|
|
return new Date(year, month - 1, day);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function getOptionLabel(options: Option[], value: string) {
|
|
|
|
|
- return options.find((item) => item.value === value)?.label ?? "";
|
|
|
|
|
|
|
+function nullableText(value: string) {
|
|
|
|
|
+ const trimmed = value.trim();
|
|
|
|
|
+ return trimmed || null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function validateForm(form: CustomerForm): FieldErrors {
|
|
function validateForm(form: CustomerForm): FieldErrors {
|
|
|
const nextErrors: FieldErrors = {};
|
|
const nextErrors: FieldErrors = {};
|
|
|
const mobile = form.mobile.trim();
|
|
const mobile = form.mobile.trim();
|
|
|
- const idCard = form.idCard.trim();
|
|
|
|
|
|
|
+ const idcard = form.idcard.trim();
|
|
|
|
|
|
|
|
if (!form.name.trim()) {
|
|
if (!form.name.trim()) {
|
|
|
nextErrors.name = "请输入客户姓名";
|
|
nextErrors.name = "请输入客户姓名";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!mobile && !idCard) {
|
|
|
|
|
|
|
+ if (!mobile && !idcard) {
|
|
|
nextErrors.mobile = "手机号和身份证号至少填写一项";
|
|
nextErrors.mobile = "手机号和身份证号至少填写一项";
|
|
|
- nextErrors.idCard = "手机号和身份证号至少填写一项";
|
|
|
|
|
|
|
+ nextErrors.idcard = "手机号和身份证号至少填写一项";
|
|
|
return nextErrors;
|
|
return nextErrors;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -134,13 +164,31 @@ function validateForm(form: CustomerForm): FieldErrors {
|
|
|
nextErrors.mobile = "请输入 11 位手机号";
|
|
nextErrors.mobile = "请输入 11 位手机号";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (idCard && !IDCARD_PATTERN.test(idCard)) {
|
|
|
|
|
- nextErrors.idCard = "请输入正确的身份证号";
|
|
|
|
|
|
|
+ if (idcard && !IDCARD_PATTERN.test(idcard)) {
|
|
|
|
|
+ nextErrors.idcard = "请输入正确的身份证号";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return nextErrors;
|
|
return nextErrors;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function buildCustomerPayload(form: CustomerForm): CustomerPayload {
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: form.name.trim(),
|
|
|
|
|
+ mobile: form.mobile.trim(),
|
|
|
|
|
+ gender: Number(form.gender) || 0,
|
|
|
|
|
+ birthday: form.birthday ? formatDate(form.birthday) : null,
|
|
|
|
|
+ idcard: form.idcard.trim(),
|
|
|
|
|
+ ext: {
|
|
|
|
|
+ nation: form.nation.trim(),
|
|
|
|
|
+ registered: nullableText(form.registered),
|
|
|
|
|
+ residential: nullableText(form.residential),
|
|
|
|
|
+ photo: nullableText(form.photo),
|
|
|
|
|
+ occupation: nullableText(form.occupation),
|
|
|
|
|
+ other: nullableText(form.other),
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function FieldLabel({
|
|
function FieldLabel({
|
|
|
label,
|
|
label,
|
|
|
required = false,
|
|
required = false,
|
|
@@ -195,7 +243,6 @@ function PickerField({
|
|
|
}) {
|
|
}) {
|
|
|
const hasValue = Boolean(value);
|
|
const hasValue = Boolean(value);
|
|
|
|
|
|
|
|
- const [selectCredit, setSelectCredit] = useState(false);
|
|
|
|
|
return (
|
|
return (
|
|
|
<View className="mb-4">
|
|
<View className="mb-4">
|
|
|
<FieldLabel label={label} required={required} />
|
|
<FieldLabel label={label} required={required} />
|
|
@@ -230,33 +277,27 @@ function PickerField({
|
|
|
export default function AddCustomerScreen() {
|
|
export default function AddCustomerScreen() {
|
|
|
const insets = useSafeAreaInsets();
|
|
const insets = useSafeAreaInsets();
|
|
|
const navigation = useNavigation();
|
|
const navigation = useNavigation();
|
|
|
- const [form, setForm] = useState<CustomerForm>({
|
|
|
|
|
- name: "",
|
|
|
|
|
- mobile: "",
|
|
|
|
|
- gender: "",
|
|
|
|
|
- birthDate: undefined,
|
|
|
|
|
- idCard: "",
|
|
|
|
|
- maritalStatus: "",
|
|
|
|
|
- hometown: "",
|
|
|
|
|
- residence: "",
|
|
|
|
|
- occupation: "",
|
|
|
|
|
- others: "",
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const [form, setForm] = useState<CustomerForm>(EMPTY_CUSTOMER_FORM);
|
|
|
const [errors, setErrors] = useState<FieldErrors>({});
|
|
const [errors, setErrors] = useState<FieldErrors>({});
|
|
|
const [birthDateSource, setBirthDateSource] = useState<BirthDateSource>("unset");
|
|
const [birthDateSource, setBirthDateSource] = useState<BirthDateSource>("unset");
|
|
|
|
|
+ const [saving, setSaving] = useState(false);
|
|
|
const leaveConfirmVisibleRef = useRef(false);
|
|
const leaveConfirmVisibleRef = useRef(false);
|
|
|
|
|
+ const savedLeaveRef = useRef(false);
|
|
|
|
|
+ const route = useRoute();
|
|
|
|
|
+ const scrollViewRef = useRef<ScrollView>(null!);
|
|
|
|
|
|
|
|
const hasDraft = Boolean(
|
|
const hasDraft = Boolean(
|
|
|
form.name.trim() ||
|
|
form.name.trim() ||
|
|
|
form.mobile.trim() ||
|
|
form.mobile.trim() ||
|
|
|
form.gender ||
|
|
form.gender ||
|
|
|
- form.birthDate ||
|
|
|
|
|
- form.idCard.trim() ||
|
|
|
|
|
- form.maritalStatus ||
|
|
|
|
|
- form.hometown.trim() ||
|
|
|
|
|
- form.residence.trim() ||
|
|
|
|
|
|
|
+ form.birthday ||
|
|
|
|
|
+ form.idcard.trim() ||
|
|
|
|
|
+ form.nation.trim() ||
|
|
|
|
|
+ form.registered.trim() ||
|
|
|
|
|
+ form.residential.trim() ||
|
|
|
|
|
+ form.photo.trim() ||
|
|
|
form.occupation.trim() ||
|
|
form.occupation.trim() ||
|
|
|
- form.others.trim()
|
|
|
|
|
|
|
+ form.other.trim()
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
const openLeaveConfirm = useCallback(
|
|
const openLeaveConfirm = useCallback(
|
|
@@ -296,6 +337,11 @@ export default function AddCustomerScreen() {
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
usePreventRemove(hasDraft, ({ data }) => {
|
|
usePreventRemove(hasDraft, ({ data }) => {
|
|
|
|
|
+ if (savedLeaveRef.current) {
|
|
|
|
|
+ navigation.dispatch(data.action);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
openLeaveConfirm(data.action);
|
|
openLeaveConfirm(data.action);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -316,23 +362,22 @@ export default function AddCustomerScreen() {
|
|
|
|
|
|
|
|
const handleMobileChange = (value: string) => {
|
|
const handleMobileChange = (value: string) => {
|
|
|
setForm((prev) => ({ ...prev, mobile: sanitizePhone(value) }));
|
|
setForm((prev) => ({ ...prev, mobile: sanitizePhone(value) }));
|
|
|
- clearErrors(["mobile", "idCard"]);
|
|
|
|
|
|
|
+ clearErrors(["mobile", "idcard"]);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleIdCardChange = (value: string) => {
|
|
const handleIdCardChange = (value: string) => {
|
|
|
const nextIdCard = sanitizeIdCard(value);
|
|
const nextIdCard = sanitizeIdCard(value);
|
|
|
const inferredBirthDate = inferBirthDateFromIdCard(nextIdCard);
|
|
const inferredBirthDate = inferBirthDateFromIdCard(nextIdCard);
|
|
|
- const canAutofillBirthDate = birthDateSource !== "manual" || !form.birthDate;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ const canAutofillBirthDate = birthDateSource !== "manual" || !form.birthday;
|
|
|
setForm((prev) => ({
|
|
setForm((prev) => ({
|
|
|
...prev,
|
|
...prev,
|
|
|
- idCard: nextIdCard,
|
|
|
|
|
- birthDate:
|
|
|
|
|
|
|
+ idcard: nextIdCard,
|
|
|
|
|
+ birthday:
|
|
|
inferredBirthDate && canAutofillBirthDate
|
|
inferredBirthDate && canAutofillBirthDate
|
|
|
? inferredBirthDate
|
|
? inferredBirthDate
|
|
|
: !inferredBirthDate && birthDateSource === "id-card"
|
|
: !inferredBirthDate && birthDateSource === "id-card"
|
|
|
? undefined
|
|
? undefined
|
|
|
- : prev.birthDate,
|
|
|
|
|
|
|
+ : prev.birthday,
|
|
|
}));
|
|
}));
|
|
|
|
|
|
|
|
if (inferredBirthDate && canAutofillBirthDate) {
|
|
if (inferredBirthDate && canAutofillBirthDate) {
|
|
@@ -341,20 +386,71 @@ export default function AddCustomerScreen() {
|
|
|
setBirthDateSource("unset");
|
|
setBirthDateSource("unset");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- clearErrors(["mobile", "idCard"]);
|
|
|
|
|
|
|
+ clearErrors(["mobile", "idcard"]);
|
|
|
};
|
|
};
|
|
|
|
|
+ const savedRef = useRef<boolean>(false);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ const onSelectCredis = (isCancel: boolean) => {
|
|
|
|
|
+ setSelectCredit(false);
|
|
|
|
|
+ // if (isCancel) {
|
|
|
|
|
+ // return;
|
|
|
|
|
+ // }
|
|
|
|
|
+ if (savedRef.current || !isCancel) {
|
|
|
|
|
+ navigation.goBack();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleSubmit = async () => {
|
|
|
|
|
+ if (saving) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- const handleSubmit = () => {
|
|
|
|
|
const nextErrors = validateForm(form);
|
|
const nextErrors = validateForm(form);
|
|
|
setErrors(nextErrors);
|
|
setErrors(nextErrors);
|
|
|
|
|
|
|
|
const firstError = Object.values(nextErrors)[0];
|
|
const firstError = Object.values(nextErrors)[0];
|
|
|
if (firstError) {
|
|
if (firstError) {
|
|
|
Toast.fail(firstError);
|
|
Toast.fail(firstError);
|
|
|
|
|
+ scrollViewRef.current?.scrollTo({ y: 160, animated: true });
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Toast.success("表单校验通过,后续可直接接入保存接口");
|
|
|
|
|
|
|
+ setSaving(true);
|
|
|
|
|
+ const toastKey = Toast.loading("正在保存客户...");
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const customerId = await api.post("/customer/add", buildCustomerPayload(form));
|
|
|
|
|
+ Toast.success("客户已保存");
|
|
|
|
|
+ savedLeaveRef.current = true;
|
|
|
|
|
+ // setForm(EMPTY_CUSTOMER_FORM);
|
|
|
|
|
+ // setBirthDateSource("unset");
|
|
|
|
|
+ // setErrors({});
|
|
|
|
|
+ // @ts-ignore
|
|
|
|
|
+
|
|
|
|
|
+ if (route.params?.onGoBack) {
|
|
|
|
|
+ // @ts-ignore
|
|
|
|
|
+ route.params.onGoBack({ customerId });
|
|
|
|
|
+ navigation.goBack();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Modal.alert("客户添加完成", "是否立即上传征信信息", [{
|
|
|
|
|
+ text: '否',
|
|
|
|
|
+ }, {
|
|
|
|
|
+ 'text': '立即上传',
|
|
|
|
|
+ onPress: () => {
|
|
|
|
|
+ setSelectCredit(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }])
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("保存客户失败:", error);
|
|
|
|
|
+ Toast.fail(error instanceof Error ? error.message : "保存客户失败,请稍后重试");
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setSaving(false);
|
|
|
|
|
+ Toast.remove(toastKey);
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@@ -365,6 +461,8 @@ export default function AddCustomerScreen() {
|
|
|
|
|
|
|
|
|
|
|
|
|
const [selectCredit, setSelectCredit] = useState(false);
|
|
const [selectCredit, setSelectCredit] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
<View className="flex-1 bg-surface">
|
|
<View className="flex-1 bg-surface">
|
|
|
<Stack.Screen options={{ title: "添加客户" }} />
|
|
<Stack.Screen options={{ title: "添加客户" }} />
|
|
@@ -373,6 +471,7 @@ export default function AddCustomerScreen() {
|
|
|
behavior={Platform.OS === "ios" ? "padding" : undefined}
|
|
behavior={Platform.OS === "ios" ? "padding" : undefined}
|
|
|
>
|
|
>
|
|
|
<ScrollView
|
|
<ScrollView
|
|
|
|
|
+ ref={scrollViewRef}
|
|
|
className="flex-1"
|
|
className="flex-1"
|
|
|
contentContainerClassName="px-5 pb-10"
|
|
contentContainerClassName="px-5 pb-10"
|
|
|
contentContainerStyle={{
|
|
contentContainerStyle={{
|
|
@@ -420,45 +519,46 @@ export default function AddCustomerScreen() {
|
|
|
<FieldMessage error={errors.name} />
|
|
<FieldMessage error={errors.name} />
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
- <Picker
|
|
|
|
|
- data={GENDER_OPTIONS}
|
|
|
|
|
- cols={1}
|
|
|
|
|
- value={form.gender ? [form.gender] : []}
|
|
|
|
|
- onOk={(values) => {
|
|
|
|
|
- const selected = values[0];
|
|
|
|
|
- setForm((prev) => ({
|
|
|
|
|
- ...prev,
|
|
|
|
|
- gender: typeof selected === "string" ? selected : String(selected ?? ""),
|
|
|
|
|
- }));
|
|
|
|
|
- }}
|
|
|
|
|
- format={(labels) => labels[0] ?? ""}
|
|
|
|
|
- >
|
|
|
|
|
- {({ disabled, toggle }) => (
|
|
|
|
|
- <PickerField
|
|
|
|
|
- label="性别"
|
|
|
|
|
- value={getOptionLabel(GENDER_OPTIONS, form.gender)}
|
|
|
|
|
- placeholder="请选择性别"
|
|
|
|
|
- disabled={disabled}
|
|
|
|
|
- onPress={toggle}
|
|
|
|
|
- />
|
|
|
|
|
- )}
|
|
|
|
|
- </Picker>
|
|
|
|
|
|
|
+ <View className="mb-4">
|
|
|
|
|
+ <FieldLabel label="性别" />
|
|
|
|
|
+ <Radio.Group
|
|
|
|
|
+ value={form.gender}
|
|
|
|
|
+ onChange={(event) => {
|
|
|
|
|
+ const selected = event.target.value;
|
|
|
|
|
+ if (typeof selected !== "string" && typeof selected !== "number") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setForm((prev) => ({
|
|
|
|
|
+ ...prev,
|
|
|
|
|
+ gender: String(selected),
|
|
|
|
|
+ }));
|
|
|
|
|
+ }}
|
|
|
|
|
+ style={styles.radioGroup}
|
|
|
|
|
+ >
|
|
|
|
|
+ {GENDER_OPTIONS.map((option) => (
|
|
|
|
|
+ <Radio key={option.value} value={option.value}>
|
|
|
|
|
+ {option.label}
|
|
|
|
|
+ </Radio>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </Radio.Group>
|
|
|
|
|
+ </View>
|
|
|
|
|
|
|
|
<DatePicker
|
|
<DatePicker
|
|
|
precision="day"
|
|
precision="day"
|
|
|
- value={form.birthDate}
|
|
|
|
|
|
|
+ value={form.birthday}
|
|
|
minDate={new Date(1950, 0, 1)}
|
|
minDate={new Date(1950, 0, 1)}
|
|
|
maxDate={new Date()}
|
|
maxDate={new Date()}
|
|
|
format={formatDate}
|
|
format={formatDate}
|
|
|
onOk={(value) => {
|
|
onOk={(value) => {
|
|
|
- setForm((prev) => ({ ...prev, birthDate: value }));
|
|
|
|
|
|
|
+ setForm((prev) => ({ ...prev, birthday: value }));
|
|
|
setBirthDateSource("manual");
|
|
setBirthDateSource("manual");
|
|
|
}}
|
|
}}
|
|
|
>
|
|
>
|
|
|
{({ disabled, toggle }) => (
|
|
{({ disabled, toggle }) => (
|
|
|
<PickerField
|
|
<PickerField
|
|
|
label="出生日期"
|
|
label="出生日期"
|
|
|
- value={formatDate(form.birthDate)}
|
|
|
|
|
|
|
+ value={formatDate(form.birthday)}
|
|
|
placeholder="请选择出生日期"
|
|
placeholder="请选择出生日期"
|
|
|
helper={birthDateHelper}
|
|
helper={birthDateHelper}
|
|
|
disabled={disabled}
|
|
disabled={disabled}
|
|
@@ -469,20 +569,20 @@ export default function AddCustomerScreen() {
|
|
|
|
|
|
|
|
<View className="mb-4">
|
|
<View className="mb-4">
|
|
|
<FieldLabel label="手机号" />
|
|
<FieldLabel label="手机号" />
|
|
|
- <Input
|
|
|
|
|
|
|
+ <TextInput
|
|
|
value={form.mobile}
|
|
value={form.mobile}
|
|
|
onChangeText={handleMobileChange}
|
|
onChangeText={handleMobileChange}
|
|
|
placeholder="请输入 11 位手机号"
|
|
placeholder="请输入 11 位手机号"
|
|
|
- allowClear
|
|
|
|
|
keyboardType="phone-pad"
|
|
keyboardType="phone-pad"
|
|
|
|
|
+ placeholderTextColor="#9ca3af"
|
|
|
textContentType="telephoneNumber"
|
|
textContentType="telephoneNumber"
|
|
|
|
|
+ underlineColorAndroid="transparent"
|
|
|
maxLength={11}
|
|
maxLength={11}
|
|
|
- status={errors.mobile ? "error" : undefined}
|
|
|
|
|
style={[
|
|
style={[
|
|
|
styles.inputContainer,
|
|
styles.inputContainer,
|
|
|
|
|
+ styles.inputText,
|
|
|
errors.mobile ? styles.inputContainerError : undefined,
|
|
errors.mobile ? styles.inputContainerError : undefined,
|
|
|
]}
|
|
]}
|
|
|
- inputStyle={styles.inputText}
|
|
|
|
|
/>
|
|
/>
|
|
|
<FieldMessage
|
|
<FieldMessage
|
|
|
error={errors.mobile}
|
|
error={errors.mobile}
|
|
@@ -492,57 +592,45 @@ export default function AddCustomerScreen() {
|
|
|
|
|
|
|
|
<View className="mb-4">
|
|
<View className="mb-4">
|
|
|
<FieldLabel label="身份证号" />
|
|
<FieldLabel label="身份证号" />
|
|
|
- <Input
|
|
|
|
|
- value={form.idCard}
|
|
|
|
|
|
|
+ <TextInput
|
|
|
|
|
+ value={form.idcard}
|
|
|
onChangeText={handleIdCardChange}
|
|
onChangeText={handleIdCardChange}
|
|
|
placeholder="请输入身份证号"
|
|
placeholder="请输入身份证号"
|
|
|
- allowClear
|
|
|
|
|
autoCapitalize="characters"
|
|
autoCapitalize="characters"
|
|
|
|
|
+ autoCorrect={false}
|
|
|
|
|
+ placeholderTextColor="#9ca3af"
|
|
|
|
|
+ underlineColorAndroid="transparent"
|
|
|
maxLength={18}
|
|
maxLength={18}
|
|
|
- status={errors.idCard ? "error" : undefined}
|
|
|
|
|
style={[
|
|
style={[
|
|
|
styles.inputContainer,
|
|
styles.inputContainer,
|
|
|
- errors.idCard ? styles.inputContainerError : undefined,
|
|
|
|
|
|
|
+ styles.inputText,
|
|
|
|
|
+ errors.idcard ? styles.inputContainerError : undefined,
|
|
|
]}
|
|
]}
|
|
|
- inputStyle={styles.inputText}
|
|
|
|
|
/>
|
|
/>
|
|
|
<FieldMessage
|
|
<FieldMessage
|
|
|
- error={errors.idCard}
|
|
|
|
|
- helper={errors.idCard ? undefined : "填写 18 位身份证后会自动识别出生日期"}
|
|
|
|
|
|
|
+ error={errors.idcard}
|
|
|
|
|
+ helper={errors.idcard ? undefined : "填写 18 位身份证后会自动识别出生日期"}
|
|
|
/>
|
|
/>
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- <Picker
|
|
|
|
|
- data={MARITAL_STATUS_OPTIONS}
|
|
|
|
|
- cols={1}
|
|
|
|
|
- value={form.maritalStatus ? [form.maritalStatus] : []}
|
|
|
|
|
- onOk={(values) => {
|
|
|
|
|
- const selected = values[0];
|
|
|
|
|
- setForm((prev) => ({
|
|
|
|
|
- ...prev,
|
|
|
|
|
- maritalStatus:
|
|
|
|
|
- typeof selected === "string" ? selected : String(selected ?? ""),
|
|
|
|
|
- }));
|
|
|
|
|
- }}
|
|
|
|
|
- format={(labels) => labels[0] ?? ""}
|
|
|
|
|
- >
|
|
|
|
|
- {({ disabled, toggle }) => (
|
|
|
|
|
- <PickerField
|
|
|
|
|
- label="婚姻状态"
|
|
|
|
|
- value={getOptionLabel(MARITAL_STATUS_OPTIONS, form.maritalStatus)}
|
|
|
|
|
- placeholder="请选择婚姻状态"
|
|
|
|
|
- disabled={disabled}
|
|
|
|
|
- onPress={toggle}
|
|
|
|
|
- />
|
|
|
|
|
- )}
|
|
|
|
|
- </Picker>
|
|
|
|
|
|
|
+ <View className="mb-4">
|
|
|
|
|
+ <FieldLabel label="民族" />
|
|
|
|
|
+ <Input
|
|
|
|
|
+ value={form.nation}
|
|
|
|
|
+ onChangeText={(value) => setForm((prev) => ({ ...prev, nation: value }))}
|
|
|
|
|
+ placeholder="请输入民族"
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ maxLength={16}
|
|
|
|
|
+ style={styles.inputContainer}
|
|
|
|
|
+ inputStyle={styles.inputText}
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>
|
|
|
|
|
|
|
|
<View className="mb-4">
|
|
<View className="mb-4">
|
|
|
<FieldLabel label="户籍所在地" />
|
|
<FieldLabel label="户籍所在地" />
|
|
|
<Input
|
|
<Input
|
|
|
- value={form.hometown}
|
|
|
|
|
- onChangeText={(value) => setForm((prev) => ({ ...prev, hometown: value }))}
|
|
|
|
|
|
|
+ value={form.registered}
|
|
|
|
|
+ onChangeText={(value) => setForm((prev) => ({ ...prev, registered: value }))}
|
|
|
placeholder="请输入户籍所在地"
|
|
placeholder="请输入户籍所在地"
|
|
|
allowClear
|
|
allowClear
|
|
|
style={styles.inputContainer}
|
|
style={styles.inputContainer}
|
|
@@ -553,8 +641,8 @@ export default function AddCustomerScreen() {
|
|
|
<View className="mb-4">
|
|
<View className="mb-4">
|
|
|
<FieldLabel label="现居住地" />
|
|
<FieldLabel label="现居住地" />
|
|
|
<Input
|
|
<Input
|
|
|
- value={form.residence}
|
|
|
|
|
- onChangeText={(value) => setForm((prev) => ({ ...prev, residence: value }))}
|
|
|
|
|
|
|
+ value={form.residential}
|
|
|
|
|
+ onChangeText={(value) => setForm((prev) => ({ ...prev, residential: value }))}
|
|
|
placeholder="请输入现居住地"
|
|
placeholder="请输入现居住地"
|
|
|
allowClear
|
|
allowClear
|
|
|
style={styles.inputContainer}
|
|
style={styles.inputContainer}
|
|
@@ -562,6 +650,18 @@ export default function AddCustomerScreen() {
|
|
|
/>
|
|
/>
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
|
|
+ <View className="mb-4">
|
|
|
|
|
+ <FieldLabel label="照片" />
|
|
|
|
|
+ <Input
|
|
|
|
|
+ value={form.photo}
|
|
|
|
|
+ onChangeText={(value) => setForm((prev) => ({ ...prev, photo: value }))}
|
|
|
|
|
+ placeholder="请输入照片地址"
|
|
|
|
|
+ allowClear
|
|
|
|
|
+ style={styles.inputContainer}
|
|
|
|
|
+ inputStyle={styles.inputText}
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
<View className="mb-4">
|
|
<View className="mb-4">
|
|
|
<FieldLabel label="职业" />
|
|
<FieldLabel label="职业" />
|
|
|
<Input
|
|
<Input
|
|
@@ -577,8 +677,8 @@ export default function AddCustomerScreen() {
|
|
|
<View>
|
|
<View>
|
|
|
<FieldLabel label="其它" />
|
|
<FieldLabel label="其它" />
|
|
|
<Input.TextArea
|
|
<Input.TextArea
|
|
|
- value={form.others}
|
|
|
|
|
- onChangeText={(value) => setForm((prev) => ({ ...prev, others: value }))}
|
|
|
|
|
|
|
+ value={form.other}
|
|
|
|
|
+ onChangeText={(value) => setForm((prev) => ({ ...prev, other: value }))}
|
|
|
placeholder="补充备注、渠道来源、客户标签等"
|
|
placeholder="补充备注、渠道来源、客户标签等"
|
|
|
autoSize={{ minRows: 4, maxRows: 6 }}
|
|
autoSize={{ minRows: 4, maxRows: 6 }}
|
|
|
maxLength={200}
|
|
maxLength={200}
|
|
@@ -589,12 +689,12 @@ export default function AddCustomerScreen() {
|
|
|
/>
|
|
/>
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
- <UIButton type="primary" icon="save" onPress={handleSubmit}>
|
|
|
|
|
|
|
+ <UIButton type="primary" icon="save" onPress={handleSubmit} disabled={saving} loading={saving}>
|
|
|
保存客户
|
|
保存客户
|
|
|
</UIButton>
|
|
</UIButton>
|
|
|
</ScrollView>
|
|
</ScrollView>
|
|
|
</KeyboardAvoidingView>
|
|
</KeyboardAvoidingView>
|
|
|
- <UploadScreen visible={selectCredit} onClose={() => setSelectCredit(false)} />
|
|
|
|
|
|
|
+ <UploadScreen visible={selectCredit} onClose={onSelectCredis} />
|
|
|
</View>
|
|
</View>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -617,6 +717,12 @@ const styles = StyleSheet.create({
|
|
|
fontSize: 16,
|
|
fontSize: 16,
|
|
|
paddingVertical: Platform.OS === "web" ? 12 : 10,
|
|
paddingVertical: Platform.OS === "web" ? 12 : 10,
|
|
|
},
|
|
},
|
|
|
|
|
+ radioGroup: {
|
|
|
|
|
+ flexDirection: "row",
|
|
|
|
|
+ gap: 24,
|
|
|
|
|
+ minHeight: 44,
|
|
|
|
|
+ alignItems: "center",
|
|
|
|
|
+ },
|
|
|
textAreaContainer: {
|
|
textAreaContainer: {
|
|
|
borderRadius: 16,
|
|
borderRadius: 16,
|
|
|
borderWidth: 1,
|
|
borderWidth: 1,
|