Commit 677dd111 authored by 法拉51246's avatar 法拉51246

增加每个页面批量删除

增加查看详情
增加多选合并打印和全选合并打印
增加过滤角色权限分配,低权限不能分配高权限
parent 5a69a12f
...@@ -204,7 +204,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { ...@@ -204,7 +204,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
private Map<String, String> buildUserInfo(Long userId, Integer userType) { private Map<String, String> buildUserInfo(Long userId, Integer userType) {
if (userType.equals(UserTypeEnum.ADMIN.getValue())) { if (userType.equals(UserTypeEnum.ADMIN.getValue())) {
AdminUserDO user = adminUserService.getUser(userId); AdminUserDO user = adminUserService.getUser(userId);
Long branchDeptId = findBranchDeptId(user.getDeptId(), 100L);//找到分公司Id,这里的100L是总公司的ID,未来某一天如果有变,就改这里 Long branchDeptId = findBranchDeptId(user.getDeptId(), 100L);//找到分公司Id,这里的100L是总公司的ID,未来某一天如果有变,就改这里 锚点
return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname()) return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname())
.put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(branchDeptId)).build(); .put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(branchDeptId)).build();
} else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {
......
...@@ -9,10 +9,13 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; ...@@ -9,10 +9,13 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
...@@ -30,6 +33,7 @@ import org.springframework.util.StringUtils; ...@@ -30,6 +33,7 @@ import org.springframework.util.StringUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
...@@ -50,6 +54,8 @@ public class RoleServiceImpl implements RoleService { ...@@ -50,6 +54,8 @@ public class RoleServiceImpl implements RoleService {
@Resource @Resource
private RoleMapper roleMapper; private RoleMapper roleMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
...@@ -187,7 +193,14 @@ public class RoleServiceImpl implements RoleService { ...@@ -187,7 +193,14 @@ public class RoleServiceImpl implements RoleService {
@Override @Override
public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) { public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) {
return roleMapper.selectListByStatus(statuses); List<RoleDO> roleDOS = roleMapper.selectListByStatus(statuses);
//判断当前用户是不是超级管理员,如果不是,需要过滤掉超级管理员
List<UserRoleDO> userRoleDOS = userRoleMapper.selectListByUserId(SecurityFrameworkUtils.getLoginUserId());
// 如果userRoleDOS中没有roleId=1的记录,则过滤掉roleDOS中id=1的记录
roleDOS = userRoleDOS.stream().noneMatch(userRole -> userRole.getRoleId() == 1L)
? roleDOS.stream().filter(role -> role.getId() != 1L).collect(Collectors.toList())
: roleDOS;
return roleDOS;
} }
@Override @Override
......
...@@ -76,6 +76,27 @@ public class CustomerInfoController { ...@@ -76,6 +76,27 @@ public class CustomerInfoController {
return success(true); return success(true);
} }
@DeleteMapping("/deleteByIds")
@Operation(summary = "批量删除客户信息")
@Parameter(name = "ids", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('visit:customer-info:delete')")
@Idempotent(timeout = 10, message = "重复请求,请稍后重试")
public CommonResult<Boolean> deleteCustomerInfoByIds(@RequestParam("ids") String ids) {
//如果ids是空的,返回异常提示,请先选择需要打印的记录
if (StringUtil.isEmpty(ids)) {
throw new RuntimeException("请先选择需要删除的记录");
}
String[] split = ids.split(",");
//转为List
List<Long> idList = new ArrayList<>();
for (String s : split) {
idList.add(Long.parseLong(s));
}
customerInfoService.deleteCustomerInfoByIds(idList);
return success(true);
}
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得客户信息") @Operation(summary = "获得客户信息")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
......
...@@ -60,12 +60,15 @@ public class CustomerInfoSaveReqVO { ...@@ -60,12 +60,15 @@ public class CustomerInfoSaveReqVO {
private String locationImage; private String locationImage;
@Schema(description = "产品信息") @Schema(description = "产品信息")
@NotEmpty(message = "产品信息不能为空")
private String productIds; private String productIds;
@Schema(description = "产品信息名称") @Schema(description = "产品信息名称")
@NotEmpty(message = "产品信息名称不能为空")
private String productNames; private String productNames;
@Schema(description = "客户部门") @Schema(description = "客户部门")
@NotEmpty(message = "客户部门不能为空")
private String department; private String department;
} }
\ No newline at end of file
...@@ -67,6 +67,26 @@ public class InfoController { ...@@ -67,6 +67,26 @@ public class InfoController {
return success(true); return success(true);
} }
@DeleteMapping("/deleteByIds")
@Operation(summary = "删除客户拜访记录")
@Parameter(name = "ids", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('visit:info:delete')")
@Idempotent(timeout = 10, message = "重复请求,请稍后重试")
public CommonResult<Boolean> deleteInfoByIds(@RequestParam("ids") String ids) {
//如果ids是空的,返回异常提示,请先选择需要打印的记录
if (StringUtil.isEmpty(ids)) {
throw new RuntimeException("请先选择需要删除的记录");
}
String[] split = ids.split(",");
//转为List
List<Long> idList = new ArrayList<>();
for (String s : split) {
idList.add(Long.parseLong(s));
}
infoService.deleteInfoByIds(idList);
return success(true);
}
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得客户拜访记录") @Operation(summary = "获得客户拜访记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
...@@ -78,6 +98,7 @@ public class InfoController { ...@@ -78,6 +98,7 @@ public class InfoController {
@GetMapping("/getPrintListByIds") @GetMapping("/getPrintListByIds")
@Operation(summary = "获得客户拜访打印信息根据Ids") @Operation(summary = "获得客户拜访打印信息根据Ids")
@PreAuthorize("@ss.hasPermission('visit:info:print')")
public CommonResult<List<InfoPrintVO>> getPrintListByIds(@RequestParam("ids") String ids) { public CommonResult<List<InfoPrintVO>> getPrintListByIds(@RequestParam("ids") String ids) {
//如果ids是空的,返回异常提示,请先选择需要打印的记录 //如果ids是空的,返回异常提示,请先选择需要打印的记录
if (StringUtil.isEmpty(ids)) { if (StringUtil.isEmpty(ids)) {
...@@ -93,8 +114,41 @@ public class InfoController { ...@@ -93,8 +114,41 @@ public class InfoController {
return success(info); return success(info);
} }
@GetMapping("/getUnionPrintListByIds")
@Operation(summary = "获得合并客户拜访打印信息根据Ids")
@PreAuthorize("@ss.hasPermission('visit:info:print')")
public CommonResult<List<InfoPrintVO>> getUnionPrintListByIds(@RequestParam("ids") String ids) {
//如果ids是空的,返回异常提示,请先选择需要打印的记录
if (StringUtil.isEmpty(ids)) {
throw new RuntimeException("请先选择需要合并的打印的记录");
}
String[] split = ids.split(",");
//转为List
List<Long> idList = new ArrayList<>();
for (String s : split) {
idList.add(Long.parseLong(s));
}
List<InfoPrintVO> info = infoService.getUnionInfoByIds(idList);
return success(info);
}
@GetMapping("/getUnionAllPrintListByIds")
@Operation(summary = "获得合并客户拜访打印信息根据Ids")
@PreAuthorize("@ss.hasPermission('visit:info:print')")
public CommonResult<List<InfoPrintVO>> getUnionAllPrintListByIds(@Valid InfoPageReqVO pageReqVO) {
PageResult<InfoDO> pageResult = infoService.getInfoPage(pageReqVO);
if (pageResult.getTotal()==0){
throw new RuntimeException("当前不存在可打印数据");
}
List<InfoDO> list = pageResult.getList();
List<InfoPrintVO> info = infoService.getUnionAllInfoByIds(list);
return success(info);
}
@GetMapping("/getPrintListByCompanyName") @GetMapping("/getPrintListByCompanyName")
@Operation(summary = "获得客户拜访打印信息根据公司名称") @Operation(summary = "获得客户拜访打印信息根据公司名称")
@PreAuthorize("@ss.hasPermission('visit:customer-info:print')")
public CommonResult<List<InfoPrintVO>> getPrintListByCompanyName(@RequestParam("companyName") String companyName) { public CommonResult<List<InfoPrintVO>> getPrintListByCompanyName(@RequestParam("companyName") String companyName) {
InfoPrintVO info = infoService.getInfoByCompanyName(companyName); InfoPrintVO info = infoService.getInfoByCompanyName(companyName);
List<InfoPrintVO> infoPrintVO = new ArrayList<>(); List<InfoPrintVO> infoPrintVO = new ArrayList<>();
......
...@@ -10,6 +10,7 @@ import lombok.Data; ...@@ -10,6 +10,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
...@@ -32,7 +33,7 @@ public class InfoPrintVO { ...@@ -32,7 +33,7 @@ public class InfoPrintVO {
private String locationImage; private String locationImage;
@Schema(description = "最近四次拜访日期", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "最近四次拜访日期", requiredMode = Schema.RequiredMode.REQUIRED)
private List<LocalDateTime> visitDate; private List<LocalDate> visitDate;
@Schema(description = "拜访品种") @Schema(description = "拜访品种")
private String visitProductNames; private String visitProductNames;
......
...@@ -17,7 +17,6 @@ public class InfoSaveReqVO { ...@@ -17,7 +17,6 @@ public class InfoSaveReqVO {
private Long id; private Long id;
@Schema(description = "客户姓名", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "客户姓名", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "客户姓名不能为空")
private String customerName; private String customerName;
@Schema(description = "联系方式") @Schema(description = "联系方式")
...@@ -68,27 +67,35 @@ public class InfoSaveReqVO { ...@@ -68,27 +67,35 @@ public class InfoSaveReqVO {
private Integer customerStatus; private Integer customerStatus;
@Schema(description = "拜访品种(多选,逗号分隔)") @Schema(description = "拜访品种(多选,逗号分隔)")
@NotEmpty(message = "拜访品种不能为空")
private String visitProductIds; private String visitProductIds;
@Schema(description = "拜访品种名称(多选,逗号分隔)") @Schema(description = "拜访品种名称(多选,逗号分隔)")
@NotEmpty(message = "拜访品种名称不能为空")
private String visitProductNames; private String visitProductNames;
@Schema(description = "拜访方式(字典)") @Schema(description = "拜访方式(字典)")
@NotNull(message = "拜访方式不能为空")
private Integer visitMethod; private Integer visitMethod;
@Schema(description = "拜访类型(字典)", example = "2") @Schema(description = "拜访类型(字典)", example = "2")
@NotNull(message = "拜访类型不能为空")
private Integer visitType; private Integer visitType;
@Schema(description = "客户所属部门(字典)") @Schema(description = "客户所属部门(字典)")
@NotEmpty(message = "客户所属部门不能为空")
private String department; private String department;
@Schema(description = "服务内容") @Schema(description = "服务内容")
@NotEmpty(message = "服务内容不能为空")
private String serviceContent; private String serviceContent;
@Schema(description = "客户反馈") @Schema(description = "客户反馈")
@NotEmpty(message = "客户反馈不能为空")
private String customerFeedback; private String customerFeedback;
@Schema(description = "服务记录图片URL列表(JSON数组)") @Schema(description = "服务记录图片")
@NotEmpty(message = "服务记录图片不能为空")
private String serviceImages; private String serviceImages;
} }
\ No newline at end of file
...@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.visit.controller.admin.product; ...@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.visit.controller.admin.product;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent; import cn.iocoder.yudao.framework.idempotent.core.annotation.Idempotent;
import cn.iocoder.yudao.module.system.enums.common.SexEnum; import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import com.fhs.common.utils.StringUtil;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
...@@ -71,6 +72,26 @@ public class ProductController { ...@@ -71,6 +72,26 @@ public class ProductController {
return success(true); return success(true);
} }
@DeleteMapping("/deleteByIds")
@Operation(summary = "删除产品")
@Parameter(name = "ids", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('visit:product:delete')")
@Idempotent(timeout = 10, message = "重复请求,请稍后重试")
public CommonResult<Boolean> deleteProductByIds(@RequestParam("ids") String ids) {
//如果ids是空的,返回异常提示,请先选择需要打印的记录
if (StringUtil.isEmpty(ids)) {
throw new RuntimeException("请先选择需要删除的记录");
}
String[] split = ids.split(",");
//转为List
List<Long> idList = new ArrayList<>();
for (String s : split) {
idList.add(Long.parseLong(s));
}
productService.deleteProductByIds(idList);
return success(true);
}
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得产品") @Operation(summary = "获得产品")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
......
...@@ -13,14 +13,17 @@ public interface ErrorCodeConstants { ...@@ -13,14 +13,17 @@ public interface ErrorCodeConstants {
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1001000001, "产品不存在"); ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1001000001, "产品不存在");
ErrorCode PRODUCT_PRODUCT_EXISTS = new ErrorCode(1001000002, "导入产品已存在!"); ErrorCode PRODUCT_PRODUCT_EXISTS = new ErrorCode(1001000002, "导入产品已存在!");
ErrorCode PRODUCT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1001000003, "导入产品数据不能为空!"); ErrorCode PRODUCT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1001000003, "导入产品数据不能为空!");
ErrorCode PRODUCT_LIST_NOT_EXISTS = new ErrorCode(1001000004, "所选产品中含有不存在的数据");
// ========== 客户信息 ========== // ========== 客户信息 ==========
ErrorCode CUSTOMER_INFO_NOT_EXISTS = new ErrorCode(1002000001, "客户信息不存在"); ErrorCode CUSTOMER_INFO_NOT_EXISTS = new ErrorCode(1002000001, "客户信息不存在");
ErrorCode CUSTOMER_INFO_COMPANY_NAME_DUPLICATE = new ErrorCode(1002000002, "该公司名称已存在"); ErrorCode CUSTOMER_INFO_COMPANY_NAME_DUPLICATE = new ErrorCode(1002000002, "该公司名称已存在");
ErrorCode CUSTOMER_INFO_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002000003, "导入客户数据不能为空!"); ErrorCode CUSTOMER_INFO_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002000003, "导入客户数据不能为空!");
ErrorCode CUSTOMER_INFO_EXISTS = new ErrorCode(1002000004, "导入客户数据已存在!"); ErrorCode CUSTOMER_INFO_EXISTS = new ErrorCode(1002000004, "导入客户数据已存在!");
ErrorCode CUSTOMER_INFO_LIST_NOT_EXISTS = new ErrorCode(1002000005, "所选客户信息中含有不存在的数据");
// ========== 客户拜访记录 ========== // ========== 客户拜访记录 ==========
ErrorCode INFO_NOT_EXISTS = new ErrorCode(1003000001, "客户拜访记录不存在"); ErrorCode INFO_NOT_EXISTS = new ErrorCode(1003000001, "客户拜访记录不存在");
ErrorCode INFO_LIST_NOT_EXISTS = new ErrorCode(1003000002, "所选客户拜访记录中含有不存在的数据");
} }
...@@ -59,4 +59,6 @@ public interface CustomerInfoService { ...@@ -59,4 +59,6 @@ public interface CustomerInfoService {
List<CustomerInfoDO> getAllCustomerInfo(); List<CustomerInfoDO> getAllCustomerInfo();
CustomerImportRespVO importCustomerList(List<CustomerImportExcelVO> list, Boolean updateSupport); CustomerImportRespVO importCustomerList(List<CustomerImportExcelVO> list, Boolean updateSupport);
void deleteCustomerInfoByIds(List<Long> idList);
} }
\ No newline at end of file
...@@ -106,6 +106,16 @@ public class CustomerInfoServiceImpl implements CustomerInfoService { ...@@ -106,6 +106,16 @@ public class CustomerInfoServiceImpl implements CustomerInfoService {
customerInfoMapper.deleteById(id); customerInfoMapper.deleteById(id);
} }
@Override
public void deleteCustomerInfoByIds(List<Long> idList) {
// 校验存在
if(customerInfoMapper.selectByIds(idList).size() != idList.size()){
throw exception(INFO_LIST_NOT_EXISTS);
}
// 删除
customerInfoMapper.deleteByIds(idList);
}
private void validateCustomerInfoExists(Long id) { private void validateCustomerInfoExists(Long id) {
if (customerInfoMapper.selectById(id) == null) { if (customerInfoMapper.selectById(id) == null) {
throw exception(CUSTOMER_INFO_NOT_EXISTS); throw exception(CUSTOMER_INFO_NOT_EXISTS);
......
...@@ -65,4 +65,10 @@ public interface InfoService { ...@@ -65,4 +65,10 @@ public interface InfoService {
* @return 客户拜访记录 * @return 客户拜访记录
*/ */
InfoPrintVO getInfoByCompanyName(String companyName); InfoPrintVO getInfoByCompanyName(String companyName);
List<InfoPrintVO> getUnionInfoByIds(List<Long> idList);
void deleteInfoByIds(List<Long> idList);
List<InfoPrintVO> getUnionAllInfoByIds(List<InfoDO> list);
} }
\ No newline at end of file
...@@ -26,11 +26,14 @@ import org.springframework.validation.annotation.Validated; ...@@ -26,11 +26,14 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.YearMonth;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.visit.enums.ErrorCodeConstants.INFO_LIST_NOT_EXISTS;
import static cn.iocoder.yudao.module.visit.enums.ErrorCodeConstants.INFO_NOT_EXISTS; import static cn.iocoder.yudao.module.visit.enums.ErrorCodeConstants.INFO_NOT_EXISTS;
import static java.nio.file.Files.createFile; import static java.nio.file.Files.createFile;
...@@ -130,6 +133,86 @@ public class InfoServiceImpl implements InfoService { ...@@ -130,6 +133,86 @@ public class InfoServiceImpl implements InfoService {
infoMapper.deleteById(id); infoMapper.deleteById(id);
} }
@Override
public void deleteInfoByIds(List<Long> idList) {
// 校验存在
if(infoMapper.selectByIds(idList).size() != idList.size()){
throw exception(INFO_LIST_NOT_EXISTS);
}
// 删除
infoMapper.deleteByIds(idList);
}
@Override
public List<InfoPrintVO> getUnionAllInfoByIds(List<InfoDO> list) {
List<InfoPrintVO> vos = new ArrayList<>();
if (list.isEmpty()){
return vos;
}
List<List<InfoDO>> groupedByCompany = list.stream()
// 按公司名称分组,取每个公司最近四次(结果:Map<String, List<InfoDO>>)
.collect(Collectors.groupingBy(InfoDO::getCompanyName))
.values().stream() // 遍历每个公司的记录列表
.map(companyRecords ->
companyRecords.stream()
// 按拜访时间降序排序
.sorted(Comparator.comparing(InfoDO::getVisitDate).reversed())
.limit(4) // 取最近 4 条
.collect(Collectors.toList())
)
.collect(Collectors.toList()); // 收集为 List<List<InfoDO>>
groupedByCompany.forEach(info -> {
//服务内容(最近四次)
List<String> serviceContent = new ArrayList<>();
//服务图片(最多四张)
List<String> serviceImages = new ArrayList<>();
//拜访时间记录
List<LocalDate> visitDateList = new ArrayList<>();
//以第一个公司名称作为合并打印的公司
String companyName = info.get(0).getCompanyName();
//取最多4张服务图片
int maxImages = 4;
for (InfoDO infoDO : info) {
if (serviceImages.size() >= maxImages) break;
String imagesStr = infoDO.getServiceImages();
if (StringUtils.isNotBlank(imagesStr)) {
List<String> imageList = Arrays.asList(imagesStr.split(","));
int remaining = maxImages - serviceImages.size();
serviceImages.addAll(imageList.stream().limit(remaining).collect(Collectors.toList()));
}
}
//获取客户拜访记录成功,获取客户信息
info.forEach(infoDO -> {
//服务内容记录
serviceContent.add(infoDO.getServiceContent());
//拜访时间记录
visitDateList.add(infoDO.getVisitDate().toLocalDate());
});
//下面获取客户信息
InfoPrintVO infoPrintVO = new InfoPrintVO();
CustomerInfoDO customerInfoDO = customerInfoMapper.selectByCompanyName(companyName);
if (customerInfoDO != null){
infoPrintVO.setVisitProductNames(customerInfoDO.getProductNames());//本次拜访品种
infoPrintVO.setCompanyName(customerInfoDO.getCompanyName());//公司名称
infoPrintVO.setSalesman(customerInfoDO.getCreator());//拜访记录的创建者就是该记录的业务员
infoPrintVO.setLocationImage(customerInfoDO.getLocationImage());//公司定位图
infoPrintVO.setContact(customerInfoDO.getContact());
infoPrintVO.setDepartment(customerInfoDO.getDepartment());
}
//添加多次的信息
infoPrintVO.setServiceContent(serviceContent);
infoPrintVO.setVisitDate(visitDateList);
infoPrintVO.setServiceImages(String.join(",", serviceImages));
//拜访次数
infoPrintVO.setServiceCount(info.size());
vos.add(infoPrintVO);
});
return vos;
}
private void validateInfoExists(Long id) { private void validateInfoExists(Long id) {
if (infoMapper.selectById(id) == null) { if (infoMapper.selectById(id) == null) {
throw exception(INFO_NOT_EXISTS); throw exception(INFO_NOT_EXISTS);
...@@ -172,7 +255,7 @@ public class InfoServiceImpl implements InfoService { ...@@ -172,7 +255,7 @@ public class InfoServiceImpl implements InfoService {
infoPrintVO.setContact(infoDO.getContact());//联系方式 infoPrintVO.setContact(infoDO.getContact());//联系方式
infoPrintVO.setSalesman(infoDO.getCreator());//拜访记录的创建者就是该记录的业务员 infoPrintVO.setSalesman(infoDO.getCreator());//拜访记录的创建者就是该记录的业务员
infoPrintVO.setLocationImage(infoDO.getLocationImage()); infoPrintVO.setLocationImage(infoDO.getLocationImage());
infoPrintVO.setVisitDate(Collections.singletonList(infoDO.getVisitDate())); infoPrintVO.setVisitDate(Collections.singletonList(infoDO.getVisitDate().toLocalDate()));
//拜访次数按拜访id打印都是1 //拜访次数按拜访id打印都是1
infoPrintVO.setServiceCount(1); infoPrintVO.setServiceCount(1);
vos.add(infoPrintVO); vos.add(infoPrintVO);
...@@ -189,12 +272,20 @@ public class InfoServiceImpl implements InfoService { ...@@ -189,12 +272,20 @@ public class InfoServiceImpl implements InfoService {
//服务图片(最多四张) //服务图片(最多四张)
List<String> serviceImages = new ArrayList<>(); List<String> serviceImages = new ArrayList<>();
//拜访时间记录 //拜访时间记录
List<LocalDateTime> visitDateList = new ArrayList<>(); List<LocalDate> visitDateList = new ArrayList<>();
// 获取当前年月(用于比较月份)
YearMonth currentYearMonth = YearMonth.now();
//现根据客户名称获取历史拜访记录 //现根据客户名称获取历史拜访记录
List<InfoDO> info = infoMapper.selectByCompanyName(companyName); List<InfoDO> info = infoMapper.selectByCompanyName(companyName);
if (info != null && !info.isEmpty()){ if (info != null && !info.isEmpty()){
//取最近四次 //取最近四次
info = info.stream().sorted(Comparator.comparing(InfoDO::getVisitDate).reversed()).limit(4).collect(Collectors.toList()); info = info.stream()
// 筛选本月记录(基于 LocalDateTime)
.filter(infoDO -> {
LocalDateTime visitDateTime = infoDO.getVisitDate();
return YearMonth.from(visitDateTime.toLocalDate()).equals(currentYearMonth);
})
.sorted(Comparator.comparing(InfoDO::getVisitDate).reversed()).limit(4).collect(Collectors.toList());
//取最多4张服务图片 //取最多4张服务图片
int maxImages = 4; int maxImages = 4;
for (InfoDO infoDO : info) { for (InfoDO infoDO : info) {
...@@ -212,7 +303,7 @@ public class InfoServiceImpl implements InfoService { ...@@ -212,7 +303,7 @@ public class InfoServiceImpl implements InfoService {
//服务内容记录 //服务内容记录
serviceContent.add(infoDO.getServiceContent()); serviceContent.add(infoDO.getServiceContent());
//拜访时间记录 //拜访时间记录
visitDateList.add(infoDO.getVisitDate()); visitDateList.add(infoDO.getVisitDate().toLocalDate());
}); });
//下面获取客户信息 //下面获取客户信息
CustomerInfoDO customerInfoDO = customerInfoMapper.selectByCompanyName(companyName); CustomerInfoDO customerInfoDO = customerInfoMapper.selectByCompanyName(companyName);
...@@ -234,4 +325,76 @@ public class InfoServiceImpl implements InfoService { ...@@ -234,4 +325,76 @@ public class InfoServiceImpl implements InfoService {
return infoPrintVO; return infoPrintVO;
} }
@Override
public List<InfoPrintVO> getUnionInfoByIds(List<Long> idList) {
List<InfoPrintVO> vos = new ArrayList<>();
//现根据客户名称获取历史拜访记录
List<InfoDO> infoAll = infoMapper.selectByIds(idList);
if (infoAll != null && !infoAll.isEmpty()){
List<List<InfoDO>> groupedByCompany = infoAll.stream()
// 按公司名称分组,取每个公司最近四次(结果:Map<String, List<InfoDO>>)
.collect(Collectors.groupingBy(InfoDO::getCompanyName))
.values().stream() // 遍历每个公司的记录列表
.map(companyRecords ->
companyRecords.stream()
// 按拜访时间降序排序
.sorted(Comparator.comparing(InfoDO::getVisitDate).reversed())
.limit(4) // 取最近 4 条
.collect(Collectors.toList())
)
.collect(Collectors.toList()); // 收集为 List<List<InfoDO>>
groupedByCompany.forEach(info -> {
//服务内容(最近四次)
List<String> serviceContent = new ArrayList<>();
//服务图片(最多四张)
List<String> serviceImages = new ArrayList<>();
//拜访时间记录
List<LocalDate> visitDateList = new ArrayList<>();
//以第一个公司名称作为合并打印的公司
String companyName = info.get(0).getCompanyName();
//取最多4张服务图片
int maxImages = 4;
for (InfoDO infoDO : info) {
if (serviceImages.size() >= maxImages) break;
String imagesStr = infoDO.getServiceImages();
if (StringUtils.isNotBlank(imagesStr)) {
List<String> imageList = Arrays.asList(imagesStr.split(","));
int remaining = maxImages - serviceImages.size();
serviceImages.addAll(imageList.stream().limit(remaining).collect(Collectors.toList()));
}
}
//获取客户拜访记录成功,获取客户信息
info.forEach(infoDO -> {
//服务内容记录
serviceContent.add(infoDO.getServiceContent());
//拜访时间记录
visitDateList.add(infoDO.getVisitDate().toLocalDate());
});
//下面获取客户信息
InfoPrintVO infoPrintVO = new InfoPrintVO();
CustomerInfoDO customerInfoDO = customerInfoMapper.selectByCompanyName(companyName);
if (customerInfoDO != null){
infoPrintVO.setVisitProductNames(customerInfoDO.getProductNames());//本次拜访品种
infoPrintVO.setCompanyName(customerInfoDO.getCompanyName());//公司名称
infoPrintVO.setSalesman(customerInfoDO.getCreator());//拜访记录的创建者就是该记录的业务员
infoPrintVO.setLocationImage(customerInfoDO.getLocationImage());//公司定位图
infoPrintVO.setContact(customerInfoDO.getContact());
infoPrintVO.setDepartment(customerInfoDO.getDepartment());
}
//添加多次的信息
infoPrintVO.setServiceContent(serviceContent);
infoPrintVO.setVisitDate(visitDateList);
infoPrintVO.setServiceImages(String.join(",", serviceImages));
//拜访次数
infoPrintVO.setServiceCount(info.size());
vos.add(infoPrintVO);
});
}
return vos;
}
} }
\ No newline at end of file
...@@ -61,4 +61,6 @@ public interface ProductService { ...@@ -61,4 +61,6 @@ public interface ProductService {
ProductImportRespVO importProductList(List<ProductImportExcelVO> list, Boolean updateSupport); ProductImportRespVO importProductList(List<ProductImportExcelVO> list, Boolean updateSupport);
List<ProductSimpleReqVO> getProductByIds(String productIds); List<ProductSimpleReqVO> getProductByIds(String productIds);
void deleteProductByIds(List<Long> idList);
} }
\ No newline at end of file
...@@ -65,6 +65,18 @@ public class ProductServiceImpl implements ProductService { ...@@ -65,6 +65,18 @@ public class ProductServiceImpl implements ProductService {
productMapper.deleteById(id); productMapper.deleteById(id);
} }
@Override
public void deleteProductByIds(List<Long> idList) {
// 校验存在
if(productMapper.selectByIds(idList).size() != idList.size()){
throw exception(PRODUCT_LIST_NOT_EXISTS);
}
// 删除
productMapper.deleteByIds(idList);
}
private void validateProductExists(Long id) { private void validateProductExists(Long id) {
if (productMapper.selectById(id) == null) { if (productMapper.selectById(id) == null) {
throw exception(PRODUCT_NOT_EXISTS); throw exception(PRODUCT_NOT_EXISTS);
......
...@@ -65,6 +65,11 @@ export const CustomerInfoApi = { ...@@ -65,6 +65,11 @@ export const CustomerInfoApi = {
return await request.delete({ url: `/visit/customer-info/delete?id=` + id }) return await request.delete({ url: `/visit/customer-info/delete?id=` + id })
}, },
// 批量删除客户信息
deleteCustomerInfoByIds: async (ids: String) => {
return await request.delete({ url: `/visit/customer-info/deleteByIds?ids=` + ids })
},
// 导出客户信息 Excel // 导出客户信息 Excel
exportCustomerInfo: async (params) => { exportCustomerInfo: async (params) => {
return await request.download({ url: `/visit/customer-info/export-excel`, params }) return await request.download({ url: `/visit/customer-info/export-excel`, params })
......
...@@ -67,6 +67,15 @@ export const InfoApi = { ...@@ -67,6 +67,15 @@ export const InfoApi = {
return await request.get({ url: `/visit/info/get?id=` + id }) return await request.get({ url: `/visit/info/get?id=` + id })
}, },
//查询合并打印客户拜访记录
getUnionPrintListByIds: async (ids: string) => {
return await request.get({ url: `/visit/info/getUnionPrintListByIds`, params: { ids } })
},
//查询合并打印客户拜访记录(筛选出的所有数据)
getUnionAllPrintListByIds: async (params: any) => {
return await request.get({ url: `/visit/info/getUnionAllPrintListByIds`, params })
},
//查询打印客户拜访记录 //查询打印客户拜访记录
getPrintListByIds: async (ids: string) => { getPrintListByIds: async (ids: string) => {
return await request.get({ url: `/visit/info/getPrintListByIds`, params: { ids } }) return await request.get({ url: `/visit/info/getPrintListByIds`, params: { ids } })
...@@ -93,6 +102,12 @@ export const InfoApi = { ...@@ -93,6 +102,12 @@ export const InfoApi = {
return await request.delete({ url: `/visit/info/delete?id=` + id }) return await request.delete({ url: `/visit/info/delete?id=` + id })
}, },
// 删除客户拜访记录
deleteInfoByIds: async (ids: String) => {
return await request.delete({ url: `/visit/info/deleteByIds?ids=` + ids })
},
// 导出客户拜访记录 Excel // 导出客户拜访记录 Excel
exportInfo: async (params) => { exportInfo: async (params) => {
return await request.download({ url: `/visit/info/export-excel`, params }) return await request.download({ url: `/visit/info/export-excel`, params })
......
...@@ -41,6 +41,12 @@ export const ProductApi = { ...@@ -41,6 +41,12 @@ export const ProductApi = {
return await request.delete({ url: `/visit/product/delete?id=` + id }) return await request.delete({ url: `/visit/product/delete?id=` + id })
}, },
// 批量删除产品
deleteProductByIds: async (ids: String) => {
return await request.delete({ url: `/visit/product/deleteByIds?ids=` + ids })
},
// 导出产品 Excel // 导出产品 Excel
exportProduct: async (params) => { exportProduct: async (params) => {
return await request.download({ url: `/visit/product/export-excel`, params }) return await request.download({ url: `/visit/product/export-excel`, params })
......
...@@ -16,10 +16,9 @@ ...@@ -16,10 +16,9 @@
:on-success="uploadSuccess" :on-success="uploadSuccess"
list-type="picture-card" list-type="picture-card"
> >
<div class="upload-empty"> <div class="upload-empty" >
<slot name="empty"> <slot name="empty">
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
<!-- <span>请上传图片</span> -->
</slot> </slot>
</div> </div>
<template #file="{ file }"> <template #file="{ file }">
...@@ -116,6 +115,7 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => { ...@@ -116,6 +115,7 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
// 图片上传成功 // 图片上传成功
interface UploadEmits { interface UploadEmits {
(e: 'update:modelValue', value: string[]): void (e: 'update:modelValue', value: string[]): void
(e: 'change', value: string[]): void
} }
const emit = defineEmits<UploadEmits>() const emit = defineEmits<UploadEmits>()
...@@ -159,16 +159,16 @@ watch( ...@@ -159,16 +159,16 @@ watch(
const emitUpdateModelValue = () => { const emitUpdateModelValue = () => {
let result: string[] = fileList.value.map((file) => file.url!) let result: string[] = fileList.value.map((file) => file.url!)
emit('update:modelValue', result) emit('update:modelValue', result)
emit('change', result)
} }
// 删除图片 // 删除图片
const handleRemove = (uploadFile: UploadFile) => { const handleRemove = (uploadFile: UploadFile) => {
fileList.value = fileList.value.filter( fileList.value = fileList.value.filter(
(item) => item.url !== uploadFile.url || item.name !== uploadFile.name (item) => item.url !== uploadFile.url || item.name !== uploadFile.name
) )
emit( const result = fileList.value.map((file) => file.url!)
'update:modelValue', emit('update:modelValue', result)
fileList.value.map((file) => file.url!) emit('change', result)
)
} }
// 图片上传错误提示 // 图片上传错误提示
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
v-loading="formLoading" v-loading="formLoading"
:disabled="isDetail"
> >
<el-form-item label="客户姓名" prop="customerName"> <el-form-item label="客户姓名" prop="customerName">
<el-input v-model="formData.customerName" placeholder="请输入客户姓名" /> <el-input v-model="formData.customerName" placeholder="请输入客户姓名" />
...@@ -51,7 +52,7 @@ ...@@ -51,7 +52,7 @@
placeholder="请点击右侧选择" placeholder="请点击右侧选择"
readonly readonly
> >
<template #append> <template #append v-if="formType !== 'detail'">
<el-link type="primary" @click="openMapPicker">选择地址</el-link> <el-link type="primary" @click="openMapPicker">选择地址</el-link>
</template> </template>
</el-input> </el-input>
...@@ -69,13 +70,13 @@ ...@@ -69,13 +70,13 @@
<!-- <el-form-item label="定位静态图 URL" prop="locationImage">--> <!-- <el-form-item label="定位静态图 URL" prop="locationImage">-->
<!-- <UploadImg v-model="formData.locationImage" />--> <!-- <UploadImg v-model="formData.locationImage" />-->
<!-- </el-form-item>--> <!-- </el-form-item>-->
<el-form-item label="产品信息" prop="productIds"> <el-form-item label="产品信息" prop="productNames">
<el-input <el-input
v-model="formData.productNames" v-model="formData.productNames"
placeholder="请点击右侧选择" placeholder="请点击右侧选择"
readonly readonly
> >
<template #append> <template #append v-if="formType !== 'detail'">
<el-link type="primary" @click="openProduct">选择产品</el-link> <el-link type="primary" @click="openProduct">选择产品</el-link>
</template> </template>
</el-input> </el-input>
...@@ -92,7 +93,7 @@ ...@@ -92,7 +93,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="formType !== 'detail'">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button> <el-button @click="dialogVisible = false">取 消</el-button>
</template> </template>
</Dialog> </Dialog>
...@@ -111,6 +112,7 @@ defineOptions({ name: 'CustomerInfoForm' }) ...@@ -111,6 +112,7 @@ defineOptions({ name: 'CustomerInfoForm' })
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const isDetail = computed(() => formType.value === 'detail')//detail说明是查看详情,要只读
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
...@@ -159,11 +161,13 @@ const formRules = reactive({ ...@@ -159,11 +161,13 @@ const formRules = reactive({
provinceName: [{ required: true, message: '省名称不能为空', trigger: 'blur' }], provinceName: [{ required: true, message: '省名称不能为空', trigger: 'blur' }],
cityName: [{ required: true, message: '市名称不能为空', trigger: 'blur' }], cityName: [{ required: true, message: '市名称不能为空', trigger: 'blur' }],
areaName: [{ required: true, message: '区名称不能为空', trigger: 'blur' }], areaName: [{ required: true, message: '区名称不能为空', trigger: 'blur' }],
regionFullName: [{ required: true, message: '所在地区不能为空', trigger: 'blur' }], regionFullName: [{ required: true, message: '所在地区不能为空', trigger: 'change' }],
locationText: [{ required: true, message: '详细地址不能为空', trigger: 'change' }], locationText: [{ required: true, message: '详细地址不能为空', trigger: 'change' }],
longitude: [{ required: true, message: '经度不能为空', trigger: 'blur' }], longitude: [{ required: true, message: '经度不能为空', trigger: 'blur' }],
latitude: [{ required: true, message: '纬度不能为空', trigger: 'blur' }], latitude: [{ required: true, message: '纬度不能为空', trigger: 'blur' }],
locationImage: [{ required: true, message: '定位静态图不能为空', trigger: 'blur' }], locationImage: [{ required: true, message: '定位静态图不能为空', trigger: 'blur' }],
productNames: [{ required: true, message: '产品信息名称不能为空', trigger: 'blur|change' }],
productIds: [{ required: true, message: '产品信息不能为空', trigger: 'blur|change' }],
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
...@@ -179,6 +183,7 @@ const handleUpdateProduct = (ids: string,names: string) => { ...@@ -179,6 +183,7 @@ const handleUpdateProduct = (ids: string,names: string) => {
//存入表单 //存入表单
formData.value.productIds = ids; formData.value.productIds = ids;
formData.value.productNames = names; formData.value.productNames = names;
formRef.value?.validateField('productNames')
}; };
//===========================================地图相关操作================================= //===========================================地图相关操作=================================
const mapPickerRef = ref() const mapPickerRef = ref()
...@@ -221,7 +226,7 @@ const handleLocation = ({ lat, lng, address }) => { ...@@ -221,7 +226,7 @@ const handleLocation = ({ lat, lng, address }) => {
city = '市辖区' city = '市辖区'
} }
selectedOptions.value = [province, city, area]//专门回显用的 selectedOptions.value = [province, city, area]//专门回显用的
formRef.value?.validateField('regionFullName')
} else { } else {
console.warn('逆地址解析失败:', res.data.message) console.warn('逆地址解析失败:', res.data.message)
} }
......
...@@ -72,6 +72,14 @@ ...@@ -72,6 +72,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="danger"
plain
@click="handleDeleteRows()"
v-hasPermi="['visit:customer-info:delete']"
>
<Icon icon="ep:delete" class="mr-5px" /> 删除
</el-button>
<el-button <el-button
type="warning" type="warning"
plain plain
...@@ -100,7 +108,7 @@ ...@@ -100,7 +108,7 @@
<el-table-column label="编号" align="center" prop="id" /> <el-table-column label="编号" align="center" prop="id" />
<el-table-column label="客户姓名" align="center" prop="customerName" min-width="100px"/> <el-table-column label="客户姓名" align="center" prop="customerName" min-width="100px"/>
<el-table-column label="联系方式" align="center" prop="contact" min-width="120px"/> <el-table-column label="联系方式" align="center" prop="contact" min-width="120px"/>
<el-table-column label="公司名称" align="center" prop="companyName" min-width="160px"/> <el-table-column label="公司名称" align="center" prop="companyName" min-width="240px"/>
<el-table-column label="性质等级" align="center" prop="customerType"> <el-table-column label="性质等级" align="center" prop="customerType">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CUSTOMER_TYPE" :value="scope.row.customerType" /> <dict-tag :type="DICT_TYPE.CUSTOMER_TYPE" :value="scope.row.customerType" />
...@@ -119,7 +127,7 @@ ...@@ -119,7 +127,7 @@
<ProductList :ids=scope.row.productIds /> <ProductList :ids=scope.row.productIds />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" min-width="320px" fixed="right"> <el-table-column label="操作" align="center" min-width="380px" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button <el-button
link link
...@@ -132,9 +140,17 @@ ...@@ -132,9 +140,17 @@
link link
type="primary" type="primary"
@click="handlePrint(scope.row.companyName)" @click="handlePrint(scope.row.companyName)"
v-hasPermi="['visit:customer-info:print']"
> >
打印拜访记录 打印拜访记录
</el-button> </el-button>
<el-button
link
type="primary"
@click="openForm('detail', scope.row.id)"
>
查看详情
</el-button>
<el-button <el-button
link link
type="primary" type="primary"
...@@ -166,7 +182,6 @@ ...@@ -166,7 +182,6 @@
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<CustomerInfoForm ref="formRef" @success="getList" /> <CustomerInfoForm ref="formRef" @success="getList" />
<!-- 打印弹窗 --> <!-- 打印弹窗 -->
<!-- <VisitPrint ref="visitPrintRef" :data="selectedData" />-->
<Teleport to="body"> <Teleport to="body">
<div v-show="false"> <div v-show="false">
<PrintContent ref="printComp" :dataList="printData" /> <PrintContent ref="printComp" :dataList="printData" />
...@@ -300,6 +315,22 @@ const handleDelete = async (id: number) => { ...@@ -300,6 +315,22 @@ const handleDelete = async (id: number) => {
} catch {} } catch {}
} }
/** 删除按钮操作 */
const handleDeleteRows = async () => {
try {
if (multipleSelection.value.length === 0) {
return message.warning('请先选择需要删除的记录')
}
// 删除的二次确认
await message.delConfirm()
// 发起删除
await CustomerInfoApi.deleteCustomerInfoByIds(multipleSelection.value.join(','))
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {
......
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
v-loading="formLoading" v-loading="formLoading"
:disabled="isDetail"
> >
<el-form-item label="拜访人姓名" prop="customerName"> <el-form-item label="客户姓名" prop="customerName">
<el-input v-model="formData.customerName" placeholder="请输入拜访人姓名" /> <el-input v-model="formData.customerName" placeholder="请输入拜访人姓名" />
</el-form-item> </el-form-item>
<el-form-item label="联系方式" prop="contact"> <el-form-item label="联系方式" prop="contact">
...@@ -38,7 +39,7 @@ ...@@ -38,7 +39,7 @@
placeholder="请点击右侧选择" placeholder="请点击右侧选择"
readonly readonly
> >
<template #append> <template #append v-if="formType !== 'detail'" >
<el-link type="primary" @click="openMapPicker">选择地址</el-link> <el-link type="primary" @click="openMapPicker">选择地址</el-link>
</template> </template>
</el-input> </el-input>
...@@ -78,7 +79,7 @@ ...@@ -78,7 +79,7 @@
placeholder="请点击右侧选择" placeholder="请点击右侧选择"
readonly readonly
> >
<template #append> <template #append v-if="formType !== 'detail'" >
<el-link type="primary" @click="openProduct">选择产品</el-link> <el-link type="primary" @click="openProduct">选择产品</el-link>
</template> </template>
</el-input> </el-input>
...@@ -124,38 +125,40 @@ ...@@ -124,38 +125,40 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="服务记录图片" prop="serviceImages"> <el-form-item label="服务记录图片" prop="serviceImages">
<UploadImgs v-model="formData.serviceImages" :limit="9"/> <UploadImgs :class="{hide_box: isDetail}" v-model="formData.serviceImages" :limit="9" @change="onServiceImageChange" :disabled="isDetail" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="formType !== 'detail'" >确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button> <el-button @click="dialogVisible = false">取 消</el-button>
</template> </template>
</Dialog> </Dialog>
<productTable ref="productListRef" @updateProduct="handleUpdateProduct" :ids="formData.visitProductIds"/> <productTable ref="productListRef" @updateProduct="handleUpdateProduct" :ids="formData.visitProductIds"/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {getIntDictOptions, DICT_TYPE, getStrDictOptions} from '@/utils/dict' import {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict'
import { InfoApi, InfoVO } from '@/api/visit/info' import {InfoApi, InfoVO} from '@/api/visit/info'
import { CustomerInfoApi } from '@/api/visit/customerinfo' import {CustomerInfoApi} from '@/api/visit/customerinfo'
import productTable from "@/views/visit/customerinfo/productTable.vue"; import productTable from "@/views/visit/customerinfo/productTable.vue";
import { pcaTextArr } from 'element-china-area-data' import {pcaTextArr} from 'element-china-area-data'
import MapPicker from '../util/MapPicker.vue' import MapPicker from '../util/MapPicker.vue'
import { onMounted } from 'vue' import {onMounted} from 'vue'
import request from "@/config/axios";
import {homesApi} from "@/api/visit/home"; import {homesApi} from "@/api/visit/home";
/** 客户拜访记录 表单 */ /** 客户拜访记录 表单 */
defineOptions({ name: 'InfoForm' }) defineOptions({ name: 'InfoForm' })
const upload_btn = ref(true)//定义图片上传展示隐藏
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formType = ref('') // 表单的类型:create - 新增;update - 修改;detail - 查看
const selectedOptions = ref<string[]>([]) const selectedOptions = ref<string[]>([])
const isDetail = computed(() => formType.value === 'detail')//detail说明是查看详情,要只读
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
customerName: undefined, customerName: undefined,
...@@ -181,21 +184,25 @@ const formData = ref({ ...@@ -181,21 +184,25 @@ const formData = ref({
serviceImages: [], serviceImages: [],
}) })
const formRules = reactive({ const formRules = reactive({
customerName: [{ required: true, message: '拜访人姓名不能为空', trigger: 'blur' }],
companyName: [{ required: true, message: '客户公司名称不能为空', trigger: 'blur|change' }], companyName: [{ required: true, message: '客户公司名称不能为空', trigger: 'blur|change' }],
provinceName: [{ required: true, message: '省名称不能为空', trigger: 'blur' }], provinceName: [{ required: true, message: '省名称不能为空', trigger: 'blur' }],
cityName: [{ required: true, message: '市名称不能为空', trigger: 'blur' }], cityName: [{ required: true, message: '市名称不能为空', trigger: 'blur' }],
areaName: [{ required: true, message: '区名称不能为空', trigger: 'blur' }], areaName: [{ required: true, message: '区名称不能为空', trigger: 'blur' }],
regionFullName: [{ required: true, message: '所在地区不能为空', trigger: 'blur' }], regionFullName: [{ required: true, message: '所在地区不能为空', trigger: 'change' }],
locationText: [{ required: true, message: '详细地址不能为空', trigger: 'change' }], locationText: [{ required: true, message: '详细地址不能为空', trigger: 'change' }],
longitude: [{ required: true, message: '经度不能为空', trigger: 'blur' }], longitude: [{ required: true, message: '经度不能为空', trigger: 'blur' }],
latitude: [{ required: true, message: '纬度不能为空', trigger: 'blur' }], latitude: [{ required: true, message: '纬度不能为空', trigger: 'blur' }],
locationImage: [{ required: true, message: '定位静态图不能为空', trigger: 'blur' }], locationImage: [{ required: true, message: '定位静态图不能为空', trigger: 'blur' }],
visitDate: [{ required: true, message: '拜访日期不能为空', trigger: 'blur' }], visitDate: [{ required: true, message: '拜访日期不能为空', trigger: 'blur' }],
customerStatus: [{ required: true, message: '性质等级不能为空', trigger: 'blur' }], customerStatus: [{ required: true, message: '性质等级不能为空', trigger: 'change' }],
department: [{ required: true, message: '客户部门不能为空', trigger: 'blur' }], department: [{ required: true, message: '客户部门不能为空', trigger: 'change' }],
visitMethod: [{ required: true, message: '拜访方式不能为空', trigger: 'change' }],
visitType: [{ required: true, message: '拜访类型不能为空', trigger: 'change' }],
visitProductNames: [{ required: true, message: '拜访品种名称不能为空', trigger: 'blur|change' }],
visitProductIds: [{ required: true, message: '拜访品种不能为空', trigger: 'blur|change' }],
serviceContent: [{ required: true, message: '请输入服务内容', trigger: 'blur' }, { max: 500, message: '最多输入 500 个字符', trigger: 'blur' }], serviceContent: [{ required: true, message: '请输入服务内容', trigger: 'blur' }, { max: 500, message: '最多输入 500 个字符', trigger: 'blur' }],
customerFeedback: [{ required: true, message: '请输入客户反馈', trigger: 'blur' }, { max: 500, message: '最多输入 500 个字符', trigger: 'blur' }], customerFeedback: [{ required: true, message: '请输入客户反馈', trigger: 'blur' }, { max: 500, message: '最多输入 500 个字符', trigger: 'blur' }],
serviceImages: [{ required: true, message: '请上传服务记录图片', trigger: 'blur|change' }]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
//===========================================下拉搜索相关==================================== //===========================================下拉搜索相关====================================
...@@ -268,6 +275,11 @@ const handleSelect = (item: Record<string, any>) => { ...@@ -268,6 +275,11 @@ const handleSelect = (item: Record<string, any>) => {
formData.value.customerStatus = item.customerStatus; formData.value.customerStatus = item.customerStatus;
formData.value.department = item.department; formData.value.department = item.department;
restoreSelectedOptionsFromForm(); restoreSelectedOptionsFromForm();
formRef.value?.validateField('companyName')
formRef.value?.validateField('regionFullName')
formRef.value?.validateField('locationText')
formRef.value?.validateField('customerStatus')
formRef.value?.validateField('department')
} }
//===========================================选择产品相关操作================================= //===========================================选择产品相关操作=================================
const productListRef = ref<InstanceType<typeof productTable> | null>(null); // 产品列表 Ref const productListRef = ref<InstanceType<typeof productTable> | null>(null); // 产品列表 Ref
...@@ -282,6 +294,7 @@ const handleUpdateProduct = (ids: string,names: string) => { ...@@ -282,6 +294,7 @@ const handleUpdateProduct = (ids: string,names: string) => {
formData.value.visitProductIds = ids; formData.value.visitProductIds = ids;
console.log(names) console.log(names)
formData.value.visitProductNames = names; formData.value.visitProductNames = names;
formRef.value?.validateField('visitProductNames')
}; };
//===========================================地图相关操作================================= //===========================================地图相关操作=================================
const mapPickerRef = ref() const mapPickerRef = ref()
...@@ -323,7 +336,7 @@ const handleLocation = ({ lat, lng, address }) => { ...@@ -323,7 +336,7 @@ const handleLocation = ({ lat, lng, address }) => {
city = '市辖区' city = '市辖区'
} }
selectedOptions.value = [province, city, area]//专门回显用的 selectedOptions.value = [province, city, area]//专门回显用的
formRef.value?.validateField('regionFullName')
} else { } else {
console.warn('逆地址解析失败:', res.data.message) console.warn('逆地址解析失败:', res.data.message)
} }
...@@ -442,8 +455,16 @@ const resetForm = () => { ...@@ -442,8 +455,16 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
selectedOptions.value = []; selectedOptions.value = [];
} }
const onServiceImageChange = () => {
// 强制触发校验
formRef.value?.validateField('serviceImages')
}
onMounted(() => { onMounted(() => {
loadAll() loadAll()
}) })
</script> </script>
<style lang="scss" scoped>
.hide_box ::v-deep .el-upload--picture-card {
display: none;
}
</style>
...@@ -117,6 +117,14 @@ ...@@ -117,6 +117,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="danger"
plain
@click="handleDeleteRows()"
v-hasPermi="['visit:info:delete']"
>
<Icon icon="ep:delete" class="mr-5px" /> 删除
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
...@@ -131,9 +139,28 @@ ...@@ -131,9 +139,28 @@
plain plain
@click="handlePrint" @click="handlePrint"
:loading="exportLoading" :loading="exportLoading"
v-hasPermi="['visit:info:print']"
> >
<Icon icon="ep:printer" class="mr-5px" /> 批量打印 <Icon icon="ep:printer" class="mr-5px" /> 批量打印
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleUnionPrint"
:loading="exportLoading"
v-hasPermi="['visit:info:print']"
>
<Icon icon="ep:printer" class="mr-5px" /> 多选合并打印
</el-button>
<el-button
type="primary"
plain
@click="handleUnionAllPrint"
:loading="exportLoading"
v-hasPermi="['visit:info:print']"
>
<Icon icon="ep:printer" class="mr-5px" /> 全选合并打印
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
...@@ -145,7 +172,7 @@ ...@@ -145,7 +172,7 @@
<el-table-column label="主键ID" align="center" prop="id" /> <el-table-column label="主键ID" align="center" prop="id" />
<el-table-column label="拜访人姓名" align="center" prop="customerName" width="100px"/> <el-table-column label="拜访人姓名" align="center" prop="customerName" width="100px"/>
<el-table-column label="联系方式" align="center" prop="contact" width="120px"/> <el-table-column label="联系方式" align="center" prop="contact" width="120px"/>
<el-table-column label="客户公司名称" align="center" prop="companyName" width="160px" /> <el-table-column label="客户公司名称" align="center" prop="companyName" width="240px" />
<el-table-column label="所在地区" align="center" prop="regionFullName" width="160px" /> <el-table-column label="所在地区" align="center" prop="regionFullName" width="160px" />
<el-table-column <el-table-column
label="拜访日期" label="拜访日期"
...@@ -181,8 +208,15 @@ ...@@ -181,8 +208,15 @@
:formatter="dateFormatter" :formatter="dateFormatter"
width="180px" width="180px"
/> />
<el-table-column label="操作" align="center" min-width="120px" fixed="right"> <el-table-column label="操作" align="center" min-width="180px" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button
link
type="primary"
@click="openForm('detail', scope.row.id)"
>
查看详情
</el-button>
<el-button <el-button
link link
type="primary" type="primary"
...@@ -214,8 +248,6 @@ ...@@ -214,8 +248,6 @@
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<InfoForm ref="formRef" @success="getList" /> <InfoForm ref="formRef" @success="getList" />
<!-- 打印弹窗 --> <!-- 打印弹窗 -->
<!-- <VisitPrint ref="visitPrintRef" :data="selectedData" />-->
<Teleport to="body"> <Teleport to="body">
<div v-show="false"> <div v-show="false">
<PrintContent ref="printComp" :dataList="printData" /> <PrintContent ref="printComp" :dataList="printData" />
...@@ -228,20 +260,17 @@ ...@@ -228,20 +260,17 @@
import {DICT_TYPE, getIntDictOptions} from '@/utils/dict' import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
import {dateFormatter} from '@/utils/formatTime' import {dateFormatter} from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import {InfoApi, InfoPrintVO, InfoVO} from '@/api/visit/info' import {InfoApi, InfoVO} from '@/api/visit/info'
import InfoForm from './InfoForm.vue' import InfoForm from './InfoForm.vue'
import ProductList from "@/views/visit/util/ProductList.vue"; import ProductList from "@/views/visit/util/ProductList.vue";
import {useRoute} from 'vue-router' import {useRoute} from 'vue-router'
import { usePrint } from '../util/print' import {usePrint} from '../util/print'
import PrintContent from '../util/PrintContent.vue' import PrintContent from '../util/PrintContent.vue'
//========== //==========
const printData = ref([ const printData = ref([])
{ name: '张三', date: '2025-06-01', remark: '拜访说明', image: 'https://image.xnszz.com//20250604/visit_1749022959344.png' },
{ name: '李四', date: '2025-06-03', remark: '再次拜访', image: 'https://image.xnszz.com//20250604/visit_1749022959344.png' },
])
const printComp = ref() const printComp = ref()
...@@ -250,7 +279,24 @@ const handlePrint = async () => { ...@@ -250,7 +279,24 @@ const handlePrint = async () => {
return message.warning('请先选择需要打印的记录') return message.warning('请先选择需要打印的记录')
} }
printData.value = await InfoApi.getPrintListByIds(multipleSelection.value.join(',')) printData.value = await InfoApi.getPrintListByIds(multipleSelection.value.join(','))
console.log(printData.value) await nextTick()
const dom = printComp.value.printRef
if (dom) usePrint(dom)
}
const handleUnionPrint = async () => {
if (multipleSelection.value.length === 0) {
return message.warning('请先选择需要合并打印的记录')
}
printData.value = await InfoApi.getUnionPrintListByIds(multipleSelection.value.join(','))
await nextTick()
const dom = printComp.value.printRef
if (dom) usePrint(dom)
}
const handleUnionAllPrint = async () => {
if (list.value.length === 0) {
return message.warning('当前不存在可打印数据')
}
printData.value = await InfoApi.getUnionAllPrintListByIds(queryParams)
await nextTick() await nextTick()
const dom = printComp.value.printRef const dom = printComp.value.printRef
if (dom) usePrint(dom) if (dom) usePrint(dom)
...@@ -315,7 +361,7 @@ const resetQuery = () => { ...@@ -315,7 +361,7 @@ const resetQuery = () => {
handleQuery() handleQuery()
} }
/** 添加/修改操作 */ /** 添加/修改/查看操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
...@@ -334,6 +380,22 @@ const handleDelete = async (id: number) => { ...@@ -334,6 +380,22 @@ const handleDelete = async (id: number) => {
} catch {} } catch {}
} }
/** 批量删除按钮操作 */
const handleDeleteRows = async () => {
try {
if (multipleSelection.value.length === 0) {
return message.warning('请先选择需要删除的记录')
}
// 删除的二次确认
await message.delConfirm()
// 发起删除
await InfoApi.deleteInfoByIds(multipleSelection.value.join(','))
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {
......
...@@ -72,6 +72,14 @@ ...@@ -72,6 +72,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="danger"
plain
@click="handleDeleteRows()"
v-hasPermi="['visit:product:delete']"
>
<Icon icon="ep:delete" class="mr-5px" /> 删除
</el-button>
<el-button <el-button
type="warning" type="warning"
plain plain
...@@ -233,6 +241,22 @@ const handleDelete = async (id: number) => { ...@@ -233,6 +241,22 @@ const handleDelete = async (id: number) => {
} catch {} } catch {}
} }
/** 批量删除按钮操作 */
const handleDeleteRows = async () => {
try {
if (multipleSelection.value.length === 0) {
return message.warning('请先选择需要删除的记录')
}
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProductApi.deleteProductByIds(multipleSelection.value.join(','))
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {
......
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
justify-content: center; /* 水平居中 */ justify-content: center; /* 水平居中 */
height: 40px; /* 与其他 div 高度一致 */ height: 40px; /* 与其他 div 高度一致 */
width: 100%; /* 需要设定宽度 */" width: 100%; /* 需要设定宽度 */"
>{{ formatDate(time) }}</li> >{{ time[0]+"" + time[1]+""+time[2]+"" }}</li>
</ul> </ul>
<div <div
style="display: flex; style="display: flex;
...@@ -170,7 +170,7 @@ const printRef = ref(null) ...@@ -170,7 +170,7 @@ const printRef = ref(null)
const formatDate = (date: string | Date) => { const formatDate = (date: string | Date) => {
const d = new Date(date) const d = new Date(date)
return d.toLocaleString() return d.toLocaleDateString()
} }
const getSafeImages = (val?: string) => { const getSafeImages = (val?: string) => {
if (!val||val==='') return [] if (!val||val==='') return []
......
<template>
<div ref="printArea" style="padding: 20px; font-size: 14px;" v-show="visible">
<div v-for="(item, index) in data" :key="index" class="print-page">
<h2 style="text-align: center; margin-bottom: 16px;">渠道服务记录</h2>
<div style="display: flex;">
<!-- 左侧 1/3 -->
<div style="width: 33%;">
<img crossorigin="anonymous" :src="item.locationImage" style="width: 100%; height: 120px;" alt=""/>
<p>业务员:{{ item.salesman }}</p>
<p>服务数量:{{ item.serviceCount }}</p>
<h4>拜访时间记录</h4>
<ul>
<li v-for="(time, i) in item.visitDate" :key="i">{{ formatDate(time) }}</li>
</ul>
<h4>主要服务内容</h4>
<ul>
<li v-for="(content, i) in item.serviceContent" :key="i">内容{{ i + 1 }}{{ content }}</li>
</ul>
</div>
<!-- 右侧 2/3 -->
<div style="width: 67%; padding-left: 20px;">
<p>客户名称:{{ item.companyName }}</p>
<p>客户部门:{{ getDictLabel(DICT_TYPE.CUSTOMER_DEPT, item.department) }}</p>
<p>推广产品:{{ item.visitProductNames }}</p>
<div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;">拜访签到实景图</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<img
crossorigin="anonymous"
v-for="(url, i) in getSafeImages(item.serviceImages)"
:key="i"
:src="url"
style="width: calc(50% - 5px); height: 100px; object-fit: cover; border: 1px solid #ccc;"
/>
</div>
</div>
</div>
<div style="page-break-after: always;"></div>
</div>
</div>
</template>
<script setup lang="ts">
import {nextTick, ref} from 'vue'
import { getDictLabel, DICT_TYPE } from '@/utils/dict'
const props = defineProps<{ data: any[] }>()
const printArea = ref<HTMLElement | null>(null)
const visible = ref(false)
const formatDate = (date: string | Date) => {
const d = new Date(date)
return d.toLocaleString()
}
const getSafeImages = (val?: string) => {
if (!val) return []
return val.split(',').slice(0, 4)
}
const print = async () => {
const printWindow = window.open('', '_blank')
if (!printWindow) return
let html = ''
for (const item of props.data) {
html += `
<div class="print-page">
<h2>渠道服务记录</h2>
<div style="display: flex;">
<div style="width: 33%;">
<img src="${item.locationImage}" crossorigin="anonymous" style="width: 100%; height: 120px;" />
<p>业务员:${item.salesman || ''}</p>
<p>服务数量:${item.serviceCount || ''}</p>
<h4>拜访时间记录</h4>
<ul>
${(item.visitDate || []).map((t: string) => `<li>${formatDate(t)}</li>`).join('')}
</ul>
<h4>主要服务内容</h4>
<ul>
${(item.serviceContent || []).map((c: string, i: number) => `<li>内容${i + 1}${c}</li>`).join('')}
</ul>
</div>
<div style="width: 67%; padding-left: 20px;">
<p>客户名称:${item.companyName || ''}</p>
<p>客户部门:${getDictLabel(DICT_TYPE.CUSTOMER_DEPT, item.department) || ''}</p>
<p>推广产品:${item.visitProductNames || ''}</p>
<div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;">拜访签到实景图</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
${(item.serviceImages || '')
.split(',')
.slice(0, 4)
.map(url => `<img src="${url}" crossorigin="anonymous" style="width: calc(50% - 5px); height: 100px; object-fit: cover; border: 1px solid #ccc;" />`)
.join('')}
</div>
</div>
</div>
<div style="page-break-after: always;"></div>
</div>
`
}
printWindow.document.write(`
<html>
<head>
<title>打印</title>
<meta charset="UTF-8">
<style>
body, html {
margin: 0;
padding: 0;
width: 210mm;
height: auto;
font-size: 14px;
-webkit-print-color-adjust: exact;
}
.print-page {
width: 210mm;
height: 297mm;
padding: 20mm;
box-sizing: border-box;
page-break-after: always;
overflow: hidden;
}
h2 {
text-align: center;
margin-bottom: 16px;
}
img {
max-width: 100%;
object-fit: cover;
}
</style>
</head>
<body>${html}</body>
</html>
`)
printWindow.document.close()
printWindow.focus()
printWindow.print()
printWindow.close()
}
defineExpose({ print })
</script>
<style scoped>
/* 普通预览样式 */
#printWrapper {
width: 210mm;
margin: 0 auto;
font-size: 14px;
}
.print-page {
width: 210mm;
height: 297mm;
padding: 20mm;
box-sizing: border-box;
page-break-after: always;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
border: 1px dashed transparent;
}
.print-page h2 {
text-align: center;
margin-bottom: 16px;
}
.print-page img {
max-width: 100%;
object-fit: cover;
}
.print-page .images-wrapper {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.print-page .images-wrapper img {
width: calc(50% - 5px);
height: 100px;
border: 1px solid #ccc;
}
/* 打印样式 */
@media print {
body, html {
margin: 0;
padding: 0;
width: 210mm;
height: auto;
-webkit-print-color-adjust: exact;
}
#printWrapper {
width: 210mm;
margin: 0 auto;
}
.print-page {
width: 210mm;
height: 297mm;
page-break-after: always;
overflow: hidden;
box-sizing: border-box;
padding: 20mm;
}
.no-print {
display: none !important;
}
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment