入职任务预览,入职任务cope,验证填入的信息
This commit is contained in:
@@ -441,11 +441,46 @@
|
||||
/>
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="v in extraVariables" :key="v.variableCode" :xs="24" :sm="12">
|
||||
<el-form-item :label="v.variableName || v.variableCode" :required="v.required">
|
||||
<el-input
|
||||
<el-form-item
|
||||
:label="v.variableName || v.variableCode"
|
||||
:required="v.required"
|
||||
:error="extraVariableErrors[v.variableCode]"
|
||||
>
|
||||
<el-select
|
||||
v-if="getVariableOptions(v).length"
|
||||
v-model="customVariables[v.variableCode]"
|
||||
:placeholder="`请选择${v.variableName}`"
|
||||
:disabled="!canEdit"
|
||||
class="full-width"
|
||||
clearable
|
||||
@change="clearExtraVariableError(v.variableCode)"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in getVariableOptions(v)"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-else-if="getVariableDataType(v) === 'date'"
|
||||
v-model="customVariables[v.variableCode]"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="`请选择${v.variableName}`"
|
||||
:disabled="!canEdit"
|
||||
class="full-width"
|
||||
@change="clearExtraVariableError(v.variableCode)"
|
||||
/>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="customVariables[v.variableCode]"
|
||||
:type="getVariableDataType(v) === 'number' ? 'number' : 'text'"
|
||||
:inputmode="getVariableDataType(v) === 'number' ? 'decimal' : 'text'"
|
||||
:placeholder="v.defaultValue || `请输入${v.variableName}`"
|
||||
:disabled="!canEdit"
|
||||
clearable
|
||||
@input="clearExtraVariableError(v.variableCode)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -680,6 +715,17 @@ import {
|
||||
OnboardingTask,
|
||||
onboardingPublicApi
|
||||
} from "@/api/modules/biz/onboarding";
|
||||
import {
|
||||
getIdCardBirthday,
|
||||
getIdCardValidationMessage,
|
||||
isBankCardNumber,
|
||||
isContactPhone,
|
||||
isEmail,
|
||||
isMobilePhone,
|
||||
isNumberInRange,
|
||||
normalizeBankCardNumber,
|
||||
normalizeIdCardNumber
|
||||
} from "@/utils/onboardingValidation";
|
||||
|
||||
const route = useRoute();
|
||||
const token = ref((route.query.token as string) || "");
|
||||
@@ -745,14 +791,96 @@ const familyMembers = ref<{ name: string; relationship: string; age: string; com
|
||||
// 2. 特殊合同增量变量 (差集)
|
||||
const customVariables = reactive<Record<string, string>>({});
|
||||
|
||||
// 表单验证规则
|
||||
function validateEmployeeName(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
const employeeName = String(value ?? "").trim();
|
||||
if (!employeeName) {
|
||||
return callback(new Error("请输入真实姓名"));
|
||||
}
|
||||
if (employeeName.length > 100) {
|
||||
return callback(new Error("姓名不能超过100个字符"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validatePhone(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
if (!isMobilePhone(value)) {
|
||||
return callback(new Error("请输入正确的11位手机号码"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validateIdCardNumber(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
const message = getIdCardValidationMessage(value);
|
||||
if (message) {
|
||||
return callback(new Error(message));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validateBirthday(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
if (!value) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
const birthday = String(value);
|
||||
const idCardBirthday = getIdCardBirthday(form.idCardNumber);
|
||||
if (idCardBirthday && birthday !== idCardBirthday) {
|
||||
return callback(new Error("出生日期与身份证号码不一致"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validateBankCardNumber(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
if (!isBankCardNumber(value)) {
|
||||
return callback(new Error("请输入12至19位银行卡号"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validateOptionalEmail(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
if (value && !isEmail(value)) {
|
||||
return callback(new Error("请输入正确的电子邮箱"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validateOptionalHeight(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
if (value && !isNumberInRange(value, 100, 250)) {
|
||||
return callback(new Error("身高应为100至250厘米之间的数字"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validateOptionalWeight(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
if (value && !isNumberInRange(value, 30, 300)) {
|
||||
return callback(new Error("体重应为30至300公斤之间的数字"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function validateOptionalContactPhone(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||
if (value && !isContactPhone(value)) {
|
||||
return callback(new Error("请输入正确的手机号码或座机号码"));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
// 固定登记表字段在前端即时校验;后端仍会执行相同的核心规则作为安全兜底。
|
||||
const regRules: FormRules = {
|
||||
employeeName: [{ required: true, message: "请输入真实姓名", trigger: "blur" }],
|
||||
employeeName: [{ required: true, validator: validateEmployeeName, trigger: "blur" }],
|
||||
gender: [{ required: true, message: "请选择性别", trigger: "change" }],
|
||||
phone: [{ required: true, message: "请输入联系电话", trigger: "blur" }],
|
||||
idCardNumber: [{ required: true, message: "请输入身份证号码", trigger: "blur" }],
|
||||
bankCardNumber: [{ required: true, message: "请输入工资银行卡号", trigger: "blur" }],
|
||||
bankName: [{ required: true, message: "请输入开户银行名称", trigger: "blur" }]
|
||||
birthday: [{ validator: validateBirthday, trigger: "change" }],
|
||||
height: [{ validator: validateOptionalHeight, trigger: "blur" }],
|
||||
weight: [{ validator: validateOptionalWeight, trigger: "blur" }],
|
||||
phone: [{ required: true, validator: validatePhone, trigger: "blur" }],
|
||||
email: [{ validator: validateOptionalEmail, trigger: "blur" }],
|
||||
idCardNumber: [{ required: true, validator: validateIdCardNumber, trigger: "blur" }],
|
||||
bankCardNumber: [{ required: true, validator: validateBankCardNumber, trigger: "blur" }],
|
||||
bankName: [
|
||||
{ required: true, message: "请输入开户银行名称", trigger: "blur" },
|
||||
{ max: 100, message: "开户银行名称不能超过100个字符", trigger: "blur" }
|
||||
],
|
||||
emergencyPhone: [{ validator: validateOptionalContactPhone, trigger: "blur" }]
|
||||
};
|
||||
|
||||
const canEdit = computed(() => {
|
||||
@@ -810,6 +938,157 @@ function addFamily() {
|
||||
familyMembers.value.push({ name: "", relationship: "", age: "", company: "", phone: "" });
|
||||
}
|
||||
|
||||
const extraVariableErrors = reactive<Record<string, string>>({});
|
||||
|
||||
function getVariableDataType(variable: any) {
|
||||
return String(variable?.dataType || "string").trim().toLowerCase();
|
||||
}
|
||||
|
||||
function getVariableOptions(variable: any) {
|
||||
if (!variable?.optionsJson) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const options = JSON.parse(variable.optionsJson);
|
||||
if (!Array.isArray(options)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return options
|
||||
.map((option: any) => {
|
||||
if (option && typeof option === "object") {
|
||||
const value = option.value ?? option.label;
|
||||
const label = option.label ?? option.value;
|
||||
return value == null
|
||||
? null
|
||||
: { value: String(value), label: String(label ?? value) };
|
||||
}
|
||||
|
||||
return option == null
|
||||
? null
|
||||
: { value: String(option), label: String(option) };
|
||||
})
|
||||
.filter(Boolean) as { value: string; label: string }[];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function clearExtraVariableError(variableCode: string) {
|
||||
extraVariableErrors[variableCode] = "";
|
||||
}
|
||||
|
||||
function isIsoDate(value: string) {
|
||||
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const date = new Date(
|
||||
Number(match[1]),
|
||||
Number(match[2]) - 1,
|
||||
Number(match[3])
|
||||
);
|
||||
return date.getFullYear() === Number(match[1])
|
||||
&& date.getMonth() === Number(match[2]) - 1
|
||||
&& date.getDate() === Number(match[3]);
|
||||
}
|
||||
|
||||
function validateExtraVariables() {
|
||||
let firstMessage = "";
|
||||
|
||||
for (const variable of extraVariables.value) {
|
||||
const variableCode = variable.variableCode;
|
||||
const variableName = variable.variableName || variableCode;
|
||||
const value = String(customVariables[variableCode] ?? "").trim();
|
||||
const options = getVariableOptions(variable);
|
||||
const dataType = getVariableDataType(variable);
|
||||
customVariables[variableCode] = value;
|
||||
extraVariableErrors[variableCode] = "";
|
||||
|
||||
if (!value && variable.required) {
|
||||
extraVariableErrors[variableCode] = `请填写${variableName}`;
|
||||
} else if (value && options.length && !options.some(option => option.value === value)) {
|
||||
extraVariableErrors[variableCode] = `${variableName}必须从配置选项中选择`;
|
||||
} else if (value && dataType === "number" && !/^-?\d+(\.\d+)?$/.test(value)) {
|
||||
extraVariableErrors[variableCode] = `${variableName}必须填写数字`;
|
||||
} else if (value && dataType === "date" && !isIsoDate(value)) {
|
||||
extraVariableErrors[variableCode] = `${variableName}必须填写正确日期`;
|
||||
}
|
||||
|
||||
if (!firstMessage && extraVariableErrors[variableCode]) {
|
||||
firstMessage = extraVariableErrors[variableCode];
|
||||
}
|
||||
}
|
||||
|
||||
if (!firstMessage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ElMessage.warning(firstMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
function validateRegistrationLists() {
|
||||
for (let index = 0; index < educationHistory.value.length; index++) {
|
||||
const row = educationHistory.value[index];
|
||||
const requiredFields = [
|
||||
["timeRange", "时间范围"],
|
||||
["location", "学校或机构"],
|
||||
["content", "专业或学习内容"],
|
||||
["result", "结果或学位"]
|
||||
] as const;
|
||||
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
||||
if (missing) {
|
||||
ElMessage.warning(`第 ${index + 1} 条教育经历请填写${missing[1]},或删除整条经历`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let index = 0; index < workHistory.value.length; index++) {
|
||||
const row = workHistory.value[index];
|
||||
const requiredFields = [
|
||||
["timeRange", "起止时间"],
|
||||
["companyName", "工作单位"],
|
||||
["position", "担任职务"],
|
||||
["leaveReason", "离职原因"]
|
||||
] as const;
|
||||
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
||||
if (missing) {
|
||||
ElMessage.warning(`第 ${index + 1} 条工作经历请填写${missing[1]},或删除整条经历`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let index = 0; index < familyMembers.value.length; index++) {
|
||||
const row = familyMembers.value[index];
|
||||
const requiredFields = [
|
||||
["name", "姓名"],
|
||||
["relationship", "关系"],
|
||||
["age", "年龄"],
|
||||
["phone", "联系电话"]
|
||||
] as const;
|
||||
const missing = requiredFields.find(([field]) => !String(row[field] || "").trim());
|
||||
if (missing) {
|
||||
ElMessage.warning(`第 ${index + 1} 条家庭成员请填写${missing[1]},或删除整条记录`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const age = Number(row.age);
|
||||
if (!Number.isInteger(age) || age < 0 || age > 120) {
|
||||
ElMessage.warning(`第 ${index + 1} 条家庭成员的年龄应为0至120之间的整数`);
|
||||
return false;
|
||||
}
|
||||
if (!isContactPhone(row.phone)) {
|
||||
ElMessage.warning(`第 ${index + 1} 条家庭成员的联系电话格式不正确`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getUploadedMaterials(matId: number) {
|
||||
return uploadedMaterials.value.filter(item => item.materialTypeId === matId);
|
||||
}
|
||||
@@ -915,6 +1194,11 @@ async function load(silent = false) {
|
||||
materialRequirements.value = data.materialRequirements || [];
|
||||
uploadedMaterials.value = data.materials || [];
|
||||
contractVariables.value = data.contractVariables || [];
|
||||
contractVariables.value.forEach((variable: any) => {
|
||||
if (!(variable.variableCode in customVariables) && variable.defaultValue != null) {
|
||||
customVariables[variable.variableCode] = String(variable.defaultValue);
|
||||
}
|
||||
});
|
||||
|
||||
// 回填入职登记表
|
||||
if (data.employeeInfo) {
|
||||
@@ -942,7 +1226,13 @@ async function load(silent = false) {
|
||||
}
|
||||
|
||||
async function saveCurrentEmployeeInfo() {
|
||||
form.bankAccountName = form.employeeName; // 开户名自动对齐姓名
|
||||
form.employeeName = String(form.employeeName || "").trim();
|
||||
form.phone = String(form.phone || "").trim();
|
||||
form.email = String(form.email || "").trim();
|
||||
form.idCardNumber = normalizeIdCardNumber(form.idCardNumber);
|
||||
form.bankCardNumber = normalizeBankCardNumber(form.bankCardNumber);
|
||||
form.bankName = String(form.bankName || "").trim();
|
||||
form.bankAccountName = form.employeeName; // 开户名始终与员工姓名保持一致
|
||||
const payload = {
|
||||
...form,
|
||||
token: token.value,
|
||||
@@ -957,11 +1247,17 @@ async function saveCurrentEmployeeInfo() {
|
||||
async function nextStep() {
|
||||
if (currentStep.value === 0) {
|
||||
await regFormRef.value?.validate();
|
||||
if (!validateRegistrationLists()) {
|
||||
return;
|
||||
}
|
||||
if (canEdit.value) {
|
||||
await saveCurrentEmployeeInfo();
|
||||
ElMessage.success("入职登记表已临时保存");
|
||||
}
|
||||
} else if (currentStep.value === 1) {
|
||||
if (!validateExtraVariables()) {
|
||||
return;
|
||||
}
|
||||
if (canEdit.value) {
|
||||
await saveCurrentEmployeeInfo();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user