|
@@ -80,13 +80,9 @@
|
|
|
|
|
|
|
|
<div class="subsection-title"><strong>组织与任职</strong></div>
|
|
<div class="subsection-title"><strong>组织与任职</strong></div>
|
|
|
<el-row :gutter="16">
|
|
<el-row :gutter="16">
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="工号" prop="employeeNo"><el-input
|
|
|
|
|
- v-model.trim="form.employeeNo" maxlength="20" placeholder="集团全局唯一" disabled><el-button
|
|
|
|
|
- v-if="!isEdit" slot="append"
|
|
|
|
|
- @click="generateEmployeeNo">生成</el-button></el-input></el-form-item></el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="用户账号"><el-input v-model.trim="form.account"
|
|
|
|
|
- maxlength="30" placeholder="系统登录账号,支持自动生成、手动修改"><template
|
|
|
|
|
- slot="prepend">@</template></el-input></el-form-item></el-col>
|
|
|
|
|
|
|
+ <!-- 左列:组织归属 + 岗位链路(机构 → 主所属部门 → 隶属部门 → 岗位序列 → 岗位 → 性质 → 层级)。
|
|
|
|
|
+ 用「列容器」而不是 el-col 平铺:主所属部门只在编辑态渲染,平铺会让它之后的字段
|
|
|
|
|
+ 整体错位一格,新增态「工号」就会挤回左列。 -->
|
|
|
<el-col :xs="24" :sm="12">
|
|
<el-col :xs="24" :sm="12">
|
|
|
<el-form-item label="所属机构" prop="groupId">
|
|
<el-form-item label="所属机构" prop="groupId">
|
|
|
<el-select v-model="form.groupId" filterable class="full-width" placeholder="请选择法人组织"
|
|
<el-select v-model="form.groupId" filterable class="full-width" placeholder="请选择法人组织"
|
|
@@ -95,24 +91,28 @@
|
|
|
:value="String(item.id)" />
|
|
:value="String(item.id)" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12">
|
|
|
|
|
- <el-form-item label="所属工厂">
|
|
|
|
|
- <el-select v-model="form.factoryId" filterable clearable class="full-width"
|
|
|
|
|
- :loading="factoryLoading" :disabled="isEdit" placeholder="请选择所属工厂" @change="handleFactoryChange">
|
|
|
|
|
- <el-option v-for="item in factoryOptions" :key="String(item.id)" :label="item.name"
|
|
|
|
|
- :value="item.id" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
|
|
+ <!-- 主所属部门:与所属机构级联,树状单选(与 kd-aiot-frontend 的 org-user-edit 同款交互)。
|
|
|
|
|
+ 新增态:选项 = 机构节点(置灰不可选)+ 机构下所有部门;选完从隶属部门里剔除,避免重复。
|
|
|
|
|
+ ★ 2026-10-28 编辑态回显改成接口 deptName 文本:原来按 deptId 去部门树里找节点显示名称,
|
|
|
|
|
+ 节点匹配不到或撞上机构根节点时就会显示成机构名字(用户实拍:主所属部门显示成机构名)。
|
|
|
|
|
+ 名称只认详情接口 deptName,取不到就留空提示,不拿机构名 / 隶属部门拼串顶替。 -->
|
|
|
|
|
+ <el-form-item label="主所属部门" :prop="isEdit ? undefined : 'primaryDeptId'">
|
|
|
|
|
+ <el-input v-if="isEdit" :value="primaryDeptName" disabled
|
|
|
|
|
+ :placeholder="primaryDeptName ? '' : '接口未返回部门名称(deptName),请后端补充'" />
|
|
|
|
|
+ <el-cascader v-else :key="`primary-dept-${form.groupId || 'none'}`" v-model="form.primaryDeptId"
|
|
|
|
|
+ :options="mainDeptOptions" :props="organizationProps" filterable clearable
|
|
|
|
|
+ :show-all-levels="false" :append-to-body="true" popper-class="employee-archive-dept-cascader"
|
|
|
|
|
+ class="full-width" :disabled="!form.groupId"
|
|
|
|
|
+ :placeholder="form.groupId ? '请选择主所属部门' : '请先选择所属机构'"
|
|
|
|
|
+ @change="handlePrimaryDeptChange" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="隶属部门" prop="deptIds"><el-cascader
|
|
|
|
|
|
|
+ <el-form-item label="隶属部门" prop="deptIds"><el-cascader
|
|
|
:key="`dept-cascader-${form.groupId || 'none'}-${deptCascaderKey}`" v-model="form.deptIds"
|
|
:key="`dept-cascader-${form.groupId || 'none'}-${deptCascaderKey}`" v-model="form.deptIds"
|
|
|
- :options="deptOptions" :props="deptCascaderProps" filterable clearable :show-all-levels="false"
|
|
|
|
|
|
|
+ :options="affiDeptOptions" :props="deptCascaderProps" filterable clearable :show-all-levels="false"
|
|
|
:append-to-body="true" popper-class="employee-archive-dept-cascader" ref="deptCascader"
|
|
:append-to-body="true" popper-class="employee-archive-dept-cascader" ref="deptCascader"
|
|
|
class="full-width dept-multi-cascader" :disabled="isEdit || !form.groupId"
|
|
class="full-width dept-multi-cascader" :disabled="isEdit || !form.groupId"
|
|
|
:placeholder="deptCascaderPlaceholder"
|
|
:placeholder="deptCascaderPlaceholder"
|
|
|
- @change="handleOrganizationChange" /></el-form-item></el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12">
|
|
|
|
|
|
|
+ @change="handleOrganizationChange" /></el-form-item>
|
|
|
<el-form-item label="岗位序列" prop="positionSequenceId">
|
|
<el-form-item label="岗位序列" prop="positionSequenceId">
|
|
|
<el-select v-model="form.positionSequenceId" filterable clearable class="full-width"
|
|
<el-select v-model="form.positionSequenceId" filterable clearable class="full-width"
|
|
|
:loading="sequenceLoading" :disabled="isEdit || !hasDept" :placeholder="hasDept ? '请选择岗位序列' : '请先选择隶属部门'"
|
|
:loading="sequenceLoading" :disabled="isEdit || !hasDept" :placeholder="hasDept ? '请选择岗位序列' : '请先选择隶属部门'"
|
|
@@ -121,8 +121,6 @@
|
|
|
:value="String(item.id)" />
|
|
:value="String(item.id)" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12">
|
|
|
|
|
<el-form-item label="岗位" prop="positionId">
|
|
<el-form-item label="岗位" prop="positionId">
|
|
|
<el-select :key="`position-select-${positionSelectKey}`" v-model="form.positionId" filterable
|
|
<el-select :key="`position-select-${positionSelectKey}`" v-model="form.positionId" filterable
|
|
|
clearable class="full-width" :loading="positionLoading" :disabled="isEdit || !hasPositionSequence"
|
|
clearable class="full-width" :loading="positionLoading" :disabled="isEdit || !hasPositionSequence"
|
|
@@ -131,21 +129,42 @@
|
|
|
:label="positionOptionLabel(item)" :value="String(item.id)" />
|
|
:label="positionOptionLabel(item)" :value="String(item.id)" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="岗位性质" prop="employmentType"><el-select
|
|
|
|
|
|
|
+ <el-form-item label="岗位性质" prop="employmentType"><el-select
|
|
|
v-model="form.employmentType" filterable
|
|
v-model="form.employmentType" filterable
|
|
|
clearable class="full-width" :disabled="isEdit" :placeholder="form.positionId ? '请选择岗位性质' : '请先选择岗位'"
|
|
clearable class="full-width" :disabled="isEdit" :placeholder="form.positionId ? '请选择岗位性质' : '请先选择岗位'"
|
|
|
@change="syncEmploymentTypeName">
|
|
@change="syncEmploymentTypeName">
|
|
|
<el-option v-for="item in employmentOptions" :key="item.key || item.value" :label="item.label"
|
|
<el-option v-for="item in employmentOptions" :key="item.key || item.value" :label="item.label"
|
|
|
:value="item.value" />
|
|
:value="item.value" />
|
|
|
- </el-select></el-form-item></el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="岗位层级" prop="staffingLevel"><el-select
|
|
|
|
|
|
|
+ </el-select></el-form-item>
|
|
|
|
|
+ <el-form-item label="岗位层级" prop="staffingLevel"><el-select
|
|
|
v-model="form.staffingLevel"
|
|
v-model="form.staffingLevel"
|
|
|
class="full-width" :disabled="isEdit || !staffingLevelOptions.length"
|
|
class="full-width" :disabled="isEdit || !staffingLevelOptions.length"
|
|
|
:placeholder="form.positionId ? '请选择岗位层级' : '请先选择岗位'" @change="handleStaffingLevelChange">
|
|
:placeholder="form.positionId ? '请选择岗位层级' : '请先选择岗位'" @change="handleStaffingLevelChange">
|
|
|
<el-option v-for="item in staffingLevelOptions" :key="item" :label="item"
|
|
<el-option v-for="item in staffingLevelOptions" :key="item" :label="item"
|
|
|
- :value="item" /></el-select></el-form-item></el-col>
|
|
|
|
|
|
|
+ :value="item" /></el-select></el-form-item>
|
|
|
|
|
+ <!-- 是否启用:2026-10-28 用户要求放到左列末尾(岗位层级之下),与右列时间/状态区分开 -->
|
|
|
|
|
+ <el-form-item label="是否启用">
|
|
|
|
|
+ <div class="enable-field"><el-switch v-model="form.enabled" active-color="#18a875" /><span>{{
|
|
|
|
|
+ form.enabled ? '启用' :
|
|
|
|
|
+ '待岗' }}</span></div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <!-- 右列:账号、工厂、直属上级与时间状态 -->
|
|
|
<el-col :xs="24" :sm="12">
|
|
<el-col :xs="24" :sm="12">
|
|
|
|
|
+ <el-form-item label="用户账号"><el-input v-model.trim="form.account"
|
|
|
|
|
+ maxlength="30" placeholder="系统登录账号,支持自动生成、手动修改"><template
|
|
|
|
|
+ slot="prepend">@</template></el-input></el-form-item>
|
|
|
|
|
+ <el-form-item label="工号" prop="employeeNo"><el-input
|
|
|
|
|
+ v-model.trim="form.employeeNo" maxlength="20" placeholder="集团全局唯一" disabled><el-button
|
|
|
|
|
+ v-if="!isEdit" slot="append"
|
|
|
|
|
+ @click="generateEmployeeNo">生成</el-button></el-input></el-form-item>
|
|
|
|
|
+ <el-form-item label="所属工厂">
|
|
|
|
|
+ <el-select v-model="form.factoryId" filterable clearable class="full-width"
|
|
|
|
|
+ :loading="factoryLoading" :disabled="isEdit" placeholder="请选择所属工厂" @change="handleFactoryChange">
|
|
|
|
|
+ <el-option v-for="item in factoryOptions" :key="String(item.id)" :label="item.name"
|
|
|
|
|
+ :value="item.id" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="直属上级">
|
|
<el-form-item label="直属上级">
|
|
|
<el-select v-model="form.managerId" filterable remote clearable class="full-width"
|
|
<el-select v-model="form.managerId" filterable remote clearable class="full-width"
|
|
|
:loading="managerLoading" :disabled="isEdit" :remote-method="searchManagers" placeholder="输入姓名/工号搜索全员"
|
|
:loading="managerLoading" :disabled="isEdit" :remote-method="searchManagers" placeholder="输入姓名/工号搜索全员"
|
|
@@ -154,21 +173,16 @@
|
|
|
:value="item.id" />
|
|
:value="item.id" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="入职时间" prop="hireDate"><el-date-picker
|
|
|
|
|
|
|
+ <el-form-item label="入职时间" prop="hireDate"><el-date-picker
|
|
|
v-model="form.hireDate" value-format="yyyy-MM-dd" type="date" placeholder="请选择入职日期"
|
|
v-model="form.hireDate" value-format="yyyy-MM-dd" type="date" placeholder="请选择入职日期"
|
|
|
- class="full-width" :disabled="isEdit" /></el-form-item></el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="转正日期"><el-date-picker v-model="form.regularDate"
|
|
|
|
|
|
|
+ class="full-width" :disabled="isEdit" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="转正日期"><el-date-picker v-model="form.regularDate"
|
|
|
value-format="yyyy-MM-dd" type="date" placeholder="转正生效日期"
|
|
value-format="yyyy-MM-dd" type="date" placeholder="转正生效日期"
|
|
|
- class="full-width" :disabled="isEdit" /></el-form-item></el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="员工状态" prop="status"><el-select v-model="form.status"
|
|
|
|
|
|
|
+ class="full-width" :disabled="isEdit" /></el-form-item>
|
|
|
|
|
+ <el-form-item label="员工状态" prop="status"><el-select v-model="form.status"
|
|
|
class="full-width" :disabled="isEdit"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label"
|
|
class="full-width" :disabled="isEdit"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label"
|
|
|
- :value="item.value" /></el-select></el-form-item></el-col>
|
|
|
|
|
- <el-col :xs="24" :sm="12"><el-form-item label="是否启用">
|
|
|
|
|
- <div class="enable-field"><el-switch v-model="form.enabled" active-color="#18a875" /><span>{{
|
|
|
|
|
- form.enabled ? '启用' :
|
|
|
|
|
- '待岗' }}</span></div>
|
|
|
|
|
- </el-form-item></el-col>
|
|
|
|
|
|
|
+ :value="item.value" /></el-select></el-form-item>
|
|
|
|
|
+ </el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
|
|
|
|
|
|
<div class="subsection-title"><strong>联系方式</strong></div>
|
|
<div class="subsection-title"><strong>联系方式</strong></div>
|
|
@@ -661,7 +675,11 @@ const emptyContract = () => ({ category: '', name: '', signDate: '', startDate:
|
|
|
const emptyForm = () => ({
|
|
const emptyForm = () => ({
|
|
|
id: null, avatar: '', name: '', employeeNo: '', gender: '', birthDate: '', age: null, idCard: '', idCardExpiry: '', ethnic: '汉族',
|
|
id: null, avatar: '', name: '', employeeNo: '', gender: '', birthDate: '', age: null, idCard: '', idCardExpiry: '', ethnic: '汉族',
|
|
|
maritalStatus: '未婚', politicalStatus: '群众', nativePlace: '', householdType: '', healthStatus: '优',
|
|
maritalStatus: '未婚', politicalStatus: '群众', nativePlace: '', householdType: '', healthStatus: '优',
|
|
|
- company: '中盈产业集团有限公司', legalEntity: '', groupId: null, factory: '', factoryId: null, deptId: null, deptIds: [], deptName: '',
|
|
|
|
|
|
|
+ /* 所属机构只认 groupId + 登录缓存里的机构名称:不再有写死的公司名 / legalEntity 缓存字段 */
|
|
|
|
|
+ groupId: null, factory: '', factoryId: null, deptId: null, deptIds: [], deptName: '',
|
|
|
|
|
+ /* 主所属部门:树状单选,回显详情接口的 deptId(deptId/deptName 与 kd-aiot-frontend 同源)。
|
|
|
|
|
+ 只在前端做选择与去重,**不参与提交**(saveNew 入参没有 deptId)。 */
|
|
|
|
|
+ primaryDeptId: '',
|
|
|
position: '', positionId: null, positionIds: [], positionBindings: [],
|
|
position: '', positionId: null, positionIds: [], positionBindings: [],
|
|
|
// 岗位序列(部门 → 序列 → 岗位 → 性质 → 层级,与办理入职一致)
|
|
// 岗位序列(部门 → 序列 → 岗位 → 性质 → 层级,与办理入职一致)
|
|
|
positionSequenceId: '', positionSequence: '', positionSequenceName: '',
|
|
positionSequenceId: '', positionSequence: '', positionSequenceName: '',
|
|
@@ -698,13 +716,13 @@ export default {
|
|
|
else callback(new Error('请输入正确的 11 位手机号码'));
|
|
else callback(new Error('请输入正确的 11 位手机号码'));
|
|
|
};
|
|
};
|
|
|
/**
|
|
/**
|
|
|
- * 身份证只校验位数(15 位纯数字 / 18 位且末位可为 X),与「办理入职」口径一致;
|
|
|
|
|
- * 不再校验出生日期合法性、不再算校验位——避免真实/历史数据被前端拦下。
|
|
|
|
|
|
|
+ * 身份证:必填(2026-10-28 用户要求),只校验位数(15 位纯数字 / 18 位且末位可为 X),
|
|
|
|
|
+ * 与「办理入职」口径一致;不再校验出生日期合法性、不再算校验位——避免真实/历史数据被前端拦下。
|
|
|
*/
|
|
*/
|
|
|
const validateIdCard = (rule, value, callback) => {
|
|
const validateIdCard = (rule, value, callback) => {
|
|
|
if (this._skipFormValidate) return callback();
|
|
if (this._skipFormValidate) return callback();
|
|
|
const text = String(value || '').trim();
|
|
const text = String(value || '').trim();
|
|
|
- if (!text) return callback();
|
|
|
|
|
|
|
+ if (!text) return callback(new Error('请输入身份证号码'));
|
|
|
// 脱敏展示(如 4301********1234)允许通过
|
|
// 脱敏展示(如 4301********1234)允许通过
|
|
|
if (/^\d{6}\*{8}[\dXx]{4}$/.test(text)) return callback();
|
|
if (/^\d{6}\*{8}[\dXx]{4}$/.test(text)) return callback();
|
|
|
if (!/^\d{15}$|^\d{17}[\dXx]$/.test(text)) {
|
|
if (!/^\d{15}$|^\d{17}[\dXx]$/.test(text)) {
|
|
@@ -772,7 +790,9 @@ export default {
|
|
|
rules: {
|
|
rules: {
|
|
|
name: [{ required: true, message: '请输入员工姓名', trigger: 'blur' }],
|
|
name: [{ required: true, message: '请输入员工姓名', trigger: 'blur' }],
|
|
|
employeeNo: [{ required: true, message: '请输入工号', trigger: 'blur' }, { pattern: /^[A-Za-z0-9_-]{2,20}$/, message: '仅支持字母、数字、下划线和短横线', trigger: 'blur' }, { validator: validateNumber, trigger: 'blur' }],
|
|
employeeNo: [{ required: true, message: '请输入工号', trigger: 'blur' }, { pattern: /^[A-Za-z0-9_-]{2,20}$/, message: '仅支持字母、数字、下划线和短横线', trigger: 'blur' }, { validator: validateNumber, trigger: 'blur' }],
|
|
|
|
|
+ /* 性别:必填(2026-10-28 用户要求)。required 只负责显示必填星号,中文提示由 validator 出 */
|
|
|
gender: [{
|
|
gender: [{
|
|
|
|
|
+ required: true,
|
|
|
validator: (rule, value, callback) => {
|
|
validator: (rule, value, callback) => {
|
|
|
if (this._skipFormValidate) return callback();
|
|
if (this._skipFormValidate) return callback();
|
|
|
if (!value) callback(new Error('请选择性别'));
|
|
if (!value) callback(new Error('请选择性别'));
|
|
@@ -801,6 +821,20 @@ export default {
|
|
|
},
|
|
},
|
|
|
trigger: 'change',
|
|
trigger: 'change',
|
|
|
}],
|
|
}],
|
|
|
|
|
+ /* 主所属部门(form.primaryDeptId):必填,只在新增态校验——编辑态该字段是只读回显文本,
|
|
|
|
|
+ 用户改不了,若历史数据没主部门会永远卡住保存(编辑态 form-item 的 prop 已置空)。 */
|
|
|
|
|
+ primaryDeptId: [{
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
|
|
+ if (this._skipFormValidate) return callback();
|
|
|
|
|
+ if (value == null || value === '') {
|
|
|
|
|
+ callback(new Error('请选择主所属部门'));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ callback();
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'change',
|
|
|
|
|
+ }],
|
|
|
/* 员工状态:必填。required 只负责显示必填星号,实际校验走 validator(_skipFormValidate 时跳过) */
|
|
/* 员工状态:必填。required 只负责显示必填星号,实际校验走 validator(_skipFormValidate 时跳过) */
|
|
|
status: [{
|
|
status: [{
|
|
|
required: true,
|
|
required: true,
|
|
@@ -878,7 +912,8 @@ export default {
|
|
|
}],
|
|
}],
|
|
|
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }, { validator: validatePhone, trigger: 'blur' }],
|
|
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }, { validator: validatePhone, trigger: 'blur' }],
|
|
|
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }],
|
|
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }],
|
|
|
- idCard: [{ validator: validateIdCard, trigger: ['blur', 'change'] }],
|
|
|
|
|
|
|
+ /* 身份证:必填(required 只为星号,空值/格式提示都在 validateIdCard 里) */
|
|
|
|
|
+ idCard: [{ required: true, validator: validateIdCard, trigger: ['blur', 'change'] }],
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
@@ -891,34 +926,63 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
isEdit() { return Boolean(this.employee?.id); },
|
|
isEdit() { return Boolean(this.employee?.id); },
|
|
|
|
|
+ /* 编辑态「主所属部门」回显文本:只认详情接口的 deptName(adaptEmployee 直读 raw.deptName)。
|
|
|
|
|
+ ★ 不能用 form.deptName:打开弹窗后 normalizeDeptAgainstOptions → applyDeptIds 会把它重写成
|
|
|
|
|
+ 「隶属部门拼接串」,主部门就显示成隶属部门了(历史 bug,用户实拍物资部/机构名)。 */
|
|
|
|
|
+ primaryDeptName() {
|
|
|
|
|
+ if (!this.isEdit) return '';
|
|
|
|
|
+ return this.employee?.deptName || '';
|
|
|
|
|
+ },
|
|
|
|
|
+ /* 主所属部门选项:机构节点本身置灰(公司级不可选)+ 其下部门(工厂层已剥掉,与隶属部门同一棵部门树)。
|
|
|
|
|
+ 与所属机构级联:换机构后 form.groupId 变化 → 这里重算,页面靠 :key 重建级联组件。 */
|
|
|
|
|
+ mainDeptOptions() {
|
|
|
|
|
+ if (!this.form.groupId) return [];
|
|
|
|
|
+ const orgNode = this.findOrgNodeById(this.form.groupId);
|
|
|
|
|
+ if (!orgNode) return [];
|
|
|
|
|
+ const company = {
|
|
|
|
|
+ id: String(orgNode.id),
|
|
|
|
|
+ // 根节点名与「所属机构」保持同一来源(编辑态优先接口 groupName)
|
|
|
|
|
+ name: this.employee?.company || orgNode.name || '',
|
|
|
|
|
+ disabled: true,
|
|
|
|
|
+ children: this.deptOptions,
|
|
|
|
|
+ };
|
|
|
|
|
+ return [company];
|
|
|
|
|
+ },
|
|
|
nameInitial() { return (this.form.name || '新').slice(-1); },
|
|
nameInitial() { return (this.form.name || '新').slice(-1); },
|
|
|
avatarStyle() { const tones = ['#1890ff', '#13a471', '#7c5ce7', '#d08716', '#d25567']; const code = [...(this.form.name || '新')].reduce((sum, char) => sum + char.charCodeAt(0), 0); return { '--avatar-color': tones[code % tones.length] }; },
|
|
avatarStyle() { const tones = ['#1890ff', '#13a471', '#7c5ce7', '#d08716', '#d25567']; const code = [...(this.form.name || '新')].reduce((sum, char) => sum + char.charCodeAt(0), 0); return { '--avatar-color': tones[code % tones.length] }; },
|
|
|
currentSectionLabel() { return this.sections.find((item) => item.name === this.activeTab)?.label || ''; },
|
|
currentSectionLabel() { return this.sections.find((item) => item.name === this.activeTab)?.label || ''; },
|
|
|
- // 所属机构:只取登录缓存所属机构(sessionStorage.loginChangeGroupVOList);
|
|
|
|
|
- // 登录缓存缺失时(兜底选项只有 id 没有名称,会显示成 "1"),回落到组织树里的
|
|
|
|
|
- // 集团/公司节点(type 10/20),保证下拉可读、可选(如「中赢合力」);
|
|
|
|
|
- // 编辑老数据时原机构不在选项里,则补一条兜底选项,避免下拉只显示 ID
|
|
|
|
|
|
|
+ // 所属机构:名称只认登录缓存「所属机构」(sessionStorage.loginChangeGroupVOList)里的 name。
|
|
|
|
|
+ // ★ 2026-10-28 清掉三处编造:名称缺失的机构不进下拉(原来会退化成显示 id),
|
|
|
|
|
+ // 编辑态补出的选项也不再用 form.legalEntity / form.company / 裸 id 顶替名称,
|
|
|
|
|
+ // 组织树里查不到就如实标注「机构名称未返回」,暴露接口缺口。
|
|
|
legalEntities() {
|
|
legalEntities() {
|
|
|
- let list = getOrgOptions().map((item) => ({
|
|
|
|
|
- id: String(item.id),
|
|
|
|
|
- name: item.name
|
|
|
|
|
- }));
|
|
|
|
|
- if (!list.some((item) => item.name)) {
|
|
|
|
|
|
|
+ let list = getOrgOptions()
|
|
|
|
|
+ .map((item) => ({ id: String(item.id), name: item.name || '' }))
|
|
|
|
|
+ .filter((item) => item.name);
|
|
|
|
|
+ if (!list.length) {
|
|
|
const tree = this.organizationTree.length ? this.organizationTree : this.organizations;
|
|
const tree = this.organizationTree.length ? this.organizationTree : this.organizations;
|
|
|
- const companies = flattenOrganizationTree(tree || [])
|
|
|
|
|
- .filter((item) => ['10', '20'].includes(String(item.type)))
|
|
|
|
|
- .map((item) => ({ id: String(item.id), name: item.name, type: item.type }));
|
|
|
|
|
- if (companies.length) list = companies;
|
|
|
|
|
|
|
+ list = flattenOrganizationTree(tree || [])
|
|
|
|
|
+ .filter((item) => ['10', '20'].includes(String(item.type)) && item.name)
|
|
|
|
|
+ .map((item) => ({ id: String(item.id), name: item.name }));
|
|
|
}
|
|
}
|
|
|
const currentId = this.form.groupId != null && this.form.groupId !== '' ? String(this.form.groupId) : '';
|
|
const currentId = this.form.groupId != null && this.form.groupId !== '' ? String(this.form.groupId) : '';
|
|
|
- if (currentId && !list.some((item) => String(item.id) === currentId)) {
|
|
|
|
|
- const tree = this.organizationTree.length ? this.organizationTree : this.organizations;
|
|
|
|
|
- const hit = flattenOrganizationTree(tree || []).find((item) => String(item.id) === currentId);
|
|
|
|
|
- list.unshift({
|
|
|
|
|
- id: currentId,
|
|
|
|
|
- name: hit?.name || this.form.legalEntity || this.form.company || currentId,
|
|
|
|
|
- type: hit?.type || '20'
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ /* 编辑态:所属机构的展示名一律以详情接口返回的 groupName(adaptEmployee → company)为准。
|
|
|
|
|
+ 原来只按 id 去组织树反查名称,命中的节点可能不是机构(实例:机构位被显示成「财务部」),
|
|
|
|
|
+ 所以接口给了 groupName 就直接用它覆盖同名 id 的选项名称。 */
|
|
|
|
|
+ const apiName = this.employee?.company || '';
|
|
|
|
|
+ if (currentId) {
|
|
|
|
|
+ const idx = list.findIndex((item) => String(item.id) === currentId);
|
|
|
|
|
+ if (idx >= 0) {
|
|
|
|
|
+ if (apiName) list[idx] = { ...list[idx], name: apiName };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const tree = this.organizationTree.length ? this.organizationTree : this.organizations;
|
|
|
|
|
+ const hit = flattenOrganizationTree(tree || []).find((item) => String(item.id) === currentId);
|
|
|
|
|
+ list.unshift({
|
|
|
|
|
+ id: currentId,
|
|
|
|
|
+ // 接口 groupName 优先,其次组织树真实名称,都没有就如实标注缺口(不拿 id 冒充)
|
|
|
|
|
+ name: apiName || hit?.name || `机构名称未返回(id: ${currentId})`
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
return list;
|
|
return list;
|
|
|
},
|
|
},
|
|
@@ -971,6 +1035,18 @@ export default {
|
|
|
deptCascaderProps() {
|
|
deptCascaderProps() {
|
|
|
return { ...this.organizationProps, multiple: true };
|
|
return { ...this.organizationProps, multiple: true };
|
|
|
},
|
|
},
|
|
|
|
|
+ /* 隶属部门选项:部门树里把「主所属部门」那个节点置灰(同一部门不能既是主部门又是隶属部门,
|
|
|
|
|
+ 与 kd-aiot-frontend 的 affiDeptTree 一致)。只做浅拷贝标记,不动 deptOptions 本身 */
|
|
|
|
|
+ affiDeptOptions() {
|
|
|
|
|
+ const primaryId = this.form.primaryDeptId == null ? '' : String(this.form.primaryDeptId);
|
|
|
|
|
+ const mark = (nodes) =>
|
|
|
|
|
+ (nodes || []).map((node) => {
|
|
|
|
|
+ const next = { ...node, disabled: Boolean(primaryId) && String(node.id) === primaryId };
|
|
|
|
|
+ if (node.children) next.children = mark(node.children);
|
|
|
|
|
+ return next;
|
|
|
|
|
+ });
|
|
|
|
|
+ return mark(this.deptOptions);
|
|
|
|
|
+ },
|
|
|
hasDept() {
|
|
hasDept() {
|
|
|
return this.normalizeDeptIds(this.form.deptIds).length > 0;
|
|
return this.normalizeDeptIds(this.form.deptIds).length > 0;
|
|
|
},
|
|
},
|
|
@@ -978,8 +1054,8 @@ export default {
|
|
|
return Boolean(this.form.positionSequenceId);
|
|
return Boolean(this.form.positionSequenceId);
|
|
|
},
|
|
},
|
|
|
requiredProgress() {
|
|
requiredProgress() {
|
|
|
- // 与 form rules 一一对应(含隶属部门 / 岗位序列 / 岗位性质 / 岗位层级),保证「必填 x/y」口径准确
|
|
|
|
|
- const keys = ['name', 'employeeNo', 'gender', 'groupId', 'deptIds', 'status', 'hireDate', 'positionSequenceId', 'positionId', 'employmentType', 'staffingLevel', 'phone'];
|
|
|
|
|
|
|
+ // 与 form rules 一一对应(含主所属部门 / 身份证 / 隶属部门 / 岗位序列 / 岗位性质 / 岗位层级),保证「必填 x/y」口径准确
|
|
|
|
|
+ const keys = ['name', 'employeeNo', 'gender', 'idCard', 'groupId', 'primaryDeptId', 'deptIds', 'status', 'hireDate', 'positionSequenceId', 'positionId', 'employmentType', 'staffingLevel', 'phone'];
|
|
|
return { base: `${keys.filter((key) => { const value = this.form[key]; return Array.isArray(value) ? value.length : Boolean(value); }).length}/${keys.length}` };
|
|
return { base: `${keys.filter((key) => { const value = this.form[key]; return Array.isArray(value) ? value.length : Boolean(value); }).length}/${keys.length}` };
|
|
|
},
|
|
},
|
|
|
sectionProgress() {
|
|
sectionProgress() {
|
|
@@ -994,7 +1070,10 @@ export default {
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
completionRate() {
|
|
completionRate() {
|
|
|
- const values = [this.form.name, this.form.employeeNo, this.form.groupId, this.form.positionId, this.form.status, this.form.hireDate, this.form.phone, this.form.gender, this.form.email, this.form.birthDate, this.form.education, this.form.school, this.form.currentAddress, this.form.contract.number, this.form.bankAccount, this.form.insuredDate, this.form.skills.length, this.form.files.length];
|
|
|
|
|
|
|
+ /* ★ 2026-10-28 修正:原来取 form.bankAccount / form.insuredDate,这两个字段在 form 上根本不存在
|
|
|
|
|
+ (它们是 bankAccounts[] / socialSecurityRecords[] 的行内字段),恒为 false → 完整度最多 16/18、
|
|
|
|
|
+ 永远填不到 100%。改成按数组是否有行来判断。 */
|
|
|
|
|
+ const values = [this.form.name, this.form.employeeNo, this.form.groupId, this.form.positionId, this.form.status, this.form.hireDate, this.form.phone, this.form.gender, this.form.email, this.form.birthDate, this.form.education, this.form.school, this.form.currentAddress, this.form.contract.number, this.form.bankAccounts?.length, this.form.socialSecurityRecords?.length, this.form.skills?.length, this.form.files?.length];
|
|
|
return Math.round(values.filter(Boolean).length / values.length * 100);
|
|
return Math.round(values.filter(Boolean).length / values.length * 100);
|
|
|
},
|
|
},
|
|
|
contractState() { return this.form.contract.name ? (this.form.contract.endDate ? '合同资料已填写' : '待补结束日期') : '待录入合同'; },
|
|
contractState() { return this.form.contract.name ? (this.form.contract.endDate ? '合同资料已填写' : '待补结束日期') : '待录入合同'; },
|
|
@@ -1116,8 +1195,7 @@ export default {
|
|
|
groupId: source.groupId != null && source.groupId !== ''
|
|
groupId: source.groupId != null && source.groupId !== ''
|
|
|
? String(source.groupId)
|
|
? String(source.groupId)
|
|
|
: (source.legalEntityId != null && source.legalEntityId !== '' ? String(source.legalEntityId) : null),
|
|
: (source.legalEntityId != null && source.legalEntityId !== '' ? String(source.legalEntityId) : null),
|
|
|
- legalEntity: source.legalEntity || source.company || '',
|
|
|
|
|
- company: source.company || source.legalEntity || '',
|
|
|
|
|
|
|
+ // 所属机构名称不再单独缓存到表单(legalEntities 直接读登录缓存 / 组织树),不编造名称
|
|
|
status: this.resolveStatusForForm(source),
|
|
status: this.resolveStatusForForm(source),
|
|
|
/* 性别:下拉 value 就是接口码(1 男 / 2 女),回填统一走 normalizeGender;
|
|
/* 性别:下拉 value 就是接口码(1 男 / 2 女),回填统一走 normalizeGender;
|
|
|
接口没给值(或值无法识别)就留空,不编造「男」也不显示「未知」 */
|
|
接口没给值(或值无法识别)就留空,不编造「男」也不显示「未知」 */
|
|
@@ -1127,6 +1205,9 @@ export default {
|
|
|
factory: source.factory || source.factoryName || '',
|
|
factory: source.factory || source.factoryName || '',
|
|
|
deptIds: this.normalizeDeptIds(source.deptIds?.length ? source.deptIds : source.deptId),
|
|
deptIds: this.normalizeDeptIds(source.deptIds?.length ? source.deptIds : source.deptId),
|
|
|
deptName: source.deptName || '',
|
|
deptName: source.deptName || '',
|
|
|
|
|
+ /* 主所属部门:回显只认详情接口的 deptId(名称由级联组件按树上的节点显示);
|
|
|
|
|
+ 取不到就留空,不从 deptIds 反查、不拿隶属部门拼串顶替 */
|
|
|
|
|
+ primaryDeptId: source.deptId != null && source.deptId !== '' ? String(source.deptId) : '',
|
|
|
managerId: source.managerId != null && source.managerId !== '' ? String(source.managerId) : null,
|
|
managerId: source.managerId != null && source.managerId !== '' ? String(source.managerId) : null,
|
|
|
manager: source.manager || '',
|
|
manager: source.manager || '',
|
|
|
positionIds: this.normalizePositionIds(source).slice(0, 1),
|
|
positionIds: this.normalizePositionIds(source).slice(0, 1),
|
|
@@ -1766,10 +1847,15 @@ export default {
|
|
|
);
|
|
);
|
|
|
this.form.deptIds = list;
|
|
this.form.deptIds = list;
|
|
|
this.form.deptId = list[0] || null;
|
|
this.form.deptId = list[0] || null;
|
|
|
- this.form.deptName = list
|
|
|
|
|
- .map((id) => this.findDeptNode(id)?.name)
|
|
|
|
|
- .filter(Boolean)
|
|
|
|
|
- .join('、');
|
|
|
|
|
|
|
+ /* ★ 2026-10-28 编辑态不重写 deptName:deptName 在详情接口里是「主所属部门」,
|
|
|
|
|
+ 这里重写成隶属部门拼接串会(a)让主所属部门回显成隶属部门(b)保存时把后端的主部门名覆盖掉。
|
|
|
|
|
+ 新增态沿用原来「隶属部门名拼接」的提交口径(saveNew 只认 deptName 一个字符串)。 */
|
|
|
|
|
+ if (!this.isEdit) {
|
|
|
|
|
+ this.form.deptName = list
|
|
|
|
|
+ .map((id) => this.findDeptNode(id)?.name)
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ .join('、');
|
|
|
|
|
+ }
|
|
|
if (resetDependents) {
|
|
if (resetDependents) {
|
|
|
/* 直属上级不再随部门联动,这里只重置岗位 */
|
|
/* 直属上级不再随部门联动,这里只重置岗位 */
|
|
|
this.resetPositionsByDepartment();
|
|
this.resetPositionsByDepartment();
|
|
@@ -1787,8 +1873,6 @@ export default {
|
|
|
: null) || list[0];
|
|
: null) || list[0];
|
|
|
if (!hit) return;
|
|
if (!hit) return;
|
|
|
this.form.groupId = String(hit.id);
|
|
this.form.groupId = String(hit.id);
|
|
|
- this.form.legalEntity = hit.name || '';
|
|
|
|
|
- this.form.company = hit.name || this.form.company;
|
|
|
|
|
},
|
|
},
|
|
|
handleOrganizationChange(ids) {
|
|
handleOrganizationChange(ids) {
|
|
|
this.applyDeptIds(ids, { resetDependents: true });
|
|
this.applyDeptIds(ids, { resetDependents: true });
|
|
@@ -1811,14 +1895,33 @@ export default {
|
|
|
/* 所属机构切换:只重置依赖机构的部门 / 岗位级联。
|
|
/* 所属机构切换:只重置依赖机构的部门 / 岗位级联。
|
|
|
所属工厂与机构、部门互不关联(工厂列表是独立的平铺数据),这里保持已选工厂不动;
|
|
所属工厂与机构、部门互不关联(工厂列表是独立的平铺数据),这里保持已选工厂不动;
|
|
|
直属上级已改为全员搜索,与机构/部门解耦,同样保持已选直属上级不动 */
|
|
直属上级已改为全员搜索,与机构/部门解耦,同样保持已选直属上级不动 */
|
|
|
- handleLegalEntityChange(id) {
|
|
|
|
|
- const selected = this.legalEntities.find((item) => String(item.id) === String(id));
|
|
|
|
|
- this.form.legalEntity = selected?.name || '';
|
|
|
|
|
- this.form.company = selected?.name || this.form.company;
|
|
|
|
|
|
|
+ handleLegalEntityChange() {
|
|
|
this.deptCascaderKey += 1;
|
|
this.deptCascaderKey += 1;
|
|
|
|
|
+ /* 机构变了,机构下的部门树整棵换掉 → 已选主所属部门 / 隶属部门都失效,一并清空 */
|
|
|
|
|
+ this.form.primaryDeptId = '';
|
|
|
this.applyDeptIds([]);
|
|
this.applyDeptIds([]);
|
|
|
this.resetPositionsByDepartment();
|
|
this.resetPositionsByDepartment();
|
|
|
},
|
|
},
|
|
|
|
|
+ /* 主所属部门选中:把该部门从隶属部门里剔除(两者不允许重复,与参考项目 checkedKeys 一致) */
|
|
|
|
|
+ handlePrimaryDeptChange(id) {
|
|
|
|
|
+ const picked = id == null || id === '' ? '' : String(id);
|
|
|
|
|
+ if (!picked) return;
|
|
|
|
|
+ const current = this.normalizeDeptIds(this.form.deptIds);
|
|
|
|
|
+ const rest = current.filter((item) => String(item) !== picked);
|
|
|
|
|
+ if (rest.length !== current.length) {
|
|
|
|
|
+ this.applyDeptIds(rest);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ /* 在组织树里按 id 找节点(主所属部门要拿机构节点做置灰根节点) */
|
|
|
|
|
+ findOrgNodeById(id, nodes = this.organizationTree.length ? this.organizationTree : this.organizations) {
|
|
|
|
|
+ for (const node of nodes || []) {
|
|
|
|
|
+ if (!node) continue;
|
|
|
|
|
+ if (String(node.id) === String(id)) return node;
|
|
|
|
|
+ const matched = this.findOrgNodeById(id, node.children || []);
|
|
|
|
|
+ if (matched) return matched;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ },
|
|
|
normalizeDeptAgainstOptions() {
|
|
normalizeDeptAgainstOptions() {
|
|
|
const ids = this.normalizeDeptIds(
|
|
const ids = this.normalizeDeptIds(
|
|
|
this.form.deptIds?.length ? this.form.deptIds : this.form.deptId,
|
|
this.form.deptIds?.length ? this.form.deptIds : this.form.deptId,
|