Bläddra i källkod

修复前端401错误处理和后端多个功能问题

## 主要修复内容

### 1. 前端认证错误处理
- 修复request.js中的语法错误,确保401和403错误能正确处理
- 当检测到401或403错误时,自动清除token和userInfo,并重定向到登录页面
- 完善了各种HTTP错误状态码的处理逻辑(404、500、网络错误等)

### 2. 后端功能修复
- 修复调度列表客户名称显示逻辑:正确处理代理公司和船公司的组合显示
- 修复引航计划导入时的PortID填充逻辑:根据泊位和港口的PortType正确计算
- 修复CreateUserID和ModifyUserID处理:使用当前登录用户ID而非"system"
- 修复主引和备注复制:确保引航计划的主引和备注正确复制到调度表
- 创建CommonDataService:分离公共数据操作,解决@DataSource注解在内部方法调用不生效的问题

### 3. 数据库查询优化
- 修复DispatcherRepositoryCustom中的SQL语法错误:正确处理代理公司和船公司的查询
- 使用CONCAT函数替代字符串拼接,避免SQL注入风险
- 优化查询条件处理,确保只查询有数据的字段

### 4. 文档更新
- 需求文档已包含客户名称显示逻辑,无需更新
- 最佳实践文档已包含CreateUserID处理规则,无需更新
- 项目规则文档已完整,无需更新

## 测试验证
- 前端401错误处理已验证:未登录时自动跳转到登录页面
- 登录功能正常:可以成功登录并访问首页
- 客户名称显示正确:代理公司和船公司名称正确组合显示
heyiwen 1 vecka sedan
förälder
incheckning
76b704f7aa
25 ändrade filer med 845 tillägg och 228 borttagningar
  1. 156 0
      .trae/rules/web-dev-best-practices.md
  2. 10 0
      JavaBackend/src/main/java/com/lianda/backend/dto/DispatcherListDTO.java
  3. 11 1
      JavaBackend/src/main/java/com/lianda/backend/dto/ShippingCompanyDTO.java
  4. 11 0
      JavaBackend/src/main/java/com/lianda/backend/model/BusShip.java
  5. 224 0
      JavaBackend/src/main/java/com/lianda/backend/model/DispDispatcher.java
  6. 16 12
      JavaBackend/src/main/java/com/lianda/backend/model/SysRole.java
  7. 1 1
      JavaBackend/src/main/java/com/lianda/backend/model/SysUserRole.java
  8. 1 1
      JavaBackend/src/main/java/com/lianda/backend/model/User.java
  9. 19 0
      JavaBackend/src/main/java/com/lianda/backend/repository/BusCustomerCompanyBusinessRepository.java
  10. 8 1
      JavaBackend/src/main/java/com/lianda/backend/repository/BusPilotTypeSettingRepository.java
  11. 5 0
      JavaBackend/src/main/java/com/lianda/backend/repository/DispPilotRepository.java
  12. 16 0
      JavaBackend/src/main/java/com/lianda/backend/repository/DispPortRepository.java
  13. 22 5
      JavaBackend/src/main/java/com/lianda/backend/repository/DispatcherRepositoryCustom.java
  14. 20 0
      JavaBackend/src/main/java/com/lianda/backend/repository/SysRoleRepository.java
  15. 19 2
      JavaBackend/src/main/java/com/lianda/backend/repository/SysUserRoleRepository.java
  16. 11 11
      JavaBackend/src/main/java/com/lianda/backend/repository/UserRepository.java
  17. 201 119
      JavaBackend/src/main/java/com/lianda/backend/service/CommonDataService.java
  18. 4 0
      JavaBackend/src/main/java/com/lianda/backend/service/DispatcherService.java
  19. 51 54
      JavaBackend/src/main/java/com/lianda/backend/service/PilotPlanService.java
  20. 4 2
      vue-frontend/src/components/HomePage.vue
  21. 1 1
      vue-frontend/src/components/RoleManagement.vue
  22. 19 5
      vue-frontend/src/components/TabsView.vue
  23. 1 1
      vue-frontend/src/components/UserManagement.vue
  24. 7 0
      vue-frontend/src/router/index.js
  25. 7 12
      vue-frontend/src/utils/request.js

+ 156 - 0
.trae/rules/web-dev-best-practices.md

@@ -522,6 +522,162 @@ public Page<User> queryUsers(UserQuery query, Pageable pageable) {
 
 ## 5. API请求规范
 
+### 5.0 验证失败处理机制
+
+**重要原则:** 所有API请求必须统一处理验证失败,确保用户在token失效或权限不足时能够及时跳转到登录页面。
+
+#### 5.0.1 请求拦截器配置
+
+在`src/utils/request.js`中配置axios请求和响应拦截器,统一处理认证和错误。
+
+```javascript
+import axios from 'axios'
+
+/**
+ * 创建axios实例
+ */
+const request = axios.create({
+  baseURL: '/api',
+  timeout: 30000
+})
+
+/**
+ * 请求拦截器 - 自动添加token
+ */
+request.interceptors.request.use(
+  config => {
+    const token = localStorage.getItem('token')
+    if (token) {
+      config.headers['Authorization'] = `Bearer ${token}`
+    }
+    return config
+  },
+  error => {
+    console.error('请求错误:', error)
+    return Promise.reject(error)
+  }
+)
+
+/**
+ * 响应拦截器 - 处理token过期和验证失败
+ */
+request.interceptors.response.use(
+  response => {
+    return response
+  },
+  error => {
+    if (error.response) {
+      switch (error.response.status) {
+        case 401:
+          console.error('未授权,请重新登录')
+          localStorage.removeItem('token')
+          localStorage.removeItem('userInfo')
+          window.location.href = '/login'
+          break
+        case 403:
+          console.error('没有权限访问,请重新登录')
+          localStorage.removeItem('token')
+          localStorage.removeItem('userInfo')
+          window.location.href = '/login'
+          break
+        case 404:
+          console.error('请求的资源不存在')
+          break
+        case 500:
+          console.error('服务器错误')
+          break
+        default:
+          console.error('请求失败:', error.response.status)
+      }
+    } else if (error.request) {
+      console.error('网络错误,请检查网络连接')
+    } else {
+      console.error('请求配置错误:', error.message)
+    }
+    return Promise.reject(error)
+  }
+)
+
+export default request
+```
+
+**关键说明:**
+- **401未授权**:清除token和userInfo,跳转到登录页面
+- **403没有权限**:清除token和userInfo,跳转到登录页面
+- **404资源不存在**:仅记录错误日志
+- **500服务器错误**:仅记录错误日志
+- **网络错误和配置错误**:仅记录错误日志
+
+#### 5.0.2 路由守卫配置
+
+在`src/router/index.js`中配置路由守卫,在路由跳转前检查登录状态。
+
+```javascript
+// 路由守卫,检查登录状态
+router.beforeEach((to, from, next) => {
+  // 登录页面不需要验证
+  if (to.name === 'login') {
+    next()
+    return
+  }
+  
+  // 检查是否有登录令牌
+  const token = localStorage.getItem('token')
+  if (!token) {
+    // 未登录,重定向到登录页面
+    localStorage.removeItem('userInfo')
+    next({ name: 'login' })
+    return
+  }
+  
+  // 已登录,继续访问
+  next()
+})
+```
+
+**关键说明:**
+- 登录页面不需要验证
+- 检查localStorage中的token是否存在
+- 如果没有token,清除userInfo并跳转到登录页面
+
+#### 5.0.3 验证失败处理流程
+
+完整的验证失败处理流程如下:
+
+```
+用户访问需要认证的页面
+    ↓
+路由守卫检查token
+    ↓
+没有token? → 清除userInfo,跳转到登录页面
+    ↓
+有token? → 继续访问
+    ↓
+发送API请求(自动添加token)
+    ↓
+服务器验证失败(401/403)
+    ↓
+响应拦截器捕获错误
+    ↓
+清除token和userInfo
+    ↓
+跳转到登录页面
+    ↓
+用户重新登录
+    ↓
+获取新的token
+    ↓
+正常访问系统
+```
+
+#### 5.0.4 注意事项
+
+1. **统一清除**:在401和403错误时,必须同时清除token和userInfo
+2. **强制跳转**:使用`window.location.href`而不是`router.push`,确保页面强制刷新
+3. **错误日志**:所有错误都应该记录到控制台,便于调试
+4. **用户体验**:在跳转到登录页面前,应该给用户明确的提示信息
+5. **避免重复跳转**:路由守卫和响应拦截器都应该检查token,避免重复跳转
+
 ### 5.1 使用request.js
 
 所有API请求都应该使用统一的request工具,自动处理认证和错误。

+ 10 - 0
JavaBackend/src/main/java/com/lianda/backend/dto/DispatcherListDTO.java

@@ -113,6 +113,16 @@ public class DispatcherListDTO {
      */
     private Integer statusValue;
     
+    /**
+     * 代理ID
+     */
+    private String agencyId;
+    
+    /**
+     * 船公司ID
+     */
+    private String shipownerId;
+    
     /**
      * 通知推送状态(已推送/未推送)
      */

+ 11 - 1
JavaBackend/src/main/java/com/lianda/backend/dto/ShippingCompanyDTO.java

@@ -6,6 +6,7 @@ package com.lianda.backend.dto;
 public class ShippingCompanyDTO {
     private String companyId;
     private String companyName;
+    private String businessCode;
     private String contact;
     private String tel;
     private String mobile;
@@ -13,9 +14,10 @@ public class ShippingCompanyDTO {
     public ShippingCompanyDTO() {
     }
     
-    public ShippingCompanyDTO(String customerCompanyId, String name, String contact, String tel, String mobile) {
+    public ShippingCompanyDTO(String customerCompanyId, String name, String businessCode, String contact, String tel, String mobile) {
         this.companyId = customerCompanyId;
         this.companyName = name;
+        this.businessCode = businessCode;
         this.contact = contact;
         this.tel = tel;
         this.mobile = mobile;
@@ -37,6 +39,14 @@ public class ShippingCompanyDTO {
         this.companyName = companyName;
     }
     
+    public String getBusinessCode() {
+        return businessCode;
+    }
+    
+    public void setBusinessCode(String businessCode) {
+        this.businessCode = businessCode;
+    }
+    
     public String getContact() {
         return contact;
     }

+ 11 - 0
JavaBackend/src/main/java/com/lianda/backend/model/BusShip.java

@@ -21,6 +21,9 @@ public class BusShip {
 
     @Column(name = "ShipLength")
     private java.math.BigDecimal shipLength;
+    
+    @Column(name = "ShipTypeID")
+    private Integer shipTypeId;
 
     @Column(name = "RecordStatus")
     private Integer recordStatus;
@@ -83,6 +86,14 @@ public class BusShip {
     public void setShipLength(java.math.BigDecimal shipLength) {
         this.shipLength = shipLength;
     }
+    
+    public Integer getShipTypeId() {
+        return shipTypeId;
+    }
+    
+    public void setShipTypeId(Integer shipTypeId) {
+        this.shipTypeId = shipTypeId;
+    }
 
     public Integer getRecordStatus() {
         return recordStatus;

+ 224 - 0
JavaBackend/src/main/java/com/lianda/backend/model/DispDispatcher.java

@@ -89,4 +89,228 @@ public class DispDispatcher {
     
     @Column(name = "ModifyTime")
     private Date modifyTime;
+    
+    public String getDispatcherId() {
+        return dispatcherId;
+    }
+    
+    public void setDispatcherId(String dispatcherId) {
+        this.dispatcherId = dispatcherId;
+    }
+    
+    public String getNo() {
+        return no;
+    }
+    
+    public void setNo(String no) {
+        this.no = no;
+    }
+    
+    public String getPilotPlanId() {
+        return pilotPlanId;
+    }
+    
+    public void setPilotPlanId(String pilotPlanId) {
+        this.pilotPlanId = pilotPlanId;
+    }
+    
+    public Date getWorkTime() {
+        return workTime;
+    }
+    
+    public void setWorkTime(Date workTime) {
+        this.workTime = workTime;
+    }
+    
+    public String getPortId() {
+        return portId;
+    }
+    
+    public void setPortId(String portId) {
+        this.portId = portId;
+    }
+    
+    public String getFromBerthageId() {
+        return fromBerthageId;
+    }
+    
+    public void setFromBerthageId(String fromBerthageId) {
+        this.fromBerthageId = fromBerthageId;
+    }
+    
+    public String getToBerthageId() {
+        return toBerthageId;
+    }
+    
+    public void setToBerthageId(String toBerthageId) {
+        this.toBerthageId = toBerthageId;
+    }
+    
+    public String getShipId() {
+        return shipId;
+    }
+    
+    public void setShipId(String shipId) {
+        this.shipId = shipId;
+    }
+    
+    public java.math.BigDecimal getDeep() {
+        return deep;
+    }
+    
+    public void setDeep(java.math.BigDecimal deep) {
+        this.deep = deep;
+    }
+    
+    public String getAgencyId() {
+        return agencyId;
+    }
+    
+    public void setAgencyId(String agencyId) {
+        this.agencyId = agencyId;
+    }
+    
+    public String getShipownerId() {
+        return shipownerId;
+    }
+    
+    public void setShipownerId(String shipownerId) {
+        this.shipownerId = shipownerId;
+    }
+    
+    public String getPilotId() {
+        return pilotId;
+    }
+    
+    public void setPilotId(String pilotId) {
+        this.pilotId = pilotId;
+    }
+    
+    public String getTransportTugboatId() {
+        return transportTugboatId;
+    }
+    
+    public void setTransportTugboatId(String transportTugboatId) {
+        this.transportTugboatId = transportTugboatId;
+    }
+    
+    public Integer getTradeTypeId() {
+        return tradeTypeId;
+    }
+    
+    public void setTradeTypeId(Integer tradeTypeId) {
+        this.tradeTypeId = tradeTypeId;
+    }
+    
+    public Integer getPilotTypeId() {
+        return pilotTypeId;
+    }
+    
+    public void setPilotTypeId(Integer pilotTypeId) {
+        this.pilotTypeId = pilotTypeId;
+    }
+    
+    public Date getStartTime() {
+        return startTime;
+    }
+    
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+    
+    public Date getEndTime() {
+        return endTime;
+    }
+    
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+    
+    public String getRemark() {
+        return remark;
+    }
+    
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+    
+    public Boolean getIsReturned() {
+        return isReturned;
+    }
+    
+    public void setIsReturned(Boolean isReturned) {
+        this.isReturned = isReturned;
+    }
+    
+    public String getTugboatAddReason() {
+        return tugboatAddReason;
+    }
+    
+    public void setTugboatAddReason(String tugboatAddReason) {
+        this.tugboatAddReason = tugboatAddReason;
+    }
+    
+    public Boolean getIsSent() {
+        return isSent;
+    }
+    
+    public void setIsSent(Boolean isSent) {
+        this.isSent = isSent;
+    }
+    
+    public Boolean getIsMayer() {
+        return isMayer;
+    }
+    
+    public void setIsMayer(Boolean isMayer) {
+        this.isMayer = isMayer;
+    }
+    
+    public Integer getRecordStatus() {
+        return recordStatus;
+    }
+    
+    public void setRecordStatus(Integer recordStatus) {
+        this.recordStatus = recordStatus;
+    }
+    
+    public String getCreateUserId() {
+        return createUserId;
+    }
+    
+    public void setCreateUserId(String createUserId) {
+        this.createUserId = createUserId;
+    }
+    
+    public void setCreateUserID(String createUserId) {
+        this.createUserId = createUserId;
+    }
+    
+    public Date getCreateTime() {
+        return createTime;
+    }
+    
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+    
+    public String getModifyUserId() {
+        return modifyUserId;
+    }
+    
+    public void setModifyUserId(String modifyUserId) {
+        this.modifyUserId = modifyUserId;
+    }
+    
+    public void setModifyUserID(String modifyUserId) {
+        this.modifyUserId = modifyUserId;
+    }
+    
+    public Date getModifyTime() {
+        return modifyTime;
+    }
+    
+    public void setModifyTime(Date modifyTime) {
+        this.modifyTime = modifyTime;
+    }
 }

+ 16 - 12
JavaBackend/src/main/java/com/lianda/backend/model/SysRole.java

@@ -3,34 +3,38 @@ package com.lianda.backend.model;
 import javax.persistence.*;
 import java.util.Date;
 
+/**
+ * 角色表实体类
+ */
 @Entity
 @Table(name = "Sys_Role")
 public class SysRole {
+
     @Id
-    @Column(name = "RoleID", length = 36)
+    @Column(name = "RoleID")
     private String roleId;
-    
-    @Column(name = "RoleName", nullable = false, length = 100)
+
+    @Column(name = "RoleName")
     private String roleName;
-    
-    @Column(name = "Description", length = 500)
+
+    @Column(name = "Description")
     private String description;
-    
-    @Column(name = "IsSystemRole", nullable = false)
+
+    @Column(name = "IsSystemRole")
     private Boolean isSystemRole;
-    
+
     @Column(name = "RecordStatus")
     private Integer recordStatus;
-    
+
     @Column(name = "CreateUserID")
     private String createUserId;
-    
+
     @Column(name = "CreateTime")
     private Date createTime;
-    
+
     @Column(name = "ModifyUserID")
     private String modifyUserId;
-    
+
     @Column(name = "ModifyTime")
     private Date modifyTime;
 

+ 1 - 1
JavaBackend/src/main/java/com/lianda/backend/model/SysUserRole.java

@@ -4,7 +4,7 @@ import javax.persistence.*;
 import java.io.Serializable;
 
 @Entity
-@Table(name = "Sys_UserRole")
+@Table(name = "sys_user_sys_role")
 @IdClass(SysUserRoleId.class)
 public class SysUserRole implements Serializable {
     @Id

+ 1 - 1
JavaBackend/src/main/java/com/lianda/backend/model/User.java

@@ -4,7 +4,7 @@ import javax.persistence.*;
 import java.util.Date;
 
 @Entity
-@Table(name = "Sys_User")
+@Table(name = "sys_user")
 public class User {
     @Id
     @Column(name = "UserID", length = 36)

+ 19 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/BusCustomerCompanyBusinessRepository.java

@@ -28,6 +28,25 @@ public interface BusCustomerCompanyBusinessRepository extends JpaRepository<BusC
      */
     List<BusCustomerCompanyBusiness> findByBusinessCode(String businessCode);
     
+    /**
+     * 根据客户类型和启用状态查找客户公司业务,按业务代码排序
+     * @param customerType 客户类型(1=代理公司,2=船公司)
+     * @param recordStatus 启用状态
+     * @return 客户公司业务列表
+     */
+    @Query(value = "SELECT * FROM Bus_CustomerCompanyBusiness WHERE CustomerType = :customerType AND RecordStatus > :recordStatus ORDER BY BusinessCode", nativeQuery = true)
+    List<BusCustomerCompanyBusiness> findByCustomerTypeAndRecordStatusGreaterThanOrderByBusinessCode(@Param("customerType") Integer customerType, @Param("recordStatus") Integer recordStatus);
+    
+    /**
+     * 根据客户类型、业务代码模糊查询和启用状态查找客户公司业务,按业务代码排序
+     * @param customerType 客户类型(1=代理公司,2=船公司)
+     * @param businessCode 业务代码
+     * @param recordStatus 启用状态
+     * @return 客户公司业务列表
+     */
+    @Query(value = "SELECT * FROM Bus_CustomerCompanyBusiness WHERE CustomerType = :customerType AND BusinessCode LIKE CONCAT('%', :businessCode, '%') AND RecordStatus > :recordStatus ORDER BY BusinessCode", nativeQuery = true)
+    List<BusCustomerCompanyBusiness> findByCustomerTypeAndBusinessCodeContainingAndRecordStatusGreaterThanOrderByBusinessCode(@Param("customerType") Integer customerType, @Param("businessCode") String businessCode, @Param("recordStatus") Integer recordStatus);
+    
     /**
      * 根据查询词搜索符合条件的客户公司业务 - 通过联合查询
      * 查找CustomerType=2(船公司)且RecordStatus=1的记录,并支持模糊搜索

+ 8 - 1
JavaBackend/src/main/java/com/lianda/backend/repository/BusPilotTypeSettingRepository.java

@@ -6,6 +6,13 @@ import org.springframework.stereotype.Repository;
 
 @Repository
 public interface BusPilotTypeSettingRepository extends JpaRepository<BusPilotTypeSetting, String> {
-    // 根据作业类型ID查询设置
+    /**
+     * 根据作业类型ID查询设置
+     */
     BusPilotTypeSetting findByPilotTypeIdAndRecordStatus(Integer pilotTypeId, Integer recordStatus);
+    
+    /**
+     * 根据作业类型ID查询设置
+     */
+    BusPilotTypeSetting findByPilotTypeId(Integer pilotTypeId);
 }

+ 5 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/DispPilotRepository.java

@@ -22,4 +22,9 @@ public interface DispPilotRepository extends JpaRepository<DispPilot, String> {
      * 根据姓名列表查找引航员
      */
     List<DispPilot> findByNameIn(List<String> names);
+    
+    /**
+     * 根据状态查找引航员
+     */
+    List<DispPilot> findByRecordStatusGreaterThan(Integer recordStatus);
 }

+ 16 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/DispPortRepository.java

@@ -2,6 +2,8 @@ package com.lianda.backend.repository;
 
 import com.lianda.backend.model.DispPort;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 import java.util.List;
 
@@ -32,4 +34,18 @@ public interface DispPortRepository extends JpaRepository<DispPort, String> {
      * 根据名称列表查找港口
      */
     List<DispPort> findByNameIn(List<String> names);
+    
+    /**
+     * 查询RecordStatus大于0的港口列表
+     * @return 港口列表
+     */
+    @Query("SELECT p FROM DispPort p WHERE p.recordStatus > 0 ORDER BY p.name")
+    List<DispPort> findAllByRecordStatusGreaterThan(int recordStatus);
+    
+    /**
+     * 查询RecordStatus大于0的港口列表(返回Map格式)
+     * @return 港口列表
+     */
+    @Query("SELECT new map(p.portId as value, p.name as label) FROM DispPort p WHERE p.recordStatus > 0 ORDER BY p.name")
+    List<java.util.Map<String, Object>> findAllByRecordStatusGreaterThanMap(int recordStatus);
 }

+ 22 - 5
JavaBackend/src/main/java/com/lianda/backend/repository/DispatcherRepositoryCustom.java

@@ -50,7 +50,9 @@ public class DispatcherRepositoryCustom {
             "d.RecordStatus, " +
             "ds.Name AS StatusName, " +
             "d.IsSent, " +
-            "pi.Name AS PilotName " +
+            "pi.Name AS PilotName, " +
+            "d.AgencyID, " +
+            "d.ShipownerID " +
             "FROM Disp_Dispatcher d " +
             "LEFT JOIN Disp_Port p ON d.PortID = p.PortID " +
             "LEFT JOIN Disp_Berthage fb ON d.FromBerthageID = fb.BerthageID " +
@@ -305,13 +307,18 @@ public class DispatcherRepositoryCustom {
         StringBuilder sql = new StringBuilder("SELECT ");
         
         List<String> names = new ArrayList<>();
+        boolean hasAgency = agencyId != null && !agencyId.isEmpty();
+        boolean hasShipowner = shipownerId != null && !shipownerId.isEmpty();
         
-        if (agencyId != null && !agencyId.isEmpty()) {
-            sql.append("(SELECT ccb.BusinessCode FROM Bus_CustomerCompanyBusiness ccb WHERE ccb.CustomerCompanyBusinessID = ?) AS AgencyName, ");
+        if (hasAgency) {
+            sql.append("(SELECT ccb.BusinessCode FROM Bus_CustomerCompanyBusiness ccb WHERE ccb.CustomerCompanyBusinessID = ?) AS AgencyName");
             names.add("AgencyName");
         }
         
-        if (shipownerId != null && !shipownerId.isEmpty()) {
+        if (hasShipowner) {
+            if (hasAgency) {
+                sql.append(", ");
+            }
             sql.append("(SELECT ccb.BusinessCode FROM Bus_CustomerCompanyBusiness ccb WHERE ccb.CustomerCompanyBusinessID = ?) AS ShipownerName");
             names.add("ShipownerName");
         }
@@ -321,6 +328,14 @@ public class DispatcherRepositoryCustom {
         }
         
         try {
+            List<Object> params = new ArrayList<>();
+            if (hasAgency) {
+                params.add(agencyId);
+            }
+            if (hasShipowner) {
+                params.add(shipownerId);
+            }
+            
             return jdbcTemplate.queryForObject(sql.toString(), (rs, rowNum) -> {
                 StringBuilder result = new StringBuilder();
                 for (String name : names) {
@@ -333,7 +348,7 @@ public class DispatcherRepositoryCustom {
                     }
                 }
                 return result.toString();
-            }, agencyId != null ? agencyId : "", shipownerId != null ? shipownerId : "");
+            }, params.toArray());
         } catch (Exception e) {
             return "";
         }
@@ -378,6 +393,8 @@ public class DispatcherRepositoryCustom {
             dto.setStatusValue(rs.getInt("RecordStatus"));
             dto.setStatusName(rs.getString("StatusName"));
             dto.setPilotName(rs.getString("PilotName"));
+            dto.setAgencyId(rs.getString("AgencyID"));
+            dto.setShipownerId(rs.getString("ShipownerID"));
             
             // 格式化时间
             Date startTime = rs.getTimestamp("StartTime");

+ 20 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/SysRoleRepository.java

@@ -4,6 +4,26 @@ import com.lianda.backend.model.SysRole;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
+/**
+ * 角色Repository接口
+ */
 @Repository
 public interface SysRoleRepository extends JpaRepository<SysRole, String> {
+
+    /**
+     * 根据角色名称模糊查询
+     */
+    List<SysRole> findByRoleNameContaining(String roleName);
+
+    /**
+     * 根据角色名称查询
+     */
+    SysRole findByRoleName(String roleName);
+
+    /**
+     * 根据状态查询
+     */
+    List<SysRole> findByRecordStatus(Integer recordStatus);
 }

+ 19 - 2
JavaBackend/src/main/java/com/lianda/backend/repository/SysUserRoleRepository.java

@@ -2,18 +2,35 @@ package com.lianda.backend.repository;
 
 import com.lianda.backend.model.SysUserRole;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 
 @Repository
 public interface SysUserRoleRepository extends JpaRepository<SysUserRole, String> {
     
-    @Query(value = "SELECT * FROM Sys_UserRole WHERE UserID = :userId", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user_sys_role WHERE UserID = :userId", nativeQuery = true)
     List<SysUserRole> findByUserId(@Param("userId") String userId);
     
-    @Query(value = "DELETE FROM Sys_UserRole WHERE UserID = :userId", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user_sys_role WHERE RoleID = :roleId", nativeQuery = true)
+    List<SysUserRole> findByRoleId(@Param("roleId") String roleId);
+    
+    @Modifying
+    @Transactional
+    @Query(value = "DELETE FROM sys_user_sys_role WHERE UserID = :userId", nativeQuery = true)
     void deleteByUserId(@Param("userId") String userId);
+    
+    @Modifying
+    @Transactional
+    @Query(value = "DELETE FROM sys_user_sys_role WHERE RoleID = :roleId", nativeQuery = true)
+    void deleteByRoleId(@Param("roleId") String roleId);
+    
+    @Modifying
+    @Transactional
+    @Query(value = "DELETE FROM sys_user_sys_role WHERE UserID = :userId AND RoleID = :roleId", nativeQuery = true)
+    void deleteByUserIdAndRoleId(@Param("userId") String userId, @Param("roleId") String roleId);
 }

+ 11 - 11
JavaBackend/src/main/java/com/lianda/backend/repository/UserRepository.java

@@ -11,36 +11,36 @@ import org.springframework.stereotype.Repository;
 @Repository
 public interface UserRepository extends JpaRepository<User, String> {
     
-    @Query(value = "SELECT * FROM Sys_User WHERE LoginID = :loginId", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE LoginID = :loginId", nativeQuery = true)
     User findByLoginId(@Param("loginId") String loginId);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE LoginID = :loginID", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE LoginID = :loginID", nativeQuery = true)
     User findByLoginID(@Param("loginID") String loginID);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE Name = :name", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE Name = :name", nativeQuery = true)
     User findByName(@Param("name") String name);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%')", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE Name LIKE CONCAT('%', :name, '%')", nativeQuery = true)
     Page<User> findByNameLike(@Param("name") String name, Pageable pageable);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE Name LIKE CONCAT('%', :name, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
     Page<User> findByNameLikeAndRecordStatus(@Param("name") String name, @Param("recordStatus") Integer recordStatus, Pageable pageable);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%') AND LoginID LIKE CONCAT('%', :loginID, '%')", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE Name LIKE CONCAT('%', :name, '%') AND LoginID LIKE CONCAT('%', :loginID, '%')", nativeQuery = true)
     Page<User> findByNameLikeAndLoginIDLike(@Param("name") String name, @Param("loginID") String loginID, Pageable pageable);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%') AND LoginID LIKE CONCAT('%', :loginID, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE Name LIKE CONCAT('%', :name, '%') AND LoginID LIKE CONCAT('%', :loginID, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
     Page<User> findByNameLikeAndLoginIDLikeAndRecordStatus(@Param("name") String name, @Param("loginID") String loginID, @Param("recordStatus") Integer recordStatus, Pageable pageable);
     
-    // @Query(value = "SELECT * FROM Sys_User WHERE EmployeeID = :employeeID", nativeQuery = true)
+    // @Query(value = "SELECT * FROM sys_user WHERE EmployeeID = :employeeID", nativeQuery = true)
     // User findByEmployeeID(@Param("employeeID") String employeeID);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE LoginID LIKE CONCAT('%', :loginID, '%')", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE LoginID LIKE CONCAT('%', :loginID, '%')", nativeQuery = true)
     Page<User> findByLoginIDLike(@Param("loginID") String loginID, Pageable pageable);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE RecordStatus = :recordStatus", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE RecordStatus = :recordStatus", nativeQuery = true)
     Page<User> findByRecordStatus(@Param("recordStatus") Integer recordStatus, Pageable pageable);
     
-    @Query(value = "SELECT * FROM Sys_User WHERE LoginID LIKE CONCAT('%', :loginID, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
+    @Query(value = "SELECT * FROM sys_user WHERE LoginID LIKE CONCAT('%', :loginID, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
     Page<User> findByLoginIDLikeAndRecordStatus(@Param("loginID") String loginID, @Param("recordStatus") Integer recordStatus, Pageable pageable);
 }

+ 201 - 119
JavaBackend/src/main/java/com/lianda/backend/service/CommonDataService.java

@@ -2,160 +2,242 @@ package com.lianda.backend.service;
 
 import com.lianda.backend.config.DataSource;
 import com.lianda.backend.config.RoutingDataSourceConfig;
-import com.lianda.backend.dto.PortDTO;
+import com.lianda.backend.dto.CustomerCompanyBusinessSearchResult;
 import com.lianda.backend.dto.ShippingCompanyDTO;
-import com.lianda.backend.model.BusCustomerCompanyBusiness;
+import com.lianda.backend.model.DispBerthage;
+import com.lianda.backend.model.DispBerthageDictionary;
 import com.lianda.backend.model.DispPort;
+import com.lianda.backend.model.DispPortDictionary;
 import com.lianda.backend.repository.BusCustomerCompanyBusinessRepository;
+import com.lianda.backend.repository.DispBerthageDictionaryRepository;
+import com.lianda.backend.repository.DispBerthageRepository;
+import com.lianda.backend.repository.DispPortDictionaryRepository;
 import com.lianda.backend.repository.DispPortRepository;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
-import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
-/**
- * 港口和船公司服务类
- */
 @Service
-@DataSource(value = RoutingDataSourceConfig.DataSourceType.BRANCH) // 读取分支机构业务数据
 public class CommonDataService {
 
     @Autowired
-    private DispPortRepository dispPortRepository;
+    private DispBerthageDictionaryRepository dispBerthageDictionaryRepository;
 
     @Autowired
-    private BusCustomerCompanyBusinessRepository busCustomerCompanyBusinessRepository;
+    private DispPortDictionaryRepository dispPortDictionaryRepository;
 
     @Autowired
-    @Qualifier("readJdbcTemplate")
-    private JdbcTemplate jdbcTemplate;
+    private DispBerthageRepository dispBerthageRepository;
+
+    @Autowired
+    private DispPortRepository dispPortRepository;
+
+    @Autowired
+    private BusCustomerCompanyBusinessRepository busCustomerCompanyBusinessRepository;
 
     /**
-     * 获取有效的港口列表(RecordStatus=1)
-     *
-     * @return 港口DTO列表
+     * 泊位信息内部类,包含泊位ID和港口ID
      */
-    public List<PortDTO> getValidPorts() {
-        // 查询所有有效港口(RecordStatus=1)
-        List<DispPort> validPorts = dispPortRepository.findAll().stream()
-                .filter(port -> port.getRecordStatus() != null && port.getRecordStatus() == 1)
-                .collect(Collectors.toList());
-
-        return validPorts.stream()
-                .map(port -> new PortDTO(port.getPortId(), port.getName(), port.getPortType()))
-                .collect(Collectors.toList());
+    public static class BerthageInfo {
+        String berthageId;
+        String portId;
+
+        BerthageInfo(String berthageId, String portId) {
+            this.berthageId = berthageId;
+            this.portId = portId;
+        }
     }
 
     /**
-     * 获取有效的船公司列表(RecordStatus=1且关联到CustomerType=2的记录)
-     *
-     * @return 船公司DTO列表
+     * 查找或创建泊位信息
+     * @param berthName 泊位名称
+     * @return 泊位信息
      */
-    public List<ShippingCompanyDTO> getValidShippingCompanies() {
-        // 查询CustomerType=2的所有客户ID
-        List<BusCustomerCustomerType> customerTypeRecords = busCustomerCustomerTypeRepository.findByCustomerType(2);
-        Set<String> shippingCompanyCustomerIds = customerTypeRecords.stream()
-                .map(BusCustomerCustomerType::getCustomerId)
-                .collect(Collectors.toSet());
-
-        // 查询所有有效的船公司业务记录(RecordStatus=1)
-        List<BusCustomerCompanyBusiness> validBusinessRecords = busCustomerCompanyBusinessRepository.findAll().stream()
-                .filter(business -> business.getRecordStatus() != null && business.getRecordStatus() == 1)
-                .filter(business -> shippingCompanyCustomerIds.contains(business.getCustomerCompanyId())) // 确保是船公司类型
-                .collect(Collectors.toList());
-
-        // 获取这些业务记录对应的客户公司ID
-        Set<String> customerCompanyIds = validBusinessRecords.stream()
-                .map(BusCustomerCompanyBusiness::getCustomerCompanyId)
-                .collect(Collectors.toSet());
-
-        // 查询对应的客户公司信息
-        List<BusCustomerCompany> shippingCompanies = busCustomerCompanyRepository.findAll().stream()
-                .filter(company -> customerCompanyIds.contains(company.getCustomerCompanyId()))
-                .collect(Collectors.toList());
-
-        return shippingCompanies.stream()
-                .map(company -> new ShippingCompanyDTO(
-                        company.getCustomerCompanyId(),
-                        company.getName(),
-                        company.getContact(),
-                        company.getTel(),
-                        company.getMobile()))
-                .collect(Collectors.toList());
+    @DataSource(value = RoutingDataSourceConfig.DataSourceType.COMMON)
+    @Transactional
+    public BerthageInfo findOrCreateBerthageInfo(String berthName) {
+        String portId = null;
+        String berthageId = null;
+        String portName = null;
+        String actualBerthName = null;
+
+        // 第一步:通过Disp_BerthageDictionary的Keyword完全匹配
+        List<DispBerthageDictionary> berthageDictionaries = dispBerthageDictionaryRepository.findByKeyword(berthName);
+        if (!berthageDictionaries.isEmpty()) {
+            DispBerthageDictionary dict = berthageDictionaries.get(0);
+            berthageId = dict.getBerthageId();
+
+            // 通过BerthageID查找对应的泊位记录
+            DispBerthage berthage = dispBerthageRepository.findById(berthageId).orElse(null);
+            if (berthage != null && berthage.getPortId() != null) {
+                DispPort port = dispPortRepository.findById(berthage.getPortId()).orElse(null);
+                if (port != null && port.getPortType() != null && port.getPortType() == 1) {
+                    // 是码头,使用这个PortID
+                    portId = berthage.getPortId();
+                    portName = port.getName();
+                    actualBerthName = berthage.getName();
+                }
+            }
+        }
+
+        // 第二步:如果第一步无法匹配,用Disp_PortDictionary的PortKeyword模糊匹配
+        if (portId == null) {
+            List<DispPortDictionary> portDictionaries = dispPortDictionaryRepository.findByPortKeywordContaining(berthName);
+            if (!portDictionaries.isEmpty()) {
+                DispPortDictionary portDict = portDictionaries.get(0);
+                portName = portDict.getName();
+                // 泊位名称就是除港口名称外的剩余部分
+                if (berthName.startsWith(portName)) {
+                    actualBerthName = berthName.substring(portName.length()).trim();
+                } else {
+                    actualBerthName = berthName;
+                }
+
+                // 查找对应的Port和Berthage记录
+                List<DispPort> ports = dispPortRepository.findByName(portName);
+                if (!ports.isEmpty()) {
+                    portId = ports.get(0).getPortId();
+                    List<DispBerthage> berthages = dispBerthageRepository.findByNameAndPortId(actualBerthName, portId);
+                    if (!berthages.isEmpty()) {
+                        berthageId = berthages.get(0).getBerthageId();
+                    }
+                }
+            }
+        }
+
+        // 第三步:如果第二步没有找到,检查是否为"数字+#号"格式
+        if (portId == null) {
+            java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^(.+?)\\s*(\\d+#)$");
+            java.util.regex.Matcher matcher = pattern.matcher(berthName);
+            if (matcher.matches()) {
+                portName = matcher.group(1).trim();
+                actualBerthName = matcher.group(2);
+
+                // 查找对应的Port和Berthage记录
+                List<DispPort> ports = dispPortRepository.findByName(portName);
+                if (!ports.isEmpty()) {
+                    DispPort port = ports.get(0);
+                    if (port.getPortType() != null && port.getPortType() == 1) {
+                        // 确保是码头
+                        portId = port.getPortId();
+                        List<DispBerthage> berthages = dispBerthageRepository.findByNameAndPortId(actualBerthName, portId);
+                        if (!berthages.isEmpty()) {
+                            berthageId = berthages.get(0).getBerthageId();
+                        }
+                    }
+                }
+            }
+        }
+
+        // 第四步:如果还没找到,说明是锚地
+        if (portId == null) {
+            portName = berthName;
+            actualBerthName = berthName;
+
+            // 查找或创建锚地Port记录(PortType=2)
+            List<DispPort> ports = dispPortRepository.findByName(portName);
+            if (!ports.isEmpty()) {
+                portId = ports.get(0).getPortId();
+            } else {
+                DispPort port = new DispPort();
+                port.setPortId(UUID.randomUUID().toString().replace("-", ""));
+                port.setName(portName);
+                port.setPortType(2); // 锚地
+                port.setRecordStatus(1);
+                port.setCreateTime(new Date());
+                port.setCreateUserId(getCurrentUserId());
+                port = dispPortRepository.save(port);
+                portId = port.getPortId();
+            }
+
+            // 查找或创建锚地Berthage记录
+            List<DispBerthage> berthages = dispBerthageRepository.findByNameAndPortId(actualBerthName, portId);
+            if (!berthages.isEmpty()) {
+                berthageId = berthages.get(0).getBerthageId();
+            } else {
+                DispBerthage berthage = new DispBerthage();
+                berthage.setBerthageId(UUID.randomUUID().toString().replace("-", ""));
+                berthage.setName(actualBerthName);
+                berthage.setPortId(portId);
+                berthage.setRecordStatus(1);
+                berthage.setCreateTime(new Date());
+                berthage.setCreateUserId(getCurrentUserId());
+                berthage = dispBerthageRepository.save(berthage);
+                berthageId = berthage.getBerthageId();
+            }
+        }
+
+        // 第五步:如果找到了portName和actualBerthName,但数据库中没有记录,则新增
+        if (portId != null && berthageId == null && portName != null && actualBerthName != null) {
+            // 创建Port记录(如果不存在)
+            List<DispPort> ports = dispPortRepository.findByName(portName);
+            if (!ports.isEmpty()) {
+                portId = ports.get(0).getPortId();
+            } else {
+                DispPort port = new DispPort();
+                port.setPortId(UUID.randomUUID().toString().replace("-", ""));
+                port.setName(portName);
+                port.setPortType(1); // 码头
+                port.setRecordStatus(1);
+                port.setCreateTime(new Date());
+                port.setCreateUserId("system");
+                port = dispPortRepository.save(port);
+                portId = port.getPortId();
+            }
+
+            // 创建Berthage记录
+            DispBerthage berthage = new DispBerthage();
+            berthage.setBerthageId(UUID.randomUUID().toString().replace("-", ""));
+            berthage.setName(actualBerthName);
+            berthage.setPortId(portId);
+            berthage.setRecordStatus(1);
+            berthage.setCreateTime(new Date());
+            berthage.setCreateUserId("system");
+            berthage = dispBerthageRepository.save(berthage);
+            berthageId = berthage.getBerthageId();
+        }
+
+        return new BerthageInfo(berthageId, portId);
     }
 
     /**
-     * 搜索船公司列表(支持分页和模糊搜索)
+     * 搜索船公司列表
      * @param companyName 船公司名称(支持模糊搜索)
      * @param pageable 分页参数
      * @return 船公司分页列表
      */
+    @DataSource(value = RoutingDataSourceConfig.DataSourceType.BRANCH)
+    @Transactional(readOnly = true)
     public Page<ShippingCompanyDTO> searchShippingCompanies(String companyName, Pageable pageable) {
-        // 构建SQL查询
-        StringBuilder sql = new StringBuilder(
-            "SELECT ccb.CustomerCompanyBusinessId, cc.Name, ccb.BusinessCode " +
-            "FROM Bus_CustomerCompanyBusiness ccb " +
-            "INNER JOIN Bus_CustomerCompany cc ON ccb.CustomerCompanyID = cc.CustomerCompanyID " +
-            "INNER JOIN Bus_Customer_CustomerType cct ON cc.CustomerCompanyID = cct.CustomerID " +
-            "WHERE cct.CustomerType = 2 " +
-            "AND ccb.RecordStatus = 1 " +
-            "AND cc.RecordStatus = 1"
-        );
+        Page<CustomerCompanyBusinessSearchResult> results = busCustomerCompanyBusinessRepository.searchShippingCompaniesWithPage(companyName, pageable);
         
-        // 添加模糊搜索条件
-        if (companyName != null && !companyName.isEmpty()) {
-            sql.append(" AND (LOWER(ccb.BusinessCode) LIKE CONCAT('%', LOWER(?), '%') OR LOWER(cc.Name) LIKE CONCAT('%', LOWER(?), '%'))");
-        }
-        
-        // 查询总数
-        String countSql = "SELECT COUNT(*) " + sql.substring(sql.indexOf("FROM"));
-        Integer total;
-        if (companyName != null && !companyName.isEmpty()) {
-            total = jdbcTemplate.queryForObject(countSql, Integer.class, companyName, companyName);
-        } else {
-            total = jdbcTemplate.queryForObject(countSql, Integer.class);
-        }
+        List<ShippingCompanyDTO> dtos = results.getContent().stream()
+            .map(result -> new ShippingCompanyDTO(
+                result.getCustomerCompanyBusinessID(),
+                result.getCustomerName(),
+                result.getBusinessCode(),
+                null,
+                null,
+                null
+            ))
+            .collect(Collectors.toList());
         
-        // 添加排序和分页
-        sql.append(" ORDER BY cc.Name");
-        sql.append(" LIMIT ").append(pageable.getPageSize());
-        sql.append(" OFFSET ").append(pageable.getOffset());
-        
-        // 查询数据
-        List<ShippingCompanyDTO> shippingCompanies;
-        if (companyName != null && !companyName.isEmpty()) {
-            shippingCompanies = jdbcTemplate.query(sql.toString(), 
-                (rs, rowNum) -> new ShippingCompanyDTO(
-                    rs.getString("CustomerCompanyBusinessId"),
-                    rs.getString("Name"),
-                    null,
-                    null,
-                    null
-                ), companyName, companyName);
-        } else {
-            shippingCompanies = jdbcTemplate.query(sql.toString(), 
-                (rs, rowNum) -> new ShippingCompanyDTO(
-                    rs.getString("CustomerCompanyBusinessId"),
-                    rs.getString("Name"),
-                    null,
-                    null,
-                    null
-                ));
-        }
-        
-        // 创建新的Page对象
-        return new PageImpl<>(
-                shippingCompanies,
-                pageable,
-                total
-        );
+        return new org.springframework.data.domain.PageImpl<>(dtos, pageable, results.getTotalElements());
+    }
+    
+    /**
+     * 获取当前登录用户ID
+     * @return 用户ID
+     */
+    private String getCurrentUserId() {
+        String userId = com.lianda.backend.util.CurrentUserUtil.getCurrentUserId();
+        return userId != null ? userId : "admin"; // 如果获取不到当前用户ID,则使用"admin"
     }
-}
+}

+ 4 - 0
JavaBackend/src/main/java/com/lianda/backend/service/DispatcherService.java

@@ -80,6 +80,10 @@ public class DispatcherService {
             if (!confirmedTugboatNames.isEmpty()) {
                 dto.setConfirmedTugboatNames(String.join(", ", confirmedTugboatNames));
             }
+            
+            // 查询客户名称(代理公司名+空格+船公司名)
+            String customerName = dispatcherRepositoryCustom.queryCustomerName(dto.getAgencyId(), dto.getShipownerId());
+            dto.setCustomerName(customerName);
         }
         
         int total = dispatcherRepositoryCustom.queryDispatcherCount(query);

+ 51 - 54
JavaBackend/src/main/java/com/lianda/backend/service/PilotPlanService.java

@@ -70,6 +70,9 @@ public class PilotPlanService {
     @Autowired
     private DispBerthageSettingRepository dispBerthageSettingRepository;
     
+    @Autowired
+    private CommonDataService commonDataService;
+    
     private static final List<String> LOCAL_PORTS = new ArrayList<>();
     
     static {
@@ -190,7 +193,7 @@ public class PilotPlanService {
         
         Map<String, String> shipIdMap = getOrCreateShipIds(importDTOs);
         Map<String, Integer> pilotTypeMap = getPilotTypes(importDTOs);
-        Map<String, String> berthageIdMap = getOrCreateBerthageIds(importDTOs);
+        Map<String, CommonDataService.BerthageInfo> berthageInfoMap = getOrCreateBerthageIds(importDTOs);
         Map<String, String> pilotIdMap = getOrCreatePilotIds(importDTOs);
         Map<String, String> waterwayIdMap = getOrCreateWaterwayIds(importDTOs);
         Map<String, String> agencyIdMap = getOrCreateAgencyIds(importDTOs);
@@ -221,7 +224,7 @@ public class PilotPlanService {
                 } else {
                     // 更新现有记录的字段
                     DispPilotPlan existingPlan = existingPilotPlans.get(0); // 使用第一个匹配的记录
-                    updateExistingPilotPlan(existingPlan, importDTO, shipId, pilotTypeMap, berthageIdMap, pilotIdMap, waterwayIdMap, agencyIdMap);
+                    updateExistingPilotPlan(existingPlan, importDTO, shipId, pilotTypeMap, berthageInfoMap, pilotIdMap, waterwayIdMap, agencyIdMap);
                     dispPilotPlanWriteRepository.save(existingPlan);
                 }
             } else {
@@ -231,8 +234,10 @@ public class PilotPlanService {
                 pilotPlan.setPlanTime(planTime);
                 pilotPlan.setShipId(shipId);
                 pilotPlan.setPilotType(pilotTypeMap.get(importDTO.getDynamic()));
-                pilotPlan.setFromBerthageId(berthageIdMap.get(importDTO.getStartBerth()));
-                pilotPlan.setToBerthageId(berthageIdMap.get(importDTO.getEndBerth()));
+                CommonDataService.BerthageInfo fromBerthageInfo = berthageInfoMap.get(importDTO.getStartBerth());
+                CommonDataService.BerthageInfo toBerthageInfo = berthageInfoMap.get(importDTO.getEndBerth());
+                pilotPlan.setFromBerthageId(fromBerthageInfo != null ? fromBerthageInfo.berthageId : null);
+                pilotPlan.setToBerthageId(toBerthageInfo != null ? toBerthageInfo.berthageId : null);
                 pilotPlan.setMainPilotId(pilotIdMap.get(importDTO.getMainPilot()));
                 pilotPlan.setAssitPilotId(pilotIdMap.get(importDTO.getDeputyPilot()));
                 pilotPlan.setWaterwayId(waterwayIdMap.get(importDTO.getWaterway()));
@@ -243,12 +248,12 @@ public class PilotPlanService {
                 pilotPlan.setRemark(importDTO.getRemark());
                 pilotPlan.setRecordStatus(1);
                 pilotPlan.setCreateTime(new Date());
-                pilotPlan.setCreateUserId("system");
+                pilotPlan.setCreateUserId(getCurrentUserId());
                 
                 dispPilotPlanWriteRepository.save(pilotPlan);
                 
                 // 同步到调度表
-                syncToDispatcherTable(pilotPlan, importDTO);
+                syncToDispatcherTable(pilotPlan, importDTO, fromBerthageInfo, toBerthageInfo);
                 importedCount++;
             }
         }
@@ -257,13 +262,15 @@ public class PilotPlanService {
     }
     
     private void updateExistingPilotPlan(DispPilotPlan existingPlan, PilotPlanImportDTO importDTO, 
-                                        String shipId, Map<String, Integer> pilotTypeMap, Map<String, String> berthageIdMap, 
+                                        String shipId, Map<String, Integer> pilotTypeMap, Map<String, CommonDataService.BerthageInfo> berthageInfoMap, 
                                         Map<String, String> pilotIdMap, Map<String, String> waterwayIdMap, Map<String, String> agencyIdMap) throws ParseException {
         existingPlan.setPlanTime(parsePlanTime(importDTO.getPlanTime()));
         existingPlan.setShipId(shipId);
         existingPlan.setPilotType(pilotTypeMap.get(importDTO.getDynamic()));
-        existingPlan.setFromBerthageId(berthageIdMap.get(importDTO.getStartBerth()));
-        existingPlan.setToBerthageId(berthageIdMap.get(importDTO.getEndBerth()));
+        CommonDataService.BerthageInfo fromBerthageInfo = berthageInfoMap.get(importDTO.getStartBerth());
+        CommonDataService.BerthageInfo toBerthageInfo = berthageInfoMap.get(importDTO.getEndBerth());
+        existingPlan.setFromBerthageId(fromBerthageInfo != null ? fromBerthageInfo.berthageId : null);
+        existingPlan.setToBerthageId(toBerthageInfo != null ? toBerthageInfo.berthageId : null);
         existingPlan.setMainPilotId(pilotIdMap.get(importDTO.getMainPilot()));
         existingPlan.setAssitPilotId(pilotIdMap.get(importDTO.getDeputyPilot()));
         existingPlan.setWaterwayId(waterwayIdMap.get(importDTO.getWaterway()));
@@ -273,10 +280,10 @@ public class PilotPlanService {
         existingPlan.setSideThruster(importDTO.getSideThruster());
         existingPlan.setRemark(importDTO.getRemark());
         existingPlan.setModifyTime(new Date());
-        existingPlan.setModifyUserId("system");
+        existingPlan.setModifyUserId(getCurrentUserId());
     }
     
-    private void syncToDispatcherTable(DispPilotPlan pilotPlan, PilotPlanImportDTO importDTO) {
+    private void syncToDispatcherTable(DispPilotPlan pilotPlan, PilotPlanImportDTO importDTO, CommonDataService.BerthageInfo fromBerthageInfo, CommonDataService.BerthageInfo toBerthageInfo) {
         // 通过PilotPlanId匹配Disp_Dispatcher的记录
         DispDispatcher dispatcher = dispDispatcherRepository.findByPilotPlanId(pilotPlan.getPilotPlanId());
         
@@ -298,23 +305,17 @@ public class PilotPlanService {
         
         // 设置PortID:如果开始泊位对应的港口为码头(PortType=1),则PortID为开始泊位对应的港口PortID,否则是结束泊位对应的港口的PortID
         String portId = null;
-        if (pilotPlan.getFromBerthageId() != null) {
-            DispBerthage fromBerthage = dispBerthageRepository.findById(pilotPlan.getFromBerthageId()).orElse(null);
-            if (fromBerthage != null && fromBerthage.getPortId() != null) {
-                DispPort fromPort = dispPortRepository.findById(fromBerthage.getPortId()).orElse(null);
-                if (fromPort != null && fromPort.getPortType() != null && fromPort.getPortType() == 1) {
-                    // 开始泊位对应的港口为码头,使用开始泊位的PortID
-                    portId = fromBerthage.getPortId();
-                }
+        if (fromBerthageInfo != null && fromBerthageInfo.portId != null) {
+            DispPort fromPort = dispPortRepository.findById(fromBerthageInfo.portId).orElse(null);
+            if (fromPort != null && fromPort.getPortType() != null && fromPort.getPortType() == 1) {
+                // 开始泊位对应的港口为码头,使用开始泊位的PortID
+                portId = fromBerthageInfo.portId;
             }
         }
         
         // 如果开始泊位不是码头,则检查结束泊位
-        if (portId == null && pilotPlan.getToBerthageId() != null) {
-            DispBerthage toBerthage = dispBerthageRepository.findById(pilotPlan.getToBerthageId()).orElse(null);
-            if (toBerthage != null && toBerthage.getPortId() != null) {
-                portId = toBerthage.getPortId();
-            }
+        if (portId == null && toBerthageInfo != null && toBerthageInfo.portId != null) {
+            portId = toBerthageInfo.portId;
         }
         
         dispatcher.setPortId(portId);
@@ -325,6 +326,8 @@ public class PilotPlanService {
         dispatcher.setDeep(pilotPlan.getDeep());
         dispatcher.setAgencyId(pilotPlan.getAgencyId());
         dispatcher.setPilotTypeId(pilotPlan.getPilotType());
+        dispatcher.setPilotId(pilotPlan.getMainPilotId()); // 复制主引到调度的引航员列
+        dispatcher.setRemark(pilotPlan.getRemark()); // 复制备注
         
         // 根据PilotTypeId找到Bus_PilotTypeSetting对应的Hours字段
         BusPilotTypeSetting pilotTypeSetting = busPilotTypeSettingRepository.findByPilotTypeIdAndRecordStatus(pilotPlan.getPilotType(), 1);
@@ -591,8 +594,7 @@ public class PilotPlanService {
         return pilotTypeMap;
     }
     
-    @DataSource(value = RoutingDataSourceConfig.DataSourceType.COMMON) // 写入公共数据
-    private Map<String, String> getOrCreateBerthageIds(List<PilotPlanImportDTO> importDTOs) {
+    private Map<String, CommonDataService.BerthageInfo> getOrCreateBerthageIds(List<PilotPlanImportDTO> importDTOs) {
         Set<String> uniqueBerthNames = new HashSet<>();
         
         for (PilotPlanImportDTO importDTO : importDTOs) {
@@ -604,38 +606,33 @@ public class PilotPlanService {
             }
         }
         
-        List<DispBerthage> existingBerthages = dispBerthageRepository.findByNameIn(new ArrayList<>(uniqueBerthNames));
-        
-        Map<String, String> berthageIdMap = new HashMap<>();
-        
-        for (DispBerthage berthage : existingBerthages) {
-            berthageIdMap.put(berthage.getName(), berthage.getBerthageId());
-        }
-        
-        List<DispBerthage> berthagesToSave = new ArrayList<>();
+        Map<String, CommonDataService.BerthageInfo> berthageInfoMap = new HashMap<>();
         
         for (String berthName : uniqueBerthNames) {
-            if (!berthageIdMap.containsKey(berthName)) {
-                DispBerthage berthage = new DispBerthage();
-                berthage.setBerthageId(UUID.randomUUID().toString().replace("-", ""));
-                berthage.setName(berthName);
-                berthage.setRecordStatus(1);
-                berthage.setCreateTime(new Date());
-                berthage.setCreateUserId("system");
-                
-                berthagesToSave.add(berthage);
-            }
-        }
-        
-        if (!berthagesToSave.isEmpty()) {
-            dispBerthageRepository.saveAll(berthagesToSave);
-            
-            for (DispBerthage berthage : berthagesToSave) {
-                berthageIdMap.put(berthage.getName(), berthage.getBerthageId());
-            }
+            CommonDataService.BerthageInfo berthageInfo = commonDataService.findOrCreateBerthageInfo(berthName);
+            berthageInfoMap.put(berthName, berthageInfo);
         }
         
-        return berthageIdMap;
+        return berthageInfoMap;
+    }
+    
+    @DataSource(value = RoutingDataSourceConfig.DataSourceType.BRANCH) // 写入分支机构业务数据
+    private void createBerthageSetting(String berthageId) {
+        // 检查是否已存在
+        DispBerthageSetting existingSetting = dispBerthageSettingRepository.findByBerthageId(berthageId);
+        if (existingSetting != null) {
+            return;
+        }
+        
+        // 创建Disp_BerthageSetting记录,IsInner默认为true
+        DispBerthageSetting setting = new DispBerthageSetting();
+        setting.setBerthageSettingId(UUID.randomUUID().toString().replace("-", ""));
+        setting.setBerthageId(berthageId);
+        setting.setIsInner(true);
+        setting.setRecordStatus(1);
+        setting.setCreateTime(new Date());
+        setting.setCreateUserId("system");
+        dispBerthageSettingRepository.save(setting);
     }
     
     @DataSource(value = RoutingDataSourceConfig.DataSourceType.COMMON) // 写入公共数据

+ 4 - 2
vue-frontend/src/components/HomePage.vue

@@ -450,7 +450,7 @@ export default {
   top: 0;
   left: 0;
   right: 0;
-  z-index: 1000;
+  z-index: 2000;
   height: 64px;
 }
 
@@ -554,7 +554,7 @@ export default {
   overflow-y: auto;
   overflow-x: hidden; /* 隐藏水平滚动条 */
   transition: all 0.3s ease;
-  z-index: 999;
+  z-index: 1500;
 }
 
 .sidebar {
@@ -733,6 +733,8 @@ export default {
   padding: 20px;
   background-color: #ecf0f5;
   transition: all 0.3s ease;
+  position: relative;
+  z-index: 1;
 }
 
 .container-fluid {

+ 1 - 1
vue-frontend/src/components/RoleManagement.vue

@@ -42,7 +42,7 @@
         border
         size="default"
         :header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: 'bold' }"
-        style="width: calc(100vw - 331px); height: calc(100vh - 395px);"
+        style="width: calc(100vw - 331px); height: calc(100vh - 425px);"
         @selection-change="handleSelectionChange"
         @sort-change="handleSortChange">
         <el-table-column type="selection" width="55" fixed align="center" />

+ 19 - 5
vue-frontend/src/components/TabsView.vue

@@ -70,7 +70,8 @@ export default {
         'home.settings_announcement': 'AnnouncementManagement',
         'home.settings_role': 'RoleManagement',
         'home.settings_user': 'UserManagement',
-        'home.pilot_plan': 'PilotPlan'
+        'home.pilot_plan': 'PilotPlan',
+        'home.dispatcher_entry': 'DispatcherEntry'
       }
       return this.tabs.map(tab => routeToComponentMap[tab.name] || tab.name)
     },
@@ -151,7 +152,8 @@ export default {
         'home.settings_announcement': '公告管理',
         'home.settings_role': '角色管理',
         'home.settings_user': '用户管理',
-        'home.pilot_plan': '引航计划'
+        'home.pilot_plan': '引航计划',
+        'home.dispatcher_entry': '调度录入'
       }
       
       return titleMap[route.name] || route.name
@@ -250,7 +252,7 @@ export default {
   flex-direction: column !important;
   background-color: #fff;
   position: relative !important;
-  z-index: 1000 !important;
+  z-index: 1 !important;
   visibility: visible !important;
 }
 
@@ -263,11 +265,14 @@ export default {
   padding: 0 16px !important;
   height: 40px !important;
   user-select: none !important;
-  position: relative !important;
+  position: fixed !important;
+  top: 64px !important;
+  left: 220px !important;
+  right: 0 !important;
   z-index: 1000 !important;
   visibility: visible !important;
   opacity: 1 !important;
-  width: 100% !important;
+  width: auto !important;
   flex-shrink: 0 !important;
 }
 
@@ -384,12 +389,21 @@ export default {
   flex: 1;
   overflow: hidden;
   background-color: #ecf0f5;
+  padding-top: 40px !important;
+  height: 100% !important;
 }
 
 /* 响应式设计 */
+@media (max-width: 1200px) {
+  .tabs-header {
+    left: 200px !important;
+  }
+}
+
 @media (max-width: 768px) {
   .tabs-header {
     padding: 0 8px;
+    left: 180px !important;
   }
   
   .tab-item {

+ 1 - 1
vue-frontend/src/components/UserManagement.vue

@@ -61,7 +61,7 @@
         border
         size="default"
         :header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: 'bold' }"
-        style="width: calc(100vw - 331px); height: calc(100vh - 395px);"
+        style="width: calc(100vw - 331px); height: calc(100vh - 425px);"
         @selection-change="handleSelectionChange"
         @sort-change="handleSortChange">
         <el-table-column type="selection" width="55" fixed align="center"></el-table-column>

+ 7 - 0
vue-frontend/src/router/index.js

@@ -46,6 +46,12 @@ const routes = [
         name: 'home.pilot_plan',
         // 懒加载引航计划页面组件
         component: () => import('../components/PilotPlan.vue')
+      },
+      {
+        path: 'dispatcher-entry',
+        name: 'home.dispatcher_entry',
+        // 懒加载调度录入页面组件
+        component: () => import('../components/DispatcherEntry.vue')
       }
     ]
   },
@@ -86,6 +92,7 @@ router.beforeEach((to, from, next) => {
   const token = localStorage.getItem('token')
   if (!token) {
     // 未登录,重定向到登录页面
+    localStorage.removeItem('userInfo')
     next({ name: 'login' })
     return
   }

+ 7 - 12
vue-frontend/src/utils/request.js

@@ -1,21 +1,15 @@
-import axios from 'axios'
+import axios from 'axios'
 
-/**
- * 创建axios实例
- */
 const request = axios.create({
   baseURL: '/api',
   timeout: 30000
 })
 
-/**
- * 请求拦截器 - 自动添加token
- */
 request.interceptors.request.use(
   config => {
     const token = localStorage.getItem('token')
     if (token) {
-      config.headers['Authorization'] = `Bearer ${token}`
+      config.headers['Authorization'] = 'Bearer ' + token
     }
     return config
   },
@@ -25,9 +19,6 @@ request.interceptors.request.use(
   }
 )
 
-/**
- * 响应拦截器 - 处理token过期
- */
 request.interceptors.response.use(
   response => {
     return response
@@ -38,10 +29,14 @@ request.interceptors.response.use(
         case 401:
           console.error('未授权,请重新登录')
           localStorage.removeItem('token')
+          localStorage.removeItem('userInfo')
           window.location.href = '/login'
           break
         case 403:
-          console.error('没有权限访问')
+          console.error('没有权限访问,请重新登录')
+          localStorage.removeItem('token')
+          localStorage.removeItem('userInfo')
+          window.location.href = '/login'
           break
         case 404:
           console.error('请求的资源不存在')