员工入职填写优化
This commit is contained in:
@@ -323,3 +323,44 @@ export function findItemNested(enumData: any, callValue: any, value: string, chi
|
|||||||
if (current[children]) return findItemNested(current[children], callValue, value, children);
|
if (current[children]) return findItemNested(current[children], callValue, value, children);
|
||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 复制文本到剪贴板(兼容安全上下文 HTTPS 与非安全上下文 HTTP/IP 环境)
|
||||||
|
* @param {String} text 要复制的文本
|
||||||
|
* @returns {Promise<boolean>} 是否成功复制
|
||||||
|
*/
|
||||||
|
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||||
|
if (!text) return false;
|
||||||
|
|
||||||
|
// 1. 现代 Clipboard API (仅在 Secure Context 如 HTTPS 或 localhost 下可用)
|
||||||
|
if (navigator?.clipboard?.writeText) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("navigator.clipboard.writeText failed, falling back to execCommand: ", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 降级方案:创建隐藏 textarea 并调用 document.execCommand('copy')
|
||||||
|
try {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = "fixed";
|
||||||
|
textArea.style.top = "0";
|
||||||
|
textArea.style.left = "-9999px";
|
||||||
|
textArea.style.opacity = "0";
|
||||||
|
textArea.setAttribute("readonly", "");
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
textArea.setSelectionRange(0, text.length);
|
||||||
|
|
||||||
|
const successful = document.execCommand("copy");
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
return successful;
|
||||||
|
} catch (fallbackErr) {
|
||||||
|
console.error("document.execCommand('copy') failed: ", fallbackErr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,6 +73,16 @@ export function getIdCardBirthday(value: unknown) {
|
|||||||
return `${text.slice(0, 4)}-${text.slice(4, 6)}-${text.slice(6, 8)}`;
|
return `${text.slice(0, 4)}-${text.slice(4, 6)}-${text.slice(6, 8)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIdCardGender(value: unknown) {
|
||||||
|
const idCardNumber = normalizeIdCardNumber(value);
|
||||||
|
if (!/^\d{17}[0-9X]$/.test(idCardNumber)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const genderCode = Number(idCardNumber.charAt(16));
|
||||||
|
return genderCode % 2 === 1 ? "男" : "女";
|
||||||
|
}
|
||||||
|
|
||||||
export function isNumberInRange(value: unknown, min: number, max: number) {
|
export function isNumberInRange(value: unknown, min: number, max: number) {
|
||||||
const text = String(value ?? "").trim();
|
const text = String(value ?? "").trim();
|
||||||
if (!/^\d+(\.\d+)?$/.test(text)) {
|
if (!/^\d+(\.\d+)?$/.test(text)) {
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ import {
|
|||||||
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||||
import { isMessageBoxConfirmed } from "@/utils/messageBox";
|
import { isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
import { useOnboardingPermission } from "@/utils/onboardingPermission";
|
import { useOnboardingPermission } from "@/utils/onboardingPermission";
|
||||||
|
import { copyToClipboard } from "@/utils";
|
||||||
|
|
||||||
const { canManage, disabledReason } = useOnboardingPermission("onboardingContractManageAll");
|
const { canManage, disabledReason } = useOnboardingPermission("onboardingContractManageAll");
|
||||||
|
|
||||||
@@ -997,9 +998,13 @@ function formatPlaceholder(code: string) {
|
|||||||
return `{{${code}}}`;
|
return `{{${code}}}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyPlaceholder(code: string) {
|
async function copyPlaceholder(code: string) {
|
||||||
navigator.clipboard.writeText(`{{${code}}}`);
|
const ok = await copyToClipboard(`{{${code}}}`);
|
||||||
ElMessage.success(`占位符 {{${code}}} 已复制到剪贴板`);
|
if (ok) {
|
||||||
|
ElMessage.success(`占位符 {{${code}}} 已复制到剪贴板`);
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(`自动复制失败,请手动复制 {{${code}}}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openVariables(row: OnboardingContractVersion) {
|
async function openVariables(row: OnboardingContractVersion) {
|
||||||
|
|||||||
@@ -1509,6 +1509,7 @@ import {
|
|||||||
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||||
import { getMessageBoxPromptResult, isMessageBoxConfirmed } from "@/utils/messageBox";
|
import { getMessageBoxPromptResult, isMessageBoxConfirmed } from "@/utils/messageBox";
|
||||||
import { useOnboardingPermission } from "@/utils/onboardingPermission";
|
import { useOnboardingPermission } from "@/utils/onboardingPermission";
|
||||||
|
import { copyToClipboard } from "@/utils";
|
||||||
|
|
||||||
const { canManage, disabledReason } = useOnboardingPermission("onboardingTaskManageAll");
|
const { canManage, disabledReason } = useOnboardingPermission("onboardingTaskManageAll");
|
||||||
|
|
||||||
@@ -2514,10 +2515,14 @@ async function createTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyText(text?: string, successMsg = "复制成功") {
|
async function copyText(text?: string, successMsg = "复制成功") {
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
navigator.clipboard.writeText(text);
|
const ok = await copyToClipboard(text);
|
||||||
ElMessage.success(successMsg);
|
if (ok) {
|
||||||
|
ElMessage.success(successMsg);
|
||||||
|
} else {
|
||||||
|
ElMessage.warning("自动复制失败,请在弹窗中长按或手动复制");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyInviteMessage() {
|
function copyInviteMessage() {
|
||||||
@@ -2543,7 +2548,7 @@ async function copyOrShareTaskLink(row: OnboardingTask) {
|
|||||||
row.linkExpireAt = data.expiresAt;
|
row.linkExpireAt = data.expiresAt;
|
||||||
proTable.value?.refresh();
|
proTable.value?.refresh();
|
||||||
|
|
||||||
copyText(linkUrl, "专属入职链接已复制到剪贴板!");
|
await copyText(linkUrl, "专属入职链接已复制到剪贴板!");
|
||||||
shareVisible.value = true;
|
shareVisible.value = true;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ElMessage.error(err?.message || "获取专属链接失败");
|
ElMessage.error(err?.message || "获取专属链接失败");
|
||||||
|
|||||||
@@ -115,6 +115,23 @@
|
|||||||
<el-input v-model="form.employeeName" placeholder="请输入真实姓名" :disabled="!canEdit" clearable />
|
<el-input v-model="form.employeeName" placeholder="请输入真实姓名" :disabled="!canEdit" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :xs="24" :sm="8">
|
||||||
|
<el-form-item label="身份证号码" prop="idCardNumber">
|
||||||
|
<el-input v-model="form.idCardNumber" placeholder="请输入18位身份证号" :disabled="!canEdit" maxlength="18"
|
||||||
|
clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :sm="8">
|
||||||
|
<el-form-item label="出生年月日" prop="birthday">
|
||||||
|
<el-input v-model="form.birthday" placeholder="根据身份证号自动生成" readonly :disabled="!canEdit"
|
||||||
|
class="full-width readonly-derived-input">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Calendar /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="12" :sm="8">
|
<el-col :xs="12" :sm="8">
|
||||||
<el-form-item label="性别" prop="gender">
|
<el-form-item label="性别" prop="gender">
|
||||||
<el-select v-model="form.gender" placeholder="请选择性别" :disabled="!canEdit" class="full-width">
|
<el-select v-model="form.gender" placeholder="请选择性别" :disabled="!canEdit" class="full-width">
|
||||||
@@ -128,19 +145,13 @@
|
|||||||
<el-input v-model="form.nation" placeholder="如 汉族" :disabled="!canEdit" clearable />
|
<el-input v-model="form.nation" placeholder="如 汉族" :disabled="!canEdit" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="24" :sm="8">
|
|
||||||
<el-form-item label="出生年月日" prop="birthday">
|
|
||||||
<el-date-picker v-model="form.birthday" type="date" value-format="YYYY-MM-DD" placeholder="选择出生日期"
|
|
||||||
:disabled="!canEdit" class="full-width" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="8">
|
<el-col :xs="24" :sm="8">
|
||||||
<el-form-item label="户籍所在地" prop="registeredResidence">
|
<el-form-item label="户籍所在地" prop="registeredResidence">
|
||||||
<el-input v-model="form.registeredResidence" placeholder="如 某省某市某区/县" :disabled="!canEdit"
|
<el-input v-model="form.registeredResidence" placeholder="如 某省某市某区/县" :disabled="!canEdit"
|
||||||
clearable />
|
clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="12" :sm="4">
|
<el-col :xs="12" :sm="4">
|
||||||
<el-form-item label="身高 (cm)" prop="height">
|
<el-form-item label="身高 (cm)" prop="height">
|
||||||
<el-input v-model="form.height" placeholder="如 175" :disabled="!canEdit" />
|
<el-input v-model="form.height" placeholder="如 175" :disabled="!canEdit" />
|
||||||
@@ -151,7 +162,6 @@
|
|||||||
<el-input v-model="form.weight" placeholder="如 65" :disabled="!canEdit" />
|
<el-input v-model="form.weight" placeholder="如 65" :disabled="!canEdit" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="12" :sm="8">
|
<el-col :xs="12" :sm="8">
|
||||||
<el-form-item label="健康状况" prop="healthCondition">
|
<el-form-item label="健康状况" prop="healthCondition">
|
||||||
<el-input v-model="form.healthCondition" placeholder="如 良好 / 无既往病史" :disabled="!canEdit" />
|
<el-input v-model="form.healthCondition" placeholder="如 良好 / 无既往病史" :disabled="!canEdit" />
|
||||||
@@ -167,12 +177,6 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="8">
|
|
||||||
<el-form-item label="身份证号码" prop="idCardNumber">
|
|
||||||
<el-input v-model="form.idCardNumber" placeholder="请输入18位身份证号" :disabled="!canEdit" maxlength="18"
|
|
||||||
clearable />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -683,6 +687,7 @@ import {
|
|||||||
} from "@/api/modules/biz/onboarding";
|
} from "@/api/modules/biz/onboarding";
|
||||||
import {
|
import {
|
||||||
getIdCardBirthday,
|
getIdCardBirthday,
|
||||||
|
getIdCardGender,
|
||||||
getIdCardValidationMessage,
|
getIdCardValidationMessage,
|
||||||
isBankCardNumber,
|
isBankCardNumber,
|
||||||
isContactPhone,
|
isContactPhone,
|
||||||
@@ -878,6 +883,33 @@ function validateBirthday(_rule: any, value: unknown, callback: (error?: Error)
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听身份证号码输入,自动提取出生年月日并联动推导性别
|
||||||
|
watch(
|
||||||
|
() => form.idCardNumber,
|
||||||
|
(newVal) => {
|
||||||
|
const rawIdCard = normalizeIdCardNumber(newVal);
|
||||||
|
if (!rawIdCard) {
|
||||||
|
form.birthday = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const birthday = getIdCardBirthday(rawIdCard);
|
||||||
|
if (birthday) {
|
||||||
|
form.birthday = birthday;
|
||||||
|
// 联动推导性别(若尚未手动选择过)
|
||||||
|
const gender = getIdCardGender(rawIdCard);
|
||||||
|
if (gender && (!form.gender || form.gender === "男" || form.gender === "女")) {
|
||||||
|
form.gender = gender;
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
regFormRef.value?.clearValidate(["birthday", "gender"]);
|
||||||
|
});
|
||||||
|
} else if (rawIdCard.length < 18) {
|
||||||
|
form.birthday = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
function validateBankCardNumber(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
function validateBankCardNumber(_rule: any, value: unknown, callback: (error?: Error) => void) {
|
||||||
if (value && !isBankCardNumber(value)) {
|
if (value && !isBankCardNumber(value)) {
|
||||||
return callback(new Error("请输入12至19位银行卡号"));
|
return callback(new Error("请输入12至19位银行卡号"));
|
||||||
@@ -963,9 +995,12 @@ const regRules = computed<FormRules>(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useChangeTrigger = changeTriggerFields.has(fieldKey);
|
const useChangeTrigger = changeTriggerFields.has(fieldKey);
|
||||||
|
const message = fieldKey === "birthday"
|
||||||
|
? "请先输入正确的身份证号码以生成出生日期"
|
||||||
|
: `${useChangeTrigger ? "请选择" : "请填写"}${requirement.fieldName || fieldKey}`;
|
||||||
rules[fieldKey] = [{
|
rules[fieldKey] = [{
|
||||||
required: true,
|
required: true,
|
||||||
message: `${useChangeTrigger ? "请选择" : "请填写"}${requirement.fieldName || fieldKey}`,
|
message,
|
||||||
trigger: useChangeTrigger ? "change" : "blur"
|
trigger: useChangeTrigger ? "change" : "blur"
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
@@ -2570,6 +2605,21 @@ onBeforeUnmount(() => {
|
|||||||
border-color: #059669 !important;
|
border-color: #059669 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.readonly-derived-input :deep(.el-input__wrapper) {
|
||||||
|
background-color: #f9fafb !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readonly-derived-input :deep(.el-input__inner) {
|
||||||
|
color: #374151 !important;
|
||||||
|
-webkit-text-fill-color: #374151 !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readonly-derived-input :deep(.el-input__prefix) {
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|
||||||
.fill-page :deep(.el-input__inner),
|
.fill-page :deep(.el-input__inner),
|
||||||
|
|||||||
Reference in New Issue
Block a user