From 59c84ef4d5af2f97ce1a8dd3e9ee1069c0d438c1 Mon Sep 17 00:00:00 2001 From: hzjcd <3357883100@qq.com> Date: Sat, 29 Nov 2025 17:08:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BF=A1=E6=81=AF=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA=E8=AE=B0=E5=BD=95=E6=8F=92=E5=85=A5=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E9=93=83=E9=93=9B=E5=9B=BE=E6=A0=87=E5=92=8C=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- INFORMATION_TRACKING_SCHEME.md | 377 ++++++++++++++++++ .../example/web/config/DynamicDataSource.java | 5 + .../web/controller/CustomerController.java | 73 ++++ .../web/controller/FollowupController.java | 168 +++++++- .../controller/InformationTraController.java | 183 +++++++++ .../controller/SupplyCustomerController.java | 73 ++++ .../example/web/entity/InformationTra.java | 115 ++++++ .../web/mapper/InformationTraMapper.java | 18 + .../example/web/service/FollowUpService.java | 8 +- .../web/service/InformationTraService.java | 105 +++++ src/main/resources/application.yaml | 1 + .../resources/mapper/InformationTraMapper.xml | 35 ++ src/main/resources/static/loginmm.html | 2 +- src/main/resources/static/mainapp-sells.html | 236 ++++++++++- .../resources/static/mainapp-supplys.html | 236 ++++++++++- 15 files changed, 1618 insertions(+), 17 deletions(-) create mode 100644 INFORMATION_TRACKING_SCHEME.md create mode 100644 src/main/java/com/example/web/controller/InformationTraController.java create mode 100644 src/main/java/com/example/web/entity/InformationTra.java create mode 100644 src/main/java/com/example/web/mapper/InformationTraMapper.java create mode 100644 src/main/java/com/example/web/service/InformationTraService.java create mode 100644 src/main/resources/mapper/InformationTraMapper.xml diff --git a/INFORMATION_TRACKING_SCHEME.md b/INFORMATION_TRACKING_SCHEME.md new file mode 100644 index 0000000..4cfa50e --- /dev/null +++ b/INFORMATION_TRACKING_SCHEME.md @@ -0,0 +1,377 @@ +# 客户信息跟踪系统解决方案 + +## 1. 需求分析 + +### 1.1 功能需求 +- 前端记录业务员操作事件(查看详情、编辑、跟进) +- 获取当前账号信息(公司、部门、组织、角色、姓名) +- 根据电话号码在两个数据源中查询客户 +- 将操作记录写入`informationtra`表 + +### 1.2 数据源要求 +- `wechat`数据源:`users`表和`informationtra`表 +- `primary`数据源:`contacts`表 + +### 1.3 数据流程 +1. 前端传递操作事件、电话号码、事件类型 +2. 后端获取当前用户认证信息 +3. 根据电话号码查询两个数据源 +4. 将查询结果与认证信息结合写入`informationtra`表 + +## 2. 数据库设计 + +### 2.1 现有表结构 +```sql +CREATE TABLE `informationtra` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `tracompany` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者公司', + `tradepartment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者部门', + `traorganization` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者组织', + `trarole` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者角色', + `trauserName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改者名字', + `traassistant` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改协助人', + `userId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户ID', + `operationEvent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '操作事件', + `operationTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_userId` (`userId`), + KEY `idx_operationTime` (`operationTime`), + KEY `idx_trauserName` (`trauserName`), + CONSTRAINT `fk_informationtra_userId` FOREIGN KEY (`userId`) REFERENCES `users` (`userId`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='信息跟踪表'; +``` + +## 3. 代码设计 + +### 3.1 实体类设计 + +#### InformationTra.java +```java +package com.example.web.entity; + +import java.time.LocalDateTime; + +public class InformationTra { + private Integer id; + private String tracompany; + private String tradepartment; + private String traorganization; + private String trarole; + private String trauserName; + private String traassistant; + private String userId; + private String operationEvent; + private LocalDateTime operationTime; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + // Getters and Setters + // ... +} +``` + +### 3.2 Mapper设计 + +#### InformationTraMapper.java +```java +package com.example.web.mapper; + +import com.example.web.entity.InformationTra; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface InformationTraMapper { + /** + * 插入操作记录 + */ + int insertInformationTra(InformationTra informationTra); + + /** + * 根据userId查询操作记录 + */ + InformationTra selectByUserId(@Param("userId") String userId); +} +``` + +#### InformationTraMapper.xml +```xml + + + + + + + + + + + + + + + + + + + + + INSERT INTO informationtra ( + tracompany, tradepartment, traorganization, trarole, + trauserName, traassistant, userId, operationEvent, + operationTime, created_at, updated_at + ) VALUES ( + #{tracompany}, #{tradepartment}, #{traorganization}, #{trarole}, + #{trauserName}, #{traassistant}, #{userId}, #{operationEvent}, + #{operationTime}, #{createdAt}, #{updatedAt} + ) + + + + +``` + +### 3.3 Service设计 + +#### InformationTraService.java +```java +package com.example.web.service; + +import com.example.web.dto.ManagerAuthInfo; +import com.example.web.entity.InformationTra; +import com.example.web.mapper.*; +import com.example.web.config.DynamicDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +public class InformationTraService { + + @Autowired + private InformationTraMapper informationTraMapper; + + @Autowired + private UsersMapper usersMapper; + + @Autowired + private ContactsMapper contactsMapper; + + /** + * 记录操作事件 + */ + public boolean recordOperationEvent(String phoneNumber, String operationEvent, ManagerAuthInfo authInfo) { + try { + // 1. 从两个数据源查询客户信息 + String userId = null; + + // 查询wechat数据源的users表 + DynamicDataSource.setDataSourceKey("wechat"); + com.example.web.entity.Users wechatUser = usersMapper.selectByPhoneNumber(phoneNumber); + + if (wechatUser != null) { + userId = wechatUser.getUserId(); + } else { + // 查询primary数据源的contacts表 + DynamicDataSource.setDataSourceKey("primary"); + com.example.web.entity.Contacts contact = contactsMapper.selectByPhoneNumber(phoneNumber); + + if (contact != null) { + userId = contact.getId(); + } + } + + // 如果都没找到,返回失败 + if (userId == null) { + return false; + } + + // 2. 构造操作记录 + InformationTra informationTra = new InformationTra(); + informationTra.setTracompany(authInfo.getManagercompany()); + informationTra.setTradepartment(authInfo.getManagerdepartment()); + informationTra.setTraorganization(authInfo.getOrganization()); + informationTra.setTrarole(authInfo.getRole()); + informationTra.setTrauserName(authInfo.getUserName()); + informationTra.setTraassistant(authInfo.getAssistant()); + informationTra.setUserId(userId); + informationTra.setOperationEvent(operationEvent); + informationTra.setOperationTime(LocalDateTime.now()); + informationTra.setCreatedAt(LocalDateTime.now()); + informationTra.setUpdatedAt(LocalDateTime.now()); + + // 3. 写入wechat数据源的informationtra表 + DynamicDataSource.setDataSourceKey("wechat"); + int result = informationTraMapper.insertInformationTra(informationTra); + + return result > 0; + } finally { + // 清除数据源标识 + DynamicDataSource.clearDataSourceKey(); + } + } +} +``` + +### 3.4 Controller设计 + +#### InformationTraController.java +```java +package com.example.web.controller; + +import com.example.web.dto.ManagerAuthInfo; +import com.example.web.service.InformationTraService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/information-tracking") +public class InformationTraController { + + @Autowired + private InformationTraService informationTraService; + + /** + * 记录操作事件 + */ + @PostMapping("/record") + public ResponseEntity> recordOperationEvent( + @RequestBody Map requestBody, + HttpServletRequest request) { + + Map response = new HashMap<>(); + + try { + // 获取请求参数 + String phoneNumber = requestBody.get("phoneNumber"); + String operationEvent = requestBody.get("operationEvent"); + + // 验证参数 + if (phoneNumber == null || phoneNumber.isEmpty() || operationEvent == null || operationEvent.isEmpty()) { + response.put("success", false); + response.put("message", "电话号码和操作事件不能为空"); + return ResponseEntity.badRequest().body(response); + } + + // 获取当前用户认证信息 + String isSupplySideParam = request.getParameter("isSupplySide"); + boolean isSupplySide = !"false".equalsIgnoreCase(isSupplySideParam); + + ManagerAuthInfo authInfo = null; + if (isSupplySide) { + // 供应端获取认证信息的逻辑 + authInfo = getManagerAuthInfoFromRequest(request, true); + } else { + // 销售端获取认证信息的逻辑 + authInfo = getManagerAuthInfoFromRequest(request, false); + } + + if (authInfo == null) { + response.put("success", false); + response.put("message", "用户未登录或认证信息缺失"); + return ResponseEntity.status(401).body(response); + } + + // 记录操作事件 + boolean success = informationTraService.recordOperationEvent(phoneNumber, operationEvent, authInfo); + + if (success) { + response.put("success", true); + response.put("message", "操作记录成功"); + return ResponseEntity.ok(response); + } else { + response.put("success", false); + response.put("message", "未找到对应的客户信息"); + return ResponseEntity.badRequest().body(response); + } + + } catch (Exception e) { + response.put("success", false); + response.put("message", "记录操作事件失败: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 从请求中获取认证信息(复用现有逻辑) + */ + private ManagerAuthInfo getManagerAuthInfoFromRequest(HttpServletRequest request, boolean isSupplySide) { + // 复用SupplyCustomerController或CustomerController中的现有逻辑 + // 这里需要根据实际情况实现 + return null; + } +} +``` + +### 3.5 前端调用示例 + +```javascript +// 前端调用API示例 +function recordOperation(phoneNumber, operationEvent) { + fetch('/api/information-tracking/record?isSupplySide=true', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + phoneNumber: phoneNumber, + operationEvent: operationEvent + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + console.log('操作记录成功'); + } else { + console.error('操作记录失败:', data.message); + } + }) + .catch(error => { + console.error('API调用失败:', error); + }); +} + +// 查看客户详情时调用 +recordOperation('13800138000', '查看客户详情'); + +// 编辑客户信息后调用 +recordOperation('13800138000', '修改客户信息'); + +// 跟进客户后调用 +recordOperation('13800138000', '更新客户跟进'); +``` + +## 4. 实现步骤 + +1. 创建InformationTra实体类 +2. 创建InformationTraMapper接口和XML文件 +3. 创建InformationTraService +4. 创建InformationTraController +5. 在前端页面添加API调用逻辑 +6. 测试功能完整性 + +## 5. 注意事项 + +1. 确保数据源切换正确,避免数据查询错误 +2. 验证电话号码格式,确保查询准确性 +3. 处理并发情况,确保操作记录的完整性 +4. 添加适当的日志记录,便于调试和监控 +5. 考虑添加权限控制,确保只有授权用户可以调用API + +## 6. 扩展建议 + +1. 添加操作记录查询功能,便于查看历史操作 +2. 实现操作记录统计分析,提供数据可视化 +3. 添加操作记录导出功能,支持Excel或PDF格式 +4. 实现操作记录告警功能,对异常操作进行告警 +5. 添加操作记录审计功能,满足合规要求 \ No newline at end of file diff --git a/src/main/java/com/example/web/config/DynamicDataSource.java b/src/main/java/com/example/web/config/DynamicDataSource.java index 2def525..a4e3f21 100644 --- a/src/main/java/com/example/web/config/DynamicDataSource.java +++ b/src/main/java/com/example/web/config/DynamicDataSource.java @@ -17,6 +17,11 @@ public class DynamicDataSource extends AbstractRoutingDataSource { public static void clearDataSourceKey() { contextHolder.remove(); } + + // 获取当前数据源(供业务逻辑调用,保存原始数据源) + public static String getCurrentDataSourceKey() { + return contextHolder.get(); + } // 路由方法:返回当前线程的数据源名称(Spring会根据此方法选择对应的数据源) @Override diff --git a/src/main/java/com/example/web/controller/CustomerController.java b/src/main/java/com/example/web/controller/CustomerController.java index 3edc25a..aa6b5e2 100644 --- a/src/main/java/com/example/web/controller/CustomerController.java +++ b/src/main/java/com/example/web/controller/CustomerController.java @@ -6,6 +6,7 @@ import com.example.web.dto.UserProductCartDTO; import com.example.web.entity.UsersManagements; import com.example.web.mapper.UsersManagementsMapper; import com.example.web.service.CustomerService; +import com.example.web.service.InformationTraService; import com.example.web.service.PoolCustomerService; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +40,9 @@ public class CustomerController { @Autowired private UsersManagementsMapper usersManagementsMapper; + + @Autowired + private InformationTraService informationTraService; /** @@ -91,6 +95,20 @@ public class CustomerController { response.put("success", true); response.put("data", customer); + + // 记录查看客户详情操作 + informationTraService.recordOperationEvent( + customer.getPhoneNumber(), + customer.getNickName() != null ? customer.getNickName() : "", + customer.getPhoneNumber() + "-查看客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } @@ -105,6 +123,20 @@ public class CustomerController { response.put("success", true); response.put("data", customer); + + // 记录查看客户详情操作 + informationTraService.recordOperationEvent( + customer.getPhoneNumber(), + customer.getNickName() != null ? customer.getNickName() : "", + customer.getPhoneNumber() + "-查看客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } else { System.out.println("❌ 无权限查看wechat数据源的非公海池客户"); @@ -122,6 +154,20 @@ public class CustomerController { System.out.println("✅ 在默认数据源中找到客户"); response.put("success", true); response.put("data", defaultCustomer); + + // 记录查看客户详情操作 + informationTraService.recordOperationEvent( + defaultCustomer.getPhoneNumber(), + defaultCustomer.getNickName() != null ? defaultCustomer.getNickName() : "", + defaultCustomer.getPhoneNumber() + "-查看客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } @@ -655,6 +701,20 @@ public class CustomerController { System.out.println("🎉 公海池客户信息更新成功"); response.put("success", true); response.put("message", "客户信息更新成功"); + + // 记录修改客户信息操作 + informationTraService.recordOperationEvent( + updatedDTO.getPhoneNumber(), + updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", + updatedDTO.getPhoneNumber() + "-更新客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } else { System.out.println("❌ 公海池客户信息更新失败"); @@ -850,6 +910,19 @@ public class CustomerController { return ResponseEntity.badRequest().body(response); } + // 记录更新客户信息操作 + informationTraService.recordOperationEvent( + updatedDTO.getPhoneNumber(), + updatedDTO.getNickName() != null ? updatedDTO.getNickName() : "", + updatedDTO.getPhoneNumber() + "-更新客户", + updatedDTO.getManagercompany() != null ? updatedDTO.getManagercompany() : "", + updatedDTO.getManagerdepartment() != null ? updatedDTO.getManagerdepartment() : "", + updatedDTO.getOrganization() != null ? updatedDTO.getOrganization() : "", + updatedDTO.getRole() != null ? updatedDTO.getRole() : "", + updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", + updatedDTO.getAssistant() != null ? updatedDTO.getAssistant() : "" + ); + response.put("success", true); response.put("message", "客户信息更新成功"); return ResponseEntity.ok(response); diff --git a/src/main/java/com/example/web/controller/FollowupController.java b/src/main/java/com/example/web/controller/FollowupController.java index 6dcd3a9..689df49 100644 --- a/src/main/java/com/example/web/controller/FollowupController.java +++ b/src/main/java/com/example/web/controller/FollowupController.java @@ -1,6 +1,9 @@ package com.example.web.controller; +import com.example.web.dto.ManagerAuthInfo; import com.example.web.service.FollowUpService; +import com.example.web.service.InformationTraService; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -11,6 +14,9 @@ public class FollowupController { @Autowired private FollowUpService followUpService; + + @Autowired + private InformationTraService informationTraService; /** * 根据电话号码获取跟进信息 @@ -29,6 +35,147 @@ public class FollowupController { } } + /** + * 从请求中获取管理员认证信息 + * @param request 请求对象 + * @return 管理员认证信息 + */ + private ManagerAuthInfo getManagerAuthInfoFromRequest(HttpServletRequest request) { + try { + // 记录请求中所有参数,便于调试 + System.out.println("🔍 开始获取认证信息,请求参数:"); + java.util.Enumeration parameterNames = request.getParameterNames(); + while (parameterNames.hasMoreElements()) { + String paramName = parameterNames.nextElement(); + String paramValue = request.getParameter(paramName); + System.out.println(" " + paramName + " = " + paramValue); + } + System.out.println("🔍 请求参数记录完成"); + + // 记录URL查询参数 + System.out.println("🔍 URL查询参数:"); + String queryString = request.getQueryString(); + System.out.println(" queryString = " + queryString); + + // 从请求参数和URL查询参数中获取认证信息 + // 优先从请求参数中获取,其次从URL查询参数中获取 + String managerId = getParameterFromRequestOrQueryString(request, "managerId"); + String organization = getParameterFromRequestOrQueryString(request, "organization"); + String role = getParameterFromRequestOrQueryString(request, "role"); + String userName = getParameterFromRequestOrQueryString(request, "userName"); + String assistant = getParameterFromRequestOrQueryString(request, "assistant"); + + // 支持两种参数名:managerCompany/managerDepartment 和 company/department + String managerCompany = getParameterFromRequestOrQueryString(request, "managerCompany"); + String managerDepartment = getParameterFromRequestOrQueryString(request, "managerDepartment"); + + // 如果managerCompany或managerDepartment为空,尝试使用company和department参数 + if (managerCompany == null || managerCompany.trim().isEmpty()) { + managerCompany = getParameterFromRequestOrQueryString(request, "company"); + } + if (managerDepartment == null || managerDepartment.trim().isEmpty()) { + managerDepartment = getParameterFromRequestOrQueryString(request, "department"); + } + + // 解码URL编码的参数 + if (organization != null) { + organization = java.net.URLDecoder.decode(organization, "UTF-8"); + } + if (role != null) { + role = java.net.URLDecoder.decode(role, "UTF-8"); + } + if (userName != null) { + userName = java.net.URLDecoder.decode(userName, "UTF-8"); + } + if (assistant != null) { + assistant = java.net.URLDecoder.decode(assistant, "UTF-8"); + } + if (managerCompany != null) { + managerCompany = java.net.URLDecoder.decode(managerCompany, "UTF-8"); + } + if (managerDepartment != null) { + managerDepartment = java.net.URLDecoder.decode(managerDepartment, "UTF-8"); + } + + // 记录解码后的参数值 + System.out.println("🔍 解码后的参数值:"); + System.out.println(" managerId = " + managerId); + System.out.println(" organization = " + organization); + System.out.println(" role = " + role); + System.out.println(" userName = " + userName); + System.out.println(" assistant = " + assistant); + System.out.println(" managerCompany = " + managerCompany); + System.out.println(" managerDepartment = " + managerDepartment); + + // 验证必填参数 + if (userName == null || userName.trim().isEmpty()) { + System.out.println("❌ 用户名不能为空"); + return null; + } + + // 验证公司和部门信息 + if (managerCompany == null || managerCompany.trim().isEmpty()) { + System.out.println("❌ 公司信息不能为空"); + return null; + } + if (managerDepartment == null || managerDepartment.trim().isEmpty()) { + System.out.println("❌ 部门信息不能为空"); + return null; + } + + // 使用正确的构造器创建ManagerAuthInfo对象 + ManagerAuthInfo authInfo = new ManagerAuthInfo( + managerId, + managerCompany, + managerDepartment, + organization, + role, + userName, + assistant + ); + + System.out.println("✅ 认证信息获取成功:" + authInfo.toString()); + return authInfo; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 从请求参数或URL查询参数中获取参数值 + * 优先从请求参数中获取,其次从URL查询参数中获取 + * @param request 请求对象 + * @param paramName 参数名 + * @return 参数值 + */ + private String getParameterFromRequestOrQueryString(HttpServletRequest request, String paramName) { + // 先从请求参数中获取 + String paramValue = request.getParameter(paramName); + if (paramValue != null && !paramValue.trim().isEmpty()) { + return paramValue; + } + + // 如果请求参数中没有,尝试从URL查询参数中获取 + try { + String queryString = request.getQueryString(); + if (queryString != null) { + // 解析queryString + String[] pairs = queryString.split("&"); + for (String pair : pairs) { + String[] keyValue = pair.split("="); + if (keyValue.length == 2 && keyValue[0].equals(paramName)) { + return keyValue[1]; + } + } + } + } catch (Exception e) { + System.err.println("解析URL查询参数失败:" + e.getMessage()); + } + + return null; + } + /** * 保存跟进信息 * @param phoneNumber 电话号码 @@ -38,10 +185,29 @@ public class FollowupController { @PostMapping("/save") public ResponseEntity saveFollowUp( @RequestParam String phoneNumber, - @RequestParam String followup) { + @RequestParam String followup, + HttpServletRequest request) { try { + ManagerAuthInfo authInfo = getManagerAuthInfoFromRequest(request); boolean result = followUpService.saveFollowUp(phoneNumber, followup); if (result) { + // 只有当authInfo不为null时才记录操作事件,确保负责人信息完整 + if (authInfo != null) { + // 记录跟进信息更新操作 + informationTraService.recordOperationEvent( + phoneNumber, + "", // 跟进记录可能无法获取客户名称,留空 + phoneNumber + "-更新跟进", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + } else { + System.err.println("❌ 认证信息为空,无法记录操作事件"); + } return ResponseEntity.ok("跟进信息保存成功"); } else { return ResponseEntity.badRequest().body("跟进信息保存失败"); diff --git a/src/main/java/com/example/web/controller/InformationTraController.java b/src/main/java/com/example/web/controller/InformationTraController.java new file mode 100644 index 0000000..3e92d89 --- /dev/null +++ b/src/main/java/com/example/web/controller/InformationTraController.java @@ -0,0 +1,183 @@ +package com.example.web.controller; + +import com.example.web.dto.ManagerAuthInfo; +import com.example.web.service.InformationTraService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 信息操作记录控制器 + * 处理前端操作记录请求,包括查看客户详情、修改客户信息、更新客户跟进等操作 + */ +@RestController +@RequestMapping("/api/info-tracking") +public class InformationTraController { + + @Autowired + private InformationTraService informationTraService; + + /** + * 记录操作事件 + * @param request HttpServletRequest对象,包含操作信息和认证信息 + * @return ResponseEntity> 响应结果 + */ + @PostMapping("/record") + public ResponseEntity> recordOperation(HttpServletRequest request) { + Map result = new HashMap<>(); + try { + // 获取操作类型 + String operationType = request.getParameter("operationType"); + // 获取电话号码 + String phoneNumber = request.getParameter("phoneNumber"); + // 获取客户名称 + String customerName = request.getParameter("customerName"); + + // 验证必要参数 + if (operationType == null || operationType.trim().isEmpty()) { + result.put("success", false); + result.put("message", "操作类型不能为空"); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } + if (phoneNumber == null || phoneNumber.trim().isEmpty()) { + result.put("success", false); + result.put("message", "电话号码不能为空"); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } + + // 获取认证信息 + ManagerAuthInfo authInfo = getManagerAuthInfoFromRequest(request, false); // 默认销售端,后续根据部门自动调整 + if (authInfo == null) { + result.put("success", false); + result.put("message", "获取认证信息失败"); + return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED); + } + + // 记录操作事件 + boolean success = informationTraService.recordOperationEvent( + phoneNumber, + customerName != null ? customerName : "", + operationType, + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + + if (success) { + result.put("success", true); + result.put("message", "操作记录成功"); + return new ResponseEntity<>(result, HttpStatus.OK); + } else { + result.put("success", false); + result.put("message", "操作记录失败"); + return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR); + } + } catch (Exception e) { + e.printStackTrace(); + result.put("success", false); + result.put("message", "操作记录异常: " + e.getMessage()); + return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * 从请求中获取管理员认证信息 + * @param request HttpServletRequest对象 + * @param isSupplySide 是否为采购端 + * @return ManagerAuthInfo 认证信息对象 + */ + private ManagerAuthInfo getManagerAuthInfoFromRequest(HttpServletRequest request, boolean isSupplySide) { + String managerId = request.getParameter("managerId"); + String managercompany = request.getParameter("company"); + String managerdepartment = request.getParameter("department"); + String organization = request.getParameter("organization"); + String role = request.getParameter("role"); + String userName = request.getParameter("userName"); + String assistant = request.getParameter("assistant"); + + // URL解码参数 + try { + if (managerId != null) managerId = java.net.URLDecoder.decode(managerId, "UTF-8"); + if (managercompany != null) managercompany = java.net.URLDecoder.decode(managercompany, "UTF-8"); + if (managerdepartment != null) managerdepartment = java.net.URLDecoder.decode(managerdepartment, "UTF-8"); + if (organization != null) organization = java.net.URLDecoder.decode(organization, "UTF-8"); + if (role != null) role = java.net.URLDecoder.decode(role, "UTF-8"); + if (userName != null) userName = java.net.URLDecoder.decode(userName, "UTF-8"); + if (assistant != null) assistant = java.net.URLDecoder.decode(assistant, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + System.err.println("URL解码失败: " + e.getMessage()); + } + + // 检查必要参数 + if (userName == null || userName.trim().isEmpty()) { + System.out.println("❌ 用户名为空,无法获取认证信息"); + return null; + } + + // 部门检查 + if (managerdepartment == null) { + System.out.println("⚠️ 部门信息为空,使用默认值"); + managerdepartment = ""; + } + + // 智能判断实际端类型:根据部门信息自动调整isSupplySide值 + boolean actualIsSupplySide = isSupplySide; + if (managerdepartment.contains("销售")) { + actualIsSupplySide = false; // 销售部门自动判定为销售端 + System.out.println("🔄 部门包含'销售',自动调整为销售端"); + } else if (managerdepartment.contains("采购")) { + actualIsSupplySide = true; // 采购部门自动判定为采购端 + System.out.println("🔄 部门包含'采购',自动调整为采购端"); + } + + System.out.println("🔍 认证信息检查,实际端类型: " + (actualIsSupplySide ? "采购端" : "销售端") + + ",部门: '" + managerdepartment + "'"); + System.out.println("✅ 认证信息检查通过"); + + // 验证公司信息一致性 + if (managercompany == null || managercompany.trim().isEmpty()) { + System.out.println("❌ 公司信息为空"); + return null; + } + + ManagerAuthInfo authInfo = new ManagerAuthInfo( + managerId != null ? managerId : "", + managercompany != null ? managercompany : "", + managerdepartment != null ? managerdepartment : "", + organization != null ? organization : "", + role != null ? role : "", + userName, + assistant != null ? assistant : ""); + + // 设置实际的端类型 + try { + // 尝试通过反射或setter方法设置supplySide属性 + java.lang.reflect.Field field = authInfo.getClass().getDeclaredField("supplySide"); + field.setAccessible(true); + field.set(authInfo, actualIsSupplySide); + } catch (Exception e) { + System.out.println("ℹ️ 无法设置supplySide属性: " + e.getMessage()); + } + + System.out.println("🎯 认证信息详情: " + + "系统类型=" + (actualIsSupplySide ? "采购端" : "销售端") + + ", 公司=" + authInfo.getManagercompany() + + ", 部门=" + authInfo.getManagerdepartment() + + ", 组织=" + authInfo.getOrganization() + + ", 角色=" + authInfo.getRole() + + ", 负责人=" + authInfo.getUserName() + + ", 协助人=" + authInfo.getAssistant()); + + return authInfo; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/web/controller/SupplyCustomerController.java b/src/main/java/com/example/web/controller/SupplyCustomerController.java index a78d5d7..87f618b 100644 --- a/src/main/java/com/example/web/controller/SupplyCustomerController.java +++ b/src/main/java/com/example/web/controller/SupplyCustomerController.java @@ -5,6 +5,7 @@ import com.example.web.dto.UnifiedCustomerDTO; import com.example.web.dto.UserProductCartDTO; import com.example.web.entity.UsersManagements; import com.example.web.mapper.SupplyUsersManagementsMapper; +import com.example.web.service.InformationTraService; import com.example.web.service.SupplyCustomerService; import com.example.web.service.SupplyPoolCustomerService; import jakarta.servlet.http.HttpServletRequest; @@ -34,6 +35,9 @@ public class SupplyCustomerController { @Autowired private SupplyUsersManagementsMapper supplyUsersManagementsMapper; + + @Autowired + private InformationTraService informationTraService; /** * 根据公司ID查询客户详情 - 优先处理wechat数据源 @@ -85,6 +89,20 @@ public class SupplyCustomerController { response.put("success", true); response.put("data", customer); + + // 记录查看客户详情操作 + informationTraService.recordOperationEvent( + customer.getPhoneNumber(), + customer.getUserName() != null ? customer.getUserName() : "", + customer.getPhoneNumber() + "-查看客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } @@ -99,6 +117,20 @@ public class SupplyCustomerController { response.put("success", true); response.put("data", customer); + + // 记录查看客户详情操作 + informationTraService.recordOperationEvent( + customer.getPhoneNumber(), + customer.getUserName() != null ? customer.getUserName() : "", + customer.getPhoneNumber() + "-查看客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } else { System.out.println("❌ 无权限查看wechat数据源的非公海池客户"); @@ -116,6 +148,20 @@ public class SupplyCustomerController { System.out.println("✅ 在默认数据源中找到客户"); response.put("success", true); response.put("data", defaultCustomer); + + // 记录查看客户详情操作 + informationTraService.recordOperationEvent( + defaultCustomer.getPhoneNumber(), + defaultCustomer.getUserName() != null ? defaultCustomer.getUserName() : "", + defaultCustomer.getPhoneNumber() + "-查看客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } @@ -616,6 +662,20 @@ public class SupplyCustomerController { System.out.println("🎉 公海池客户信息更新成功"); response.put("success", true); response.put("message", "客户信息更新成功"); + + // 记录修改客户信息操作 + informationTraService.recordOperationEvent( + updatedDTO.getPhoneNumber(), + updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", + updatedDTO.getPhoneNumber() + "-更新客户", + authInfo.getManagercompany(), + authInfo.getManagerdepartment(), + authInfo.getOrganization(), + authInfo.getRole(), + authInfo.getUserName(), + authInfo.getAssistant() + ); + return ResponseEntity.ok(response); } else { System.out.println("❌ 公海池客户信息更新失败"); @@ -820,6 +880,19 @@ public class SupplyCustomerController { return ResponseEntity.badRequest().body(response); } + // 记录更新客户信息操作 + informationTraService.recordOperationEvent( + updatedDTO.getPhoneNumber(), + updatedDTO.getNickName() != null ? updatedDTO.getNickName() : "", + updatedDTO.getPhoneNumber() + "-更新客户", + updatedDTO.getManagercompany() != null ? updatedDTO.getManagercompany() : "", + updatedDTO.getManagerdepartment() != null ? updatedDTO.getManagerdepartment() : "", + updatedDTO.getOrganization() != null ? updatedDTO.getOrganization() : "", + updatedDTO.getRole() != null ? updatedDTO.getRole() : "", + updatedDTO.getUserName() != null ? updatedDTO.getUserName() : "", + updatedDTO.getAssistant() != null ? updatedDTO.getAssistant() : "" + ); + // 🎯 关键修复:返回修正后的数据源信息给前端 Map result = new HashMap<>(); result.put("success", true); diff --git a/src/main/java/com/example/web/entity/InformationTra.java b/src/main/java/com/example/web/entity/InformationTra.java new file mode 100644 index 0000000..39c6248 --- /dev/null +++ b/src/main/java/com/example/web/entity/InformationTra.java @@ -0,0 +1,115 @@ +package com.example.web.entity; + +import java.time.LocalDateTime; + +public class InformationTra { + private Integer id; + private String tracompany; + private String tradepartment; + private String traorganization; + private String trarole; + private String trauserName; + private String traassistant; + private String userId; + private String operationEvent; + private LocalDateTime operationTime; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + // Getters and Setters + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTracompany() { + return tracompany; + } + + public void setTracompany(String tracompany) { + this.tracompany = tracompany; + } + + public String getTradepartment() { + return tradepartment; + } + + public void setTradepartment(String tradepartment) { + this.tradepartment = tradepartment; + } + + public String getTraorganization() { + return traorganization; + } + + public void setTraorganization(String traorganization) { + this.traorganization = traorganization; + } + + public String getTrarole() { + return trarole; + } + + public void setTrarole(String trarole) { + this.trarole = trarole; + } + + public String getTrauserName() { + return trauserName; + } + + public void setTrauserName(String trauserName) { + this.trauserName = trauserName; + } + + public String getTraassistant() { + return traassistant; + } + + public void setTraassistant(String traassistant) { + this.traassistant = traassistant; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getOperationEvent() { + return operationEvent; + } + + public void setOperationEvent(String operationEvent) { + this.operationEvent = operationEvent; + } + + public LocalDateTime getOperationTime() { + return operationTime; + } + + public void setOperationTime(LocalDateTime operationTime) { + this.operationTime = operationTime; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/web/mapper/InformationTraMapper.java b/src/main/java/com/example/web/mapper/InformationTraMapper.java new file mode 100644 index 0000000..2261831 --- /dev/null +++ b/src/main/java/com/example/web/mapper/InformationTraMapper.java @@ -0,0 +1,18 @@ +package com.example.web.mapper; + +import com.example.web.entity.InformationTra; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface InformationTraMapper { + /** + * 插入操作记录 + */ + int insertInformationTra(InformationTra informationTra); + + /** + * 根据userId查询操作记录 + */ + InformationTra selectByUserId(@Param("userId") String userId); +} \ No newline at end of file diff --git a/src/main/java/com/example/web/service/FollowUpService.java b/src/main/java/com/example/web/service/FollowUpService.java index 9727376..826b83f 100644 --- a/src/main/java/com/example/web/service/FollowUpService.java +++ b/src/main/java/com/example/web/service/FollowUpService.java @@ -11,7 +11,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor public class FollowUpService { private final ContactsMapper contactsMapper; @@ -19,6 +18,13 @@ public class FollowUpService { private final SupplyContactsMapper supplyContactsMapper; private final SupplyUsersMapper supplyUsersMapper; + public FollowUpService(ContactsMapper contactsMapper, UsersMapper usersMapper, SupplyContactsMapper supplyContactsMapper, SupplyUsersMapper supplyUsersMapper) { + this.contactsMapper = contactsMapper; + this.usersMapper = usersMapper; + this.supplyContactsMapper = supplyContactsMapper; + this.supplyUsersMapper = supplyUsersMapper; + } + /** * 根据电话号码查询跟进信息 * @param phoneNumber 电话号码 diff --git a/src/main/java/com/example/web/service/InformationTraService.java b/src/main/java/com/example/web/service/InformationTraService.java new file mode 100644 index 0000000..4c6f57d --- /dev/null +++ b/src/main/java/com/example/web/service/InformationTraService.java @@ -0,0 +1,105 @@ +package com.example.web.service; + +import com.example.web.dto.UserProductCartDTO; +import com.example.web.entity.Contacts; +import com.example.web.entity.InformationTra; +import com.example.web.mapper.*; +import com.example.web.config.DynamicDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +public class InformationTraService { + + @Autowired + private InformationTraMapper informationTraMapper; + + @Autowired + private UsersMapper usersMapper; + + @Autowired + private ContactsMapper contactsMapper; + + /** + * 记录操作事件 + */ + public boolean recordOperationEvent(String phoneNumber, String customerName, String operationEvent, + String managerCompany, String managerDepartment, String organization, + String role, String userName, String assistant) { + // 保存原始数据源,以便在finally块中恢复 + String originalDataSource = DynamicDataSource.getCurrentDataSourceKey(); + try { + // 1. 从两个数据源查询客户信息 + String userId = null; + + // 查询wechat数据源的users表 + DynamicDataSource.setDataSourceKey("wechat"); + UserProductCartDTO wechatUser = usersMapper.selectByPhone(phoneNumber); + + if (wechatUser != null) { + userId = wechatUser.getUserId(); + System.out.println("ℹ️ 从wechat数据源获取到userId: " + userId); + } else { + System.out.println("ℹ️ 在wechat数据源中未找到客户"); + // 查询primary数据源的contacts表 + DynamicDataSource.setDataSourceKey("primary"); + Contacts contact = contactsMapper.selectByPhoneNumber(phoneNumber); + + if (contact != null) { + System.out.println("✅ 在primary数据源中找到客户: " + contact.getNickName()); + userId = contact.getId(); + System.out.println("ℹ️ 使用primary数据源的contact.id作为userId: " + userId); + } + } + + // 如果都没找到,返回失败 + if (userId == null) { + System.err.println("❌ 无法获取客户ID,操作记录失败"); + return false; + } + + // 2. 获取当前时间 + LocalDateTime now = LocalDateTime.now(); + + // 3. 构造操作记录 + InformationTra informationTra = new InformationTra(); + informationTra.setTracompany(managerCompany); + informationTra.setTradepartment(managerDepartment); + informationTra.setTraorganization(organization); + informationTra.setTrarole(role); + informationTra.setTrauserName(userName); + informationTra.setTraassistant(assistant); + informationTra.setUserId(userId); + informationTra.setOperationEvent(operationEvent); + informationTra.setOperationTime(now); + informationTra.setCreatedAt(now); + informationTra.setUpdatedAt(now); + + // 4. 始终写入wechat数据源的informationtra表 + DynamicDataSource.setDataSourceKey("wechat"); + System.out.println("🔄 设置数据源为wechat,准备插入信息跟踪记录"); + int result = informationTraMapper.insertInformationTra(informationTra); + if (result > 0) { + System.out.println("✅ 插入信息跟踪记录成功,影响行数: " + result); + return true; + } else { + System.err.println("❌ 插入信息跟踪记录失败,影响行数: " + result); + return false; + } + } catch (Exception e) { + e.printStackTrace(); + System.err.println("❌ 操作记录异常: " + e.getMessage()); + return false; + } finally { + // 恢复原始数据源,避免线程池复用问题 + if (originalDataSource != null) { + DynamicDataSource.setDataSourceKey(originalDataSource); + } else { + DynamicDataSource.clearDataSourceKey(); + } + System.out.println("🔄 恢复数据源为原始值: " + (originalDataSource != null ? originalDataSource : "默认数据源")); + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index fab977f..7e4eaa7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -22,6 +22,7 @@ app: organization-to-department-days: 30 server: + port: 8081 servlet: context-path: /DL # 在Tomcat中部署时,端口由Tomcat配置决定,这里不需要指定 diff --git a/src/main/resources/mapper/InformationTraMapper.xml b/src/main/resources/mapper/InformationTraMapper.xml new file mode 100644 index 0000000..adf8e24 --- /dev/null +++ b/src/main/resources/mapper/InformationTraMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + INSERT INTO informationtra ( + tracompany, tradepartment, traorganization, trarole, + trauserName, traassistant, userId, operationEvent, + operationTime, created_at, updated_at + ) VALUES ( + #{tracompany}, #{tradepartment}, #{traorganization}, #{trarole}, + #{trauserName}, #{traassistant}, #{userId}, #{operationEvent}, + #{operationTime}, #{createdAt}, #{updatedAt} + ) + + + + \ No newline at end of file diff --git a/src/main/resources/static/loginmm.html b/src/main/resources/static/loginmm.html index 5a72bbd..4576522 100644 --- a/src/main/resources/static/loginmm.html +++ b/src/main/resources/static/loginmm.html @@ -520,7 +520,7 @@ errorElement.classList.remove('show'); } - const API_BASE_URL = 'http://8.137.125.67:8080/DL'; // 服务器API地址 + const API_BASE_URL = 'http://localhost:8080/DL'; // 服务器API地址 async function sendLoginRequest(projectName, userName, password) { try { // 使用URL编码的表单数据 diff --git a/src/main/resources/static/mainapp-sells.html b/src/main/resources/static/mainapp-sells.html index 4136926..86db328 100644 --- a/src/main/resources/static/mainapp-sells.html +++ b/src/main/resources/static/mainapp-sells.html @@ -590,6 +590,189 @@ padding: 15px 25px; } + /* 通知铃铛按钮样式 */ + .notification-btn { + background-color: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: all 0.3s ease; + margin-right: 10px; + color: #666; + } + + .notification-btn:hover { + background-color: #e9ecef; + border-color: #adb5bd; + color: #495057; + transform: scale(1.05); + } + + .notification-btn i { + font-size: 18px; + } + + /* 头部操作容器样式 */ + .header-actions-container { + display: flex; + align-items: center; + } + + /* 用户信息容器样式 */ + .user-info-container { + position: relative; + } + + /* 用户信息按钮样式 */ + .user-info-btn { + display: flex; + align-items: center; + background-color: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 8px 12px; + cursor: pointer; + transition: all 0.3s ease; + color: #666; + } + + .user-info-btn:hover { + background-color: #e9ecef; + border-color: #adb5bd; + color: #495057; + } + + .user-info-btn i { + margin: 0 5px; + } + + /* 用户头像样式 */ + .user-avatar-large { + display: flex; + justify-content: center; + align-items: center; + width: 24px; + height: 24px; + background-color: #4CAF50; + color: white; + border-radius: 50%; + margin-right: 8px; + } + + /* 用户信息下拉菜单样式 */ + .user-info-dropdown { + position: absolute; + top: 100%; + right: 0; + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 10px 0; + min-width: 250px; + z-index: 1000; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + transform: translateY(-5px); + } + + .user-info-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); + } + + /* 用户信息头部样式 */ + .user-info-header { + display: flex; + align-items: center; + padding: 0 15px 10px; + border-bottom: 1px solid #e0e0e0; + } + + /* 用户信息主要内容样式 */ + .user-info-main { + margin-left: 10px; + } + + /* 用户名字样式 */ + .user-name { + font-weight: bold; + font-size: 14px; + } + + /* 用户角色样式 */ + .user-role { + font-size: 12px; + color: #666; + } + + /* 用户信息详情样式 */ + .user-info-details { + padding: 10px 0; + } + + /* 用户信息项样式 */ + .user-info-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 15px; + font-size: 13px; + } + + .user-info-item:hover { + background-color: #f8f9fa; + } + + /* 用户信息标签样式 */ + .user-info-label { + display: flex; + align-items: center; + color: #666; + } + + .user-info-label i { + margin-right: 5px; + font-size: 12px; + } + + /* 用户信息值样式 */ + .user-info-value { + color: #333; + font-weight: normal; + } + + /* 退出按钮样式 */ + .logout-btn { + width: 100%; + padding: 10px; + background-color: transparent; + border: none; + border-top: 1px solid #e0e0e0; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + color: #666; + } + + .logout-btn:hover { + background-color: #f8f9fa; + color: #dc3545; + } + + .logout-btn i { + margin-right: 5px; + } + .header-content { display: flex; align-items: center; @@ -1917,8 +2100,7 @@ border-bottom: 2px solid #f0f2f5; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } - margin-bottom: 20px; - } + .follow-up-details-table th, .follow-up-details-table td { @@ -2548,6 +2730,9 @@
Welcome, hope you have a nice day
+ @@ -4438,7 +4623,9 @@ // 从URL参数获取认证信息 function getAuthInfoFromURL() { const urlParams = new URLSearchParams(window.location.search); - const authInfo = { + + // 先从URL中获取认证信息 + let authInfo = { managerId: decodeURIComponent(urlParams.get('managerId') || ''), managercompany: decodeURIComponent(urlParams.get('managercompany') || ''), managerdepartment: decodeURIComponent(urlParams.get('managerdepartment') || ''), @@ -4449,6 +4636,30 @@ token: urlParams.get('token') || '', isSupplySide: urlParams.get('isSupplySide') === 'true' }; + + // 如果URL中没有获取到完整的认证信息,从localStorage中获取 + if (!authInfo.userName || !authInfo.managerId) { + console.log('URL中认证信息不完整,尝试从localStorage获取'); + try { + const currentUser = localStorage.getItem('currentUser'); + if (currentUser) { + const user = JSON.parse(currentUser); + authInfo = { + managerId: authInfo.managerId || user.managerId || '', + managercompany: authInfo.managercompany || user.managercompany || '', + managerdepartment: authInfo.managerdepartment || user.managerdepartment || '', + organization: authInfo.organization || user.organization || '', + role: authInfo.role || user.role || '', + userName: authInfo.userName || user.userName || '', + assistant: authInfo.assistant || user.assistant || '', + token: authInfo.token || localStorage.getItem('authToken') || '', + isSupplySide: urlParams.get('isSupplySide') === 'true' + }; + } + } catch (error) { + console.error('从localStorage获取认证信息失败:', error); + } + } console.log('=== 从URL参数获取认证信息 ==='); console.log('认证信息:', authInfo); @@ -4478,6 +4689,9 @@ // 修改登录按钮 const loginButton = document.getElementById('loginButton'); if (loginButton && authInfo.userName) { + // 找到通知按钮 + const notificationButton = document.getElementById('notificationButton'); + // 创建用户信息容器 const userInfoContainer = document.createElement('div'); userInfoContainer.className = 'user-info-container'; @@ -4548,10 +4762,18 @@ `; - // 替换原来的登录按钮 + // 创建包含通知按钮和用户信息按钮的容器 + const headerActionsContainer = document.createElement('div'); + headerActionsContainer.className = 'header-actions-container'; + + // 先添加通知按钮,再添加用户信息按钮和下拉菜单 + headerActionsContainer.appendChild(notificationButton); userInfoContainer.appendChild(userInfoBtn); userInfoContainer.appendChild(userInfoDropdown); - loginButton.parentNode.replaceChild(userInfoContainer, loginButton); + headerActionsContainer.appendChild(userInfoContainer); + + // 替换原来的登录按钮 + loginButton.parentNode.replaceChild(headerActionsContainer, loginButton); // 绑定显示/隐藏下拉菜单事件 userInfoBtn.addEventListener('click', function (e) { @@ -10729,7 +10951,7 @@ console.log('获取跟进信息:', phoneNumber); // 发送请求获取跟进信息,不再需要指定数据源 - fetch(`/DL/api/followup/get?phoneNumber=${encodeURIComponent(phoneNumber)}`) + fetch(appendAuthParams(`/DL/api/followup/get?phoneNumber=${encodeURIComponent(phoneNumber)}`)) .then(response => response.text()) .then(followup => { console.log('获取到的跟进信息:', followup); @@ -10754,7 +10976,7 @@ const followup = document.getElementById('follow-up-content').value; // 发送请求保存跟进信息,不再需要指定数据源 - fetch(`/DL/api/followup/save?phoneNumber=${encodeURIComponent(phoneNumber)}&followup=${encodeURIComponent(followup)}`, { + fetch(appendAuthParams(`/DL/api/followup/save?phoneNumber=${encodeURIComponent(phoneNumber)}&followup=${encodeURIComponent(followup)}`), { method: 'POST' }) .then(response => response.text()) diff --git a/src/main/resources/static/mainapp-supplys.html b/src/main/resources/static/mainapp-supplys.html index fd51b96..9f3d38c 100644 --- a/src/main/resources/static/mainapp-supplys.html +++ b/src/main/resources/static/mainapp-supplys.html @@ -590,6 +590,189 @@ padding: 15px 25px; } + /* 通知铃铛按钮样式 */ + .notification-btn { + background-color: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: all 0.3s ease; + margin-right: 10px; + color: #666; + } + + .notification-btn:hover { + background-color: #e9ecef; + border-color: #adb5bd; + color: #495057; + transform: scale(1.05); + } + + .notification-btn i { + font-size: 18px; + } + + /* 头部操作容器样式 */ + .header-actions-container { + display: flex; + align-items: center; + } + + /* 用户信息容器样式 */ + .user-info-container { + position: relative; + } + + /* 用户信息按钮样式 */ + .user-info-btn { + display: flex; + align-items: center; + background-color: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 8px 12px; + cursor: pointer; + transition: all 0.3s ease; + color: #666; + } + + .user-info-btn:hover { + background-color: #e9ecef; + border-color: #adb5bd; + color: #495057; + } + + .user-info-btn i { + margin: 0 5px; + } + + /* 用户头像样式 */ + .user-avatar-large { + display: flex; + justify-content: center; + align-items: center; + width: 24px; + height: 24px; + background-color: #4CAF50; + color: white; + border-radius: 50%; + margin-right: 8px; + } + + /* 用户信息下拉菜单样式 */ + .user-info-dropdown { + position: absolute; + top: 100%; + right: 0; + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 10px 0; + min-width: 250px; + z-index: 1000; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + transform: translateY(-5px); + } + + .user-info-dropdown.active { + opacity: 1; + visibility: visible; + transform: translateY(0); + } + + /* 用户信息头部样式 */ + .user-info-header { + display: flex; + align-items: center; + padding: 0 15px 10px; + border-bottom: 1px solid #e0e0e0; + } + + /* 用户信息主要内容样式 */ + .user-info-main { + margin-left: 10px; + } + + /* 用户名字样式 */ + .user-name { + font-weight: bold; + font-size: 14px; + } + + /* 用户角色样式 */ + .user-role { + font-size: 12px; + color: #666; + } + + /* 用户信息详情样式 */ + .user-info-details { + padding: 10px 0; + } + + /* 用户信息项样式 */ + .user-info-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 15px; + font-size: 13px; + } + + .user-info-item:hover { + background-color: #f8f9fa; + } + + /* 用户信息标签样式 */ + .user-info-label { + display: flex; + align-items: center; + color: #666; + } + + .user-info-label i { + margin-right: 5px; + font-size: 12px; + } + + /* 用户信息值样式 */ + .user-info-value { + color: #333; + font-weight: normal; + } + + /* 退出按钮样式 */ + .logout-btn { + width: 100%; + padding: 10px; + background-color: transparent; + border: none; + border-top: 1px solid #e0e0e0; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + color: #666; + } + + .logout-btn:hover { + background-color: #f8f9fa; + color: #dc3545; + } + + .logout-btn i { + margin-right: 5px; + } + .header-content { display: flex; align-items: center; @@ -1917,8 +2100,7 @@ border-bottom: 2px solid #f0f2f5; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } - margin-bottom: 20px; - } + .follow-up-details-table th, .follow-up-details-table td { @@ -2548,6 +2730,9 @@
Welcome, hope you have a nice day
+ @@ -4426,7 +4611,9 @@ // 从URL参数获取认证信息 function getAuthInfoFromURL() { const urlParams = new URLSearchParams(window.location.search); - const authInfo = { + + // 先从URL中获取认证信息 + let authInfo = { managerId: decodeURIComponent(urlParams.get('managerId') || ''), managercompany: decodeURIComponent(urlParams.get('managercompany') || ''), managerdepartment: decodeURIComponent(urlParams.get('managerdepartment') || ''), @@ -4437,6 +4624,30 @@ token: urlParams.get('token') || '', isSupplySide: urlParams.get('isSupplySide') === 'true' }; + + // 如果URL中没有获取到完整的认证信息,从localStorage中获取 + if (!authInfo.userName || !authInfo.managerId) { + console.log('URL中认证信息不完整,尝试从localStorage获取'); + try { + const currentUser = localStorage.getItem('currentUser'); + if (currentUser) { + const user = JSON.parse(currentUser); + authInfo = { + managerId: authInfo.managerId || user.managerId || '', + managercompany: authInfo.managercompany || user.managercompany || '', + managerdepartment: authInfo.managerdepartment || user.managerdepartment || '', + organization: authInfo.organization || user.organization || '', + role: authInfo.role || user.role || '', + userName: authInfo.userName || user.userName || '', + assistant: authInfo.assistant || user.assistant || '', + token: authInfo.token || localStorage.getItem('authToken') || '', + isSupplySide: urlParams.get('isSupplySide') === 'true' + }; + } + } catch (error) { + console.error('从localStorage获取认证信息失败:', error); + } + } console.log('=== 从URL参数获取认证信息 ==='); console.log('认证信息:', authInfo); @@ -4466,6 +4677,9 @@ // 修改登录按钮 const loginButton = document.getElementById('loginButton'); if (loginButton && authInfo.userName) { + // 找到通知按钮 + const notificationButton = document.getElementById('notificationButton'); + // 创建用户信息容器 const userInfoContainer = document.createElement('div'); userInfoContainer.className = 'user-info-container'; @@ -4536,10 +4750,18 @@ `; - // 替换原来的登录按钮 + // 创建包含通知按钮和用户信息按钮的容器 + const headerActionsContainer = document.createElement('div'); + headerActionsContainer.className = 'header-actions-container'; + + // 先添加通知按钮,再添加用户信息按钮和下拉菜单 + headerActionsContainer.appendChild(notificationButton); userInfoContainer.appendChild(userInfoBtn); userInfoContainer.appendChild(userInfoDropdown); - loginButton.parentNode.replaceChild(userInfoContainer, loginButton); + headerActionsContainer.appendChild(userInfoContainer); + + // 替换原来的登录按钮 + loginButton.parentNode.replaceChild(headerActionsContainer, loginButton); // 绑定显示/隐藏下拉菜单事件 userInfoBtn.addEventListener('click', function (e) { @@ -10555,7 +10777,7 @@ console.log('获取跟进信息:', phoneNumber); // 发送请求获取跟进信息,不再需要指定数据源 - fetch(`/DL/api/followup/get?phoneNumber=${encodeURIComponent(phoneNumber)}`) + fetch(appendAuthParams(`/DL/api/followup/get?phoneNumber=${encodeURIComponent(phoneNumber)}`)) .then(response => response.text()) .then(followup => { console.log('获取到的跟进信息:', followup); @@ -10580,7 +10802,7 @@ const followup = document.getElementById('follow-up-content').value; // 发送请求保存跟进信息,不再需要指定数据源 - fetch(`/DL/api/followup/save?phoneNumber=${encodeURIComponent(phoneNumber)}&followup=${encodeURIComponent(followup)}`, { + fetch(appendAuthParams(`/DL/api/followup/save?phoneNumber=${encodeURIComponent(phoneNumber)}&followup=${encodeURIComponent(followup)}`), { method: 'POST' }) .then(response => response.text())