feat(quotation): 添加阿里云翻译功能并完善机型配置管理

- 新增AliYunTranslate实现类,集成阿里云翻译服务并支持Redis缓存
- 添加ModelConfigItemVO数据传输对象用于配置项树形结构展示
- 扩展ModelConfigController控制器,新增部件或系统树形结构查询接口
- 实现getModelConfigItemTree方法,支持按配置ID和语言编码获取树形结构
- 完善ModelConfigVO对象,增加机型ID、推荐标识、图片地址等字段
- 重构QuotationModelConfig实体,调整字段类型并添加更新策略注解
- 移除QuotationModelConfigItem中的多语言字段,统一通过关联表管理
- 更新QuotationModelConfigItemLanguage实体,添加配置ID字段和更新策略
- 实现IQuotationModelConfigItemService接口的getVOListByConfigId方法
- 优化QuotationModelConfigMapper.xml查询语句,改进搜索逻辑和排序方式
- 添加ModelConfigItemAddRequest请求对象,支持部件或系统的添加操作
- 集成translate依赖,添加阿里云翻译相关Maven依赖包
This commit is contained in:
曹鹏飞 2026-02-16 16:13:15 +08:00
parent c74f0f5f17
commit 23d74b85e6
15 changed files with 525 additions and 22 deletions

View File

@ -18,10 +18,10 @@ public class ModelConfigSearchRequest extends PageRequest {
/**
* 系列批次号
*/
private String seriesNumber;
private Long seriesNumber;
/**
* 类型批次号
*/
private String typeNumber;
private Long typeNumber;
}

View File

@ -2,6 +2,8 @@ package com.nflg.mobilebroken.common.pojo.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ModelConfigVO {
@ -20,8 +22,48 @@ public class ModelConfigVO {
*/
private String typeName;
/**
* 机型id
*/
private Long modelId;
/**
* 机型编号
*/
private String modelNo;
/**
* 是否新品
*/
private Boolean recommend;
/**
* 图片地址
*/
private String image;
/**
* 配置版本号
*/
private String configVersion;
/**
* 创建人
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,55 @@
package com.nflg.mobilebroken.common.pojo.vo.quotation;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@Accessors(chain = true)
public class ModelConfigItemVO {
private Long id;
/**
* 上级id
*/
private Long parentId;
/**
* 多语言项id
*/
private Long languageItemId;
/**
* 部件或系统
*/
private String partName;
/**
* 参数/描述
*/
private String partRemark;
/**
* 类别0可选配置1标准配置
*/
private Integer type;
/**
* 选配类别0新增可选1替换可选
*/
private Integer optionalType;
/**
* 是否启用
*/
private Boolean enable;
/**
* 图片
*/
private String imageUrl;
private List<ModelConfigItemVO> children;
}

View File

@ -98,6 +98,22 @@
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alimt20181012</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.3.9</version>
<exclusions>
<exclusion>
<artifactId>dom4j</artifactId>
<groupId>org.dom4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>

View File

@ -1,33 +1,187 @@
package com.nflg.mobilebroken.quotation.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import com.nflg.mobilebroken.common.constant.Constant;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.PageData;
import com.nflg.mobilebroken.common.pojo.request.ModelConfigSearchRequest;
import com.nflg.mobilebroken.common.pojo.vo.LanguageVO;
import com.nflg.mobilebroken.common.pojo.vo.ModelConfigVO;
import com.nflg.mobilebroken.common.pojo.vo.quotation.ModelConfigItemVO;
import com.nflg.mobilebroken.common.util.AdminUserUtil;
import com.nflg.mobilebroken.common.util.VUtils;
import com.nflg.mobilebroken.quotation.pojo.request.ModelConfigItemAddRequest;
import com.nflg.mobilebroken.repository.entity.Language;
import com.nflg.mobilebroken.repository.entity.QuotationModelConfig;
import com.nflg.mobilebroken.repository.entity.QuotationModelConfigItem;
import com.nflg.mobilebroken.repository.entity.QuotationModelConfigItemLanguage;
import com.nflg.mobilebroken.repository.service.ILanguageService;
import com.nflg.mobilebroken.repository.service.IQuotationModelConfigItemLanguageService;
import com.nflg.mobilebroken.repository.service.IQuotationModelConfigItemService;
import com.nflg.mobilebroken.repository.service.IQuotationModelConfigService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.nflg.mobilebroken.starter.service.ITranslate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* 机型配置
*/
@RestController
@RequestMapping("/model/config")
public class ModelConfigController extends ControllerBase{
public class ModelConfigController extends ControllerBase {
@Resource
private IQuotationModelConfigService modelConfigService;
@Resource
private IQuotationModelConfigItemService modelConfigItemService;
@Resource
private IQuotationModelConfigItemLanguageService modelConfigItemLanguageService;
@Resource
private ILanguageService languageService;
@Resource
private ITranslate translate;
/**
* 获取机型配置列表
*/
@PostMapping("/search")
public ApiResult<PageData<ModelConfigVO>> search(@Valid @RequestBody ModelConfigSearchRequest request){
public ApiResult<PageData<ModelConfigVO>> search(@Valid @RequestBody ModelConfigSearchRequest request) {
return ApiResult.success(modelConfigService.search(request));
}
/**
* 获取部件或系统树形结构
* @param configId 机型配置ID
* @param languageCode 语言编码
*/
@GetMapping("/item/tree")
public ApiResult<List<ModelConfigItemVO>> getModelConfigItemTree(@RequestParam Long configId, @RequestParam String languageCode) {
List<ModelConfigItemVO> vos = modelConfigItemService.getVOListByConfigId(configId, languageCode);
if (CollectionUtil.isEmpty(vos)) {
return ApiResult.success(Collections.emptyList());
}
vos.stream().filter(vo -> vo.getParentId() == 0L)
.sorted(Comparator.comparing(ModelConfigItemVO::getId))
.forEach(vo -> vo.setChildren(vos.stream()
.filter(i -> i.getParentId().equals(vo.getId()))
.map(i -> new ModelConfigItemVO()
.setId(i.getId())
.setPartName(i.getPartName())
.setPartRemark(i.getPartRemark())
.setImageUrl(i.getImageUrl())
.setEnable(i.getEnable())
.setType(i.getType())
.setOptionalType(i.getOptionalType())
).sorted(Comparator.comparing(ModelConfigItemVO::getId))
.collect(Collectors.toList())
));
return ApiResult.success(vos);
}
/**
* 添加部件或系统
*/
@PostMapping("/item/add")
@Transactional
public ApiResult<Long> addItem(@Valid @RequestBody ModelConfigItemAddRequest request) {
List<Language> languages = languageService.getLanguages();
Language cn = languages.stream().filter(it -> it.getCode().equals(Constant.DEFAULT_LANGUAGE_CODE)).findFirst().get();
if (Objects.nonNull(request.getConfigId())) {
QuotationModelConfig config = modelConfigService.getById(request.getConfigId());
VUtils.trueThrowBusinessError(Objects.isNull(config)).throwMessage("无效的机型配置");
if (config.getConfigStatus() != 0) {
List<QuotationModelConfigItem> items = modelConfigItemService.lambdaQuery()
.eq(QuotationModelConfigItem::getConfigId, request.getConfigId())
.list();
List<QuotationModelConfigItemLanguage> itemLanguages = modelConfigItemLanguageService.lambdaQuery()
.eq(QuotationModelConfigItemLanguage::getConfigId, request.getConfigId())
.list();
config.setId(IdUtil.getSnowflakeNextId());
request.setConfigId(config.getId());
config.setCreateById(AdminUserUtil.getUserId());
config.setCreateBy(AdminUserUtil.getUserName());
config.setCreateTime(LocalDateTime.now());
config.setUpdateById(null);
config.setUpdateBy(null);
config.setUpdateTime(null);
modelConfigService.save(config);
if (CollectionUtil.isNotEmpty(items)) {
items.forEach(it -> {
it.setId(IdUtil.getSnowflakeNextId());
it.setConfigId(config.getId());
});
modelConfigItemService.saveBatch(items);
}
if (CollectionUtil.isNotEmpty(itemLanguages)) {
itemLanguages.forEach(it -> {
it.setId(IdUtil.getSnowflakeNextId());
it.setConfigId(config.getId());
it.setCreateById(AdminUserUtil.getUserId());
it.setCreateBy(AdminUserUtil.getUserName());
it.setCreateTime(LocalDateTime.now());
it.setUpdateById(null);
it.setUpdateBy(null);
it.setUpdateTime(null);
});
modelConfigItemLanguageService.saveBatch(itemLanguages);
}
}VUtils.trueThrowBusinessError(modelConfigItemLanguageService.lambdaQuery()
.eq(QuotationModelConfigItemLanguage::getConfigId, request.getConfigId())
.eq(QuotationModelConfigItemLanguage::getLanguageId, cn.getId())
.eq(QuotationModelConfigItemLanguage::getPartName, request.getPartName())
.exists())
.throwMessage("已存在该名称的部件或系统");
} else {
QuotationModelConfig config = new QuotationModelConfig()
.setModelId(request.getModelId())
.setCreateById(AdminUserUtil.getUserId())
.setCreateBy(AdminUserUtil.getUserName())
.setCreateTime(LocalDateTime.now());
modelConfigService.save(config);
}
QuotationModelConfigItem item=new QuotationModelConfigItem()
.setId(IdUtil.getSnowflakeNextId())
.setConfigId(request.getConfigId())
.setType(request.getType())
.setEnable(request.getEnable())
.setOptionalType(request.getOptionalType())
.setParentId(request.getParentId());
List<QuotationModelConfigItemLanguage> itemLanguages = new ArrayList<>();
QuotationModelConfigItemLanguage cnItem=new QuotationModelConfigItemLanguage()
.setConfigId(request.getConfigId())
.setConfigItemId(item.getId())
.setLanguageId(cn.getId())
.setPartName(request.getPartName())
.setPartRemark(request.getPartRemark())
.setCreateById(AdminUserUtil.getUserId())
.setCreateBy(AdminUserUtil.getUserName())
.setCreateTime(LocalDateTime.now());
itemLanguages.add(cnItem);
languages.remove(cn);
languages.forEach(language -> {
itemLanguages.add(new QuotationModelConfigItemLanguage()
.setConfigId(request.getConfigId())
.setConfigItemId(item.getId())
.setLanguageId(cn.getId())
.setPartName(translate.translateWord(request.getPartName(), language.getTranslateCode()))
.setPartRemark(translate.translateWord(request.getPartRemark(), language.getTranslateCode()))
.setCreateById(AdminUserUtil.getUserId())
.setCreateBy(AdminUserUtil.getUserName())
.setCreateTime(LocalDateTime.now())
);
});
modelConfigItemLanguageService.saveBatch(itemLanguages);
return ApiResult.success(request.getConfigId());
}
}

View File

@ -0,0 +1,61 @@
package com.nflg.mobilebroken.quotation.pojo.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class ModelConfigItemAddRequest {
/**
* 机型id
*/
@NotNull
private Long modelId;
/**
* 配置id
*/
private Long configId;
/**
* 上级id
*/
@NotNull
private Long parentId;
/**
* 部件或系统
*/
@NotBlank
private String partName;
/**
* 参数/描述
*/
private String partRemark;
/**
* 类别0可选配置1标准配置
*/
@NotNull
private Integer type;
/**
* 选配类别0新增可选1替换可选
*/
@NotNull
private Integer optionalType;
/**
* 是否启用
*/
@NotNull
private Boolean enable;
/**
* 图片
*/
private String imageUrl;
}

View File

@ -0,0 +1,115 @@
package com.nflg.mobilebroken.quotation.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.aliyun.alimt20181012.models.TranslateGeneralResponse;
import com.aliyun.credentials.Client;
import com.aliyun.credentials.models.Config;
import com.aliyun.tea.TeaException;
import com.nflg.mobilebroken.common.constant.STATE;
import com.nflg.mobilebroken.common.exception.NflgException;
import com.nflg.mobilebroken.common.util.VUtils;
import com.nflg.mobilebroken.starter.service.ITranslate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Primary
@Service
@ConditionalOnProperty(name = "translate.platform", havingValue = "aliyun")
@Slf4j
public class AliYunTranslate implements ITranslate {
@Value("${translate.aliyun.accessKeyId}")
private String accessKeyId;
@Value("${translate.aliyun.accessKeySecret}")
private String accessKeySecret;
@Value("${translate.aliyun.endpoint}")
private String endpoint;
private com.aliyun.alimt20181012.Client client;
@Resource
private StringRedisTemplate stringRedisTemplate;
@PostConstruct
public void init() throws Exception {
Config credentialConfig = new Config();
credentialConfig.type="access_key";
credentialConfig.accessKeyId=accessKeyId;
credentialConfig.accessKeySecret=accessKeySecret;
Client credentialClient = new Client(credentialConfig);
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
.setCredential(credentialClient);
// Endpoint 请参考 https://api.aliyun.com/product/alimt
config.endpoint = endpoint;
client=new com.aliyun.alimt20181012.Client(config);
}
@Override
public String translateWord(String text, String sourceLanguage, String targetLanguage, String formatType) {
if (isOnlyDigitsAndSymbols(text)){
return text;
}
com.aliyun.alimt20181012.models.TranslateGeneralRequest request = new com.aliyun.alimt20181012.models.TranslateGeneralRequest()
.setTargetLanguage(targetLanguage)
.setSourceLanguage(sourceLanguage)
.setFormatType(formatType)
.setSourceText(text);
log.info("翻译,请求参数:{}", JSONUtil.toJsonStr(request));
String key = "translate:" + SecureUtil.md5(text) + ":" + targetLanguage;
String result = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(result)) {
log.info("翻译,响应,从缓存读取,翻译结果:{}", result);
return result;
}
try {
// 复制代码运行请自行打印 API 的返回值
TranslateGeneralResponse response = client.translateGeneralWithOptions(request, new com.aliyun.teautil.models.RuntimeOptions());
VUtils.trueThrowBusinessError(response.statusCode != 200).throwMessage("翻译失败:" + response.getBody().getMessage());
log.info("翻译,响应,识别到的原始语言:{},字数:{},翻译结果:{}"
, response.getBody().getData().getDetectedLanguage()
, response.getBody().getData().getWordCount()
, JSONUtil.toJsonStr(response.getBody().getData().getTranslated()));
result = response.body.getData().getTranslated();
stringRedisTemplate.opsForValue().set(key, result, 30, TimeUnit.DAYS);
return result;
} catch (Exception ex) {
TeaException error = new TeaException(ex.getMessage(), ex);
// 此处仅做打印展示请谨慎对待异常处理在工程项目中切勿直接忽略异常
// 错误 message
log.error("翻译异常:{}", error.getMessage());
// 诊断地址
log.error("诊断地址:{}", error.getData().get("Recommend"));
throw new NflgException(STATE.BusinessError, "翻译失败:" + error.getMessage());
}
}
@Override
public String translateWord(String text, String targetLanguage, String formatType) {
return translateWord(text, "auto", targetLanguage, formatType);
}
@Override
public String translateWord(String text, String targetLanguage) {
return translateWord(text, targetLanguage, "text");
}
private boolean isOnlyDigitsAndSymbols(String content) {
if (StrUtil.isBlank(content)) {
return true;
}
// 正则只允许数字符号不含中英文字母
return content.matches("[0-9\\s\\p{Punct}]+");
}
}

View File

@ -1,5 +1,7 @@
package com.nflg.mobilebroken.repository.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
@ -28,7 +30,7 @@ public class QuotationModelConfig implements Serializable {
/**
* 机型表batch_number
*/
private Integer modelId;
private Long modelId;
/**
* 年份
@ -63,15 +65,18 @@ public class QuotationModelConfig implements Serializable {
/**
* 修改人编号
*/
@TableField(value = "update_by_id",updateStrategy = FieldStrategy.ALWAYS)
private Integer updateById;
/**
* 更新人
*/
@TableField(value = "update_by",updateStrategy = FieldStrategy.ALWAYS)
private String updateBy;
/**
* 更新时间
*/
@TableField(value = "update_time",updateStrategy = FieldStrategy.ALWAYS)
private LocalDateTime updateTime;
}

View File

@ -34,15 +34,15 @@ public class QuotationModelConfigItem implements Serializable {
*/
private Long parentId;
/**
* 部件或系统
*/
private String partName;
// /**
// * 部件或系统
// */
// private String partName;
/**
* 参数/描述
*/
private String partRemark;
// /**
// * 参数/描述
// */
// private String partRemark;
/**
* 类别0可选配置1标准配置
@ -59,8 +59,8 @@ public class QuotationModelConfigItem implements Serializable {
*/
private Boolean enable;
/**
* 图片
*/
private String imageUrl;
// /**
// * 图片
// */
// private String imageUrl;
}

View File

@ -1,5 +1,7 @@
package com.nflg.mobilebroken.repository.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
@ -25,6 +27,11 @@ public class QuotationModelConfigItemLanguage implements Serializable {
private Long id;
/**
* 配置id
*/
private Long configId;
/**
* 配置项id
*/
@ -63,15 +70,18 @@ public class QuotationModelConfigItemLanguage implements Serializable {
/**
* 修改人编号
*/
@TableField(value = "update_by_id",updateStrategy = FieldStrategy.ALWAYS)
private Integer updateById;
/**
* 更新人
*/
@TableField(value = "update_by",updateStrategy = FieldStrategy.ALWAYS)
private String updateBy;
/**
* 更新时间
*/
@TableField(value = "update_time",updateStrategy = FieldStrategy.ALWAYS)
private LocalDateTime updateTime;
}

View File

@ -1,8 +1,11 @@
package com.nflg.mobilebroken.repository.mapper;
import com.nflg.mobilebroken.common.pojo.vo.quotation.ModelConfigItemVO;
import com.nflg.mobilebroken.repository.entity.QuotationModelConfigItem;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* <p>
* 报价-机型配置-子项 Mapper 接口
@ -13,4 +16,5 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface QuotationModelConfigItemMapper extends BaseMapper<QuotationModelConfigItem> {
List<ModelConfigItemVO> getVOListByConfigId(Long configId, String languageCode);
}

View File

@ -1,8 +1,11 @@
package com.nflg.mobilebroken.repository.service;
import com.nflg.mobilebroken.common.pojo.vo.quotation.ModelConfigItemVO;
import com.nflg.mobilebroken.repository.entity.QuotationModelConfigItem;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 报价-机型配置-子项 服务类
@ -13,4 +16,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/
public interface IQuotationModelConfigItemService extends IService<QuotationModelConfigItem> {
List<ModelConfigItemVO> getVOListByConfigId(Long configId, String languageCode);
}

View File

@ -1,11 +1,14 @@
package com.nflg.mobilebroken.repository.service.impl;
import com.nflg.mobilebroken.common.pojo.vo.quotation.ModelConfigItemVO;
import com.nflg.mobilebroken.repository.entity.QuotationModelConfigItem;
import com.nflg.mobilebroken.repository.mapper.QuotationModelConfigItemMapper;
import com.nflg.mobilebroken.repository.service.IQuotationModelConfigItemService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 报价-机型配置-子项 服务实现类
@ -17,4 +20,8 @@ import org.springframework.stereotype.Service;
@Service
public class QuotationModelConfigItemServiceImpl extends ServiceImpl<QuotationModelConfigItemMapper, QuotationModelConfigItem> implements IQuotationModelConfigItemService {
@Override
public List<ModelConfigItemVO> getVOListByConfigId(Long configId, String languageCode) {
return baseMapper.getVOListByConfigId(configId, languageCode);
}
}

View File

@ -2,4 +2,11 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nflg.mobilebroken.repository.mapper.QuotationModelConfigItemMapper">
<select id="getVOListByConfigId" resultType="com.nflg.mobilebroken.common.pojo.vo.quotation.ModelConfigItemVO">
SELECT mcil.id as languageItemId,mci.parent_id,mcil.part_name,mcil.part_remark,mci.*
FROM quotation_model_config_item mci
INNER JOIN quotation_model_config_item_language mcil ON mci.id=mcil.config_item_id
INNER JOIN `language` l ON l.id=mcil.language_id
WHERE mci.config_id=#{configId} AND l.code=#{languageCode}
</select>
</mapper>

View File

@ -3,6 +3,29 @@
<mapper namespace="com.nflg.mobilebroken.repository.mapper.QuotationModelConfigMapper">
<select id="search" resultType="com.nflg.mobilebroken.common.pojo.vo.ModelConfigVO">
SELECT di.`name` as 'moduleName',ps.`name` AS 'seriesName',pt.`name` as 'typeName',pm.batch_number AS 'modelId'
,pm.`no` as 'modelNo',pm.recommend,pm.image,qmc.config_version,qmc.create_by,qmc.create_time,qmc.update_by
,qmc.update_time
FROM product_model pm
LEFT JOIN product_type pt on pm.type_number=pt.batch_number AND pt.state=1
LEFT JOIN product_series ps ON pm.series_number=ps.batch_number AND ps.state=1
LEFT JOIN dictionary_item di ON di.id=pm.module_id
LEFT JOIN v_quotation_model_config qmc ON pm.batch_number=qmc.model_id
WHERE pm.state=1
<where>
<if test="request.moduleId!=null">
AND pm.module_id=#{request.moduleId}
</if>
<if test="request.seriesNumber!=null">
AND pm.series_number=#{request.seriesNumber}
</if>
<if test="request.typeNumber!=null">
AND pm.type_number=#{request.typeNumber}
</if>
<if test="request.no!=null and request.no!=''">
AND pm.`no` like concat('%', #{request.no}, '%')
</if>
</where>
order by qmc.update_time desc,pm.id
</select>
</mapper>