Lin Qilong 2 天之前
父节点
当前提交
536181f3b5
共有 35 个文件被更改,包括 923 次插入168 次删除
  1. 5 0
      gw-admin/src/main/java/com/goldenwater/web/controller/system/ObjTendController.java
  2. 4 0
      gw-common/src/main/java/com/goldenwater/common/core/domain/entity/AttPersExt.java
  3. 3 0
      gw-common/src/main/java/com/goldenwater/common/core/domain/entity/ObjTend.java
  4. 3 0
      gw-common/src/main/java/com/goldenwater/common/core/domain/obj/ObjEng.java
  5. 5 0
      gw-slaj/src/main/java/com/goldenwater/slaj/hidd/controller/ObjEngController.java
  6. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngCwsMapper.xml
  7. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngDikeMapper.xml
  8. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngHystMapper.xml
  9. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngIrrMapper.xml
  10. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngOtheMapper.xml
  11. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngPustMapper.xml
  12. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngResMapper.xml
  13. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngSdMapper.xml
  14. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngWadiMapper.xml
  15. 1 1
      gw-slaj/src/main/resources/mapper/slaj/eng/AttEngWagaMapper.xml
  16. 1 1
      gw-slaj/src/main/resources/mapper/slaj/hidd/AttOrgExtMapper.xml
  17. 17 6
      gw-slaj/src/main/resources/mapper/slaj/hidd/ObjEngMapper.xml
  18. 8 0
      gw-system/src/main/java/com/goldenwater/system/mapper/SysRoleDeptMapper.java
  19. 21 0
      gw-system/src/main/java/com/goldenwater/system/service/IDataScopeService.java
  20. 9 0
      gw-system/src/main/java/com/goldenwater/system/service/impl/AttPersExtServiceImpl.java
  21. 127 0
      gw-system/src/main/java/com/goldenwater/system/service/impl/DataScopeServiceImpl.java
  22. 10 2
      gw-system/src/main/resources/mapper/system/AttPersExtMapper.xml
  23. 9 0
      gw-system/src/main/resources/mapper/system/ObjTendMapper.xml
  24. 4 0
      gw-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml
  25. 12 4
      gw-ui/src/components/AdSelect/index.vue
  26. 1 1
      gw-ui/src/views/slaj/eng/attengcws/index.vue
  27. 3 4
      gw-ui/src/views/slaj/eng/attengdike/index.vue
  28. 1 1
      gw-ui/src/views/slaj/eng/attenghyst/index.vue
  29. 3 3
      gw-ui/src/views/slaj/eng/attengothe/index.vue
  30. 1 1
      gw-ui/src/views/slaj/eng/attengpust/index.vue
  31. 8 4
      gw-ui/src/views/slaj/eng/attengres/index.vue
  32. 4 4
      gw-ui/src/views/slaj/eng/attengsd/index.vue
  33. 4 4
      gw-ui/src/views/slaj/eng/attengwaga/index.vue
  34. 4 0
      gw-ui/src/views/system/orgext/index.vue
  35. 646 123
      gw-ui/src/views/system/projbase/tend/index.vue

+ 5 - 0
gw-admin/src/main/java/com/goldenwater/web/controller/system/ObjTendController.java

@@ -12,6 +12,7 @@ import com.goldenwater.common.core.domain.vo.ObjTendDetailVO;
 import com.goldenwater.common.core.page.TableDataInfo;
 import com.goldenwater.common.enums.BusinessType;
 import com.goldenwater.system.service.IObjTendService;
+import com.goldenwater.system.service.IDataScopeService;
 
 @RestController
 @RequestMapping("/business/objtend")
@@ -20,9 +21,13 @@ public class ObjTendController extends BaseController
     @Autowired
     private IObjTendService objTendService;
 
+    @Autowired
+    private IDataScopeService dataScopeService;
+
     @PreAuthorize("@ss.hasPermi('business:objtend:list')")
     @GetMapping("/list")
     public TableDataInfo list(ObjTend objTend) {
+        objTend.setOrgGuids(dataScopeService.resolveVisibleOrgGuids("business:objtend:list"));
         startPage();
         List<ObjTendDetailVO> list = objTendService.selectObjTendDetailList(objTend);
         return getDataTable(list);

+ 4 - 0
gw-common/src/main/java/com/goldenwater/common/core/domain/entity/AttPersExt.java

@@ -2,6 +2,7 @@ package com.goldenwater.common.core.domain.entity;
 
 import java.util.Date;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
@@ -30,6 +31,7 @@ public class AttPersExt
     private String sex;
 
     /** 出生日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
     private Date bornDate;
 
     /** 累计从事监理工作时间 */
@@ -39,9 +41,11 @@ public class AttPersExt
     private String note;
 
     /** 采集时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date collTime;
 
     /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date updTime;
 
     /** 记录人员GUID */

+ 3 - 0
gw-common/src/main/java/com/goldenwater/common/core/domain/entity/ObjTend.java

@@ -4,6 +4,7 @@ package com.goldenwater.common.core.domain.entity;
 import lombok.Data;
 
 import java.util.Date;
+import java.util.List;
 
 /**
  * 标段对象 obj_tend
@@ -44,4 +45,6 @@ public class ObjTend {
     private Date toDate;
     /** 完工状态:2=已完工 */
     private String stat;
+    /** 可见机构GUID集合(仅查询用:数据权限过滤,非 obj_tend 表字段) */
+    private List<String> orgGuids;
 }

+ 3 - 0
gw-common/src/main/java/com/goldenwater/common/core/domain/obj/ObjEng.java

@@ -2,6 +2,7 @@ package com.goldenwater.common.core.domain.obj;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class ObjEng {
@@ -29,4 +30,6 @@ public class ObjEng {
     private String oldCode;
     /** 管理模式 */
     private String manageMode;
+    /** 可见机构GUID集合(仅查询用:数据权限过滤,非 OBJ_ENG 表字段) */
+    private List<String> orgGuids;
 }

+ 5 - 0
gw-slaj/src/main/java/com/goldenwater/slaj/hidd/controller/ObjEngController.java

@@ -9,6 +9,7 @@ import com.goldenwater.common.core.controller.BaseController;
 import com.goldenwater.common.core.domain.AjaxResult;
 import com.goldenwater.common.core.page.TableDataInfo;
 import com.goldenwater.slaj.hidd.service.IObjEngService;
+import com.goldenwater.system.service.IDataScopeService;
 
 /**
  * 工程信息
@@ -19,8 +20,12 @@ public class ObjEngController extends BaseController {
     @Autowired
     private IObjEngService objEngService;
 
+    @Autowired
+    private IDataScopeService dataScopeService;
+
     @GetMapping("/list")
     public TableDataInfo list(ObjEng objEng) {
+        objEng.setOrgGuids(dataScopeService.resolveVisibleOrgGuids(null));
         startPage();
         List<ObjEng> list = objEngService.selectObjEngList(objEng);
         return getDataTable(list);

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngCwsMapper.xml

@@ -43,7 +43,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngCwsResult">
-        <include refid="selectAttEngCwsVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngCwsVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngCws" resultMap="AttEngCwsResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngDikeMapper.xml

@@ -88,7 +88,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngDikeResult">
-        <include refid="selectAttEngDikeVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngDikeVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngDike" resultMap="AttEngDikeResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngHystMapper.xml

@@ -51,7 +51,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngHystResult">
-        <include refid="selectAttEngHystVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngHystVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngHyst" resultMap="AttEngHystResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngIrrMapper.xml

@@ -54,7 +54,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngIrrResult">
-        <include refid="selectAttEngIrrVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngIrrVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngIrr" resultMap="AttEngIrrResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngOtheMapper.xml

@@ -42,7 +42,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngOtheResult">
-        <include refid="selectAttEngOtheVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngOtheVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngOthe" resultMap="AttEngOtheResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngPustMapper.xml

@@ -46,7 +46,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngPustResult">
-        <include refid="selectAttEngPustVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngPustVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngPust" resultMap="AttEngPustResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngResMapper.xml

@@ -54,7 +54,7 @@
     </sql>
 
     <select id="selectAttEngResByGuid" parameterType="String" resultMap="AttEngResResult">
-        <include refid="selectAttEngResVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngResVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectAttEngResList" parameterType="com.goldenwater.slaj.eng.domain.AttEngRes" resultMap="AttEngResResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngSdMapper.xml

@@ -43,7 +43,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngSdResult">
-        <include refid="selectAttEngSdVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngSdVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngSd" resultMap="AttEngSdResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngWadiMapper.xml

@@ -46,7 +46,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngWadiResult">
-        <include refid="selectAttEngWadiVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngWadiVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngWadi" resultMap="AttEngWadiResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/eng/AttEngWagaMapper.xml

@@ -45,7 +45,7 @@
     </sql>
 
     <select id="selectByGuid" parameterType="String" resultMap="AttEngWagaResult">
-        <include refid="selectAttEngWagaVo"/> where o.guid = #{guid}
+        <include refid="selectAttEngWagaVo"/> where a.guid = #{guid}
     </select>
 
     <select id="selectList" parameterType="com.goldenwater.slaj.eng.domain.AttEngWaga" resultMap="AttEngWagaResult">

+ 1 - 1
gw-slaj/src/main/resources/mapper/slaj/hidd/AttOrgExtMapper.xml

@@ -162,7 +162,7 @@
                  left join sys_dept d on d.dept_id = e.org_guid
                  left join sys_dept pd on pd.dept_id = d.parent_id
                  left join att_org_base b on b.guid = e.org_guid
-        where e.org_guid = #{guid}
+        where e.guid = #{guid}
     </select>
     <select id="selectAttOrgExtList" parameterType="com.goldenwater.slaj.hidd.domain.AttOrgExt"
             resultMap="AttOrgExtResult">

+ 17 - 6
gw-slaj/src/main/resources/mapper/slaj/hidd/ObjEngMapper.xml

@@ -15,9 +15,9 @@
         <result property="manageMode" column="manage_mode"/>
     </resultMap>
     <sql id="selectObjEngVo">
-        select guid, eng_code, eng_name, eng_type, from_date, to_date, pub_guid, org_jurd,
-               org_rest, old_code, manage_mode
-        from obj_eng
+        select o.guid, o.eng_code, o.eng_name, o.eng_type, o.from_date, o.to_date, o.pub_guid, o.org_jurd,
+               o.org_rest, o.old_code, o.manage_mode
+        from obj_eng o
     </sql>
     <select id="selectObjEngByGuid" parameterType="String" resultMap="ObjEngResult">
         <include refid="selectObjEngVo"/>
@@ -26,9 +26,20 @@
     <select id="selectObjEngList" parameterType="com.goldenwater.common.core.domain.obj.ObjEng" resultMap="ObjEngResult">
         <include refid="selectObjEngVo"/>
         <where>
-            <if test="engCode != null and engCode != ''">AND eng_code like '%' || #{engCode} || '%'</if>
-            <if test="engName != null and engName != ''">AND eng_name like '%' || #{engName} || '%'</if>
-            <if test="engType != null and engType != ''">AND eng_type = #{engType}</if>
+            <if test="engCode != null and engCode != ''">AND o.eng_code like '%' || #{engCode} || '%'</if>
+            <if test="engName != null and engName != ''">AND o.eng_name like '%' || #{engName} || '%'</if>
+            <if test="engType != null and engType != ''">AND o.eng_type = #{engType}</if>
+            <if test="orgGuids != null and orgGuids.size() > 0">
+                AND (
+                    EXISTS (SELECT 1 FROM rel_eng_org r WHERE r.eng_guid = o.guid AND r.org_guid IN
+                        <foreach collection="orgGuids" item="orgGuid" open="(" separator="," close=")">#{orgGuid}</foreach>)
+                    OR EXISTS (SELECT 1 FROM att_eng_base b WHERE b.eng_guid = o.guid AND b.org_guid IN
+                        <foreach collection="orgGuids" item="orgGuid" open="(" separator="," close=")">#{orgGuid}</foreach>)
+                )
+            </if>
+            <if test="orgGuids != null and orgGuids.size() == 0">
+                AND 1 = 0
+            </if>
         </where>
     </select>
     <insert id="insertObjEng" parameterType="com.goldenwater.common.core.domain.obj.ObjEng">

+ 8 - 0
gw-system/src/main/java/com/goldenwater/system/mapper/SysRoleDeptMapper.java

@@ -34,6 +34,14 @@ public interface SysRoleDeptMapper
      */
     public int selectCountRoleDeptByDeptId(String deptId);
 
+    /**
+     * 查询角色数据权限(自定义范围)对应的部门ID集合
+     *
+     * @param roleId 角色ID
+     * @return 部门ID集合
+     */
+    public List<String> selectDeptIdsByRoleId(String roleId);
+
     /**
      * 批量新增角色部门信息
      *

+ 21 - 0
gw-system/src/main/java/com/goldenwater/system/service/IDataScopeService.java

@@ -0,0 +1,21 @@
+package com.goldenwater.system.service;
+
+import java.util.List;
+
+/**
+ * 数据权限解析服务:按当前用户角色的数据权限配置(sys_role.data_scope)
+ * 解析可见机构GUID集合,供业务表(obj_tend / obj_eng 等)查询过滤使用。
+ *
+ * @author goldenwater
+ */
+public interface IDataScopeService
+{
+    /**
+     * 解析当前登录用户可见的机构GUID列表。
+     *
+     * @param permission 权限字符,仅统计拥有该权限的角色参与数据范围计算;
+     *                   null/空 表示不按权限字符过滤角色
+     * @return 可见机构GUID列表;返回 null 表示不限制(超级管理员或“全部数据权限”)
+     */
+    List<String> resolveVisibleOrgGuids(String permission);
+}

+ 9 - 0
gw-system/src/main/java/com/goldenwater/system/service/impl/AttPersExtServiceImpl.java

@@ -1,9 +1,14 @@
 package com.goldenwater.system.service.impl;
 
+import java.util.Date;
 import java.util.List;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+
 import com.goldenwater.common.core.domain.entity.AttPersExt;
+import com.goldenwater.common.utils.SecurityUtils;
+import com.goldenwater.common.utils.uuid.IdUtils;
 import com.goldenwater.system.mapper.AttPersExtMapper;
 import com.goldenwater.system.service.IAttPersExtService;
 
@@ -30,11 +35,15 @@ public class AttPersExtServiceImpl implements IAttPersExtService
 
     @Override
     public int insertAttPersExt(AttPersExt attPersExt) {
+        attPersExt.setGuid(IdUtils.fastSimpleUUID());
+        attPersExt.setCollTime(new Date());
+        attPersExt.setRecPers(String.valueOf(SecurityUtils.getUserId()));
         return attPersExtMapper.insertAttPersExt(attPersExt);
     }
 
     @Override
     public int updateAttPersExt(AttPersExt attPersExt) {
+        attPersExt.setUpdTime(new Date());
         return attPersExtMapper.updateAttPersExt(attPersExt);
     }
 

+ 127 - 0
gw-system/src/main/java/com/goldenwater/system/service/impl/DataScopeServiceImpl.java

@@ -0,0 +1,127 @@
+package com.goldenwater.system.service.impl;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.goldenwater.common.constant.Constants;
+import com.goldenwater.common.constant.UserConstants;
+import com.goldenwater.common.core.domain.entity.SysDept;
+import com.goldenwater.common.core.domain.entity.SysRole;
+import com.goldenwater.common.core.domain.entity.SysUser;
+import com.goldenwater.common.core.domain.model.LoginUser;
+import com.goldenwater.common.core.text.Convert;
+import com.goldenwater.common.utils.SecurityUtils;
+import com.goldenwater.common.utils.StringUtils;
+import com.goldenwater.system.mapper.SysDeptMapper;
+import com.goldenwater.system.mapper.SysRoleDeptMapper;
+import com.goldenwater.system.service.IDataScopeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 数据权限解析服务实现(PostgreSQL)
+ *
+ * 与 RuoYi DataScopeAspect 语义一致:
+ * 1=全部数据权限、2=自定义数据权限、3=本部门数据权限、4=本部门及以下数据权限、5=仅本人数据权限。
+ * 业务数据按“机构”(dept_id / org_guid)归属,因此统一解析为可见机构GUID集合;
+ * 5=仅本人 在单位维度业务数据上没有“本人”归属概念,按可见范围为空(不返回任何数据)处理。
+ *
+ * @author goldenwater
+ */
+@Service
+public class DataScopeServiceImpl implements IDataScopeService
+{
+    @Autowired
+    private SysDeptMapper deptMapper;
+
+    @Autowired
+    private SysRoleDeptMapper roleDeptMapper;
+
+    @Override
+    public List<String> resolveVisibleOrgGuids(String permission)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (loginUser == null)
+        {
+            return null;
+        }
+        SysUser user = loginUser.getUser();
+        // 超级管理员不过滤
+        if (user == null || user.isAdmin())
+        {
+            return null;
+        }
+        List<SysRole> roles = user.getRoles();
+        if (roles == null || roles.isEmpty())
+        {
+            // 与 RuoYi 行为一致:无角色参与时不返回任何数据
+            return new ArrayList<>();
+        }
+        Set<String> orgGuids = new LinkedHashSet<>();
+        boolean qualified = false;
+        for (SysRole role : roles)
+        {
+            // 停用角色不参与
+            if (UserConstants.ROLE_DISABLE.equals(role.getStatus()))
+            {
+                continue;
+            }
+            // 未携带该权限字符的角色不参与
+            if (StringUtils.isNotEmpty(permission)
+                    && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
+            {
+                continue;
+            }
+            String dataScope = role.getDataScope();
+            if (StringUtils.isEmpty(dataScope))
+            {
+                continue;
+            }
+            qualified = true;
+            if (Constants.Dept.DATA_SCOPE_ALL.equals(dataScope))
+            {
+                // 任一角色为“全部数据权限”即不限
+                return null;
+            }
+            else if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(dataScope))
+            {
+                List<String> deptIds = roleDeptMapper.selectDeptIdsByRoleId(role.getRoleId());
+                if (deptIds != null)
+                {
+                    orgGuids.addAll(deptIds);
+                }
+            }
+            else if (Constants.Dept.DATA_SCOPE_DEPT.equals(dataScope))
+            {
+                if (user.getDeptId() != null)
+                {
+                    orgGuids.add(String.valueOf(user.getDeptId()));
+                }
+            }
+            else if (Constants.Dept.DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+            {
+                if (user.getDeptId() != null)
+                {
+                    String deptId = String.valueOf(user.getDeptId());
+                    orgGuids.add(deptId);
+                    List<SysDept> children = deptMapper.selectChildrenDeptById(deptId);
+                    if (children != null)
+                    {
+                        for (SysDept child : children)
+                        {
+                            if (child.getDeptId() != null)
+                            {
+                                orgGuids.add(child.getDeptId());
+                            }
+                        }
+                    }
+                }
+            }
+            // 5=仅本人:单位维度业务数据无“本人”归属,不加入任何机构,等价于无可见数据
+        }
+        // 无任何有效角色参与时,与 RuoYi 行为一致:不返回任何数据
+        return qualified ? new ArrayList<>(orgGuids) : new ArrayList<>();
+    }
+}

+ 10 - 2
gw-system/src/main/resources/mapper/system/AttPersExtMapper.xml

@@ -40,6 +40,8 @@
 
     <insert id="insertAttPersExt" parameterType="AttPersExt">
         insert into ATT_PERS_EXT(
+        <trim suffixOverrides=",">
+            <if test="guid != null">GUID,</if>
             <if test="persName != null">PERS_NAME,</if>
             <if test="tendGuid != null">TEND_GUID,</if>
             <if test="admDuty != null">ADM_DUTY,</if>
@@ -47,8 +49,12 @@
             <if test="bornDate != null">BORN_DATE,</if>
             <if test="workDate != null">WORK_DATE,</if>
             <if test="note != null">NOTE,</if>
-            <if test="recPers != null">REC_PERS</if>
+            <if test="recPers != null">REC_PERS,</if>
+            <if test="collTime != null">COLL_TIME,</if>
+        </trim>
         )values(
+        <trim suffixOverrides=",">
+            <if test="guid != null">#{guid},</if>
             <if test="persName != null">#{persName},</if>
             <if test="tendGuid != null">#{tendGuid},</if>
             <if test="admDuty != null">#{admDuty},</if>
@@ -56,7 +62,9 @@
             <if test="bornDate != null">#{bornDate},</if>
             <if test="workDate != null">#{workDate},</if>
             <if test="note != null">#{note},</if>
-            <if test="recPers != null">#{recPers}</if>
+            <if test="recPers != null">#{recPers},</if>
+            <if test="collTime != null">#{collTime}</if>
+        </trim>
         )
     </insert>
 

+ 9 - 0
gw-system/src/main/resources/mapper/system/ObjTendMapper.xml

@@ -59,6 +59,15 @@
             <if test="engGuid != null and engGuid != ''">AND t.eng_guid = #{engGuid}</if>
             <if test="projGuid != null and projGuid != ''">AND eb.proj_guid = #{projGuid}</if>
             <if test="tendName != null and tendName != ''">AND t.tend_name like '%' || #{tendName} || '%'</if>
+            <if test="orgGuids != null and orgGuids.size() > 0">
+                AND t.org_guid IN
+                <foreach collection="orgGuids" item="orgGuid" open="(" separator="," close=")">
+                    #{orgGuid}
+                </foreach>
+            </if>
+            <if test="orgGuids != null and orgGuids.size() == 0">
+                AND 1 = 0
+            </if>
         </where>
         order by t.guid
     </select>

+ 4 - 0
gw-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml

@@ -17,6 +17,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	    select count(1) from sys_role_dept where dept_id=#{deptId}
 	</select>
 
+	<select id="selectDeptIdsByRoleId" parameterType="String" resultType="String">
+	    select dept_id from sys_role_dept where role_id = #{roleId}
+	</select>
+
 	<delete id="deleteRoleDept" parameterType="String">
  		delete from sys_role_dept where role_id in
  		<foreach collection="ids" item="roleId" open="(" separator="," close=")">

+ 12 - 4
gw-ui/src/components/AdSelect/index.vue

@@ -14,7 +14,7 @@
       clearable
       check-strictly
       :render-after-expand="false"
-      cache-data
+      :cache-data="cacheData"
       :disabled="disabled"
       :placeholder="placeholder"
       style="width: 100%"
@@ -24,7 +24,7 @@
 </template>
 
 <script setup>
-import { watch } from 'vue'
+import { ref, watch } from 'vue'
 import { listAdChildren, getAttAdBase } from '@/api/system/attAdBase'
 
 const props = defineProps({
@@ -63,10 +63,18 @@ function handleChange(val) {
   emit('update:modelValue', val)
 }
 
-// 编辑回显:modelValue 变化时获取节点信息
+// 编辑回显:懒加载模式下将已选节点放入 cache-data,
+// 否则选中的行政区划在未展开节点前无法显示名称
+const cacheData = ref([])
 watch(() => props.modelValue, (val) => {
+  cacheData.value = []
   if (val) {
-    getAttAdBase(val).catch(() => {})
+    getAttAdBase(val).then(res => {
+      const d = res.data
+      if (d) {
+        cacheData.value = [{ guid: d.guid || val, adName: d.adName || val, children: [] }]
+      }
+    }).catch(() => {})
   }
 }, { immediate: true })
 </script>

+ 1 - 1
gw-ui/src/views/slaj/eng/attengcws/index.vue

@@ -77,7 +77,7 @@
             <dict-tag :options="slaj_eng_scal_cws" :value="scope.row.engScal" />
           </template>
         </el-table-column>
-        <el-table-column label="水净化处理" align="center" v-if="columns.watePuri.visible">
+        <el-table-column label="水净化处理" align="center" v-if="columns.watePuri.visible" width="100">
           <template #default="scope">
             <dict-tag :options="slaj_wate_puri" :value="scope.row.watePuri" />
           </template>

+ 3 - 4
gw-ui/src/views/slaj/eng/attengdike/index.vue

@@ -117,12 +117,11 @@
                       :value="scope.row.desFlStand"/>
           </template>
         </el-table-column>
-        <el-table-column label="起点位置" prop="startLoc" align="center" v-if="columns.startLoc.visible"/>
-        <el-table-column label="终点位置" prop="endLoc" align="center" v-if="columns.endLoc.visible"/>
+        <el-table-column label="起点位置" prop="startLoc" width="220" header-align="center" align="left" v-if="columns.startLoc.visible" :show-overflow-tooltip="true"/>
+        <el-table-column label="终点位置" prop="endLoc" width="220" header-align="center" align="left" v-if="columns.endLoc.visible" :show-overflow-tooltip="true"/>
         <el-table-column label="工程状态" align="center" v-if="columns.engStat.visible">
           <template #default="scope">
-            <dict-tag
-                :options="DPC_ENG_STAT" :value="scope.row.engStat"/>
+            <dict-tag :options="DPC_ENG_STAT" :value="scope.row.engStat"/>
           </template>
         </el-table-column>
         <el-table-column label="备注" prop="note" align="center" :show-overflow-tooltip="true"

+ 1 - 1
gw-ui/src/views/slaj/eng/attenghyst/index.vue

@@ -59,7 +59,7 @@
         <el-table-column label="电站代码" :min-width="150" align="center" v-if="columns.hystCode.visible">
           <template #default="scope">{{ scope.row.engCode || scope.row.hystCode }}</template>
         </el-table-column>
-        <el-table-column label="电站位置" :min-width="150" prop="hystSite" header-align="center" align="left" v-if="columns.hystSite.visible"/>
+        <el-table-column label="电站位置" :min-width="240" prop="hystSite" header-align="center" align="left" v-if="columns.hystSite.visible" :show-overflow-tooltip="true"/>
         <el-table-column label="电站类型" align="center" v-if="columns.hystType.visible">
           <template #default="scope">
             <dict-tag

+ 3 - 3
gw-ui/src/views/slaj/eng/attengothe/index.vue

@@ -54,8 +54,8 @@
 
       <el-table v-loading="loading" :data="dataList" @selection-change="handleSelect" height="calc(100% - 100px)">
         <el-table-column type="selection" width="55" align="center"/>
-        <el-table-column label="工程名称"  :min-width="220" prop="engName" header-align="center" align="left"/>
-        <el-table-column label="工程代码" :min-width="150" align="center">
+        <el-table-column label="工程名称"  :min-width="220" prop="engName" header-align="center" align="left" :show-overflow-tooltip="true"/>
+        <el-table-column label="工程代码" :min-width="170" align="center">
           <template #default="scope">{{ scope.row.engCode || scope.row.objCode }}</template>
         </el-table-column>
         <el-table-column label="工程位置" :min-width="220" prop="engLoc" header-align="center" align="left" :show-overflow-tooltip="true"/>
@@ -65,7 +65,7 @@
         <el-table-column label="自定义4" prop="custom4" align="center"/>
         <el-table-column label="自定义5" prop="custom5" align="center"/>
         <el-table-column label="自定义6" prop="custom6" align="center"/>
-        <el-table-column label="备注" prop="note" align="center"/>
+        <el-table-column label="备注" prop="note" align="center" :show-overflow-tooltip="true"/>
         <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width" fixed="right">
           <template #default="scope">
             <el-button link type="primary" @click="handleUpdate(scope.row)">修改</el-button>

+ 1 - 1
gw-ui/src/views/slaj/eng/attengpust/index.vue

@@ -61,7 +61,7 @@
         <el-table-column label="泵站代码" :min-width="150" align="center" v-if="columns.pustCode.visible">
           <template #default="scope">{{ scope.row.engCode || scope.row.pustCode }}</template>
         </el-table-column>
-        <el-table-column label="泵站位置" :min-width="240" prop="pustLoc" header-align="center" align="left" v-if="columns.pustLoc.visible"/>
+        <el-table-column label="泵站位置" :min-width="240" prop="pustLoc" header-align="center" align="left" v-if="columns.pustLoc.visible" :show-overflow-tooltip="true"/>
         <el-table-column label="泵站类型" align="center" v-if="columns.pustType.visible">
           <template #default="scope">
             <dict-tag

+ 8 - 4
gw-ui/src/views/slaj/eng/attengres/index.vue

@@ -47,7 +47,7 @@
         <el-table-column label="水库代码" width="150" align="center" v-if="columns.resCode.visible">
           <template #default="scope">{{ scope.row.engCode || scope.row.resCode }}</template>
         </el-table-column>
-        <el-table-column label="水库位置" width="240" prop="resLoc" align="left" header-align="center" v-if="columns.resLoc.visible" />
+        <el-table-column label="水库位置" width="240" prop="resLoc" align="left" header-align="center" v-if="columns.resLoc.visible"  :show-overflow-tooltip="true"/>
         <el-table-column label="中心经度(°)" prop="cenLong" align="center" v-if="columns.cenLong.visible" />
         <el-table-column label="中心纬度(°)" prop="cenLat" align="center" v-if="columns.cenLat.visible" />
         <el-table-column label="工程等级" align="center" v-if="columns.engGrad.visible">
@@ -459,9 +459,13 @@ function handleUpdate(row) {
   activeTab.value = 'basic';
   const g = row ? row.guid : ids.value[0];
   getAttEngRes(g).then(r => {
-    form.value = r.data;
-    open.value = true;
-    title.value = "修改水库工程"
+    if (r.data) {
+      form.value = r.data;
+      open.value = true;
+      title.value = "修改水库工程";
+    } else {
+      proxy.$modal.msgWarning("未找到该水库工程数据,无法编辑");
+    }
   })
 }
 

+ 4 - 4
gw-ui/src/views/slaj/eng/attengsd/index.vue

@@ -61,10 +61,10 @@
         <el-table-column label="淤地坝代码" :min-width="150" align="center" v-if="columns.sdCode.visible">
           <template #default="scope">{{ scope.row.engCode || scope.row.sdCode }}</template>
         </el-table-column>
-        <el-table-column label="淤地坝位置" :min-width="240" prop="sdLoc" header-align="center" align="left" v-if="columns.sdLoc.visible"/>
-        <el-table-column label="控制面积(km2)" prop="conArea" align="center" v-if="columns.conArea.visible"/>
-        <el-table-column label="总库容(104m3)" prop="totCap" align="center" v-if="columns.totCap.visible"/>
-        <el-table-column label="淤积库容(104m3)" width="130" prop="warpCap" align="center"
+        <el-table-column label="淤地坝位置" :min-width="240" prop="sdLoc" header-align="center" align="left" v-if="columns.sdLoc.visible" :show-overflow-tooltip="true"/>
+        <el-table-column label="控制面积(k㎡)" prop="conArea" width="110" align="center" v-if="columns.conArea.visible"/>
+        <el-table-column label="总库容(104m³)" prop="totCap" width="110" align="center" v-if="columns.totCap.visible"/>
+        <el-table-column label="淤积库容(104m³)" width="130" prop="warpCap" align="center"
                          v-if="columns.warpCap.visible"/>
         <el-table-column label="坝长(m)" prop="sdSizeLen" align="center" v-if="columns.sdSizeLen.visible"/>
         <el-table-column label="坝高(m)" prop="sdSizeHig" align="center" v-if="columns.sdSizeHig.visible"/>

+ 4 - 4
gw-ui/src/views/slaj/eng/attengwaga/index.vue

@@ -59,14 +59,14 @@
         <el-table-column label="水闸代码" :min-width="150" align="center" v-if="columns.wagaCode.visible">
           <template #default="scope">{{ scope.row.engCode || scope.row.wagaCode }}</template>
         </el-table-column>
-        <el-table-column label="水闸位置" :min-width="240" prop="wagaLoc" header-align="center" align="left" v-if="columns.wagaLoc.visible"/>
+        <el-table-column label="水闸位置" :min-width="240" prop="wagaLoc" header-align="center" align="left" v-if="columns.wagaLoc.visible" :show-overflow-tooltip="true"/>
         <el-table-column label="水闸类型" align="center" v-if="columns.wagaType.visible">
           <template #default="scope">
             <dict-tag
                 :options="slaj_waga_type" :value="scope.row.wagaType"/>
           </template>
         </el-table-column>
-        <el-table-column label="闸孔数量(孔)" prop="gaorNum" align="center" v-if="columns.gaorNum.visible"/>
+        <el-table-column label="闸孔数量(孔)" prop="gaorNum" width="110" align="center" v-if="columns.gaorNum.visible"/>
         <el-table-column label="工程等级" align="center" v-if="columns.engGrad.visible">
           <template #default="scope">
             <dict-tag
@@ -79,14 +79,14 @@
                 :options="slaj_eng_scal_waga" :value="scope.row.engScal"/>
           </template>
         </el-table-column>
-        <el-table-column label="过闸流量(m3/s)" prop="lockDisc" align="center" v-if="columns.lockDisc.visible"/>
+        <el-table-column label="过闸流量(m³/s)" width="120" prop="lockDisc" align="center" v-if="columns.lockDisc.visible"/>
         <el-table-column label="消能方式" align="center" v-if="columns.enerDiss.visible">
           <template #default="scope">
             <dict-tag
                 :options="slaj_ener_diss" :value="scope.row.enerDiss"/>
           </template>
         </el-table-column>
-        <el-table-column label="设计防洪标准(年)" prop="desFlSta" align="center" v-if="columns.desFlSta.visible"/>
+        <el-table-column label="设计防洪标准(年)" prop="desFlSta" width="150" align="center" v-if="columns.desFlSta.visible"/>
         <el-table-column label="备注" prop="note" align="center" :show-overflow-tooltip="true"
                          v-if="columns.note.visible"/>
         <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width" fixed="right">

+ 4 - 0
gw-ui/src/views/system/orgext/index.vue

@@ -719,6 +719,10 @@ function handleUpdate(row) {
   reset()
   const guid = row.guid || ids.value
   getAttOrgExt(guid).then(response => {
+    if (!response.data) {
+      proxy.$modal.msgError("未查询到该机构数据,可能已被删除")
+      return
+    }
     // 后端返回三表融合数据,前端直接展示
     form.value = response.data
     open.value = true

+ 646 - 123
gw-ui/src/views/system/projbase/tend/index.vue

@@ -4,12 +4,25 @@
       <template #header>
         <div class="card-header">
           <div class="header-left">
-            <el-icon :size="20" color="#409EFF"><List /></el-icon>
+            <el-icon :size="20" color="#409EFF">
+              <List/>
+            </el-icon>
             <span class="page-title">标段管理</span>
           </div>
           <div class="search-right">
-            <el-button type="primary" plain @click="handleAdd" v-hasPermi="['business:objtend:add']"><el-icon><Plus /></el-icon> 新增</el-button>
-            <el-button type="danger" plain :disabled="multiple" @click="handleDelete" v-hasPermi="['business:objtend:remove']"><el-icon><Delete /></el-icon> 删除</el-button>
+            <el-button type="primary" plain @click="handleAdd" v-hasPermi="['business:objtend:add']">
+              <el-icon>
+                <Plus/>
+              </el-icon>
+              新增
+            </el-button>
+            <el-button type="danger" plain :disabled="multiple" @click="handleDelete"
+                       v-hasPermi="['business:objtend:remove']">
+              <el-icon>
+                <Delete/>
+              </el-icon>
+              删除
+            </el-button>
           </div>
         </div>
       </template>
@@ -17,23 +30,37 @@
       <div class="search-bar" v-show="showSearch">
         <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
           <el-form-item label="标段名称" prop="tendName">
-            <el-input v-model="queryParams.tendName" placeholder="请输入标段名称" clearable @keyup.enter="handleQuery" />
+            <el-input v-model="queryParams.tendName" placeholder="请输入标段名称" clearable @keyup.enter="handleQuery"/>
           </el-form-item>
           <el-form-item>
-            <el-button type="primary" @click="handleQuery"><el-icon><Search /></el-icon> 搜索</el-button>
-            <el-button @click="resetQuery"><el-icon><Refresh /></el-icon> 重置</el-button>
+            <el-button type="primary" @click="handleQuery">
+              <el-icon>
+                <Search/>
+              </el-icon>
+              搜索
+            </el-button>
+            <el-button @click="resetQuery">
+              <el-icon>
+                <Refresh/>
+              </el-icon>
+              重置
+            </el-button>
           </el-form-item>
         </el-form>
       </div>
 
-      <el-table :height="tableHeight" v-loading="loading" :data="objTendList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="50" align="center" />
-        <el-table-column label="标段名称" align="center" prop="tendName" :show-overflow-tooltip="true" />
-        <el-table-column label="所属工程" align="center" prop="engName" :show-overflow-tooltip="true" min-width="140" />
-        <el-table-column label="所属单位" align="center" prop="orgName" :show-overflow-tooltip="true" min-width="140" />
-        <el-table-column label="负责人" align="center" prop="legPers" :show-overflow-tooltip="true" min-width="100" />
-        <el-table-column label="联系电话" align="center" prop="legTel" :show-overflow-tooltip="true" min-width="120" />
-        <el-table-column label="合同金额" align="center" prop="conAmout" :show-overflow-tooltip="true" min-width="120" />
+      <el-table height="calc(100% - 100px)" v-loading="loading" :data="objTendList"
+                @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="50" align="center"/>
+        <el-table-column label="标段名称" align="left" header-align="center" prop="tendName" width="200"
+                         :show-overflow-tooltip="true"/>
+        <el-table-column label="所属工程" align="left" header-align="center" prop="engName" width="200"
+                         :show-overflow-tooltip="true" min-width="140"/>
+        <el-table-column label="所属单位" align="left" header-align="center" prop="orgName" width="200"
+                         :show-overflow-tooltip="true" min-width="140"/>
+        <el-table-column label="负责人" align="center" prop="legPers" :show-overflow-tooltip="true" min-width="100"/>
+        <el-table-column label="联系电话" align="center" prop="legTel" :show-overflow-tooltip="true" min-width="120"/>
+        <el-table-column label="合同金额" align="center" prop="conAmout" :show-overflow-tooltip="true" min-width="120"/>
         <el-table-column label="完工状态" align="center" prop="stat" min-width="90">
           <template #default="scope">
             <el-tag v-if="scope.row.stat === '1'" type="warning">在建</el-tag>
@@ -41,30 +68,31 @@
             <span v-else>{{ scope.row.stat }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="备注" align="center" prop="note" :show-overflow-tooltip="true" min-width="120" />
-
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="260">
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="260" fixed="right">
           <template #default="scope">
             <el-button link type="primary" @click="handleUpdate(scope.row)"
-              v-hasPermi="['business:objtend:edit']">修改</el-button>
+                       v-hasPermi="['business:objtend:edit']">修改
+            </el-button>
             <el-button link type="success" @click="handleViewOrg(scope.row)"
-              v-hasPermi="['business:reltendorg:list']">参建单位</el-button>
+                       v-hasPermi="['business:reltendorg:list']">参建单位/成员
+            </el-button>
             <el-button link type="danger" @click="handleDelete(scope.row)"
-              v-hasPermi="['business:objtend:remove']">删除</el-button>
+                       v-hasPermi="['business:objtend:remove']">删除
+            </el-button>
           </template>
         </el-table-column>
       </el-table>
 
       <div class="pagination">
         <el-pagination
-          v-show="total > 0"
-          v-model:current-page="queryParams.pageNum"
-          v-model:page-size="queryParams.pageSize"
-          :total="total"
-          :page-sizes="[10, 20, 50, 100]"
-          layout="total, sizes, prev, pager, next, jumper"
-          @size-change="getList"
-          @current-change="getList"
+            v-show="total > 0"
+            v-model:current-page="queryParams.pageNum"
+            v-model:page-size="queryParams.pageSize"
+            :total="total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            @size-change="getList"
+            @current-change="getList"
         />
       </div>
     </el-card>
@@ -73,57 +101,65 @@
     <el-dialog :title="title" v-model="open" width="760px" append-to-body @closed="cancel">
       <el-form ref="objTendRef" :model="form" :rules="rules" label-width="120px" class="tend-form">
         <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="标段名称" prop="tendName">
-              <el-input v-model="form.tendName" placeholder="请输入标段名称" />
+          <el-col :span="24">
+            <el-form-item label="关联工程" prop="engGuid">
+              <div style="display:flex; width:100%; gap:8px;">
+                <el-input :model-value="engName" placeholder="点击右侧按钮选择工程" readonly clearable
+                          @clear="handleClearEng"/>
+                <el-button type="primary" plain @click="handleOpenEngDialog">
+                  <el-icon>
+                    <Search/>
+                  </el-icon>
+                  选择工程
+                </el-button>
+              </div>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <EngSelect v-model="form.engGuid" label="关联工程" prop="engGuid" placeholder="请输入工程名称搜索" required />
-          </el-col>
-          <el-col :span="12">
-            <ProjSelect v-model="form.orgProjDeptGuid" label="关联项目" prop="orgProjDeptGuid" placeholder="请输入项目名称搜索"
-              required />
+          <el-col :span="24">
+            <el-form-item label="标段名称" prop="tendName">
+              <el-input v-model="form.tendName" placeholder="请输入标段名称"/>
+            </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <OrgSelect v-model="form.orgGuid" label="关联单位" placeholder="请输入单位名称搜索" />
+          <el-col :span="24">
+            <OrgSelect v-model="form.orgGuid" label="关联单位" placeholder="请输入单位名称搜索" required
+                       :disabled="!isSuperAdmin"/>
           </el-col>
           <el-col :span="12">
             <el-form-item label="负责人" prop="legPers">
-              <el-input v-model="form.legPers" placeholder="请输入负责人" />
+              <el-input v-model="form.legPers" placeholder="请输入负责人"/>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="联系电话" prop="legTel">
-              <el-input v-model="form.legTel" placeholder="请输入联系电话" />
+              <el-input v-model="form.legTel" placeholder="请输入联系电话"/>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="合同金额" prop="conAmout">
-              <el-input v-model="form.conAmout" placeholder="请输入合同金额" type="number" />
+              <el-input v-model="form.conAmout" placeholder="请输入合同金额" type="number"/>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="安全生产费用" prop="totalSpc">
-              <el-input v-model="form.totalSpc" placeholder="请输入安全生产费用总和" type="number" />
+              <el-input v-model="form.totalSpc" placeholder="请输入安全生产费用总和" type="number"/>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="完工状态" prop="stat">
               <el-select v-model="form.stat" placeholder="请选择完工状态" style="width:100%">
-                <el-option label="在建" value="1" />
-                <el-option label="已完工" value="2" />
+                <el-option label="在建" value="1"/>
+                <el-option label="已完工" value="2"/>
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="24">
             <el-form-item label="建设内容" prop="consCont">
-              <el-input v-model="form.consCont" type="textarea" :rows="3" placeholder="请输入建设内容" />
+              <el-input v-model="form.consCont" type="textarea" :rows="3" placeholder="请输入建设内容"/>
             </el-form-item>
           </el-col>
           <el-col :span="24">
             <el-form-item label="备注" prop="note">
-              <el-input v-model="form.note" type="textarea" :rows="2" placeholder="请输入备注" />
+              <el-input v-model="form.note" type="textarea" :rows="2" placeholder="请输入备注"/>
             </el-form-item>
           </el-col>
         </el-row>
@@ -134,65 +170,290 @@
       </template>
     </el-dialog>
 
-    <!-- ====== 标段参建单位关系 Dialog ====== -->
-    <el-dialog title="标段参建单位" v-model="relOpen" width="900px" append-to-body>
-      <el-alert v-if="relTendRow.tendName" :title="'当前标段:' + relTendRow.tendName" type="info" :closable="false"
-        style="margin-bottom:12px" />
-      <el-row class="mb8">
-        <el-col :span="24">
-          <el-form :inline="true" @submit.prevent>
-            <el-form-item label="参建单位">
-              <el-select v-model="relForm.orgUwatGuid" filterable remote clearable reserve-keyword
-                :remote-method="remoteSearchRelOrg" :loading="relOrgLoading" placeholder="请输入参建单位名称搜索"
-                style="width:240px">
-                <el-option v-for="item in relOrgOptions" :key="item.guid" :label="item.orgCode + ' - ' + item.orgName"
-                  :value="item.guid" />
-              </el-select>
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" @click="submitRelForm"><el-icon>
-                  <Plus />
-                </el-icon> 添加参建单位</el-button>
-            </el-form-item>
-          </el-form>
-        </el-col>
-      </el-row>
-      <el-table v-loading="relLoading" :data="relOrgList" border>
-        <el-table-column label="参建单位代码" align="center" prop="orgCode" :show-overflow-tooltip="true" min-width="120" />
-        <el-table-column label="参建单位名称" align="center" prop="orgName" :show-overflow-tooltip="true" min-width="150" />
-        <el-table-column label="参建单位类型" align="center" prop="orgType" :show-overflow-tooltip="true" min-width="100">
+    <!-- ====== 选择工程 Dialog(OBJ_ENG) ====== -->
+    <el-dialog title="选择工程" v-model="engOpen" width="760px" append-to-body :close-on-click-modal="false">
+      <div class="search-bar">
+        <el-form :model="engQuery" :inline="true" @submit.prevent>
+          <el-form-item label="工程编码" prop="engCode">
+            <el-input v-model="engQuery.engCode" placeholder="请输入工程编码" clearable style="width:140px"
+                      @keyup.enter="handleEngQuery"/>
+          </el-form-item>
+          <el-form-item label="工程名称" prop="engName">
+            <el-input v-model="engQuery.engName" placeholder="请输入工程名称" clearable style="width:160px"
+                      @keyup.enter="handleEngQuery"/>
+          </el-form-item>
+          <el-form-item label="工程类型" prop="engType">
+            <el-select v-model="engQuery.engType" placeholder="请选择工程类型" clearable style="width:120px">
+              <el-option v-for="d in slaj_eng_type" :key="d.value" :label="d.label" :value="d.value"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleEngQuery">
+              <el-icon>
+                <Search/>
+              </el-icon>
+              搜索
+            </el-button>
+            <el-button @click="resetEngQuery">
+              <el-icon>
+                <Refresh/>
+              </el-icon>
+              重置
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <el-table v-loading="engLoading" :data="engList" height="360" highlight-current-row
+                @row-click="handleEngSelect">
+        <el-table-column label="工程编码" align="center" prop="engCode" width="160" :show-overflow-tooltip="true"/>
+        <el-table-column label="工程名称" align="left" prop="engName" :show-overflow-tooltip="true" min-width="180"/>
+        <el-table-column label="工程类型" align="center" prop="engType" width="110">
           <template #default="scope">
-            <dict-tag :options="slaj_uwat_org_type" :value="scope.row.orgType" />
+            <dict-tag :options="slaj_eng_type" :value="scope.row.engType"/>
           </template>
         </el-table-column>
-        <el-table-column label="操作" align="center" width="100">
+        <el-table-column label="操作" align="center" width="80">
           <template #default="scope">
-            <el-button link type="danger" @click="handleRelDelete(scope.row)">解除</el-button>
+            <el-button link type="primary" @click.stop="handleEngSelect(scope.row)">选择</el-button>
           </template>
         </el-table-column>
       </el-table>
+      <div class="pagination">
+        <el-pagination
+            v-show="engTotal > 0"
+            v-model:current-page="engQuery.pageNum"
+            v-model:page-size="engQuery.pageSize"
+            :total="engTotal"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            @size-change="loadEngList"
+            @current-change="loadEngList"
+        />
+      </div>
+      <template #footer>
+        <el-button @click="engOpen = false">取消</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- ====== 标段配置 Dialog(参建单位 + 项目组成员) ====== -->
+    <el-dialog title="标段配置" v-model="relOpen" width="920px" append-to-body>
+      <el-alert v-if="relTendRow.tendName" :title="'当前标段:' + relTendRow.tendName" type="info" :closable="false"
+                style="margin-bottom:12px"/>
+      <el-tabs v-model="relTab">
+        <!-- ====== 参建单位(REL_TEND_ORG) ====== -->
+        <el-tab-pane label="参建单位" name="org">
+          <div class="search-bar">
+            <el-form :model="relCandParams" :inline="true" @submit.prevent>
+              <el-form-item label="单位代码" prop="orgCode">
+                <el-input v-model="relCandParams.orgCode" placeholder="请输入单位代码" clearable style="width:140px"
+                          @keyup.enter="handleRelCandQuery"/>
+              </el-form-item>
+              <el-form-item label="单位名称" prop="orgName">
+                <el-input v-model="relCandParams.orgName" placeholder="请输入单位名称" clearable style="width:160px"
+                          @keyup.enter="handleRelCandQuery"/>
+              </el-form-item>
+              <el-form-item label="单位类型" prop="orgType">
+                <el-select v-model="relCandParams.orgType" placeholder="请选择单位类型" clearable style="width:120px">
+                  <el-option v-for="d in slaj_uwat_org_type" :key="d.value" :label="d.label" :value="d.value"/>
+                </el-select>
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" @click="handleRelCandQuery">
+                  <el-icon>
+                    <Search/>
+                  </el-icon>
+                  搜索
+                </el-button>
+                <el-button @click="resetRelCandQuery">
+                  <el-icon>
+                    <Refresh/>
+                  </el-icon>
+                  重置
+                </el-button>
+              </el-form-item>
+            </el-form>
+          </div>
+
+          <div class="rel-block-title">可选参建单位(勾选后可批量添加)</div>
+          <el-table v-loading="relCandLoading" :data="relCandList" height="210" border
+                    @selection-change="handleRelCandSelection" ref="relCandTableRef">
+            <el-table-column type="selection" width="45" align="center"/>
+            <el-table-column label="单位代码" align="center" prop="orgCode" :show-overflow-tooltip="true"
+                             min-width="130"/>
+            <el-table-column label="单位名称" align="center" prop="orgName" :show-overflow-tooltip="true"
+                             min-width="180"/>
+            <el-table-column label="单位类型" align="center" prop="orgType" min-width="110">
+              <template #default="scope">
+                <dict-tag :options="slaj_uwat_org_type" :value="scope.row.orgType"/>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div class="pagination">
+            <el-pagination
+                v-show="relCandTotal > 0"
+                v-model:current-page="relCandParams.pageNum"
+                v-model:page-size="relCandParams.pageSize"
+                :total="relCandTotal"
+                :page-sizes="[10, 20, 50, 100]"
+                layout="total, sizes, prev, pager, next, jumper"
+                @size-change="loadRelCandList"
+                @current-change="loadRelCandList"
+            />
+          </div>
+          <div class="rel-block-toolbar">
+            <el-button type="primary" :disabled="relCandSelected.length === 0" @click="submitRelBatch">
+              <el-icon>
+                <Plus/>
+              </el-icon>
+              添加选中参建单位({{ relCandSelected.length }})
+            </el-button>
+          </div>
+
+          <el-divider content-position="left">已关联参建单位</el-divider>
+          <el-table v-loading="relLoading" :data="relOrgList" height="200" border
+                    @selection-change="handleRelSelection">
+            <el-table-column type="selection" width="45" align="center"/>
+            <el-table-column label="参建单位代码" align="center" prop="orgCode" :show-overflow-tooltip="true"
+                             min-width="130"/>
+            <el-table-column label="参建单位名称" align="center" prop="orgName" :show-overflow-tooltip="true"
+                             min-width="180"/>
+            <el-table-column label="参建单位类型" align="center" prop="orgType" min-width="110">
+              <template #default="scope">
+                <dict-tag :options="slaj_uwat_org_type" :value="scope.row.orgType"/>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="90" fixed="right">
+              <template #default="scope">
+                <el-button link type="danger" @click="handleRelDelete(scope.row)">解除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div class="rel-block-toolbar">
+            <el-button type="danger" plain :disabled="relSelected.length === 0" @click="handleRelBatchDelete">
+              <el-icon>
+                <Delete/>
+              </el-icon>
+              解除选中
+            </el-button>
+          </div>
+        </el-tab-pane>
+
+        <!-- ====== 项目组成员(ATT_PERS_EXT) ====== -->
+        <el-tab-pane label="项目组成员" name="pers">
+          <div class="rel-block-toolbar">
+            <el-button type="primary" plain @click="handlePersAdd" v-hasPermi="['business:persext:add']">
+              <el-icon>
+                <Plus/>
+              </el-icon>
+              新增成员
+            </el-button>
+            <el-button type="danger" plain :disabled="persMultiple" @click="handlePersBatchDelete"
+                       v-hasPermi="['business:persext:remove']">
+              <el-icon>
+                <Delete/>
+              </el-icon>
+              删除
+            </el-button>
+          </div>
+          <el-table v-loading="persLoading" :data="persList" height="360" border
+                    @selection-change="handlePersSelection">
+            <el-table-column type="selection" width="45" align="center"/>
+            <el-table-column label="姓名" align="center" prop="persName" :show-overflow-tooltip="true"
+                             min-width="110"/>
+            <el-table-column label="职务级别" align="center" prop="admDuty" :show-overflow-tooltip="true"
+                             min-width="100"/>
+            <el-table-column label="性别" align="center" prop="sex" min-width="70"/>
+            <el-table-column label="出生日期" align="center" prop="bornDate" min-width="110"/>
+            <el-table-column label="累计从事监理工作时间" align="center" prop="workDate" :show-overflow-tooltip="true"
+                             min-width="150"/>
+            <el-table-column label="备注" align="center" prop="note" :show-overflow-tooltip="true"
+                             min-width="120"/>
+            <el-table-column label="操作" align="center" width="120" fixed="right">
+              <template #default="scope">
+                <el-button link type="primary" @click="handlePersUpdate(scope.row)"
+                           v-hasPermi="['business:persext:edit']">修改
+                </el-button>
+                <el-button link type="danger" @click="handlePersDelete(scope.row)"
+                           v-hasPermi="['business:persext:remove']">删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+      </el-tabs>
+    </el-dialog>
+
+    <!-- ====== 项目组成员 新增/编辑 Dialog ====== -->
+    <el-dialog :title="persTitle" v-model="persOpen" width="560px" append-to-body>
+      <el-form ref="persFormRef" :model="persForm" :rules="persRules" label-width="150px">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="人员姓名" prop="persName">
+              <el-input v-model="persForm.persName" placeholder="请输入人员姓名"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="职务级别" prop="admDuty">
+              <el-input v-model="persForm.admDuty" placeholder="请输入职务级别"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="性别" prop="sex">
+              <el-select v-model="persForm.sex" placeholder="请选择性别" style="width:100%">
+                <el-option label="男" value="男"/>
+                <el-option label="女" value="女"/>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="出生日期" prop="bornDate">
+              <el-date-picker v-model="persForm.bornDate" type="date" value-format="YYYY-MM-DD"
+                              placeholder="请选择出生日期" style="width:100%"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="累计从事监理工作时间" prop="workDate">
+              <el-input v-model="persForm.workDate" placeholder="请输入累计从事监理工作时间"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="备注" prop="note">
+              <el-input v-model="persForm.note" type="textarea" :rows="2" placeholder="请输入备注"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <el-button @click="persOpen = false">取消</el-button>
+        <el-button type="primary" @click="submitPersForm">确定</el-button>
+      </template>
     </el-dialog>
   </div>
 </template>
 
 <script setup name="ProjBaseTend">
-import { useRoute } from 'vue-router'
-import { listObjTend, getObjTend, delObjTend, addObjTend, updateObjTend } from "@/api/system/objTend"
-import { listAttOrgUwatBase } from "@/api/system/attOrgUwatBase"
-import { listRelTendOrg, addRelTendOrg, delRelTendOrg } from "@/api/system/relTendOrg"
+import {useRoute} from 'vue-router'
+import {addObjTend, delObjTend, getObjTend, listObjTend, updateObjTend} from "@/api/system/objTend"
+import {addAttPersExt, delAttPersExt, getAttPersExt, listAttPersExt, updateAttPersExt} from "@/api/system/attPersExt"
+import {listAttOrgUwatBase} from "@/api/system/attOrgUwatBase"
+import {addRelTendOrg, delRelTendOrg, listRelTendOrg} from "@/api/system/relTendOrg"
+import {getObjEng, listObjEng} from "@/api/slaj/eng"
 import OrgSelect from "@/components/OrgSelect"
-import EngSelect from "@/components/EngSelect"
-import ProjSelect from "@/components/ProjSelect"
-import { useTableHeight } from '@/hooks/useTableHeight'
-import { Plus, Delete, Search, Refresh, Back, List } from '@element-plus/icons-vue'
-
-const { tableHeight, containerRef } = useTableHeight()
-const { proxy } = getCurrentInstance()
-const { slaj_uwat_org_type } = proxy.useDict("slaj_uwat_org_type")
+import useUserStore from '@/store/modules/user'
+import {useTableHeight} from '@/hooks/useTableHeight'
+import {Delete, List, Plus, Refresh, Search} from '@element-plus/icons-vue'
+
+const {tableHeight, containerRef} = useTableHeight()
+const {proxy} = getCurrentInstance()
+const {slaj_uwat_org_type} = proxy.useDict("slaj_uwat_org_type")
 const route = useRoute()
+const userStore = useUserStore()
 
 const projGuid = ref(route.params.projGuid || undefined)
 
+// 超级管理员(userId=1)可修改关联单位,其他角色锁定为本单位
+const isSuperAdmin = computed(() => Number(userStore.id) === 1)
+
 const objTendList = ref([])
 const open = ref(false)
 const loading = ref(true)
@@ -203,6 +464,14 @@ const multiple = ref(true)
 const total = ref(0)
 const title = ref("")
 
+// 工程类型(与工程管理页保持一致,无字典,硬编码)
+const slaj_eng_type = [
+  {label: '水库', value: '01'}, {label: '水闸', value: '02'}, {label: '泵站', value: '03'},
+  {label: '水电站', value: '04'}, {label: '堤防', value: '05'}, {label: '灌区', value: '06'},
+  {label: '引调水', value: '07'}, {label: '淤地坝', value: '08'}, {label: '农村供水', value: '09'},
+  {label: '其他', value: '10'}, {label: '农村水电站', value: '11'}
+]
+
 const data = reactive({
   form: {
     tendName: undefined,
@@ -223,15 +492,85 @@ const data = reactive({
     pageSize: 10
   },
   rules: {
-    tendName: [{ required: true, message: "标段名称不能为空", trigger: "blur" }]
+    tendName: [{required: true, message: "标段名称不能为空", trigger: "blur"}],
+    engGuid: [{required: true, message: "请选择关联工程", trigger: "change"}]
   }
 })
 
-const { queryParams, form, rules } = toRefs(data)
+const {queryParams, form, rules} = toRefs(data)
+
+// =====================================================
+// 关联工程选择弹窗(OBJ_ENG)
+// =====================================================
+const engOpen = ref(false)
+const engLoading = ref(false)
+const engList = ref([])
+const engTotal = ref(0)
+const engName = ref("")
+const engQuery = reactive({
+  engCode: undefined,
+  engName: undefined,
+  engType: undefined,
+  pageNum: 1,
+  pageSize: 10
+})
+
+function handleOpenEngDialog() {
+  resetEngQuery()
+  engOpen.value = true
+}
+
+function loadEngList() {
+  engLoading.value = true
+  listObjEng({...engQuery}).then(res => {
+    engList.value = res.rows || []
+    engTotal.value = res.total || 0
+  }).finally(() => {
+    engLoading.value = false
+  })
+}
+
+function handleEngQuery() {
+  engQuery.pageNum = 1
+  loadEngList()
+}
+
+function resetEngQuery() {
+  engQuery.engCode = undefined
+  engQuery.engName = undefined
+  engQuery.engType = undefined
+  engQuery.pageNum = 1
+  loadEngList()
+}
+
+function handleEngSelect(row) {
+  form.value.engGuid = row.guid
+  engName.value = row.engName
+  engOpen.value = false
+}
+
+function handleClearEng() {
+  form.value.engGuid = undefined
+  engName.value = ""
+}
+
+function loadEngName(guid) {
+  if (!guid) {
+    engName.value = ""
+    return
+  }
+  getObjEng(guid).then(res => {
+    const d = res.data
+    if (d && d.guid) {
+      engName.value = d.engName || ""
+    }
+  }).catch(() => {
+  })
+}
 
 function getList() {
   loading.value = true
-  listObjTend({ ...queryParams.value, projGuid: projGuid.value }).then(response => {
+  listObjTend({...queryParams.value, projGuid: projGuid.value}).then(response => {
     objTendList.value = response.rows
     total.value = response.total
     loading.value = false
@@ -257,6 +596,7 @@ function reset() {
     note: undefined,
     stat: undefined
   }
+  engName.value = ""
   proxy.resetForm("objTendRef")
 }
 
@@ -278,6 +618,7 @@ function handleSelectionChange(selection) {
 
 function handleAdd() {
   reset()
+  form.value.orgGuid = userStore.orgGuid || undefined
   open.value = true
   title.value = "添加标段"
 }
@@ -287,6 +628,7 @@ function handleUpdate(row) {
   const guid = row.guid || ids.value
   getObjTend(guid).then(response => {
     form.value = response.data
+    loadEngName(form.value.engGuid)
     open.value = true
     title.value = "修改标段"
   })
@@ -328,67 +670,236 @@ function goBack() {
 }
 
 // =====================================================
-// 标段参建单位关系(REL_TEND_ORG
+// 标段配置 Dialog(参建单位 REL_TEND_ORG + 项目组成员 ATT_PERS_EXT
 // =====================================================
 const relOpen = ref(false)
+const relTab = ref("org")
 const relTendRow = ref({})
 const relOrgList = ref([])
 const relLoading = ref(false)
-const relOrgOptions = ref([])
-const relOrgLoading = ref(false)
-const relForm = ref({ orgUwatGuid: undefined, fromDate: undefined })
+const relSelected = ref([])
+
+// 可选参建单位(候选列表,支持多选批量添加)
+const relCandList = ref([])
+const relCandTotal = ref(0)
+const relCandLoading = ref(false)
+const relCandSelected = ref([])
+const relCandTableRef = ref(null)
+const relCandParams = reactive({
+  orgCode: undefined,
+  orgName: undefined,
+  orgType: undefined,
+  pageNum: 1,
+  pageSize: 10
+})
 
-/** 打开某标段的参建单位关系弹窗 */
+/** 打开某标段的配置弹窗(参建单位 + 项目组成员) */
 function handleViewOrg(row) {
   relTendRow.value = row
-  relForm.value = { orgUwatGuid: undefined, fromDate: undefined }
-  relOrgOptions.value = []
+  relTab.value = "org"
+  relSelected.value = []
+  relCandSelected.value = []
   relOpen.value = true
   loadRelOrgList()
+  resetRelCandQuery()
+  loadPersList()
 }
 
 function loadRelOrgList() {
   relLoading.value = true
-  listRelTendOrg({ tendGuid: relTendRow.value.guid, pageNum: 1, pageSize: 999 }).then(res => {
+  listRelTendOrg({tendGuid: relTendRow.value.guid, pageNum: 1, pageSize: 999}).then(res => {
     relOrgList.value = res.rows || []
-  }).finally(() => { relLoading.value = false })
+  }).finally(() => {
+    relLoading.value = false
+  })
+}
+
+/** 加载可选参建单位候选列表 */
+function loadRelCandList() {
+  relCandLoading.value = true
+  listAttOrgUwatBase({...relCandParams}).then(res => {
+    relCandList.value = res.rows || []
+    relCandTotal.value = res.total || 0
+  }).finally(() => {
+    relCandLoading.value = false
+  })
 }
 
-/** 远程搜索参建单位 */
-function remoteSearchRelOrg(query) {
-  if (!query) { relOrgOptions.value = []; return }
-  relOrgLoading.value = true
-  listAttOrgUwatBase({ orgName: query, pageNum: 1, pageSize: 20 }).then(res => {
-    relOrgOptions.value = (res.rows || []).map(r => ({ guid: r.guid, orgName: r.orgName }))
-  }).finally(() => { relOrgLoading.value = false })
+function handleRelCandQuery() {
+  relCandParams.pageNum = 1
+  loadRelCandList()
 }
 
-/** 添加标段↔参建单位关系 */
-function submitRelForm() {
-  if (!relForm.value.orgUwatGuid) {
-    proxy.$modal.msgWarning("请选择参建单位")
+function resetRelCandQuery() {
+  relCandParams.orgCode = undefined
+  relCandParams.orgName = undefined
+  relCandParams.orgType = undefined
+  relCandParams.pageNum = 1
+  loadRelCandList()
+}
+
+function handleRelCandSelection(sel) {
+  relCandSelected.value = sel
+}
+
+function handleRelSelection(sel) {
+  relSelected.value = sel
+}
+
+/** 批量添加选中的参建单位 */
+function submitRelBatch() {
+  if (!relCandSelected.value.length) {
+    proxy.$modal.msgWarning("请先勾选参建单位")
     return
   }
-  addRelTendOrg({
-    tendGuid: relTendRow.value.guid,
-    orgUwatGuid: relForm.value.orgUwatGuid,
-    fromDate: relForm.value.fromDate
-  }).then(() => {
-    proxy.$modal.msgSuccess("添加成功")
-    relForm.value = { orgUwatGuid: undefined, fromDate: undefined }
-    relOrgOptions.value = []
+  const exist = new Set((relOrgList.value || []).map(r => r.orgUwatGuid))
+  const targets = relCandSelected.value.filter(r => !exist.has(r.guid))
+  if (!targets.length) {
+    proxy.$modal.msgWarning("所选参建单位均已关联")
+    relCandSelected.value = []
+    relCandTableRef.value && relCandTableRef.value.clearSelection()
+    return
+  }
+  const tasks = targets.map(r => addRelTendOrg({tendGuid: relTendRow.value.guid, orgUwatGuid: r.guid}))
+  Promise.all(tasks).then(() => {
+    proxy.$modal.msgSuccess(`成功关联 ${targets.length} 个参建单位`)
+    relCandSelected.value = []
+    relCandTableRef.value && relCandTableRef.value.clearSelection()
     loadRelOrgList()
+    loadRelCandList()
   }).catch(() => {
-    proxy.$modal.msgError("添加失败,请重试")
+    proxy.$modal.msgError("关联失败,请重试")
   })
 }
 
-/** 解除标段↔参建单位关系 */
+/** 解除单个标段↔参建单位关系 */
 function handleRelDelete(row) {
   proxy.$modal.confirm('确认解除该参建单位关系?').then(() => delRelTendOrg(row.guid)).then(() => {
     loadRelOrgList()
     proxy.$modal.msgSuccess("已解除")
-  }).catch(() => { })
+  }).catch(() => {
+  })
+}
+
+/** 批量解除选中的参建单位关系 */
+function handleRelBatchDelete() {
+  if (!relSelected.value.length) {
+    proxy.$modal.msgWarning("请先勾选需要解除的参建单位")
+    return
+  }
+  const guids = relSelected.value.map(r => r.guid)
+  proxy.$modal.confirm(`确认解除选中的 ${guids.length} 个参建单位?`).then(() => delRelTendOrg(guids)).then(() => {
+    proxy.$modal.msgSuccess("已解除")
+    relSelected.value = []
+    loadRelOrgList()
+  }).catch(() => {
+  })
+}
+
+// =====================================================
+// 项目组成员(ATT_PERS_EXT)
+// =====================================================
+const persList = ref([])
+const persLoading = ref(false)
+const persIds = ref([])
+const persMultiple = ref(true)
+const persOpen = ref(false)
+const persTitle = ref("")
+const persForm = ref({
+  guid: undefined,
+  persName: undefined,
+  tendGuid: undefined,
+  admDuty: undefined,
+  sex: undefined,
+  bornDate: undefined,
+  workDate: undefined,
+  note: undefined
+})
+const persRules = {
+  persName: [{required: true, message: "人员姓名不能为空", trigger: "blur"}]
+}
+
+function loadPersList() {
+  persLoading.value = true
+  listAttPersExt({tendGuid: relTendRow.value.guid, pageNum: 1, pageSize: 999}).then(res => {
+    persList.value = res.rows || []
+  }).finally(() => {
+    persLoading.value = false
+  })
+}
+
+function resetPersForm() {
+  persForm.value = {
+    guid: undefined,
+    persName: undefined,
+    tendGuid: relTendRow.value.guid,
+    admDuty: undefined,
+    sex: undefined,
+    bornDate: undefined,
+    workDate: undefined,
+    note: undefined
+  }
+  proxy.resetForm("persFormRef")
+}
+
+function handlePersAdd() {
+  resetPersForm()
+  persOpen.value = true
+  persTitle.value = "新增成员"
+}
+
+function handlePersUpdate(row) {
+  resetPersForm()
+  getAttPersExt(row.guid).then(response => {
+    persForm.value = response.data
+    persOpen.value = true
+    persTitle.value = "修改成员"
+  })
+}
+
+function submitPersForm() {
+  proxy.$refs["persFormRef"].validate(valid => {
+    if (!valid) {
+      return
+    }
+    if (persForm.value.guid != undefined) {
+      updateAttPersExt(persForm.value).then(() => {
+        proxy.$modal.msgSuccess("修改成功")
+        persOpen.value = false
+        loadPersList()
+      })
+    } else {
+      addAttPersExt(persForm.value).then(() => {
+        proxy.$modal.msgSuccess("新增成功")
+        persOpen.value = false
+        loadPersList()
+      })
+    }
+  })
+}
+
+function handlePersDelete(row) {
+  proxy.$modal.confirm('确认删除该成员信息?').then(() => delAttPersExt(row.guid)).then(() => {
+    loadPersList()
+    proxy.$modal.msgSuccess("删除成功")
+  }).catch(() => {
+  })
+}
+
+function handlePersBatchDelete() {
+  if (!persIds.value.length) {
+    return
+  }
+  proxy.$modal.confirm(`确认删除选中的 ${persIds.value.length} 条成员信息?`).then(() => delAttPersExt(persIds.value)).then(() => {
+    loadPersList()
+    proxy.$modal.msgSuccess("删除成功")
+  }).catch(() => {
+  })
+}
+
+function handlePersSelection(sel) {
+  persIds.value = sel.map(r => r.guid)
+  persMultiple.value = !sel.length
 }
 
 getList()
@@ -398,4 +909,16 @@ getList()
 .tend-form .el-textarea__inner {
   resize: none;
 }
+
+.rel-block-title {
+  font-weight: 600;
+  color: #303133;
+  margin: 4px 0 8px;
+}
+
+.rel-block-toolbar {
+  margin: 8px 0;
+  display: flex;
+  gap: 8px;
+}
 </style>