Browse Source

Merge pull request '修复信息跟踪记录插入失败问题,添加通知铃铛图标和样式' (#2) from update-loginmm into master

Reviewed-on: http://8.137.125.67:4000/SwtTt29/Webpage--users/pulls/2
master
SwtTt29 3 months ago
parent
commit
1b3da63436
  1. 377
      INFORMATION_TRACKING_SCHEME.md
  2. 5
      src/main/java/com/example/web/config/DynamicDataSource.java
  3. 73
      src/main/java/com/example/web/controller/CustomerController.java
  4. 168
      src/main/java/com/example/web/controller/FollowupController.java
  5. 183
      src/main/java/com/example/web/controller/InformationTraController.java
  6. 73
      src/main/java/com/example/web/controller/SupplyCustomerController.java
  7. 115
      src/main/java/com/example/web/entity/InformationTra.java
  8. 18
      src/main/java/com/example/web/mapper/InformationTraMapper.java
  9. 8
      src/main/java/com/example/web/service/FollowUpService.java
  10. 105
      src/main/java/com/example/web/service/InformationTraService.java
  11. 1
      src/main/resources/application.yaml
  12. 35
      src/main/resources/mapper/InformationTraMapper.xml
  13. 2
      src/main/resources/static/loginmm.html
  14. 236
      src/main/resources/static/mainapp-sells.html
  15. 236
      src/main/resources/static/mainapp-supplys.html

377
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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.web.mapper.InformationTraMapper">
<resultMap id="informationTraMap" type="com.example.web.entity.InformationTra">
<id property="id" column="id"/>
<result property="tracompany" column="tracompany"/>
<result property="tradepartment" column="tradepartment"/>
<result property="traorganization" column="traorganization"/>
<result property="trarole" column="trarole"/>
<result property="trauserName" column="trauserName"/>
<result property="traassistant" column="traassistant"/>
<result property="userId" column="userId"/>
<result property="operationEvent" column="operationEvent"/>
<result property="operationTime" column="operationTime"/>
<result property="createdAt" column="created_at"/>
<result property="updatedAt" column="updated_at"/>
</resultMap>
<insert id="insertInformationTra" parameterType="com.example.web.entity.InformationTra">
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}
)
</insert>
<select id="selectByUserId" resultMap="informationTraMap">
SELECT * FROM informationtra WHERE userId = #{userId}
</select>
</mapper>
```
### 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<Map<String, Object>> recordOperationEvent(
@RequestBody Map<String, String> requestBody,
HttpServletRequest request) {
Map<String, Object> 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. 添加操作记录审计功能,满足合规要求

5
src/main/java/com/example/web/config/DynamicDataSource.java

@ -17,6 +17,11 @@ public class DynamicDataSource extends AbstractRoutingDataSource {
public static void clearDataSourceKey() { public static void clearDataSourceKey() {
contextHolder.remove(); contextHolder.remove();
} }
// 获取当前数据源(供业务逻辑调用,保存原始数据源)
public static String getCurrentDataSourceKey() {
return contextHolder.get();
}
// 路由方法:返回当前线程的数据源名称(Spring会根据此方法选择对应的数据源) // 路由方法:返回当前线程的数据源名称(Spring会根据此方法选择对应的数据源)
@Override @Override

73
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.entity.UsersManagements;
import com.example.web.mapper.UsersManagementsMapper; import com.example.web.mapper.UsersManagementsMapper;
import com.example.web.service.CustomerService; import com.example.web.service.CustomerService;
import com.example.web.service.InformationTraService;
import com.example.web.service.PoolCustomerService; import com.example.web.service.PoolCustomerService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -39,6 +40,9 @@ public class CustomerController {
@Autowired @Autowired
private UsersManagementsMapper usersManagementsMapper; private UsersManagementsMapper usersManagementsMapper;
@Autowired
private InformationTraService informationTraService;
/** /**
@ -91,6 +95,20 @@ public class CustomerController {
response.put("success", true); response.put("success", true);
response.put("data", customer); 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); return ResponseEntity.ok(response);
} }
@ -105,6 +123,20 @@ public class CustomerController {
response.put("success", true); response.put("success", true);
response.put("data", customer); 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); return ResponseEntity.ok(response);
} else { } else {
System.out.println("❌ 无权限查看wechat数据源的非公海池客户"); System.out.println("❌ 无权限查看wechat数据源的非公海池客户");
@ -122,6 +154,20 @@ public class CustomerController {
System.out.println("✅ 在默认数据源中找到客户"); System.out.println("✅ 在默认数据源中找到客户");
response.put("success", true); response.put("success", true);
response.put("data", defaultCustomer); 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); return ResponseEntity.ok(response);
} }
@ -655,6 +701,20 @@ public class CustomerController {
System.out.println("🎉 公海池客户信息更新成功"); System.out.println("🎉 公海池客户信息更新成功");
response.put("success", true); response.put("success", true);
response.put("message", "客户信息更新成功"); 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); return ResponseEntity.ok(response);
} else { } else {
System.out.println("❌ 公海池客户信息更新失败"); System.out.println("❌ 公海池客户信息更新失败");
@ -850,6 +910,19 @@ public class CustomerController {
return ResponseEntity.badRequest().body(response); 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("success", true);
response.put("message", "客户信息更新成功"); response.put("message", "客户信息更新成功");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);

168
src/main/java/com/example/web/controller/FollowupController.java

@ -1,6 +1,9 @@
package com.example.web.controller; package com.example.web.controller;
import com.example.web.dto.ManagerAuthInfo;
import com.example.web.service.FollowUpService; 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.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -11,6 +14,9 @@ public class FollowupController {
@Autowired @Autowired
private FollowUpService followUpService; 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<String> 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 电话号码 * @param phoneNumber 电话号码
@ -38,10 +185,29 @@ public class FollowupController {
@PostMapping("/save") @PostMapping("/save")
public ResponseEntity<String> saveFollowUp( public ResponseEntity<String> saveFollowUp(
@RequestParam String phoneNumber, @RequestParam String phoneNumber,
@RequestParam String followup) { @RequestParam String followup,
HttpServletRequest request) {
try { try {
ManagerAuthInfo authInfo = getManagerAuthInfoFromRequest(request);
boolean result = followUpService.saveFollowUp(phoneNumber, followup); boolean result = followUpService.saveFollowUp(phoneNumber, followup);
if (result) { 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("跟进信息保存成功"); return ResponseEntity.ok("跟进信息保存成功");
} else { } else {
return ResponseEntity.badRequest().body("跟进信息保存失败"); return ResponseEntity.badRequest().body("跟进信息保存失败");

183
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<Map<String, Object>> 响应结果
*/
@PostMapping("/record")
public ResponseEntity<Map<String, Object>> recordOperation(HttpServletRequest request) {
Map<String, Object> 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;
}
}

73
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.dto.UserProductCartDTO;
import com.example.web.entity.UsersManagements; import com.example.web.entity.UsersManagements;
import com.example.web.mapper.SupplyUsersManagementsMapper; import com.example.web.mapper.SupplyUsersManagementsMapper;
import com.example.web.service.InformationTraService;
import com.example.web.service.SupplyCustomerService; import com.example.web.service.SupplyCustomerService;
import com.example.web.service.SupplyPoolCustomerService; import com.example.web.service.SupplyPoolCustomerService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@ -34,6 +35,9 @@ public class SupplyCustomerController {
@Autowired @Autowired
private SupplyUsersManagementsMapper supplyUsersManagementsMapper; private SupplyUsersManagementsMapper supplyUsersManagementsMapper;
@Autowired
private InformationTraService informationTraService;
/** /**
* 根据公司ID查询客户详情 - 优先处理wechat数据源 * 根据公司ID查询客户详情 - 优先处理wechat数据源
@ -85,6 +89,20 @@ public class SupplyCustomerController {
response.put("success", true); response.put("success", true);
response.put("data", customer); 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); return ResponseEntity.ok(response);
} }
@ -99,6 +117,20 @@ public class SupplyCustomerController {
response.put("success", true); response.put("success", true);
response.put("data", customer); 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); return ResponseEntity.ok(response);
} else { } else {
System.out.println("❌ 无权限查看wechat数据源的非公海池客户"); System.out.println("❌ 无权限查看wechat数据源的非公海池客户");
@ -116,6 +148,20 @@ public class SupplyCustomerController {
System.out.println("✅ 在默认数据源中找到客户"); System.out.println("✅ 在默认数据源中找到客户");
response.put("success", true); response.put("success", true);
response.put("data", defaultCustomer); 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); return ResponseEntity.ok(response);
} }
@ -616,6 +662,20 @@ public class SupplyCustomerController {
System.out.println("🎉 公海池客户信息更新成功"); System.out.println("🎉 公海池客户信息更新成功");
response.put("success", true); response.put("success", true);
response.put("message", "客户信息更新成功"); 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); return ResponseEntity.ok(response);
} else { } else {
System.out.println("❌ 公海池客户信息更新失败"); System.out.println("❌ 公海池客户信息更新失败");
@ -820,6 +880,19 @@ public class SupplyCustomerController {
return ResponseEntity.badRequest().body(response); 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<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("success", true); result.put("success", true);

115
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;
}
}

18
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);
}

8
src/main/java/com/example/web/service/FollowUpService.java

@ -11,7 +11,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class FollowUpService { public class FollowUpService {
private final ContactsMapper contactsMapper; private final ContactsMapper contactsMapper;
@ -19,6 +18,13 @@ public class FollowUpService {
private final SupplyContactsMapper supplyContactsMapper; private final SupplyContactsMapper supplyContactsMapper;
private final SupplyUsersMapper supplyUsersMapper; 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 电话号码 * @param phoneNumber 电话号码

105
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 : "默认数据源"));
}
}
}

1
src/main/resources/application.yaml

@ -22,6 +22,7 @@ app:
organization-to-department-days: 30 organization-to-department-days: 30
server: server:
port: 8081
servlet: servlet:
context-path: /DL context-path: /DL
# 在Tomcat中部署时,端口由Tomcat配置决定,这里不需要指定 # 在Tomcat中部署时,端口由Tomcat配置决定,这里不需要指定

35
src/main/resources/mapper/InformationTraMapper.xml

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.web.mapper.InformationTraMapper">
<resultMap id="informationTraMap" type="com.example.web.entity.InformationTra">
<id property="id" column="id"/>
<result property="tracompany" column="tracompany"/>
<result property="tradepartment" column="tradepartment"/>
<result property="traorganization" column="traorganization"/>
<result property="trarole" column="trarole"/>
<result property="trauserName" column="trauserName"/>
<result property="traassistant" column="traassistant"/>
<result property="userId" column="userId"/>
<result property="operationEvent" column="operationEvent"/>
<result property="operationTime" column="operationTime"/>
<result property="createdAt" column="created_at"/>
<result property="updatedAt" column="updated_at"/>
</resultMap>
<insert id="insertInformationTra" parameterType="com.example.web.entity.InformationTra">
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}
)
</insert>
<select id="selectByUserId" resultMap="informationTraMap">
SELECT * FROM informationtra WHERE userId = #{userId}
</select>
</mapper>

2
src/main/resources/static/loginmm.html

@ -520,7 +520,7 @@
errorElement.classList.remove('show'); 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) { async function sendLoginRequest(projectName, userName, password) {
try { try {
// 使用URL编码的表单数据 // 使用URL编码的表单数据

236
src/main/resources/static/mainapp-sells.html

@ -590,6 +590,189 @@
padding: 15px 25px; 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 { .header-content {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1917,8 +2100,7 @@
border-bottom: 2px solid #f0f2f5; border-bottom: 2px solid #f0f2f5;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
} }
margin-bottom: 20px;
}
.follow-up-details-table th, .follow-up-details-table th,
.follow-up-details-table td { .follow-up-details-table td {
@ -2548,6 +2730,9 @@
<div class="welcome-english">Welcome, hope you have a nice day</div> <div class="welcome-english">Welcome, hope you have a nice day</div>
</div> </div>
</div> </div>
<button class="notification-btn" id="notificationButton">
<i class="fas fa-bell"></i>
</button>
<button class="primary-btn login-btn" id="loginButton"> <button class="primary-btn login-btn" id="loginButton">
<i class="fas fa-sign-in-alt"></i> 登录 <i class="fas fa-sign-in-alt"></i> 登录
</button> </button>
@ -4438,7 +4623,9 @@
// 从URL参数获取认证信息 // 从URL参数获取认证信息
function getAuthInfoFromURL() { function getAuthInfoFromURL() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const authInfo = {
// 先从URL中获取认证信息
let authInfo = {
managerId: decodeURIComponent(urlParams.get('managerId') || ''), managerId: decodeURIComponent(urlParams.get('managerId') || ''),
managercompany: decodeURIComponent(urlParams.get('managercompany') || ''), managercompany: decodeURIComponent(urlParams.get('managercompany') || ''),
managerdepartment: decodeURIComponent(urlParams.get('managerdepartment') || ''), managerdepartment: decodeURIComponent(urlParams.get('managerdepartment') || ''),
@ -4449,6 +4636,30 @@
token: urlParams.get('token') || '', token: urlParams.get('token') || '',
isSupplySide: urlParams.get('isSupplySide') === 'true' 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('=== 从URL参数获取认证信息 ===');
console.log('认证信息:', authInfo); console.log('认证信息:', authInfo);
@ -4478,6 +4689,9 @@
// 修改登录按钮 // 修改登录按钮
const loginButton = document.getElementById('loginButton'); const loginButton = document.getElementById('loginButton');
if (loginButton && authInfo.userName) { if (loginButton && authInfo.userName) {
// 找到通知按钮
const notificationButton = document.getElementById('notificationButton');
// 创建用户信息容器 // 创建用户信息容器
const userInfoContainer = document.createElement('div'); const userInfoContainer = document.createElement('div');
userInfoContainer.className = 'user-info-container'; userInfoContainer.className = 'user-info-container';
@ -4548,10 +4762,18 @@
</button> </button>
`; `;
// 替换原来的登录按钮 // 创建包含通知按钮和用户信息按钮的容器
const headerActionsContainer = document.createElement('div');
headerActionsContainer.className = 'header-actions-container';
// 先添加通知按钮,再添加用户信息按钮和下拉菜单
headerActionsContainer.appendChild(notificationButton);
userInfoContainer.appendChild(userInfoBtn); userInfoContainer.appendChild(userInfoBtn);
userInfoContainer.appendChild(userInfoDropdown); userInfoContainer.appendChild(userInfoDropdown);
loginButton.parentNode.replaceChild(userInfoContainer, loginButton); headerActionsContainer.appendChild(userInfoContainer);
// 替换原来的登录按钮
loginButton.parentNode.replaceChild(headerActionsContainer, loginButton);
// 绑定显示/隐藏下拉菜单事件 // 绑定显示/隐藏下拉菜单事件
userInfoBtn.addEventListener('click', function (e) { userInfoBtn.addEventListener('click', function (e) {
@ -10729,7 +10951,7 @@
console.log('获取跟进信息:', phoneNumber); 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(response => response.text())
.then(followup => { .then(followup => {
console.log('获取到的跟进信息:', followup); console.log('获取到的跟进信息:', followup);
@ -10754,7 +10976,7 @@
const followup = document.getElementById('follow-up-content').value; 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' method: 'POST'
}) })
.then(response => response.text()) .then(response => response.text())

236
src/main/resources/static/mainapp-supplys.html

@ -590,6 +590,189 @@
padding: 15px 25px; 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 { .header-content {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1917,8 +2100,7 @@
border-bottom: 2px solid #f0f2f5; border-bottom: 2px solid #f0f2f5;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
} }
margin-bottom: 20px;
}
.follow-up-details-table th, .follow-up-details-table th,
.follow-up-details-table td { .follow-up-details-table td {
@ -2548,6 +2730,9 @@
<div class="welcome-english">Welcome, hope you have a nice day</div> <div class="welcome-english">Welcome, hope you have a nice day</div>
</div> </div>
</div> </div>
<button class="notification-btn" id="notificationButton">
<i class="fas fa-bell"></i>
</button>
<button class="primary-btn login-btn" id="loginButton"> <button class="primary-btn login-btn" id="loginButton">
<i class="fas fa-sign-in-alt"></i> 登录 <i class="fas fa-sign-in-alt"></i> 登录
</button> </button>
@ -4426,7 +4611,9 @@
// 从URL参数获取认证信息 // 从URL参数获取认证信息
function getAuthInfoFromURL() { function getAuthInfoFromURL() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const authInfo = {
// 先从URL中获取认证信息
let authInfo = {
managerId: decodeURIComponent(urlParams.get('managerId') || ''), managerId: decodeURIComponent(urlParams.get('managerId') || ''),
managercompany: decodeURIComponent(urlParams.get('managercompany') || ''), managercompany: decodeURIComponent(urlParams.get('managercompany') || ''),
managerdepartment: decodeURIComponent(urlParams.get('managerdepartment') || ''), managerdepartment: decodeURIComponent(urlParams.get('managerdepartment') || ''),
@ -4437,6 +4624,30 @@
token: urlParams.get('token') || '', token: urlParams.get('token') || '',
isSupplySide: urlParams.get('isSupplySide') === 'true' 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('=== 从URL参数获取认证信息 ===');
console.log('认证信息:', authInfo); console.log('认证信息:', authInfo);
@ -4466,6 +4677,9 @@
// 修改登录按钮 // 修改登录按钮
const loginButton = document.getElementById('loginButton'); const loginButton = document.getElementById('loginButton');
if (loginButton && authInfo.userName) { if (loginButton && authInfo.userName) {
// 找到通知按钮
const notificationButton = document.getElementById('notificationButton');
// 创建用户信息容器 // 创建用户信息容器
const userInfoContainer = document.createElement('div'); const userInfoContainer = document.createElement('div');
userInfoContainer.className = 'user-info-container'; userInfoContainer.className = 'user-info-container';
@ -4536,10 +4750,18 @@
</button> </button>
`; `;
// 替换原来的登录按钮 // 创建包含通知按钮和用户信息按钮的容器
const headerActionsContainer = document.createElement('div');
headerActionsContainer.className = 'header-actions-container';
// 先添加通知按钮,再添加用户信息按钮和下拉菜单
headerActionsContainer.appendChild(notificationButton);
userInfoContainer.appendChild(userInfoBtn); userInfoContainer.appendChild(userInfoBtn);
userInfoContainer.appendChild(userInfoDropdown); userInfoContainer.appendChild(userInfoDropdown);
loginButton.parentNode.replaceChild(userInfoContainer, loginButton); headerActionsContainer.appendChild(userInfoContainer);
// 替换原来的登录按钮
loginButton.parentNode.replaceChild(headerActionsContainer, loginButton);
// 绑定显示/隐藏下拉菜单事件 // 绑定显示/隐藏下拉菜单事件
userInfoBtn.addEventListener('click', function (e) { userInfoBtn.addEventListener('click', function (e) {
@ -10555,7 +10777,7 @@
console.log('获取跟进信息:', phoneNumber); 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(response => response.text())
.then(followup => { .then(followup => {
console.log('获取到的跟进信息:', followup); console.log('获取到的跟进信息:', followup);
@ -10580,7 +10802,7 @@
const followup = document.getElementById('follow-up-content').value; 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' method: 'POST'
}) })
.then(response => response.text()) .then(response => response.text())

Loading…
Cancel
Save