Ver código fonte

feat: 新增部门岗位管理功能,支持批量新增和分页查询

xieyong 3 dias atrás
pai
commit
b3f0e5edfa

+ 10 - 0
src/api/organization/index.js

@@ -50,6 +50,16 @@ export async function updateDepartmentPosition(data) {
   return unwrap(await request.put("/fm/tmaindepartmentposition/updateBatch", data));
 }
 
+// 部门岗位关联:批量新增部门岗位编制(POST /fm/tmaindepartmentposition/saveBatch)
+export async function saveBatchDepartmentPosition(data) {
+  return unwrap(await request.post("/fm/tmaindepartmentposition/saveBatch", data));
+}
+
+// 部门岗位关联:分页查询部门岗位编制(POST /fm/tmaindepartmentposition/page)
+export async function pageDepartmentPosition(params) {
+  return unwrap(await request.post("/fm/tmaindepartmentposition/page", params));
+}
+
 export async function removeOrganization(ids) {
   const response = await request.post("/main/group/delete", ids);
   if (response.data.code == 0) {

+ 30 - 0
src/styles/views/organization/components/OrganizationEditDialog.scss

@@ -160,3 +160,33 @@
   font-size: 12px;
 }
 
+.dept-section-title {
+  justify-content: flex-start;
+}
+
+.dept-section-add {
+  margin-left: auto;
+}
+
+.dept-tabs ::v-deep .el-tabs__header {
+  margin-bottom: 14px;
+}
+
+.dept-tabs ::v-deep .el-tabs__item {
+  height: 38px;
+  padding: 0 16px;
+  color: #5b6b80;
+  font-size: 13px;
+}
+
+.dept-tab-close {
+  margin-left: 6px;
+  font-size: 12px;
+  color: #909399;
+  transition: color 0.2s;
+
+  &:hover {
+    color: #f56c6c;
+  }
+}
+

+ 249 - 84
src/views/organization/components/OrganizationEditDialog.vue

@@ -137,66 +137,96 @@
       </el-row>
     </el-form>
 
-      <!-- 部门信息:非 tab,直接置于组织表单底部,以分区样式区分 -->
+      <!-- 部门信息:tab 项可新增/删除,至少保留一项 -->
       <template v-if="showDepartment">
-        <div class="section-title">
+        <div class="section-title dept-section-title">
           <span><i class="el-icon-s-grid"></i></span>
           <div>
-            <strong>部门信息</strong>
-            <small>配置部门岗位、岗位层级与编制人数,保存后同步部门岗位关联</small>
+            <strong>岗位信息</strong>
+            <small>配置岗位、岗位层级与编制人数,保存后同步岗位关联</small>
           </div>
+          <el-button
+            size="small"
+            type="primary"
+            plain
+            icon="el-icon-plus"
+            class="dept-section-add"
+            @click="addDeptTab"
+          >
+            添加岗位信息
+          </el-button>
         </div>
-        <el-form ref="deptForm" :model="deptForm" label-position="top">
-          <el-row :gutter="20">
-            <el-col :xs="24" :sm="12">
-              <el-form-item label="岗位名称" prop="positionId">
-                <el-select
-                  v-model="deptForm.positionId"
-                  filterable
-                  clearable
-                  placeholder="请选择岗位"
-                  class="full-width"
-                  @change="onPositionChange"
-                >
-                  <el-option
-                    v-for="position in positions"
-                    :key="position.id"
-                    :label="position.positionName"
-                    :value="position.id"
-                  />
-                </el-select>
-              </el-form-item>
-            </el-col>
-            <el-col :xs="24" :sm="12">
-              <el-form-item label="岗位层级" prop="positionLevel">
-                <el-select
-                  v-model="deptForm.positionLevel"
-                  clearable
-                  placeholder="请选择岗位层级"
-                  class="full-width"
-                >
-                  <el-option
-                    v-for="level in levelOptions"
-                    :key="level"
-                    :label="level"
-                    :value="level"
-                  />
-                </el-select>
-              </el-form-item>
-            </el-col>
-            <el-col :xs="24" :sm="12">
-              <el-form-item label="编制人数" prop="establishmentCount">
-                <el-input-number
-                  v-model="deptForm.establishmentCount"
-                  :min="0"
-                  :max="9999"
-                  controls-position="right"
-                  class="full-width"
-                />
-              </el-form-item>
-            </el-col>
-          </el-row>
-        </el-form>
+        <el-tabs
+          v-model="activeDeptIndex"
+          class="dept-tabs"
+        >
+          <el-tab-pane
+            v-for="(item, index) in deptFormList"
+            :key="index"
+            :name="String(index)"
+          >
+            <template slot="label">
+              <span>{{ deptTabLabel(index) }}</span>
+              <i
+                v-if="deptFormList.length > 1"
+                class="el-icon-close dept-tab-close"
+                @click.stop="removeDeptTab(index)"
+              />
+            </template>
+            <el-form :model="item" label-position="top">
+              <el-row :gutter="20">
+                <el-col :xs="24" :sm="12">
+                  <el-form-item label="岗位名称">
+                    <el-select
+                      v-model="item.positionId"
+                      filterable
+                      clearable
+                      placeholder="请选择岗位"
+                      class="full-width"
+                      @change="onPositionChange(index)"
+                    >
+                      <el-option
+                        v-for="position in positions"
+                        :key="position.id"
+                        :label="position.positionName"
+                        :value="position.id"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :xs="24" :sm="12">
+                  <el-form-item label="岗位层级">
+                    <el-select
+                      v-model="item.positionLevel"
+                      clearable
+                      placeholder="请选择岗位层级"
+                      class="full-width"
+                      @change="onLevelChange(index)"
+                    >
+                      <el-option
+                        v-for="level in getLevelOptions(index)"
+                        :key="level"
+                        :label="level"
+                        :value="level"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :xs="24" :sm="12">
+                  <el-form-item label="编制人数">
+                    <el-input-number
+                      v-model="item.establishmentCount"
+                      :min="0"
+                      :max="9999"
+                      controls-position="right"
+                      class="full-width"
+                    />
+                  </el-form-item>
+                </el-col>
+              </el-row>
+            </el-form>
+          </el-tab-pane>
+        </el-tabs>
       </template>
       </el-tab-pane>
 
@@ -341,6 +371,8 @@ import {
   getOrganization,
   getOrganizationCode,
   listOrganizations,
+  pageDepartmentPosition,
+  saveBatchDepartmentPosition,
   updateDepartmentPosition,
   updateOrganization,
 } from "@/api/organization";
@@ -362,6 +394,7 @@ const defaultForm = () => ({
 });
 
 const defaultDeptForm = () => ({
+  id: null,
   positionId: null,
   positionLevel: "",
   establishmentCount: 0,
@@ -398,7 +431,9 @@ export default {
     return {
       form: defaultForm(),
       enterprise: defaultEnterprise(),
-      deptForm: defaultDeptForm(),
+      // 部门信息:支持新增/删除 tab 项,至少保留一项
+      deptFormList: [defaultDeptForm()],
+      activeDeptIndex: "0",
       saving: false,
       loadingDetail: false,
       activeTab: "organization",
@@ -418,13 +453,6 @@ export default {
     showDepartment() {
       return String(this.form.type) === "30";
     },
-    // 岗位层级:随所选岗位联动(取岗位的 positionLevels)
-    levelOptions() {
-      const position = this.positions.find(
-        (item) => String(item.id) === String(this.deptForm.positionId),
-      );
-      return position?.positionLevels || [];
-    },
     firstManagerName() {
       const user = this.form.managerFirst?.[0];
       if (!user) return '';
@@ -484,7 +512,8 @@ export default {
       this.activeTab = "organization";
       this.form = defaultForm();
       this.enterprise = defaultEnterprise();
-      this.deptForm = defaultDeptForm();
+      this.deptFormList = [defaultDeptForm()];
+      this.activeDeptIndex = "0";
       this.form.parentId = this.parentId || null;
       this.loadPositions();
       if (this.isUpdate) {
@@ -505,6 +534,14 @@ export default {
               ? detail.enterprise.businessLicenseFile
               : [],
           };
+          // 部门类型:加载已有的部门岗位编制
+          if (String(this.form.type) === "30") {
+            console.log(detail,'detail')
+            await this.loadDepartmentPositions(detail.id);
+          } else {
+            this.deptFormList = [defaultDeptForm()];
+            this.activeDeptIndex = "0";
+          }
         } catch (error) {
           this.$message.error(error.message || "组织详情加载失败");
         } finally {
@@ -558,26 +595,121 @@ export default {
         this.positions = [];
       }
     },
+    // 加载部门已有的岗位编制(编辑时回填 deptFormList)
+    async loadDepartmentPositions(deptId) {
+      try {
+        const result = await pageDepartmentPosition({
+          deptId,
+          pageNum: 1,
+          size: 1000,
+          sortName: "createTime",
+          orderBy: "ascending",
+        });
+        const list = Array.isArray(result?.list) ? result.list : [];
+        if (list.length === 0) {
+          this.deptFormList = [defaultDeptForm()];
+          this.activeDeptIndex = "0";
+          return;
+        }
+        this.deptFormList = list.map((item) => ({
+          id: item.id ?? null,
+          positionId: item.positionId ?? null,
+          positionLevel: item.positionLevel || "",
+          establishmentCount: Number(item.establishmentCount) || 0,
+        }));
+        this.activeDeptIndex = "0";
+      } catch (error) {
+        this.deptFormList = [defaultDeptForm()];
+        this.activeDeptIndex = "0";
+        this.$message.error(error.message || "部门岗位编制加载失败");
+      }
+    },
     // 切换岗位时清空已选层级(联动)
-    onPositionChange() {
-      this.deptForm.positionLevel = "";
+    onPositionChange(index) {
+      if (this.deptFormList[index]) {
+        this.deptFormList[index].positionLevel = "";
+      }
+    },
+    // 切换岗位层级时校验:同岗位下层级不能重复
+    onLevelChange(index) {
+      const item = this.deptFormList[index];
+      if (!item || !item.positionId || !item.positionLevel) {
+        return;
+      }
+      const duplicate = this.deptFormList.some((other, idx) => {
+        if (idx === index) return false;
+        return (
+          String(other.positionId) === String(item.positionId) &&
+          String(other.positionLevel) === String(item.positionLevel)
+        );
+      });
+      if (duplicate) {
+        this.$message.warning("同一岗位下岗位层级不能重复,请重新选择");
+        item.positionLevel = "";
+      }
     },
     positionLabel(positionId) {
       return this.positions.find((item) => String(item.id) === String(positionId))
         ?.positionName || "";
     },
+    // 新增一个部门信息 tab 项
+    addDeptTab() {
+      this.deptFormList.push(defaultDeptForm());
+      this.activeDeptIndex = String(this.deptFormList.length - 1);
+    },
+    // 删除指定 tab 项(至少保留一项)
+    removeDeptTab(index) {
+      if (this.deptFormList.length <= 1) {
+        this.$message.warning("部门信息至少保留一项");
+        return;
+      }
+      this.deptFormList.splice(index, 1);
+      if (
+        Number(this.activeDeptIndex) >= this.deptFormList.length
+      ) {
+        this.activeDeptIndex = String(this.deptFormList.length - 1);
+      }
+    },
+    // tab 标题:已选岗位则显示岗位名,否则按序号显示
+    deptTabLabel(index) {
+      const item = this.deptFormList[index];
+      if (item && item.positionId) {
+        return this.positionLabel(item.positionId) || `岗位信息 ${index + 1}`;
+      }
+      return `岗位信息 ${index + 1}`;
+    },
+    // 岗位层级:随所选岗位联动(取岗位的 positionLevels)
+    getLevelOptions(index) {
+      const item = this.deptFormList[index];
+      if (!item) return [];
+      const position = this.positions.find(
+        (pos) => String(pos.id) === String(item.positionId),
+      );
+      return position?.positionLevels || [];
+    },
     validateDepartment() {
       if (!this.showDepartment) {
         return Promise.resolve(true);
       }
-      if (!this.deptForm.positionId) {
-        this.$message.warning("部门类型请选择岗位名称");
-        return Promise.resolve(false);
+      // 同岗位下岗位层级不能重复
+      const seen = new Set();
+      for (const item of this.deptFormList) {
+        if (item && item.positionId && item.positionLevel) {
+          const key = `${item.positionId}__${item.positionLevel}`;
+          if (seen.has(key)) {
+            this.$message.warning("同一岗位下岗位层级不能重复");
+            return Promise.resolve(false);
+          }
+          seen.add(key);
+        }
       }
       return Promise.resolve(true);
     },
-    // 保存部门岗位关联(PUT /fm/tmaindepartmentposition/updateBatch)
-    async saveDepartmentPosition(payload, result) {
+    // 保存部门岗位关联
+    // 新增组织(isCreate=true)→ 全部走 saveBatch
+    // 更新组织(isCreate=false)→ 按是否有 id 拆分:
+    //   有 id 的项 → updateBatch;新增的 tab(无 id)→ saveBatch
+    async saveDepartmentPosition(payload, result, isCreate) {
       let deptId = payload.id || this.data?.id;
       if (!deptId && result && typeof result === "object") {
         deptId = result.id;
@@ -598,18 +730,42 @@ export default {
         this.$message.warning("组织已保存,但未能获取组织ID,部门岗位编制未保存");
         return;
       }
-      const position = this.positions.find(
-        (item) => String(item.id) === String(this.deptForm.positionId),
-      );
-      await updateDepartmentPosition([
-        {
+      const buildItem = (item) => {
+        const position = this.positions.find(
+          (pos) => String(pos.id) === String(item.positionId),
+        );
+        const base = {
           deptId,
-          positionId: this.deptForm.positionId,
-          positionLevel: this.deptForm.positionLevel || position?.positionLevels?.[0] || "",
+          positionId: item.positionId,
+          positionLevel:
+            item.positionLevel || position?.positionLevels?.[0] || "",
           positionName: position?.positionName || "",
-          establishmentCount: Number(this.deptForm.establishmentCount) || 0,
-        },
-      ]);
+          establishmentCount: Number(item.establishmentCount) || 0,
+        };
+        // updateBatch 需要主键 id
+        if (item.id) {
+          base.id = item.id;
+        }
+        return base;
+      };
+      const items = (this.deptFormList || [])
+        .filter((item) => item && item.positionId)
+        .map(buildItem);
+      if (items.length === 0) {
+        return;
+      }
+      if (isCreate) {
+        await saveBatchDepartmentPosition(items);
+      } else {
+        const existing = items.filter((item) => item.id);
+        const fresh = items.filter((item) => !item.id);
+        if (existing.length > 0) {
+          await updateDepartmentPosition(existing);
+        }
+        if (fresh.length > 0) {
+          await saveBatchDepartmentPosition(fresh);
+        }
+      }
       this.$message.success("部门岗位编制已保存");
     },
     beforeLicenseUpload(file) {
@@ -701,9 +857,17 @@ export default {
               : result?.message || "保存成功",
           );
           // 部门类型:同步保存部门岗位关联(编制人数)
-          if (this.showDepartment && this.deptForm.positionId) {
+          if (
+            this.showDepartment &&
+            this.deptFormList.some((item) => item.positionId)
+          ) {
             try {
-              await this.saveDepartmentPosition(payload, result);
+              // 新增组织走 saveBatch,更新组织走 updateBatch
+              await this.saveDepartmentPosition(
+                payload,
+                result,
+                !this.isUpdate,
+              );
             } catch (error) {
               this.$message.warning(
                 "组织已保存,但部门岗位编制保存失败:" +
@@ -723,7 +887,8 @@ export default {
     reset() {
       this.form = defaultForm();
       this.enterprise = defaultEnterprise();
-      this.deptForm = defaultDeptForm();
+      this.deptFormList = [defaultDeptForm()];
+      this.activeDeptIndex = "0";
       this.activeTab = "organization";
       this.parentCheckedKeys = [];
       this.$refs.form?.clearValidate();