Forráskód Böngészése

职责/金额输入回填不再擅自清洗 + 序列卡片数字改为岗位数:api/hr 里 dutyContent → 数组的解析去掉「剥行首序号」正则(「1、初级职责」回填后被永久截成「初级职责」),回填与展示一律优先用接口原始 dutyContent,避免 splitText 按「;」拆条后再 join(\n) 把用户分号改成换行;PositionEditDialog 职责回填同步改用 form.dutyText 原始值;岗位管理序列卡片右侧数字原来返回 children.length(层级数),放在序列名后会被当成岗位数,改为按当前机构/部门/关键词/状态口径统计的岗位数,与右侧表格对得上;岗位编辑弹窗金额输入框统一 :controls=false 且 :precision=0(右侧 40px 上下箭头盖住输入区,点箭头区不聚焦输入框,表现为「第一下点击没反应」),并补 is-focus 边框高亮;员工档案编辑态不再预拉全员直属上级列表(下拉 disabled 用不到,回显走 syncManagerField 的按 id 单查)、不再拉工号池(工号框 disabled 且生成按钮 v-if=!isEdit);合并上游「所属部门校验」时保留其版本并注明取舍

lihaiting 6 napja
szülő
commit
6f71cc4102

+ 15 - 3
src/api/hr/index.js

@@ -172,11 +172,18 @@ function collectPositionLevels(raw = {}) {
   return result;
 }
 
+/**
+ * 职责文本 → 多条数组:按换行或分号拆分,**行内内容原样保留**。
+ * ★ 2026-10-28 去掉原先「剥掉行首序号」的那条正则(匹配 "开头空白 + 数字 + 顿号/点" 并删除):
+ *   它会把用户手写的「1、初级职责」剥成「初级职责」,回填到弹框再保存就永久丢序号
+ *   (提交和接口返回都是「1、初级职责」,页面却只剩「初级职责」)。
+ *   前端不做内容清洗,序号属于用户原文。
+ */
 function splitText(value) {
   if (Array.isArray(value)) return value.filter(Boolean);
   return String(value || "")
     .split(/\r?\n|[;;]/)
-    .map((item) => item.replace(/^\s*\d+[.、]?\s*/, "").trim())
+    .map((item) => item.trim())
     .filter(Boolean);
 }
 
@@ -250,7 +257,8 @@ function adaptLevelConfig(cfg = {}) {
       conditions,
     },
     duties,
-    dutyText: duties.join("\n"),
+    /* 回填用原始 dutyContent(未经拆分/去分隔符),保证弹框里看到的和接口返回一字不差 */
+    dutyText: salary.dutyContent || duties.join("\n"),
   };
 }
 
@@ -352,6 +360,9 @@ export function adaptPosition(raw = {}) {
     effectDate: salary.birthDateRequirement || "",
     version,
     duties: splitText(salary.dutyContent),
+    /* 职责任务弹框回填直接用原始文本:splitText 会按「;」拆条,
+       拿 join("\n") 回填相当于擅自把用户的分号改成换行(2026-10-28) */
+    dutyText: salary.dutyContent || "",
     skills: (raw.skills || []).map((item) => ({
       ...item,
       name: item.skillName || "",
@@ -1204,7 +1215,8 @@ export function adaptPositionApplicationDetail(raw = {}) {
   return {
     ...adaptPositionApplication(raw),
     duties,
-    dutiesText: duties.join("\n"),
+    /* 展示用原始 dutyContent,避免 join("\n") 把用户写的「;」改成换行 */
+    dutiesText: salary.dutyContent || duties.join("\n"),
     salaryMin: Number(salary.salaryMin ?? 0),
     salaryMid: Number(salary.salaryMid ?? 0),
     salaryMax: Number(salary.salaryMax ?? 0),

+ 11 - 0
src/styles/views/positionManagement/components/PositionEditDialog.scss

@@ -51,6 +51,17 @@
     background: linear-gradient(180deg, #f8fbff 0, #fff 84px);
   }
 
+  /* 金额框聚焦态要明确可见:控件已去掉上下箭头,全靠边框高亮告诉用户「已经点进去了」 */
+  .salary-section ::v-deep .el-input__inner {
+    border-radius: 8px;
+    transition: border-color .2s, box-shadow .2s;
+  }
+
+  .salary-section ::v-deep .el-input.is-focus .el-input__inner {
+    border-color: #4a91ed;
+    box-shadow: 0 0 0 3px rgba(24,144,255,0.08);
+  }
+
   .qualification-section {
     padding: 20px 22px 8px;
     border-color: #dbe7f6;

+ 9 - 3
src/views/hrManagement/employeeArchive/components/EmployeeArchiveForm.vue

@@ -1199,9 +1199,15 @@ export default {
           if (!this.staffingLevelOptions.length) this.syncStaffingLevelsFromPosition();
         });
       /* 直属上级与部门/机构解耦(全员搜索):表单填充后预拉候选,
-         确保编辑时已有直属上级能立刻回显「名字(工号)」而不是裸 id;
-         接口没给姓名时按 id 反查,反查不到则清空该字段(不显示占位) */
-      this.searchManagers('').then(() => this.syncManagerField());
+         确保**新增**时点开下拉立刻有选项(下拉 focus 时还有 ensureManagerOptions 兜一次)。
+         编辑态下拉是 disabled(模板 :disabled="isEdit"),候选没有任何用处;
+         回显姓名走 syncManagerField 内部「候选 → 按 id 单查(loadEmployeeBriefs)」的路径即可,
+         不再为一个点不开的下拉白拉一次全员列表(2026-10-28)。 */
+      if (this.isEdit) {
+        this.syncManagerField();
+      } else {
+        this.searchManagers('').then(() => this.syncManagerField());
+      }
       this.loadOrganizations().then(() => {
         /* 新增员工默认带出所属机构(登录机构列表第一个) */
         this.applyDefaultLegalEntity();

+ 2 - 2
src/views/hrManagement/employeeArchive/index.vue

@@ -755,8 +755,8 @@ export default {
           hireDate: detail.hireDate || employee.hireDate || '',
         };
         this.formVisible = true;
-        // 弹窗里要用工号池做查重,此时才拉
-        this.ensureEmployeeNumbers();
+        /* 编辑态不拉工号池:工号输入框始终 disabled,且「生成」按钮是 v-if="!isEdit",
+           编辑用不到查重 / 自动生成编号(2026-10-28,原先这里会白拉一次全员 getUserPage) */
       } catch (error) {
         this.$message.error(error.message || '员工详情加载失败');
       } finally {

+ 15 - 5
src/views/position/positionManagement/components/PositionEditDialog.vue

@@ -165,15 +165,17 @@
                       <span class="salary-summary">{{ salarySummary }}</span>
                     </div>
                     <el-row :gutter="16">
+                      <!-- 金额框一律 :controls="false":右侧 40px 的上下箭头会盖住输入区,
+                           点在箭头区不聚焦输入框(表现为「第一下点击没反应」);金额也不需要 ±500 步进 -->
                       <el-col :xs="24" :sm="8"><el-form-item label="薪资下限" prop="salaryMin"
                           data-field-anchor="field-salaryMin"><el-input-number v-model="form.salaryMin" :min="0"
-                            :step="500" controls-position="right" class="full-width"
+                            :controls="false" :precision="0" class="full-width"
                             @change="syncSalaryMid" /></el-form-item></el-col>
                       <el-col :xs="24" :sm="8"><el-form-item label="薪资中位值"><el-input-number v-model="form.salaryMid"
-                            :min="0" :step="500" controls-position="right" class="full-width" /></el-form-item></el-col>
+                            :min="0" :controls="false" :precision="0" class="full-width" /></el-form-item></el-col>
                       <el-col :xs="24" :sm="8"><el-form-item label="薪资上限" prop="salaryMax"
                           data-field-anchor="field-salaryMax"><el-input-number v-model="form.salaryMax" :min="0"
-                            :step="500" controls-position="right" class="full-width"
+                            :controls="false" :precision="0" class="full-width"
                             @change="syncSalaryMid" /></el-form-item></el-col>
                       <el-col :xs="24" :sm="12"><el-form-item label="薪资模板"><el-select v-model="form.salaryTemplateId"
                             filterable allow-create default-first-option class="full-width"
@@ -1048,7 +1050,11 @@ export default {
         promote: this.normalizePromote(source.promote),
         levels: [],
       };
-      this.dutyText = (this.form.duties || []).join("\n");
+      /* 优先用接口原始 dutyContent 回填(adaptPosition.dutyText):
+         退回 duties.join("\n") 会丢掉行首序号、并把原文的「;」换成换行(2026-10-28) */
+      this.dutyText =
+        this.form.dutyText ||
+        (Array.isArray(this.form.duties) ? this.form.duties.join("\n") : "");
       if (this.isUpdate) {
         // 编辑:优先用接口返回的每层级配置,再按序列启用层级补齐缺失项
         const seqLevels = this.fetchEnabledLevels(this.form.positionSequence);
@@ -1559,7 +1565,11 @@ export default {
       /* 所属部门:界面已下线该字段,历史数据这一列存的是「机构 id」(dept_id NOT NULL)。
          departments 由 index.vue 构建时主动剔除了机构节点,所以机构 id 在 departments 里必然找不到,
          原逻辑会把「合法历史数据」判成非法并锁死保存。此处把机构节点本身也视为合法归属,
-         只有既不是部门、也不是任何机构 id 的野值才拦截。 */
+         只有既不是部门、也不是任何机构 id 的野值才拦截。
+         ★ 2026-10-28 合并说明:本地分支曾整段删除该校验(理由是同字段已在模板注释下线),
+         与上游这次「机构节点也算合法归属」的修法目标一致(都是避免把合法历史数据判死),
+         这里保留上游版本——它多拦住了真正的野值,且 deptId 在 validate 前已由 normalizeDeptId()
+         归一到当前机构 id,正常编辑不会被这条拦住。 */
       const deptKey = String(this.form.deptId ?? "").trim();
       const department = this.departments.find(
         (item) => String(item.id) === deptKey,

+ 11 - 2
src/views/position/positionManagement/index.vue

@@ -848,13 +848,22 @@ export default {
       this.selectedSequence = "";
       this.selectedLevel = null;
     },
-    // 序列右侧数字 = 该序列下层级导航(children)数量
+    /**
+     * 序列卡片右侧数字 = 该序列下**当前可筛出的岗位数**(按当前机构/部门/关键词/状态口径统计)。
+     * ★ 2026-10-28 原来返回 children.length(层级数量),放在序列名后面极易被当成岗位数:
+     *   看到「开发技术 5」点进去却是空列表(5 是层级,不是岗位)。层级数量在选中序列后的
+     *   「岗位层级」清单里已经逐个列出,这里改成岗位数才能和右侧表格对得上,也不会再出现
+     *   「明明写着有数字、点进去却空」的困惑。
+     */
     countBySeq(seqOrNode) {
       const node =
         typeof seqOrNode === "object" && seqOrNode
           ? seqOrNode
           : this.sequenceTree.find((item) => item.key === seqOrNode);
-      return Array.isArray(node?.children) ? node.children.length : 0;
+      if (!node) return 0;
+      return this.localPositions.filter((item) =>
+        this.positionInSequence(item, node.key),
+      ).length;
     },
     /* 岗位是否属于所选序列:先按序列名精确匹配,再退回编码比对 */
     positionInSequence(item, sequenceKey) {