diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/productinfo/ProductInfoController.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/productinfo/ProductInfoController.java index 5f9fb18..90f07da 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/productinfo/ProductInfoController.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/productinfo/ProductInfoController.java @@ -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 importExcel(@RequestParam("file") MultipartFile file) throws Exception { + List 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')") diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/productinfo/vo/ProductInfoExcelVO.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/productinfo/vo/ProductInfoExcelVO.java new file mode 100644 index 0000000..f2341a7 --- /dev/null +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/productinfo/vo/ProductInfoExcelVO.java @@ -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; + +} diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/saleorder/front/dto/SaleOrderSkuDTO.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/saleorder/front/dto/SaleOrderSkuDTO.java index 5a53609..82332b8 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/saleorder/front/dto/SaleOrderSkuDTO.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/controller/admin/saleorder/front/dto/SaleOrderSkuDTO.java @@ -49,10 +49,15 @@ public class SaleOrderSkuDTO implements Serializable { private Double height; /** - * 最终稿件 base64编码数据 + * 预览稿件 */ private String previewImage; + /** + * 源文件链接 + */ + private String sourceFile; + /** * 模板类型-字典 product_draft_template_type 1有模板 2无模板 3尺码唛 */ @@ -92,6 +97,7 @@ public class SaleOrderSkuDTO implements Serializable { this.width = saleOrderSkuDO.getWidth().doubleValue(); this.height = saleOrderSkuDO.getHeight().doubleValue(); this.previewImage = saleOrderSkuDO.getPreviewImage(); + this.sourceFile = saleOrderSkuDO.getSourceFile(); this.productTemplateType = saleOrderSkuDO.getProductTemplateType(); this.propInfo = saleOrderSkuDO.getPropInfo(); this.labelSize = saleOrderSkuDO.getLabelSize(); diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/dal/dataobject/saleordersku/SaleOrderSkuDO.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/dal/dataobject/saleordersku/SaleOrderSkuDO.java index 8d09cdc..e5c432e 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/dal/dataobject/saleordersku/SaleOrderSkuDO.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/dal/dataobject/saleordersku/SaleOrderSkuDO.java @@ -68,6 +68,10 @@ public class SaleOrderSkuDO extends BaseDO { * 预览图 */ private String previewImage; + /** + * 设计稿源文件 + */ + private String sourceFile; /** * 动态属性 json动态属性 */ diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandService.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandService.java index facba3f..5da021c 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandService.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandService.java @@ -78,4 +78,6 @@ public interface BrandService { * @return {@link Integer } */ Integer updateProductCount(Long id); + + BrandDO getBrandByName(String brandName); } \ No newline at end of file diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandServiceImpl.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandServiceImpl.java index d89bac3..85e6e66 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandServiceImpl.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/brand/BrandServiceImpl.java @@ -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 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 lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(BrandDO::getName, brandName); + lambdaQueryWrapper.eq(BrandDO::getDeleted, false); + List 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) { diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoService.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoService.java index 0f3eab3..6448d03 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoService.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoService.java @@ -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 list); } \ No newline at end of file diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoServiceImpl.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoServiceImpl.java index 21296c1..77fb3ac 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoServiceImpl.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/productinfo/ProductInfoServiceImpl.java @@ -272,4 +272,38 @@ public class ProductInfoServiceImpl implements ProductInfoService { return new BigDecimal(0); } + + @Override + public String importExcel(List list) { + List 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 ""; + } } \ No newline at end of file diff --git a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/saleorder/SaleOrderServiceImpl.java b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/saleorder/SaleOrderServiceImpl.java index 4c203cf..d9e9943 100644 --- a/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/saleorder/SaleOrderServiceImpl.java +++ b/hangtag-module-oms/hangtag-module-oms-biz/src/main/java/cn/hangtag/module/oms/service/saleorder/SaleOrderServiceImpl.java @@ -719,6 +719,7 @@ public class SaleOrderServiceImpl implements SaleOrderService { saleOrderSkuDO.setProductTemplateType(saleOrderSkuDTO.getProductTemplateType()); saleOrderSkuDO.setSpecInfo(JSONUtil.toJsonStr(saleOrderSkuDTO.getSpecInfo())); saleOrderSkuDO.setRemark(saleOrderSkuDTO.getRemark()); + saleOrderSkuDO.setSourceFile(saleOrderSkuDTO.getSourceFile()); saleOrderSkuDO.setId(FuncUtil.getNextId()); skuList.add(saleOrderSkuDO); } diff --git a/hangtag-ui/hangtag-ui-admin/index.html b/hangtag-ui/hangtag-ui-admin/index.html index b2323e4..c53c816 100644 --- a/hangtag-ui/hangtag-ui-admin/index.html +++ b/hangtag-ui/hangtag-ui-admin/index.html @@ -15,6 +15,7 @@ content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!" /> %VITE_APP_TITLE% +
diff --git a/hangtag-ui/hangtag-ui-admin/package.json b/hangtag-ui/hangtag-ui-admin/package.json index ea734b3..fb0e980 100644 --- a/hangtag-ui/hangtag-ui-admin/package.json +++ b/hangtag-ui/hangtag-ui-admin/package.json @@ -49,12 +49,12 @@ "axios": "^1.6.8", "benz-amr-recorder": "^1.1.5", "bpmn-js-token-simulation": "^0.10.0", + "bwip-js": "^4.5.1", "camunda-bpmn-moddle": "^7.0.1", "cropperjs": "^1.6.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.10", "diagram-js": "^12.8.0", - "dom-to-image": "^2.6.0", "driver.js": "^1.3.1", "echarts": "^5.5.0", "echarts-wordcloud": "^2.1.0", diff --git a/hangtag-ui/hangtag-ui-admin/public/js/dom-to-image.js b/hangtag-ui/hangtag-ui-admin/public/js/dom-to-image.js new file mode 100644 index 0000000..ab14a2a --- /dev/null +++ b/hangtag-ui/hangtag-ui-admin/public/js/dom-to-image.js @@ -0,0 +1,777 @@ +(function (global) { + 'use strict'; + + var util = newUtil(); + var inliner = newInliner(); + var fontFaces = newFontFaces(); + var images = newImages(); + + // Default impl options + var defaultOptions = { + // Default is to fail on error, no placeholder + imagePlaceholder: undefined, + // Default cache bust is false, it will use the cache + cacheBust: false + }; + + var domtoimage = { + toSvg: toSvg, + toPng: toPng, + toJpeg: toJpeg, + toBlob: toBlob, + toPixelData: toPixelData, + impl: { + fontFaces: fontFaces, + images: images, + util: util, + inliner: inliner, + options: {} + } + }; + + if (typeof module !== 'undefined') + module.exports = domtoimage; + else + global.domtoimage = domtoimage; + + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options + * @param {Function} options.filter - Should return true if passed node should be included in the output + * (excluding node means excluding it's children as well). Not called on the root node. + * @param {String} options.bgcolor - color for the background, any valid CSS color value. + * @param {Number} options.width - width to be applied to node before rendering. + * @param {Number} options.height - height to be applied to node before rendering. + * @param {Object} options.style - an object whose properties to be copied to node's style before rendering. + * @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only), + defaults to 1.0. + * @param {String} options.imagePlaceholder - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch + * @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url + * @return {Promise} - A promise that is fulfilled with a SVG image data URL + * */ + function toSvg(node, options) { + options = options || {}; + copyOptions(options); + return Promise.resolve(node) + .then(function (node) { + return cloneNode(node, options.filter, true); + }) + .then(embedFonts) + .then(inlineImages) + .then(applyOptions) + .then(function (clone) { + return makeSvgDataUri(clone, + options.width || util.width(node), + options.height || util.height(node) + ); + }); + + function applyOptions(clone) { + if (options.bgcolor) clone.style.backgroundColor = options.bgcolor; + + if (options.width) clone.style.width = options.width + 'px'; + if (options.height) clone.style.height = options.height + 'px'; + + if (options.style) + Object.keys(options.style).forEach(function (property) { + clone.style[property] = options.style[property]; + }); + + return clone; + } + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data. + * */ + function toPixelData(node, options) { + return draw(node, options || {}) + .then(function (canvas) { + return canvas.getContext('2d').getImageData( + 0, + 0, + util.width(node), + util.height(node) + ).data; + }); + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a PNG image data URL + * */ + function toPng(node, options) { + return draw(node, options || {}) + .then(function (canvas) { + return canvas.toDataURL(); + }); + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a JPEG image data URL + * */ + function toJpeg(node, options) { + options = options || {}; + return draw(node, options) + .then(function (canvas) { + return canvas.toDataURL('image/jpeg', options.quality || 1.0); + }); + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a PNG image blob + * */ + function toBlob(node, options) { + return draw(node, options || {}) + .then(util.canvasToBlob); + } + + function copyOptions(options) { + // Copy options to impl options for use in impl + if(typeof(options.imagePlaceholder) === 'undefined') { + domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder; + } else { + domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder; + } + + if(typeof(options.cacheBust) === 'undefined') { + domtoimage.impl.options.cacheBust = defaultOptions.cacheBust; + } else { + domtoimage.impl.options.cacheBust = options.cacheBust; + } + } + + function draw(domNode, options) { + return toSvg(domNode, options) + .then(util.makeImage) + .then(util.delay(100)) + .then(function (image) { + var canvas = newCanvas(domNode); + let context = canvas.getContext('2d'); + const ratio = window.devicePixelRatio || 1; + canvas.width *= ratio; + canvas.height *= ratio; + context.scale(ratio, ratio); + context.drawImage(image, 0, 0); + return canvas; + + }); + + function newCanvas(domNode) { + var canvas = document.createElement('canvas'); + canvas.width = options.width || util.width(domNode); + canvas.height = options.height || util.height(domNode); + + + if (options.bgcolor) { + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = options.bgcolor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + + return canvas; + } + } + + function cloneNode(node, filter, root) { + if (!root && filter && !filter(node)) return Promise.resolve(); + + return Promise.resolve(node) + .then(makeNodeCopy) + .then(function (clone) { + return cloneChildren(node, clone, filter); + }) + .then(function (clone) { + return processClone(node, clone); + }); + + function makeNodeCopy(node) { + if (node instanceof HTMLCanvasElement) return util.makeImage(node.toDataURL()); + return node.cloneNode(false); + } + + function cloneChildren(original, clone, filter) { + var children = original.childNodes; + if (children.length === 0) return Promise.resolve(clone); + + return cloneChildrenInOrder(clone, util.asArray(children), filter) + .then(function () { + return clone; + }); + + function cloneChildrenInOrder(parent, children, filter) { + var done = Promise.resolve(); + children.forEach(function (child) { + done = done + .then(function () { + return cloneNode(child, filter); + }) + .then(function (childClone) { + if (childClone) parent.appendChild(childClone); + }); + }); + return done; + } + } + + function processClone(original, clone) { + if (!(clone instanceof Element)) return clone; + + return Promise.resolve() + .then(cloneStyle) + .then(clonePseudoElements) + .then(copyUserInput) + .then(fixSvg) + .then(function () { + return clone; + }); + + function cloneStyle() { + copyStyle(window.getComputedStyle(original), clone.style); + + function copyStyle(source, target) { + if (source.cssText) target.cssText = source.cssText; + else copyProperties(source, target); + + function copyProperties(source, target) { + util.asArray(source).forEach(function (name) { + target.setProperty( + name, + source.getPropertyValue(name), + source.getPropertyPriority(name) + ); + }); + } + } + } + + function clonePseudoElements() { + [':before', ':after'].forEach(function (element) { + clonePseudoElement(element); + }); + + function clonePseudoElement(element) { + var style = window.getComputedStyle(original, element); + var content = style.getPropertyValue('content'); + + if (content === '' || content === 'none') return; + + var className = util.uid(); + clone.className = clone.className + ' ' + className; + var styleElement = document.createElement('style'); + styleElement.appendChild(formatPseudoElementStyle(className, element, style)); + clone.appendChild(styleElement); + + function formatPseudoElementStyle(className, element, style) { + var selector = '.' + className + ':' + element; + var cssText = style.cssText ? formatCssText(style) : formatCssProperties(style); + return document.createTextNode(selector + '{' + cssText + '}'); + + function formatCssText(style) { + var content = style.getPropertyValue('content'); + return style.cssText + ' content: ' + content + ';'; + } + + function formatCssProperties(style) { + + return util.asArray(style) + .map(formatProperty) + .join('; ') + ';'; + + function formatProperty(name) { + return name + ': ' + + style.getPropertyValue(name) + + (style.getPropertyPriority(name) ? ' !important' : ''); + } + } + } + } + } + + function copyUserInput() { + if (original instanceof HTMLTextAreaElement) clone.innerHTML = original.value; + if (original instanceof HTMLInputElement) clone.setAttribute("value", original.value); + } + + function fixSvg() { + if (!(clone instanceof SVGElement)) return; + clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + + if (!(clone instanceof SVGRectElement)) return; + ['width', 'height'].forEach(function (attribute) { + var value = clone.getAttribute(attribute); + if (!value) return; + + clone.style.setProperty(attribute, value); + }); + } + } + } + + function embedFonts(node) { + return fontFaces.resolveAll() + .then(function (cssText) { + var styleNode = document.createElement('style'); + node.appendChild(styleNode); + styleNode.appendChild(document.createTextNode(cssText)); + return node; + }); + } + + function inlineImages(node) { + return images.inlineAll(node) + .then(function () { + return node; + }); + } + + function makeSvgDataUri(node, width, height) { + return Promise.resolve(node) + .then(function (node) { + node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + return new XMLSerializer().serializeToString(node); + }) + .then(util.escapeXhtml) + .then(function (xhtml) { + return '' + xhtml + ''; + }) + .then(function (foreignObject) { + return '' + + foreignObject + ''; + }) + .then(function (svg) { + return 'data:image/svg+xml;charset=utf-8,' + svg; + }); + } + + function newUtil() { + return { + escape: escape, + parseExtension: parseExtension, + mimeType: mimeType, + dataAsUrl: dataAsUrl, + isDataUrl: isDataUrl, + canvasToBlob: canvasToBlob, + resolveUrl: resolveUrl, + getAndEncode: getAndEncode, + uid: uid(), + delay: delay, + asArray: asArray, + escapeXhtml: escapeXhtml, + makeImage: makeImage, + width: width, + height: height + }; + + function mimes() { + /* + * Only WOFF and EOT mime types for fonts are 'real' + * see http://www.iana.org/assignments/media-types/media-types.xhtml + */ + var WOFF = 'application/font-woff'; + var JPEG = 'image/jpeg'; + + return { + 'woff': WOFF, + 'woff2': WOFF, + 'ttf': 'application/font-truetype', + 'eot': 'application/vnd.ms-fontobject', + 'png': 'image/png', + 'jpg': JPEG, + 'jpeg': JPEG, + 'gif': 'image/gif', + 'tiff': 'image/tiff', + 'svg': 'image/svg+xml' + }; + } + + function parseExtension(url) { + var match = /\.([^\.\/]*?)$/g.exec(url); + if (match) return match[1]; + else return ''; + } + + function mimeType(url) { + var extension = parseExtension(url).toLowerCase(); + return mimes()[extension] || ''; + } + + function isDataUrl(url) { + return url.search(/^(data:)/) !== -1; + } + + function toBlob(canvas) { + return new Promise(function (resolve) { + var binaryString = window.atob(canvas.toDataURL().split(',')[1]); + var length = binaryString.length; + var binaryArray = new Uint8Array(length); + + for (var i = 0; i < length; i++) + binaryArray[i] = binaryString.charCodeAt(i); + + resolve(new Blob([binaryArray], { + type: 'image/png' + })); + }); + } + + function canvasToBlob(canvas) { + if (canvas.toBlob) + return new Promise(function (resolve) { + canvas.toBlob(resolve); + }); + + return toBlob(canvas); + } + + function resolveUrl(url, baseUrl) { + var doc = document.implementation.createHTMLDocument(); + var base = doc.createElement('base'); + doc.head.appendChild(base); + var a = doc.createElement('a'); + doc.body.appendChild(a); + base.href = baseUrl; + a.href = url; + return a.href; + } + + function uid() { + var index = 0; + + return function () { + return 'u' + fourRandomChars() + index++; + + function fourRandomChars() { + /* see http://stackoverflow.com/a/6248722/2519373 */ + return ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4); + } + }; + } + + function makeImage(uri) { + return new Promise(function (resolve, reject) { + var image = new Image(); + image.onload = function () { + resolve(image); + }; + image.onerror = reject; + image.src = uri; + }); + } + + function getAndEncode(url) { + var TIMEOUT = 30000; + if(domtoimage.impl.options.cacheBust) { + // Cache bypass so we dont have CORS issues with cached images + // Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache + url += ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime(); + } + + return new Promise(function (resolve) { + var request = new XMLHttpRequest(); + + request.onreadystatechange = done; + request.ontimeout = timeout; + request.responseType = 'blob'; + request.timeout = TIMEOUT; + request.open('GET', url, true); + request.send(); + + var placeholder; + if(domtoimage.impl.options.imagePlaceholder) { + var split = domtoimage.impl.options.imagePlaceholder.split(/,/); + if(split && split[1]) { + placeholder = split[1]; + } + } + + function done() { + if (request.readyState !== 4) return; + + if (request.status !== 200) { + if(placeholder) { + resolve(placeholder); + } else { + fail('cannot fetch resource: ' + url + ', status: ' + request.status); + } + + return; + } + + var encoder = new FileReader(); + encoder.onloadend = function () { + var content = encoder.result.split(/,/)[1]; + resolve(content); + }; + encoder.readAsDataURL(request.response); + } + + function timeout() { + if(placeholder) { + resolve(placeholder); + } else { + fail('timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url); + } + } + + function fail(message) { + console.error(message); + resolve(''); + } + }); + } + + function dataAsUrl(content, type) { + return 'data:' + type + ';base64,' + content; + } + + function escape(string) { + return string.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1'); + } + + function delay(ms) { + return function (arg) { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(arg); + }, ms); + }); + }; + } + + function asArray(arrayLike) { + var array = []; + var length = arrayLike.length; + for (var i = 0; i < length; i++) array.push(arrayLike[i]); + return array; + } + + function escapeXhtml(string) { + return string.replace(/#/g, '%23').replace(/\n/g, '%0A'); + } + + function width(node) { + var leftBorder = px(node, 'border-left-width'); + var rightBorder = px(node, 'border-right-width'); + return node.scrollWidth + leftBorder + rightBorder; + } + + function height(node) { + var topBorder = px(node, 'border-top-width'); + var bottomBorder = px(node, 'border-bottom-width'); + return node.scrollHeight + topBorder + bottomBorder; + } + + function px(node, styleProperty) { + var value = window.getComputedStyle(node).getPropertyValue(styleProperty); + return parseFloat(value.replace('px', '')); + } + } + + function newInliner() { + var URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/g; + + return { + inlineAll: inlineAll, + shouldProcess: shouldProcess, + impl: { + readUrls: readUrls, + inline: inline + } + }; + + function shouldProcess(string) { + return string.search(URL_REGEX) !== -1; + } + + function readUrls(string) { + var result = []; + var match; + while ((match = URL_REGEX.exec(string)) !== null) { + result.push(match[1]); + } + return result.filter(function (url) { + return !util.isDataUrl(url); + }); + } + + function inline(string, url, baseUrl, get) { + return Promise.resolve(url) + .then(function (url) { + return baseUrl ? util.resolveUrl(url, baseUrl) : url; + }) + .then(get || util.getAndEncode) + .then(function (data) { + return util.dataAsUrl(data, util.mimeType(url)); + }) + .then(function (dataUrl) { + return string.replace(urlAsRegex(url), '$1' + dataUrl + '$3'); + }); + + function urlAsRegex(url) { + return new RegExp('(url\\([\'"]?)(' + util.escape(url) + ')([\'"]?\\))', 'g'); + } + } + + function inlineAll(string, baseUrl, get) { + if (nothingToInline()) return Promise.resolve(string); + + return Promise.resolve(string) + .then(readUrls) + .then(function (urls) { + var done = Promise.resolve(string); + urls.forEach(function (url) { + done = done.then(function (string) { + return inline(string, url, baseUrl, get); + }); + }); + return done; + }); + + function nothingToInline() { + return !shouldProcess(string); + } + } + } + + function newFontFaces() { + return { + resolveAll: resolveAll, + impl: { + readAll: readAll + } + }; + + function resolveAll() { + return readAll(document) + .then(function (webFonts) { + return Promise.all( + webFonts.map(function (webFont) { + return webFont.resolve(); + }) + ); + }) + .then(function (cssStrings) { + return cssStrings.join('\n'); + }); + } + + function readAll() { + return Promise.resolve(util.asArray(document.styleSheets)) + .then(getCssRules) + .then(selectWebFontRules) + .then(function (rules) { + return rules.map(newWebFont); + }); + + function selectWebFontRules(cssRules) { + return cssRules + .filter(function (rule) { + return rule.type === CSSRule.FONT_FACE_RULE; + }) + .filter(function (rule) { + return inliner.shouldProcess(rule.style.getPropertyValue('src')); + }); + } + + function getCssRules(styleSheets) { + var cssRules = []; + styleSheets.forEach(function (sheet) { + try { + util.asArray(sheet.cssRules || []).forEach(cssRules.push.bind(cssRules)); + } catch (e) { + console.log('Error while reading CSS rules from ' + sheet.href, e.toString()); + } + }); + return cssRules; + } + + function newWebFont(webFontRule) { + return { + resolve: function resolve() { + var baseUrl = (webFontRule.parentStyleSheet || {}).href; + return inliner.inlineAll(webFontRule.cssText, baseUrl); + }, + src: function () { + return webFontRule.style.getPropertyValue('src'); + } + }; + } + } + } + + function newImages() { + return { + inlineAll: inlineAll, + impl: { + newImage: newImage + } + }; + + function newImage(element) { + return { + inline: inline + }; + + function inline(get) { + if (util.isDataUrl(element.src)) return Promise.resolve(); + + return Promise.resolve(element.src) + .then(get || util.getAndEncode) + .then(function (data) { + return util.dataAsUrl(data, util.mimeType(element.src)); + }) + .then(function (dataUrl) { + return new Promise(function (resolve, reject) { + element.onload = resolve; + element.onerror = reject; + element.src = dataUrl; + }); + }); + } + } + + function inlineAll(node) { + if (!(node instanceof Element)) return Promise.resolve(node); + + return inlineBackground(node) + .then(function () { + if (node instanceof HTMLImageElement) + return newImage(node).inline(); + else + return Promise.all( + util.asArray(node.childNodes).map(function (child) { + return inlineAll(child); + }) + ); + }); + + function inlineBackground(node) { + var background = node.style.getPropertyValue('background'); + + if (!background) return Promise.resolve(node); + + return inliner.inlineAll(background) + .then(function (inlined) { + node.style.setProperty( + 'background', + inlined, + node.style.getPropertyPriority('background') + ); + }) + .then(function () { + return node; + }); + } + } + } +})(this); diff --git a/hangtag-ui/hangtag-ui-admin/src/api/oms/productinfo/index.ts b/hangtag-ui/hangtag-ui-admin/src/api/oms/productinfo/index.ts index ed2fa71..a453c8b 100644 --- a/hangtag-ui/hangtag-ui-admin/src/api/oms/productinfo/index.ts +++ b/hangtag-ui/hangtag-ui-admin/src/api/oms/productinfo/index.ts @@ -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` }) + } } diff --git a/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/DesignPropEdit.vue b/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/DesignPropEdit.vue index 83e9b22..0dc938f 100644 --- a/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/DesignPropEdit.vue +++ b/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/DesignPropEdit.vue @@ -1,4 +1,5 @@ + + + diff --git a/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/node/TextCellNode.vue b/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/node/TextCellNode.vue index a6028b6..be8ccb0 100644 --- a/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/node/TextCellNode.vue +++ b/hangtag-ui/hangtag-ui-admin/src/components/DraftDesign/components/node/TextCellNode.vue @@ -12,7 +12,8 @@ + style="word-break: keep-all; white-space: normal; overflow-wrap: break-word;hyphens: auto;" + v-html="safeHtml(textShowLabel)"> { + return ` + + + + + oms稿件预览源文件 + + + +
+ *仅供参考 +
+ ${htmlStr} + + + + + + + ` +} diff --git a/hangtag-ui/hangtag-ui-admin/src/locales/en.ts b/hangtag-ui/hangtag-ui-admin/src/locales/en.ts index 12131ab..9611d4f 100644 --- a/hangtag-ui/hangtag-ui-admin/src/locales/en.ts +++ b/hangtag-ui/hangtag-ui-admin/src/locales/en.ts @@ -670,6 +670,7 @@ export default { "viewOrder": "viewOrder", "backHome": "BackHome", "tipsDataChange": "DataChange Click Update Button", + "downloadSuccess": "downloadSuccess", }, "productDialogList": { "title": "Product List", diff --git a/hangtag-ui/hangtag-ui-admin/src/locales/zh-CN.ts b/hangtag-ui/hangtag-ui-admin/src/locales/zh-CN.ts index 8a43b62..a0462ce 100644 --- a/hangtag-ui/hangtag-ui-admin/src/locales/zh-CN.ts +++ b/hangtag-ui/hangtag-ui-admin/src/locales/zh-CN.ts @@ -666,6 +666,7 @@ export default { viewOrder: "查看订单", backHome: "返回首页", tipsDataChange: "数据已更新,请点击更新按钮", + downloadSuccess: "下载成功", }, productDialogList:{ title: '产品列表', diff --git a/hangtag-ui/hangtag-ui-admin/src/views/oms/productinfo/ProductInfoExcelImport.vue b/hangtag-ui/hangtag-ui-admin/src/views/oms/productinfo/ProductInfoExcelImport.vue new file mode 100644 index 0000000..0c9ca95 --- /dev/null +++ b/hangtag-ui/hangtag-ui-admin/src/views/oms/productinfo/ProductInfoExcelImport.vue @@ -0,0 +1,138 @@ + + diff --git a/hangtag-ui/hangtag-ui-admin/src/views/oms/productinfo/index.vue b/hangtag-ui/hangtag-ui-admin/src/views/oms/productinfo/index.vue index 41f6992..b4cf4a7 100644 --- a/hangtag-ui/hangtag-ui-admin/src/views/oms/productinfo/index.vue +++ b/hangtag-ui/hangtag-ui-admin/src/views/oms/productinfo/index.vue @@ -102,7 +102,19 @@ 导出 +
+ + + excel导入 + + +
+ @@ -200,6 +212,7 @@ const designPreviewDialogRef = ref() const loading = ref(true) // 列表的加载中 const list = ref([]) // 列表的数据 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() +} diff --git a/hangtag-ui/hangtag-ui-front/index.html b/hangtag-ui/hangtag-ui-front/index.html index 1ed3e88..e6afc7c 100644 --- a/hangtag-ui/hangtag-ui-front/index.html +++ b/hangtag-ui/hangtag-ui-front/index.html @@ -14,6 +14,7 @@ content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!" /> %VITE_APP_TITLE% +
diff --git a/hangtag-ui/hangtag-ui-front/package.json b/hangtag-ui/hangtag-ui-front/package.json index 5178dcb..b3f1285 100644 --- a/hangtag-ui/hangtag-ui-front/package.json +++ b/hangtag-ui/hangtag-ui-front/package.json @@ -54,7 +54,6 @@ "crypto-js": "^4.2.0", "dayjs": "^1.11.10", "diagram-js": "^12.8.0", - "dom-to-image": "^2.6.0", "driver.js": "^1.3.1", "echarts": "^5.5.0", "echarts-wordcloud": "^2.1.0", diff --git a/hangtag-ui/hangtag-ui-front/public/js/dom-to-image.js b/hangtag-ui/hangtag-ui-front/public/js/dom-to-image.js new file mode 100644 index 0000000..ab14a2a --- /dev/null +++ b/hangtag-ui/hangtag-ui-front/public/js/dom-to-image.js @@ -0,0 +1,777 @@ +(function (global) { + 'use strict'; + + var util = newUtil(); + var inliner = newInliner(); + var fontFaces = newFontFaces(); + var images = newImages(); + + // Default impl options + var defaultOptions = { + // Default is to fail on error, no placeholder + imagePlaceholder: undefined, + // Default cache bust is false, it will use the cache + cacheBust: false + }; + + var domtoimage = { + toSvg: toSvg, + toPng: toPng, + toJpeg: toJpeg, + toBlob: toBlob, + toPixelData: toPixelData, + impl: { + fontFaces: fontFaces, + images: images, + util: util, + inliner: inliner, + options: {} + } + }; + + if (typeof module !== 'undefined') + module.exports = domtoimage; + else + global.domtoimage = domtoimage; + + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options + * @param {Function} options.filter - Should return true if passed node should be included in the output + * (excluding node means excluding it's children as well). Not called on the root node. + * @param {String} options.bgcolor - color for the background, any valid CSS color value. + * @param {Number} options.width - width to be applied to node before rendering. + * @param {Number} options.height - height to be applied to node before rendering. + * @param {Object} options.style - an object whose properties to be copied to node's style before rendering. + * @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only), + defaults to 1.0. + * @param {String} options.imagePlaceholder - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch + * @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url + * @return {Promise} - A promise that is fulfilled with a SVG image data URL + * */ + function toSvg(node, options) { + options = options || {}; + copyOptions(options); + return Promise.resolve(node) + .then(function (node) { + return cloneNode(node, options.filter, true); + }) + .then(embedFonts) + .then(inlineImages) + .then(applyOptions) + .then(function (clone) { + return makeSvgDataUri(clone, + options.width || util.width(node), + options.height || util.height(node) + ); + }); + + function applyOptions(clone) { + if (options.bgcolor) clone.style.backgroundColor = options.bgcolor; + + if (options.width) clone.style.width = options.width + 'px'; + if (options.height) clone.style.height = options.height + 'px'; + + if (options.style) + Object.keys(options.style).forEach(function (property) { + clone.style[property] = options.style[property]; + }); + + return clone; + } + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data. + * */ + function toPixelData(node, options) { + return draw(node, options || {}) + .then(function (canvas) { + return canvas.getContext('2d').getImageData( + 0, + 0, + util.width(node), + util.height(node) + ).data; + }); + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a PNG image data URL + * */ + function toPng(node, options) { + return draw(node, options || {}) + .then(function (canvas) { + return canvas.toDataURL(); + }); + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a JPEG image data URL + * */ + function toJpeg(node, options) { + options = options || {}; + return draw(node, options) + .then(function (canvas) { + return canvas.toDataURL('image/jpeg', options.quality || 1.0); + }); + } + + /** + * @param {Node} node - The DOM Node object to render + * @param {Object} options - Rendering options, @see {@link toSvg} + * @return {Promise} - A promise that is fulfilled with a PNG image blob + * */ + function toBlob(node, options) { + return draw(node, options || {}) + .then(util.canvasToBlob); + } + + function copyOptions(options) { + // Copy options to impl options for use in impl + if(typeof(options.imagePlaceholder) === 'undefined') { + domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder; + } else { + domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder; + } + + if(typeof(options.cacheBust) === 'undefined') { + domtoimage.impl.options.cacheBust = defaultOptions.cacheBust; + } else { + domtoimage.impl.options.cacheBust = options.cacheBust; + } + } + + function draw(domNode, options) { + return toSvg(domNode, options) + .then(util.makeImage) + .then(util.delay(100)) + .then(function (image) { + var canvas = newCanvas(domNode); + let context = canvas.getContext('2d'); + const ratio = window.devicePixelRatio || 1; + canvas.width *= ratio; + canvas.height *= ratio; + context.scale(ratio, ratio); + context.drawImage(image, 0, 0); + return canvas; + + }); + + function newCanvas(domNode) { + var canvas = document.createElement('canvas'); + canvas.width = options.width || util.width(domNode); + canvas.height = options.height || util.height(domNode); + + + if (options.bgcolor) { + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = options.bgcolor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + + return canvas; + } + } + + function cloneNode(node, filter, root) { + if (!root && filter && !filter(node)) return Promise.resolve(); + + return Promise.resolve(node) + .then(makeNodeCopy) + .then(function (clone) { + return cloneChildren(node, clone, filter); + }) + .then(function (clone) { + return processClone(node, clone); + }); + + function makeNodeCopy(node) { + if (node instanceof HTMLCanvasElement) return util.makeImage(node.toDataURL()); + return node.cloneNode(false); + } + + function cloneChildren(original, clone, filter) { + var children = original.childNodes; + if (children.length === 0) return Promise.resolve(clone); + + return cloneChildrenInOrder(clone, util.asArray(children), filter) + .then(function () { + return clone; + }); + + function cloneChildrenInOrder(parent, children, filter) { + var done = Promise.resolve(); + children.forEach(function (child) { + done = done + .then(function () { + return cloneNode(child, filter); + }) + .then(function (childClone) { + if (childClone) parent.appendChild(childClone); + }); + }); + return done; + } + } + + function processClone(original, clone) { + if (!(clone instanceof Element)) return clone; + + return Promise.resolve() + .then(cloneStyle) + .then(clonePseudoElements) + .then(copyUserInput) + .then(fixSvg) + .then(function () { + return clone; + }); + + function cloneStyle() { + copyStyle(window.getComputedStyle(original), clone.style); + + function copyStyle(source, target) { + if (source.cssText) target.cssText = source.cssText; + else copyProperties(source, target); + + function copyProperties(source, target) { + util.asArray(source).forEach(function (name) { + target.setProperty( + name, + source.getPropertyValue(name), + source.getPropertyPriority(name) + ); + }); + } + } + } + + function clonePseudoElements() { + [':before', ':after'].forEach(function (element) { + clonePseudoElement(element); + }); + + function clonePseudoElement(element) { + var style = window.getComputedStyle(original, element); + var content = style.getPropertyValue('content'); + + if (content === '' || content === 'none') return; + + var className = util.uid(); + clone.className = clone.className + ' ' + className; + var styleElement = document.createElement('style'); + styleElement.appendChild(formatPseudoElementStyle(className, element, style)); + clone.appendChild(styleElement); + + function formatPseudoElementStyle(className, element, style) { + var selector = '.' + className + ':' + element; + var cssText = style.cssText ? formatCssText(style) : formatCssProperties(style); + return document.createTextNode(selector + '{' + cssText + '}'); + + function formatCssText(style) { + var content = style.getPropertyValue('content'); + return style.cssText + ' content: ' + content + ';'; + } + + function formatCssProperties(style) { + + return util.asArray(style) + .map(formatProperty) + .join('; ') + ';'; + + function formatProperty(name) { + return name + ': ' + + style.getPropertyValue(name) + + (style.getPropertyPriority(name) ? ' !important' : ''); + } + } + } + } + } + + function copyUserInput() { + if (original instanceof HTMLTextAreaElement) clone.innerHTML = original.value; + if (original instanceof HTMLInputElement) clone.setAttribute("value", original.value); + } + + function fixSvg() { + if (!(clone instanceof SVGElement)) return; + clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + + if (!(clone instanceof SVGRectElement)) return; + ['width', 'height'].forEach(function (attribute) { + var value = clone.getAttribute(attribute); + if (!value) return; + + clone.style.setProperty(attribute, value); + }); + } + } + } + + function embedFonts(node) { + return fontFaces.resolveAll() + .then(function (cssText) { + var styleNode = document.createElement('style'); + node.appendChild(styleNode); + styleNode.appendChild(document.createTextNode(cssText)); + return node; + }); + } + + function inlineImages(node) { + return images.inlineAll(node) + .then(function () { + return node; + }); + } + + function makeSvgDataUri(node, width, height) { + return Promise.resolve(node) + .then(function (node) { + node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + return new XMLSerializer().serializeToString(node); + }) + .then(util.escapeXhtml) + .then(function (xhtml) { + return '' + xhtml + ''; + }) + .then(function (foreignObject) { + return '' + + foreignObject + ''; + }) + .then(function (svg) { + return 'data:image/svg+xml;charset=utf-8,' + svg; + }); + } + + function newUtil() { + return { + escape: escape, + parseExtension: parseExtension, + mimeType: mimeType, + dataAsUrl: dataAsUrl, + isDataUrl: isDataUrl, + canvasToBlob: canvasToBlob, + resolveUrl: resolveUrl, + getAndEncode: getAndEncode, + uid: uid(), + delay: delay, + asArray: asArray, + escapeXhtml: escapeXhtml, + makeImage: makeImage, + width: width, + height: height + }; + + function mimes() { + /* + * Only WOFF and EOT mime types for fonts are 'real' + * see http://www.iana.org/assignments/media-types/media-types.xhtml + */ + var WOFF = 'application/font-woff'; + var JPEG = 'image/jpeg'; + + return { + 'woff': WOFF, + 'woff2': WOFF, + 'ttf': 'application/font-truetype', + 'eot': 'application/vnd.ms-fontobject', + 'png': 'image/png', + 'jpg': JPEG, + 'jpeg': JPEG, + 'gif': 'image/gif', + 'tiff': 'image/tiff', + 'svg': 'image/svg+xml' + }; + } + + function parseExtension(url) { + var match = /\.([^\.\/]*?)$/g.exec(url); + if (match) return match[1]; + else return ''; + } + + function mimeType(url) { + var extension = parseExtension(url).toLowerCase(); + return mimes()[extension] || ''; + } + + function isDataUrl(url) { + return url.search(/^(data:)/) !== -1; + } + + function toBlob(canvas) { + return new Promise(function (resolve) { + var binaryString = window.atob(canvas.toDataURL().split(',')[1]); + var length = binaryString.length; + var binaryArray = new Uint8Array(length); + + for (var i = 0; i < length; i++) + binaryArray[i] = binaryString.charCodeAt(i); + + resolve(new Blob([binaryArray], { + type: 'image/png' + })); + }); + } + + function canvasToBlob(canvas) { + if (canvas.toBlob) + return new Promise(function (resolve) { + canvas.toBlob(resolve); + }); + + return toBlob(canvas); + } + + function resolveUrl(url, baseUrl) { + var doc = document.implementation.createHTMLDocument(); + var base = doc.createElement('base'); + doc.head.appendChild(base); + var a = doc.createElement('a'); + doc.body.appendChild(a); + base.href = baseUrl; + a.href = url; + return a.href; + } + + function uid() { + var index = 0; + + return function () { + return 'u' + fourRandomChars() + index++; + + function fourRandomChars() { + /* see http://stackoverflow.com/a/6248722/2519373 */ + return ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4); + } + }; + } + + function makeImage(uri) { + return new Promise(function (resolve, reject) { + var image = new Image(); + image.onload = function () { + resolve(image); + }; + image.onerror = reject; + image.src = uri; + }); + } + + function getAndEncode(url) { + var TIMEOUT = 30000; + if(domtoimage.impl.options.cacheBust) { + // Cache bypass so we dont have CORS issues with cached images + // Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache + url += ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime(); + } + + return new Promise(function (resolve) { + var request = new XMLHttpRequest(); + + request.onreadystatechange = done; + request.ontimeout = timeout; + request.responseType = 'blob'; + request.timeout = TIMEOUT; + request.open('GET', url, true); + request.send(); + + var placeholder; + if(domtoimage.impl.options.imagePlaceholder) { + var split = domtoimage.impl.options.imagePlaceholder.split(/,/); + if(split && split[1]) { + placeholder = split[1]; + } + } + + function done() { + if (request.readyState !== 4) return; + + if (request.status !== 200) { + if(placeholder) { + resolve(placeholder); + } else { + fail('cannot fetch resource: ' + url + ', status: ' + request.status); + } + + return; + } + + var encoder = new FileReader(); + encoder.onloadend = function () { + var content = encoder.result.split(/,/)[1]; + resolve(content); + }; + encoder.readAsDataURL(request.response); + } + + function timeout() { + if(placeholder) { + resolve(placeholder); + } else { + fail('timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url); + } + } + + function fail(message) { + console.error(message); + resolve(''); + } + }); + } + + function dataAsUrl(content, type) { + return 'data:' + type + ';base64,' + content; + } + + function escape(string) { + return string.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1'); + } + + function delay(ms) { + return function (arg) { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(arg); + }, ms); + }); + }; + } + + function asArray(arrayLike) { + var array = []; + var length = arrayLike.length; + for (var i = 0; i < length; i++) array.push(arrayLike[i]); + return array; + } + + function escapeXhtml(string) { + return string.replace(/#/g, '%23').replace(/\n/g, '%0A'); + } + + function width(node) { + var leftBorder = px(node, 'border-left-width'); + var rightBorder = px(node, 'border-right-width'); + return node.scrollWidth + leftBorder + rightBorder; + } + + function height(node) { + var topBorder = px(node, 'border-top-width'); + var bottomBorder = px(node, 'border-bottom-width'); + return node.scrollHeight + topBorder + bottomBorder; + } + + function px(node, styleProperty) { + var value = window.getComputedStyle(node).getPropertyValue(styleProperty); + return parseFloat(value.replace('px', '')); + } + } + + function newInliner() { + var URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/g; + + return { + inlineAll: inlineAll, + shouldProcess: shouldProcess, + impl: { + readUrls: readUrls, + inline: inline + } + }; + + function shouldProcess(string) { + return string.search(URL_REGEX) !== -1; + } + + function readUrls(string) { + var result = []; + var match; + while ((match = URL_REGEX.exec(string)) !== null) { + result.push(match[1]); + } + return result.filter(function (url) { + return !util.isDataUrl(url); + }); + } + + function inline(string, url, baseUrl, get) { + return Promise.resolve(url) + .then(function (url) { + return baseUrl ? util.resolveUrl(url, baseUrl) : url; + }) + .then(get || util.getAndEncode) + .then(function (data) { + return util.dataAsUrl(data, util.mimeType(url)); + }) + .then(function (dataUrl) { + return string.replace(urlAsRegex(url), '$1' + dataUrl + '$3'); + }); + + function urlAsRegex(url) { + return new RegExp('(url\\([\'"]?)(' + util.escape(url) + ')([\'"]?\\))', 'g'); + } + } + + function inlineAll(string, baseUrl, get) { + if (nothingToInline()) return Promise.resolve(string); + + return Promise.resolve(string) + .then(readUrls) + .then(function (urls) { + var done = Promise.resolve(string); + urls.forEach(function (url) { + done = done.then(function (string) { + return inline(string, url, baseUrl, get); + }); + }); + return done; + }); + + function nothingToInline() { + return !shouldProcess(string); + } + } + } + + function newFontFaces() { + return { + resolveAll: resolveAll, + impl: { + readAll: readAll + } + }; + + function resolveAll() { + return readAll(document) + .then(function (webFonts) { + return Promise.all( + webFonts.map(function (webFont) { + return webFont.resolve(); + }) + ); + }) + .then(function (cssStrings) { + return cssStrings.join('\n'); + }); + } + + function readAll() { + return Promise.resolve(util.asArray(document.styleSheets)) + .then(getCssRules) + .then(selectWebFontRules) + .then(function (rules) { + return rules.map(newWebFont); + }); + + function selectWebFontRules(cssRules) { + return cssRules + .filter(function (rule) { + return rule.type === CSSRule.FONT_FACE_RULE; + }) + .filter(function (rule) { + return inliner.shouldProcess(rule.style.getPropertyValue('src')); + }); + } + + function getCssRules(styleSheets) { + var cssRules = []; + styleSheets.forEach(function (sheet) { + try { + util.asArray(sheet.cssRules || []).forEach(cssRules.push.bind(cssRules)); + } catch (e) { + console.log('Error while reading CSS rules from ' + sheet.href, e.toString()); + } + }); + return cssRules; + } + + function newWebFont(webFontRule) { + return { + resolve: function resolve() { + var baseUrl = (webFontRule.parentStyleSheet || {}).href; + return inliner.inlineAll(webFontRule.cssText, baseUrl); + }, + src: function () { + return webFontRule.style.getPropertyValue('src'); + } + }; + } + } + } + + function newImages() { + return { + inlineAll: inlineAll, + impl: { + newImage: newImage + } + }; + + function newImage(element) { + return { + inline: inline + }; + + function inline(get) { + if (util.isDataUrl(element.src)) return Promise.resolve(); + + return Promise.resolve(element.src) + .then(get || util.getAndEncode) + .then(function (data) { + return util.dataAsUrl(data, util.mimeType(element.src)); + }) + .then(function (dataUrl) { + return new Promise(function (resolve, reject) { + element.onload = resolve; + element.onerror = reject; + element.src = dataUrl; + }); + }); + } + } + + function inlineAll(node) { + if (!(node instanceof Element)) return Promise.resolve(node); + + return inlineBackground(node) + .then(function () { + if (node instanceof HTMLImageElement) + return newImage(node).inline(); + else + return Promise.all( + util.asArray(node.childNodes).map(function (child) { + return inlineAll(child); + }) + ); + }); + + function inlineBackground(node) { + var background = node.style.getPropertyValue('background'); + + if (!background) return Promise.resolve(node); + + return inliner.inlineAll(background) + .then(function (inlined) { + node.style.setProperty( + 'background', + inlined, + node.style.getPropertyPriority('background') + ); + }) + .then(function () { + return node; + }); + } + } + } +})(this); diff --git a/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/DesignPropEdit.vue b/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/DesignPropEdit.vue index 5109b61..0dc938f 100644 --- a/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/DesignPropEdit.vue +++ b/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/DesignPropEdit.vue @@ -262,10 +262,11 @@
-
-
+ > -
-
w:{{ imageSize.width }} - h:{{ imageSize.height }} -
- +
+
w:{{ imageSize.width }} + h:{{ imageSize.height }} +
+ *仅供参考 +
+
+
-
-
@@ -309,19 +310,24 @@ import {reactive, watch} from 'vue' import {useMessage} from "@/hooks/web/useMessage"; import {DraftDesignDataApi} from "@/api/oms/draftdesigndata"; import DraftDesign from "@/components/DraftDesign/index.vue"; -import {ShapeType} from "@/components/DraftDesign/config"; +import {getHtml, ShapeType} from "@/components/DraftDesign/config"; import {ProductCareItemApi} from "@/api/oms/productcareitem"; import {replaceDomain} from "@/utils"; import * as FileApi from "@/api/infra/file"; import {ProductInfoApi} from "@/api/oms/productinfo"; //@ts-ignore -import domtoimage from 'dom-to-image'; +// import domtoimage from './dom-to-image.js'; import {GroupTypeEnum} from "@/components/DraftDesign/config/constant"; import {useI18n} from "@/hooks/web/useI18n"; const {t} = useI18n() - +const svgSize= computed(() => { + return { + width: that.svgWidth * 10, + height: that.svgHeight * 10, + } +}) const emit = defineEmits(['change', 'initSucceed']) const draftDesignEditRef = ref() const that = reactive({ @@ -351,6 +357,7 @@ const that = reactive({ changeCount: 0, previewUrl: "", previewData: '', + sourceFile: '', // 源文件 html ingredientInfoList: [], sizeInfo: null, brandId: null, @@ -583,15 +590,27 @@ const getIngredientInfoListByType = (type) => { } const uploadFile = async (fileName) => { return new Promise((resolve, reject) => { - let svgElement = document.getElementById(that.svgId); + document.querySelector(`#${that.svgId} .bg-info`).style.width = `${that.svgWidth}px` + document.querySelector(`#${that.svgId} .bg-info`).style.height = `${that.svgHeight}px` + + console.log("svgElement?.innerHTML",) +// 创建一个 Blob 对象,用于保存 SVG 文件 + saveMaxSvg(svgElement,fileName); +// // 创建一个链接元素来触发下载 +// const link = document.createElement('a'); +// link.href = URL.createObjectURL(blob); +// link.download = 'graph.html'; // 指定下载文件名 +// // 触发点击事件来下载文件 +// link.click(); domtoimage.toBlob(svgElement, { - width: that.svgWidth + 5, - height: that.svgHeight + 5, - type: 'image/jpeg', + width: that.svgWidth + 10, + height: that.svgHeight + 10, + type: 'image/png', + scale:4, quality: 1 }).then((blob) => { - const file = new File([blob], fileName, {type: 'image/jpeg'}); + const file = new File([blob], fileName, {type: 'image/png'}); FileApi.updateFile({file: file}) .then((res) => { if (res.code === 0) { @@ -612,6 +631,22 @@ const uploadFile = async (fileName) => { }); } +const saveMaxSvg = (svgElement,fileName)=>{ + const htmlStr = getHtml(`${svgElement?.parentElement?.innerHTML}`); + const blob = new Blob([htmlStr], { type: 'text/html' }); + const file = new File([blob], fileName+".html", {type: 'text/html'}); + FileApi.updateFile({file: file}) + .then((res) => { + console.log("res"+res) + if (res.code === 0) { + that.sourceFile = res.data + } else { + } + }).catch((res) => { + + }) +} + const deleteList = (key, index) => { const item = cloneDeep(that.propInfo[key]) @@ -1310,6 +1345,7 @@ const getPropInfo = () => { }) // 成功后再获取图片 draftDesignEditRef.value.toSVGData((url) => { + that.previewUrl = url that.hideCreate = false; setTimeout(() => { @@ -1323,6 +1359,8 @@ const getPropInfo = () => { const svgDom = document.querySelector(`#${that.svgId} svg`).getBoundingClientRect() that.svgHeight = dom.height; that.svgWidth = dom.width; + + console.log("that.svgHeight",that.svgHeight,that.svgWidth) document.querySelector(`#${that.svgId} svg`).style.left = `${svgDom.left - dom.left}px`; document.querySelector(`#${that.svgId} svg`).style.top = `${svgDom.top - dom.top}px`; // that.pageLoading.close() @@ -1348,6 +1386,7 @@ const getPropInfo = () => { // 设计稿id draftDesignId: that.draftDesignId, previewImage: that.previewData, + sourceFile: that.sourceFile, propInfo: that.propInfo }) }) diff --git a/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/node/TextCellNode.vue b/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/node/TextCellNode.vue index a6028b6..be8ccb0 100644 --- a/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/node/TextCellNode.vue +++ b/hangtag-ui/hangtag-ui-front/src/components/DraftDesign/components/node/TextCellNode.vue @@ -12,7 +12,8 @@ + style="word-break: keep-all; white-space: normal; overflow-wrap: break-word;hyphens: auto;" + v-html="safeHtml(textShowLabel)"> { + return ` + + + + + oms稿件预览源文件 + + + +
+ *仅供参考 +
+ ${htmlStr} + + + + + + + ` +} diff --git a/hangtag-ui/hangtag-ui-front/src/locales/en.ts b/hangtag-ui/hangtag-ui-front/src/locales/en.ts index a942e99..9b240ff 100644 --- a/hangtag-ui/hangtag-ui-front/src/locales/en.ts +++ b/hangtag-ui/hangtag-ui-front/src/locales/en.ts @@ -671,6 +671,7 @@ export default { "viewOrder": "viewOrder", "backHome": "BackHome", "tipsDataChange": "DataChange Click Update Button", + "downloadSuccess": "downloadSuccess", }, "productDialogList": { diff --git a/hangtag-ui/hangtag-ui-front/src/locales/zh-CN.ts b/hangtag-ui/hangtag-ui-front/src/locales/zh-CN.ts index 6a8db57..b0cecd9 100644 --- a/hangtag-ui/hangtag-ui-front/src/locales/zh-CN.ts +++ b/hangtag-ui/hangtag-ui-front/src/locales/zh-CN.ts @@ -667,6 +667,7 @@ export default { viewOrder: "查看订单", backHome: "返回首页", tipsDataChange: "数据已更新,请点击更新按钮", + downloadSuccess: "下载成功", }, productDialogList:{ title: '产品列表', diff --git a/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductItem1.vue b/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductItem1.vue index 861b838..fb7884f 100644 --- a/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductItem1.vue +++ b/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductItem1.vue @@ -92,7 +92,21 @@ const props = defineProps({ type: Object, required: true, default: () => ({ - previewImage: '' + productId: "", + orderQty: 1, + propInfo: "", + propOrderByList: [], + draftDesignId: "", + previewImage: "", + sourceFile: "", + draftDesignInfo: {}, + specInfo: {} + }) + }, + product: { + type: Object, + required: true, + default: () => ({ }) }, badge: { @@ -101,12 +115,21 @@ const props = defineProps({ default: '' } }) +const that = { + productInfo:{}, +} const getLabelName = (item) => { if (item.shape === ShapeType.vueShapeImage) { return item.groupName + ":" } return item.groupName + ":" } +watch(()=>props.product,(value, oldValue, onCleanup)=>{ + that.productInfo = value +},{ + deep: true, + immediate: true +}) const getValue = (item:any) => { let arr = []; for (let i = 0; i < item.dataInfo.length; i++) { @@ -117,6 +140,7 @@ const getValue = (item:any) => { } return arr.join(";") } + const formData = ref({}) watch(() => props.productInfo, (newVal) => { console.log("newVal",newVal) @@ -148,7 +172,8 @@ const propInfoOrderByList = computed(() => { const previewUrl = computed(() => { return formData.value.previewImage || "" }) -const init = ()=>{ + +const init = (productInfo:any)=>{ } const submit = (data:any) => { @@ -161,6 +186,7 @@ const submit = (data:any) => { tmpData.propOrderByList = data.propOrderByList; tmpData.draftDesignId = data.draftDesignId; tmpData.previewImage = data.previewImage; + tmpData.sourceFile = data.sourceFile; tmpData.draftDesignInfo = {...data.draftDesignInfo}; // 将颜色信息保存到规格信息中 tmpData.specInfo = { @@ -181,12 +207,66 @@ const change = () => { emit("update:productInfo", formData.value) emit("change", formData.value) } +const getDownloadName = ()=>{ + console.log("getFileName@@",that.productInfo) + if(that.productInfo){ + return `${that.productInfo.name}(${that.productInfo.code})` + } + return new Date().getTime(); +} +const getFileName = ()=>{ + if(formData.value.sourceFile){ + return `${getDownloadName()}.html` + } + return `${getDownloadName()}.png` +} const imagePreview = (imgUrl: string) => { + + addDownloadImg(formData.value.sourceFile || formData.value.previewImage ,getFileName()) createImageViewer({ zIndex: 9999999, urlList: [imgUrl] }) } + +const addDownloadImg = (url: string,name = 'img.jpg') => { + 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; // 设置下载文件名 + link.click(); // 触发下载 + URL.revokeObjectURL(blobUrl); // 释放 blob URL 资源 + }) + .catch((err) => { + console.error("下载失败:", err); + }); + }); + } + }, 100); +}; + + + defineExpose({ init }) diff --git a/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductSkuList.vue b/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductSkuList.vue index 878c3b2..860c947 100644 --- a/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductSkuList.vue +++ b/hangtag-ui/hangtag-ui-front/src/views/oms/order/createorder/components/OrderAddProductStep/ProductSkuList.vue @@ -12,6 +12,7 @@