Browse Source

feat: 添加客户申请和审批功能,修复商品详情弹窗和分配客户问题

hzj
Trae AI 1 month ago
parent
commit
9ece2fa049
  1. 2
      web/src/main/java/com/example/web/config/SecurityConfig.java
  2. 32
      web/src/main/java/com/example/web/controller/UserController.java
  3. 33
      web/src/main/java/com/example/web/entity/CustomerApply.java
  4. 45
      web/src/main/java/com/example/web/entity/Product.java
  5. 21
      web/src/main/java/com/example/web/entity/UserTrace.java
  6. 54
      web/src/main/java/com/example/web/mapper/CustomerApplyMapper.java
  7. 5
      web/src/main/java/com/example/web/mapper/PersonnelMapper.java
  8. 31
      web/src/main/java/com/example/web/mapper/ProductMapper.java
  9. 12
      web/src/main/java/com/example/web/mapper/UserTraceMapper.java
  10. 9
      web/src/main/java/com/example/web/mapper/UsersMapper.java
  11. 41
      web/src/main/java/com/example/web/service/CustomerApplyService.java
  12. 12
      web/src/main/java/com/example/web/service/UserService.java
  13. 245
      web/src/main/java/com/example/web/service/impl/CustomerApplyServiceImpl.java
  14. 161
      web/src/main/java/com/example/web/service/impl/UserServiceImpl.java
  15. 44
      web/src/main/resources/mapper/CustomerApplyMapper.xml
  16. 4
      web/src/main/resources/mapper/PersonnelMapper.xml
  17. 50
      web/src/main/resources/mapper/ProductMapper.xml
  18. 13
      web/src/main/resources/mapper/UserTraceMapper.xml
  19. 12
      web/src/main/resources/mapper/UsersMapper.xml
  20. 884
      web/src/main/resources/static/index.html
  21. 2
      web/src/main/resources/static/login.html

2
web/src/main/java/com/example/web/config/SecurityConfig.java

@ -14,7 +14,7 @@ public class SecurityConfig {
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/login.html", "/index.html", "/api/login", "/api/users/**").permitAll()
.requestMatchers("/login.html", "/index.html", "/api/login", "/api/users/**", "/api/products/**").permitAll()
.requestMatchers("/static/**").permitAll()
.anyRequest().authenticated()
.and()

32
web/src/main/java/com/example/web/controller/UserController.java

@ -103,8 +103,40 @@ public class UserController {
return userService.assignCustomers(params);
}
@PostMapping("/users/apply")
public Map<String, Object> applyCustomer(@RequestBody Map<String, Object> params) {
return userService.applyCustomer(params);
}
@PostMapping("/users/approve")
public Map<String, Object> approveApply(@RequestBody Map<String, Object> params) {
return userService.approveApply(params);
}
@GetMapping("/users/apply/list")
public List<com.example.web.entity.CustomerApply> getApplyList(
@RequestParam(required = false) Integer status) {
return userService.getApplyList(status);
}
@PostMapping("/users/updateAllocationStatus")
public Map<String, Object> updateAllocationStatus(@RequestBody List<Map<String, Object>> params) {
return userService.updateAllocationStatus(params);
}
// 商品相关API
@GetMapping("/products")
public Map<String, Object> getProducts(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam String userName,
@RequestParam String userRole) {
return userService.getProducts(userName, userRole, page, size);
}
@GetMapping("/products/detail")
public Map<String, Object> getProductDetail(
@RequestParam String productId) {
return userService.getProductDetail(productId);
}
}

33
web/src/main/java/com/example/web/entity/CustomerApply.java

@ -0,0 +1,33 @@
package com.example.web.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 客户申请实体类
* 用于存储业务员申请客户的记录
*/
@Data
@NoArgsConstructor
public class CustomerApply {
private Integer id; // 自增主键
private String user_id; // 客户ID
private String sales_id; // 业务员ID
private String sales_name; // 业务员姓名
private Integer status; // 申请状态:0-申请中,1-通过,2-失败
private LocalDateTime apply_time; // 申请时间
private LocalDateTime approve_time; // 审批时间
private String approve_by; // 审批人
private String reason; // 申请理由/失败原因
public CustomerApply(String user_id, String sales_id, String sales_name, String reason) {
this.user_id = user_id;
this.sales_id = sales_id;
this.sales_name = sales_name;
this.status = 0; // 默认状态为申请中
this.apply_time = LocalDateTime.now();
this.reason = reason;
}
}

45
web/src/main/java/com/example/web/entity/Product.java

@ -0,0 +1,45 @@
package com.example.web.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.Timestamp;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private String id;
private String productId;
private String sellerId;
private String productName;
private String price;
private String costprice;
private String quantity;
private String grossWeight;
private String yolk;
private String specification;
private Timestamp created_at;
private Timestamp updated_at;
private String status;
private String region;
private String sourceType;
private String supplyStatus;
private String category;
private String producting;
private String description;
private Integer frequency;
private String contact_phone;
private String product_contact;
private String negotiateStatus;
private String fullRegion;
private Timestamp createdAt;
private Integer reservedCount;
private Integer reservedCountDisplay;
private Integer sales;
private Integer totalStock;
private Integer originalTotalStock;
private String displaySpecification;
private String displayYolk;
}

21
web/src/main/java/com/example/web/entity/UserTrace.java

@ -0,0 +1,21 @@
package com.example.web.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.Timestamp;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserTrace {
private Integer id;
private String phoneNumber;
private String userId;
private Timestamp operationTime;
private String originalData;
private String nickName;
private String followup;
private String managerName;
}

54
web/src/main/java/com/example/web/mapper/CustomerApplyMapper.java

@ -0,0 +1,54 @@
package com.example.web.mapper;
import com.example.web.entity.CustomerApply;
import java.util.List;
import java.util.Map;
/**
* 客户申请Mapper接口
*/
public interface CustomerApplyMapper {
/**
* 插入申请记录
* @param apply 申请对象
* @return 影响行数
*/
int insert(CustomerApply apply);
/**
* 更新申请状态
* @param params 参数Map包含idstatusapprove_byreason
* @return 影响行数
*/
int updateStatus(Map<String, Object> params);
/**
* 根据客户ID和业务员ID查询申请记录
* @param user_id 客户ID
* @param sales_id 业务员ID
* @return 申请记录
*/
CustomerApply findByUserIdAndSalesId(String user_id, String sales_id);
/**
* 查询所有申请记录
* @return 申请记录列表
*/
List<CustomerApply> findAll();
/**
* 根据状态查询申请记录
* @param status 状态
* @return 申请记录列表
*/
List<CustomerApply> findByStatus(int status);
/**
* 根据id查询申请记录
* @param id 申请记录id
* @return 申请记录
*/
CustomerApply findById(Integer id);
}

5
web/src/main/java/com/example/web/mapper/PersonnelMapper.java

@ -3,10 +3,15 @@ package com.example.web.mapper;
import com.example.web.entity.Personnel;
import com.example.web.annotation.DataSource;
import java.util.List;
public interface PersonnelMapper {
@DataSource("primary")
Personnel findByName(String name);
@DataSource("primary")
Personnel findByNameAndProjectName(String name, String projectName);
@DataSource("primary")
List<Personnel> findAll();
}

31
web/src/main/java/com/example/web/mapper/ProductMapper.java

@ -0,0 +1,31 @@
package com.example.web.mapper;
import com.example.web.annotation.DataSource;
import com.example.web.entity.Product;
import java.util.List;
import java.util.Map;
public interface ProductMapper {
@DataSource("wechat")
List<Product> getSellerProducts(Map<String, Object> params);
@DataSource("wechat")
List<Product> getBuyerProducts(Map<String, Object> params);
@DataSource("wechat")
List<Product> getAllProducts(Map<String, Object> params);
@DataSource("wechat")
Integer getSellerProductsCount(List<String> phoneNumbers);
@DataSource("wechat")
Integer getBuyerProductsCount(List<String> userIds);
@DataSource("wechat")
Integer getAllProductsCount();
@DataSource("wechat")
Product getProductById(String productId);
}

12
web/src/main/java/com/example/web/mapper/UserTraceMapper.java

@ -0,0 +1,12 @@
package com.example.web.mapper;
import com.example.web.annotation.DataSource;
import com.example.web.entity.UserTrace;
import java.util.List;
public interface UserTraceMapper {
@DataSource("wechat")
List<UserTrace> getProductTraces(String productId);
}

9
web/src/main/java/com/example/web/mapper/UsersMapper.java

@ -66,4 +66,13 @@ public interface UsersMapper {
@DataSource("wechat")
int updateSyncStatusWithValue(Map<String, Object> params);
@DataSource("wechat")
List<Users> findByUserName(String userName);
@DataSource("wechat")
List<Users> findWechatUsersByPhone(String phoneNumber);
@DataSource("wechat")
List<Users> findAllUsers();
}

41
web/src/main/java/com/example/web/service/CustomerApplyService.java

@ -0,0 +1,41 @@
package com.example.web.service;
import com.example.web.entity.CustomerApply;
import java.util.List;
import java.util.Map;
/**
* 客户申请服务接口
*/
public interface CustomerApplyService {
/**
* 提交申请
* @param params 参数Map包含userIdsalesIdsalesNamereason
* @return 结果Map包含success和message
*/
Map<String, Object> submitApply(Map<String, Object> params);
/**
* 审批申请
* @param params 参数Map包含idstatusapprove_byreason
* @return 结果Map包含success和message
*/
Map<String, Object> approveApply(Map<String, Object> params);
/**
* 获取申请列表
* @param status 状态null表示所有状态
* @return 申请列表
*/
List<CustomerApply> getApplyList(Integer status);
/**
* 根据客户ID和业务员ID查询申请记录
* @param userId 客户ID
* @param salesId 业务员ID
* @return 申请记录
*/
CustomerApply getApplyByUserIdAndSalesId(String userId, String salesId);
}

12
web/src/main/java/com/example/web/service/UserService.java

@ -1,7 +1,10 @@
package com.example.web.service;
import com.example.web.entity.Managers;
import com.example.web.entity.Product;
import com.example.web.entity.UserTrace;
import com.example.web.entity.Users;
import com.example.web.entity.CustomerApply;
import java.util.List;
import java.util.Map;
@ -20,4 +23,13 @@ public interface UserService {
Map<String, Object> assignCustomers(Map<String, Object> params);
Map<String, Object> updateAllocationStatus(List<Map<String, Object>> params);
// 商品相关方法
Map<String, Object> getProducts(String userName, String userRole, int page, int size);
Map<String, Object> getProductDetail(String productId);
// 申请相关方法
Map<String, Object> applyCustomer(Map<String, Object> params);
Map<String, Object> approveApply(Map<String, Object> params);
List<CustomerApply> getApplyList(Integer status);
}

245
web/src/main/java/com/example/web/service/impl/CustomerApplyServiceImpl.java

@ -0,0 +1,245 @@
package com.example.web.service.impl;
import com.example.web.entity.CustomerApply;
import com.example.web.entity.Personnel;
import com.example.web.entity.Users;
import com.example.web.mapper.CustomerApplyMapper;
import com.example.web.mapper.PersonnelMapper;
import com.example.web.mapper.UsersMapper;
import com.example.web.mapper.UsersManagementsMapper;
import com.example.web.service.CustomerApplyService;
import com.example.web.config.DataSourceContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 客户申请服务实现类
*/
@Service
public class CustomerApplyServiceImpl implements CustomerApplyService {
@Autowired
private CustomerApplyMapper customerApplyMapper;
@Autowired
private UsersManagementsMapper usersManagementsMapper;
@Autowired
private PersonnelMapper personnelMapper;
@Autowired
private UsersMapper usersMapper;
@Override
public Map<String, Object> submitApply(Map<String, Object> params) {
Map<String, Object> result = new HashMap<>();
try {
// 切换到wechat数据源
DataSourceContextHolder.setDataSource("wechat");
String userId = (String) params.get("userId");
String salesId = (String) params.get("salesId");
String salesName = (String) params.get("salesName");
String reason = (String) params.get("reason");
if (userId == null || salesId == null || salesName == null) {
result.put("success", false);
result.put("message", "缺少必要参数");
return result;
}
// 检查是否已经有申请记录
CustomerApply existingApply = customerApplyMapper.findByUserIdAndSalesId(userId, salesId);
if (existingApply != null && existingApply.getStatus() == 0) {
result.put("success", false);
result.put("message", "该客户已有申请记录,正在审批中");
return result;
}
// 创建新的申请记录
CustomerApply apply = new CustomerApply(userId, salesId, salesName, reason);
int count = customerApplyMapper.insert(apply);
if (count > 0) {
result.put("success", true);
result.put("message", "申请提交成功");
} else {
result.put("success", false);
result.put("message", "申请提交失败");
}
} catch (Exception e) {
e.printStackTrace();
result.put("success", false);
result.put("message", "系统异常: " + e.getMessage());
} finally {
// 清除数据源上下文
DataSourceContextHolder.clearDataSource();
}
return result;
}
@Override
public Map<String, Object> approveApply(Map<String, Object> params) {
Map<String, Object> result = new HashMap<>();
try {
// 切换到wechat数据源
DataSourceContextHolder.setDataSource("wechat");
Integer id = (Integer) params.get("id");
Integer status = (Integer) params.get("status");
String approveBy = (String) params.get("approve_by");
String reason = (String) params.get("reason");
if (id == null || status == null || approveBy == null) {
result.put("success", false);
result.put("message", "缺少必要参数");
return result;
}
// 更新申请状态
Map<String, Object> updateParams = new HashMap<>();
updateParams.put("id", id);
updateParams.put("status", status);
updateParams.put("approve_by", approveBy);
updateParams.put("reason", reason);
updateParams.put("approve_time", LocalDateTime.now());
int count = customerApplyMapper.updateStatus(updateParams);
if (count > 0) {
// 如果审批通过,需要更新客户的负责人信息
if (status == 1) {
// 根据id查询完整的申请记录
CustomerApply apply = customerApplyMapper.findById(id);
if (apply != null) {
// 实现更新客户负责人的逻辑
com.example.web.entity.UsersManagements usersManagements = usersManagementsMapper.findByUserId(apply.getUser_id());
// 切换到primary数据源查询personnel表
DataSourceContextHolder.setDataSource("primary");
// 从personnel表中查询业务员的详细信息
Personnel personnel = null;
try {
// 只通过name查询业务员信息
personnel = personnelMapper.findByName(apply.getSales_name());
} catch (Exception e) {
e.printStackTrace();
System.out.println("查询Personnel时发生错误: " + e.getMessage());
}
// 切换回wechat数据源
DataSourceContextHolder.setDataSource("wechat");
if (usersManagements != null) {
// 更新现有记录
usersManagements.setManagerId(apply.getSales_id());
usersManagements.setUserName(apply.getSales_name());
if (personnel != null) {
usersManagements.setManagercompany(personnel.getManagercompany());
usersManagements.setManagerdepartment(personnel.getManagerdepartment());
usersManagements.setOrganization(personnel.getOrganization());
usersManagements.setRole(personnel.getProjectName()); // projectName与role同值
}
usersManagements.setUpdated_at(LocalDateTime.now());
usersManagementsMapper.update(usersManagements);
} else {
// 检查客户是否存在于users表中
Users user = null;
try {
user = usersMapper.findByUserId(apply.getUser_id());
} catch (Exception e) {
e.printStackTrace();
}
if (user != null) {
// 创建新记录
com.example.web.entity.UsersManagements newUsersManagements = new com.example.web.entity.UsersManagements();
newUsersManagements.setUserId(apply.getUser_id());
newUsersManagements.setManagerId(apply.getSales_id());
newUsersManagements.setUserName(apply.getSales_name());
if (personnel != null) {
newUsersManagements.setManagercompany(personnel.getManagercompany());
newUsersManagements.setManagerdepartment(personnel.getManagerdepartment());
newUsersManagements.setOrganization(personnel.getOrganization());
newUsersManagements.setRole(personnel.getProjectName()); // projectName与role同值
}
newUsersManagements.setCreated_at(LocalDateTime.now());
newUsersManagements.setUpdated_at(LocalDateTime.now());
try {
usersManagementsMapper.insert(newUsersManagements);
} catch (Exception e) {
e.printStackTrace();
System.out.println("创建UsersManagements记录时发生错误: " + e.getMessage());
}
} else {
System.out.println("客户ID " + apply.getUser_id() + " 在users表中不存在,无法创建usermanagements记录");
}
}
}
}
result.put("success", true);
result.put("message", "审批操作成功");
} else {
result.put("success", false);
result.put("message", "审批操作失败");
}
} catch (Exception e) {
e.printStackTrace();
result.put("success", false);
result.put("message", "系统异常: " + e.getMessage());
} finally {
// 清除数据源上下文
DataSourceContextHolder.clearDataSource();
}
return result;
}
@Override
public List<CustomerApply> getApplyList(Integer status) {
try {
// 切换到wechat数据源
DataSourceContextHolder.setDataSource("wechat");
if (status == null) {
return customerApplyMapper.findAll();
} else {
return customerApplyMapper.findByStatus(status);
}
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 清除数据源上下文
DataSourceContextHolder.clearDataSource();
}
}
@Override
public CustomerApply getApplyByUserIdAndSalesId(String userId, String salesId) {
try {
// 切换到wechat数据源
DataSourceContextHolder.setDataSource("wechat");
return customerApplyMapper.findByUserIdAndSalesId(userId, salesId);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 清除数据源上下文
DataSourceContextHolder.clearDataSource();
}
}
}

161
web/src/main/java/com/example/web/service/impl/UserServiceImpl.java

@ -3,18 +3,26 @@ package com.example.web.service.impl;
import com.example.web.entity.InformationTra;
import com.example.web.entity.Managers;
import com.example.web.entity.Personnel;
import com.example.web.entity.Product;
import com.example.web.entity.UserTrace;
import com.example.web.entity.Users;
import com.example.web.entity.UsersManagements;
import com.example.web.entity.CustomerApply;
import com.example.web.mapper.PersonnelMapper;
import com.example.web.mapper.ProductMapper;
import com.example.web.mapper.UserTraceMapper;
import com.example.web.mapper.UsersMapper;
import com.example.web.mapper.ManagersMapper;
import com.example.web.mapper.UsersManagementsMapper;
import com.example.web.service.InformationTraService;
import com.example.web.service.UserService;
import com.example.web.service.CustomerApplyService;
import com.example.web.config.DataSourceContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -37,6 +45,15 @@ public class UserServiceImpl implements UserService {
@Autowired
private InformationTraService informationTraService;
@Autowired
private CustomerApplyService customerApplyService;
@Autowired
private ProductMapper productMapper;
@Autowired
private UserTraceMapper userTraceMapper;
@Override
public Map<String, Object> getUserList(Map<String, Object> requestParams) {
Map<String, Object> result = new HashMap<>();
@ -484,7 +501,7 @@ public class UserServiceImpl implements UserService {
return result;
}
// 1. 到managers表获取完整的负责人信息
// 1. 到managers表获取完整的负责人信息 (使用primary数据源)
Managers selectedManager = managersMapper.findByManagerId(managerId);
if (selectedManager == null) {
@ -500,6 +517,9 @@ public class UserServiceImpl implements UserService {
return result;
}
// 切换到wechat数据源
DataSourceContextHolder.setDataSource("wechat");
// 2. 遍历客户ID列表,为每个客户分配负责人
for (String userId : userIds) {
// 3. 检查客户是否已经在usersmanagements表中有记录
@ -517,7 +537,11 @@ public class UserServiceImpl implements UserService {
existingRecord.setAssistant(selectedManager.getAssistant());
usersManagementsMapper.update(existingRecord);
} else {
// 5. 如果没有记录,插入新记录
// 检查用户是否存在于users表中,避免外键约束错误
// 注意:由于UsersManagementsMapper已经使用wechat数据源,这里不需要额外切换
Users user = usersMapper.findByUserId(userId);
if (user != null) {
// 5. 如果用户存在且没有记录,插入新记录
UsersManagements newRecord = new UsersManagements();
newRecord.setUserId(userId);
newRecord.setManagerId(selectedManager.getManagerId());
@ -529,16 +553,11 @@ public class UserServiceImpl implements UserService {
newRecord.setRoot(selectedManager.getRoot());
newRecord.setAssistant(selectedManager.getAssistant());
usersManagementsMapper.insert(newRecord);
} else {
System.out.println("用户ID " + userId + " 不存在于users表中,跳过分配");
continue;
}
}
// 不需要更新users表,因为我们已经通过UsersManagementsMapper更新了所有必要的字段
// Map<String, Object> updateParams = new HashMap<>();
// updateParams.put("userId", userId);
// updateParams.put("userName", selectedManager.getUserName());
// usersMapper.updateUsersManagements(updateParams);
// 不更新users表的sync_status,保持原始值
// usersMapper.updateSyncStatus(updateParams);
// 记录分配操作
InformationTra tra = createInformationTra(
@ -557,8 +576,12 @@ public class UserServiceImpl implements UserService {
result.put("success", true);
result.put("message", "分配成功");
} catch (Exception e) {
e.printStackTrace();
result.put("success", false);
result.put("message", "分配失败: " + e.getMessage());
} finally {
// 清除数据源上下文
DataSourceContextHolder.clearDataSource();
}
return result;
@ -639,4 +662,120 @@ public class UserServiceImpl implements UserService {
return result;
}
@Override
public Map<String, Object> getProducts(String userName, String userRole, int page, int size) {
Map<String, Object> result = new HashMap<>();
List<Product> products = new ArrayList<>();
int total = 0;
try {
int offset = (page - 1) * size;
if ("管理员".equals(userRole)) {
// 管理员:查看所有商品
Map<String, Object> params = new HashMap<>();
params.put("pageSize", size);
params.put("offset", offset);
products = productMapper.getAllProducts(params);
total = productMapper.getAllProductsCount();
} else if ("销售员".equals(userRole)) {
// 销售员:从personnel表获取电话号码,到products表查询contact_phone相同的
Personnel personnel = personnelMapper.findByName(userName);
if (personnel != null && personnel.getPhoneNumber() != null) {
List<String> phoneNumbers = new ArrayList<>();
phoneNumbers.add(personnel.getPhoneNumber());
Map<String, Object> params = new HashMap<>();
params.put("list", phoneNumbers);
params.put("pageSize", size);
params.put("offset", offset);
products = productMapper.getSellerProducts(params);
total = productMapper.getSellerProductsCount(phoneNumbers);
}
} else if ("采购员".equals(userRole)) {
// 采购员:从users表获取phoneNumber相同的userId,到products表查询sellerId相同的
List<Users> users = usersMapper.findByUserName(userName);
if (users != null && !users.isEmpty()) {
List<String> userIds = new ArrayList<>();
for (Users user : users) {
if (user.getPhoneNumber() != null) {
List<Users> wechatUsers = usersMapper.findWechatUsersByPhone(user.getPhoneNumber());
for (Users wechatUser : wechatUsers) {
if (wechatUser.getUserId() != null) {
userIds.add(wechatUser.getUserId());
}
}
}
}
if (!userIds.isEmpty()) {
Map<String, Object> params = new HashMap<>();
params.put("list", userIds);
params.put("pageSize", size);
params.put("offset", offset);
products = productMapper.getBuyerProducts(params);
total = productMapper.getBuyerProductsCount(userIds);
}
}
}
result.put("success", true);
result.put("products", products);
result.put("total", total);
result.put("page", page);
result.put("size", size);
result.put("pages", (total + size - 1) / size);
} catch (Exception e) {
e.printStackTrace();
result.put("success", false);
result.put("message", "获取商品列表失败: " + e.getMessage());
}
return result;
}
@Override
public Map<String, Object> getProductDetail(String productId) {
Map<String, Object> result = new HashMap<>();
try {
// 获取商品详情
Product product = productMapper.getProductById(productId);
if (product == null) {
result.put("success", false);
result.put("message", "商品不存在");
return result;
}
// 获取浏览记录
List<UserTrace> traces = userTraceMapper.getProductTraces(productId);
result.put("success", true);
result.put("product", product);
result.put("traces", traces);
result.put("traceCount", traces.size());
} catch (Exception e) {
e.printStackTrace();
result.put("success", false);
result.put("message", "获取商品详情失败: " + e.getMessage());
}
return result;
}
@Override
public Map<String, Object> applyCustomer(Map<String, Object> params) {
return customerApplyService.submitApply(params);
}
@Override
public Map<String, Object> approveApply(Map<String, Object> params) {
return customerApplyService.approveApply(params);
}
@Override
public List<CustomerApply> getApplyList(Integer status) {
return customerApplyService.getApplyList(status);
}
}

44
web/src/main/resources/mapper/CustomerApplyMapper.xml

@ -0,0 +1,44 @@
<?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.CustomerApplyMapper">
<insert id="insert" parameterType="com.example.web.entity.CustomerApply">
INSERT INTO customer_apply (user_id, sales_id, sales_name, status, apply_time, reason)
VALUES (#{user_id}, #{sales_id}, #{sales_name}, #{status}, #{apply_time}, #{reason})
</insert>
<update id="updateStatus" parameterType="java.util.Map">
UPDATE customer_apply
SET status = #{status},
approve_time = NOW(),
approve_by = #{approve_by},
reason = #{reason}
WHERE id = #{id}
</update>
<select id="findByUserIdAndSalesId" resultType="com.example.web.entity.CustomerApply">
SELECT * FROM customer_apply
WHERE user_id = #{user_id} AND sales_id = #{sales_id}
ORDER BY apply_time DESC
LIMIT 1
</select>
<select id="findAll" resultType="com.example.web.entity.CustomerApply">
SELECT * FROM customer_apply
ORDER BY apply_time DESC
</select>
<select id="findByStatus" resultType="com.example.web.entity.CustomerApply">
SELECT * FROM customer_apply
WHERE status = #{status}
ORDER BY apply_time DESC
</select>
<select id="findById" resultType="com.example.web.entity.CustomerApply">
SELECT * FROM customer_apply
WHERE id = #{id}
</select>
</mapper>

4
web/src/main/resources/mapper/PersonnelMapper.xml

@ -8,4 +8,8 @@
<select id="findByNameAndProjectName" resultType="com.example.web.entity.Personnel">
SELECT * FROM personnel WHERE name = #{name} AND projectName = #{projectName}
</select>
<select id="findAll" resultType="com.example.web.entity.Personnel">
SELECT * FROM personnel
</select>
</mapper>

50
web/src/main/resources/mapper/ProductMapper.xml

@ -0,0 +1,50 @@
<?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.ProductMapper">
<select id="getSellerProducts" resultType="com.example.web.entity.Product">
SELECT * FROM products WHERE contact_phone IN
<foreach collection="list" item="phone" open="(" separator="," close=")">
#{phone}
</foreach>
ORDER BY created_at DESC
LIMIT #{pageSize} OFFSET #{offset}
</select>
<select id="getBuyerProducts" resultType="com.example.web.entity.Product">
SELECT * FROM products WHERE sellerId IN
<foreach collection="list" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
ORDER BY created_at DESC
LIMIT #{pageSize} OFFSET #{offset}
</select>
<select id="getSellerProductsCount" resultType="java.lang.Integer">
SELECT COUNT(*) FROM products WHERE contact_phone IN
<foreach collection="list" item="phone" open="(" separator="," close=")">
#{phone}
</foreach>
</select>
<select id="getBuyerProductsCount" resultType="java.lang.Integer">
SELECT COUNT(*) FROM products WHERE sellerId IN
<foreach collection="list" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
</select>
<select id="getAllProducts" resultType="com.example.web.entity.Product">
SELECT * FROM products
ORDER BY created_at DESC
LIMIT #{pageSize} OFFSET #{offset}
</select>
<select id="getAllProductsCount" resultType="java.lang.Integer">
SELECT COUNT(*) FROM products
</select>
<select id="getProductById" resultType="com.example.web.entity.Product">
SELECT * FROM products WHERE productId = #{productId}
</select>
</mapper>

13
web/src/main/resources/mapper/UserTraceMapper.xml

@ -0,0 +1,13 @@
<?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.UserTraceMapper">
<select id="getProductTraces" resultType="com.example.web.entity.UserTrace">
SELECT ut.*, u.nickName, u.followup, um.userName as managerName
FROM usertraces ut
LEFT JOIN users u ON ut.userId = u.userId
LEFT JOIN usermanagements um ON ut.userId = um.userId
WHERE ut.originalData LIKE CONCAT('%', #{productId}, '%')
ORDER BY ut.operationTime DESC
</select>
</mapper>

12
web/src/main/resources/mapper/UsersMapper.xml

@ -269,4 +269,16 @@
<select id="findByUserId" parameterType="String" resultType="com.example.web.entity.Users">
SELECT * FROM users WHERE userId = #{userId}
</select>
<select id="findByUserName" parameterType="String" resultType="com.example.web.entity.Users">
SELECT * FROM users WHERE nickName = #{userName}
</select>
<select id="findWechatUsersByPhone" parameterType="String" resultType="com.example.web.entity.Users">
SELECT * FROM users WHERE phoneNumber = #{phoneNumber}
</select>
<select id="findAllUsers" resultType="com.example.web.entity.Users">
SELECT * FROM users WHERE type != 'Colleague'
</select>
</mapper>

884
web/src/main/resources/static/index.html

@ -573,6 +573,7 @@
<div class="user-info">
<span id="userRole"></span>
<span id="userName"></span>
<button id="applyButton" onclick="openApplyModal()" style="display: none; margin-right: 10px; padding: 5px 10px; background-color: #722ed1; color: white; border: none; border-radius: 4px; font-size: 14px; cursor: pointer;">申请管理</button>
<button id="permissionButton" onclick="openPermissionModal()" style="display: none; margin-right: 10px; padding: 5px 10px; background-color: #1890ff; color: white; border: none; border-radius: 4px; font-size: 14px; cursor: pointer;">权限管理</button>
<button class="logout-btn" onclick="logout()">退出登录</button>
</div>
@ -616,6 +617,7 @@
<div class="tab-container-scroll">
<div class="tab-container">
<button class="tab-button active" onclick="switchTab('personal', this)">个人数据</button>
<button class="tab-button" onclick="switchTab('products', this)">货源浏览</button>
<button class="tab-button" onclick="switchTab('public', this)">公海池数据</button>
</div>
</div>
@ -688,6 +690,35 @@
</div>
</div>
<!-- 货源浏览标签页 -->
<div id="products" class="tab-content">
<div class="filter-container">
<div class="filter-bar">
<button onclick="loadProducts()" style="background-color: #1890ff; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-size: 14px;">刷新货源</button>
</div>
</div>
<div class="table-container">
<table id="productsTable">
<thead>
<tr>
<th>商品名称</th>
<th>价格</th>
<th>库存</th>
<th>地区</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="productsBody">
</tbody>
</table>
</div>
<div id="productsEmpty" class="empty-state">暂无货源数据</div>
<div class="pagination" id="productsPagination">
</div>
</div>
<!-- 公海池数据标签页 -->
<div id="public" class="tab-content">
<div class="filter-container">
@ -758,13 +789,18 @@
var userInfo = null;
var personalPage = 1;
var publicPage = 1;
var productsPage = 1;
var personalPageSize = 10;
var publicPageSize = 10;
var productsPageSize = 10;
var personalFilter = 'all'; // all, followed, unfollowed
var managersList = [];
var allPersonalData = [];
var allPublicData = [];
var allProducts = [];
var productsTotal = 0;
var productsTotalPages = 0;
var isLoadingAllData = false;
var currentManagerFilter = null;
var currentFilterTable = 'personal';
@ -819,6 +855,7 @@
var assignButton = document.getElementById('assignButton');
var publicAssignButton = document.getElementById('publicAssignButton');
var permissionButton = document.getElementById('permissionButton');
var applyButton = document.getElementById('applyButton');
if (assignButton) {
assignButton.style.display = isAdmin ? 'inline-block' : 'none';
}
@ -828,6 +865,9 @@
if (permissionButton) {
permissionButton.style.display = isAdmin ? 'inline-block' : 'none';
}
if (applyButton) {
applyButton.style.display = isAdmin ? 'inline-block' : 'none';
}
}
function switchTab(tabName, button) {
@ -873,6 +913,8 @@
loadPersonalData();
} else if (tabName === 'public') {
loadPublicData();
} else if (tabName === 'products') {
loadProducts();
}
} catch (e) {
console.error('加载数据失败:', e);
@ -1065,6 +1107,13 @@
jianDaoYunButton = '<button onclick="openJianDaoYunModal(\'' + (user.userId || '') + '\', \'' + (user.nickName || '') + '\', \'' + (user.followup || '') + '\'); event.stopPropagation();" style="padding: 4px 8px; background-color: #52c41a; color: white; border: none; border-radius: 4px; font-size: 12px;">简道云</button>';
}
// 生成申请按钮,只对普通用户显示
var applyButton = '';
if (!isAdmin) {
applyButton = ' <button onclick="applyCustomer(\'' + (user.userId || '') + '\', \'' + (user.nickName || '') + '\', this); event.stopPropagation();" style="padding: 4px 8px; background-color: #722ed1; color: white; border: none; border-radius: 4px; font-size: 12px; margin-right: 4px;">申请</button>';
}
// 只有管理员显示复选框
var checkboxCell = isAdmin ? '<td><input type="checkbox" class="userCheckbox" data-userid="' + (user.userId || '') + '"></td>' : '<td style="width: 40px;"></td>';
@ -1855,6 +1904,589 @@
</style>
`;
// 加载货源数据
function loadProducts() {
var userRole = userInfo.loginInfo.projectName;
var userName = userInfo.loginInfo.userName;
var url = '/KH/api/products?userName=' + encodeURIComponent(userName) + '&userRole=' + encodeURIComponent(userRole) + '&page=' + productsPage + '&size=' + productsPageSize;
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = JSON.parse(xhr.responseText);
if (data.success) {
allProducts = data.products || [];
productsTotal = data.total || 0;
productsTotalPages = data.pages || 0;
displayProducts(data.products);
// 确保使用当前的productsPageSize值渲染分页组件
renderProductsPagination(productsPage, productsTotalPages, productsTotal);
} else {
showAlert('加载货源失败: ' + data.message);
}
} else if (xhr.readyState == 4) {
console.error('加载货源失败:', xhr.status, xhr.statusText);
showAlert('加载货源失败,请刷新页面重试');
}
};
xhr.send();
}
// 显示货源数据
function displayProducts(products) {
var productsBody = document.getElementById('productsBody');
var productsEmpty = document.getElementById('productsEmpty');
var productsPagination = document.getElementById('productsPagination');
productsBody.innerHTML = '';
if (products && products.length > 0) {
productsEmpty.style.display = 'none';
productsPagination.style.display = 'flex';
for (var i = 0; i < products.length; i++) {
var product = products[i];
var row = '<tr>' +
'<td>' + (product.productName || '-') + '</td>' +
'<td>' + (product.price || '-') + '</td>' +
'<td>' + (product.quantity || '-') + '</td>' +
'<td>' + (product.fullRegion || product.region || '-') + '</td>' +
'<td>' + formatDateTime(product.created_at) + '</td>' +
'<td><button onclick="openProductDetailModal(\'' + (product.productId || '') + '\')" style="padding: 4px 8px; background-color: #1890ff; color: white; border: none; border-radius: 4px; font-size: 12px;">查看详情</button></td>' +
'</tr>';
productsBody.innerHTML += row;
}
} else {
productsEmpty.style.display = 'block';
productsPagination.style.display = 'none';
}
}
// 打开商品详情弹窗
function openProductDetailModal(productId) {
var url = '/KH/api/products/detail?productId=' + encodeURIComponent(productId);
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = JSON.parse(xhr.responseText);
if (data.success) {
showProductDetail(data.product, data.traces);
} else {
showAlert('获取商品详情失败: ' + data.message);
}
} else if (xhr.readyState == 4) {
console.error('获取商品详情失败:', xhr.status, xhr.statusText);
showAlert('获取商品详情失败,请重试');
}
};
xhr.send();
}
// 显示商品详情
function showProductDetail(product, traces) {
// 对traces数据进行分组,按用户ID分组
var groupedTraces = {};
if (traces && traces.length > 0) {
traces.forEach(trace => {
var key = trace.userId || (trace.phoneNumber || trace.nickName);
if (!groupedTraces[key]) {
groupedTraces[key] = {
nickName: trace.nickName,
phoneNumber: trace.phoneNumber,
followup: trace.followup,
responseTime: trace.responseTime,
managerName: trace.managerName,
operationTimes: []
};
}
groupedTraces[key].operationTimes.push(trace.operationTime);
});
}
// 将分组后的数据转换为数组
var mergedTraces = Object.values(groupedTraces);
// 检查是否为管理员
var isAdmin = userInfo && userInfo.loginInfo && userInfo.loginInfo.projectName === '管理员';
// 创建详情弹窗
var modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
`;
var modalContent = document.createElement('div');
modalContent.style.cssText = `
background: white;
border-radius: 8px;
width: 90%;
max-width: 1400px;
max-height: 80%;
display: flex;
flex-direction: column;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
`;
// 固定导航栏
var navBar = `
<div style="padding: 16px 20px; border-bottom: 1px solid #e8e8e8; display: flex; justify-content: space-between; align-items: center; background-color: white; border-radius: 8px 8px 0 0;">
<h3 style="margin: 0; color: #1890ff;">商品详情</h3>
<button onclick="this.parentElement.parentElement.parentElement.remove()" style="padding: 6px 12px; background-color: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">
关闭
</button>
</div>
`;
// 滚动内容区域
var contentArea = document.createElement('div');
contentArea.style.cssText = `
padding: 20px;
overflow-y: auto;
flex: 1;
`;
// 商品基本信息
var productInfo = `
<div style="margin-bottom: 20px;">
<h4 style="margin-bottom: 10px;">基本信息</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;">
<div><strong>商品名称:</strong> ${product.productName || '-'}</div>
<div><strong>价格:</strong> ${product.price || '-'}</div>
<div><strong>成本价:</strong> ${product.costprice || '-'}</div>
<div><strong>库存:</strong> ${product.quantity || '-'}</div>
<div><strong>地区:</strong> ${product.fullRegion || product.region || '-'}</div>
<div><strong>分类:</strong> ${product.category || '-'}</div>
<div><strong>来源类型:</strong> ${product.sourceType || '-'}</div>
<div><strong>供应状态:</strong> ${product.supplyStatus || '-'}</div>
<div><strong>规格:</strong> ${product.displaySpecification || product.specification || '-'}</div>
<div><strong>蛋黄:</strong> ${product.displayYolk || product.yolk || '-'}</div>
<div><strong>创建时间:</strong> ${formatDateTime(product.created_at)}</div>
</div>
</div>
<div style="margin-bottom: 20px;">
<h4 style="margin-bottom: 10px;">商品描述</h4>
<div style="padding: 10px; border: 1px solid #e8e8e8; border-radius: 4px; background-color: #fafafa;">
${product.description || '-'}
</div>
</div>
`;
// 浏览记录
var traceInfo = `
<div style="margin-bottom: 20px;">
<h4 style="margin-bottom: 10px;">浏览记录 (${traces ? traces.length : 0}次)</h4>
${traces && traces.length > 0 ? `
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
<thead>
<tr style="background-color: #f5f5f5;">
<th style="padding: 8px; border: 1px solid #e8e8e8;">昵称</th>
<th style="padding: 8px; border: 1px solid #e8e8e8;">手机号</th>
<th style="padding: 8px; border: 1px solid #e8e8e8;">浏览时间</th>
<th style="padding: 8px; border: 1px solid #e8e8e8;">跟进内容</th>
<th style="padding: 8px; border: 1px solid #e8e8e8;">响应时间</th>
<th style="padding: 8px; border: 1px solid #e8e8e8;">负责人</th>
<th style="padding: 8px; border: 1px solid #e8e8e8;">操作</th>
</tr>
</thead>
<tbody>
${mergedTraces.map(trace => `
<tr>
<td style="padding: 8px; border: 1px solid #e8e8e8; white-space: nowrap; vertical-align: top;">${trace.nickName || '-'}</td>
<td style="padding: 8px; border: 1px solid #e8e8e8; white-space: nowrap; vertical-align: top;">${trace.phoneNumber || '-'}</td>
<td style="padding: 8px; border: 1px solid #e8e8e8; vertical-align: top;">
${trace.operationTimes.map(time => formatDateTime(time)).join('<br>')}
</td>
<td style="padding: 8px; border: 1px solid #e8e8e8; white-space: normal; word-wrap: break-word; max-width: 300px; vertical-align: top;">${trace.followup || '-'}</td>
<td style="padding: 8px; border: 1px solid #e8e8e8; white-space: nowrap; vertical-align: top;">${trace.responseTime || '-'}</td>
<td style="padding: 8px; border: 1px solid #e8e8e8; white-space: nowrap; vertical-align: top;">${trace.managerName || '-'}</td>
<td style="padding: 8px; border: 1px solid #e8e8e8; white-space: nowrap; vertical-align: top;">
${!isAdmin ? `<button onclick="applyCustomer('${trace.userId || ''}', '${trace.nickName || ''}', this); event.stopPropagation();" style="padding: 4px 8px; background-color: #722ed1; color: white; border: none; border-radius: 4px; font-size: 12px;">申请</button>` : '-'}
</td>
</tr>
`).join('')}
</tbody>
</table>
` : '<div style="padding: 10px; border: 1px solid #e8e8e8; border-radius: 4px; background-color: #fafafa;">暂无浏览记录</div>'}
</div>
`;
// 组装弹窗内容
modalContent.innerHTML = navBar;
contentArea.innerHTML = productInfo + traceInfo;
modalContent.appendChild(contentArea);
modal.appendChild(modalContent);
document.body.appendChild(modal);
// 加载申请状态并更新按钮
loadApplyStatusForTraces(mergedTraces);
}
// 加载浏览记录中客户的申请状态
function loadApplyStatusForTraces(traces) {
if (!traces || traces.length === 0) return;
// 获取所有客户ID
var userIds = traces.map(trace => trace.userId).filter(Boolean);
if (userIds.length === 0) return;
var url = '/KH/api/users/apply/list';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var applyList = JSON.parse(xhr.responseText);
if (applyList && applyList.length > 0) {
// 创建申请状态映射
var applyStatusMap = {};
applyList.forEach(apply => {
if (apply.user_id) {
applyStatusMap[apply.user_id] = apply.status;
}
});
// 更新按钮状态
// 查找所有包含浏览记录的表格
var tables = document.querySelectorAll('table');
tables.forEach(table => {
// 检查表格是否包含浏览记录
var header = table.querySelector('th');
if (header && header.textContent === '昵称') {
var tbody = table.querySelector('tbody');
if (tbody) {
var rows = tbody.querySelectorAll('tr');
rows.forEach((row, index) => {
// 获取对应的trace
var trace = traces[index];
if (trace && trace.userId) {
var status = applyStatusMap[trace.userId];
var button = row.querySelector('button[onclick^="applyCustomer"]');
if (button && status !== undefined) {
if (status === 0) {
// 申请中
button.textContent = '申请中';
button.disabled = true;
button.style.backgroundColor = '#faad14';
} else if (status === 1) {
// 申请通过
button.textContent = '申请通过';
button.disabled = true;
button.style.backgroundColor = '#52c41a';
} else if (status === 2) {
// 申请失败
button.textContent = '申请失败';
button.disabled = false;
button.style.backgroundColor = '#722ed1';
}
}
}
});
}
}
});
}
}
};
xhr.send();
}
// 显示提示信息
function showAlert(message) {
var alert = document.createElement('div');
alert.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background-color: #ff4d4f;
color: white;
padding: 12px 20px;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1001;
animation: slideIn 0.3s ease;
`;
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(function() {
alert.style.animation = 'fadeOut 0.3s ease';
setTimeout(function() {
alert.remove();
}, 300);
}, 3000);
}
// 渲染商品分页控件
function renderProductsPagination(currentPage, totalPages, totalItems) {
// 更新全局变量,确保事件处理函数能获取到最新值
productsTotalPages = totalPages;
productsTotal = totalItems;
var paginationContainer = document.getElementById('productsPagination');
paginationContainer.innerHTML = '';
paginationContainer.style.display = 'flex';
paginationContainer.style.justifyContent = 'center';
paginationContainer.style.alignItems = 'center';
paginationContainer.style.gap = '8px';
paginationContainer.style.flexWrap = 'wrap';
// 当总页数大于1时显示分页组件
// 或者当用户选择的每页显示条数不等于默认值时也显示分页组件
var defaultPageSize = 10;
if (totalPages <= 1 && totalItems <= productsPageSize && productsPageSize === defaultPageSize) {
paginationContainer.style.display = 'none';
return;
}
// 首页按钮
var firstButton = document.createElement('button');
firstButton.textContent = '首页';
firstButton.disabled = currentPage === 1;
firstButton.onclick = function() {
if (productsPage > 1) {
productsPage = 1;
loadProducts();
}
};
paginationContainer.appendChild(firstButton);
// 上一页按钮
var prevButton = document.createElement('button');
prevButton.textContent = '上一页';
prevButton.disabled = currentPage === 1;
prevButton.onclick = function() {
if (productsPage > 1) {
productsPage = productsPage - 1;
loadProducts();
}
};
paginationContainer.appendChild(prevButton);
// 页码信息显示
var pageInfo = document.createElement('span');
pageInfo.textContent = '第' + currentPage + '页,共' + totalPages + '页';
pageInfo.style.margin = '0 10px';
paginationContainer.appendChild(pageInfo);
// 下一页按钮
var nextButton = document.createElement('button');
nextButton.textContent = '下一页';
nextButton.disabled = currentPage === totalPages;
nextButton.onclick = function() {
if (productsPage < productsTotalPages) {
productsPage = productsPage + 1;
loadProducts();
}
};
paginationContainer.appendChild(nextButton);
// 末页按钮
var lastButton = document.createElement('button');
lastButton.textContent = '末页';
lastButton.disabled = currentPage === totalPages;
lastButton.onclick = function() {
if (productsPage < productsTotalPages) {
productsPage = productsTotalPages;
loadProducts();
}
};
paginationContainer.appendChild(lastButton);
// 总数显示
var totalInfo = document.createElement('span');
totalInfo.textContent = '总数:' + totalItems + '条';
totalInfo.style.margin = '0 10px';
paginationContainer.appendChild(totalInfo);
// 每页显示条数选择
var pageSizeInfo = document.createElement('span');
pageSizeInfo.textContent = '每页';
paginationContainer.appendChild(pageSizeInfo);
var pageSizeSelect = document.createElement('select');
var pageSizes = [10, 20, 50, 100];
for (var i = 0; i < pageSizes.length; i++) {
var option = document.createElement('option');
option.value = pageSizes[i];
option.textContent = pageSizes[i] + '条';
pageSizeSelect.appendChild(option);
}
// 先添加option元素,然后再设置select元素的value属性
pageSizeSelect.value = productsPageSize;
console.log('Setting pageSizeSelect value to:', productsPageSize);
// 使用闭包确保正确获取select元素
(function(selectElement) {
selectElement.onchange = function() {
// 确保正确更新全局变量
var selectedSize = parseInt(selectElement.value);
console.log('Selected page size:', selectedSize);
productsPageSize = selectedSize;
console.log('Updated productsPageSize to:', productsPageSize);
productsPage = 1; // 重置为第一页
loadProducts();
};
})(pageSizeSelect);
paginationContainer.appendChild(pageSizeSelect);
// 跳转功能
var jumpInfo = document.createElement('span');
jumpInfo.textContent = '跳转';
jumpInfo.style.margin = '0 10px';
paginationContainer.appendChild(jumpInfo);
var jumpInput = document.createElement('input');
jumpInput.type = 'number';
jumpInput.value = currentPage;
jumpInput.min = 1;
jumpInput.max = totalPages;
jumpInput.style.width = '60px';
jumpInput.style.padding = '4px';
paginationContainer.appendChild(jumpInput);
var jumpButton = document.createElement('button');
jumpButton.textContent = '确定';
jumpButton.onclick = function() {
// 直接获取输入框的当前值
var jumpInputElement = document.querySelector('#productsPagination input[type="number"]');
if (jumpInputElement) {
var jumpPage = parseInt(jumpInputElement.value);
if (jumpPage >= 1 && jumpPage <= productsTotalPages) {
productsPage = jumpPage;
loadProducts();
} else {
showAlert('请输入有效的页码');
}
}
};
paginationContainer.appendChild(jumpButton);
}
// 渲染商品分页控件(旧版,已废弃)
function renderProductsPaginationOld(currentPage, totalPages, totalItems) {
var paginationContainer = document.getElementById('productsPagination');
paginationContainer.innerHTML = '';
if (totalPages <= 1) {
return;
}
// 上一页按钮
var prevButton = document.createElement('button');
prevButton.textContent = '上一页';
prevButton.disabled = currentPage === 1;
prevButton.onclick = function() {
if (currentPage > 1) {
productsPage = currentPage - 1;
loadProducts();
}
};
paginationContainer.appendChild(prevButton);
// 页码按钮
var startPage = Math.max(1, currentPage - 2);
var endPage = Math.min(totalPages, startPage + 4);
if (startPage > 1) {
var firstButton = document.createElement('button');
firstButton.textContent = '1';
firstButton.onclick = function() {
productsPage = 1;
loadProducts();
};
paginationContainer.appendChild(firstButton);
if (startPage > 2) {
var ellipsis = document.createElement('span');
ellipsis.textContent = '...';
ellipsis.style.padding = '0 10px';
paginationContainer.appendChild(ellipsis);
}
}
for (var i = startPage; i <= endPage; i++) {
var pageButton = document.createElement('button');
pageButton.textContent = i;
pageButton.classList.toggle('active', i === currentPage);
pageButton.onclick = function() {
productsPage = parseInt(this.textContent);
loadProducts();
};
paginationContainer.appendChild(pageButton);
}
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
var ellipsis = document.createElement('span');
ellipsis.textContent = '...';
ellipsis.style.padding = '0 10px';
paginationContainer.appendChild(ellipsis);
}
var lastButton = document.createElement('button');
lastButton.textContent = totalPages;
lastButton.onclick = function() {
productsPage = totalPages;
loadProducts();
};
paginationContainer.appendChild(lastButton);
}
// 下一页按钮
var nextButton = document.createElement('button');
nextButton.textContent = '下一页';
nextButton.disabled = currentPage === totalPages;
nextButton.onclick = function() {
if (currentPage < totalPages) {
productsPage = currentPage + 1;
loadProducts();
}
};
paginationContainer.appendChild(nextButton);
// 显示总条数
var totalInfo = document.createElement('span');
totalInfo.textContent = '共 ' + totalItems + ' 条';
totalInfo.style.marginLeft = '20px';
totalInfo.style.fontSize = '14px';
totalInfo.style.color = '#666';
paginationContainer.appendChild(totalInfo);
}
// 淡入淡出动画
var style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
`;
document.head.appendChild(style);
// 将样式添加到页面头部
document.head.insertAdjacentHTML('beforeend', formHoverStyles);
@ -2378,6 +3010,51 @@
</div>
`;
// 申请管理模态框HTML
var applyModalHTML = `
<div id="applyModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.7) 100%); backdrop-filter: blur(5px); display: none; z-index: 1000; animation: fadeIn 0.3s ease;">
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border-radius: 8px; width: 90%; max-width: 1000px; max-height: 80vh; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.15); animation: slideIn 0.3s ease;">
<div style="padding: 16px 20px; border-bottom: 1px solid #e8e8e8; display: flex; justify-content: space-between; align-items: center; background-color: white; border-radius: 8px 8px 0 0;">
<h3 style="margin: 0; color: #722ed1;">申请管理</h3>
<button onclick="closeApplyModal()" style="padding: 6px 12px; background-color: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">
关闭
</button>
</div>
<div style="padding: 20px; overflow-y: auto; max-height: calc(80vh - 120px);">
<div class="filter-container" style="margin-bottom: 20px;">
<div class="filter-bar" style="display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="loadApplyList()" style="padding: 8px 16px; background-color: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">刷新申请</button>
<button onclick="filterApplyList('all')" style="padding: 8px 16px; background-color: #52c41a; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">全部</button>
<button onclick="filterApplyList('0')" style="padding: 8px 16px; background-color: #faad14; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">申请中</button>
<button onclick="filterApplyList('1')" style="padding: 8px 16px; background-color: #52c41a; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">已通过</button>
<button onclick="filterApplyList('2')" style="padding: 8px 16px; background-color: #ff4d4f; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">已拒绝</button>
</div>
</div>
<div id="applyList" style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
<thead>
<tr style="background-color: #f5f5f5;">
<th style="padding: 12px; border: 1px solid #e8e8e8; text-align: left;">申请ID</th>
<th style="padding: 12px; border: 1px solid #e8e8e8; text-align: left;">客户ID</th>
<th style="padding: 12px; border: 1px solid #e8e8e8; text-align: left;">业务员</th>
<th style="padding: 12px; border: 1px solid #e8e8e8; text-align: left;">申请时间</th>
<th style="padding: 12px; border: 1px solid #e8e8e8; text-align: left;">状态</th>
<th style="padding: 12px; border: 1px solid #e8e8e8; text-align: left;">申请理由</th>
<th style="padding: 12px; border: 1px solid #e8e8e8; text-align: left;">操作</th>
</tr>
</thead>
<tbody id="applyTableBody">
<tr>
<td colspan="7" style="padding: 20px; text-align: center; color: #999;">暂无申请记录</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
`;
// 权限管理模态框HTML
var permissionModalHTML = `
<div id="permissionModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.7) 100%); backdrop-filter: blur(5px); display: none; z-index: 1000; animation: fadeIn 0.3s ease;">
@ -2465,6 +3142,7 @@
document.body.insertAdjacentHTML('beforeend', detailModalHTML);
document.body.insertAdjacentHTML('beforeend', assignModalHTML);
document.body.insertAdjacentHTML('beforeend', permissionModalHTML);
document.body.insertAdjacentHTML('beforeend', applyModalHTML);
document.body.insertAdjacentHTML('beforeend', alertModalHTML);
document.body.insertAdjacentHTML('beforeend', typeHelpModalHTML);
@ -4380,6 +5058,212 @@
pagination.appendChild(jumpContainer);
}
function applyCustomer(userId, userName, button) {
// 显示确认弹窗
if (confirm('确认申请该客户吗?')) {
// 获取当前按钮元素
if (button) {
// 保存原始状态
button.dataset.originalText = button.textContent;
button.dataset.originalDisabled = button.disabled;
// 更改为申请中状态
button.textContent = '申请中';
button.disabled = true;
button.style.backgroundColor = '#faad14';
} else {
// 尝试通过选择器查找按钮(兼容旧格式)
var buttons = document.querySelectorAll('button[onclick*="applyCustomer(\'" + userId + "\'")]');
buttons.forEach(function(btn) {
// 保存原始状态
btn.dataset.originalText = btn.textContent;
btn.dataset.originalDisabled = btn.disabled;
// 更改为申请中状态
btn.textContent = '申请中';
btn.disabled = true;
btn.style.backgroundColor = '#faad14';
});
}
var personnel = userInfo.personnel;
var usersManagements = userInfo.usersManagements;
var params = {
userId: userId,
salesId: personnel.managerId || usersManagements.userId || '',
salesName: personnel.name || usersManagements.userName || '',
reason: '申请成为该客户的负责人'
};
var url = '/KH/api/users/apply';
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = JSON.parse(xhr.responseText);
if (data.success) {
showAlert('申请提交成功,请等待管理员审批');
// 保持申请中状态
} else {
showAlert('申请失败: ' + data.message);
// 恢复按钮状态
buttons.forEach(function(button) {
button.textContent = button.dataset.originalText;
button.disabled = button.dataset.originalDisabled === 'true';
button.style.backgroundColor = '#722ed1';
});
}
} else if (xhr.readyState == 4) {
// 请求失败,恢复按钮状态
showAlert('申请提交失败,请重试');
buttons.forEach(function(button) {
button.textContent = button.dataset.originalText;
button.disabled = button.dataset.originalDisabled === 'true';
button.style.backgroundColor = '#722ed1';
});
}
};
xhr.send(JSON.stringify(params));
}
}
// 申请管理相关函数
function openApplyModal() {
document.getElementById('applyModal').style.display = 'block';
// 加载申请列表
loadApplyList();
}
function closeApplyModal() {
document.getElementById('applyModal').style.display = 'none';
}
function loadApplyList() {
var url = '/KH/api/users/apply/list';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = JSON.parse(xhr.responseText);
displayApplyList(data);
}
};
xhr.send();
}
function filterApplyList(status) {
var url = '/KH/api/users/apply/list';
if (status !== 'all') {
url += '?status=' + status;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = JSON.parse(xhr.responseText);
displayApplyList(data);
}
};
xhr.send();
}
function displayApplyList(applies) {
var tbody = document.getElementById('applyTableBody');
tbody.innerHTML = '';
if (applies && applies.length > 0) {
for (var i = 0; i < applies.length; i++) {
var apply = applies[i];
var statusText = '';
var statusColor = '';
switch (apply.status) {
case 0:
statusText = '申请中';
statusColor = '#faad14';
break;
case 1:
statusText = '已通过';
statusColor = '#52c41a';
break;
case 2:
statusText = '已拒绝';
statusColor = '#ff4d4f';
break;
}
var actionButtons = '';
if (apply.status == 0) {
actionButtons = `
<button onclick="approveApply(${apply.id}, 1, '${apply.user_id}', '${apply.sales_id}');" style="padding: 4px 8px; background-color: #52c41a; color: white; border: none; border-radius: 4px; font-size: 12px; margin-right: 4px;">通过</button>
<button onclick="approveApply(${apply.id}, 2, '${apply.user_id}', '${apply.sales_id}');" style="padding: 4px 8px; background-color: #ff4d4f; color: white; border: none; border-radius: 4px; font-size: 12px;">拒绝</button>
`;
}
var row = `
<tr>
<td style="padding: 10px; border: 1px solid #e8e8e8;">${apply.id}</td>
<td style="padding: 10px; border: 1px solid #e8e8e8;">${apply.user_id}</td>
<td style="padding: 10px; border: 1px solid #e8e8e8;">${apply.sales_name}</td>
<td style="padding: 10px; border: 1px solid #e8e8e8;">${formatDateTime(apply.apply_time)}</td>
<td style="padding: 10px; border: 1px solid #e8e8e8;">
<span style="display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 12px; color: white; background-color: ${statusColor};">${statusText}</span>
</td>
<td style="padding: 10px; border: 1px solid #e8e8e8; white-space: normal; word-wrap: break-word; max-width: 300px;">${apply.reason}</td>
<td style="padding: 10px; border: 1px solid #e8e8e8;">${actionButtons}</td>
</tr>
`;
tbody.innerHTML += row;
}
} else {
tbody.innerHTML = '<tr><td colspan="7" style="padding: 20px; text-align: center; color: #999;">暂无申请记录</td></tr>';
}
}
function approveApply(id, status, userId, salesId) {
var action = status == 1 ? '通过' : '拒绝';
if (confirm(`确认${action}该申请吗?`)) {
var reason = '';
if (status == 2) {
reason = prompt('请输入拒绝原因:');
if (!reason) {
return;
}
}
var params = {
id: id,
status: status,
approve_by: userInfo.loginInfo.userName,
reason: reason,
userId: userId,
salesId: salesId
};
var url = '/KH/api/users/approve';
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = JSON.parse(xhr.responseText);
if (data.success) {
showAlert(`${action}成功`);
loadApplyList();
} else {
showAlert(`${action}失败: ` + data.message);
}
}
};
xhr.send(JSON.stringify(params));
}
}
window.onload = init;
</script>
</body>

2
web/src/main/resources/static/login.html

@ -273,7 +273,7 @@
console.log('收到来自其他页面的消息:', event);
// 验证消息来源(可选,增强安全性)
if (event.origin === 'http://8.137.125.67:3005' || event.origin === 'http://8.137.125.67:8083') {
if (event.origin === 'http://localhost:3005' || event.origin === 'http://localhost:8083') {
try {
const messageData = event.data;
if (messageData.type === 'LOGIN_INFO') {

Loading…
Cancel
Save