修改相关bug

This commit is contained in:
fuenmao
2026-09-17 11:37:32 +08:00
parent a0a181af57
commit 04c621961c
6 changed files with 255 additions and 82 deletions
+39 -9
View File
@@ -230,8 +230,12 @@
</template>
</el-table-column>
<el-table-column prop="effectiveDate" label="生效日期" width="130" align="center" />
<el-table-column prop="expiryDate" label="失效日期" width="130" align="center" />
<el-table-column label="生效时间" width="175" align="center">
<template #default="scope">{{ formatVersionTime(scope.row.effectiveDate) }}</template>
</el-table-column>
<el-table-column label="失效时间" width="175" align="center">
<template #default="scope">{{ formatVersionTime(scope.row.expiryDate) }}</template>
</el-table-column>
<el-table-column prop="remark" label="版本说明" min-width="150" />
<el-table-column label="操作" width="340" fixed="right" align="center">
@@ -344,12 +348,26 @@
</div>
</el-form-item>
<el-form-item label="生效日期">
<el-date-picker v-model="versionForm.effectiveDate" value-format="YYYY-MM-DD" class="w-full" />
<el-form-item label="生效时间">
<el-date-picker
v-model="versionForm.effectiveDate"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择生效时间"
class="w-full"
/>
</el-form-item>
<el-form-item label="失效日期">
<el-date-picker v-model="versionForm.expiryDate" value-format="YYYY-MM-DD" class="w-full" />
<el-form-item label="失效时间">
<el-date-picker
v-model="versionForm.expiryDate"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择失效时间"
class="w-full"
/>
</el-form-item>
<el-form-item label="版本备注">
@@ -476,7 +494,7 @@
</el-form-item>
<el-form-item label="变量类型" required>
<el-select v-model="variableForm.variableType" class="w-full">
<el-select v-model="variableForm.variableType" disabled class="w-full">
<el-option label="员工填写(从新员工基本信息/动态字段读取)" value="employee" />
<el-option label="公司信息(从公司档案自动读取)" value="company" />
<el-option label="人事预设填写(人事发起时录入)" value="contract" />
@@ -485,7 +503,7 @@
</el-form-item>
<el-form-item label="数据类型" required>
<el-select v-model="variableForm.dataType" class="w-full">
<el-select v-model="variableForm.dataType" disabled class="w-full">
<el-option label="文本" value="string" />
<el-option label="日期" value="date" />
<el-option label="数字" value="number" />
@@ -494,7 +512,7 @@
</el-form-item>
<el-form-item v-if="['employee', 'company'].includes(variableForm.variableType || '')" label="取值字段">
<el-input v-model="variableForm.employeeField" placeholder="如:EmployeeName、IdCardNumber、Phone" />
<el-input v-model="variableForm.employeeField" disabled placeholder="如:EmployeeName、IdCardNumber、Phone" />
</el-form-item>
<el-form-item label="默认值">
@@ -529,6 +547,7 @@
<script setup lang="ts" name="onboardingContract">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import { Check } from "@element-plus/icons-vue";
import {
onboardingTaskApi,
@@ -892,10 +911,21 @@ async function uploadDocx(options: any) {
}
}
function formatVersionTime(value?: string) {
return value ? dayjs(value).format("YYYY-MM-DD HH:mm:ss") : "--";
}
async function saveVersion() {
if (!selectedTemplate.value || !versionForm.versionNo || !versionForm.sourceDocxFileId) {
return ElMessage.warning("请填写版本号并上传 DOCX 文件");
}
if (
versionForm.effectiveDate
&& versionForm.expiryDate
&& !dayjs(versionForm.expiryDate).isAfter(dayjs(versionForm.effectiveDate))
) {
return ElMessage.warning("失效时间必须晚于生效时间");
}
await onboardingTaskApi.addVersion({ ...versionForm, templateId: selectedTemplate.value.id });
versionVisible.value = false;
await loadVersions();
@@ -295,6 +295,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {
OnboardingCompany,
OnboardingContractTemplate,
@@ -508,10 +509,23 @@ async function onContractTemplateChange(row: any) {
await loadContractVersions(row.contractTemplateId);
}
function isVersionCurrentlyAvailable(version: OnboardingContractVersion) {
const now = dayjs();
const effectiveAt = version.effectiveDate ? dayjs(version.effectiveDate) : undefined;
const expiresAt = version.expiryDate ? dayjs(version.expiryDate) : undefined;
return version.versionStatus === "enabled"
&& (!effectiveAt || (effectiveAt.isValid() && !effectiveAt.isAfter(now)))
&& (!expiresAt || (expiresAt.isValid() && expiresAt.isAfter(now)));
}
async function loadContractVersions(templateId: number) {
if (versionOptions[templateId]) return;
const res = await onboardingTaskApi.versionPage({ templateId, pageNum: 1, pageSize: 100 });
versionOptions[templateId] = (res.data?.list || []).filter((it: any) => it.versionStatus === "enabled");
const res = await onboardingTaskApi.versionPage({
templateId,
versionStatus: "enabled",
pageNum: 1,
pageSize: 100
});
versionOptions[templateId] = (res.data?.list || []).filter(isVersionCurrentlyAvailable);
}
function addContract() {
@@ -619,12 +633,25 @@ async function openEdit(row: OnboardingTemplate) {
const positionRes = await onboardingTaskApi.listPositionsByDepartment(form.departmentId);
positions.value = positionRes.data || [];
}
const configuredContractCount = form.contracts.length;
await loadAvailableContractTemplates();
const removedTemplateCount = configuredContractCount - form.contracts.length;
let clearedVersionCount = 0;
for (const contract of form.contracts) {
if (contract.contractTemplateId) {
await loadContractVersions(contract.contractTemplateId);
const availableVersions = versionOptions[contract.contractTemplateId] || [];
if (!availableVersions.some(item => item.id === contract.contractTemplateVersionId)) {
contract.contractTemplateVersionId = undefined as any;
clearedVersionCount++;
}
}
}
if (removedTemplateCount > 0 || clearedVersionCount > 0) {
ElMessage.warning(
"该入职模板原来关联了已停用、未生效或已失效的签署材料,已从当前选择中移除或清空,请重新选择后保存"
);
}
visible.value = true;
}
@@ -645,6 +672,14 @@ async function save() {
if (!form.contracts.length || form.contracts.some(item => !item.contractTemplateId || !item.contractTemplateVersionId)) {
return ElMessage.warning("请完整选择至少一份签署材料模板及版本");
}
const containsUnavailableVersion = form.contracts.some(contract => {
const versions = versionOptions[contract.contractTemplateId] || [];
const selected = versions.find(item => item.id === contract.contractTemplateVersionId);
return !selected || !isVersionCurrentlyAvailable(selected);
});
if (containsUnavailableVersion) {
return ElMessage.warning("所选签署材料版本已停用、尚未生效或已经失效,请重新选择");
}
saving.value = true;
try {
+95 -33
View File
@@ -78,9 +78,17 @@
</template>
<template #companyId="scope">
<el-tag effect="plain" type="primary">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
<el-tooltip
v-if="scope.row.companyName || getCompanyName(scope.row.companyId)"
:content="scope.row.companyName || getCompanyName(scope.row.companyId)"
placement="top"
:show-after="300"
>
<el-tag effect="plain" type="primary" class="truncate-tag">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
</el-tooltip>
<span v-else class="text-gray-400">-</span>
</template>
<template #status="scope">
@@ -135,15 +143,30 @@
</template>
<template #companyId="scope">
<el-tag effect="plain" type="primary">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
<el-tooltip
v-if="scope.row.companyName || getCompanyName(scope.row.companyId)"
:content="scope.row.companyName || getCompanyName(scope.row.companyId)"
placement="top"
:show-after="300"
>
<el-tag effect="plain" type="primary" class="truncate-tag">
{{ scope.row.companyName || getCompanyName(scope.row.companyId) }}
</el-tag>
</el-tooltip>
<span v-else class="text-gray-400">-</span>
</template>
<template #departmentName="scope">
<el-tag v-if="scope.row.departmentName" effect="light" type="warning">
{{ scope.row.departmentName }}
</el-tag>
<el-tooltip
v-if="scope.row.departmentName"
:content="scope.row.departmentName"
placement="top"
:show-after="300"
>
<el-tag effect="light" type="warning" class="truncate-tag">
{{ scope.row.departmentName }}
</el-tag>
</el-tooltip>
<span v-else class="text-gray-400">-</span>
</template>
@@ -204,18 +227,18 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="公司编码" prop="companyCode">
<el-input v-model.trim="companyForm.companyCode" placeholder="如:TECH_CORP" clearable />
<el-input v-model.trim="companyForm.companyCode" placeholder="如:TECH_CORP" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司简称/名称" prop="companyName">
<el-input v-model.trim="companyForm.companyName" placeholder="如:总公司 / 科技分部" clearable />
<el-input v-model.trim="companyForm.companyName" placeholder="如:总公司 / 科技分部" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="签署材料甲方全称" prop="legalName">
<el-input v-model.trim="companyForm.legalName" placeholder="必须与契约锁中的企业名称完全一致" clearable />
<el-input v-model.trim="companyForm.legalName" placeholder="必须与契约锁中的企业名称完全一致" maxlength="100" show-word-limit clearable />
</el-form-item>
<el-row :gutter="20">
@@ -225,14 +248,15 @@
v-model.trim="companyForm.unifiedSocialCreditCode"
placeholder="18位统一社会信用代码"
maxlength="18"
show-word-limit
clearable
@input="val => companyForm.unifiedSocialCreditCode = (val || '').toUpperCase()"
@input="val => companyForm.unifiedSocialCreditCode = (val || '').replace(/[^0-9A-Za-z]/g, '').toUpperCase()"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="法定代表人" prop="legalRepresentative">
<el-input v-model.trim="companyForm.legalRepresentative" placeholder="法定代表人姓名" clearable />
<el-input v-model.trim="companyForm.legalRepresentative" placeholder="法定代表人姓名" maxlength="20" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
@@ -240,7 +264,7 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model.trim="companyForm.contactPhone" placeholder="企业联系电话(手机或固话)" clearable />
<el-input v-model.trim="companyForm.contactPhone" placeholder="企业联系电话(手机或固话)" maxlength="20" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -254,11 +278,11 @@
</el-row>
<el-form-item label="注册地址" prop="registeredAddress">
<el-input v-model.trim="companyForm.registeredAddress" placeholder="企业法定注册地址" clearable />
<el-input v-model.trim="companyForm.registeredAddress" placeholder="企业法定注册地址" maxlength="150" show-word-limit clearable />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="companyForm.remark" type="textarea" :rows="2" placeholder="备注信息(选填)" />
<el-input v-model="companyForm.remark" type="textarea" :rows="3" placeholder="备注信息(选填)" maxlength="300" show-word-limit />
</el-form-item>
<el-divider content-position="left">契约锁电子签配置(由技术人员添加)</el-divider>
@@ -272,12 +296,26 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="默认业务分类 Id" prop="qiyuesuoCategoryId">
<el-input v-model.trim="companyForm.qiyuesuoCategoryId" placeholder="契约锁当前公司的分类 Id" clearable />
<el-input
v-model.trim="companyForm.qiyuesuoCategoryId"
placeholder="契约锁当前公司的分类 Id"
maxlength="19"
show-word-limit
clearable
@input="val => companyForm.qiyuesuoCategoryId = (val || '').replace(/\D/g, '')"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业印章 Id" prop="qiyuesuoSealId">
<el-input v-model.trim="companyForm.qiyuesuoSealId" placeholder="契约锁当前公司的印章 Id" clearable />
<el-input
v-model.trim="companyForm.qiyuesuoSealId"
placeholder="契约锁当前公司的印章 Id"
maxlength="19"
show-word-limit
clearable
@input="val => companyForm.qiyuesuoSealId = (val || '').replace(/\D/g, '')"
/>
</el-form-item>
</el-col>
</el-row>
@@ -299,12 +337,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="部门编码" required>
<el-input v-model="departmentForm.departmentCode" placeholder="如:DEPT_TECH" />
<el-input v-model.trim="departmentForm.departmentCode" placeholder="如:DEPT_TECH" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门名称" required>
<el-input v-model="departmentForm.departmentName" placeholder="如:技术研发部" />
<el-input v-model.trim="departmentForm.departmentName" placeholder="如:技术研发部" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
@@ -326,7 +364,7 @@
</el-row>
<el-form-item label="备注">
<el-input v-model="departmentForm.remark" type="textarea" :rows="2" placeholder="备注信息" />
<el-input v-model="departmentForm.remark" type="textarea" :rows="2" placeholder="备注信息" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
@@ -370,12 +408,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="岗位编码" required>
<el-input v-model="positionForm.positionCode" placeholder="如:DEV_FRONTEND" />
<el-input v-model.trim="positionForm.positionCode" placeholder="如:DEV_FRONTEND" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="岗位名称" required>
<el-input v-model="positionForm.positionName" placeholder="如:高级前端开发工程师" />
<el-input v-model.trim="positionForm.positionName" placeholder="如:高级前端开发工程师" maxlength="50" show-word-limit clearable />
</el-form-item>
</el-col>
</el-row>
@@ -401,7 +439,7 @@
</el-form-item>
<el-form-item label="备注">
<el-input v-model="positionForm.remark" type="textarea" :rows="2" placeholder="备注信息" />
<el-input v-model="positionForm.remark" type="textarea" :rows="2" placeholder="备注信息" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
@@ -505,7 +543,7 @@ const companyRules = reactive<FormRules>({
],
companyName: [
{ required: true, message: "请输入公司简称/名称", trigger: "blur" },
{ min: 2, max: 100, message: "公司名称长度需在 2 到 100 个字符之间", trigger: "blur" }
{ min: 2, max: 50, message: "公司名称长度需在 2 到 50 个字符之间", trigger: "blur" }
],
legalName: [
{ required: true, message: "请输入签署材料甲方全称", trigger: "blur" },
@@ -540,21 +578,26 @@ const companyRules = reactive<FormRules>({
],
registeredAddress: [
{ required: true, message: "请输入企业法定注册地址", trigger: "blur" },
{ min: 4, max: 200, message: "注册地址请至少输入4个字符", trigger: "blur" }
{ min: 4, max: 150, message: "注册地址需在 4 到 150 个字符之间", trigger: "blur" }
],
remark: [
{ max: 300, message: "备注不能超过 300 个字符", trigger: "blur" }
],
qiyuesuoCategoryId: [
{
pattern: /^\d*$/,
message: "业务分类 Id 必须为纯数字",
trigger: "blur"
}
},
{ max: 19, message: "业务分类 Id 不能超过 19 位数字", trigger: "blur" }
],
qiyuesuoSealId: [
{
pattern: /^\d*$/,
message: "企业印章 Id 必须为纯数字",
trigger: "blur"
}
},
{ max: 19, message: "企业印章 Id 不能超过 19 位数字", trigger: "blur" }
]
});
const departmentForm = reactive<Partial<OnboardingDepartment>>(emptyDepartment());
@@ -646,7 +689,8 @@ const departmentColumns: ColumnProps<OnboardingDepartment>[] = [
prop: "departmentName",
label: "部门名称",
minWidth: 180,
search: { el: "input" }
search: { el: "input" },
showOverflowTooltip: true
},
{
prop: "sortCode",
@@ -693,13 +737,15 @@ const positionColumns: ColumnProps<OnboardingPosition>[] = [
prop: "positionCode",
label: "岗位编码",
width: 150,
search: { el: "input" }
search: { el: "input" },
showOverflowTooltip: true
},
{
prop: "positionName",
label: "岗位名称",
minWidth: 180,
search: { el: "input" }
search: { el: "input" },
showOverflowTooltip: true
},
{
prop: "defaultContractYears",
@@ -831,7 +877,10 @@ async function save() {
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 必须同时填写(或均不填)");
}
if (categoryId && (!/^\d+$/.test(categoryId) || !/^\d+$/.test(sealId))) {
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 只能填写数字");
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 只能填写纯数字");
}
if (categoryId && (categoryId.length > 19 || sealId.length > 19)) {
return ElMessage.warning("契约锁默认业务分类 Id 和企业印章 Id 不能超过 19 位数字");
}
companyForm.qiyuesuoCategoryId = categoryId;
companyForm.qiyuesuoSealId = sealId;
@@ -971,4 +1020,17 @@ onMounted(loadCompanies);
padding-top: 3px;
line-height: 1.25;
}
.truncate-tag {
max-width: 140px;
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.truncate-tag :deep(.el-tag__content) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
+32 -3
View File
@@ -769,7 +769,7 @@
<el-descriptions-item
v-for="item in detailCustomVars"
:key="item.key"
:label="item.key"
:label="item.label"
>
{{ item.value || '-' }}
</el-descriptions-item>
@@ -1685,10 +1685,30 @@ const detailFamilyList = computed(() => {
}
});
const registrationEmployeeFieldCodes = new Set([
"employeename", "name", "username", "gender", "nation", "birthday", "registeredresidence",
"height", "weight", "healthcondition", "maritalstatus", "education", "major", "graduatedschool",
"college", "vocationalqualification", "qualificationlevel", "technicalspecialty", "phone",
"mobile", "email", "idcardnumber", "idcard", "currentaddress", "contactaddress", "landlordcontact",
"bankcardnumber", "bankcard", "bankname", "bankaccountname", "emergencycontact", "emergencyphone",
"emergencyaddress", "onboardingdepartment", "onboardingdate", "onboardingposition", "otherremarks"
]);
const detailContractVariableNames = ref<Record<string, string>>({});
function isRegistrationEmployeeVariable(variableCode: string) {
const fieldCode = (String(variableCode || "").split(".")[1] || variableCode || "").toLowerCase();
return registrationEmployeeFieldCodes.has(fieldCode);
}
const detailCustomVars = computed(() => {
try {
const obj = JSON.parse(detail.value?.employeeInfo?.customVariablesJson || "{}");
return Object.entries(obj).map(([k, v]) => ({ key: k, value: String(v) }));
return Object.entries(obj)
.filter(([key]) => !isRegistrationEmployeeVariable(key))
.map(([key, value]) => ({
key,
label: detailContractVariableNames.value[key] || key,
value: value == null ? "" : String(value)
}));
} catch {
return [];
}
@@ -2604,7 +2624,16 @@ async function copyOrShareTaskLink(row: OnboardingTask) {
async function showDetail(row: OnboardingTask, preserveTab: boolean = false) {
const currentTab = activeDetailTab.value;
const { data }: any = await onboardingTaskApi.detail({ id: row.id });
const [{ data }, variableResponse]: any[] = await Promise.all([
onboardingTaskApi.detail({ id: row.id }),
onboardingTaskApi.taskContractVariables(row.id).catch(() => ({ data: [] }))
]);
detailContractVariableNames.value = Object.fromEntries(
(variableResponse.data || [])
.flatMap((group: OnboardingTaskContractVariableGroup) => group.variables || [])
.filter((variable: any) => variable.variableType === "employee" && !isRegistrationEmployeeVariable(variable.variableCode))
.map((variable: any) => [variable.variableCode, variable.variableName || variable.variableCode])
);
detail.value = data;
if (!preserveTab) {
activeDetailTab.value = "profile";