Commit c9be6378 by wusiyi

Merge branch 'dev_heat_transfer_printing' into 'master'

Dev heat transfer printing

See merge request !2
parents e2fcbfe9 7506e6b5
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"name": "JomallProductionAssistant", "name": "JomallProductionAssistant",
"productName": "JomallProductionAssistant", "productName": "JomallProductionAssistant",
"description": "", "description": "",
"version": "1.0.39", "version": "1.0.40",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
"bignumber.js": "^9.2.1", "bignumber.js": "^9.2.1",
"compressing": "^1.10.1", "compressing": "^1.10.1",
"core-js": "^3.6.4", "core-js": "^3.6.4",
"dayjs": "^1.11.21",
"electron-builder": "^24.13.3", "electron-builder": "^24.13.3",
"electron-dialogs": "^1.4.0", "electron-dialogs": "^1.4.0",
"electron-icon-builder": "^2.0.1", "electron-icon-builder": "^2.0.1",
...@@ -38,6 +39,7 @@ ...@@ -38,6 +39,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"fabric": "^6.7.0", "fabric": "^6.7.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"lodash": "^4.18.1",
"lodash-id": "^0.14.0", "lodash-id": "^0.14.0",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"moment": "^2.30.1", "moment": "^2.30.1",
...@@ -75,6 +77,5 @@ ...@@ -75,6 +77,5 @@
"pug-plain-loader": "^1.0.0", "pug-plain-loader": "^1.0.0",
"vue-cli-plugin-electron-builder": "^1.4.6", "vue-cli-plugin-electron-builder": "^1.4.6",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.11"
}, }
"__npminstall_done": false
} }
<template>
<div class="commodity-card" :class="{ active: active }">
<div class="commodity-card-image">
<div class="before" :style="{ paddingTop: paddingTop }"></div>
<div class="image">
<img :src="mainImageSrc" />
</div>
<!-- 左上角 -->
<div class="img_top_left">
<span v-if="showSelectIcon" class="select-icon"></span>
<slot name="top_left"></slot>
</div>
<!-- 右上角 -->
<div class="img_top_right">
<slot name="top_right"></slot>
</div>
<!-- 左下角 -->
<div class="img_bottom_left">
<slot name="bottom_left"></slot>
</div>
<!-- 右下角 -->
<div class="img_bottom_right">
<slot name="operations"></slot>
</div>
</div>
<div class="commodity-card-info">
<!-- 颜色图片列表 -->
<div v-if="hasImageList" class="color-image flex flex-gap-10">
<div
v-for="(img, index) in imageList"
:key="index"
class="color-image-item"
>
<img :src="getItemImagePath(img)" />
</div>
</div>
<!-- 时间信息插槽 -->
<slot name="time"></slot>
<!-- 名称和价格 -->
<!-- <div
v-if="cardItem?.productName"
class="commodity-card-name-price flex flex-justify-space-between"
>
<slot name="productName">
<span>{{ cardItem?.productName }}</span>
</slot>
<slot name="price"></slot>
</div> -->
<!-- SKU信息 -->
<div
v-if="cardItem?.sku"
class="commodity-card-sku"
@click.stop="copy(String(cardItem?.sku))"
>
<!-- <img src="../assets/images/id.png" height="20" /> -->
<span>{{ cardItem?.sku }}</span>
</div>
<slot name="images"></slot>
<!-- 自定义信息 -->
<slot name="info"></slot>
</div>
</div>
</template>
<script>
/**
* 通用卡片封装 BaseCard
* 基于ElementUI el-card二次封装,配置驱动 + 多插槽拓展
* @prop {Object} cardConfig 卡片核心配置对象
* @prop {String} bodyPadding 卡片body内边距,默认 0px
*/
export default {
name: "BaseCard",
props: {
cardItem: {
type: Object,
default: () => ({})
},
active: {
type: Boolean,
default: false
},
showSelectIcon: {
type: Boolean,
default: true
},
showSku: {
type: Boolean,
default: false
},
showProductInfo: {
type: Boolean,
default: false
},
showImageList: {
type: Boolean,
default: false
},
imageField: {
type: String,
default: "variantImage"
},
imageListField: {
type: String,
default: "imageList"
},
imagePathField: {
type: String,
default: "imagePath"
},
paddingTop: {
type: String,
default: "120%"
}
},
data() {
return { imageList: [] };
},
computed: {
hasImageList() {
return true;
},
mainImageSrc() {
const item = this.cardItem;
// 使用索引访问避免联合类型的属性访问问题
if (
this.imageField === "variantImage" &&
typeof item[this.imageField] === "string"
) {
return item[this.imageField];
}
if (
this.imageField === "mainImage" &&
typeof item[this.imageField] === "string"
) {
return item[this.imageField];
}
// 默认返回空字符串
return "";
}
},
methods: {}
};
</script>
<style lang="less" scoped>
.commodity-card {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.commodity-card-image {
position: relative;
border: 1px solid #eee;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
.img_top_left {
position: absolute;
top: 5px;
left: 5px;
display: flex;
align-items: center;
gap: 4px;
}
.img_top_right {
position: absolute;
top: 0px;
right: 0px;
}
.img_bottom_left {
position: absolute;
bottom: 5px;
left: 5px;
}
.img_bottom_right {
position: absolute;
bottom: 5px;
right: 5px;
display: flex;
align-items: center;
:deep(.svg-icon) {
width: 2em;
height: 2em;
}
}
}
.before {
height: 0;
}
.image {
position: absolute;
// position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
}
}
.color-image {
.color-image-item {
width: 50px;
height: 50px;
}
img {
width: 100%;
height: 100%;
}
}
.commodity-card-info {
flex: 1;
padding: 10px;
font-size: 14px;
background: linear-gradient(
90deg,
rgba(228, 228, 228, 0.4) 0%,
rgba(255, 255, 255, 1) 100%
);
}
.commodity-card-name-price {
height: 30px;
line-height: 30px;
}
.commodity-card-sku {
display: flex;
align-items: center;
gap: 5px;
background-color: #fff;
cursor: pointer;
}
.select-icon {
position: relative;
display: block;
width: 16px;
height: 16px;
border: 1px solid #cccccc;
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.4) inset;
border-radius: 8px;
background: #fff;
}
.commodity-card.active .select-icon {
background: #4168ff;
position: relative;
border-color: #4168ff;
box-shadow: none;
font-size: 18px;
top: -1px;
left: -1px;
}
.commodity-card.active .select-icon::after {
position: absolute;
content: "";
top: 0px;
left: 4px;
width: 4px;
height: 8px;
border-width: 2px;
border-style: solid;
border-color: transparent #fff #fff transparent;
transform: rotate(45deg) scale(0.8);
}
</style>
<template>
<div class="base-table-wrap">
<!-- ElementUI 原生表格,所有属性透传 -->
<el-table
ref="tableRef"
:data="tableData"
v-bind="$attrs"
v-on="$listeners"
@selection-change="handleSelectionChange"
>
<!-- 多选框列 -->
<el-table-column
v-if="hasSelection"
type="selection"
width="55"
align="center"
/>
<!-- 序号列 -->
<el-table-column
v-if="hasIndex"
type="index"
label="序号"
width="50"
align="center"
/>
<!-- 动态循环渲染列配置 -->
<el-table-column
v-for="col in tableConfig"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:fixed="col.fixed"
:align="col.align || 'center'"
:header-align="col.headerAlign || col.align || 'center'"
:sortable="col.sortable"
:show-overflow-tooltip="col.showOverflowTooltip ?? true"
>
<template slot-scope="scope">
<!-- 1. 自定义render -->
<span
v-if="col.render"
v-html="col.render(scope.row, scope.column, scope.$index)"
></span>
<!-- 3. 图片 -->
<el-image
v-else-if="col.type === 'image'"
:src="scope.row[col.prop]"
style="width: 40px; height: 40px"
fit="cover"
:preview-src-list="[scope.row[col.prop]]"
/>
<!-- 4. 开关 -->
<el-switch
v-else-if="col.type === 'switch'"
v-model="scope.row[col.prop]"
@change="col.onSwitchChange && col.onSwitchChange(scope.row)"
active-text="开启"
inactive-text="关闭"
/>
<!-- 5. 操作按钮 -->
<div v-else-if="col.type === 'button'" class="table-btn-group">
<el-button
v-for="btn in col.btns"
:key="btn.key"
size="mini"
:type="btn.type || 'primary'"
:icon="btn.icon"
:disabled="btn.disabled ? btn.disabled(scope.row) : false"
@click="btn.click(scope.row, scope.$index)"
>
{{ btn.label }}
</el-button>
</div>
<!-- 6. 外部自定义插槽 -->
<slot v-else-if="col.slotName" :name="col.slotName" v-bind="scope" />
<!-- 7. 普通文本兜底(现在一定能拿到scope) -->
<span v-else>{{ scope.row[col.prop] ?? "" }}</span>
</template>
<!-- 7. 默认文本展示 -->
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
small
v-if="showPagination"
class="table-pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageInfo.currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageInfo.total"
/>
</div>
</template>
<script>
export default {
name: "BaseTable",
props: {
/**
* 表格数据源
*/
tableData: {
type: Array,
default: () => []
},
/**
* 列配置数组【核心】
* @type Array<Object>
* 字段说明:
* prop: 绑定字段名
* label: 表头文字
* width/minWidth: 列宽
* fixed: 固定列 left/right
* align/headerAlign: 对齐方式
* sortable: 是否可排序
* showOverflowTooltip: 文字溢出悬浮提示
* type: tag/image/switch/button 特殊类型渲染
* render: 自定义渲染函数(row, column, index) => html字符串
* slotName: 自定义插槽名称
* btns: 操作按钮数组(type=button时生效)
*/
tableConfig: {
type: Array,
required: true
},
/**
* 是否显示多选框
*/
hasSelection: {
type: Boolean,
default: false
},
/**
* 是否显示序号列
*/
hasIndex: {
type: Boolean,
default: false
},
/**
* 是否展示分页
*/
showPagination: {
type: Boolean,
default: true
},
/**
* 分页信息
* { currentPage: 1, pageSize: 10, total: 0 }
*/
pageInfo: {
type: Object,
default: () => ({
currentPage: 1,
pageSize: 10,
total: 0
})
}
},
data() {
return {
// 选中行存储
selectedRows: []
};
},
methods: {
/**
* 多选框选中回调
*/
handleSelectionChange(val) {
this.selectedRows = val;
this.$emit("selection", val);
},
/**
* 获取当前选中的所有行数据
*/
getSelectedRows() {
return this.selectedRows;
},
/**
* 清空多选框选中状态
*/
clearSelection() {
this.$refs.tableRef.clearSelection();
this.selectedRows = [];
},
/**
* 每页条数改变
*/
handleSizeChange(size) {
this.$emit("page-size-change", size);
},
/**
* 页码改变
*/
handleCurrentChange(page) {
this.$emit("page-current-change", page);
},
toggleAllSelection(value) {
this.$refs.tableRef.toggleAllSelection(value);
}
},
mounted() {
// console.log("tableConfig", this.tableConfig);
// console.log("tableData", this.tableData);
}
};
</script>
<style scoped>
.el-table {
max-height: calc(100% - 75px);
}
.base-table-wrap {
width: 100%;
height: 100%;
/* overflow-y: scroll; */
}
.table-pagination {
margin-top: 10px;
text-align: center;
}
.table-btn-group {
display: flex;
gap: 4px;
flex-wrap: wrap;
justify-content: center;
}
</style>
<template>
<div class="log-list">
<div v-for="l in logList" :key="l.id" class="log-item flex">
<div class="log-item-icon">
<el-icon name="a-2labadianji3x" />
</div>
<div class="log-item-time">
<span>{{ l.local && `${l.local}:` }}{{ l.createTime }}</span>
</div>
<div v-if="l.employeeName" class="log-item-name">
<span>{{ l.employeeName }}</span>
</div>
<div class="log-item-content" :title="l.description">
<span>{{ l.description }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: "LogList",
props: {
logList: {
default: () => [],
type: Array
}
}
};
</script>
<style lang="less" scoped>
.log-list {
height: 500px;
overflow: auto;
font-size: 14px;
}
.log-item {
line-height: 26px;
white-space: pre-wrap;
word-break: break-all;
}
.log-item div:not(:last-child) {
margin-right: 6px;
}
.log-item-time {
// width: 220px;
}
.log-item-name {
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-item-content {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
// white-space: nowrap;
}
</style>
<template>
<el-dialog
:visible.sync="visible"
width="92%"
top="5vh"
:before-close="handleClose"
:show-close="true"
>
<div class="order-detail-container" style="display: flex;height: 800px;">
<!-- 左侧图片区域 -->
<div class="left-images" style="width: 50%; text-align: center;">
<el-carousel
v-if="parseImageAry(info.imageAry).length > 0"
style="height: 100%"
:autoplay="false"
>
<el-carousel-item
v-for="(item, index) in parseImageAry(info.imageAry)"
:key="index"
style="height: 100%"
>
<div class="left-image">
<b v-show="item?.title && item?.url">
{{ item.title }}
<span
v-if="item?.id"
style="
text-decoration: underline;
cursor: pointer;
color: blue;
"
>
(DID:{{ item.id }}
</span>
</b>
<img :src="item.url" alt="" />
</div>
</el-carousel-item>
</el-carousel>
</div>
<!-- 右侧信息区域 -->
<div class="right-info" style="flex: 1;">
<!-- 客户+订单号栏 -->
<div
class="customer-order-row"
style="display: flex; justify-content: space-between; border: 1px solid #e4e7ed; padding: 15px 20px; margin-bottom: 18px;"
>
<div class="text-label">
<span>客户:</span>
<span style="color: #f56c6c;font-size: 20px;font-weight: 700;">{{
info.userMark
}}</span>
</div>
<div class="text-label">
<span>订单号:</span>
<span style="color: #f56c6c;font-size: 20px;font-weight: 700;">{{
info.factoryOrderNumber
}}</span>
</div>
</div>
<!-- 操作单信息区块 -->
<div class="div-text"><b>操作单信息</b></div>
<div
class="info-box"
style="border: 1px solid #e4e7ed; padding: 18px 22px; margin-bottom: 26px;"
>
<div
style="display: grid; grid-template-columns: 1fr 1fr; row-gap: 14px;"
>
<div class="text-label">
<span>操作单号:</span>{{ info.operationNo }}
</div>
<div class="text-label">
<span>生产工艺:</span>{{ info.craftName }}
</div>
<div class="text-label"><span>基版:</span>{{ info.baseSku }}</div>
<div class="text-label">
<span>变体SKU:</span>{{ info.variantSku }}
</div>
<div class="text-label"><span>数量:</span>{{ info.quantity }}</div>
<div class="text-label"><span>尺寸:</span>{{ info.size }}</div>
<div class="text-label">
<span>店铺单号:</span>{{ info.shopNumber }}
</div>
<div class="text-label">
<span>创建时间:</span>{{ info.createTime }}
</div>
</div>
</div>
<!-- 底部按钮 -->
<div style="text-align: right;">
<el-button type="primary" size="medium" @click="downloadFn"
>下载素材</el-button
>
</div>
</div>
</div>
</el-dialog>
</template>
<script>
import bus from "@/bus";
export default {
name: "OrderDetailDialog",
props: {
// 弹窗显示控制
show: {
type: Boolean,
default: false
},
// 订单详情数据
info: {
type: Object,
default: () => ({})
}
},
computed: {
visible: {
get() {
return this.show;
},
set(val) {
this.$emit("update:show", val);
}
}
},
data() {
return {
scanDownloadCheck: true
};
},
methods: {
handleClose() {
this.$emit("update:show", false);
},
parseImageAry(imageAry) {
if (typeof imageAry !== "string" || !imageAry.trim()) return [];
const trimmed = imageAry.trim();
if (trimmed.startsWith("http")) {
return trimmed
.split(",")
.map(url => ({ url: url.trim() }))
.filter(item => item.url);
}
try {
const parsed = JSON.parse(trimmed);
if (!Array.isArray(parsed)) return [{ url: trimmed }];
const result = [];
for (const item of parsed) {
if (typeof item === "string" && item) {
result.push({ url: item });
} else if (item && typeof item === "object" && "url" in item) {
const url = item.url;
if (url) {
result.push({
url,
title: item.title
});
}
}
}
return result;
} catch {
return [{ url: trimmed }];
}
},
downloadFn() {
bus.$emit("downLoadFile", this.info);
}
}
};
</script>
<style scoped lang="less">
.left-images {
display: flex;
width: 95%;
height: 100%;
flex-direction: column;
b {
color: black;
text-align: center;
margin-bottom: 15px;
}
.left-image {
display: flex;
height: 100%;
flex-direction: column;
justify-content: center;
img {
height: auto;
width: 100%;
max-height: 90%;
object-fit: contain;
}
b {
color: black;
font-size: 18px;
margin-bottom: 15px;
}
}
}
.div-text {
b {
position: relative;
background: white;
top: 9px;
left: 13px;
padding: 0 10px;
font-size: 22px;
color: black;
z-index: 3;
}
}
.text-label {
flex: 50%;
font-size: 16px;
}
::v-deep.el-carousel {
.el-carousel__container {
height: 100% !important;
}
}
</style>
...@@ -19,9 +19,9 @@ ...@@ -19,9 +19,9 @@
:draggable="true" :draggable="true"
:resizable="true" :resizable="true"
:rotatable="true" :rotatable="true"
@dragstop="(a) => dragStop(a, item)" @dragstop="a => dragStop(a, item)"
@resizestop="(a) => resizeStop(a, item)" @resizestop="a => resizeStop(a, item)"
@rotatestop="(a) => rotating(a, item)" @rotatestop="a => rotating(a, item)"
:angle="item.r" :angle="item.r"
> >
<div <div
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
:class="{ 'no-border-grid': systemSetting.gridShow !== 1 }" :class="{ 'no-border-grid': systemSetting.gridShow !== 1 }"
:style="{ :style="{
width: gridWH.w + 'px', width: gridWH.w + 'px',
height: gridWH.h + 'px', height: gridWH.h + 'px'
}" }"
class="grid" class="grid"
> >
...@@ -89,9 +89,9 @@ export default { ...@@ -89,9 +89,9 @@ export default {
// console.log(data); // console.log(data);
this.$set(item, "r", data.angle); this.$set(item, "r", data.angle);
this.imgHistoryList.push(JSON.parse(JSON.stringify(this.imgList))); this.imgHistoryList.push(JSON.parse(JSON.stringify(this.imgList)));
}, }
}, },
mounted() {}, mounted() {}
}; };
</script> </script>
<style lang="less"> <style lang="less">
...@@ -105,7 +105,7 @@ export default { ...@@ -105,7 +105,7 @@ export default {
#line { #line {
position: absolute; position: absolute;
left: 5%; left: 5%;
top: 50%; top: 46%;
transform: translate(0, -50%); transform: translate(0, -50%);
background-color: #f0f2f6; background-color: #f0f2f6;
} }
......
...@@ -21,7 +21,8 @@ export const pathMap = { ...@@ -21,7 +21,8 @@ export const pathMap = {
CN: "factory/podJomallOrderProductCn/completeDelivery", CN: "factory/podJomallOrderProductCn/completeDelivery",
US: "factory/podJomallOrderProductUs/completeDelivery", US: "factory/podJomallOrderProductUs/completeDelivery",
GC: "factory/podJomallOrderProduct/completeDelivery", GC: "factory/podJomallOrderProduct/completeDelivery",
OP: "factory/podOrderOperation/printerCompleteDelivery" OP: "factory/podOrderOperation/printerCompleteDelivery",
DTF: "factory/podOrderOperation/completeDelivery"
}, },
//惠立彩素材下载接口 //惠立彩素材下载接口
processTransparentBackground: { processTransparentBackground: {
...@@ -34,14 +35,57 @@ export const pathMap = { ...@@ -34,14 +35,57 @@ export const pathMap = {
}, },
checkInventory: { checkInventory: {
OP: "factory/podOrderOperation/check-inventory" OP: "factory/podOrderOperation/check-inventory"
} },
//烫画
//获取生产配置
productionConfig: "factory/production-config/get",
//更新生产配置
productionConfigUpdate: "factory/production-config/update",
//排版列表(分页)
productionSoftware: "factory/podOrderBatchDownload/page-production-software",
//查询该批次下的所有素材
listMaterialsByBatch: "factory/podOrderBatchDownload/list-materials-by-batch",
//重新合成排版
psReComposingDesignImages:
"factory/podOrderBatchDownload/psReComposingDesignImages",
//根据批次分页查询操作单
listOperationByBatch: "factory/podOrderBatchDownload/listOperationByBatch",
//排版
replenishmentComposingDesignImages:
"factory/podOrderOperation/softwareProduction-replenishmentComposingDesignImages",
//获取单个批次号信息
podOrderBatchDownload: "factory/podOrderBatchDownload/get",
//操作日志
getPodOrderLog: "factory/podOrderLog/getPodOrderLog"
};
// 操作单状态枚举
export const statusMap = {
待排单: "PENDING_SCHEDULE",
待拣胚: "PENDING_PICK",
待补胚: "PENDING_REPLENISH",
生产中: "IN_PRODUCTION",
待配货: "PENDING_PACKING",
配货完成: "PACKING_COMPLETED",
已完成: "COMPLETED",
已取消: "CANCELLED",
已归档: "ARCHIVE"
}; };
export function autoRegisterRouter(router, funcKey, handler) { export function autoRegisterRouter(router, funcKey, handler) {
const configGroup = pathMap[funcKey]; const pathConfig = pathMap[funcKey];
// 遍历配置自动注册路由 // 根据类型收集所有需要注册的接口路径
Object.values(configGroup).forEach(el => { let pathList = [];
router.post(`/${el}`, (req, res) => { if (typeof pathConfig === "string") {
// 字符串类型:单一路径(如 productionConfig)
pathList = [pathConfig];
} else if (typeof pathConfig === "object" && pathConfig !== null) {
// 对象类型:多站点路径集合(如 findByPodProductionNo)
pathList = Object.values(pathConfig);
}
// 批量注册路由
pathList.forEach(path => {
router.post(`/${path}`, (req, res) => {
handler(req, res); handler(req, res);
}); });
}); });
......
[
{
"name": "Shopify",
"lowercase": "shopify",
"type": "SHOPIFY",
"icon": "@/assets/images/icon/logo-1.png",
"cycle": "today",
"route": "store_profits_shopify"
},
{
"name": "AliExpress",
"lowercase": "aliExpress",
"type": "AE",
"icon": "@/assets/images/icon/logo-3.png",
"cycle": "today",
"route": "store_profits_aliexpress"
},
{
"name": "Amazon",
"lowercase": "amazon",
"type": "AMAZON",
"icon": "@/assets/images/icon/logo-2.png",
"cycle": "yesterday",
"route": "store_profits_amazon"
},
{
"name": "Magento",
"lowercase": "magento",
"type": "MAGENTO",
"icon": "@/assets/images/icon/logo-6.png",
"cycle": "today",
"route": "store_profits_magento"
},
{
"name": "SHOPLINE",
"lowercase": "shopline",
"type": "SHOPLINE",
"cycle": "today",
"icon": "@/assets/images/icon/logo-4.png",
"route": "store_profits_shopline"
},
{
"name": "Shoplazza",
"lowercase": "shoplazza",
"type": "SHOPLAZZA",
"cycle": "today",
"icon": "@/assets/images/icon/logo-5.png",
"route": "store_profits_shoplazza"
},
{
"name": "temu",
"lowercase": "temu",
"type": "TEMU",
"cycle": "today",
"icon": "@/assets/images/icon/temu.png",
"route": "store_profits_temu"
},
{
"name": "eBay",
"lowercase": "ebay",
"type": "EBAY",
"cycle": "today",
"icon": "@/assets/images/icon/eBay.png",
"route": "store_profits_ebay"
},
{
"name": "Etsy",
"lowercase": "etsy",
"type": "ETSY",
"cycle": "today",
"icon": "@/assets/images/icon/etsy.png",
"route": "store_profits_etsy"
},
{
"name": "Alibaba",
"lowercase": "alibaba",
"type": "ALIBABA",
"cycle": "today",
"icon": "@/assets/images/icon/alibaba.png",
"route": "store_profits_alibaba"
},
{
"name": "walmart",
"lowercase": "walmart",
"type": "WALMART",
"cycle": "today",
"icon": "@/assets/images/icon/walmart.png",
"route": "store_profits_walmart"
},
{
"name": "tiktokshop",
"lowercase": "tiktok",
"type": "TIKTOK",
"cycle": "today",
"icon": "@/assets/images/icon/tiktokshop.png",
"route": "store_profits_tiktokshop"
},
{
"name": "Customize",
"lowercase": "customize",
"type": "CUSTOMIZE",
"cycle": "today",
"icon": "@/assets/images/icon/customize.png",
"route": "store_profits_customize"
},
{
"name": "1688",
"type": "ALIBABA_DOMESTIC",
"cycle": "today",
"icon": "@/assets/images/icon/1688.png",
"route": "store_profits_1688"
}
]
...@@ -8,38 +8,62 @@ const routes = [ ...@@ -8,38 +8,62 @@ const routes = [
path: "/", path: "/",
name: "login", name: "login",
meta: { meta: {
title: "登录", title: "登录"
}, },
component: (resolve) => require(["../views/login/index.vue"], resolve), component: resolve => require(["../views/login/index.vue"], resolve)
}, },
{ {
path: "/design", path: "/design",
name: "design", name: "design",
meta: { meta: {
title: "设计页面", title: "设计页面"
}, },
component: (resolve) => require(["../views/design/index.vue"], resolve), component: resolve => require(["../views/design/tabIndex.vue"], resolve),
}, redirect: "/design/dtg",
children: [
{ // DTG
path: "/design-detail", {
name: "design", path: "dtg",
meta: { name: "design-dtg",
title: "设计详情页面", meta: { title: "DTG生产" },
}, component: resolve => require(["../views/design/index.vue"], resolve)
component: (resolve) => },
require(["../views/design/detail/index.vue"], resolve), // DTF 列表
{
path: "dtf",
name: "design-dtf",
meta: { title: "DTF生产" },
component: resolve => require(["@/views/dtf-list/index.vue"], resolve)
},
// DTF 排版设置
{
path: "dtf/setting",
name: "dtf-setting",
meta: { title: "DTF排版设置" },
component: resolve =>
require(["@/views/dtf-setting/index.vue"], resolve)
},
// DTF 批次详情
{
path: "dtf/batch-detail",
name: "dtf-batch-detail",
meta: { title: "DTF批次详情" },
component: resolve =>
require(["@/views/dtf-batch-detail/detail.vue"], resolve)
}
]
}, },
// 删掉原来独立的 /dtf-setting、/dtf-batch-detail、/dtf-file
{ {
path: "*", path: "*",
redirect: "/", redirect: "/"
}, }
]; ];
const router = new VueRouter({ const router = new VueRouter({
mode: "hash", mode: "hash",
base: process.env.BASE_URL, base: process.env.BASE_URL,
routes, routes
}); });
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
let user = Vue.prototype.$dataStore.get("user"); let user = Vue.prototype.$dataStore.get("user");
...@@ -57,6 +81,8 @@ router.beforeEach((to, from, next) => { ...@@ -57,6 +81,8 @@ router.beforeEach((to, from, next) => {
if (user) { if (user) {
return next(); return next();
} else { } else {
console.log("router", user);
return next("/"); return next("/");
} }
} }
......
...@@ -3,8 +3,11 @@ import { ...@@ -3,8 +3,11 @@ import {
downloadOtherImage, downloadOtherImage,
toSend, toSend,
writeProfileXml, writeProfileXml,
copySingleImage copySingleImage,
createBatchFolder,
downloadDTFFiles
} from "@/server/utils"; } from "@/server/utils";
const { const {
cropImageTransparentEdges, cropImageTransparentEdges,
cropTransparentEdges, cropTransparentEdges,
...@@ -72,7 +75,7 @@ function readEnv() { ...@@ -72,7 +75,7 @@ function readEnv() {
fileEnv = config.fileApiUrl; fileEnv = config.fileApiUrl;
env = config.apiApiHost; env = config.apiApiHost;
visionUrl = config.visionUrl; visionUrl = config.visionUrl;
console.log("配置加载完成:", { fileEnv, env, visionUrl }); // console.log("配置加载完成:", { fileEnv, env, visionUrl });
} catch (err) { } catch (err) {
console.error("读取配置文件失败:", err); console.error("读取配置文件失败:", err);
...@@ -153,6 +156,7 @@ export default { ...@@ -153,6 +156,7 @@ export default {
env = getHostApi().apiApiHost; env = getHostApi().apiApiHost;
fileEnv = getHostApi().fileApiUrl; fileEnv = getHostApi().fileApiUrl;
const params = req.body; const params = req.body;
const targetDir = params.targetDir || "";
const token = req.headers["jwt-token"]; const token = req.headers["jwt-token"];
const forceProduction = params.forceProduction || false; const forceProduction = params.forceProduction || false;
const downloadBySubOrderNumber = pathMap["downloadBySubOrderNumber"]; const downloadBySubOrderNumber = pathMap["downloadBySubOrderNumber"];
...@@ -194,20 +198,23 @@ export default { ...@@ -194,20 +198,23 @@ export default {
let files = [path]; let files = [path];
files = files.map(el => ({ url: `${fileEnv}${el}` })); files = files.map(el => ({ url: `${fileEnv}${el}` }));
let result;
const downloadFunc = if (params.device === 4) {
params.device === 2 ? downloadOtherImage : downloadImage; result = await downloadDTFFiles(files[0].url, targetDir);
const result = await downloadFunc(files, { } else {
processDesignA, const downloadFunc =
processDesignB params.device === 2 ? downloadOtherImage : downloadImage;
}); result = await downloadFunc(files, {
processDesignA,
processDesignB
});
}
console.log("result200", result); console.log("result200", result);
res.json({ code: 200, data: result }); res.json({ code: 200, data: result });
} catch (err) { } catch (err) {
console.error("Download error:", err); console.error("Download error:", err);
res.status(500).json({ code: 500, msg: err }); res.status(500).json({ code: 500, msg: err.message });
} }
}, },
findByPodProductionNo: async (req, res) => { findByPodProductionNo: async (req, res) => {
...@@ -265,7 +272,8 @@ export default { ...@@ -265,7 +272,8 @@ export default {
podJomallOrderUsId: params.podJomallOrderUsId || "" podJomallOrderUsId: params.podJomallOrderUsId || ""
}, },
GC: { id: params.id }, GC: { id: params.id },
OP: { ...params.data } OP: { ...params.data },
DTF: params.ids
}; };
let url = pathMap["completeDelivery"][params.orderType]; let url = pathMap["completeDelivery"][params.orderType];
...@@ -383,7 +391,6 @@ export default { ...@@ -383,7 +391,6 @@ export default {
try { try {
env = getHostApi().apiApiHost; env = getHostApi().apiApiHost;
console.log(`${env}/factory/login`);
let { data } = await axios.post(`${env}/factory/login`, req.body); let { data } = await axios.post(`${env}/factory/login`, req.body);
res.send(data); res.send(data);
} catch (err) { } catch (err) {
...@@ -557,7 +564,8 @@ export default { ...@@ -557,7 +564,8 @@ export default {
//获取国家名称及代码 //获取国家名称及代码
getAllCountry: async (req, res) => { getAllCountry: async (req, res) => {
const token = req.headers["jwt-token"]; const token = req.headers["jwt-token"];
let url = "https://factory.jomalls.com/api/logisticsAddress/getAllCountry"; env = getHostApi().apiApiHost;
let url = `${env}/logisticsAddress/getAllCountry`;
try { try {
let { data } = await axios.get(url, { headers: { "jwt-token": token } }); let { data } = await axios.get(url, { headers: { "jwt-token": token } });
...@@ -653,5 +661,188 @@ export default { ...@@ -653,5 +661,188 @@ export default {
console.log("checkInventory", error); console.log("checkInventory", error);
res.json({ code: 500, msg: error }); res.json({ code: 500, msg: error });
} }
},
createBatchFolder: async (req, res) => {
try {
const { downloadDir, isArray, basePath } = req.body;
const fn = isArray ? createBatchFolder : createBatchFolder;
const data = await fn(basePath, downloadDir);
res.send(data);
} catch (error) {
console.log("createBatchFolder", error);
res.json({ code: 500, msg: error });
}
},
//获取生产配置
productionConfig: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
let url = pathMap["productionConfig"];
try {
let { data } = await axios.get(`${env}/${url}`, {
headers: { "jwt-token": token }
});
res.send(data);
} catch (error) {
console.log("productionConfig", error);
res.json({ code: 500, msg: error });
}
},
//更新生产配置
productionConfigUpdate: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const params = req.body;
let url = pathMap["productionConfigUpdate"];
try {
let { data } = await axios.post(`${env}/${url}`, params, {
headers: { "jwt-token": token }
});
res.send(data);
} catch (error) {
console.log("productionConfigUpdate", error);
res.json({ code: 500, msg: error });
}
},
//排版列表(分页)
productionSoftware: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const params = req.body;
let url = pathMap["productionSoftware"];
try {
let { data } = await axios.post(`${env}/${url}`, params, {
headers: { "jwt-token": token }
});
res.send(data);
} catch (error) {
console.log("productionSoftware", error);
res.json({ code: 500, msg: error });
}
},
//查询该批次下的所有素材
listMaterialsByBatch: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const params = req.body;
let url = pathMap["listMaterialsByBatch"];
try {
let { data } = await axios.get(`${env}/${url}`, {
headers: { "jwt-token": token },
params
});
res.send(data);
} catch (error) {
console.log("listMaterialsByBatch", error);
res.json({ code: 500, msg: error });
}
},
//重新合成排版
psReComposingDesignImages: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const params = req.body;
let url = pathMap["psReComposingDesignImages"];
try {
let { data } = await axios.get(`${env}/${url}`, {
headers: { "jwt-token": token },
params
});
res.send(data);
} catch (error) {
console.log("psReComposingDesignImages", error);
res.json({ code: 500, msg: error });
}
},
//根据批次分页查询操作单
listOperationByBatch: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const params = req.body;
let url = pathMap["listOperationByBatch"];
try {
let { data } = await axios.post(`${env}/${url}`, params, {
headers: { "jwt-token": token }
});
res.send(data);
} catch (error) {
console.log("listOperationByBatch", error);
res.json({ code: 500, msg: error });
}
},
replenishmentComposingDesignImages: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const { ids, type, templateWidth, targetDir } = req.body;
let url = pathMap["replenishmentComposingDesignImages"];
try {
let { data } = await axios.post(
`${env}/${url}?type=${type}&templateWidth=${templateWidth}`,
ids,
{
headers: { "jwt-token": token }
}
);
if (data.code !== 200) {
return res.json(data);
}
console.log("data", data);
let path = data.message;
// 转义中文
if (path) {
path = encodeURIComponent(path);
}
let files = [path];
files = files.map(el => ({ url: `${fileEnv}${el}` }));
const result = await downloadDTFFiles(files[0].url, targetDir);
res.json({ code: 200, data: result });
} catch (error) {
console.log("replenishmentComposingDesignImages", error);
res.json({ code: 500, msg: error });
}
},
podOrderBatchDownload: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const { id } = req.body;
let url = pathMap["podOrderBatchDownload"];
try {
let { data } = await axios.get(`${env}/${url}?id=${id}`, {
headers: { "jwt-token": token }
});
res.json({ code: 200, data });
} catch (error) {
console.log("podOrderBatchDownload", error);
res.json({ code: 500, msg: error });
}
},
getPodOrderLog: async (req, res) => {
env = getHostApi().apiApiHost;
const token = req.headers["jwt-token"];
const { id } = req.body;
let url = pathMap["getPodOrderLog"];
console.log(req.body);
try {
let { data } = await axios.get(`${env}/${url}?id=${id}`, {
headers: { "jwt-token": token }
});
res.json({ code: 200, data });
} catch (error) {
console.log("getPodOrderLog", error);
res.json({ code: 500, msg: error });
}
} }
}; };
...@@ -13,7 +13,7 @@ export const createServer = () => { ...@@ -13,7 +13,7 @@ export const createServer = () => {
webApp.use(logMiddleWare()); webApp.use(logMiddleWare());
webApp.use(express.json()); webApp.use(express.json());
webApp.use(express.urlencoded({ extended: false })); webApp.use(express.urlencoded({ extended: false }));
webApp.use("/", router); webApp.use("/", router);
// catch 404 // catch 404
webApp.use((req, res) => { webApp.use((req, res) => {
res.status(404).send("Sorry! 404 Error."); res.status(404).send("Sorry! 404 Error.");
...@@ -29,7 +29,7 @@ export const createServer = () => { ...@@ -29,7 +29,7 @@ export const createServer = () => {
res.status(err.status || 500); res.status(err.status || 500);
res.json({ res.json({
message: err.message, message: err.message,
error: err, error: err
}); });
}); });
......
...@@ -51,10 +51,39 @@ router.post("/getAllCountry", fn.getAllCountry); ...@@ -51,10 +51,39 @@ router.post("/getAllCountry", fn.getAllCountry);
//根据图片地址保存文件到本地 //根据图片地址保存文件到本地
router.post("/saveImgByUrl", fn.saveImgByUrl); router.post("/saveImgByUrl", fn.saveImgByUrl);
//获取目标路径文件夹下所以文件
router.post("/createBatchFolder", fn.createBatchFolder);
//复制文件夹下文件 //复制文件夹下文件
router.post("/copySingleImageFn", fn.copySingleImageFn); router.post("/copySingleImageFn", fn.copySingleImageFn);
//获取生产配置
autoRegisterRouter(router, "productionConfig", fn.productionConfig);
//更新生产配置
autoRegisterRouter(router, "productionConfigUpdate", fn.productionConfigUpdate);
//排版列表(分页)
autoRegisterRouter(router, "productionSoftware", fn.productionSoftware);
//查询该批次下的所有素材
autoRegisterRouter(router, "listMaterialsByBatch", fn.listMaterialsByBatch);
//重新合成排版
autoRegisterRouter(
router,
"psReComposingDesignImages",
fn.psReComposingDesignImages
);
//排版
autoRegisterRouter(
router,
"replenishmentComposingDesignImages",
fn.replenishmentComposingDesignImages
);
//排版
autoRegisterRouter(router, "podOrderBatchDownload", fn.podOrderBatchDownload);
//根据批次分页查询操作单
autoRegisterRouter(router, "listOperationByBatch", fn.listOperationByBatch);
//操作日志
autoRegisterRouter(router, "getPodOrderLog", fn.getPodOrderLog);
// 根据生产单号查询详情 // 根据生产单号查询详情
autoRegisterRouter(router, "findByPodProductionNo", fn.findByPodProductionNo); autoRegisterRouter(router, "findByPodProductionNo", fn.findByPodProductionNo);
......
...@@ -7,7 +7,6 @@ const MAX_LIMIT_RECORD_KEY = "originalIdMaxLimitRecord"; // 永久上限标记 ...@@ -7,7 +7,6 @@ const MAX_LIMIT_RECORD_KEY = "originalIdMaxLimitRecord"; // 永久上限标记
// ===== 2. 核心内存缓存 ===== // ===== 2. 核心内存缓存 =====
let orderCacheMap = new Map(); // key格式:前缀-数字(如AAAF...-1),value:原始订单对象 let orderCacheMap = new Map(); // key格式:前缀-数字(如AAAF...-1),value:原始订单对象
// ===== 3. 永久上限标记方法(保留你的原有核心逻辑,无改动) ===== // ===== 3. 永久上限标记方法(保留你的原有核心逻辑,无改动) =====
/** /**
* 获取已达永久上限的原始newId Map * 获取已达永久上限的原始newId Map
...@@ -197,11 +196,47 @@ module.exports = { ...@@ -197,11 +196,47 @@ module.exports = {
store.set("desktoBoard", version); store.set("desktoBoard", version);
}, },
getBoard: () => store.get("desktoBoard") || 3, getBoard: () => store.get("desktoBoard") || 3,
setIsOver: value => { setIsOver: value => {
store.set("isOver", value); store.set("isOver", value);
}, },
getIsOver: () => store.get("isOver") || false, getIsOver: () => store.get("isOver") || false,
//储存用户信息
setUser: value => {
store.set("userData", value);
},
getUser: () => store.get("userData"),
//本地储存 DTG、DTF生产类型
setProductType: value => {
store.set("productType", value);
},
getProductType: () => store.get("productType") || "DTG",
//DTF生产设置
setDTFSetting: value => {
store.set("DTFSetting", value);
},
getDTFSetting: () =>
store.get("DTFSetting") || {
widthStrategy: "1",
// 最大素材数模式:batch / custom
materialStrategy: "1",
// 自定义素材数量
materialCount: 30,
// 自动排版规格 40+2 / 60 / none
autoLayoutStrategy: "3",
// 默认下载目录
downloadDir: ""
},
//本地储存 文件批次号
setBatchNumbers: value => {
store.set("batchNumbers", value);
},
getBatchNumbers: () => store.get("batchNumbers") || [],
setLocation: (version, locationKey) => { setLocation: (version, locationKey) => {
if (!locationKey) return; if (!locationKey) return;
store.set(locationKey, version); store.set(locationKey, version);
......
...@@ -26,6 +26,7 @@ function endLoading() { ...@@ -26,6 +26,7 @@ function endLoading() {
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
const user = Vue.prototype.$dataStore.get("user"); const user = Vue.prototype.$dataStore.get("user");
if (user) { if (user) {
config.headers["jwt-token"] = user.token; config.headers["jwt-token"] = user.token;
} }
...@@ -82,8 +83,26 @@ service.interceptors.response.use( ...@@ -82,8 +83,26 @@ service.interceptors.response.use(
}, },
error => { error => {
endLoading(); endLoading();
// do something with response error let errMsg = "请求失败,请稍后重试";
return Promise.reject(error);
// 1. 有服务端响应:提取后端返回的 msg
if (error.response && error.response.data) {
const data = error.response.data;
// 优先取后端返回的 msg(适配你后端 {code:500, msg:'xxx'} 的格式)
errMsg =
data.msg || data.message || `请求错误(${error.response.status})`;
}
// 2. 无响应:网络层面错误
else if (error.message) {
if (error.message.includes("timeout")) {
errMsg = "请求超时,请检查网络";
} else if (error.message.includes("Network Error")) {
errMsg = "网络连接异常,请检查网络";
} else {
errMsg = error.message;
}
}
return Promise.reject(new Error(errMsg));
} }
); );
......
const { ipcRenderer } = window.require("electron");
// ========== 原素材下载 API(原有保留) ==========
/**
* 提交下载任务
* @param {Object} params { urls, targetPath, batchArrangeNum }
* @returns {Promise<Object>}
*/
export function submitDownload(params) {
return new Promise(resolve => {
ipcRenderer.once("download:submit-reply", (_, res) => resolve(res));
ipcRenderer.send("download:submit", params);
});
}
/**
* 获取当前下载状态
* @returns {Promise<Object>}
*/
export function getDownloadStatus() {
return new Promise(resolve => {
ipcRenderer.once("download:getStatus-reply", (_, res) => resolve(res));
ipcRenderer.send("download:getStatus");
});
}
/**
* 监听下载状态变更
* @param {Function} callback
* @returns {Function} 取消监听函数
*/
export function onDownloadStatusChange(callback) {
const handler = (_, data) => callback(data);
ipcRenderer.on("download:status", handler);
return () => ipcRenderer.removeListener("download:status", handler);
}
// ========== 新增:手动排版下载 API ==========
/**
* 提交手动排版下载任务
* @param {Object} params { urlStr: 逗号分隔URL字符串, targetPath: 保存目录 }
* @returns {Promise<Object>}
*/
export function submitTypesettingDownload(params) {
return new Promise(resolve => {
ipcRenderer.once("download-typesetting:submit-reply", (_, res) =>
resolve(res)
);
ipcRenderer.send("download-typesetting:submit", params);
});
}
/**
* 查询手动排版下载状态
* @returns {Promise<Object>}
*/
export function getTypesettingDownloadStatus() {
return new Promise(resolve => {
ipcRenderer.once("download-typesetting:getStatus-reply", (_, res) =>
resolve(res)
);
ipcRenderer.send("download-typesetting:getStatus");
});
}
/**
* 监听手动排版下载状态变更
* @param {Function} callback
* @returns {Function} 取消监听函数
*/
export function onTypesettingStatusChange(callback) {
const handler = (_, data) => callback(data);
ipcRenderer.on("download-typesetting:status", handler);
return () =>
ipcRenderer.removeListener("download-typesetting:status", handler);
}
const path = require("path");
const fs = require("fs");
const http = require("http");
const https = require("https");
const url = require("url");
const { setBatchNumbers, getBatchNumbers } = require("@/server/utils/store");
/* ===================== 基础工具函数 ===================== */
/**
* 从URL提取文件名
*/
function getFileName(urlStr) {
const formatUrl = urlStr.replace(/\\/g, "/");
const lastName = path.basename(formatUrl);
console.log("lastName", lastName);
// 先分割拿到数组
const nameArr = lastName.split("-");
// 判断是否存在横线分割成两段
if (nameArr.length >= 2) {
// OP260630368_test_1361_11852.png
return `${nameArr[1]}_${nameArr[0]}`;
}
// 没有横线直接返回原名
return lastName;
}
/**
* 判断是否为绝对URL
*/
function isAbsoluteUrl(urlStr) {
return /^https?:\/\//i.test(urlStr);
}
/**
* 补全相对路径为完整URL
*/
function resolveUrl(urlStr, baseUrl) {
if (isAbsoluteUrl(urlStr)) return urlStr;
let url = urlStr.replace(/^\//, "");
const nameArr = url.split("-");
if (nameArr.length >= 2) url = nameArr[0];
console.log("resolveUrl", `${baseUrl.replace(/\/$/, "")}/${url}`);
return `${baseUrl.replace(/\/$/, "")}/${url}`;
}
/**
* 递归创建目录
*/
async function ensureDir(dirPath) {
await fs.promises.mkdir(dirPath, { recursive: true });
}
/**
* 判断文件是否存在
*/
async function isFileExists(filePath) {
try {
await fs.promises.access(filePath, fs.constants.F_OK);
return true;
} catch (e) {
return false;
}
}
/**
* 并发限制器
*/
function pLimit(concurrency) {
let activeCount = 0;
const queue = [];
const next = () => {
if (queue.length > 0 && activeCount < concurrency) {
activeCount++;
const task = queue.shift();
task().finally(() => {
activeCount--;
next();
});
}
};
return fn => {
return new Promise((resolve, reject) => {
queue.push(() =>
fn()
.then(resolve)
.catch(reject)
);
next();
});
};
}
/* ===================== 核心下载能力 ===================== */
/**
* 单文件流式下载(支持重定向、超时、自动清理残文件)
* @param {string} fileUrl 完整下载链接
* @param {string} filePath 本地保存路径
* @param {number} timeout 超时时间ms
* @param {number} redirectCount 已重定向次数
*/
function streamDownload(fileUrl, filePath, timeout = 30000, redirectCount = 0) {
return new Promise((resolve, reject) => {
try {
console.log("fileUrl", fileUrl);
const requestModule = fileUrl.startsWith("https") ? https : http;
const req = requestModule.get(fileUrl, { timeout }, res => {
// 重定向处理,最多3次防死循环
if ([301, 302, 307, 308].includes(res.statusCode)) {
if (redirectCount >= 3) {
res.resume();
reject(new Error("重定向次数超过3次"));
return;
}
const redirectUrl = url.resolve(fileUrl, res.headers.location);
streamDownload(redirectUrl, filePath, timeout, redirectCount + 1)
.then(resolve)
.catch(reject);
return;
}
if (res.statusCode !== 200) {
res.resume();
reject(new Error(`HTTP状态码异常: ${res.statusCode}`));
return;
}
let writeStream;
try {
writeStream = fs.createWriteStream(filePath);
} catch (err) {
res.resume();
reject(new Error(`路径创建失败:${err.message}`));
return;
}
res.pipe(writeStream);
writeStream.on("finish", () => {
writeStream.close(() => resolve(filePath));
});
writeStream.on("error", err => {
writeStream.destroy();
fs.promises.unlink(filePath).catch(() => {});
reject(err);
});
res.on("error", err => {
writeStream.destroy();
fs.promises.unlink(filePath).catch(() => {});
reject(new Error(`网络响应异常: ${err.message}`));
});
});
req.on("error", err => reject(new Error(`网络请求失败: ${err.message}`)));
req.on("timeout", () => {
req.destroy();
reject(new Error("下载超时"));
});
} catch (syncErr) {
reject(new Error(`请求初始化失败: ${syncErr.message}`));
}
});
}
/**
* 带失败重试的单文件下载
*/
async function downloadWithRetry(
fileUrl,
filePath,
retryTimes = 0,
timeout = 30000
) {
const attempt = async remain => {
try {
return await streamDownload(fileUrl, filePath, timeout);
} catch (err) {
if (remain > 0) {
console.log(`[下载重试] 剩余 ${remain} 次:${path.basename(filePath)}`);
return attempt(remain - 1);
}
throw err;
}
};
return attempt(retryTimes);
}
/* ===================== 批量下载封装 ===================== */
/**
* 批量下载(URL数组版本)
* @param {string[]} urlList URL数组
* @param {string} targetDir 目标目录
* @param {Object} options 配置
* @returns {Promise<{success: string[], failed: Array<{url:string, error:string}>}>}
*/
async function batchDownloadByList(urlList, targetDir, options = {}) {
const {
concurrency = 5,
timeout = 30000,
retryTimes = 0,
baseUrl = "",
skipExists = true,
fileNameFormatter = null, // 新增:自定义文件名回调,
isCheck = false,
batchId = "" // 新增:批次唯一标识,isCheck 模式下使用
} = options;
const success = [];
const failed = [];
if (isCheck) {
// 批次已处理过则直接跳过整个批次,不执行后续任何下载逻辑
const batchNumbers = getBatchNumbers() || [];
if (batchNumbers.includes(batchId)) {
console.log(`该批次已执行过,直接跳过 | 批次号: ${batchId}`);
return {
success: [],
failed: [],
skipped: true,
batchId
};
}
}
// 1. 创建目标目录
try {
await ensureDir(targetDir);
} catch (dirErr) {
const errMsg = `目标目录创建失败:${dirErr.message}`;
console.error(`[批量下载][路径错误] ${errMsg} | 目录: ${targetDir}`);
return {
success: [],
failed: [{ url: "", error: errMsg }]
};
}
// 2. 并发执行下载
const limit = pLimit(concurrency);
const tasks = urlList.map(urlStr =>
limit(async () => {
const fullUrl = baseUrl ? resolveUrl(urlStr, baseUrl) : urlStr;
const fileName = fileNameFormatter
? fileNameFormatter(urlStr, baseUrl)
: getFileName(urlStr);
const savePath = path.join(targetDir, fileName);
try {
// 已存在则跳过
if (skipExists && (await isFileExists(savePath))) {
console.log(`[批量下载][跳过] 文件已存在: ${fileName}`);
success.push(savePath);
return;
}
await downloadWithRetry(fullUrl, savePath, retryTimes, timeout);
console.log(`[批量下载][成功] ${fileName}`);
success.push(savePath);
} catch (err) {
// 识别路径类错误
const isPathError =
["ENOENT", "EINVAL", "ENAMETOOLONG", "EPERM", "EACCES"].includes(
err.code
) || /路径|path|file/i.test(err.message);
const errorType = isPathError ? "路径错误" : "下载失败";
console.error(
`[批量下载][${errorType}] 跳过文件: ${fileName} | 链接: ${fullUrl} | 原因: ${err.message}`
);
failed.push({ url: fullUrl, error: err.message });
}
})
);
if (isCheck && batchId) {
setBatchNumbers([...getBatchNumbers(), batchId]);
console.log(`批量下载 批次标记已标记批次为已处理 | 批次号: ${batchId}`);
}
await Promise.all(tasks);
console.log(`批量下载完成,成功: ${success.length},失败: ${failed.length}`);
return { success, failed };
}
/**
* 批量下载(逗号分隔URL字符串版本)
* @param {string} urlStr 逗号分隔的URL字符串
* @param {string} targetDir 目标目录
* @param {Object} options 配置
*/
async function batchDownloadByStr(urlStr, targetDir, options = {}) {
// 参数校验
if (!urlStr || typeof urlStr !== "string") {
console.error("[批量下载][参数错误] 传入的URL字符串为空或格式无效");
return { success: [], failed: [{ url: "", error: "URL字符串无效" }] };
}
// 拆分URL
const urlList = urlStr
.split(",")
.map(u => u.trim())
.filter(u => u);
if (urlList.length === 0) {
console.warn("[批量下载][无有效链接] 拆分后无可用URL,直接跳过");
return { success: [], failed: [] };
}
return batchDownloadByList(urlList, targetDir, options);
}
module.exports = {
// 工具函数
getFileName,
isAbsoluteUrl,
resolveUrl,
ensureDir,
isFileExists,
pLimit,
// 核心下载
streamDownload,
downloadWithRetry,
// 批量下载
batchDownloadByList,
batchDownloadByStr
};
const { ipcMain, BrowserWindow } = require("electron");
const { getHostApi } = require("@/server/utils/store");
const { batchDownloadByList } = require("./download-core");
/* ========== 任务管理模块(全局互斥锁 + 状态广播) ========== */
const taskState = {
isRunning: false,
currentBatchNo: "",
latestResult: { success: [], failed: [] }
};
function broadcastStatus() {
const allWindows = BrowserWindow.getAllWindows();
allWindows.forEach(win => {
if (!win.isDestroyed()) {
win.webContents.send("download:status", {
isRunning: taskState.isRunning,
currentBatchNo: taskState.currentBatchNo,
result: taskState.latestResult
});
}
});
}
function submitTask(params) {
const { urls, targetPath, batchArrangeNum } = params;
if (taskState.isRunning) {
return {
code: 501,
msg: `当前正在下载批次【${taskState.currentBatchNo}】,请等待完成后再操作`,
data: { isRunning: true }
};
}
if (!Array.isArray(urls) || urls.length === 0) {
return { code: 500, msg: "下载链接数组为空" };
}
taskState.isRunning = true;
taskState.currentBatchNo = batchArrangeNum || "未知批次";
taskState.latestResult = { success: [], failed: [] };
broadcastStatus();
// 调用核心下载能力
batchDownloadByList(urls, targetPath, {
concurrency: 2,
timeout: 30000,
retryTimes: 1,
baseUrl: getHostApi().fileApiUrl
})
.then(result => {
taskState.latestResult = result;
})
.catch(err => {
taskState.latestResult = {
success: [],
failed: [{ url: "", error: err.message || "下载任务异常中断" }]
};
})
.finally(() => {
taskState.isRunning = false;
taskState.currentBatchNo = "";
broadcastStatus();
});
return {
code: 200,
msg: "下载进行中,下载完成的素材将在文件夹原素材中显示",
data: { isRunning: true }
};
}
function getStatus() {
return {
code: 200,
data: {
isRunning: taskState.isRunning,
currentBatchNo: taskState.currentBatchNo,
result: taskState.latestResult
}
};
}
/* ========== IPC 注册 ========== */
function registerDownloadIpc() {
ipcMain.on("download:submit", (event, params) => {
const result = submitTask(params);
event.sender.send("download:submit-reply", result);
});
ipcMain.on("download:getStatus", event => {
const result = getStatus();
event.sender.send("download:getStatus-reply", result);
});
}
module.exports = registerDownloadIpc;
const path = require("path");
const { pathMap } = require("@/config/index.js");
const { getDTFSetting, getHostApi } = require("@/server/utils/store");
import api from "./request";
const { batchDownloadByStr } = require("./download-core.js");
/* ========== 全局状态 ========== */
const taskState = {
currentPage: 1,
pageSize: 100,
layoutStatus: "3,5",
isDownloading: false,
timer: null,
total: 0,
interval: 1 * 60 * 1000,
maxBatchConcurrency: 3,
maxImageConcurrency: 10
};
/* ========== 工具函数(业务专属,保留) ========== */
function logDownloadError(type, batchNo, url, message) {
console.error(
`[自动下载失败][${type}] 批次号: ${batchNo || "未知"} | 链接: ${url ||
"-"} | 详情: ${message}`
);
}
// 复用core的并发控制器,也可以直接用core导出的pLimit
const { pLimit } = require("./download-core");
/* ========== 业务逻辑 ========== */
async function processBatchItem(item) {
const { batchArrangeNum, spUrl: urlStr } = item;
if (!batchArrangeNum) {
logDownloadError(
"参数无效",
batchArrangeNum,
"",
"缺少批次号batchArrangeNum"
);
return;
}
if (!urlStr || typeof urlStr !== "string") {
logDownloadError(
"参数无效",
batchArrangeNum,
urlStr,
"url字段为空或非字符串格式"
);
return;
}
// 构建保存目录
let saveDir;
try {
const downloadRoot = getDTFSetting().downloadDir;
if (!downloadRoot) throw new Error("下载根目录downloadDir配置为空");
saveDir = path.join(
downloadRoot,
`批次号:${batchArrangeNum}`,
"自动排版素材"
);
} catch (dirErr) {
logDownloadError("目录创建失败", batchArrangeNum, "", dirErr.message);
return;
}
// 调用核心批量下载
await batchDownloadByStr(urlStr, saveDir, {
concurrency: taskState.maxImageConcurrency,
timeout: 30000,
baseUrl: getHostApi().fileApiUrl,
isCheck: true,
batchId: batchArrangeNum
});
}
async function runTask() {
if (taskState.isDownloading) {
console.log("当前有下载任务进行中,跳过本轮定时任务");
return;
}
try {
taskState.isDownloading = true;
console.log(`=== 开始执行定时任务,当前页码:${taskState.currentPage} ===`);
// 获取排版策略
const settingRes = await api.post(pathMap["productionConfig"]);
if (settingRes.data.autoLayoutStrategy === 3) {
console.log("不自动排版,跳过本次下载");
return;
}
// 拉取排版数据
const layoutRes = await api.post(pathMap["productionSoftware"], {
currentPage: taskState.currentPage,
pageSize: taskState.pageSize,
layoutStatus: taskState.layoutStatus
});
const list = layoutRes.data.records || [];
taskState.total = layoutRes.data.total || 0;
if (list.length === 0) {
console.log("当前页无数据,本轮任务结束");
return;
}
console.log(layoutRes);
// 多批次并行下载
const batchLimit = pLimit(taskState.maxBatchConcurrency);
const batchPromises = list.map(item =>
batchLimit(() => processBatchItem(item))
);
await Promise.all(batchPromises);
console.log(`=== 第 ${taskState.currentPage} 页全部处理完成 ===`);
// 更新页码
taskState.currentPage++;
if ((taskState.currentPage - 1) * taskState.pageSize >= taskState.total) {
taskState.currentPage = 1;
console.log("已遍历全部数据,下轮任务从第1页重新开始");
}
} catch (err) {
console.error("定时任务执行异常:", err.message);
} finally {
taskState.isDownloading = false;
console.log("本轮任务结束,锁已释放");
}
}
/* ========== 对外启停方法 ========== */
function startAutoDownloadTask() {
if (taskState.timer) return;
console.log("自动下载定时任务已启动,5分钟后首次执行");
taskState.timer = setInterval(runTask, taskState.interval);
}
function stopAutoDownloadTask() {
if (taskState.timer) {
clearInterval(taskState.timer);
taskState.timer = null;
taskState.isDownloading = false;
console.log("自动下载定时任务已停止");
}
}
export default {
startAutoDownloadTask,
stopAutoDownloadTask
};
const { ipcMain, BrowserWindow } = require("electron");
const path = require("path");
const { getHostApi } = require("@/server/utils/store");
const { batchDownloadByStr } = require("./download-core");
/* ===================== 原有命名逻辑(完全保留) ===================== */
function formatTime() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
const hour = String(now.getHours()).padStart(2, "0");
const minute = String(now.getMinutes()).padStart(2, "0");
const second = String(now.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day}_${hour}.${minute}.${second}`;
}
function customFileNameFormatter(urlStr) {
let cleanPath = urlStr;
try {
cleanPath = decodeURIComponent(urlStr);
} catch (e) {
cleanPath = urlStr;
}
cleanPath = cleanPath.replace(/\\/g, "/");
const originalFileName = path.basename(cleanPath);
const timePrefix = formatTime();
return originalFileName
? `${timePrefix} ${originalFileName}`
: `${timePrefix}.png`;
}
/**
* 原有纯函数调用方式(保留,兼容现有代码)
*/
async function batchDownloadUrls(urlStr, targetDir, options = {}) {
return batchDownloadByStr(urlStr, targetDir, {
baseUrl: getHostApi().fileApiUrl,
fileNameFormatter: customFileNameFormatter,
...options
});
}
/* ===================== 新增:IPC 任务管理模块(独立锁,与原素材隔离) ===================== */
const taskState = {
isRunning: false,
latestResult: { success: [], failed: [], currentBatchNo: "" }
};
/**
* 向所有窗口广播状态
*/
function broadcastStatus() {
const allWindows = BrowserWindow.getAllWindows();
allWindows.forEach(win => {
if (!win.isDestroyed()) {
win.webContents.send("download-typesetting:status", {
isRunning: taskState.isRunning,
result: taskState.latestResult
});
}
});
}
/**
* 提交下载任务(异步执行,立即返回)
*/
function submitTask(params) {
const { urlStr, targetPath, batchArrangeNum } = params;
taskState.currentBatchNo = batchArrangeNum;
// 全局互斥:有任务在跑直接拒绝
if (taskState.isRunning) {
return {
code: 500,
msg: "当前正在下载手动排版素材,请等待完成后再操作",
data: { isRunning: true }
};
}
if (!urlStr || typeof urlStr !== "string") {
return { code: 500, msg: "下载链接为空或格式无效" };
}
// 加锁
taskState.isRunning = true;
taskState.latestResult = { success: [], failed: [] };
broadcastStatus();
// 后台异步执行下载,不阻塞 IPC 返回
batchDownloadUrls(urlStr, targetPath)
.then(result => {
taskState.latestResult = result;
})
.catch(err => {
taskState.latestResult = {
success: [],
failed: [{ url: "", error: err.message || "下载任务异常中断" }]
};
})
.finally(() => {
// 释放锁
taskState.isRunning = false;
broadcastStatus();
});
return {
code: 200,
msg: "排版进行中,请查看处理状态,排版完成的素材至文件夹排版素材查看",
data: { isRunning: true }
};
}
/**
* 查询当前下载状态
*/
function getStatus() {
return {
code: 0,
data: {
isRunning: taskState.isRunning,
result: taskState.latestResult
}
};
}
/* ===================== 新增:IPC 事件注册 ===================== */
/**
* 注册手动排版下载 IPC 事件
* 在主进程入口调用一次即可
*/
function registerTypesettingDownloadIpc() {
// 提交下载任务
ipcMain.on("download-typesetting:submit", (event, params) => {
const result = submitTask(params);
event.sender.send("download-typesetting:submit-reply", result);
});
// 查询下载状态
ipcMain.on("download-typesetting:getStatus", event => {
const result = getStatus();
event.sender.send("download-typesetting:getStatus-reply", result);
});
}
/* ===================== 导出(兼容两种使用方式) ===================== */
module.exports = {
// 原有纯函数,可直接在主进程内调用
batchDownloadUrls,
// IPC 注册函数,主进程入口调用
registerTypesettingDownloadIpc
};
...@@ -62,7 +62,7 @@ export const cutArea = (canvasEl, mousewheel) => { ...@@ -62,7 +62,7 @@ export const cutArea = (canvasEl, mousewheel) => {
boundaryLeft, boundaryLeft,
boundaryTop, boundaryTop,
boundaryWidth, boundaryWidth,
boundaryHeight, boundaryHeight
} = getRealBoundary(canvasEl, mousewheel); } = getRealBoundary(canvasEl, mousewheel);
//存储正方形边界 //存储正方形边界
...@@ -70,7 +70,7 @@ export const cutArea = (canvasEl, mousewheel) => { ...@@ -70,7 +70,7 @@ export const cutArea = (canvasEl, mousewheel) => {
boundaryLeft, boundaryLeft,
boundaryTop, boundaryTop,
boundaryWidth, boundaryWidth,
boundaryHeight, boundaryHeight
}); });
cutCtx.clearRect(boundaryLeft, boundaryTop, boundaryWidth, boundaryHeight); cutCtx.clearRect(boundaryLeft, boundaryTop, boundaryWidth, boundaryHeight);
...@@ -92,7 +92,7 @@ export const cutArea = (canvasEl, mousewheel) => { ...@@ -92,7 +92,7 @@ export const cutArea = (canvasEl, mousewheel) => {
canvasEl.set({ canvasEl.set({
cutWidth: width, cutWidth: width,
cutHeight: height, cutHeight: height
}); });
//设置蒙版图片 //设置蒙版图片
...@@ -119,7 +119,7 @@ export const cutArea = (canvasEl, mousewheel) => { ...@@ -119,7 +119,7 @@ export const cutArea = (canvasEl, mousewheel) => {
canvasEl.maskDiv.appendChild(mask_img); canvasEl.maskDiv.appendChild(mask_img);
insertAfter( insertAfter(
canvasEl.maskDiv, canvasEl.maskDiv,
document.getElementById(canvasEl.cutCanvas.id), document.getElementById(canvasEl.cutCanvas.id)
); );
} }
...@@ -177,7 +177,7 @@ const getRealBoundary = function(canvasEl, mousewheel) { ...@@ -177,7 +177,7 @@ const getRealBoundary = function(canvasEl, mousewheel) {
if (!canvasEl.oldCutWidth) { if (!canvasEl.oldCutWidth) {
canvasEl.set({ canvasEl.set({
oldCutWidth: cutWidth, oldCutWidth: cutWidth,
oldCutHeight: cutHeight, oldCutHeight: cutHeight
}); });
} }
} }
...@@ -218,7 +218,7 @@ const getRealBoundary = function(canvasEl, mousewheel) { ...@@ -218,7 +218,7 @@ const getRealBoundary = function(canvasEl, mousewheel) {
boundaryLeft, boundaryLeft,
boundaryTop, boundaryTop,
boundaryWidth, boundaryWidth,
boundaryHeight, boundaryHeight
}; };
}; };
......
import axios from "axios";
import Store from "electron-store"; // 主进程用 electron-store 存取用户信息,和渲染进程的 $dataStore 数据互通
const dataStore = new Store();
// 纯请求实例:无任何 UI 依赖,仅处理 token 注入和错误格式化
const service = axios.create({
baseURL: "http://localhost:3000",
timeout: 12600000
});
// 请求拦截:仅注入 token
service.interceptors.request.use(
config => {
const user = dataStore.get("user");
if (user.token) {
config.headers["jwt-token"] = user.token;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截:仅处理数据和错误信息,不做任何 UI 操作
service.interceptors.response.use(
response => response.data,
error => {
let errMsg = "请求失败,请稍后重试";
if (error.response.data) {
const data = error.response.data;
errMsg =
data.msg || data.message || `请求错误(${error.response.status})`;
} else if (error.message) {
errMsg = error.message.includes("timeout")
? "请求超时,请检查网络"
: error.message;
}
return Promise.reject(new Error(errMsg));
}
);
export default service;
...@@ -1144,7 +1144,7 @@ export default { ...@@ -1144,7 +1144,7 @@ export default {
z-index: 4; z-index: 4;
bottom: 0; bottom: 0;
height: calc(100% - 50px); height: calc(100% - 75px);
.print-content { .print-content {
background: #fff; background: #fff;
......
<script> <script>
import PHead from "./head/index.vue"; import PHead from "./head/index.vue";
import PMain from "./main/index.vue"; import PMain from "./main/index.vue";
const { getUser } = require("@/server/utils/store");
import UpdateDialog from "@/views/design/updateDialog.vue";
export default { export default {
components: { PHead, PMain, UpdateDialog }, name: "design-dtg",
components: { PHead, PMain },
data() { data() {
return { return {
user: {}, user: {},
factoryType: "CN", factoryType: "CN",
userData: getUser()
}; };
}, },
mounted() { mounted() {},
this.user = this.$dataStore.get("user"); methods: {}
if (this.user.factory && this.user.factory.countryCode) {
this.factoryType = this.user.factory.countryCode;
}
// console.log(17, this.user);
this.$refs.updateDialog.checkUpdate().then((data) => {
// console.log(17, data);
if (data) {
// 有新版本更新
this.$refs.updateDialog.open(data);
}
});
},
}; };
</script> </script>
<template> <template>
<div class="page"> <div class="page">
<p-head :user="user" :factoryType="factoryType" /> <p-head :user="userData.user" :factoryType="userData.factoryType" />
<p-main :factoryType="factoryType" /> <p-main :factoryType="factoryType" />
<update-dialog ref="updateDialog" />
</div> </div>
</template> </template>
<style scoped> <style scoped lang="less">
.page { .page {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
// border-top: 1px solid #dcdfe6;
} }
</style> </style>
...@@ -18,33 +18,33 @@ export default { ...@@ -18,33 +18,33 @@ export default {
y: 0, y: 0,
w: 0, w: 0,
h: 0, h: 0,
r: 0, r: 0
}, }
}; };
}, },
computed: { computed: {
...mapState(["isPreView"]), ...mapState(["isPreView"])
}, },
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
default: false, default: false
}, },
item: { item: {
type: Object, type: Object,
default: () => {}, default: () => {}
}, },
newDesktopDevice: { newDesktopDevice: {
type: [String, Number], type: [String, Number],
default: "1", default: "1"
}, }
}, },
watch: { watch: {
visible: { visible: {
handler() { handler() {
this.drawerShow = this.visible; this.drawerShow = this.visible;
}, },
immediate: true, immediate: true
}, },
item: { item: {
handler(val) { handler(val) {
...@@ -67,14 +67,14 @@ export default { ...@@ -67,14 +67,14 @@ export default {
y: 0, y: 0,
w: 0, w: 0,
h: 0, h: 0,
r: 0, r: 0
}; };
} }
// console.log("item", this.item); // console.log("item", this.item);
}, },
immediate: true, immediate: true,
deep: true, deep: true
}, }
}, },
methods: { methods: {
pxToUnit, pxToUnit,
...@@ -87,7 +87,7 @@ export default { ...@@ -87,7 +87,7 @@ export default {
this.item.x = item.x - item.w / 2; this.item.x = item.x - item.w / 2;
this.$dataStore.set( this.$dataStore.set(
"position_before_px", "position_before_px",
JSON.parse(JSON.stringify(this.item)), JSON.parse(JSON.stringify(this.item))
); );
console.log("this.item", this.item); console.log("this.item", this.item);
...@@ -107,7 +107,7 @@ export default { ...@@ -107,7 +107,7 @@ export default {
this.form.r = this.item.r; this.form.r = this.item.r;
this.$dataStore.set( this.$dataStore.set(
`position_after_px`, `position_after_px`,
JSON.parse(JSON.stringify(this.form)), JSON.parse(JSON.stringify(this.form))
); );
}, },
formChange(t) { formChange(t) {
...@@ -169,8 +169,8 @@ export default { ...@@ -169,8 +169,8 @@ export default {
if (this.form[f] < 0) this.$set(this.form, f, 0); if (this.form[f] < 0) this.$set(this.form, f, 0);
} }
this.formChange(); this.formChange();
}, }
}, }
}; };
</script> </script>
...@@ -370,14 +370,14 @@ export default { ...@@ -370,14 +370,14 @@ export default {
width: 500px; width: 500px;
// bottom: 0; // bottom: 0;
// top: 50px; // top: 50px;
padding: 10px; // padding: 10px;
// z-index: 99; // z-index: 99;
background: white; background: white;
// box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2), // box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2),
// 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12); // 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12);
// left: 0; // left: 0;
// position: fixed; // position: fixed;
border-right: 1px solid #ececec; // border-right: 1px solid #ececec;
// height: calc(100vh - 50px); // height: calc(100vh - 50px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
......
...@@ -38,22 +38,18 @@ export default { ...@@ -38,22 +38,18 @@ export default {
}, },
created() { created() {
this.$store.commit("getNewGrid"); this.$store.commit("getNewGrid");
let proportion;
ipcRenderer.on("window-size", (event, size) => { ipcRenderer.on("window-size", (event, size) => {
const { width, height } = size; const { width, height } = size;
console.log(`接收到窗口尺寸:${width}x${height}`); console.log(`接收到窗口尺寸:${width}x${height}`);
let proportion;
proportion = 1.4; proportion = 1.4;
// } else {
// proportion = 1.5;
// }
this.$store.commit("setWHproportion", proportion); this.$store.commit("setWHproportion", proportion);
this.$store.commit("setWindows", { width, height }); this.$store.commit("setWindows", { width, height });
this.$store.commit("setDefaultproportion", proportion);
this.$store.commit("setGrid");
}); });
this.$store.commit("setDefaultproportion", proportion);
this.$store.commit("setGrid");
if (!this.countryList.length) { if (!this.countryList.length) {
this.getAllCountryFn(); this.getAllCountryFn();
} }
...@@ -1593,7 +1589,7 @@ export default { ...@@ -1593,7 +1589,7 @@ export default {
<div class="item-label">工厂:</div> <div class="item-label">工厂:</div>
<div class="item-value">{{ detail.factoryCode }}</div> <div class="item-value">{{ detail.factoryCode }}</div>
</div> </div>
<div :title="detail.shopNumber" class="div-item" style="flex: 50%;"> <div :title="detail.shopNumber" class="div-item">
<div> <div>
店铺单号: 店铺单号:
</div> </div>
...@@ -1601,11 +1597,7 @@ export default { ...@@ -1601,11 +1597,7 @@ export default {
{{ detail.shopNumber }} {{ detail.shopNumber }}
</div> </div>
</div> </div>
<div <div :title="detail.endProductId" class="div-item">
:title="detail.endProductId"
class="div-item"
style="flex: 50%;"
>
<div> <div>
客户保存记录ID: 客户保存记录ID:
</div> </div>
...@@ -1613,19 +1605,11 @@ export default { ...@@ -1613,19 +1605,11 @@ export default {
{{ detail.endProductId || "" }} {{ detail.endProductId || "" }}
</div> </div>
</div> </div>
<div <div :title="detail.createTime" class="div-item">
:title="detail.createTime"
class="div-item"
style="flex: 100%;"
>
<div>创建时间:</div> <div>创建时间:</div>
<div>{{ detail.createTime }}</div> <div>{{ detail.createTime }}</div>
</div> </div>
<div <div :title="detail.startStockingTime" class="div-item">
:title="detail.startStockingTime"
class="div-item"
style="flex: 100%;"
>
<div> <div>
生产确认时间: 生产确认时间:
</div> </div>
...@@ -2033,6 +2017,7 @@ export default { ...@@ -2033,6 +2017,7 @@ export default {
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
height: 100%;
} }
.main-bg { .main-bg {
...@@ -2049,7 +2034,7 @@ export default { ...@@ -2049,7 +2034,7 @@ export default {
// z-index: 2; // z-index: 2;
left: 5%; left: 5%;
top: 50%; top: 46%;
transform: translate(0, -50%); transform: translate(0, -50%);
background-color: #f0f2f6; background-color: #f0f2f6;
} }
......
<script>
import UpdateDialog from "@/views/design/updateDialog.vue";
import { Loading } from "element-ui";
const {
getProductType,
setProductType,
setUser
} = require("@/server/utils/store");
export default {
components: { UpdateDialog },
data() {
return {
user: {},
factoryType: "CN",
activeName: getProductType(),
loading: null
};
},
mounted() {
this.user = this.$dataStore.get("user");
if (this.user.factory && this.user.factory.countryCode) {
this.factoryType = this.user.factory.countryCode;
}
this.$refs.updateDialog?.checkUpdate().then(data => {
if (data) {
// 有新版本更新
this.$refs.updateDialog?.open(data);
}
});
setUser({ user: this.user, factoryType: this.factoryType });
const routerName = this.$router.currentRoute.name;
const routerMap = { DTG: "design-dtg", DTF: "design-dtf" };
if (routerName !== routerMap[getProductType()]) {
if (getProductType() === "DTG") {
this.$router.push({ name: "design-dtg" });
} else if (getProductType() === "DTF") {
this.$router.push({ name: "design-dtf" });
}
}
},
methods: {
tabClick(tab) {
const targetName = tab.name === "DTG" ? "design-dtg" : "design-dtf";
// 如果已经在目标路由,直接返回,不跳转
if (this.$route.name === targetName) {
return;
}
this.startLoading();
try {
setProductType(tab.name);
if (tab.name === "DTG") {
this.$router.push({ name: "design-dtg" });
} else if (tab.name === "DTF") {
this.$router.push({ name: "design-dtf" });
}
} catch (error) {
console.log(error);
} finally {
this.endLoading();
}
},
startLoading() {
this.loading = Loading.service({
lock: true,
text: "拼命加载中...",
spinner: "el-icon-loading",
background: "rgba(0,0,0,.7)"
});
},
endLoading() {
this.loading.close();
}
}
};
</script>
<template>
<div class="page">
<el-tabs type="border-card" v-model="activeName" @tab-click="tabClick">
<el-tab-pane label="DTG生产" name="DTG">
<router-view v-if="activeName === 'DTG'"></router-view>
</el-tab-pane>
<el-tab-pane label="DTF生产" name="DTF">
<router-view v-if="activeName === 'DTF'"></router-view>
</el-tab-pane>
</el-tabs>
<update-dialog ref="updateDialog" />
</div>
</template>
<style scoped lang="less">
.page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
// border-top: 1px solid #dcdfe6;
}
::v-deep {
.el-tabs {
height: 100%;
border: none;
}
.el-tabs__header {
margin: 0;
background-color: #c6c6c6;
// height: 20px;
}
.el-tabs__header .el-tabs__item {
font-size: 12px;
line-height: 25px;
height: 25px;
// border-bottom: none;
}
.el-tabs--border-card > .el-tabs__content {
padding: 0;
height: 100%;
}
.el-tab-pane {
height: 100%;
}
.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
border: none;
background-color: #243151;
color: #fff;
}
.el-tabs--border-card > .el-tabs__header .el-tabs__item {
border: none;
}
.el-tabs--border-card
> .el-tabs__header
.el-tabs__item:not(.is-disabled):hover {
color: #fff;
// background-color: #5b647c;
}
}
</style>
<script>
const { getDTFSetting } = require("@/server/utils/store");
const { ipcRenderer } = require("electron");
const { statusMap } = require("@/config/index.js");
const { pathMap } = require("@/config/index.js");
const { getUser } = require("@/server/utils/store");
var path = require("path");
import bus from "@/bus";
export default {
props: {
selection: { type: Array, default: () => [] },
podOrderProductIds: { type: Array, default: () => [] }
},
data() {
return {
dialogVisible: false,
dtfSetting: getDTFSetting(),
radio: "",
curBatchArrangeNum: "",
searchForm: {},
statusMap: statusMap,
userData: getUser()
};
},
mounted() {
const params = this.$route.params;
this.curBatchArrangeNum = params.batchArrangeNum;
bus.$on("downLoadFile", async v => {
let { podOrderProductId, batchArrangeNumber } = v;
console.log(31, v);
try {
let res = await this.$api.post(
pathMap["downloadBySubOrderNumber"]["OP"],
{
ids: [podOrderProductId],
device: 4,
orderType: "OP",
targetDir: path.join(
getDTFSetting().downloadDir,
`批次号:${batchArrangeNumber}`,
"原素材"
)
}
);
console.log(77, res);
} catch (error) {
this.$message.error(error);
}
});
},
created() {},
methods: {
dropdownCommand(v) {
switch (v) {
case "logout":
this.$confirm("是否退出登录?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$dataStore.delete("user");
this.$router.push("/");
})
.catch(() => {});
break;
case "cache":
this.checkList = [];
this.cacheVisible = true;
break;
}
},
getCountryName(code) {
if (code) {
const item = this.countryList?.find(el => el.countryCode == code);
if (item) {
return `(${item.nameCn})`;
}
}
return "";
},
goBack() {
this.$router.push({ name: "design-dtf" });
},
async downLoadFn() {
if (!this.podOrderProductIds.length) {
return this.$message.warning("请选择操作单");
}
try {
let res = await this.$api.post(
pathMap["downloadBySubOrderNumber"]["OP"],
{
ids: this.podOrderProductIds,
device: 4,
orderType: "OP",
targetDir: path.join(
getDTFSetting().downloadDir,
`批次号:${this.curBatchArrangeNum}`,
"原素材"
)
}
);
console.log(77, res);
} catch (error) {
this.$message.error(error);
}
},
getDataInfo() {
this.$emit("search", this.searchForm);
},
productionCompleted() {
if (!this.selection.length) {
return this.$message.warning("请选择操作单");
}
this.$confirm(`确定生产完成?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
await this.$api.post(pathMap.completeDelivery["DTF"], {
orderType: "DTF",
ids: this.selection
});
});
},
changeTypesetting() {
if (!this.selection.length) {
return this.$message.warning("请选择操作单");
}
this.dialogVisible = true;
this.radio = "";
},
async submit() {
try {
if (!this.radio) {
return this.$message.error("请选择排版宽度");
}
const payload = {
ids: this.selection,
type: "png",
templateWidth: this.radio,
targetDir: path.join(
getDTFSetting().downloadDir,
`批次号:${this.curBatchArrangeNum}`,
"手动排版素材"
)
};
if (this.dtfSetting.widthStrategy == 3 && this.radio == 60) {
this.$confirm(
"由于排版设置中选择“按更大规格排版”仅适用于40+2,选60时,默认超60的规格不排版",
"提示",
{
confirmButtonText: "确定",
type: "warning"
}
);
const { data } = await this.$api.post(
pathMap["replenishmentComposingDesignImages"],
payload
);
console.log("submit", data);
this.$message.success("DTF排版成功,请到手动排版素材中查看");
} else {
const { data } = await this.$api.post(
pathMap["replenishmentComposingDesignImages"],
payload
);
console.log("submit", data);
this.$message.success("DTF排版成功,请到手动排版素材中查看");
}
this.dialogVisible = false;
this.getDataInfo();
} catch (error) {
console.log("submit", error);
}
},
async goFliePage() {
let { data } = await this.$api.post("/createBatchFolder", {
basePath: this.dtfSetting.downloadDir,
downloadDir: this.curBatchArrangeNum,
isArray: false
});
if (!data) {
return this.$message.error("下载文件目录未配置或者目录错误");
}
ipcRenderer.send("open-folder-only", data);
}
}
};
</script>
<template>
<div>
<div class="page-header">
<el-form style="display: flex;flex-wrap: nowrap;" inline>
<el-form-item>
<el-button icon="el-icon-arrow-left" @click="goBack"
>返回</el-button
></el-form-item
>
<el-form-item>
<el-input
placeholder="请扫描操作单"
v-model="searchForm.operationNo"
clearable
></el-input
></el-form-item>
<el-form-item label="操作单状态">
<el-select
style="width: 120px;"
v-model="searchForm.status"
placeholder="请选择"
clearable
>
<el-option
v-for="(value, label) in statusMap"
:key="value"
:label="label"
:value="value"
></el-option> </el-select
></el-form-item>
<el-form-item label="操作单排版状态">
<el-select
v-model="searchForm.layout"
placeholder="请选择"
clearable
style="width: 120px;"
>
<el-option label="排版成功" :value="true"></el-option>
<el-option label="排版失败" :value="false"> </el-option> </el-select
></el-form-item>
<el-form-item>
<el-button @click="getDataInfo" type="primary"
>查询
</el-button></el-form-item
>
<el-form-item>
<el-button @click="productionCompleted" type="success"
>生产完成
</el-button></el-form-item
>
<el-form-item>
<el-button @click="downLoadFn" type="success"
>下载素材
</el-button></el-form-item
>
<el-form-item>
<el-button @click="changeTypesetting" type="success"
>排版
</el-button></el-form-item
>
<el-form-item>
<el-button @click="goFliePage" type="success"
>查看排版文件
</el-button></el-form-item
>
</el-form>
<div class="right-user">
<div
v-if="userData && userData.factory"
style="font-weight: 700;margin-left: 8px;"
>
{{
userData.user.countryCode
? `${userData.user.countryCode}${getCountryName(
userData.user.countryCode
)}`
: "CN(中国)"
}}
</div>
<p v-if="userData.user && userData.user.factory">
{{ userData.user.factory.title }}
</p>
<el-dropdown @command="dropdownCommand">
<div style="margin-right: 20px">
<b style="cursor:pointer;">{{ userData.user.account }}</b>
<i class="el-icon-arrow-down"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<el-dialog title="排版宽度" :visible.sync="dialogVisible" width="25%">
<el-radio-group v-model="radio">
<el-radio :label="42">40+2cm</el-radio>
<el-radio :label="60">60cm</el-radio>
</el-radio-group>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</span>
</el-dialog>
</div>
</template>
<style lang="less" scoped>
.right-user {
display: flex;
align-items: center;
b {
margin: 0 3px;
display: inline-block;
color: black;
font-size: 15px;
font-weight: bold;
}
p {
margin: 0 10px;
font-weight: bold;
display: inline-block;
font-size: 15px;
color: #e6a23c;
}
}
.page-header {
display: flex;
justify-content: space-between;
background-color: #ececec;
padding: 5px;
.el-form-item {
margin-bottom: 0;
}
}
</style>
<script>
const { getUser } = require("@/server/utils/store");
export default {
data() {
return {
searchForm: { batchArrangeNumber: "", layoutStatus: "", startTime: "" },
userData: getUser()
};
},
computed: {},
mounted() {},
created() {},
methods: {
dropdownCommand(v) {
switch (v) {
case "logout":
this.$confirm("是否退出登录?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$dataStore.delete("user");
this.$router.push("/");
})
.catch(() => {});
break;
case "cache":
this.checkList = [];
this.cacheVisible = true;
break;
}
},
getCountryName(code) {
if (code) {
const item = this.countryList?.find(el => el.countryCode == code);
if (item) {
return `(${item.nameCn})`;
}
}
return "";
},
goSetting() {
this.$router.push({
name: "dtf-setting"
});
},
goSetting1() {
this.$router.push({
name: "dtf-batch-detail"
});
},
getDataInfo() {
this.$emit("search", this.searchForm);
}
}
};
</script>
<template>
<div>
<div class="page-header">
<el-form
style="display: flex;flex-wrap: nowrap;"
inline
:model="searchForm"
>
<el-form-item label="批次号">
<el-input
v-model="searchForm.batchArrangeNumber"
placeholder="请输入批次号"
clearable
></el-input
></el-form-item>
<el-form-item label="排版状态">
<el-select
v-model="searchForm.layoutStatus"
placeholder="请选择排版状态"
clearable
>
<el-option label="待排版" :value="1"></el-option>
<el-option label="排版中" :value="2"> </el-option>
<el-option label="排版成功" :value="3"> </el-option>
<el-option label="排版失败" :value="4"> </el-option>
<el-option label="部分成功" :value="5"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
style="width: 380px;"
clearable
v-model="searchForm.timeRange"
type="datetimerange"
placeholder="选择时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss"
>
</el-date-picker
></el-form-item>
<el-form-item>
<el-button @click="getDataInfo" type="primary"
>查询
</el-button></el-form-item
>
<el-form-item>
<el-button @click="goSetting" type="success"
>DTF排版设置
</el-button></el-form-item
>
</el-form>
<div class="right-user">
<div
v-if="userData.user && userData.user.factory"
style="font-weight: 700;margin-left: 8px;"
>
{{
userData.user.countryCode
? `${userData.user.countryCode}${getCountryName(
userData.user.countryCode
)}`
: "CN(中国)"
}}
</div>
<p v-if="userData.user && userData.user.factory">
{{ userData.user.factory.title }}
</p>
<el-dropdown @command="dropdownCommand">
<div style="margin-right: 20px">
<b style="cursor:pointer;">{{ userData.user.account }}</b>
<i class="el-icon-arrow-down"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.right-user {
display: flex;
align-items: center;
b {
margin: 0 3px;
display: inline-block;
color: black;
font-size: 15px;
font-weight: bold;
}
p {
margin: 0 10px;
font-weight: bold;
display: inline-block;
font-size: 15px;
color: #e6a23c;
}
}
.page-header {
display: flex;
justify-content: space-between;
background-color: #ececec;
padding: 5px;
.el-form-item {
margin-bottom: 0;
}
}
</style>
<template>
<div style="height:100%">
<div class="dtf-setting-page">
<!-- 返回按钮 -->
<div class="page-top">
<el-button icon="el-icon-arrow-left" @click="goBack">
返回
</el-button>
</div>
<el-card class="setting-card">
<template #header>
<div class="card-header">
<i
class="el-icon-setting"
style="color: #568cf9;font-size: 24px;margin-right: 8px;"
></i>
<span>DTF排版设置</span>
</div>
</template>
<div class="setting-group">
<div class="group-title">素材超排版宽度时:</div>
<el-radio-group v-model="form.widthStrategy">
<el-radio :label="1">自动缩小素材,适配烫画模宽度</el-radio>
<el-radio :label="2">超出排版宽度不排版</el-radio>
<el-radio :label="3">超出排版宽度,使用更大规格排版</el-radio>
</el-radio-group>
</div>
<div class="setting-group">
<div class="group-title">单张排版最大素材数:</div>
<el-radio-group v-model="form.materialStrategy">
<el-radio :label="1">批次素材数</el-radio>
<div class="custom-num-row">
<el-radio :label="2">自定义素材数(建议数量30):</el-radio>
<el-input
v-model.number="form.materialCount"
placeholder="请输入素材数"
style="width: 180px; margin-left: 8px"
:disabled="form.materialStrategy !== 2"
/>
</div>
</el-radio-group>
</div>
<div class="setting-group">
<div class="group-title">自动排版设置</div>
<el-radio-group v-model="form.autoLayoutStrategy">
<el-radio :label="1">40+2</el-radio>
<el-radio :label="2">60</el-radio>
<el-radio :label="3">不执行自动排版</el-radio>
</el-radio-group>
</div>
<div class="setting-group">
<div class="group-title">下载文件目录</div>
<div class="dir-input-row">
<el-input v-model="form.downloadDir" readonly style="flex: 1">
<template slot="prepend">素材默认下载位置:</template>
</el-input>
<el-button
icon="el-icon-folder-opened"
@click="selectFolder"
></el-button>
</div>
</div>
<!-- <div class="btn-wrap">
<el-button type="primary" @click="saveConfig">保存设置</el-button>
</div> -->
</el-card>
</div>
</div>
</template>
<script>
const { ipcRenderer } = require("electron");
const { setDTFSetting, getDTFSetting } = require("@/server/utils/store");
const { pathMap } = require("@/config/index.js");
import debounce from "lodash/debounce";
export default {
name: "dtf-setting",
data() {
return {
// 表单配置,可从本地配置文件读取初始化
form: {},
isInitLoad: true
};
},
mounted() {
// 页面加载读取本地配置回填表单
this.loadLocalConfig();
this.debounceSaveConfig = debounce(() => {
this.saveConfig();
}, 300);
},
watch: {
form: {
handler() {
console.log(999);
if (this.isInitLoad) return;
this.debounceSaveConfig();
},
deep: true
}
},
beforeDestroy() {
// 组件销毁清除防抖,防止内存泄漏
this.debounceSaveConfig.cancel();
},
methods: {
// 返回上一页
goBack() {
this.$router.push({ name: "design-dtf" });
},
// 读取本地配置
async loadLocalConfig() {
this.isFist = true;
try {
const { data } = await this.$api.post(pathMap["productionConfig"]);
this.form = { ...getDTFSetting(), ...data };
setDTFSetting({ ...this.form });
this.$nextTick(() => (this.isInitLoad = false));
} catch (error) {
console.log(error);
}
},
// 选择文件夹弹窗
selectFolder() {
ipcRenderer.removeAllListeners("select-folder-result");
ipcRenderer.send("request-select-folder", this.form.downloadDir);
ipcRenderer.once("select-folder-result", (_, path) => {
if (path) {
this.form = {
...this.form,
downloadDir: path
};
}
});
// console.log("selectFolder", this.form);
},
// 保存配置到本地文件
async saveConfig() {
console.log(999);
if (this.form.autoLayoutStrategy == 2 && this.form.widthStrategy == 3) {
await this.$confirm(
"由于排版设置中选择“按更大规格排版”仅适用于40+2,选60时,默认超60的规格不排版",
"提示",
{
confirmButtonText: "确定",
type: "warning"
}
);
}
await this.$api.post(pathMap["productionConfigUpdate"], this.form);
setDTFSetting({ ...this.form });
console.log(getDTFSetting());
console.log(this.form);
this.$message.success("排版设置保存成功!");
}
}
};
</script>
<style lang="less" scoped>
.dtf-setting-page {
padding: 16px;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.page-top {
margin-bottom: 12px;
}
.setting-card {
width: 100%;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 20px;
font-weight: 700;
}
.setting-group {
margin-bottom: 24px;
}
.group-title {
font-size: 20px;
color: #303133;
margin-bottom: 10px;
}
.el-radio-group {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px 16px;
border: 1px solid #e4e7ed;
border-radius: 4px;
}
.custom-num-row {
display: flex;
align-items: center;
}
.dir-input-row {
display: flex;
align-items: center;
gap: 8px;
}
.btn-wrap {
margin-top: 30px;
text-align: left;
}
.setting-card {
::v-deep {
.el-card__header {
border-bottom: none;
}
.el-card__body {
padding: 10px 200px;
}
}
}
.el-radio-group {
.el-radio {
padding: 10px 0;
font-size: 18px;
}
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment