新增 产品信息导入,稿件下载功能
This commit is contained in:
parent
22888ef18c
commit
c2285e42e4
|
|
@ -1,8 +1,9 @@
|
|||
package cn.hangtag.module.oms.controller.admin.productinfo;
|
||||
|
||||
import cn.hangtag.framework.mybatis.build.QueryFilterInfo;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import lombok.AllArgsConstructor;
|
||||
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;
|
||||
|
|
@ -28,15 +29,17 @@ import static cn.hangtag.framework.apilog.core.enums.OperateTypeEnum.*;
|
|||
import cn.hangtag.module.oms.controller.admin.productinfo.vo.*;
|
||||
import cn.hangtag.module.oms.dal.dataobject.productinfo.ProductInfoDO;
|
||||
import cn.hangtag.module.oms.service.productinfo.ProductInfoService;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Tag(name = "管理后台 - 产品资料 ")
|
||||
@RestController
|
||||
@RequestMapping("/oms/product-info")
|
||||
@Validated
|
||||
@AllArgsConstructor
|
||||
public class ProductInfoController {
|
||||
|
||||
@Resource
|
||||
private ProductInfoService productInfoService;
|
||||
private final ProductInfoService productInfoService;
|
||||
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建产品资料 ")
|
||||
|
|
@ -88,6 +91,22 @@ public class ProductInfoController {
|
|||
return success(BeanUtils.toBean(pageResult, ProductInfoRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/excel-template")
|
||||
@Operation(summary = "产品信息导入模板")
|
||||
public void importTemplate(HttpServletResponse response) throws IOException {
|
||||
// 输出
|
||||
ExcelUtils.write(response, "产品信息导入模板.xls", "产品信息", ProductInfoExcelVO.class, null);
|
||||
}
|
||||
@PostMapping("/import")
|
||||
@Operation(summary = "导入生产制单")
|
||||
@Parameters({ @Parameter(name = "file", description = "Excel 文件", required = true),
|
||||
})
|
||||
public CommonResult<String> importExcel(@RequestParam("file") MultipartFile file) throws Exception {
|
||||
List<ProductInfoExcelVO> list = ExcelUtils.read(file, ProductInfoExcelVO.class);
|
||||
String res = productInfoService.importExcel(list);
|
||||
return success(res);
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出产品资料 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('oms:product-info:export')")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package cn.hangtag.module.oms.controller.admin.productinfo.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 产品信息导入 Excel VO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = false) // 设置 chain = false,避免数据导入有问题
|
||||
public class ProductInfoExcelVO {
|
||||
|
||||
@ExcelProperty("产品编码")
|
||||
private String code;
|
||||
|
||||
@ExcelProperty("产品名称")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("品牌名称")
|
||||
private String brandName;
|
||||
|
||||
}
|
||||
|
|
@ -78,4 +78,6 @@ public interface BrandService {
|
|||
* @return {@link Integer }
|
||||
*/
|
||||
Integer updateProductCount(Long id);
|
||||
|
||||
BrandDO getBrandByName(String brandName);
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package cn.hangtag.module.oms.service.brand;
|
|||
import cn.hangtag.framework.common.exception.ServiceException;
|
||||
import cn.hangtag.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.hangtag.framework.common.util.FuncUtil;
|
||||
import cn.hangtag.framework.common.util.cache.CacheUtils;
|
||||
import cn.hangtag.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.hangtag.module.oms.base.dal.dataobject.producttype.ProductTypeDO;
|
||||
import cn.hangtag.module.oms.dal.dataobject.customerbrand.CustomerBrandDO;
|
||||
|
|
@ -14,6 +15,8 @@ import cn.hangtag.module.oms.serialnumber.CodingRulesUtils;
|
|||
import cn.hangtag.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.hangtag.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||
import cn.hangtag.module.system.dal.mysql.permission.RoleMenuMapper;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
|
@ -159,6 +162,26 @@ public class BrandServiceImpl implements BrandService {
|
|||
}
|
||||
return convertSet(customerBrandMapper.selectListByCustomerId(customerIds), CustomerBrandDO::getBrandId);
|
||||
}
|
||||
private final FIFOCache<String, BrandDO> brandDOS = CacheUtil.newFIFOCache(100,1000*60*60*10);
|
||||
@Override
|
||||
public BrandDO getBrandByName(String brandName) {
|
||||
|
||||
if (FuncUtil.isNotEmpty(brandName)) {
|
||||
BrandDO brandDO = brandDOS.get(brandName);
|
||||
if (FuncUtil.isNotEmpty(brandDO)) {
|
||||
return brandDO;
|
||||
}
|
||||
LambdaQueryWrapper<BrandDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
lambdaQueryWrapper.eq(BrandDO::getName, brandName);
|
||||
lambdaQueryWrapper.eq(BrandDO::getDeleted, false);
|
||||
List<BrandDO> dos = brandMapper.selectList(lambdaQueryWrapper);
|
||||
if (FuncUtil.isNotEmpty(dos)) {
|
||||
brandDOS.put(brandName, dos.get(0));
|
||||
return brandDO;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void checkCode(Long id, String code) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import cn.hangtag.module.oms.dal.dataobject.productinfo.ProductInfoDO;
|
|||
import cn.hangtag.framework.common.pojo.PageResult;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 产品资料 Service 接口
|
||||
|
|
@ -72,4 +73,6 @@ public interface ProductInfoService {
|
|||
* @return {@link BigDecimal }
|
||||
*/
|
||||
BigDecimal queryPriceByProductId(Long productId, String currency);
|
||||
|
||||
String importExcel(List<ProductInfoExcelVO> list);
|
||||
}
|
||||
|
|
@ -272,4 +272,38 @@ public class ProductInfoServiceImpl implements ProductInfoService {
|
|||
return new BigDecimal(0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String importExcel(List<ProductInfoExcelVO> list) {
|
||||
List<ProductInfoDO> newList = new ArrayList<>();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ProductInfoExcelVO excelVO = list.get(i);
|
||||
ProductInfoDO productInfo = new ProductInfoDO();
|
||||
|
||||
String code = excelVO.getCode();
|
||||
if(FuncUtil.isNotEmpty(code)){
|
||||
try {
|
||||
checkCode(productInfo.getId(),code);
|
||||
}catch (Exception e){
|
||||
return "第"+(i+1)+"行编码重复:"+code;
|
||||
}
|
||||
}else {
|
||||
code = productInfo.getCode();
|
||||
}
|
||||
productInfo.setName(excelVO.getName());
|
||||
String brandName = excelVO.getBrandName();
|
||||
productInfo.setCode(code);
|
||||
productInfo.setTemplateType("2");
|
||||
if(FuncUtil.isNotEmpty(brandName)){
|
||||
BrandDO brandDO = brandService.getBrandByName(brandName);
|
||||
if(FuncUtil.isNotEmpty(brandDO)){
|
||||
productInfo.setBrandId(brandDO.getId());
|
||||
}
|
||||
}
|
||||
newList.add(productInfo);
|
||||
}
|
||||
|
||||
productInfoMapper.insertBatch(newList);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,9 @@ export const ProductInfoApi = {
|
|||
|
||||
// 导出产品资料 Excel
|
||||
exportProductInfo: async (params) => {
|
||||
return await request.download({ url: `/oms/product-info/export-excel`, params })
|
||||
return await request.download({ url: `/oms/product-info/export-excel`, params ,timeout: 1000*60 * 20})
|
||||
},
|
||||
downloadTemplate: async()=> {
|
||||
return await request.download({ url: `/oms/product-info/excel-template` })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -670,6 +670,7 @@ export default {
|
|||
"viewOrder": "viewOrder",
|
||||
"backHome": "BackHome",
|
||||
"tipsDataChange": "DataChange Click Update Button",
|
||||
"downloadSuccess": "downloadSuccess",
|
||||
},
|
||||
"productDialogList": {
|
||||
"title": "Product List",
|
||||
|
|
|
|||
|
|
@ -666,6 +666,7 @@ export default {
|
|||
viewOrder: "查看订单",
|
||||
backHome: "返回首页",
|
||||
tipsDataChange: "数据已更新,请点击更新按钮",
|
||||
downloadSuccess: "下载成功",
|
||||
},
|
||||
productDialogList:{
|
||||
title: '产品列表',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="props.title" :width="props.width">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="importUrl"
|
||||
:auto-upload="false"
|
||||
:disabled="formLoading"
|
||||
:headers="uploadHeaders"
|
||||
:limit="1"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
accept=".xlsx, .xls"
|
||||
drag
|
||||
>
|
||||
<Icon icon="ep:upload" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<span>支持导入 xls、xlsx 格式文件。</span>
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline"
|
||||
type="primary"
|
||||
@click="importTemplate"
|
||||
>
|
||||
下载模板
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">导入</el-button>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import { computed } from 'vue';
|
||||
defineOptions({ name: 'ProductInfoExcelImport' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const uploadRef = ref()
|
||||
|
||||
const uploadHeaders = ref() // 上传 Header 头
|
||||
const fileList = ref([]) // 文件列表
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '产品导入'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '50vw'
|
||||
},
|
||||
uploadUrl: {
|
||||
type: String,
|
||||
default: '/oms/product-info/import'
|
||||
}
|
||||
})
|
||||
|
||||
const importUrl = computed(() => {
|
||||
return `${import.meta.env.VITE_BASE_URL}${import.meta.env.VITE_API_URL}${props.uploadUrl}`;
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
fileList.value = []
|
||||
resetForm()
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
formLoading.value = true
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功 */
|
||||
const emits = defineEmits(['success','downloadTemplate'])
|
||||
const submitFormSuccess = (response: any) => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
// 发送操作成功的事件
|
||||
emits('success',response.data)
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
resetForm();
|
||||
formLoading.value = false
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = async (): Promise<void> => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
await nextTick()
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
|
||||
/** 下载模板操作 */
|
||||
const importTemplate = async () => {
|
||||
emits('downloadTemplate')
|
||||
}
|
||||
defineExpose({ open,close,resetForm }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
</script>
|
||||
|
|
@ -102,7 +102,19 @@
|
|||
<Icon icon="ep:download" class="mr-5px"/>
|
||||
导出
|
||||
</el-button>
|
||||
<div class="ml-2" v-hasPermi="['oms:product-info:create']">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleImport"
|
||||
>
|
||||
<Icon icon="ep:upload" class="mr-5px"/>
|
||||
excel导入
|
||||
</el-button>
|
||||
<ProductInfoExcelImport ref="excelImportRef" @success="importSuccess" @download-template="downloadTemplate"/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
|
|
@ -200,6 +212,7 @@ const designPreviewDialogRef = ref()
|
|||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ProductInfoVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const excelImportRef = ref();
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
|
|
@ -313,8 +326,25 @@ onMounted(() => {
|
|||
openForm('create')
|
||||
}
|
||||
}
|
||||
|
||||
getList();
|
||||
init();
|
||||
})
|
||||
const importSuccess = (res)=>{
|
||||
if(!res){
|
||||
message.success('导入成功')
|
||||
excelImportRef.value.close()
|
||||
getList();
|
||||
}else {
|
||||
message.error(res)
|
||||
excelImportRef.value.resetForm()
|
||||
}
|
||||
}
|
||||
const downloadTemplate = ()=>{
|
||||
ProductInfoApi.downloadTemplate().then(res => {
|
||||
download.excel(res, '产品导入模板.xlsx')
|
||||
})
|
||||
}
|
||||
const handleImport = () => {
|
||||
excelImportRef.value.open()
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -747,8 +747,8 @@ const toSVGData = (callback: (url: string) => void, options?: {
|
|||
}
|
||||
}) => {
|
||||
|
||||
let widthScale = 20 // options && options.widthScale ? options.widthScale : 5
|
||||
let heightScale = 20 // options && options.heightScale ? options.heightScale : 5
|
||||
let widthScale = 60 // options && options.widthScale ? options.widthScale : 5
|
||||
let heightScale = 60 // options && options.heightScale ? options.heightScale : 5
|
||||
graph.toSVG((dataUri) => {
|
||||
that.previewUrl = dataUri;
|
||||
callback(that.previewUrl)
|
||||
|
|
|
|||
|
|
@ -670,6 +670,7 @@ export default {
|
|||
"viewOrder": "viewOrder",
|
||||
"backHome": "BackHome",
|
||||
"tipsDataChange": "DataChange Click Update Button",
|
||||
"downloadSuccess": "downloadSuccess",
|
||||
|
||||
},
|
||||
"productDialogList": {
|
||||
|
|
|
|||
|
|
@ -666,6 +666,7 @@ export default {
|
|||
viewOrder: "查看订单",
|
||||
backHome: "返回首页",
|
||||
tipsDataChange: "数据已更新,请点击更新按钮",
|
||||
downloadSuccess: "下载成功",
|
||||
},
|
||||
productDialogList:{
|
||||
title: '产品列表',
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ const getValue = (item:any) => {
|
|||
}
|
||||
return arr.join(";")
|
||||
}
|
||||
|
||||
const formData = ref({})
|
||||
watch(() => props.productInfo, (newVal) => {
|
||||
console.log("newVal",newVal)
|
||||
|
|
@ -182,11 +183,50 @@ const change = () => {
|
|||
emit("change", formData.value)
|
||||
}
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
addDownloadImg(imgUrl,new Date().getTime())
|
||||
createImageViewer({
|
||||
zIndex: 9999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
const addDownloadImg = (url: string,name = 'img') => {
|
||||
setTimeout(() => {
|
||||
let wrapper = document.getElementsByClassName("el-image-viewer__btn el-image-viewer__actions");
|
||||
if (wrapper.length > 0) {
|
||||
let downImg = document.createElement("i");
|
||||
downImg.style.fontSize = "20px";
|
||||
downImg.style.padding = "4px";
|
||||
downImg.style.color = "#fff";
|
||||
downImg.setAttribute("class", "icon-lk_down");
|
||||
|
||||
// 将下载按钮添加到容器中
|
||||
wrapper[0].appendChild(downImg);
|
||||
|
||||
// 为下载按钮添加点击事件
|
||||
downImg.addEventListener("click", () => {
|
||||
// 创建下载链接
|
||||
const link = document.createElement("a");
|
||||
useMessage().success(t("createOrder.downloadSuccess"))
|
||||
// 使用 fetch 请求获取图片的二进制数据
|
||||
fetch(url)
|
||||
.then((res) => res.blob())
|
||||
.then((blob) => {
|
||||
const blobUrl = URL.createObjectURL(blob); // 创建 blob URL
|
||||
link.href = blobUrl; // 设置下载链接
|
||||
link.download = name+".jpg"; // 设置下载文件名
|
||||
link.click(); // 触发下载
|
||||
URL.revokeObjectURL(blobUrl); // 释放 blob URL 资源
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("下载失败:", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
-- 销售订单sku备注
|
||||
ALTER TABLE oms_sale_order_sku
|
||||
ADD COLUMN `remark` varchar(1024) DEFAULT NULL COMMENT '备注信息' AFTER `main_color`;
|
||||
Loading…
Reference in New Issue