Compare commits

..

2 Commits

Author SHA1 Message Date
yf c5a8f8f032 Merge branch 'dev' of https://git.yfgame.vip/r/hangtag into dev
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2025-03-05 00:23:58 +08:00
yf b153c22261 新增 客户组别,新增稿件附件,及下单附件上传 2025-03-05 00:21:13 +08:00
68 changed files with 3383 additions and 138 deletions

View File

@ -25,6 +25,7 @@ public interface GlobalErrorCodeConstants {
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求不允许
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
ErrorCode NOT_SUPPORT_FILE_TYPE = new ErrorCode(430, "不支持上传该文件类型");
// ========== 服务端错误段 ==========

View File

@ -0,0 +1,42 @@
package cn.hangtag.framework.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "文件信息")
@Data
public class FileInfoVO {
/**
* infra_file id
*/
@Schema(description = "文件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
/**
* infra_file_config id
*/
@Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
private Long configId;
@Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "hangtag.jpg")
private String path;
@Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "hangtag.jpg")
private String name;
@Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/hangtag.jpg")
private String url;
@Schema(description = "文件MIME类型", example = "application/octet-stream")
private String type;
@Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer size;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -10,6 +10,7 @@ import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.HashSet;
/**
* 文件工具类
@ -18,6 +19,49 @@ import java.io.File;
*/
public class FileUtils {
public static final HashSet<String> SUFFIXS = new HashSet<String>() {{
// 常用的图片文件类型
add("jpg");
add("jpeg");
add("png");
add("gif");
add("bmp");
add("tiff");
add("webp");
add("ico");
add("svg");
// 常用的文档文件类型
add("pdf");
add("doc");
add("docx");
add("xls");
add("xlsx");
add("ppt");
add("pptx");
add("txt");
add("rtf");
// 常用的视频文件类型
add("mp4");
add("avi");
add("mov");
add("wmv");
add("flv");
add("mkv");
// 常用的音频文件类型
add("mp3");
add("wav");
add("ogg");
add("aac");
// 压缩文件类型
add("zip");
add("rar");
add("7z");
}};
/**
* 创建临时文件
* 该文件会在 JVM 退出时进行删除

View File

@ -1,5 +1,9 @@
package cn.hangtag.module.infra.api.file;
import cn.hangtag.framework.common.pojo.FileInfoVO;
import java.util.List;
/**
* 文件 API 接口
*
@ -38,4 +42,31 @@ public interface FileApi {
*/
String createFile(String name, String path, byte[] content);
/**
* 上传文件
*
* @param originalFilename 原始文件名
* @param path 路径
* @param bytes 字节
* @return {@link FileInfoVO }
*/
FileInfoVO uploadFile(String originalFilename, String path, byte[] bytes);
/**
* 获取文件网址
*
* @param ids IDs
* @return {@link List }<{@link String }>
*/
List<FileInfoVO> getFileInfo(String ids);
/**
* 重命名
*
* @param id ID
* @param name 名称
* @return {@link FileInfoVO }
*/
FileInfoVO rename(String id, String name);
}

View File

@ -1,10 +1,14 @@
package cn.hangtag.module.infra.api.file;
import cn.hangtag.framework.common.pojo.FileInfoVO;
import cn.hangtag.module.infra.controller.admin.file.vo.file.FileRespVO;
import cn.hangtag.module.infra.service.file.FileService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
/**
* 文件 API 实现类
@ -23,4 +27,18 @@ public class FileApiImpl implements FileApi {
return fileService.createFile(name, path, content);
}
@Override
public FileInfoVO uploadFile(String originalFilename, String path, byte[] bytes) {
return fileService.uploadFile(originalFilename, path, bytes);
}
@Override
public List<FileInfoVO> getFileInfo(String ids) {
return fileService.getFileInfo(ids);
}
@Override
public FileInfoVO rename(String id, String name) {
return fileService.rename(id, name);
}
}

View File

@ -1,5 +1,6 @@
package cn.hangtag.module.infra.controller.admin.file;
import cn.hangtag.framework.common.pojo.FileInfoVO;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
@ -25,6 +26,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
import static cn.hangtag.framework.common.pojo.CommonResult.success;
import static cn.hangtag.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;
@ -45,6 +48,26 @@ public class FileController {
String path = uploadReqVO.getPath();
return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
}
@PostMapping("/upload-plus")
@Operation(summary = "上传文件", description = "后端上传文件并返回文件信息")
public CommonResult<FileInfoVO> uploadPlus(FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile();
String path = uploadReqVO.getPath();
FileInfoVO res = fileService.uploadFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()));
return success(res);
}
@GetMapping("/info/{ids}")
@Operation(summary = "上传文件", description = "根据id查询文件信息")
public CommonResult<List<FileInfoVO>> getUploadUrl(@PathVariable("ids") String ids) {
List<FileInfoVO> res = fileService.getFileInfo(ids);
return success(res);
}
@PostMapping("/rename/{id}")
@Operation(summary = "上传文件", description = "修改文件名称")
public CommonResult<FileInfoVO> getUploadUrl(@PathVariable("id") String ids,@Validated @RequestBody FileRenameReqVO renameReqVO) {
FileInfoVO res = fileService.rename(ids,renameReqVO.getName());
return success(res);
}
@GetMapping("/presigned-url")
@Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")

View File

@ -0,0 +1,16 @@
package cn.hangtag.module.infra.controller.admin.file.vo.file;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 文件修改名称 Request VO")
@Data
public class FileRenameReqVO {
@NotNull(message = "文件名不能为空")
@Schema(description = "文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "abc")
private String name;
}

View File

@ -1,11 +1,15 @@
package cn.hangtag.module.infra.service.file;
import cn.hangtag.framework.common.pojo.FileInfoVO;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
import cn.hangtag.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.hangtag.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
import cn.hangtag.module.infra.controller.admin.file.vo.file.FileRespVO;
import cn.hangtag.module.infra.dal.dataobject.file.FileDO;
import java.util.List;
/**
* 文件 Service 接口
*
@ -69,4 +73,32 @@ public interface FileService {
* @return {@link String}
*/
String getDomain();
/**
* 上传文件
*
* @param originalFilename 原始文件名
* @param path 路径
* @param bytes 字节
* @return {@link FileRespVO }
*/
FileInfoVO uploadFile(String originalFilename, String path, byte[] bytes);
/**
* 获取文件网址
*
* @param ids ID infra_file id 多个使用 , 分割
* @return {@link List }<{@link String }>
*/
List<FileInfoVO> getFileInfo(String ids);
/**
* 重命名
*
* @param id ID infra_file id
* @param name 名称 文件名称 不包含后缀名称
* @return {@link FileInfoVO }
*/
FileInfoVO rename(String id, String name);
}

View File

@ -1,7 +1,14 @@
package cn.hangtag.module.infra.service.file;
import cn.hangtag.framework.common.exception.ErrorCode;
import cn.hangtag.framework.common.exception.ServiceException;
import cn.hangtag.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.hangtag.framework.common.pojo.FileInfoVO;
import cn.hangtag.framework.common.util.FuncUtil;
import cn.hangtag.framework.mybatis.core.dataobject.BaseDO;
import cn.hangtag.module.infra.controller.admin.file.vo.file.FileRespVO;
import cn.hangtag.module.infra.service.config.YmlUtils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hangtag.framework.common.pojo.PageResult;
@ -15,6 +22,7 @@ import cn.hangtag.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.hangtag.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
import cn.hangtag.module.infra.dal.dataobject.file.FileDO;
import cn.hangtag.module.infra.dal.mysql.file.FileMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.stereotype.Service;
@ -24,8 +32,12 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static cn.hangtag.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.hangtag.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
@ -145,6 +157,97 @@ public class FileServiceImpl implements FileService {
}
return domain;
}
@SneakyThrows
@Override
public FileInfoVO uploadFile(String name, String path, byte[] content) {
// 计算默认的 path
String type = FileTypeUtils.getMineType(content, name);
if (StrUtil.isEmpty(path)) {
path = FileUtils.generatePath(content, name);
}
// 如果 name 为空则使用 path 填充
if (StrUtil.isEmpty(name)) {
name = path;
}
String suffix1 = FileUtil.getSuffix(name);
if(!FileUtils.SUFFIXS.contains(suffix1)){
throw new ServiceException(GlobalErrorCodeConstants.NOT_SUPPORT_FILE_TYPE);
}
// 上传到文件存储器
FileClient client = fileConfigService.getMasterFileClient();
Assert.notNull(client, "客户端(master) 不能为空");
String url = client.upload(content, path, type);
// 保存到数据库
FileDO file = new FileDO();
file.setConfigId(client.getId());
file.setName(name);
file.setPath(path);
file.setUrl(url);
file.setType(type);
file.setSize(content.length);
file.setCreateTime(LocalDateTime.now());
fileMapper.insert(file);
return new FileInfoVO()
.setId(file.getId())
.setConfigId(client.getId())
.setName(name)
.setPath(path)
.setUrl(url)
.setType(type)
.setSize(content.length);
}
@Override
public List<FileInfoVO> getFileInfo(String ids) {
List<FileInfoVO> list = new ArrayList<>();
if(FuncUtil.isNotEmpty(ids)){
LambdaQueryWrapper<FileDO> wrapper = new LambdaQueryWrapper<>();
wrapper.in(FileDO::getId, FuncUtil.toStrList(ids));
wrapper.eq(BaseDO::getDeleted,false);
wrapper.orderByDesc(BaseDO::getCreateTime);
List<FileDO> fileDOS = fileMapper.selectList(wrapper);
for (FileDO fileDO : fileDOS) {
list.add(
new FileInfoVO()
.setId(fileDO.getId())
.setConfigId(fileDO.getConfigId())
.setName(fileDO.getName())
.setPath(fileDO.getName())
.setUrl(fileDO.getUrl())
.setType(fileDO.getType())
.setSize(fileDO.getSize())
);
}
}
return list;
}
@Override
public FileInfoVO rename(String id, String name) {
FileDO fileDO = fileMapper.selectById(id);
if(FuncUtil.isNotEmpty(fileDO.getName())){
FileDO newInfo = new FileDO();
String name1 = fileDO.getName();
// 获取后缀
String suffix1 = FileUtil.getSuffix(name1);
fileDO.setName(name+"."+suffix1);
newInfo.setName(fileDO.getName());
newInfo.setId(fileDO.getId());
fileMapper.updateById(newInfo);
return new FileInfoVO()
.setId(fileDO.getId())
.setConfigId(fileDO.getConfigId())
.setName(newInfo.getName())
.setPath(fileDO.getPath())
.setUrl(fileDO.getUrl())
.setType(fileDO.getType())
.setSize(fileDO.getSize());
}
return null;
}
public static String domain(HttpServletRequest request) {
String domain = YmlUtils.get("localupload.domain");
if(FuncUtil.isNotEmpty(domain)){

View File

@ -27,5 +27,5 @@ public interface ErrorCodeConstants extends cn.hangtag.module.system.enums.Erro
ErrorCode PRODUCT_PRICE_NOT_EXISTS = new ErrorCode(600, "产品单价记录不存在");
ErrorCode CUSTOMER_EMAIL_EXISTS = new ErrorCode(600, "已存在重复的客户邮箱号");
ErrorCode SALE_ORDER_NOT_FILE_EXPORT = new ErrorCode(7000, "订单中没有可导出的稿件");
ErrorCode CUSTOMER_GROUP_NOT_EXISTS = new ErrorCode(7100, "客户组别 不存在");
}

View File

@ -102,10 +102,6 @@
<artifactId>x-easypdf</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>cn.hangtag</groupId>
<artifactId>hangtag-spring-boot-starter-protection</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -31,8 +31,8 @@ public class AppMaterialController {
@GetMapping("/page")
@Operation(summary = "获得产品资料 分页")
public CommonResult<PageResult<ProductInfoRespVO>> getProductInfoPage(@Valid ProductInfoPageReqVO pageReqVO) {
PageResult<ProductInfoDO> pageResult = productInfoService.getProductInfoPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ProductInfoRespVO.class));
PageResult<ProductInfoRespVO> pageResult = productInfoService.getProductInfoPage(pageReqVO);
return success(pageResult);
}
}

View File

@ -0,0 +1,95 @@
package cn.hangtag.module.oms.controller.admin.customergroup;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.hangtag.framework.common.pojo.PageParam;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.pojo.CommonResult;
import cn.hangtag.framework.common.util.object.BeanUtils;
import static cn.hangtag.framework.common.pojo.CommonResult.success;
import cn.hangtag.framework.excel.core.util.ExcelUtils;
import cn.hangtag.framework.apilog.core.annotation.ApiAccessLog;
import static cn.hangtag.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.hangtag.module.oms.controller.admin.customergroup.vo.*;
import cn.hangtag.module.oms.dal.dataobject.customergroup.CustomerGroupDO;
import cn.hangtag.module.oms.service.customergroup.CustomerGroupService;
@Tag(name = "管理后台 - 客户组别 ")
@RestController
@RequestMapping("/oms/customer-group")
@Validated
public class CustomerGroupController {
@Resource
private CustomerGroupService customerGroupService;
@PostMapping("/create")
@Operation(summary = "创建客户组别 ")
@PreAuthorize("@ss.hasPermission('oms:customer-group:create')")
public CommonResult<Long> createCustomerGroup(@Valid @RequestBody CustomerGroupSaveReqVO createReqVO) {
return success(customerGroupService.createCustomerGroup(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新客户组别 ")
@PreAuthorize("@ss.hasPermission('oms:customer-group:update')")
public CommonResult<Boolean> updateCustomerGroup(@Valid @RequestBody CustomerGroupSaveReqVO updateReqVO) {
customerGroupService.updateCustomerGroup(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除客户组别 ")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('oms:customer-group:delete')")
public CommonResult<Boolean> deleteCustomerGroup(@RequestParam("id") Long id) {
customerGroupService.deleteCustomerGroup(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得客户组别 ")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
// @PreAuthorize("@ss.hasPermission('oms:customer-group:query')")
public CommonResult<CustomerGroupRespVO> getCustomerGroup(@RequestParam("id") Long id) {
CustomerGroupDO customerGroup = customerGroupService.getCustomerGroup(id);
return success(BeanUtils.toBean(customerGroup, CustomerGroupRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得客户组别 分页")
// @PreAuthorize("@ss.hasPermission('oms:customer-group:query')")
public CommonResult<PageResult<CustomerGroupRespVO>> getCustomerGroupPage(@Valid CustomerGroupPageReqVO pageReqVO) {
PageResult<CustomerGroupDO> pageResult = customerGroupService.getCustomerGroupPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, CustomerGroupRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出客户组别 Excel")
@PreAuthorize("@ss.hasPermission('oms:customer-group:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportCustomerGroupExcel(@Valid CustomerGroupPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<CustomerGroupDO> list = customerGroupService.getCustomerGroupPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "客户组别 .xls", "数据", CustomerGroupRespVO.class,
BeanUtils.toBean(list, CustomerGroupRespVO.class));
}
}

View File

@ -0,0 +1,34 @@
package cn.hangtag.module.oms.controller.admin.customergroup.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.hangtag.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.hangtag.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 客户组别 分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CustomerGroupPageReqVO extends PageParam {
@Schema(description = "编码")
private String code;
@Schema(description = "名称", example = "张三")
private String name;
@Schema(description = "排序号")
private Integer sort;
@Schema(description = "备注", example = "随便")
private String remark;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,39 @@
package cn.hangtag.module.oms.controller.admin.customergroup.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - 客户组别 Response VO")
@Data
@ExcelIgnoreUnannotated
public class CustomerGroupRespVO {
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "5719")
@ExcelProperty("id")
private Long id;
@Schema(description = "编码")
@ExcelProperty("编码")
private String code;
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
@ExcelProperty("名称")
private String name;
@Schema(description = "排序号")
@ExcelProperty("排序号")
private Integer sort;
@Schema(description = "备注", example = "随便")
@ExcelProperty("备注")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,28 @@
package cn.hangtag.module.oms.controller.admin.customergroup.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 客户组别 新增/修改 Request VO")
@Data
public class CustomerGroupSaveReqVO {
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "5719")
private Long id;
@Schema(description = "编码")
private String code;
@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
@NotEmpty(message = "名称不能为空")
private String name;
@Schema(description = "排序号")
private Integer sort;
@Schema(description = "备注", example = "随便")
private String remark;
}

View File

@ -26,6 +26,9 @@ public class DraftDesignDataRespVO {
@ExcelProperty("封面")
private String cover;
@Schema(description = "底稿附件多个 infra_file 表id")
private String fileIds;
@Schema(description = "设计稿名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@ExcelProperty("设计稿名称")
private String name;

View File

@ -19,6 +19,9 @@ public class DraftDesignDataSaveReqVO {
@Schema(description = "封面")
private String cover;
@Schema(description = "底稿附件多个 infra_file 表id")
private String fileIds;
@Schema(description = "设计稿名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotEmpty(message = "设计稿名称不能为空")
private String name;

View File

@ -70,16 +70,16 @@ public class ProductInfoController {
@Parameter(name = "id", description = "编号", required = true, example = "1024")
// @PreAuthorize("@ss.hasPermission('oms:product-info:query')")
public CommonResult<ProductInfoRespVO> getProductInfo(@RequestParam("id") Long id) {
ProductInfoDO productInfo = productInfoService.getProductInfo(id);
return success(BeanUtils.toBean(productInfo, ProductInfoRespVO.class));
ProductInfoRespVO vo = productInfoService.getFrontProductInfo(id);
return success(vo);
}
@GetMapping("/page")
@Operation(summary = "获得产品资料 分页")
// @PreAuthorize("@ss.hasPermission('oms:product-info:query')")
public CommonResult<PageResult<ProductInfoRespVO>> getProductInfoPage(@Valid ProductInfoPageReqVO pageReqVO) {
PageResult<ProductInfoDO> pageResult = productInfoService.getProductInfoPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ProductInfoRespVO.class));
PageResult<ProductInfoRespVO> pageResult = productInfoService.getProductInfoPage(pageReqVO);
return success(pageResult);
}
@ -87,8 +87,8 @@ public class ProductInfoController {
@Operation(summary = "获得产品资料 分页")
// @PreAuthorize("@ss.hasPermission('oms:product-info:query')")
public CommonResult<PageResult<ProductInfoRespVO>> queryPage(@RequestBody QueryFilterInfo<ProductInfoPageReqVO> queryFilterInfo) {
PageResult<ProductInfoDO> pageResult = productInfoService.queryPage(queryFilterInfo);
return success(BeanUtils.toBean(pageResult, ProductInfoRespVO.class));
PageResult<ProductInfoRespVO> pageResult = productInfoService.queryPage(queryFilterInfo);
return success(pageResult);
}
@GetMapping("/excel-template")
@ -114,10 +114,9 @@ public class ProductInfoController {
public void exportProductInfoExcel(@Valid ProductInfoPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ProductInfoDO> list = productInfoService.getProductInfoPage(pageReqVO).getList();
List<ProductInfoRespVO> list = productInfoService.getProductInfoPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "产品资料 .xls", "数据", ProductInfoRespVO.class,
BeanUtils.toBean(list, ProductInfoRespVO.class));
ExcelUtils.write(response, "产品资料 .xls", "数据", ProductInfoRespVO.class,list);
}
}

View File

@ -30,6 +30,13 @@ public class ProductInfoPageReqVO extends PageParam {
@Schema(description = "品牌", example = "12523")
private Long brandId;
@Schema(description = "客户组别id oms_customer_group", example = "12523,1233")
private String customerGroupId;
@Schema(description = "客户组别 oms_customer_group name", example = "vip")
private String customerGroupName;
@Schema(description = "产品类型id", example = "26002")
private Long productTypeId;

View File

@ -35,6 +35,13 @@ public class ProductInfoRespVO {
@ExcelProperty("品牌")
private Long brandId;
@Schema(description = "客户组别id oms_customer_group", example = "12523,1233")
private String customerGroupId;
@Schema(description = "客户组别 oms_customer_group name", example = "vip")
private String customerGroupName;
@Schema(description = "产品类型id", example = "26002")
@ExcelProperty("产品类型id")
private Long productTypeId;
@ -85,6 +92,11 @@ public class ProductInfoRespVO {
*/
private String currency;
/**
* 底稿文件id
*/
private String fileIds;
/**
* json数组数据 [{p:100.25,c:"RMB",d:true}]
* 1.p:价格 2.c:币种 3.d:是否默认

View File

@ -30,6 +30,9 @@ public class ProductInfoSaveReqVO {
@Schema(description = "品牌", example = "12523")
private Long brandId;
@Schema(description = "客户组别id oms_customer_group", example = "12523,1233")
private String customerGroupId;
@Schema(description = "产品类型id", example = "26002")
private Long productTypeId;

View File

@ -120,6 +120,12 @@ public class CreateSaleOrderDTO implements Serializable {
*/
private String rejectReason;
/**
* 下单客户上传的 附件文件 infra_file id
* 允许多个
*/
private String accessoryFileIds;
private List<SaleOrderEntryItemDTO> saleOrderEntry;
}

View File

@ -0,0 +1,47 @@
package cn.hangtag.module.oms.dal.dataobject.customergroup;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.hangtag.framework.mybatis.core.dataobject.BaseDO;
/**
* 客户组别 DO
*
* @author 管理员
*/
@TableName("oms_customer_group")
@KeySequence("oms_customer_group_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CustomerGroupDO extends BaseDO {
/**
* id
*/
@TableId
private Long id;
/**
* 编码
*/
private String code;
/**
* 名称
*/
private String name;
/**
* 排序号
*/
private Integer sort;
/**
* 备注
*/
private String remark;
}

View File

@ -36,6 +36,14 @@ public class DraftDesignDataDO extends BaseDO {
* 封面
*/
private String cover;
/**
* 底稿附件多个 infra_file 表id
* 多个用,连接
*/
@TableField("file_ids")
private String fileIds;
/**
* 设计稿名称
*/

View File

@ -51,11 +51,13 @@ public class ProductInfoDO extends BaseDO {
*/
private Long productTypeId;
/**
* 设计稿id
* 设计稿id 12,22
* oms_draft_design_data id 多个用,连接
*/
private String draftDesignDataId;
/**
* 设计稿列表 json数据带名称
* [{"remark":"","label":"默认jq4","id":16}]
*/
private String draftDesignList;
@ -124,6 +126,12 @@ public class ProductInfoDO extends BaseDO {
@TableField("template_type")
private String templateType;
@Schema(description = "客户组别id oms_customer_group", example = "12523,1233")
@TableField("customer_group_id")
private String customerGroupId;
@TableField(exist = false)
private String customerGroupName;
/**
* 品牌名称
@ -137,4 +145,6 @@ public class ProductInfoDO extends BaseDO {
@TableField(exist = false)
private String productTypeName;
}

View File

@ -178,6 +178,13 @@ public class SaleOrderDO extends BaseDO {
*/
private String currencyType;
/**
* 下单客户上传的 附件文件 infra_file id
* 允许多个
*/
@TableField("accessory_file_ids")
private String accessoryFileIds;
public SaleOrderDO(CreateSaleOrderDTO dto) {
BeanUtil.copyProperties(dto, this,"bizdate","plansenddate");
}

View File

@ -0,0 +1,30 @@
package cn.hangtag.module.oms.dal.mysql.customergroup;
import java.util.*;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.hangtag.framework.mybatis.core.mapper.BaseMapperX;
import cn.hangtag.module.oms.dal.dataobject.customergroup.CustomerGroupDO;
import org.apache.ibatis.annotations.Mapper;
import cn.hangtag.module.oms.controller.admin.customergroup.vo.*;
/**
* 客户组别 Mapper
*
* @author 管理员
*/
@Mapper
public interface CustomerGroupMapper extends BaseMapperX<CustomerGroupDO> {
default PageResult<CustomerGroupDO> selectPage(CustomerGroupPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CustomerGroupDO>()
.likeIfPresent(CustomerGroupDO::getCode, reqVO.getCode())
.likeIfPresent(CustomerGroupDO::getName, reqVO.getName())
.eqIfPresent(CustomerGroupDO::getSort, reqVO.getSort())
.eqIfPresent(CustomerGroupDO::getRemark, reqVO.getRemark())
.betweenIfPresent(CustomerGroupDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(CustomerGroupDO::getId));
}
}

View File

@ -34,6 +34,7 @@ public interface ProductInfoMapper extends BaseMapperX<ProductInfoDO> {
.eqIfPresent(ProductInfoDO::getDraftDesignList, reqVO.getDraftDesignList())
.eqIfPresent(ProductInfoDO::getEnabled, reqVO.getEnabled())
.eqIfPresent(ProductInfoDO::getRemark, reqVO.getRemark())
.eqIfPresent(ProductInfoDO::getCustomerGroupId, reqVO.getCustomerGroupId())
.eqIfPresent(ProductInfoDO::getDetails, reqVO.getDetails())
.betweenIfPresent(ProductInfoDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(ProductInfoDO::getId));

View File

@ -0,0 +1,55 @@
package cn.hangtag.module.oms.service.customergroup;
import java.util.*;
import javax.validation.*;
import cn.hangtag.module.oms.controller.admin.customergroup.vo.*;
import cn.hangtag.module.oms.dal.dataobject.customergroup.CustomerGroupDO;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.pojo.PageParam;
/**
* 客户组别 Service 接口
*
* @author 管理员
*/
public interface CustomerGroupService {
/**
* 创建客户组别
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createCustomerGroup(@Valid CustomerGroupSaveReqVO createReqVO);
/**
* 更新客户组别
*
* @param updateReqVO 更新信息
*/
void updateCustomerGroup(@Valid CustomerGroupSaveReqVO updateReqVO);
/**
* 删除客户组别
*
* @param id 编号
*/
void deleteCustomerGroup(Long id);
/**
* 获得客户组别
*
* @param id 编号
* @return 客户组别
*/
CustomerGroupDO getCustomerGroup(Long id);
/**
* 获得客户组别 分页
*
* @param pageReqVO 分页查询
* @return 客户组别 分页
*/
PageResult<CustomerGroupDO> getCustomerGroupPage(CustomerGroupPageReqVO pageReqVO);
}

View File

@ -0,0 +1,74 @@
package cn.hangtag.module.oms.service.customergroup;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.hangtag.module.oms.controller.admin.customergroup.vo.*;
import cn.hangtag.module.oms.dal.dataobject.customergroup.CustomerGroupDO;
import cn.hangtag.framework.common.pojo.PageResult;
import cn.hangtag.framework.common.pojo.PageParam;
import cn.hangtag.framework.common.util.object.BeanUtils;
import cn.hangtag.module.oms.dal.mysql.customergroup.CustomerGroupMapper;
import static cn.hangtag.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.hangtag.module.oms.enums.ErrorCodeConstants.*;
/**
* 客户组别 Service 实现类
*
* @author 管理员
*/
@Service
@Validated
public class CustomerGroupServiceImpl implements CustomerGroupService {
@Resource
private CustomerGroupMapper customerGroupMapper;
@Override
public Long createCustomerGroup(CustomerGroupSaveReqVO createReqVO) {
// 插入
CustomerGroupDO customerGroup = BeanUtils.toBean(createReqVO, CustomerGroupDO.class);
customerGroupMapper.insert(customerGroup);
// 返回
return customerGroup.getId();
}
@Override
public void updateCustomerGroup(CustomerGroupSaveReqVO updateReqVO) {
// 校验存在
validateCustomerGroupExists(updateReqVO.getId());
// 更新
CustomerGroupDO updateObj = BeanUtils.toBean(updateReqVO, CustomerGroupDO.class);
customerGroupMapper.updateById(updateObj);
}
@Override
public void deleteCustomerGroup(Long id) {
// 校验存在
validateCustomerGroupExists(id);
// 删除
customerGroupMapper.deleteById(id);
}
private void validateCustomerGroupExists(Long id) {
if (customerGroupMapper.selectById(id) == null) {
throw exception(CUSTOMER_GROUP_NOT_EXISTS);
}
}
@Override
public CustomerGroupDO getCustomerGroup(Long id) {
return customerGroupMapper.selectById(id);
}
@Override
public PageResult<CustomerGroupDO> getCustomerGroupPage(CustomerGroupPageReqVO pageReqVO) {
return customerGroupMapper.selectPage(pageReqVO);
}
}

View File

@ -48,15 +48,23 @@ public interface ProductInfoService {
*/
ProductInfoDO getProductInfo(Long id);
/**
* 获取前台产品信息
*
* @param id ID
* @return {@link ProductInfoRespVO }
*/
ProductInfoRespVO getFrontProductInfo(Long id);
/**
* 获得产品资料 分页
*
* @param pageReqVO 分页查询
* @return 产品资料 分页
*/
PageResult<ProductInfoDO> getProductInfoPage(ProductInfoPageReqVO pageReqVO);
PageResult<ProductInfoRespVO> getProductInfoPage(ProductInfoPageReqVO pageReqVO);
PageResult<ProductInfoDO> queryPage(QueryFilterInfo<ProductInfoPageReqVO> queryFilterInfo);
PageResult<ProductInfoRespVO> queryPage(QueryFilterInfo<ProductInfoPageReqVO> queryFilterInfo);
/**
* 获取编码
@ -75,4 +83,5 @@ public interface ProductInfoService {
BigDecimal queryPriceByProductId(Long productId, String currency);
String importExcel(List<ProductInfoExcelVO> list);
}

View File

@ -13,9 +13,12 @@ import cn.hangtag.module.oms.controller.admin.productinfo.dto.PriceListItemDTO;
import cn.hangtag.module.oms.controller.admin.productinfo.dto.ProductInfoPageDTO;
import cn.hangtag.module.oms.controller.admin.productprocess.vo.ProductProcessSaveReqVO;
import cn.hangtag.module.oms.dal.dataobject.brand.BrandDO;
import cn.hangtag.module.oms.dal.dataobject.customergroup.CustomerGroupDO;
import cn.hangtag.module.oms.dal.dataobject.draftdesigndata.DraftDesignDataDO;
import cn.hangtag.module.oms.dal.dataobject.productprocess.ProductProcessDO;
import cn.hangtag.module.oms.dal.mysql.brand.BrandMapper;
import cn.hangtag.module.oms.dal.mysql.customergroup.CustomerGroupMapper;
import cn.hangtag.module.oms.dal.mysql.draftdesigndata.DraftDesignDataMapper;
import cn.hangtag.module.oms.dal.mysql.productprocess.ProductProcessMapper;
import cn.hangtag.module.oms.serialnumber.CodingRulesUtils;
import cn.hangtag.module.oms.service.brand.BrandService;
@ -59,10 +62,11 @@ public class ProductInfoServiceImpl implements ProductInfoService {
private final ProductInfoMapper productInfoMapper;
private final BrandMapper brandMapper;
private final ProductTypeMapper productTypeMapper;
private final CustomerGroupMapper customerGroupMapper;
private final ProductProcessMapper productProcessMapper;
private final BrandService brandService;
private final DraftDesignDataService draftDesignDataService;
private final DraftDesignDataMapper draftDesignDataMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createProductInfo(ProductInfoSaveReqVO createReqVO) {
@ -169,50 +173,29 @@ public class ProductInfoServiceImpl implements ProductInfoService {
public ProductInfoDO getProductInfo(Long id) {
return productInfoMapper.selectById(id);
}
@Override
public PageResult<ProductInfoDO> getProductInfoPage(ProductInfoPageReqVO pageReqVO) {
PageResult<ProductInfoDO> productInfoDOPageResult = productInfoMapper.selectPage(pageReqVO);
List<ProductInfoDO> list = productInfoDOPageResult.getList();
list.forEach(productInfoDO -> {
if(FuncUtil.isNotEmpty(productInfoDO.getBrandId())){
BrandDO brandDO = brandMapper.selectById(productInfoDO.getBrandId());
if(FuncUtil.isNotEmpty(brandDO)){
productInfoDO.setBrandName(brandDO.getName());
}
}
if(FuncUtil.isNotEmpty(productInfoDO.getProductTypeId())){
ProductTypeDO productTypeDO = productTypeMapper.selectById(productInfoDO.getProductTypeId());
if(FuncUtil.isNotEmpty(productTypeDO)){
productInfoDO.setProductTypeName(productTypeDO.getLabel());
}
}
});
productInfoDOPageResult.setList(list);
return productInfoDOPageResult;
public ProductInfoRespVO getFrontProductInfo(Long id) {
ProductInfoDO infoDO = productInfoMapper.selectById(id);
ProductInfoRespVO vo = BeanUtils.toBean(infoDO, ProductInfoRespVO.class);
return wrapperFileIds(vo);
}
@Override
public PageResult<ProductInfoDO> queryPage(QueryFilterInfo<ProductInfoPageReqVO> queryFilterInfo) {
public PageResult<ProductInfoRespVO> getProductInfoPage(ProductInfoPageReqVO pageReqVO) {
PageResult<ProductInfoDO> productInfoDOPageResult = productInfoMapper.selectPage(pageReqVO);
List<ProductInfoDO> list = productInfoDOPageResult.getList();
List<ProductInfoRespVO> resList = wrapperRespVO(list);
return new PageResult<>(resList, productInfoDOPageResult.getTotal());
}
@Override
public PageResult<ProductInfoRespVO> queryPage(QueryFilterInfo<ProductInfoPageReqVO> queryFilterInfo) {
PageResult<ProductInfoDO> productInfoDOPageResult = productInfoMapper.selectPagePlus(queryFilterInfo);
List<ProductInfoDO> list = productInfoDOPageResult.getList();
list.forEach(productInfoDO -> {
if(FuncUtil.isNotEmpty(productInfoDO.getBrandId())){
BrandDO brandDO = brandMapper.selectById(productInfoDO.getBrandId());
if(FuncUtil.isNotEmpty(brandDO)){
productInfoDO.setBrandName(brandDO.getName());
}
}
if(FuncUtil.isNotEmpty(productInfoDO.getProductTypeId())){
ProductTypeDO productTypeDO = productTypeMapper.selectById(productInfoDO.getProductTypeId());
if(FuncUtil.isNotEmpty(productTypeDO)){
productInfoDO.setProductTypeName(productTypeDO.getLabel());
}
}
});
productInfoDOPageResult.setList(list);
return productInfoDOPageResult;
List<ProductInfoRespVO> resList = wrapperRespVO(list);
return new PageResult<>(resList, productInfoDOPageResult.getTotal());
}
private void checkCode(Long id, String code){
@ -273,6 +256,7 @@ public class ProductInfoServiceImpl implements ProductInfoService {
}
@Override
public String importExcel(List<ProductInfoExcelVO> list) {
List<ProductInfoDO> newList = new ArrayList<>();
@ -306,4 +290,87 @@ public class ProductInfoServiceImpl implements ProductInfoService {
productInfoMapper.insertBatch(newList);
return "";
}
private List<ProductInfoRespVO> wrapperRespVO(List<ProductInfoDO> list){
List<ProductInfoRespVO> resList = new ArrayList<>();
list.forEach(productInfoDO -> {
ProductInfoRespVO vo = BeanUtils.toBean(productInfoDO, ProductInfoRespVO.class);
wrapperBrandName(vo);
wrapperProductTypeName(vo);
wrapperCustomerGroupName(vo);
wrapperFileIds(vo);
resList.add(vo);
});
return resList;
}
private ProductInfoRespVO wrapperFileIds(ProductInfoRespVO vo){
if(FuncUtil.isNotEmpty(vo)){
String draftDesignDataIds = vo.getDraftDesignDataId();
if(FuncUtil.isNotEmpty(draftDesignDataIds)){
LambdaQueryWrapper<DraftDesignDataDO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(DraftDesignDataDO::getId, Arrays.asList(draftDesignDataIds.split(",")));
queryWrapper.eq(DraftDesignDataDO::getDeleted, false);
queryWrapper.isNotNull(DraftDesignDataDO::getFileIds);
queryWrapper.orderByDesc(BaseDO::getCreateTime);
List<DraftDesignDataDO> dos = draftDesignDataMapper.selectList(queryWrapper);
List<String> fileIds = new ArrayList<>();
if(FuncUtil.isNotEmpty(dos)){
for (DraftDesignDataDO designDataDO : dos) {
List<String> strList = FuncUtil.toStrList(designDataDO.getFileIds());
fileIds.addAll(strList);
}
}
if(FuncUtil.isNotEmpty(fileIds)){
vo.setFileIds(String.join(",",fileIds));
}
}
}
return vo;
}
private ProductInfoRespVO wrapperCustomerGroupName(ProductInfoRespVO vo){
if(FuncUtil.isNotEmpty(vo)){
if(FuncUtil.isNotEmpty(vo.getCustomerGroupId())){
LambdaQueryWrapper<CustomerGroupDO> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CustomerGroupDO::getId, FuncUtil.toStrList(vo.getCustomerGroupId()));
wrapper.eq(BaseDO::getDeleted,false);
List<CustomerGroupDO> groupDOS = customerGroupMapper.selectList(wrapper);
String name = null;
if(FuncUtil.isNotEmpty(groupDOS)){
List<String> names = new ArrayList<>();
for (CustomerGroupDO groupDO : groupDOS) {
if(names.contains(groupDO.getName())){
continue;
}
names.add(groupDO.getName());
}
name = String.join(",", names);
}
vo.setCustomerGroupName(name);
}
}
return vo;
}
private ProductInfoRespVO wrapperProductTypeName(ProductInfoRespVO vo){
if(FuncUtil.isNotEmpty(vo)){
if(FuncUtil.isNotEmpty(vo.getProductTypeId())){
ProductTypeDO productTypeDO = productTypeMapper.selectById(vo.getProductTypeId());
if(FuncUtil.isNotEmpty(productTypeDO)){
vo.setProductTypeName(productTypeDO.getLabel());
}
}
}
return vo;
}
private ProductInfoRespVO wrapperBrandName(ProductInfoRespVO vo){
if(FuncUtil.isNotEmpty(vo)){
if(FuncUtil.isNotEmpty(vo.getBrandId())){
BrandDO brandDO = brandMapper.selectById(vo.getBrandId());
if(FuncUtil.isNotEmpty(brandDO)){
vo.setBrandName(brandDO.getName());
}
}
}
return vo;
}
}

View File

@ -0,0 +1,12 @@
<?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="cn.hangtag.module.oms.dal.mysql.customergroup.CustomerGroupMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -0,0 +1,142 @@
package cn.hangtag.module.oms.service.customergroup;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import cn.hangtag.framework.test.core.ut.BaseDbUnitTest;
import cn.hangtag.module.oms.controller.admin.customergroup.vo.*;
import cn.hangtag.module.oms.dal.dataobject.customergroup.CustomerGroupDO;
import cn.hangtag.module.oms.dal.mysql.customergroup.CustomerGroupMapper;
import cn.hangtag.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*;
import java.time.LocalDateTime;
import static cn.hutool.core.util.RandomUtil.*;
import static cn.hangtag.module.oms.enums.ErrorCodeConstants.*;
import static cn.hangtag.framework.test.core.util.AssertUtils.*;
import static cn.hangtag.framework.test.core.util.RandomUtils.*;
import static cn.hangtag.framework.common.util.date.LocalDateTimeUtils.*;
import static cn.hangtag.framework.common.util.object.ObjectUtils.*;
import static cn.hangtag.framework.common.util.date.DateUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link CustomerGroupServiceImpl} 的单元测试类
*
* @author 管理员
*/
@Import(CustomerGroupServiceImpl.class)
public class CustomerGroupServiceImplTest extends BaseDbUnitTest {
@Resource
private CustomerGroupServiceImpl customerGroupService;
@Resource
private CustomerGroupMapper customerGroupMapper;
@Test
public void testCreateCustomerGroup_success() {
// 准备参数
CustomerGroupSaveReqVO createReqVO = randomPojo(CustomerGroupSaveReqVO.class).setId(null);
// 调用
Long customerGroupId = customerGroupService.createCustomerGroup(createReqVO);
// 断言
assertNotNull(customerGroupId);
// 校验记录的属性是否正确
CustomerGroupDO customerGroup = customerGroupMapper.selectById(customerGroupId);
assertPojoEquals(createReqVO, customerGroup, "id");
}
@Test
public void testUpdateCustomerGroup_success() {
// mock 数据
CustomerGroupDO dbCustomerGroup = randomPojo(CustomerGroupDO.class);
customerGroupMapper.insert(dbCustomerGroup);// @Sql: 先插入出一条存在的数据
// 准备参数
CustomerGroupSaveReqVO updateReqVO = randomPojo(CustomerGroupSaveReqVO.class, o -> {
o.setId(dbCustomerGroup.getId()); // 设置更新的 ID
});
// 调用
customerGroupService.updateCustomerGroup(updateReqVO);
// 校验是否更新正确
CustomerGroupDO customerGroup = customerGroupMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, customerGroup);
}
@Test
public void testUpdateCustomerGroup_notExists() {
// 准备参数
CustomerGroupSaveReqVO updateReqVO = randomPojo(CustomerGroupSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> customerGroupService.updateCustomerGroup(updateReqVO), CUSTOMER_GROUP_NOT_EXISTS);
}
@Test
public void testDeleteCustomerGroup_success() {
// mock 数据
CustomerGroupDO dbCustomerGroup = randomPojo(CustomerGroupDO.class);
customerGroupMapper.insert(dbCustomerGroup);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbCustomerGroup.getId();
// 调用
customerGroupService.deleteCustomerGroup(id);
// 校验数据不存在了
assertNull(customerGroupMapper.selectById(id));
}
@Test
public void testDeleteCustomerGroup_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> customerGroupService.deleteCustomerGroup(id), CUSTOMER_GROUP_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值然后删除 @Disabled 注解
public void testGetCustomerGroupPage() {
// mock 数据
CustomerGroupDO dbCustomerGroup = randomPojo(CustomerGroupDO.class, o -> { // 等会查询到
o.setCode(null);
o.setName(null);
o.setRemark(null);
o.setCreateTime(null);
});
customerGroupMapper.insert(dbCustomerGroup);
// 测试 code 不匹配
customerGroupMapper.insert(cloneIgnoreId(dbCustomerGroup, o -> o.setCode(null)));
// 测试 name 不匹配
customerGroupMapper.insert(cloneIgnoreId(dbCustomerGroup, o -> o.setName(null)));
// 测试 remark 不匹配
customerGroupMapper.insert(cloneIgnoreId(dbCustomerGroup, o -> o.setRemark(null)));
// 测试 createTime 不匹配
customerGroupMapper.insert(cloneIgnoreId(dbCustomerGroup, o -> o.setCreateTime(null)));
// 准备参数
CustomerGroupPageReqVO reqVO = new CustomerGroupPageReqVO();
reqVO.setCode(null);
reqVO.setName(null);
reqVO.setRemark(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<CustomerGroupDO> pageResult = customerGroupService.getCustomerGroupPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbCustomerGroup, pageResult.getList().get(0));
}
}

View File

@ -160,11 +160,11 @@ public class ProductInfoServiceImplTest extends BaseDbUnitTest {
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<ProductInfoDO> pageResult = productInfoService.getProductInfoPage(reqVO);
// PageResult<ProductInfoDO> pageResult = productInfoService.getProductInfoPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbProductInfo, pageResult.getList().get(0));
// assertEquals(1, pageResult.getTotal());
// assertEquals(1, pageResult.getList().size());
// assertPojoEquals(dbProductInfo, pageResult.getList().get(0));
}
}

View File

@ -47,9 +47,9 @@ spring:
primary: master
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/hangtag-uat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://202.74.40.60:33061/oms-uat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
username: root
password: 123456
password: qygo5gYNhivG
# slave: # 模拟从库,可根据自己需要修改
# lazy: true # 开启懒加载,保证启动速度
# url: jdbc:mysql://43.136.71.164:3306/hangtag?allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&useUnicode=true&characterEncoding=UTF-8

View File

@ -49,3 +49,21 @@ export const updateFile = (data: any) => {
export const getDomain = () => {
return request.get({ url: '/infra/file/domain'})
}
/**
*
* @param data
*/
export const updateFilePlus = (data: any) => {
return request.upload({ url: '/infra/file/upload-plus', data })
}
/**
* id
* @param ids
*/
export const infoByIds = (ids: string) => {
return request.get({ url: '/infra/file/info/'+ids })
}
export const renameById = (id: string, name: string) => {
return request.post({ url:`/infra/file/rename/${id}`,data:{name} })
}

View File

@ -0,0 +1,43 @@
import request from '@/config/axios'
// 客户组别 VO
export interface CustomerGroupVO {
id: number // id
code: string // 编码
name: string // 名称
sort: number // 排序号
remark: string // 备注
}
// 客户组别 API
export const CustomerGroupApi = {
// 查询客户组别 分页
getCustomerGroupPage: async (params: any) => {
return await request.get({ url: `/oms/customer-group/page`, params })
},
// 查询客户组别 详情
getCustomerGroup: async (id: number) => {
return await request.get({ url: `/oms/customer-group/get?id=` + id })
},
// 新增客户组别
createCustomerGroup: async (data: CustomerGroupVO) => {
return await request.post({ url: `/oms/customer-group/create`, data })
},
// 修改客户组别
updateCustomerGroup: async (data: CustomerGroupVO) => {
return await request.put({ url: `/oms/customer-group/update`, data })
},
// 删除客户组别
deleteCustomerGroup: async (id: number) => {
return await request.delete({ url: `/oms/customer-group/delete?id=` + id })
},
// 导出客户组别 Excel
exportCustomerGroup: async (params) => {
return await request.download({ url: `/oms/customer-group/export-excel`, params })
},
}

View File

@ -0,0 +1,138 @@
<template>
<div>
<div class="w-full">
<el-button @click="dialogVisible = true" :size="size" type="primary">
<Icon icon="ep:download"/>
{{ t('common.fileDownloadBtnText') }}
</el-button>
</div>
<Dialog
v-model="dialogVisible"
:title="t('common.fileDownloadTitle')"
width="800px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
:destroy-on-close="true"
:close-on-hash-change="false"
>
<el-table
ref="tableRef"
stripe
:data="fileList"
border
style="width: 100%">
<el-table-column prop="name" :label="t('common.fileDownloadNameLabel')"/>
<el-table-column prop="size" :label="t('common.fileDownloadSizeLabel')">
<template #default="scope">
{{ formatBytes(scope.row.size) }}
</template>
</el-table-column>
<el-table-column prop="url" :label="t('common.fileDownloadOptionsLabel')">
<template #default="scope">
<div>
<el-link
:href="scope.row.url"
:underline="false"
download
target="_blank"
type="primary"
>
<Icon icon="ep:download" :size="24"/>
</el-link>
</div>
</template>
</el-table-column>
</el-table>
</Dialog>
</div>
</template>
<script lang="ts" setup>
// @ts-nocheck
import {propTypes} from '@/utils/propTypes'
import type {UploadUserFile} from 'element-plus'
import {infoByIds} from "@/api/infra/file";
import {useMessage} from "@/hooks/web/useMessage";
import {useI18n} from "@/hooks/web/useI18n";
const {t} = useI18n()
//
defineOptions({name: 'AccessoryDownload'})
const message = useMessage() //
const emit = defineEmits(['update:modelValue'])
const text = t('fileUploadBtnText');
const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
size:{
// "" | "default" | "small" | "large"
type: String,
default: ''
}
})
const dialogVisible = ref(false);
const fileList = ref<UploadUserFile[]>([])
const that = reactive({
ids: [],
})
const formatBytes = (bytes: any, decimals = 2) =>{
if (bytes === null || bytes === undefined) {
return ''
}
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
const res = parseFloat((bytes / Math.pow(k, i)).toFixed(dm))
if (isNaN(res)) {
return '0 ' + sizes[0]
}
return res + ' ' + sizes[i]
}
//
watch(
() => props.modelValue,
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix
return
}
const arr = `${val}`.split(",");
const query = [];
for (let i = 0; i < arr.length; i++) {
const item = `${arr[i]}`
if(!that.ids.includes(item)){
query.push(item)
}
}
if(query.length > 0){
infoByIds(query.join(",")).then((res) => {
console.log("init", res)
if (res) {
for (let i = 0; i < res.length; i++) {
const item = res[i];
that.ids.push(`${item.id}`)
fileList.value.push({
name: item.name,
url: item.url,
size: item.size,
// 'ready' | 'uploading' | 'success' | 'fail'
status: "success",
response: item,
uid: item.id
})
}
}
})
}
},
{immediate: true, deep: true}
)
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,324 @@
<template>
<div class="upload-file w-full">
<el-upload
ref="uploadRef"
:file-list="fileList"
:action="'#'"
:auto-upload="autoUpload"
:before-upload="beforeUpload"
:drag="drag"
:http-request="httpRequest"
:limit="props.limit"
:multiple="props.limit > 1"
:on-error="excelUploadError"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-success="handleFileSuccess"
:show-file-list="true"
:accept="accept"
:disabled="disabled || fileList.length >= props.limit"
class="upload-file-uploader"
name="file"
>
<el-button v-if="!disabled" type="primary" :disabled="disabled || fileList.length >= props.limit">
<Icon icon="ep:upload-filled"/>
{{btnText}}
<span>
({{fileList.length}}/{{props.limit}})
</span>
</el-button>
<template v-if="isShowTip && !disabled" #tip>
<div style="font-size: 0.8rem">
{{ t(`common.fileUploadMaxSize`) }} <b style="color: #f56c6c">{{ fileSize }}MB</b>
</div>
<div style="font-size: 0.8rem">
{{ t(`common.fileUploadFormat`) }} <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</div>
</template>
<template #file="row">
<div class="flex items-center">
<div>
<!-- 'ready' | 'uploading' | 'success' | 'fail'-->
<div v-if="row.file.status === 'ready'">
<el-tag type="info">
<Icon icon="ep:clock"/>
</el-tag>
</div>
<div v-if="row.file.status === 'uploading'">
<el-tag type="warning" effect="dark">
<Icon icon="ep:loading"/>
</el-tag>
</div>
<div v-if="row.file.status === 'success'">
<el-tag type="success" effect="dark">
<Icon icon="ep:success-filled"/>
</el-tag>
</div>
<div v-if="row.file.status === 'fail'" style="color: #ff0000">
<el-tag type="danger" effect="dark">
<Icon icon="ep:warning-filled"/>
</el-tag>
{{ row.file.error }}
</div>
</div>
<el-tooltip
class="box-item"
effect="dark"
:content="row.file.name"
placement="top-start"
>
<div class="ml-2 line-clamp-1">{{ row.file.name }}</div>
</el-tooltip>
<div class="flex">
<div class="ml-1">
<el-link
:href="row.file.url"
:underline="false"
download
target="_blank"
type="primary"
>
<Icon icon="ep:download" :size="24"/>
</el-link>
</div>
<div class="ml-1" v-if="!disabled">
<el-button link type="primary" @click="reName(row.file)">
<Icon icon="ep:edit" :size="18"/>
</el-button>
</div>
<div class="ml-1" v-if="!disabled">
<el-button link type="danger" @click="handleRemove(row.file)">
<Icon icon="ep:delete" :size="18"/>
</el-button>
</div>
</div>
</div>
</template>
</el-upload>
</div>
</template>
<script lang="ts" setup>
// @ts-nocheck
import {propTypes} from '@/utils/propTypes'
import type {UploadInstance, UploadProps, UploadRawFile, UploadUserFile} from 'element-plus'
import {useUpload} from '@/components/UploadFile/src/useUploadlPlus'
import {UploadFile} from 'element-plus/es/components/upload/src/upload'
import {infoByIds, renameById} from "@/api/infra/file";
import {useMessage} from "@/hooks/web/useMessage";
import {useI18n} from "@/hooks/web/useI18n";
const {t} = useI18n()
//
defineOptions({name: 'AccessoryUpload'})
const message = useMessage() //
const emit = defineEmits(['update:modelValue'])
const text = t('fileUploadBtnText');
const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // , ['png', 'jpg', 'jpeg']
fileSize: propTypes.number.def(5), // (MB)
limit: propTypes.number.def(5), //
autoUpload: propTypes.bool.def(true), //
drag: propTypes.bool.def(false), //
isShowTip: propTypes.bool.def(true), //
disabled: propTypes.bool.def(false), // ==> false
buttonText: propTypes.string.def(''),
})
const btnText = computed(() => {
if(props.buttonText){
return props.buttonText
}
return t('common.fileUploadBtnText')
})
const accept = computed(() => {
let accept = ''
props.fileType.forEach((type: string) => {
accept += `.${type},`
})
return accept.slice(0, -1)
})
// ========== ==========
const uploadRef = ref<UploadInstance>()
const fileList = ref<UploadUserFile[]>([])
const uploadNumber = ref<number>(0)
const that = reactive({
ids: [],
})
const {httpRequest} = useUpload()
//
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) {
message.error(`${t("common.fileUploadMaxCountTips")}${props.limit}`)
return false
}
let fileExtension = ''
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
}
const isImg = props.fileType.some((type: string) => {
if (file.type.indexOf(type) > -1) return true
return !!(fileExtension && fileExtension.indexOf(type) > -1)
})
const isLimit = file.size < props.fileSize * 1024 * 1024
if (!isImg) {
message.error(`${t("common.fileUploadFormatErrorTips")}${props.fileType.join('/')}`)
return false
}
if (!isLimit) {
message.error(`${t("common.fileUploadMaxSizeTips")}${props.fileSize}`)
return false
}
message.success(t("common.fileUploadingTips"))
uploadNumber.value++
}
//
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
console.log("上传成功", res)
const item = res.data;
fileList.value.push({
name: item.name,
url: item.url,
size: item.size,
// 'ready' | 'uploading' | 'success' | 'fail'
status: "success",
response: item,
uid: item.id
})
that.ids.push(`${res.data.id}`)
emitUpdateModelValue()
}
//
const handleExceed: UploadProps['onExceed'] = (): void => {
message.error(`${t('common.fileUploadMaxCountTips')}${props.limit}`)
}
//
const excelUploadError: UploadProps['onError'] = (): void => {
message.error(t('common.fileUploadFailedTips'))
}
//
const handleRemove = (file: UploadFile) => {
const index = fileList.value.map((f) => f.name).indexOf(file.name)
if (index > -1) {
useMessage().delConfirm().then((res) => {
if (res) {
fileList.value.splice(index, 1)
emitUpdateModelValue()
}
})
}
}
const reName = (file: UploadFile) => {
console.log(file)
if (file.status == 'success') {
//
const name = file.name.substring(0, file.name.lastIndexOf('.'))
const options = {inputVal: name, tip: t('common.fileUploadInputFileName')};
useMessage().submit(options).then((res) => {
if (res) {
renameById(`${file.uid}`, res.value).then((res2) => {
message.success(t('common.success'))
fileList.value.forEach((item, index) => {
if (`${item.uid}` === `${file.uid}`) {
fileList.value[index].name = res2.name
}
})
})
}
})
} else {
useMessage().warning(t('common.fileUploadNotUploadTips'));
}
}
const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
console.log(uploadFile)
}
//
watch(
() => props.modelValue,
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix
return
}
const arr = `${val}`.split(",");
const query = [];
for (let i = 0; i < arr.length; i++) {
const item = `${arr[i]}`
if(!that.ids.includes(item)){
query.push(item)
}
}
if(query.length > 0){
infoByIds(query.join(",")).then((res) => {
console.log("init", res)
if (res) {
for (let i = 0; i < res.length; i++) {
const item = res[i];
that.ids.push(`${item.id}`)
fileList.value.push({
name: item.name,
url: item.url,
size: item.size,
// 'ready' | 'uploading' | 'success' | 'fail'
status: "success",
response: item,
uid: item.id
})
}
}
})
}
},
{immediate: true, deep: true}
)
//
const emitUpdateModelValue = () => {
// 1
let result: string | string[] = fileList.value.map((file) => `${file.uid}`)
console.log("result",result,fileList.value)
result = result.join(',')
emit('update:modelValue', result)
}
</script>
<style lang="scss" scoped>
.upload-file-uploader {
margin-bottom: 5px;
}
:deep(.upload-file-list .el-upload-list__item) {
position: relative;
margin-bottom: 10px;
line-height: 2;
border: 1px solid #e4e7ed;
}
:deep(.el-upload-list__item-file-name) {
max-width: 250px;
}
:deep(.upload-file-list .ele-upload-list__item-content) {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
:deep(.ele-upload-list__item-content-action .el-link) {
margin-right: 10px;
}
</style>

View File

@ -0,0 +1,100 @@
import * as FileApi from '@/api/infra/file'
import CryptoJS from 'crypto-js'
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
import axios from 'axios'
export const useUpload = () => {
// 后端上传地址
const uploadUrl = import.meta.env.VITE_UPLOAD_URL
// 是否使用前端直连上传
const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE
// 重写ElUpload上传方法
const httpRequest = async (options: UploadRequestOptions) => {
// 模式一:前端上传
if (isClientUpload) {
// 1.1 生成文件名称
const fileName = await generateFileName(options.file)
// 1.2 获取文件预签名地址
const presignedInfo = await FileApi.getFilePresignedUrl(fileName)
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传Minio 不支持)
return axios.put(presignedInfo.uploadUrl, options.file, {
headers: {
'Content-Type': options.file.type,
}
}).then(() => {
// 1.4. 记录文件信息到后端(异步)
createFile(presignedInfo, fileName, options.file)
// 通知成功,数据格式保持与后端上传的返回结果一致
return { data: presignedInfo.url , filename: options.file.name}
})
} else {
// 模式二:后端上传
// 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
return new Promise((resolve, reject) => {
FileApi.updateFilePlus({ file: options.file })
.then((res) => {
if (res.code === 0) {
resolve({
...res,
filename: options.file.name
})
} else {
reject(res)
}
})
.catch((res) => {
reject(res)
})
})
}
}
return {
uploadUrl,
httpRequest
}
}
/**
*
* @param vo
* @param name
* @param file
*/
function createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: UploadRawFile) {
const fileVo = {
configId: vo.configId,
url: vo.url,
path: name,
name: file.name,
type: file.type,
size: file.size
}
FileApi.createFile(fileVo)
return fileVo
}
/**
* 使SHA256
* @param file
*/
async function generateFileName(file: UploadRawFile) {
// 读取文件内容
const data = await file.arrayBuffer()
const wordArray = CryptoJS.lib.WordArray.create(data)
// 计算SHA256
const sha256 = CryptoJS.SHA256(wordArray).toString()
// 拼接后缀
const ext = file.name.substring(file.name.lastIndexOf('.'))
return `${sha256}${ext}`
}
/**
*
*/
enum UPLOAD_TYPE {
// 客户端直接上传只支持S3服务
CLIENT = 'client',
// 客户端发送到后端上传
SERVER = 'server'
}

View File

@ -1,7 +1,8 @@
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import { useI18n } from './useI18n'
import {ElMessage, ElMessageBox, ElNotification} from 'element-plus'
import {useI18n} from './useI18n'
export const useMessage = () => {
const { t } = useI18n()
const {t} = useI18n()
return {
// 消息提示
info(content: string) {
@ -25,15 +26,15 @@ export const useMessage = () => {
},
// 错误提示
alertError(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'error' })
ElMessageBox.alert(content, t('common.confirmTitle'), {type: 'error'})
},
// 成功提示
alertSuccess(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'success' })
ElMessageBox.alert(content, t('common.confirmTitle'), {type: 'success'})
},
// 警告提示
alertWarning(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'warning' })
ElMessageBox.alert(content, t('common.confirmTitle'), {type: 'warning'})
},
// 通知提示
notify(content: string) {
@ -90,6 +91,29 @@ export const useMessage = () => {
cancelButtonText: t('common.cancel'),
type: 'warning'
})
},
submit({ inputVal, tip, content = '',inputValidator = undefined }: {
inputVal: string,
tip: string,
content?: string,
inputValidator?: Function | undefined
}) {
return ElMessageBox.prompt(content, tip, {
inputValue: inputVal,
inputPlaceholder: t('common.inputText'),
inputValidator: (value) => {
if (inputValidator) {
return inputValidator(value);
} else {
if (!value) {
return t('common.inputText')
}
}
return true
},
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
})
}
}
}

View File

@ -54,7 +54,23 @@ export default {
updateTime: 'Update Time',
copy: 'Copy',
copySuccess: 'Copy Success',
copyError: 'Copy Error'
copyError: 'Copy Error',
keywordPlaceholder: 'Please enter a keyword',
fileUploadMaxSize: "Maximum size per file",
fileUploadFormat: "Supported formats",
fileUploadBtnText: "Upload attachments",
fileUploadMaxCountTips: "The number of uploaded files cannot exceed",
fileUploadMaxSizeTips: "The size of the uploaded file cannot exceed",
fileUploadFormatErrorTips: "The file format is incorrect. Please upload",
fileUploadingTips: "The file is being uploaded. Please wait...",
fileUploadFailedTips: "The data upload failed. Please upload again!",
fileUploadInputFileName: "Please enter the file name",
fileUploadNotUploadTips: "Please upload the file first",
fileDownloadNameLabel: "File name",
fileDownloadSizeLabel: "File size",
fileDownloadOptionsLabel: "Operations",
fileDownloadTitle: "File list",
fileDownloadBtnText: "Download working papers"
},
lock: {
lockScreen: 'Lock screen',
@ -671,6 +687,8 @@ export default {
"viewOrder": "viewOrder",
"backHome": "BackHome",
"downloadSuccess": "downloadSuccess",
"downloadFile": "Download of working papers",
"labelAccessoryFile": "Attachment"
},
"productDialogList": {
"title": "Product List",
@ -679,6 +697,7 @@ export default {
"colLabelCover": "Cover",
"colLabelName": "Product Name",
"colLabelRemark": "Remarks",
"colLabelDetails": "Details Description"
"colLabelDetails": "Details Description",
"colLabelFileIds": 'Working paper attachments'
}
}

View File

@ -54,7 +54,23 @@ export default {
updateTime: '更新时间',
copy: '复制',
copySuccess: '复制成功',
copyError: '复制失败'
copyError: '复制失败',
fileUploadMaxSize: "单文件最大",
keywordPlaceholder: '请输入关键字',
fileUploadFormat: "支持格式",
fileUploadBtnText: "附件上传",
fileUploadMaxCountTips: "上传文件数量不能超过",
fileUploadMaxSizeTips: "上传文件大小不能超过",
fileUploadFormatErrorTips: "文件格式不正确, 请上传",
fileUploadingTips: "正在上传文件,请稍候...",
fileUploadFailedTips: "数据失败上传失败,请您重新上传!",
fileUploadInputFileName: "请输入文件名称",
fileUploadNotUploadTips: "请先上传文件",
fileDownloadNameLabel: "文件名称",
fileDownloadSizeLabel: "文件大小",
fileDownloadOptionsLabel: "操作",
fileDownloadTitle: "文件列表",
fileDownloadBtnText: "底稿下载",
},
lock: {
lockScreen: '锁定屏幕',
@ -667,6 +683,8 @@ export default {
viewOrder: "查看订单",
backHome: "返回首页",
downloadSuccess: "下载成功",
downloadFile: "底稿下载",
labelAccessoryFile: "附件"
},
productDialogList:{
title: '产品列表',
@ -676,5 +694,6 @@ export default {
colLabelName: '产品名称',
colLabelRemark: '备注',
colLabelDetails: '详情描述',
colLabelFileIds: '底稿附件',
},
}

View File

@ -0,0 +1,107 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入编码" />
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="排序号" prop="sort">
<el-input-number v-model="formData.sort" placeholder="请输入排序号" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { CustomerGroupApi, CustomerGroupVO } from '@/api/oms/customergroup'
/** 客户组别 表单 */
defineOptions({ name: 'CustomerGroupForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
code: undefined,
name: undefined,
sort: 1,
remark: undefined,
})
const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await CustomerGroupApi.getCustomerGroup(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as CustomerGroupVO
if (formType.value === 'create') {
await CustomerGroupApi.createCustomerGroup(data)
message.success(t('common.createSuccess'))
} else {
await CustomerGroupApi.updateCustomerGroup(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
code: undefined,
name: undefined,
sort: undefined,
remark: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,220 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="编码" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="排序号" prop="sort">
<el-input-number
v-model="queryParams.sort"
placeholder="请输入排序号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryParams.remark"
placeholder="请输入备注"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['oms:customer-group:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['oms:customer-group:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="id" align="center" prop="id" />
<el-table-column label="编码" align="center" prop="code" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="排序号" align="center" prop="sort" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['oms:customer-group:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['oms:customer-group:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<CustomerGroupForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { CustomerGroupApi, CustomerGroupVO } from '@/api/oms/customergroup'
import CustomerGroupForm from './CustomerGroupForm.vue'
/** 客户组别 列表 */
defineOptions({ name: 'CustomerGroup' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerGroupVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
name: undefined,
sort: undefined,
remark: undefined,
createTime: [],
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerGroupApi.getCustomerGroupPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await CustomerGroupApi.deleteCustomerGroup(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await CustomerGroupApi.exportCustomerGroup(queryParams)
download.excel(data, '客户组别 .xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -23,6 +23,7 @@
</el-form-item>
</el-col>
</el-row>
<el-button @click="editMore">编辑更多属性</el-button>
<div>
<DraftDesign ref="draftDesignRef" @save="submitForm"/>
</div>
@ -57,10 +58,26 @@
</el-form-item>
</el-col>
</el-row>
<el-form-item label="cover" prop="cover">
<UploadImg v-model="formData.cover" />
</el-form-item>
<el-row>
<el-col :span="8" :xs="24">
<el-form-item label="cover" prop="cover">
<UploadImg v-model="formData.cover" />
</el-form-item>
</el-col>
<el-col :span="12" :xs="24">
<el-form-item label="底稿文件" prop="fileList">
<AccessoryUpload
v-model="formData.fileIds"
:fileType="['svg', 'png', 'jpg', 'jpeg','doc', 'xls', 'ppt', 'txt', 'pdf']"
:file-size="20"
:limit="10"
drag
/>
</el-form-item>
</el-col>
</el-row>
<div id="bottomDiv"> </div>
</el-form>
</template>
@ -77,7 +94,6 @@ const { t } = useI18n() // 国际化
const message = useMessage() //
const route = useRoute() //
const draftDesignRef = ref()
const designPropEditRef = ref()
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
@ -86,6 +102,7 @@ const formData = ref({
id: route.params.id,
code: undefined,
cover: undefined,
fileIds: "",
name: undefined,
author: undefined,
version: 1,
@ -110,12 +127,19 @@ onMounted(()=>{
})
const editMore = () => {
//
const target = document.getElementById('bottomDiv');
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
}
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
// designPropEditRef.value.init(1);
resetForm()
//
if (id) {

View File

@ -79,7 +79,7 @@ const submitForm = async () => {
'tenant-id': getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
uploadRef.value!.submit({})
}
/** 文件上传成功 */

View File

@ -89,7 +89,7 @@ const submitForm = async () => {
'tenant-id': getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
uploadRef.value!.submit({})
}
/** 文件上传成功 */

View File

@ -43,6 +43,14 @@
<el-form-item label="产品名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入产品名称"/>
</el-form-item>
<el-form-item label="产品类型" prop="productTypeId">
<ProductTypeDataListDialog
v-model="formData.productTypeId"
placeholder="请选择产品类型"/>
</el-form-item>
<el-form-item label="品牌" prop="brandId">
<BrandDataListDialog v-model="formData.brandId" placeholder="请选择品牌"/>
</el-form-item>
</el-col>
<el-col :span="12" :xs="24">
<el-form-item label="封面" prop="cover">
@ -53,16 +61,15 @@
</el-row>
<el-row>
<el-col :span="12" :xs="24">
<el-form-item label="品牌" prop="brandId">
<BrandDataListDialog v-model="formData.brandId" placeholder="请选择品牌"/>
</el-form-item>
</el-col>
<el-col :span="12" :xs="24">
<el-form-item label="产品类型" prop="productTypeId">
<ProductTypeDataListDialog v-model="formData.productTypeId"
placeholder="请选择产品类型"/>
<el-form-item label="客户组别" prop="customerGroupId">
<CustomerGroupDataListDialog
:is-can-edit="true"
v-model="formData.customerGroupId"
placeholder="请选择客户组别"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" :xs="24">
@ -75,7 +82,6 @@
<el-input-number :min="0" :precision="2" v-model="formData.specSizeHeight"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
@ -106,6 +112,7 @@
</el-col>
</el-row>
<el-form-item label="关联设计稿" v-if="formData.templateType === '1'"
prop="draftDesignDataId">
<div style="width: calc(100% - 20px);">
@ -168,7 +175,8 @@
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{$index}">
<el-button size="small" type="danger" @click="removeRow(that.draftDesignList,$index) ">
<el-button size="small" type="danger"
@click="removeRow(that.draftDesignList,$index) ">
<Icon icon="ep:delete"/>
</el-button>
</template>
@ -240,12 +248,14 @@
</div>
</template>
<template #default="scope">
<el-checkbox v-model="scope.row.d" @change="changSetDefault(scope.$index,scope.row.d)"/>
<el-checkbox v-model="scope.row.d"
@change="changSetDefault(scope.$index,scope.row.d)"/>
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{$index}">
<el-button size="small" type="danger" @click="removeRow(that.priceList,$index) ">
<el-button size="small" type="danger"
@click="removeRow(that.priceList,$index) ">
<Icon icon="ep:delete"/>
</el-button>
</template>
@ -371,6 +381,7 @@ const formData = ref({
summary: undefined,
brandId: '',
productTypeId: '',
customerGroupId: '',
draftDesignDataId: undefined,
draftDesignList: '',
priceList: '',
@ -415,7 +426,7 @@ const addPriceRow = () => {
})
}
const activeName = ref('base')
const removeRow = (data,index) => {
const removeRow = (data, index) => {
if (data.length > 1) {
data.splice(index, 1)
} else {
@ -464,10 +475,10 @@ const duplicateInfoCheck = (name: string) => {
}
return count > 1;
}
const changSetDefault = (index,d)=>{
if(d){
for(let i=0;i<that.priceList.length;i++){
if(index != i){
const changSetDefault = (index, d) => {
if (d) {
for (let i = 0; i < that.priceList.length; i++) {
if (index != i) {
that.priceList[i].d = false
}
}
@ -520,7 +531,7 @@ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成
const submitCheck = () => {
if(!formData.value.name){
if (!formData.value.name) {
formData.value.name = formData.value.code || '未命名'
}
formRef.value.validate().then(() => {
@ -609,10 +620,10 @@ const submitForm = async () => {
data.draftDesignList = JSON.stringify(that.draftDesignList)
//
if(that.priceList.length > 0){
if (that.priceList.length > 0) {
let match = false;
for (let i = 0; i < that.priceList.length; i++) {
if(duplicatePriceCheck(that.priceList[i].c)){
if (duplicatePriceCheck(that.priceList[i].c)) {
message.error(`单价设置-第${i + 1} 行币种重复`)
return;
}

View File

@ -43,21 +43,27 @@ export const createFile = (data: any) => {
export const updateFile = (data: any) => {
return request.upload({ url: '/infra/file/upload', data })
}
/**
*
*/
export const getDomain = () => {
return new Promise((resolve)=>{
const domain= sessionStorage.getItem('_$domain$_')
if(domain){
resolve(domain)
return;
}
request.get({ url: '/infra/file/domain'}).then(res =>{
sessionStorage.setItem('_$domain$_',res)
resolve(res)
})
})
return request.get({ url: '/infra/file/domain'})
}
/**
*
* @param data
*/
export const updateFilePlus = (data: any) => {
return request.upload({ url: '/infra/file/upload-plus', data })
}
/**
* id
* @param ids
*/
export const infoByIds = (ids: string) => {
return request.get({ url: '/infra/file/info/'+ids })
}
export const renameById = (id: string, name: string) => {
return request.post({ url:`/infra/file/rename/${id}`,data:{name} })
}

View File

@ -0,0 +1,43 @@
import request from '@/config/axios'
// 客户组别 VO
export interface CustomerGroupVO {
id: number // id
code: string // 编码
name: string // 名称
sort: number // 排序号
remark: string // 备注
}
// 客户组别 API
export const CustomerGroupApi = {
// 查询客户组别 分页
getCustomerGroupPage: async (params: any) => {
return await request.get({ url: `/oms/customer-group/page`, params })
},
// 查询客户组别 详情
getCustomerGroup: async (id: number) => {
return await request.get({ url: `/oms/customer-group/get?id=` + id })
},
// 新增客户组别
createCustomerGroup: async (data: CustomerGroupVO) => {
return await request.post({ url: `/oms/customer-group/create`, data })
},
// 修改客户组别
updateCustomerGroup: async (data: CustomerGroupVO) => {
return await request.put({ url: `/oms/customer-group/update`, data })
},
// 删除客户组别
deleteCustomerGroup: async (id: number) => {
return await request.delete({ url: `/oms/customer-group/delete?id=` + id })
},
// 导出客户组别 Excel
exportCustomerGroup: async (params) => {
return await request.download({ url: `/oms/customer-group/export-excel`, params })
},
}

View File

@ -0,0 +1,71 @@
<template>
<Dialog :title="dialogTitle" append-to-body v-model="dialogVisible">
<Form :disabled="disabled" ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" />
<template #footer>
<el-button v-if="!disabled" @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { rules, allSchemas } from './config.data'
import {CustomerGroupApi, CustomerGroupVO} from "@/api/oms/customergroup";
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formRef = ref() // Ref
const disabled = computed(() => {
return formType.value !== 'create' && formType.value !== 'update'
})
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
//
if (id) {
formLoading.value = true
try {
const data = await CustomerGroupApi.getCustomerGroup(id)
formRef.value.setValues(data)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.getElFormRef().validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formRef.value.formModel as CustomerGroupVO
if (formType.value === 'create') {
await CustomerGroupApi.createCustomerGroup(data)
message.success(t('common.createSuccess'))
} else {
await CustomerGroupApi.updateCustomerGroup(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
</script>

View File

@ -0,0 +1,33 @@
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
// 表单校验
export const rules = reactive({
})
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive<CrudSchema[]>([
{
label: 'code',
field: 'code',
width: 200,
isSearch: true,
},
{
label: 'name',
field: 'name',
isSearch: true,
},
{
label: 'remark',
field: 'remark',
isTable: false,
},
{
label: 'options',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -0,0 +1,319 @@
<template>
<slot>
<div>
<el-input
v-model="that.showValue"
clearable
:placeholder="props.placeholder"
@clear="clearData"
@click="viewDetails"
>
<template #append>
<el-button @click.stop="openDialog">
<Icon icon="ep:search"/>
</el-button>
</template>
</el-input>
</div>
</slot>
{{tmp}}
<Dialog
:title="dialogTitle"
width="80%"
append-to-body
v-model="that.visible"
@close="updateVisible(false)">
<div>
<!-- 搜索工作栏 -->
<ContentWrap>
<Search
:schema="allSchemas.searchSchema"
:is-col="false"
@search="setSearchParams"
@reset="setSearchParams">
<!-- 新增等操作按钮 -->
<template #actionMore>
<el-button v-if="isCanEdit"
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['oms:draft-design-data:create']"
>
<Icon icon="ep:plus" class="mr-5px"/>
新增
</el-button>
<div v-else>
</div>
</template>
</Search>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<Table
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:loading="tableObject.loading"
:selection="true"
ref="tableRef"
@selection-change="selectionChange"
:pagination="{ total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
>
<template #action="{ row }">
<div v-if="isCanEdit">
<el-button
link
type="primary"
@click="openForm('update', row.id)"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(row.id)"
>
删除
</el-button>
</div>
<div v-else>
-
</div>
</template>
</Table>
</ContentWrap>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="updateVisible(false,true)">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="submit">{{ t('common.ok') }}</el-button>
</span>
</template>
</Dialog>
<!-- 表单弹窗添加/修改 -->
<DataForm ref="formRef" @success="getList"/>
</template>
<script lang="ts" setup name="ProductTypeDataListDialog">
import {allSchemas} from './config.data'
import DataForm from './DataForm.vue'
import {CustomerGroupApi} from "@/api/oms/customergroup";
defineOptions({name: 'CustomerGroupDataListDialog'})
const emit = defineEmits(['update:modelValue', 'update:visible', 'submit']) // success
const {t} = useI18n() //
const dialogTitle = ref('Customer group') //
const props = defineProps({
modelValue: {
type: [String,Number],
required: true
},
dataKey: {
type: String,
required: false,
default: 'id'
},
showKey: {
type: String,
required: false,
default: 'name'
},
multiple: {
type: Boolean,
required: false,
default: false
},
isCanEdit: {
type: Boolean,
required: false,
default: false
},
placeholder: {
type: String,
required: false,
default: 'selectText'
},
width: {
type: String,
required: false,
default: '64px'
},
height: {
type: String,
required: false,
default: '64px'
},
visible: {
type: Boolean,
required: false,
default: false
}
})
const that = reactive({
inputVal: '',
showValue: '',
visible: false,
})
const toStr = (data: any, def = '') => {
if (data !== null && data !== undefined) {
return `${data}`
}
return def
}
const openDialog = () => {
updateVisible(true);
}
const viewDetails = () => {
if (that.inputVal) {
openForm("preview", that.inputVal.split(",")[0])
} else {
openDialog();
}
}
let map = new Map();
const initInput = async () => {
const dataKey = that.inputVal + ',' + props.dataKey + ',' + props.showKey + ',' + props.multiple;
if (map.has(dataKey)) {
const data = map.get(dataKey)
if (data) {
that.inputVal = data.inputVal
that.showValue = data.showValue
console.log('缓存数据', data)
return;
}
}
const ids = that.inputVal.split(",");
let tmpInput = [];
let tmpShow = [];
for (let i = 0; i < ids.length; i++) {
const data = await CustomerGroupApi.getCustomerGroup(ids[i])
tmpInput.push(data[props.dataKey]);
tmpShow.push(data[props.showKey]);
}
that.inputVal = tmpInput.join(',');
that.showValue = tmpShow.join(',');
map.set(dataKey, {
inputVal: that.inputVal,
showValue: that.showValue
})
}
watch(() => props.visible, (newVal) => {
that.visible = newVal;
})
watch(() => props.modelValue, (newVal)=>{
that.inputVal = toStr(newVal,'')
if (that.inputVal) {
initInput();
}
},{
immediate: true
})
//
const tmp = computed(()=>{
setTimeout(()=>{
that.inputVal = toStr(props.modelValue,that.inputVal)
if (that.inputVal) {
initInput();
}
},100)
return ''
},{deep: true})
const clearData = () => {
that.inputVal = '';
that.showValue = '';
updateValue();
}
const updateVisible = (visible: boolean, clearInput = false) => {
that.visible = visible;
emit("update:visible", visible)
if (clearInput) {
clearData();
}
}
defineExpose({}) // open
const submit = () => {
updateValue();
updateVisible(false)
}
const updateValue = () => {
emit("update:modelValue", that.inputVal)
}
//
const selectionChange = (row) => {
if (row && row.length > 0) {
if (props.multiple) {
that.inputVal = row.map(item => item[props.dataKey || 'id']).join(',')
that.showValue = row.map(item => item[props.showKey || 'id']).join(',')
} else {
if (row.length > 1) {
useMessage().warning('单选数据,已忽略其他')
}
that.showValue = row[row.length - 1][props.showKey || 'id']
that.inputVal = row[row.length - 1][props.dataKey || 'id']
}
} else {
that.inputVal = ''
that.showValue = ''
}
}
const {tableObject, tableMethods} = useTable({
getListApi: CustomerGroupApi.getCustomerGroupPage, //
delListApi: CustomerGroupApi.deleteCustomerGroup //
})
//
const {getList, setSearchParams} = tableMethods
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = (id: number) => {
tableMethods.delList(id, false)
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
:deep(.el-input__wrapper) {
position: relative;
.el-input__inner {
padding-right: 18px;
}
.el-input__suffix {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
}
}
</style>

View File

@ -1,5 +1,4 @@
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
import {useI18n} from "@/hooks/web/useI18n";
const {t} = useI18n()
// 表单校验
@ -20,6 +19,16 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'code',
isSearch: true,
},
{
label: t('productDialogList.customerGroupId'),
field: 'customerGroupId',
isTable: false,
isSearch: true,
},
{
label: t('productDialogList.customerGroupName'),
field: 'customerGroupName',
},
{
label: t('productDialogList.colLabelType'),
field: 'productTypeId',
@ -45,6 +54,11 @@ const crudSchemas = reactive<CrudSchema[]>([
label: t('productDialogList.colLabelDetails'),
field: 'details',
isSearch: false,
},
{
label: t('productDialogList.colLabelFileIds'),
field: 'fileIds',
isSearch: false,
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -11,7 +11,7 @@
<ContentWrap>
<Search
:expand="true"
:expandField="['productTypeId','name']"
:expandField="['productTypeId','name','customerGroupId']"
:schema="allSchemas.searchSchema"
:is-col="false"
ref="searchRef"
@ -21,17 +21,29 @@
<!-- 新增等操作按钮 -->
<template #productTypeId="{data}">
<div>
<el-select class="w-full min-w-[200px]" v-model="data.productTypeId" clearable placeholder="Please select the product type">
<el-option v-for="item in that.typeList" :key="item.id" :label="item.label" :value="item.id"/>
<el-select class="w-full min-w-[200px]" v-model="data.productTypeId" clearable
placeholder="Please select the product type">
<el-option
v-for="item in that.typeList" :key="item.id" :label="item.label"
:value="item.id"/>
</el-select>
</div>
</template>
<template #code="{data}">
<div>
<KeywordSearch v-model="data.code"
:type="'productInfoCode'"
:filter="that.queryInfo.brandId"
:count="15"/>
<KeywordSearch
v-model="data.code"
:type="'productInfoCode'"
:filter="that.queryInfo.brandId"
:count="15"/>
</div>
</template>
<template #customerGroupId="{data}">
<div>
<CustomerGroupDataListDialog
v-model="data.customerGroupId"
:is-can-edit="false"
/>
</div>
</template>
</Search>
@ -65,6 +77,12 @@
</div>
<div v-else>-</div>
</template>
<template #fileIds="{row}">
<div v-if="row.fileIds">
<AccessoryDownload size="small" v-model="row.fileIds"/>
</div>
<div v-else>-</div>
</template>
</Table>
</ContentWrap>
@ -86,7 +104,8 @@ import {allSchemas} from './config.data'
import {ProductInfoApi} from '@/api/oms/productinfo'
import DataForm from './DataForm.vue'
import {ProductTypeApi} from "@/api/base/producttype";
import {KeywordApi} from "@/api/base/keyword";
import CustomerGroupDataListDialog
from "@/components/Dialog/src/CustomerGroupDataListDialog/index.vue";
/** 稿件图片库 */
defineOptions({name: 'ProductInfoList'})
@ -154,7 +173,7 @@ const that = reactive({
queryInfo: {
productTypeId: null,
code: '',
brandId:'',
brandId: '',
}
})

View File

@ -0,0 +1,138 @@
<template>
<div>
<div class="w-full">
<el-button @click="dialogVisible = true" :size="size" type="primary">
<Icon icon="ep:download"/>
{{ t('common.fileDownloadBtnText') }}
</el-button>
</div>
<Dialog
v-model="dialogVisible"
:title="t('common.fileDownloadTitle')"
width="800px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
:destroy-on-close="true"
:close-on-hash-change="false"
>
<el-table
ref="tableRef"
stripe
:data="fileList"
border
style="width: 100%">
<el-table-column prop="name" :label="t('common.fileDownloadNameLabel')"/>
<el-table-column prop="size" :label="t('common.fileDownloadSizeLabel')">
<template #default="scope">
{{ formatBytes(scope.row.size) }}
</template>
</el-table-column>
<el-table-column prop="url" :label="t('common.fileDownloadOptionsLabel')">
<template #default="scope">
<div>
<el-link
:href="scope.row.url"
:underline="false"
download
target="_blank"
type="primary"
>
<Icon icon="ep:download" :size="24"/>
</el-link>
</div>
</template>
</el-table-column>
</el-table>
</Dialog>
</div>
</template>
<script lang="ts" setup>
// @ts-nocheck
import {propTypes} from '@/utils/propTypes'
import type {UploadUserFile} from 'element-plus'
import {infoByIds} from "@/api/infra/file";
import {useMessage} from "@/hooks/web/useMessage";
import {useI18n} from "@/hooks/web/useI18n";
const {t} = useI18n()
//
defineOptions({name: 'AccessoryDownload'})
const message = useMessage() //
const emit = defineEmits(['update:modelValue'])
const text = t('fileUploadBtnText');
const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
size:{
// "" | "default" | "small" | "large"
type: String,
default: ''
}
})
const dialogVisible = ref(false);
const fileList = ref<UploadUserFile[]>([])
const that = reactive({
ids: [],
})
const formatBytes = (bytes: any, decimals = 2) =>{
if (bytes === null || bytes === undefined) {
return ''
}
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
const res = parseFloat((bytes / Math.pow(k, i)).toFixed(dm))
if (isNaN(res)) {
return '0 ' + sizes[0]
}
return res + ' ' + sizes[i]
}
//
watch(
() => props.modelValue,
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix
return
}
const arr = `${val}`.split(",");
const query = [];
for (let i = 0; i < arr.length; i++) {
const item = `${arr[i]}`
if(!that.ids.includes(item)){
query.push(item)
}
}
if(query.length > 0){
infoByIds(query.join(",")).then((res) => {
console.log("init", res)
if (res) {
for (let i = 0; i < res.length; i++) {
const item = res[i];
that.ids.push(`${item.id}`)
fileList.value.push({
name: item.name,
url: item.url,
size: item.size,
// 'ready' | 'uploading' | 'success' | 'fail'
status: "success",
response: item,
uid: item.id
})
}
}
})
}
},
{immediate: true, deep: true}
)
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,324 @@
<template>
<div class="upload-file w-full">
<el-upload
ref="uploadRef"
:file-list="fileList"
:action="'#'"
:auto-upload="autoUpload"
:before-upload="beforeUpload"
:drag="drag"
:http-request="httpRequest"
:limit="props.limit"
:multiple="props.limit > 1"
:on-error="excelUploadError"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-success="handleFileSuccess"
:show-file-list="true"
:accept="accept"
:disabled="disabled || fileList.length >= props.limit"
class="upload-file-uploader"
name="file"
>
<el-button v-if="!disabled" type="primary" :disabled="disabled || fileList.length >= props.limit">
<Icon icon="ep:upload-filled"/>
{{btnText}}
<span>
({{fileList.length}}/{{props.limit}})
</span>
</el-button>
<template v-if="isShowTip && !disabled" #tip>
<div style="font-size: 0.8rem">
{{ t(`common.fileUploadMaxSize`) }} <b style="color: #f56c6c">{{ fileSize }}MB</b>
</div>
<div style="font-size: 0.8rem">
{{ t(`common.fileUploadFormat`) }} <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</div>
</template>
<template #file="row">
<div class="flex items-center">
<div>
<!-- 'ready' | 'uploading' | 'success' | 'fail'-->
<div v-if="row.file.status === 'ready'">
<el-tag type="info">
<Icon icon="ep:clock"/>
</el-tag>
</div>
<div v-if="row.file.status === 'uploading'">
<el-tag type="warning" effect="dark">
<Icon icon="ep:loading"/>
</el-tag>
</div>
<div v-if="row.file.status === 'success'">
<el-tag type="success" effect="dark">
<Icon icon="ep:success-filled"/>
</el-tag>
</div>
<div v-if="row.file.status === 'fail'" style="color: #ff0000">
<el-tag type="danger" effect="dark">
<Icon icon="ep:warning-filled"/>
</el-tag>
{{ row.file.error }}
</div>
</div>
<el-tooltip
class="box-item"
effect="dark"
:content="row.file.name"
placement="top-start"
>
<div class="ml-2 line-clamp-1">{{ row.file.name }}</div>
</el-tooltip>
<div class="flex">
<div class="ml-1">
<el-link
:href="row.file.url"
:underline="false"
download
target="_blank"
type="primary"
>
<Icon icon="ep:download" :size="24"/>
</el-link>
</div>
<div class="ml-1" v-if="!disabled">
<el-button link type="primary" @click="reName(row.file)">
<Icon icon="ep:edit" :size="18"/>
</el-button>
</div>
<div class="ml-1" v-if="!disabled">
<el-button link type="danger" @click="handleRemove(row.file)">
<Icon icon="ep:delete" :size="18"/>
</el-button>
</div>
</div>
</div>
</template>
</el-upload>
</div>
</template>
<script lang="ts" setup>
// @ts-nocheck
import {propTypes} from '@/utils/propTypes'
import type {UploadInstance, UploadProps, UploadRawFile, UploadUserFile} from 'element-plus'
import {useUpload} from '@/components/UploadFile/src/useUploadlPlus'
import {UploadFile} from 'element-plus/es/components/upload/src/upload'
import {infoByIds, renameById} from "@/api/infra/file";
import {useMessage} from "@/hooks/web/useMessage";
import {useI18n} from "@/hooks/web/useI18n";
const {t} = useI18n()
//
defineOptions({name: 'AccessoryUpload'})
const message = useMessage() //
const emit = defineEmits(['update:modelValue'])
const text = t('fileUploadBtnText');
const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // , ['png', 'jpg', 'jpeg']
fileSize: propTypes.number.def(5), // (MB)
limit: propTypes.number.def(5), //
autoUpload: propTypes.bool.def(true), //
drag: propTypes.bool.def(false), //
isShowTip: propTypes.bool.def(true), //
disabled: propTypes.bool.def(false), // ==> false
buttonText: propTypes.string.def(''),
})
const btnText = computed(() => {
if(props.buttonText){
return props.buttonText
}
return t('common.fileUploadBtnText')
})
const accept = computed(() => {
let accept = ''
props.fileType.forEach((type: string) => {
accept += `.${type},`
})
return accept.slice(0, -1)
})
// ========== ==========
const uploadRef = ref<UploadInstance>()
const fileList = ref<UploadUserFile[]>([])
const uploadNumber = ref<number>(0)
const that = reactive({
ids: [],
})
const {httpRequest} = useUpload()
//
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) {
message.error(`${t("common.fileUploadMaxCountTips")}${props.limit}`)
return false
}
let fileExtension = ''
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
}
const isImg = props.fileType.some((type: string) => {
if (file.type.indexOf(type) > -1) return true
return !!(fileExtension && fileExtension.indexOf(type) > -1)
})
const isLimit = file.size < props.fileSize * 1024 * 1024
if (!isImg) {
message.error(`${t("common.fileUploadFormatErrorTips")}${props.fileType.join('/')}`)
return false
}
if (!isLimit) {
message.error(`${t("common.fileUploadMaxSizeTips")}${props.fileSize}`)
return false
}
message.success(t("common.fileUploadingTips"))
uploadNumber.value++
}
//
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
console.log("上传成功", res)
const item = res.data;
fileList.value.push({
name: item.name,
url: item.url,
size: item.size,
// 'ready' | 'uploading' | 'success' | 'fail'
status: "success",
response: item,
uid: item.id
})
that.ids.push(`${res.data.id}`)
emitUpdateModelValue()
}
//
const handleExceed: UploadProps['onExceed'] = (): void => {
message.error(`${t('common.fileUploadMaxCountTips')}${props.limit}`)
}
//
const excelUploadError: UploadProps['onError'] = (): void => {
message.error(t('common.fileUploadFailedTips'))
}
//
const handleRemove = (file: UploadFile) => {
const index = fileList.value.map((f) => f.name).indexOf(file.name)
if (index > -1) {
useMessage().delConfirm().then((res) => {
if (res) {
fileList.value.splice(index, 1)
emitUpdateModelValue()
}
})
}
}
const reName = (file: UploadFile) => {
console.log(file)
if (file.status == 'success') {
//
const name = file.name.substring(0, file.name.lastIndexOf('.'))
const options = {inputVal: name, tip: t('common.fileUploadInputFileName')};
useMessage().submit(options).then((res) => {
if (res) {
renameById(`${file.uid}`, res.value).then((res2) => {
message.success(t('common.success'))
fileList.value.forEach((item, index) => {
if (`${item.uid}` === `${file.uid}`) {
fileList.value[index].name = res2.name
}
})
})
}
})
} else {
useMessage().warning(t('common.fileUploadNotUploadTips'));
}
}
const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
console.log(uploadFile)
}
//
watch(
() => props.modelValue,
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix
return
}
const arr = `${val}`.split(",");
const query = [];
for (let i = 0; i < arr.length; i++) {
const item = `${arr[i]}`
if(!that.ids.includes(item)){
query.push(item)
}
}
if(query.length > 0){
infoByIds(query.join(",")).then((res) => {
console.log("init", res)
if (res) {
for (let i = 0; i < res.length; i++) {
const item = res[i];
that.ids.push(`${item.id}`)
fileList.value.push({
name: item.name,
url: item.url,
size: item.size,
// 'ready' | 'uploading' | 'success' | 'fail'
status: "success",
response: item,
uid: item.id
})
}
}
})
}
},
{immediate: true, deep: true}
)
//
const emitUpdateModelValue = () => {
// 1
let result: string | string[] = fileList.value.map((file) => `${file.uid}`)
console.log("result",result,fileList.value)
result = result.join(',')
emit('update:modelValue', result)
}
</script>
<style lang="scss" scoped>
.upload-file-uploader {
margin-bottom: 5px;
}
:deep(.upload-file-list .el-upload-list__item) {
position: relative;
margin-bottom: 10px;
line-height: 2;
border: 1px solid #e4e7ed;
}
:deep(.el-upload-list__item-file-name) {
max-width: 250px;
}
:deep(.upload-file-list .ele-upload-list__item-content) {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
:deep(.ele-upload-list__item-content-action .el-link) {
margin-right: 10px;
}
</style>

View File

@ -0,0 +1,100 @@
import * as FileApi from '@/api/infra/file'
import CryptoJS from 'crypto-js'
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
import axios from 'axios'
export const useUpload = () => {
// 后端上传地址
const uploadUrl = import.meta.env.VITE_UPLOAD_URL
// 是否使用前端直连上传
const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE
// 重写ElUpload上传方法
const httpRequest = async (options: UploadRequestOptions) => {
// 模式一:前端上传
if (isClientUpload) {
// 1.1 生成文件名称
const fileName = await generateFileName(options.file)
// 1.2 获取文件预签名地址
const presignedInfo = await FileApi.getFilePresignedUrl(fileName)
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传Minio 不支持)
return axios.put(presignedInfo.uploadUrl, options.file, {
headers: {
'Content-Type': options.file.type,
}
}).then(() => {
// 1.4. 记录文件信息到后端(异步)
createFile(presignedInfo, fileName, options.file)
// 通知成功,数据格式保持与后端上传的返回结果一致
return { data: presignedInfo.url , filename: options.file.name}
})
} else {
// 模式二:后端上传
// 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
return new Promise((resolve, reject) => {
FileApi.updateFilePlus({ file: options.file })
.then((res) => {
if (res.code === 0) {
resolve({
...res,
filename: options.file.name
})
} else {
reject(res)
}
})
.catch((res) => {
reject(res)
})
})
}
}
return {
uploadUrl,
httpRequest
}
}
/**
*
* @param vo
* @param name
* @param file
*/
function createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: UploadRawFile) {
const fileVo = {
configId: vo.configId,
url: vo.url,
path: name,
name: file.name,
type: file.type,
size: file.size
}
FileApi.createFile(fileVo)
return fileVo
}
/**
* 使SHA256
* @param file
*/
async function generateFileName(file: UploadRawFile) {
// 读取文件内容
const data = await file.arrayBuffer()
const wordArray = CryptoJS.lib.WordArray.create(data)
// 计算SHA256
const sha256 = CryptoJS.SHA256(wordArray).toString()
// 拼接后缀
const ext = file.name.substring(file.name.lastIndexOf('.'))
return `${sha256}${ext}`
}
/**
*
*/
enum UPLOAD_TYPE {
// 客户端直接上传只支持S3服务
CLIENT = 'client',
// 客户端发送到后端上传
SERVER = 'server'
}

View File

@ -1,7 +1,8 @@
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import { useI18n } from './useI18n'
import {ElMessage, ElMessageBox, ElNotification} from 'element-plus'
import {useI18n} from './useI18n'
export const useMessage = () => {
const { t } = useI18n()
const {t} = useI18n()
return {
// 消息提示
info(content: string) {
@ -25,15 +26,15 @@ export const useMessage = () => {
},
// 错误提示
alertError(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'error' })
ElMessageBox.alert(content, t('common.confirmTitle'), {type: 'error'})
},
// 成功提示
alertSuccess(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'success' })
ElMessageBox.alert(content, t('common.confirmTitle'), {type: 'success'})
},
// 警告提示
alertWarning(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'warning' })
ElMessageBox.alert(content, t('common.confirmTitle'), {type: 'warning'})
},
// 通知提示
notify(content: string) {
@ -90,6 +91,29 @@ export const useMessage = () => {
cancelButtonText: t('common.cancel'),
type: 'warning'
})
},
submit({ inputVal, tip, content = '',inputValidator = undefined }: {
inputVal: string,
tip: string,
content?: string,
inputValidator?: Function | undefined
}) {
return ElMessageBox.prompt(content, tip, {
inputValue: inputVal,
inputPlaceholder: t('common.inputText'),
inputValidator: (value) => {
if (inputValidator) {
return inputValidator(value);
} else {
if (!value) {
return t('common.inputText')
}
}
return true
},
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
})
}
}
}

View File

@ -55,7 +55,22 @@ export default {
copy: 'Copy',
copySuccess: 'Copy Success',
copyError: 'Copy Error',
keywordPlaceholder: 'Please enter a keyword'
keywordPlaceholder: 'Please enter a keyword',
fileUploadMaxSize: "Maximum size per file",
fileUploadFormat: "Supported formats",
fileUploadBtnText: "Upload attachments",
fileUploadMaxCountTips: "The number of uploaded files cannot exceed",
fileUploadMaxSizeTips: "The size of the uploaded file cannot exceed",
fileUploadFormatErrorTips: "The file format is incorrect. Please upload",
fileUploadingTips: "The file is being uploaded. Please wait...",
fileUploadFailedTips: "The data upload failed. Please upload again!",
fileUploadInputFileName: "Please enter the file name",
fileUploadNotUploadTips: "Please upload the file first",
fileDownloadNameLabel: "File name",
fileDownloadSizeLabel: "File size",
fileDownloadOptionsLabel: "Operations",
fileDownloadTitle: "File list",
fileDownloadBtnText: "Download working papers"
},
lock: {
lockScreen: 'Lock screen',
@ -674,15 +689,19 @@ export default {
"viewOrder": "viewOrder",
"backHome": "BackHome",
"downloadSuccess": "downloadSuccess",
"downloadFile": "Download of working papers",
"labelAccessoryFile": "Attachment"
},
"productDialogList": {
"title": "Product List",
"colLabelCode": "Product Code",
"customerGroupId": 'Customer group',
"customerGroupName": 'Customer group',
"colLabelType": "Product Type",
"colLabelCover": "Cover",
"colLabelName": "Product Name",
"colLabelRemark": "Remarks",
"colLabelDetails": "Details Description"
"colLabelDetails": "Details Description",
"colLabelFileIds": 'Working paper attachments'
}
}

View File

@ -55,7 +55,22 @@ export default {
copy: '复制',
copySuccess: '复制成功',
copyError: '复制失败',
keywordPlaceholder: '请输入关键字'
keywordPlaceholder: '请输入关键字',
fileUploadMaxSize: "单文件最大",
fileUploadFormat: "支持格式",
fileUploadBtnText: "附件上传",
fileUploadMaxCountTips: "上传文件数量不能超过",
fileUploadMaxSizeTips: "上传文件大小不能超过",
fileUploadFormatErrorTips: "文件格式不正确, 请上传",
fileUploadingTips: "正在上传文件,请稍候...",
fileUploadFailedTips: "数据失败上传失败,请您重新上传!",
fileUploadInputFileName: "请输入文件名称",
fileUploadNotUploadTips: "请先上传文件",
fileDownloadNameLabel: "文件名称",
fileDownloadSizeLabel: "文件大小",
fileDownloadOptionsLabel: "操作",
fileDownloadTitle: "文件列表",
fileDownloadBtnText: "底稿下载",
},
lock: {
lockScreen: '锁定屏幕',
@ -670,14 +685,19 @@ export default {
viewOrder: "查看订单",
backHome: "返回首页",
downloadSuccess: "下载成功",
downloadFile: "底稿下载",
labelAccessoryFile: "附件"
},
productDialogList:{
title: '产品列表',
colLabelCode: '产品编码',
customerGroupId: '客户组别',
customerGroupName: '客户组别',
colLabelType: '产品类型',
colLabelCover: '封面',
colLabelName: '产品名称',
colLabelRemark: '备注',
colLabelDetails: '详情描述',
colLabelFileIds: '底稿附件',
},
}

View File

@ -19,7 +19,19 @@
<el-table-column prop="productCode" :label="t('createOrder.labelColProductCode')"/>
<el-table-column prop="productName" :label="t('createOrder.labelColProductName')"/>
<el-table-column prop="producer" :label="t('createOrder.labelColProducer')"/>
<el-table-column prop="details" :label="t('createOrder.labelColProductDetails')"/>
<el-table-column prop="details" :label="t('createOrder.labelColProductDetails')">
<template #default="scope">
<el-tooltip
effect="dark"
:content="scope.row.details"
placement="top-start"
>
<div class="line-clamp-2">
{{scope.row.details}}
</div>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="cover" :label="t('createOrder.labelColProductPicture')">
<template #default="scope">
<el-image
@ -44,7 +56,6 @@
</el-table-column>
<el-table-column prop="deliveryDate" :label="t('createOrder.labelColDeliveryDate')" width="200">
<template #default="scope">
<el-date-picker
v-if="that.isBatch"
:disabledDate="(time)=> { return time.getTime() < (Date.now() + 8 * 60 * 60 * 1000); }"
@ -57,6 +68,7 @@
</template>
</el-table-column>
<el-table-column prop="orderQty" :label="t('createOrder.labelColOrderQty')"/>
</el-table>
</div>
@ -67,6 +79,7 @@
import {useEmitt} from "@/hooks/web/useEmitt";
import {PRODUCT_DEL_ROW_EVENT, STEP0_FINISH_EVENT} from "@/constants/EmitEventName";
import {formatDate} from "@/utils/formatTime";
import {copyToClip} from "@/utils";
const {t} = useI18n() //
const emit = defineEmits(['rowClick'])
@ -80,6 +93,7 @@ const that = reactive({
filterParam: {
brandId: '',
},
fileIds: '270,269,271,288',
})
const handleEvent = (data: any) => {
@ -117,7 +131,11 @@ const submit = (data) => {
addRow(data[i])
}
}
const copyData = (str: string) => {
copyToClip(str, () => {
useMessage.success(t('common.copySuccess'));
})
}
const addRow = (row) => {
for (let i = 0; i < that.tableData.length; i++) {
if (that.tableData[i].productId === row.id) {
@ -125,6 +143,7 @@ const addRow = (row) => {
return
}
}
if (!row.deliveryDate) {
row.deliveryDate = Date.now() + (24 * 60 * 60 * 1000)
}
@ -204,7 +223,6 @@ defineExpose({
<style lang="scss" scoped>
:deep(.el-table__body tr.current-row>td) {
background-color: rgba(49, 106, 197, 0.81) !important; //
color: #ffffff;

View File

@ -1,12 +1,16 @@
<template>
<div>
<div>
<div class="flex">
<el-button
type="primary"
v-show="that.templateType !== '2' && that.tableData.length > 0"
@click="addSku">{{ t('createOrder.btnAddSku') }}
</el-button>
<div v-if="that.productInfo.fileIds" class="ml-2">
<AccessoryDownload
v-model="that.productInfo.fileIds"/>
</div>
</div>
<div class="flex flex-wrap" v-if="that.templateType === '1'">
<template v-for="(item,index) in that.tableData" :key="item.key">
@ -17,7 +21,6 @@
ref="productItem1Ref"
@del-item="delItem"
:badge="`${index+1}`"
v-model:product-info="that.tableData[index]"/>
</template>
</div>

View File

@ -223,6 +223,17 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item :label="t('createOrder.labelAccessoryFile')" prop="remarks">
<AccessoryUpload
:fileType="['zip', 'rar','xls', 'doc', 'xlsx', 'docx','pdf','png','jpg', 'jpeg']"
:limit="10"
v-model="formData.accessoryFileIds" />
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-collapse-item>
@ -544,6 +555,7 @@ const formData = ref({
saleOrderEntry: [],
orderFollowerUser: '',
rejectReason: '',
accessoryFileIds: '',
})
const isEditState = computed(() => {
return route.query.id != undefined && route.query.type == undefined

View File

@ -0,0 +1,38 @@
-- 稿件模板 底稿附件id
ALTER TABLE oms_draft_design_data
ADD COLUMN `file_ids` varchar(1024) DEFAULT NULL COMMENT '底稿附件多个 infra_file 表 id' AFTER `cover`;
-- 产品信息 添加客户组
ALTER TABLE oms_product_info
ADD COLUMN `customer_group_id` varchar(1024) DEFAULT NULL COMMENT 'oms_customer_group 表 id';
-- 销售订单 添加附件上传
ALTER TABLE oms_saleorder
ADD COLUMN `accessory_file_ids` varchar(1024) DEFAULT NULL COMMENT '下单附件多个 infra_file 表 id';
-- 客户组别
DROP TABLE IF EXISTS oms_customer_group;
CREATE TABLE oms_customer_group(
id BIGINT(19) NOT NULL AUTO_INCREMENT COMMENT 'id' ,
code VARCHAR(64) COMMENT '编码' ,
name VARCHAR(512) NOT NULL COMMENT '名称' ,
sort INT DEFAULT 0 COMMENT '排序号' ,
remark VARCHAR(512) COMMENT '备注' ,
creator VARCHAR(64) COMMENT '创建者' ,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ,
updater VARCHAR(64) COMMENT '更新者' ,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间' ,
deleted BIT(1) NOT NULL DEFAULT 0 COMMENT '是否删除' ,
tenant_id BIGINT(19) NOT NULL COMMENT '租户编号' ,
PRIMARY KEY (id)
) COMMENT = '客户组别 ';
-- 菜单sql
INSERT INTO `system_menu`(`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2855, '客户组别 导出', 'oms:customer-group:export', 3, 5, 2850, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-04 09:56:36', '', '2025-03-04 09:56:36', b'0');
INSERT INTO `system_menu`(`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2854, '客户组别 删除', 'oms:customer-group:delete', 3, 4, 2850, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-04 09:56:35', '', '2025-03-04 09:56:35', b'0');
INSERT INTO `system_menu`(`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2852, '客户组别 创建', 'oms:customer-group:create', 3, 2, 2850, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-04 09:56:34', '', '2025-03-04 09:56:34', b'0');
INSERT INTO `system_menu`(`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2853, '客户组别 更新', 'oms:customer-group:update', 3, 3, 2850, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-04 09:56:34', '', '2025-03-04 09:56:34', b'0');
INSERT INTO `system_menu`(`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2851, '客户组别 查询', 'oms:customer-group:query', 3, 1, 2850, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-04 09:56:32', '', '2025-03-04 09:56:32', b'0');
INSERT INTO `system_menu`(`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2850, '客户组别 管理', '', 2, 0, 2758, 'customer-group', '', 'oms/customergroup/index', 'CustomerGroup', 0, b'1', b'1', b'1', '', '2025-03-04 09:56:30', '', '2025-03-04 09:56:30', b'0');