Compare commits

...

67 Commits

Author SHA1 Message Date
aixianling
f69c86f92b 管理权限统一 2024-11-05 09:35:31 +08:00
aixianling
c7cb7c103d 先提交一下,打个钉子 2024-11-04 17:47:02 +08:00
juanmao2009
1ecf85475d Merge branch 'label' of gitee.com:three-make-money/temu-plugin into master
Signed-off-by: juanmao2009 <499672082@qq.com>
2024-10-19 09:47:44 +00:00
liushiwei
9c2b6c434c 调整 2024-10-19 17:44:22 +08:00
yanran200830
90a3e60e76 【新增】管理SKU页面 2024-10-18 13:37:26 +08:00
yanran200830
4d8b597abf 【新增】标签目录和打印组件 2024-10-16 18:15:51 +08:00
liushiwei
4a90536696 调整 2024-09-19 21:28:59 +08:00
liushiwei
5e8b1ea682 调整 2024-08-15 21:21:58 +08:00
liushiwei
25ba3eb387 调整 2024-08-07 14:46:26 +08:00
liushiwei
0808f42ce7 调整 2024-07-18 14:48:44 +08:00
liushiwei
6356004d74 调整 2024-06-29 11:51:35 +08:00
liushiwei
9253427bcc 提交 2024-05-25 10:12:50 +08:00
liushiwei
10c5bce468 调整 2024-05-24 22:25:50 +08:00
liushiwei
2c2538c828 Merge branch 'master' of https://gitee.com/three-make-money/temu-plugin 2024-05-18 16:06:53 +08:00
liushiwei
2e1a84681d 调整 2024-05-18 16:06:44 +08:00
刘仕伟
360803de94 Merge branch 'master' of https://gitee.com/three-make-money/temu-plugin 2024-05-18 16:05:18 +08:00
刘仕伟
5fb4a5fb6b 调整 2024-05-18 16:05:11 +08:00
98fa8fc150 妈蛋,总算搞定了,明天刘老板自己搞 2024-04-25 01:40:31 +08:00
aixianling
c3ef37e184 解决token获取 2024-04-23 18:15:42 +08:00
aixianling
f5c64d0800 解决token获取 2024-04-23 17:55:23 +08:00
aixianling
fa9eba44cf 允许手动传入token 2024-04-23 15:47:30 +08:00
aixianling
45f727ed7e 兼容异常,并给出示例 2024-04-23 15:10:24 +08:00
aixianling
482391d6d3 优化方法 2024-04-23 14:02:12 +08:00
aixianling
845ceae3cd 接入到chromeApi当中 2024-04-22 18:07:55 +08:00
aixianling
f7af9b6b25 增加速卖通的获取sign的方法 2024-04-22 17:33:32 +08:00
aixianling
a0cd09e6ed 更新源 2024-04-19 12:00:50 +08:00
刘仕伟
bc2403bea6 调整 2024-03-15 21:13:07 +08:00
刘仕伟
218f382c4a 调整 2024-03-13 22:31:23 +08:00
liushiwei
60c4b0d9c0 调整 2024-03-13 14:06:59 +08:00
刘仕伟
be762a6f23 3.1.12 2024-03-04 19:34:04 +08:00
刘仕伟
81c81b3c2a 调整 2024-03-04 19:28:50 +08:00
刘仕伟
66624fc77a 调整 2024-02-28 11:26:21 +08:00
刘仕伟
d1c746be14 调整 2024-02-27 15:38:08 +08:00
刘仕伟
8e87162e94 调整 2024-02-23 10:29:39 +08:00
刘仕伟
cd08530b62 调整 2024-01-26 00:20:31 +08:00
刘仕伟
7eec4b50ce 调整 2024-01-24 10:31:55 +08:00
刘仕伟
0047e3dbe7 调整 2024-01-23 22:40:02 +08:00
刘仕伟
21b754497b 调整 2024-01-20 01:37:46 +08:00
刘仕伟
492e6b83ea 调整 2024-01-18 00:46:23 +08:00
aixianling
eaea055f6c 拆解页面脚本和应用层脚本的强关联 2024-01-10 15:02:05 +08:00
liushiwei
53e8d2da74 调整 2024-01-10 11:32:43 +08:00
刘仕伟
fc6cec2eca 调整 2024-01-10 01:27:15 +08:00
liushiwei
b304fb53a0 调整 2024-01-09 22:03:14 +08:00
f9d84526a2 针对上传文件进行处理 2023-12-25 23:44:26 +08:00
刘仕伟
4863fcb199 文 件上传 2023-12-25 21:18:46 +08:00
刘仕伟
3db7e387f1 更新 2023-12-20 22:09:55 +08:00
刘仕伟
2e957ac485 更新 2023-12-20 20:45:00 +08:00
liushiwei
0a7f8eb036 更新 2023-12-16 22:56:15 +08:00
liushiwei
fa57f0fa99 更新 2023-12-06 16:50:06 +08:00
liushiwei
3921daba27 更新 2023-11-27 21:30:08 +08:00
liushiwei
15cd54a44e 更新 2023-11-24 01:04:54 +08:00
liushiwei
fe6fd6cfdc 更新 2023-11-10 10:41:19 +08:00
liushiwei
0016f7e5bc 更新 2023-11-08 22:55:13 +08:00
liushiwei
0e9d59b1eb 调整 2023-11-08 13:39:21 +08:00
liushiwei
66c89f71e1 更新 2023-10-31 10:52:27 +08:00
liushiwei
9909a938ca 更新 2023-10-28 12:28:12 +08:00
liushiwei
eb518c412f 更新 2023-10-26 11:26:37 +08:00
liushiwei
af109bf3ad 调整 2023-10-25 16:18:28 +08:00
liushiwei
9c644627bc 版本 2023-10-25 14:05:32 +08:00
liushiwei
0522cadeee 调整 2023-10-25 12:20:10 +08:00
liushiwei
e52e62c1d5 调整 2023-10-19 00:03:01 +08:00
liushiwei
17986a574b 更新 2023-10-17 14:03:25 +08:00
liushiwei
de0229419b 更新 2023-10-16 12:25:16 +08:00
liushiwei
af84e9fca6 调整 2023-10-16 10:18:31 +08:00
liushiwei
0a1f6f1105 更新 2023-10-16 01:23:01 +08:00
juanmao2009
0076b54016 !5 更新
Merge pull request !5 from juanmao2009/ai-rob
2023-10-15 14:25:33 +00:00
liushiwei
a26050de73 更新 2023-10-15 22:25:05 +08:00
135 changed files with 36393 additions and 4870 deletions

View File

@@ -3,23 +3,31 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vue-cli-service --env.NODE_ENV=development build-watch --mode development", "dev": "vue-cli-service --env.NODE_ENV=development build-watch --mode development",
"build": "vue-cli-service build" "build": "vue-cli-service build"
}, },
"dependencies": { "dependencies": {
"@antv/g2plot": "^2.4.31", "@antv/g2plot": "^2.4.25",
"axios": "^1.4.0", "axios": "^1.4.0",
"bi-vue-mindmap": "^0.6.12",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"crypto-js": "^4.0.0",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"element-ui": "^2.15.13", "element-ui": "^2.15.13",
"file-saver": "^2.0.5",
"html2canvas": "^1.4.1",
"query-string": "^9.0.0",
"spark-md5": "^3.0.2",
"v-viewer": "^1.6.4", "v-viewer": "^1.6.4",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-cropper": "^0.6.4", "vue-cropper": "^0.6.4",
"vue-json-excel": "^0.3.0", "vue-json-excel": "^0.3.0",
"vue-plugin-hiprint": "^0.0.56",
"vue-qr": "^4.0.9", "vue-qr": "^4.0.9",
"vue-router": "^3.2.0", "vue-router": "^3.2.0",
"vuex": "^3.4.0", "vuex": "^3.4.0",
"vuex-persistedstate": "^4.1.0" "vuex-persistedstate": "^4.1.0",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
@@ -31,7 +39,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.0.3",
"javascript-obfuscator": "2.6.0", "javascript-obfuscator": "2.6.0",
"sass": "^1.68.0", "sass": "1.32.13",
"sass-loader": "^7.3.1", "sass-loader": "^7.3.1",
"vue-cli-plugin-chrome-extension-cli": "~1.1.4", "vue-cli-plugin-chrome-extension-cli": "~1.1.4",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "^2.6.14",

349
public/css/print-lock.css Normal file
View File

@@ -0,0 +1,349 @@
@media print {
body {
margin: 0px;
padding: 0px;
}
}
@page {
margin: 0;
}
.hiprint-printPaper * {
box-sizing: border-box;
-moz-box-sizing: border-box; /* Firefox */
-webkit-box-sizing: border-box; /* Safari */
}
.hiprint-printPaper *:focus {
outline: -webkit-focus-ring-color auto 0px;
}
.hiprint-printPaper {
position: relative;
padding: 0 0 0 0;
page-break-after: always;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
user-select: none;
overflow-x: hidden;
overflow: hidden;
}
.hiprint-printPaper .hiprint-printPaper-content {
position: relative;
}
/* 火狐浏览器打印 第一页过后 重叠问题 */
@-moz-document url-prefix() {
.hiprint-printPaper .hiprint-printPaper-content {
position: relative;
margin-top: 20px;
top: -20px
}
}
.hiprint-printPaper.design {
overflow: visible;
}
.hiprint-printTemplate .hiprint-printPanel {
page-break-after: always;
}
.hiprint-printPaper, hiprint-printPanel {
box-sizing: border-box;
border: 0px;
}
.hiprint-printPanel .hiprint-printPaper:last-child {
page-break-after: avoid;
}
.hiprint-printTemplate .hiprint-printPanel:last-child {
page-break-after: avoid;
}
.hiprint-printPaper .hideheaderLinetarget {
border-top: 0px dashed rgb(201, 190, 190) !important;
}
.hiprint-printPaper .hidefooterLinetarget {
border-top: 0px dashed rgb(201, 190, 190) !important;
}
.hiprint-printPaper.design {
border: 1px dashed rgba(170, 170, 170, 0.7);
}
.design .hiprint-printElement-table-content, .design .hiprint-printElement-longText-content {
overflow: hidden;
box-sizing: border-box;
}
.design .resize-panel {
box-sizing: border-box;
border: 1px dotted;
}
.hiprint-printElement-text {
background-color: transparent;
background-repeat: repeat;
padding: 0 0 0 0;
border: 0.75pt none rgb(0, 0, 0);
direction: ltr;
font-family: 'SimSun';
font-size: 9pt;
font-style: normal;
font-weight: normal;
padding-bottom: 0pt;
padding-left: 0pt;
padding-right: 0pt;
padding-top: 0pt;
text-align: left;
text-decoration: none;
line-height: 9.75pt;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
}
.design .hiprint-printElement-text-content {
border: 1px dashed rgb(206, 188, 188);
box-sizing: border-box;
}
.hiprint-printElement-longText {
background-color: transparent;
background-repeat: repeat;
border: 0.75pt none rgb(0, 0, 0);
direction: ltr;
font-family: 'SimSun';
font-size: 9pt;
font-style: normal;
font-weight: normal;
padding-bottom: 0pt;
padding-left: 0pt;
padding-right: 0pt;
padding-top: 0pt;
text-align: left;
text-decoration: none;
line-height: 9.75pt;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
/*white-space: pre-wrap*/
}
.hiprint-printElement-table {
background-color: transparent;
background-repeat: repeat;
color: rgb(0, 0, 0);
border-color: rgb(0, 0, 0);
border-style: none;
direction: ltr;
font-family: 'SimSun';
font-size: 9pt;
font-style: normal;
font-weight: normal;
padding-bottom: 0pt;
padding-left: 0pt;
padding-right: 0pt;
padding-top: 0pt;
text-align: left;
text-decoration: none;
padding: 0 0 0 0;
box-sizing: border-box;
line-height: 9.75pt;
}
.hiprint-printElement-table thead {
background: #e8e8e8;
font-weight: 700;
}
table.hiprint-printElement-tableTarget {
width: 100%;
}
.hiprint-printElement-tableTarget, .hiprint-printElement-tableTarget tr, .hiprint-printElement-tableTarget td {
border-color: rgb(0, 0, 0);
/*border-style: none;*/
/*border: 1px solid rgb(0, 0, 0);*/
font-weight: normal;
direction: ltr;
padding-bottom: 0pt;
padding-left: 4pt;
padding-right: 4pt;
padding-top: 0pt;
text-decoration: none;
vertical-align: middle;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
/*line-height: 9.75pt;
font-size: 9pt;*/
}
.hiprint-printElement-tableTarget-border-all {
border: 1px solid;
}
.hiprint-printElement-tableTarget-border-none {
border: 0px solid;
}
.hiprint-printElement-tableTarget-border-lr {
border-left: 1px solid;
border-right: 1px solid;
}
.hiprint-printElement-tableTarget-border-left {
border-left: 1px solid;
}
.hiprint-printElement-tableTarget-border-right {
border-right: 1px solid;
}
.hiprint-printElement-tableTarget-border-tb {
border-top: 1px solid;
border-bottom: 1px solid;
}
.hiprint-printElement-tableTarget-border-top {
border-top: 1px solid;
}
.hiprint-printElement-tableTarget-border-bottom {
border-bottom: 1px solid;
}
.hiprint-printElement-tableTarget-border-td-none td {
border: 0px solid;
}
.hiprint-printElement-tableTarget-border-td-all td:not(:nth-last-child(-n+2)) {
border-right: 1px solid;
}
.hiprint-printElement-tableTarget-border-td-all td:last-child {
border-left: 1px solid;
}
.hiprint-printElement-tableTarget-border-td-all td:last-child:first-child {
border-left: none;
}
/*.hiprint-printElement-tableTarget tr,*/
.hiprint-printElement-tableTarget td {
height: 18pt;
}
.hiprint-printPaper .hiprint-paperNumber {
font-size: 9pt;
}
.design .hiprint-printElement-table-handle {
position: absolute;
height: 21pt;
width: 21pt;
background: red;
z-index: 1;
}
.hiprint-printPaper .hiprint-paperNumber-disabled {
float: right !important;
right: 0 !important;
color: gainsboro !important;
}
.hiprint-printElement-vline, .hiprint-printElement-hline {
border: 0px none rgb(0, 0, 0);
}
.hiprint-printElement-vline {
border-left: 0.75pt solid #000;
border-right: 0px none rgb(0, 0, 0) !important;
border-bottom: 0px none rgb(0, 0, 0) !important;
border-top: 0px none rgb(0, 0, 0) !important;
}
.hiprint-printElement-hline {
border-top: 0.75pt solid #000;
border-right: 0px none rgb(0, 0, 0) !important;
border-bottom: 0px none rgb(0, 0, 0) !important;
border-left: 0px none rgb(0, 0, 0) !important;
}
.hiprint-printElement-oval, .hiprint-printElement-rect {
border: 0.75pt solid #000;
}
.hiprint-text-content-middle {
}
.hiprint-text-content-middle > div {
display: grid;
align-items: center;
}
.hiprint-text-content-bottom {
}
.hiprint-text-content-bottom > div {
display: grid;
align-items: flex-end;
}
.hiprint-text-content-wrap {
}
.hiprint-text-content-wrap .hiprint-text-content-wrap-nowrap {
white-space: nowrap;
}
.hiprint-text-content-wrap .hiprint-text-content-wrap-clip {
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
}
.hiprint-text-content-wrap .hiprint-text-content-wrap-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/*hi-grid-row */
.hi-grid-row {
position: relative;
height: auto;
margin-right: 0;
margin-left: 0;
zoom: 1;
display: block;
box-sizing: border-box;
}
.hi-grid-row::after, .hi-grid-row::before {
display: table;
content: '';
box-sizing: border-box;
}
.hi-grid-col {
display: block;
box-sizing: border-box;
position: relative;
float: left;
flex: 0 0 auto;
}
.table-grid-row {
margin-left: -0pt;
margin-right: -0pt;
}
.tableGridColumnsGutterRow {
padding-left: 0pt;
padding-right: 0pt;
}
.hiprint-gridColumnsFooter {
text-align: left;
clear: both;
}

View File

@@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" type="text/css" media="print" href="<%= BASE_URL %>css/print-lock.css">
<title>TEMU助手</title> <title>TEMU助手</title>
</head> </head>
<body> <body>

View File

@@ -1,5 +1,10 @@
function matchSheinDomain(url) {
const urlPattern = /https:\/\/([\da-z\.-]+)\.shein\.com\/([\S.-]+)-p-([\S.-]+)/
return urlPattern.test(url);
}
function init() { function init() {
if (window.location.href.startsWith('https://www.aliexpress.us/item/')) { if (window.location.href.startsWith('https://www.aliexpress.com/item/')) {
const popup = document.createElement("div") const popup = document.createElement("div")
popup.innerText = "下载图片" popup.innerText = "下载图片"
const styles = { const styles = {
@@ -148,7 +153,7 @@ function init() {
var imgObjList = document.querySelectorAll('div.aplus-v2 img') var imgObjList = document.querySelectorAll('div.aplus-v2 img')
for (var i = 0; i < imgObjList.length; i++) { for (var i = 0; i < imgObjList.length; i++) {
baseList.push({type: 0, index: i+1, src: imgObjList[i].getAttribute('data-src'), folder: imgsDetail}) baseList.push({type: 0, index: i+1, src: imgObjList[i].getAttribute('data-src') || imgObjList[i].src, folder: imgsDetail})
} }
for (var k = 0; k < baseList.length; k++) { for (var k = 0; k < baseList.length; k++) {
@@ -187,6 +192,111 @@ function init() {
}) })
document.body.appendChild(popup) document.body.appendChild(popup)
} }
} else if (matchSheinDomain(window.location.href)) {
const popup = document.createElement("div")
popup.innerText = "下载图片"
const styles = {
position: "fixed",
right: '10px',
top: '60px',
zIndex: 9999,
padding: "8px",
background: "#409EFF",
color: "#fff",
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
popup.addEventListener('click', async () => {
var baseList = [];
var downloadList = []
let bannerIdx = 1, detailIdx = 1
baseList.push({type: 0, index: bannerIdx++, src: window.gbRawData.productIntroData.goods_imgs.main_image.origin_image})
var detailImages = window.gbRawData.productIntroData.goods_imgs.detail_image
for (var i = 0; i < detailImages.length; i++) {
if (!(detailImages[i].isMoreDetail)) {
baseList.push({type: 0, index: bannerIdx++, src: detailImages[i].origin_image})
} else {
baseList.push({type: 1, index: detailIdx++, src: detailImages[i].origin_image})
}
}
var video = document.querySelector('video')
if (window.gbRawData.productIntroData.goods_imgs.video_url) {
baseList.push({type: 2, index: 1, src: window.gbRawData.productIntroData.goods_imgs.video_url})
}
var zip = new JSZip();
var imgsBanner = zip.folder("轮播图");
var imgsDetail = zip.folder("详情图");
var videos = zip.folder("视频");
for (var k = 0; k < baseList.length; k++) {
let type = baseList[k].type
let index = baseList[k].index
if (type == 2) {
let x = new XMLHttpRequest()
x.open('GET', baseList[k].src, true)
x.responseType = 'blob'
x.onload = (e) => {
downloadList.push({type: type, index: index, data: x.response});
if (downloadList.length === baseList.length && downloadList.length > 0) {
for (let l = 0; l < downloadList.length; l++) {
if (downloadList[l].type == '0') {
imgsDetail.file(`详情图${downloadList[l].index}.png`, downloadList[l].data, { base64: true });
} else if (downloadList[l].type == '1') {
imgsBanner.file(`轮播图${downloadList[l].index}.png`, downloadList[l].data, { base64: true });
} else if (downloadList[l].type == '2') {
videos.file(`视频.mp4`, downloadList[l].data, { Blob: true });
}
}
zip.generateAsync({ type: "blob" }).then(function (content) {
// see FileSaver.js
saveAs(content, "shein_" + id + ".zip");
});
}
}
x.send()
} else {
let image = new Image();
// 解决跨域 Canvas 污染问题
image.setAttribute("crossOrigin", "anonymous");
image.onload = function () {
let canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
let context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
let url = canvas.toDataURL(); // 得到图片的base64编码数据
canvas.toDataURL("image/png");
downloadList.push({type: type, index: index, data: url.substring(22)}); // 去掉base64编码前的 data:image/png;base64,
if (downloadList.length === baseList.length && downloadList.length > 0) {
for (let l = 0; l < downloadList.length; l++) {
if (downloadList[l].type == '0') {
imgsDetail.file(`详情图${downloadList[l].index}.png`, downloadList[l].data, { base64: true });
} else if (downloadList[l].type == '1') {
imgsBanner.file(`轮播图${downloadList[l].index}.png`, downloadList[l].data, { base64: true });
} else if (downloadList[l].type == '2') {
videos.file(`视频.mp4`, downloadList[l].data, { Blob: true });
}
}
zip.generateAsync({ type: "blob" }).then(function (content) {
// see FileSaver.js
saveAs(content, "shein_" + id + ".zip");
});
}
};
image.src = baseList[k].src;
}
}
})
document.body.appendChild(popup)
} }
} }

390
public/js/temuSeller.js Normal file
View File

@@ -0,0 +1,390 @@
function matchTemuDomain(url) {
const urlPattern = /https:\/\/([\da-z\.-]+)\.kuajingmaihuo\.com\//
return urlPattern.test(url);
}
function init() {
if (matchTemuDomain(window.location.href)) {
let j = 0
let timer = setInterval(() => {
let models = document.querySelectorAll('div[class^="MDL_header"]')
let flag = false
for (let i = 0; i < models.length; i++) {
if (models[i].textContent.includes("商品降价提醒") || models[i].textContent.includes("降价,提升竞争力,避免限制备货")) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
models[i].appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = models[i].parentElement.querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}
const regex = /price-adjust-confirm[^\n]+content/
let modelsAll = document.querySelectorAll('div[class*="price-adjust-confirm"]')
const results = Array.from(modelsAll).filter(item => regex.test(item.getAttribute('class')))
for (let i = 0; i < results.length; i++) {
let tipsObj = results[i].querySelector('div[class*="_tips"]')
let spanObj = tipsObj.querySelector('div span:last-child')
if (spanObj) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
spanObj.appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = results[i].querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}
let models2 = document.querySelectorAll('div[class^="modal-content_platBanner"]')
for (let i = 0; i < models2.length; i++) {
let tipsObj = models2[i].parentElement.querySelector('div[class^="modal-content_content"]')
let spanObj = tipsObj.querySelector('div span:last-child')
if (spanObj) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
spanObj.appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = models2[i].parentElement.querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj && firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}
let models3 = document.querySelectorAll('div[class^="modal-content_content"]')
for (let i = 0; i < models2.length; i++) {
let tipsObj = models2[i].parentElement.querySelector('div[class^="modal-content_content"]')
let spanObj = tipsObj.querySelector('div span:last-child')
if (spanObj) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
spanObj.appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = models2[i].parentElement.querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj && firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}
/*let models3 = document.querySelectorAll('div[class^="price-adjust-confirm-new_content"]')
for (let i = 0; i < models3.length; i++) {
let tipsObj = models3[i].querySelector('div[class^="price-adjust-confirm-new_tips"]')
let spanObj = tipsObj.querySelector('div span:last-child')
if (spanObj) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
spanObj.appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = models3[i].querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}
let models4 = document.querySelectorAll('div[class^="price-adjust-confirm-system-old_contentWrp"]')
for (let i = 0; i < models4.length; i++) {
let tipsObj = models4[i].querySelector('div[class^="price-adjust-confirm-system-old_tips"]')
let spanObj = tipsObj.querySelector('div span:last-child')
if (spanObj) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
spanObj.appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = models4[i].querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}
let models5 = document.querySelectorAll('div[class^="new-price-adjust-confirm_content"]')
for (let i = 0; i < models5.length; i++) {
let tipsObj = models5[i].querySelector('div[class^="new-price-adjust-confirm_tips"]')
let spanObj = tipsObj.querySelector('div span:last-child')
if (spanObj) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
spanObj.appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = models5[i].querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}
let models6 = document.querySelectorAll('div[class^="grape-price-adjust-confirm_content"]')
for (let i = 0; i < models6.length; i++) {
let tipsObj = models6[i].querySelector('div[class^="grape-price-adjust-confirm_tips"]')
let spanObj = tipsObj.querySelector('div span:last-child')
if (spanObj) {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
padding: "8px",
background: "#fb7701",
color: "#fff",
display: 'inline',
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
spanObj.appendChild(popup)
popup.addEventListener('click', async () => {
let tbodyObj = models6[i].querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let firstLabelObj = tdObj.querySelector('div label:first-child')
if (firstLabelObj.getAttribute("data-checked")) {
let labelObj = tdObj.querySelector('div label:nth-child(2)')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
})
flag = true
}
}*/
if (flag) {
clearInterval(timer)
} else {
j++
if (j == 10) {
clearInterval(timer)
}
}
}, 3000)
// document.body.appendChild(popup)
}
}
/*
function createElement() {
const popup = document.createElement("div")
popup.innerText = "拒绝调价"
const styles = {
position: "fixed",
right: '10px',
top: '60px',
zIndex: 999999,
padding: "8px",
background: "#409EFF",
color: "#fff",
borderRadius: "8px",
cursor: "pointer"
}
for (const e in styles) {
popup.style[e] = styles[e]
}
popup.addEventListener('click', async () => {
let divObj1 = document.querySelector('div[class^="price-adjust-confirm-system_contentWrp"]')
if (divObj1) {
let tbodyObj = divObj1.querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let labelObj = tdObj.querySelector('div label:last-child')
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
// await sleepSync(50)
radioObj.click()
}
}
let divObj2 = document.querySelector('div[class^="price-adjust-confirm_header"]')
if (divObj2) {
let divObj = divObj2.nextElementSibling
let tbodyObj = divObj.querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let labelObj = tdObj.querySelector('div label:last-child')
//let radioObj = labelObj.querySelector("[type='radio']")
// await sleepSync(50)
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
radioObj.click()
}
}
let divObj3 = document.querySelector('div[class^="price-adjust-confirm-new_banner"]')
if (divObj3) {
let divObj = divObj3.nextElementSibling
let tbodyObj = divObj.querySelector('tbody')
let trList = tbodyObj.querySelectorAll('tr')
for (let i = 0; i < trList.length; i++) {
let tdObj = trList[i].querySelector('td:last-child')
let labelObj = tdObj.querySelector('div label:last-child')
//let radioObj = labelObj.querySelector("[type='radio']")
// await sleepSync(50)
let radioObj = labelObj.querySelector('div[class^="RD_radioWrapper"]')
radioObj.click()
}
}
})
return popup
}
*/
init()

View File

@@ -0,0 +1,8 @@
function injectScript(file, node) {
var th = document.getElementsByTagName(node)[0];
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src', file);
th.appendChild(s);
}
injectScript( chrome.runtime.getURL('/js/temuSeller.js'), 'body');

View File

@@ -8,17 +8,17 @@
{ {
"header": "Origin", "header": "Origin",
"operation": "set", "operation": "set",
"value": "https://kuajing.pinduoduo.com" "value": "https://seller.kuajingmaihuo.com"
}, },
{ {
"header": "Referer", "header": "Referer",
"operation": "set", "operation": "set",
"value": "https://kuajing.pinduoduo.com/main/order-manage" "value": "https://seller.kuajingmaihuo.com/main/order-manage"
} }
] ]
}, },
"condition": { "condition": {
"urlFilter": "||kuajing.pinduoduo.com" "urlFilter": "||seller.kuajingmaihuo.com"
} }
} }
] ]

24
public/rules_10.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 21,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://oms.goodcang.com/"
},
{
"header": "Referer",
"operation": "set",
"value": "https://oms.goodcang.com/"
}
]
},
"condition": {
"urlFilter": "||oms.goodcang.com"
}
}
]

24
public/rules_11.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 22,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://sellerhub.shein.com/"
},
{
"header": "Referer",
"operation": "set",
"value": "https://sellerhub.shein.com/"
}
]
},
"condition": {
"urlFilter": "||sellerhub.shein.com"
}
}
]

24
public/rules_2.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 13,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://www.temu.com"
},
{
"header": "Referer",
"operation": "set",
"value": "https://www.temu.com"
}
]
},
"condition": {
"urlFilter": "||www.temu.com"
}
}
]

24
public/rules_3.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 14,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://www.aliexpress.com"
},
{
"header": "Referer",
"operation": "set",
"value": "https://www.aliexpress.com"
}
]
},
"condition": {
"urlFilter": "||aeproductsourcesite.alicdn.com"
}
}
]

24
public/rules_4.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 15,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://seller.kuajingmaihuo.com"
},
{
"header": "Referer",
"operation": "set",
"value": "https://seller.kuajingmaihuo.com"
}
]
},
"condition": {
"urlFilter": "||file.kuajingmaihuo.com"
}
}
]

24
public/rules_5.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 16,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://sso.geiwohuo.com/"
},
{
"header": "Referer",
"operation": "set",
"value": "https://sso.geiwohuo.com/"
}
]
},
"condition": {
"urlFilter": "||sso.geiwohuo.com"
}
}
]

29
public/rules_6.json Normal file
View File

@@ -0,0 +1,29 @@
[
{
"id": 17,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://csp.aliexpress.com"
},
{
"header": "Referer",
"operation": "set",
"value": "https://csp.aliexpress.com/"
},
{
"header": "Sec-Fetch-Site",
"operation": "set",
"value": "same-site"
}
]
},
"condition": {
"urlFilter": "||aliexpress.com"
}
}
]

29
public/rules_7.json Normal file
View File

@@ -0,0 +1,29 @@
[
{
"id": 18,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://img.ltwebstatic.com"
},
{
"header": "Referer",
"operation": "set",
"value": "https://img.ltwebstatic.com"
},
{
"header": "Sec-Fetch-Mode",
"operation": "set",
"value": "no-cors"
}
]
},
"condition": {
"urlFilter": "||img.ltwebstatic.com"
}
}
]

24
public/rules_8.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 19,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://www.geiwohuo.com/"
},
{
"header": "Referer",
"operation": "set",
"value": "https://www.geiwohuo.com/"
}
]
},
"condition": {
"urlFilter": "||www.geiwohuo.com"
}
}
]

24
public/rules_9.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 20,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://agentseller.temu.com/"
},
{
"header": "Referer",
"operation": "set",
"value": "https://agentseller.temu.com/"
}
]
},
"condition": {
"urlFilter": "||agentseller.temu.com"
}
}
]

162
src/api/aliExpress.js Normal file
View File

@@ -0,0 +1,162 @@
/**
*
* @param token 从cookie中获取,判断从[_m_h5_c,_m_h5_tk]中取值,优先判断第一个
* @param appKey 取值window.mtopConfig
* @param formData formData中的data
* @param t 时间戳
* @returns {*}
*/
export const getSign = (token, appKey, formData, t = (new Date()).getTime()) => {
token = token?.split("_")[0]
console.log("获取sign的参数:", token, appKey, formData, t)
return {
t, sign: function (e) {
function t(e, t) {
return e << t | e >>> 32 - t
}
function n(e, t) {
var n, i, r, o, a;
return r = 2147483648 & e,
o = 2147483648 & t,
a = (1073741823 & e) + (1073741823 & t),
(n = 1073741824 & e) & (i = 1073741824 & t) ? 2147483648 ^ a ^ r ^ o : n | i ? 1073741824 & a ? 3221225472 ^ a ^ r ^ o : 1073741824 ^ a ^ r ^ o : a ^ r ^ o
}
function i(e, i, r, o, a, s, l) {
return e = n(e, n(n(function (e, t, n) {
return e & t | ~e & n
}(i, r, o), a), l)),
n(t(e, s), i)
}
function r(e, i, r, o, a, s, l) {
return e = n(e, n(n(function (e, t, n) {
return e & n | t & ~n
}(i, r, o), a), l)),
n(t(e, s), i)
}
function o(e, i, r, o, a, s, l) {
return e = n(e, n(n(function (e, t, n) {
return e ^ t ^ n
}(i, r, o), a), l)),
n(t(e, s), i)
}
function a(e, i, r, o, a, s, l) {
return e = n(e, n(n(function (e, t, n) {
return t ^ (e | ~n)
}(i, r, o), a), l)),
n(t(e, s), i)
}
function s(e) {
var t, n = "", i = "";
for (t = 0; 3 >= t; t++)
n += (i = "0" + (e >>> 8 * t & 255).toString(16)).substr(i.length - 2, 2);
return n
}
var l, u, c, d, p, f, h, g, m, v;
for (v = function (e) {
for (var t, n = e.length, i = n + 8, r = 16 * ((i - i % 64) / 64 + 1), o = new Array(r - 1), a = 0, s = 0; n > s;)
a = s % 4 * 8,
o[t = (s - s % 4) / 4] = o[t] | e.charCodeAt(s) << a,
s++;
return a = s % 4 * 8,
o[t = (s - s % 4) / 4] = o[t] | 128 << a,
o[r - 2] = n << 3,
o[r - 1] = n >>> 29,
o
}(e = function (e) {
e = e.replace(/\r\n/g, "\n");
for (var t = "", n = 0; n < e.length; n++) {
var i = e.charCodeAt(n);
128 > i ? t += String.fromCharCode(i) : i > 127 && 2048 > i ? (t += String.fromCharCode(i >> 6 | 192),
t += String.fromCharCode(63 & i | 128)) : (t += String.fromCharCode(i >> 12 | 224),
t += String.fromCharCode(i >> 6 & 63 | 128),
t += String.fromCharCode(63 & i | 128))
}
return t
}(e)),
f = 1732584193,
h = 4023233417,
g = 2562383102,
m = 271733878,
l = 0; l < v.length; l += 16)
u = f,
c = h,
d = g,
p = m,
f = i(f, h, g, m, v[l], 7, 3614090360),
m = i(m, f, h, g, v[l + 1], 12, 3905402710),
g = i(g, m, f, h, v[l + 2], 17, 606105819),
h = i(h, g, m, f, v[l + 3], 22, 3250441966),
f = i(f, h, g, m, v[l + 4], 7, 4118548399),
m = i(m, f, h, g, v[l + 5], 12, 1200080426),
g = i(g, m, f, h, v[l + 6], 17, 2821735955),
h = i(h, g, m, f, v[l + 7], 22, 4249261313),
f = i(f, h, g, m, v[l + 8], 7, 1770035416),
m = i(m, f, h, g, v[l + 9], 12, 2336552879),
g = i(g, m, f, h, v[l + 10], 17, 4294925233),
h = i(h, g, m, f, v[l + 11], 22, 2304563134),
f = i(f, h, g, m, v[l + 12], 7, 1804603682),
m = i(m, f, h, g, v[l + 13], 12, 4254626195),
g = i(g, m, f, h, v[l + 14], 17, 2792965006),
f = r(f, h = i(h, g, m, f, v[l + 15], 22, 1236535329), g, m, v[l + 1], 5, 4129170786),
m = r(m, f, h, g, v[l + 6], 9, 3225465664),
g = r(g, m, f, h, v[l + 11], 14, 643717713),
h = r(h, g, m, f, v[l], 20, 3921069994),
f = r(f, h, g, m, v[l + 5], 5, 3593408605),
m = r(m, f, h, g, v[l + 10], 9, 38016083),
g = r(g, m, f, h, v[l + 15], 14, 3634488961),
h = r(h, g, m, f, v[l + 4], 20, 3889429448),
f = r(f, h, g, m, v[l + 9], 5, 568446438),
m = r(m, f, h, g, v[l + 14], 9, 3275163606),
g = r(g, m, f, h, v[l + 3], 14, 4107603335),
h = r(h, g, m, f, v[l + 8], 20, 1163531501),
f = r(f, h, g, m, v[l + 13], 5, 2850285829),
m = r(m, f, h, g, v[l + 2], 9, 4243563512),
g = r(g, m, f, h, v[l + 7], 14, 1735328473),
f = o(f, h = r(h, g, m, f, v[l + 12], 20, 2368359562), g, m, v[l + 5], 4, 4294588738),
m = o(m, f, h, g, v[l + 8], 11, 2272392833),
g = o(g, m, f, h, v[l + 11], 16, 1839030562),
h = o(h, g, m, f, v[l + 14], 23, 4259657740),
f = o(f, h, g, m, v[l + 1], 4, 2763975236),
m = o(m, f, h, g, v[l + 4], 11, 1272893353),
g = o(g, m, f, h, v[l + 7], 16, 4139469664),
h = o(h, g, m, f, v[l + 10], 23, 3200236656),
f = o(f, h, g, m, v[l + 13], 4, 681279174),
m = o(m, f, h, g, v[l], 11, 3936430074),
g = o(g, m, f, h, v[l + 3], 16, 3572445317),
h = o(h, g, m, f, v[l + 6], 23, 76029189),
f = o(f, h, g, m, v[l + 9], 4, 3654602809),
m = o(m, f, h, g, v[l + 12], 11, 3873151461),
g = o(g, m, f, h, v[l + 15], 16, 530742520),
f = a(f, h = o(h, g, m, f, v[l + 2], 23, 3299628645), g, m, v[l], 6, 4096336452),
m = a(m, f, h, g, v[l + 7], 10, 1126891415),
g = a(g, m, f, h, v[l + 14], 15, 2878612391),
h = a(h, g, m, f, v[l + 5], 21, 4237533241),
f = a(f, h, g, m, v[l + 12], 6, 1700485571),
m = a(m, f, h, g, v[l + 3], 10, 2399980690),
g = a(g, m, f, h, v[l + 10], 15, 4293915773),
h = a(h, g, m, f, v[l + 1], 21, 2240044497),
f = a(f, h, g, m, v[l + 8], 6, 1873313359),
m = a(m, f, h, g, v[l + 15], 10, 4264355552),
g = a(g, m, f, h, v[l + 6], 15, 2734768916),
h = a(h, g, m, f, v[l + 13], 21, 1309151649),
f = a(f, h, g, m, v[l + 4], 6, 4149444226),
m = a(m, f, h, g, v[l + 11], 10, 3174756917),
g = a(g, m, f, h, v[l + 2], 15, 718787259),
h = a(h, g, m, f, v[l + 9], 21, 3951481745),
f = n(f, u),
h = n(h, c),
g = n(g, d),
m = n(m, p);
return (s(f) + s(h) + s(g) + s(m)).toLowerCase()
}(token + "&" + t + "&" + appKey + "&" + formData)
}
}

View File

@@ -6,22 +6,50 @@ import {genAnti} from "@/api/genAnti";
* @param message 消息 * @param message 消息
*/ */
export async function sendChromeAPIMessage(message) { export async function sendChromeAPIMessage(message) {
message.type = 'api' message.type = 'api'
message.url = "https://kuajing.pinduoduo.com/" + message.url; if (!message.url.startsWith('http')) {
message.anti = message.anti || false message.url = "https://seller.kuajingmaihuo.com/" + message.url;
if (message.needMallId) { }
// 如果参数中没有携带MallId则从state中获取 message.anti = message.anti || false
if (!message.mallId) { if (message.needMallId) {
message.mallId = store.state.mallId; // 如果参数中没有携带MallId则从state中获取
} if (!message.mallId) {
message.mallId = store.state.mallId;
} }
if (message.anti) { }
message.anti = await genAnti.a() if (message.anti) {
message.anti = await genAnti.a()
}
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
}
/**
* 向Chrome发送消息
* @param message 消息
*/
export async function sendTemuSellerAgentMessage(message) {
message.type = 'api'
if (!message.url.startsWith('http')) {
message.url = "https://agentseller.temu.com/" + message.url;
}
message.anti = message.anti || false
if (message.needMallId) {
// 如果参数中没有携带MallId则从state中获取
if (!message.mallId) {
message.mallId = store.state.mallId;
} }
return new Promise((resolve) => { }
// @ts-ignore if (message.anti) {
chrome.runtime.sendMessage(message, resolve) message.anti = await genAnti.a()
}) }
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
} }
/** /**
@@ -29,16 +57,17 @@ export async function sendChromeAPIMessage(message) {
* @param message 消息 * @param message 消息
*/ */
export async function sendTemuAPIMessage(message) { export async function sendTemuAPIMessage(message) {
message.type = 'temuApi' message.type = 'temuApi'
message.url = "https://www.temu.com/" + message.url; message.url = "https://www.temu.com/" + message.url;
message.anti = message.anti || false message.anti = message.anti || false
if (message.anti) { if (message.anti) {
message.anti = await genAnti.a() message.anti = await genAnti.a()
} message.data.anti_content = message.anti
return new Promise((resolve) => { }
// @ts-ignore return new Promise((resolve) => {
chrome.runtime.sendMessage(message, resolve) // @ts-ignore
}) chrome.runtime.sendMessage(message, resolve)
})
} }
/** /**
@@ -46,10 +75,40 @@ export async function sendTemuAPIMessage(message) {
* @param message 消息 * @param message 消息
*/ */
export async function sendChromeWebReqMessage(message) { export async function sendChromeWebReqMessage(message) {
return new Promise((resolve) => { return new Promise((resolve) => {
// @ts-ignore // @ts-ignore
chrome.runtime.sendMessage(message, resolve) chrome.runtime.sendMessage(message, resolve)
}) })
}
/**
* 向Chrome发送消息
* @param message 消息
*/
export async function sendSheinAPIMessage(message) {
message.type = 'sheinApi'
message.url = "https://www.shein.com/" + message.url;
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
}
/**
* 向Chrome发送消息
* @param message 消息
*/
export async function sendGeiwohuoAPIMessage(message) {
message.type = 'geiwohuoApi'
if (message.isWWW) {
message.url = "https://www.geiwohuo.com/" + message.url;
} else {
message.url = "https://sso.geiwohuo.com/" + message.url;
}
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
} }
/** /**
@@ -57,9 +116,39 @@ export async function sendChromeWebReqMessage(message) {
* @param message 消息 * @param message 消息
*/ */
export function sendChromeNotification(message) { export function sendChromeNotification(message) {
message.type = 'notify' message.type = 'notify'
return new Promise((resolve) => { return new Promise((resolve) => {
// @ts-ignore // @ts-ignore
chrome.runtime.sendMessage(message, resolve) chrome.runtime.sendMessage(message, resolve)
}) })
}
/**
* 向Chrome发送消息
* @param message 消息
*/
export function sendAliexpressAPIMessage(message) {
message.type = 'aliexpress'
if (!message.url.startsWith('http')) {
message.url = "https://seller-acs.aliexpress.com/" + message.url;
}
const {mtopConfig = {appKey: "12574478"}} = window
message.appKey = message.appKey || mtopConfig.appKey
return new Promise((resolve) => {
chrome.runtime.sendMessage(message, resolve)
})
}
/**
* 向Chrome发送消息
* @param message 消息
*/
export function sendGoodcangAPIMessage(message) {
message.type = 'goodcangApi'
if (!message.url.startsWith('http')) {
message.url = "https://oms.goodcang.com/" + message.url;
}
return new Promise((resolve) => {
chrome.runtime.sendMessage(message, resolve)
})
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ import store from '@/store'
var instance = axios.create({ var instance = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? 'http://temu.jjcp52.com' : 'http://temu.jjcp52.com', baseURL: process.env.NODE_ENV === 'production' ? 'http://124.71.2.127:8888' : 'http://124.71.2.127:8888',
timeout: 50000, timeout: 50000,
validateStatus: function (status) { validateStatus: function (status) {
return status < 500 return status < 500

View File

@@ -53133,7 +53133,7 @@ export default[function(e, t, r) {
)) ))
} }
, r = new XMLHttpRequest; , r = new XMLHttpRequest;
r.open("GET", "https://kuajing.pinduoduo.com/api/server/_stm", !0), r.open("GET", "https://seller.kuajingmaihuo.com/api/server/_stm", !0),
r.setRequestHeader("Content-type", "application/json; charset=utf-8"), r.setRequestHeader("Content-type", "application/json; charset=utf-8"),
r.withCredentials = !0, r.withCredentials = !0,
r.onreadystatechange = function() { r.onreadystatechange = function() {

View File

@@ -10,6 +10,14 @@
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.svg#iconfont') format('svg'); url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.svg#iconfont') format('svg');
} }
@font-face {
font-family: "iconfont";
src: url('https://at.alicdn.com/t/c/font_4680344_rxl7gevvsys.woff2?t=1725970465332') format('woff2'),
url('https://at.alicdn.com/t/c/font_4680344_rxl7gevvsys.woff?t=1725970465332') format('woff'),
url('https://at.alicdn.com/t/c/font_4680344_rxl7gevvsys.ttf?t=1725970465332') format('truetype');
}
.iconfont { .iconfont {
font-family: "iconfont" !important; font-family: "iconfont" !important;
font-size: 16px; font-size: 16px;
@@ -393,7 +401,7 @@ img {
} }
.el-pagination { .el-pagination {
margin-top: 40px; margin-top: 20px;
text-align: center; text-align: center;
} }
@@ -483,3 +491,16 @@ img {
width: 240px; width: 240px;
} }
} }
.el-table__fixed-body-wrapper .el-table__body {
padding-bottom: 6px; // 6px为横向滚动条高度
}
.hiprint_rul_wrapper {
display: block;
}
.hiprint-printPanel {
display: flex;
justify-content: center;
}

View File

@@ -0,0 +1,349 @@
@media print {
body {
margin: 0px;
padding: 0px;
}
}
@page {
margin: 0;
}
.hiprint-printPaper * {
box-sizing: border-box;
-moz-box-sizing: border-box; /* Firefox */
-webkit-box-sizing: border-box; /* Safari */
}
.hiprint-printPaper *:focus {
outline: -webkit-focus-ring-color auto 0px;
}
.hiprint-printPaper {
position: relative;
padding: 0 0 0 0;
page-break-after: always;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
user-select: none;
overflow-x: hidden;
overflow: hidden;
}
.hiprint-printPaper .hiprint-printPaper-content {
position: relative;
}
/* 火狐浏览器打印 第一页过后 重叠问题 */
@-moz-document url-prefix() {
.hiprint-printPaper .hiprint-printPaper-content {
position: relative;
margin-top: 20px;
top: -20px
}
}
.hiprint-printPaper.design {
overflow: visible;
}
.hiprint-printTemplate .hiprint-printPanel {
page-break-after: always;
}
.hiprint-printPaper, hiprint-printPanel {
box-sizing: border-box;
border: 0px;
}
.hiprint-printPanel .hiprint-printPaper:last-child {
page-break-after: avoid;
}
.hiprint-printTemplate .hiprint-printPanel:last-child {
page-break-after: avoid;
}
.hiprint-printPaper .hideheaderLinetarget {
border-top: 0px dashed rgb(201, 190, 190) !important;
}
.hiprint-printPaper .hidefooterLinetarget {
border-top: 0px dashed rgb(201, 190, 190) !important;
}
.hiprint-printPaper.design {
border: 1px dashed rgba(170, 170, 170, 0.7);
}
.design .hiprint-printElement-table-content, .design .hiprint-printElement-longText-content {
overflow: hidden;
box-sizing: border-box;
}
.design .resize-panel {
box-sizing: border-box;
border: 1px dotted;
}
.hiprint-printElement-text {
background-color: transparent;
background-repeat: repeat;
padding: 0 0 0 0;
border: 0.75pt none rgb(0, 0, 0);
direction: ltr;
font-family: 'SimSun';
font-size: 9pt;
font-style: normal;
font-weight: normal;
padding-bottom: 0pt;
padding-left: 0pt;
padding-right: 0pt;
padding-top: 0pt;
text-align: left;
text-decoration: none;
line-height: 9.75pt;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
}
.design .hiprint-printElement-text-content {
border: 1px dashed rgb(206, 188, 188);
box-sizing: border-box;
}
.hiprint-printElement-longText {
background-color: transparent;
background-repeat: repeat;
border: 0.75pt none rgb(0, 0, 0);
direction: ltr;
font-family: 'SimSun';
font-size: 9pt;
font-style: normal;
font-weight: normal;
padding-bottom: 0pt;
padding-left: 0pt;
padding-right: 0pt;
padding-top: 0pt;
text-align: left;
text-decoration: none;
line-height: 9.75pt;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
/*white-space: pre-wrap*/
}
.hiprint-printElement-table {
background-color: transparent;
background-repeat: repeat;
color: rgb(0, 0, 0);
border-color: rgb(0, 0, 0);
border-style: none;
direction: ltr;
font-family: 'SimSun';
font-size: 9pt;
font-style: normal;
font-weight: normal;
padding-bottom: 0pt;
padding-left: 0pt;
padding-right: 0pt;
padding-top: 0pt;
text-align: left;
text-decoration: none;
padding: 0 0 0 0;
box-sizing: border-box;
line-height: 9.75pt;
}
.hiprint-printElement-table thead {
background: #e8e8e8;
font-weight: 700;
}
table.hiprint-printElement-tableTarget {
width: 100%;
}
.hiprint-printElement-tableTarget, .hiprint-printElement-tableTarget tr, .hiprint-printElement-tableTarget td {
border-color: rgb(0, 0, 0);
/*border-style: none;*/
/*border: 1px solid rgb(0, 0, 0);*/
font-weight: normal;
direction: ltr;
padding-bottom: 0pt;
padding-left: 4pt;
padding-right: 4pt;
padding-top: 0pt;
text-decoration: none;
vertical-align: middle;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
/*line-height: 9.75pt;
font-size: 9pt;*/
}
.hiprint-printElement-tableTarget-border-all {
border: 1px solid;
}
.hiprint-printElement-tableTarget-border-none {
border: 0px solid;
}
.hiprint-printElement-tableTarget-border-lr {
border-left: 1px solid;
border-right: 1px solid;
}
.hiprint-printElement-tableTarget-border-left {
border-left: 1px solid;
}
.hiprint-printElement-tableTarget-border-right {
border-right: 1px solid;
}
.hiprint-printElement-tableTarget-border-tb {
border-top: 1px solid;
border-bottom: 1px solid;
}
.hiprint-printElement-tableTarget-border-top {
border-top: 1px solid;
}
.hiprint-printElement-tableTarget-border-bottom {
border-bottom: 1px solid;
}
.hiprint-printElement-tableTarget-border-td-none td {
border: 0px solid;
}
.hiprint-printElement-tableTarget-border-td-all td:not(:nth-last-child(-n+2)) {
border-right: 1px solid;
}
.hiprint-printElement-tableTarget-border-td-all td:last-child {
border-left: 1px solid;
}
.hiprint-printElement-tableTarget-border-td-all td:last-child:first-child {
border-left: none;
}
/*.hiprint-printElement-tableTarget tr,*/
.hiprint-printElement-tableTarget td {
height: 18pt;
}
.hiprint-printPaper .hiprint-paperNumber {
font-size: 9pt;
}
.design .hiprint-printElement-table-handle {
position: absolute;
height: 21pt;
width: 21pt;
background: red;
z-index: 1;
}
.hiprint-printPaper .hiprint-paperNumber-disabled {
float: right !important;
right: 0 !important;
color: gainsboro !important;
}
.hiprint-printElement-vline, .hiprint-printElement-hline {
border: 0px none rgb(0, 0, 0);
}
.hiprint-printElement-vline {
border-left: 0.75pt solid #000;
border-right: 0px none rgb(0, 0, 0) !important;
border-bottom: 0px none rgb(0, 0, 0) !important;
border-top: 0px none rgb(0, 0, 0) !important;
}
.hiprint-printElement-hline {
border-top: 0.75pt solid #000;
border-right: 0px none rgb(0, 0, 0) !important;
border-bottom: 0px none rgb(0, 0, 0) !important;
border-left: 0px none rgb(0, 0, 0) !important;
}
.hiprint-printElement-oval, .hiprint-printElement-rect {
border: 0.75pt solid #000;
}
.hiprint-text-content-middle {
}
.hiprint-text-content-middle > div {
display: grid;
align-items: center;
}
.hiprint-text-content-bottom {
}
.hiprint-text-content-bottom > div {
display: grid;
align-items: flex-end;
}
.hiprint-text-content-wrap {
}
.hiprint-text-content-wrap .hiprint-text-content-wrap-nowrap {
white-space: nowrap;
}
.hiprint-text-content-wrap .hiprint-text-content-wrap-clip {
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
}
.hiprint-text-content-wrap .hiprint-text-content-wrap-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/*hi-grid-row */
.hi-grid-row {
position: relative;
height: auto;
margin-right: 0;
margin-left: 0;
zoom: 1;
display: block;
box-sizing: border-box;
}
.hi-grid-row::after, .hi-grid-row::before {
display: table;
content: '';
box-sizing: border-box;
}
.hi-grid-col {
display: block;
box-sizing: border-box;
position: relative;
float: left;
flex: 0 0 auto;
}
.table-grid-row {
margin-left: -0pt;
margin-right: -0pt;
}
.tableGridColumnsGutterRow {
padding-left: 0pt;
padding-right: 0pt;
}
.hiprint-gridColumnsFooter {
text-align: left;
clear: both;
}

BIN
src/assets/free.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
src/assets/off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -37,7 +37,7 @@
margin-bottom: 20px; margin-bottom: 20px;
padding-bottom: 20px; padding-bottom: 20px;
background: #FFFFFF; background: #FFFFFF;
overflow: hidden; overflow: auto;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15); box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px; border-radius: 2px;

View File

@@ -0,0 +1,294 @@
<template>
<div>
<el-alert
title="采集一个商品添加进草稿箱将消耗20金币"
type="success"
:closable="false" style="margin-bottom: 10px;">
</el-alert>
<el-form class="ai-form" :model="form" label-width="150px" ref="form">
<el-form-item v-if="!isMultiCopy" label="商品地址:" style="width: 100%;" prop="url" :rules="[{ required: true, message: '请输入商品地址', trigger: 'blur' }]">
<el-input type="textarea" :rows="5" v-model="form.url"></el-input>
</el-form-item>
<el-form-item label="店铺:" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择店铺', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品分类:" style="width: 100%;" prop="targetCatId" :rules="[{ required: true, message: '请选择商品分类', trigger: 'blur' }]">
<ai-lazy-cascader
style="width: 380px"
v-model="form.targetCatId"
filterable
:props="props"></ai-lazy-cascader>
</el-form-item>
</el-form>
<div class="bottom flex-center">
<el-button @click="$emit('onClose')"> </el-button>
<el-button type="primary" @click="toAddToDraft" :loading="isCopying">确定</el-button>
</div>
</div>
</template>
<script>
import {sendChromeAPIMessage, sendChromeWebReqMessage} from '@/api/chromeApi'
import AiLazyCascader from "@/components/AiLazyCascader.vue"
import { getImageMd5, uploadImage } from "@/utils/image.js"
import { extractImagesAndText } from "@/utils/html.js"
import { transformAliExpress } from "@/utils/product.js"
import { formatDate } from "@/utils/date.js"
import { createFolderApi } from "@/utils/folder.js"
import { Message } from 'element-ui'
import { MessageBox } from 'element-ui';
export default {
name: 'AiCopyFromTemu',
props: ['params', 'isMultiCopy'],
components: {AiLazyCascader},
data() {
return {
props: {
value: 'catId',
label: 'catName',
lazy: true,
lazyLoad (value, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list',
needMallId: true,
data: {
parentCatId: value || ''
}
}).then(res => {
if (res.errorCode === 1000000) {
resolve(res.result.categoryNodeVOS.map(v => {
return {
...v,
leaf: v.isLeaf
}
}))
}
})
},
lazySearch(queryString, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/search',
needMallId: true,
data: {
searchText: queryString || ''
}
}).then(res => {
if (res.errorCode === 1000000) {
resolve(res.result.categoryPaths.map(v => {
let value = []
let label = []
for (let i = 1; i <= 10; i++ ) {
if (v['cat'+i+'NodeVO']) {
value.push(v['cat'+i+'NodeVO'].catId)
label.push(v['cat'+i+'NodeVO'].catName)
}
}
return {
catId: value,
catName: label
}
}))
}
})
}
},
form: {
url: '',
type: 2, // 默认从temu复制
targetMallId: '',
targetCatId: []
},
goods: {},
sku: {},
productDetail: {},
isCopying: false,
goodsId: '',
currentUrl: '',
goodsProperty: [],
catId: null,
currentIndex: 0,
successList: []
}
},
created () {
if (this.params?.url) {
this.form.url = this.params.url
}
},
methods: {
toAddToDraft() {
this.$refs.form.validate((valid) => {
if (valid) {
this.currentUrl = this.form.url
this.addToDraft()
}
})
},
async addToDraft() {
// let test = await createFolderApi(formatDate(new Date()).split('-'),this.form.targetMallId)
this.isCopying = true
let res = await sendChromeWebReqMessage({
type: 'aliexpress',
url: this.currentUrl,
})
if (res.indexOf("runParams") == -1) {
Message.error("请检查地址是否正确,或者“速卖通”网站是否出现滑动条")
return
}
let str = res.substring(res.indexOf("runParams"))
str = str.substring(0, str.indexOf("<\/script>"))
str = str.substring(str.indexOf("{"))
str = str.substring(0, str.lastIndexOf("}"))
str = str.substring(str.indexOf('data'))
str = str.substring(5)
let obj = JSON.parse(str)
let folderId = await createFolderApi(formatDate(new Date()).split('-'),this.form.targetMallId)
let carouselImageUrls = [], detailImageUrls = []
let imageConponent = obj.imageComponent
for (let i = 0; i < imageConponent.imagePathList.length; i++) {
let img = await uploadImage(folderId, imageConponent.imagePathList[i], this.form.targetMallId)
carouselImageUrls.push(img)
}
let res1 = await sendChromeWebReqMessage({
type: 'aliexpress',
url: obj.productDescComponent.descriptionUrl,
})
res1 = res1.substring(0, res1.indexOf("<script>"))
res1 = extractImagesAndText(res1)
res1 = JSON.parse(res1)
console.log(res1)
for (let i = 0; i < res1.images.length; i++) {
let img = await uploadImage(folderId, res1.images[i], this.form.targetMallId)
detailImageUrls.push(img)
}
this.createDraft(transformAliExpress({
title: obj.productInfoComponent.subject,
carouselImageUrls,
detailImageList: detailImageUrls,
text: res1.text
}))
},
async createDraft(data) {
let catId = this.form.targetCatId[this.form.targetCatId.length - 1]
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/add',
needMallId: true,
mallId: this.form.targetMallId,
data: {
catId: catId
}})
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
let content = data
let i = 0
for (; i < this.form.targetCatId.length; i++) {
content['cat' + (i+1) + 'Id'] = this.form.targetCatId[i]
}
for (; i < 10; i++) {
content['cat' + (i+1) + 'Id'] = ''
}
content.productDraftId = draftId
this.createProduct(content)
} else {
setTimeout(() => {
this.createDraft(data)
}, 500)
}
},
createProduct(content) {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
mallId: this.form.targetMallId,
data: content
}).then((res) => {
if (res.errorCode == 1000000) {
this.successList.push(this.currentUrl)
this.saveInfo()
this.isCopying = false
Message.success("成功添加到草稿箱")
} else {
setTimeout(() => {
this.createProduct(content)
}, 500)
}
})
},
getSpecIdNew(data) {
return sendChromeAPIMessage({
url: 'bg-anniston-mms/sku/spec/byName/queryOrAdd',
needMallId: true,
mallId: this.form.targetMallId,
data: {
parentSpecId: data.spec_key_id,
specName: data.spec_value
}}).then((res) => {
if (res.errorCode == 1000000) {
return res
} else {
this.getSpecIdNew(data)
}
})
},
getSpecId(data) {
return sendChromeAPIMessage({
url: 'bg-anniston-mms/sku/spec/byName/queryOrAdd',
needMallId: true,
mallId: this.form.targetMallId,
data: {
parentSpecId: data.specKeyId,
specName: data.specValue
}}).then((res) => {
if (res.errorCode == 1000000) {
return res
} else {
this.getSpecId(data)
}
})
},
saveInfo() {
let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.form.targetMallId
})
this.$http.post('/api/copyProduct/add', {
mallId: mallInfo[0].mallId,
mallName: mallInfo[0].mallName,
url: this.currentUrl,
type: this.form.type
}).then(res1 => {
if (res1.code == 0) {
this.$store.dispatch('getUserInfo')
if (!this.isMultiCopy) {
this.$emit('onSuccess')
}
}
})
}
}
}
</script>
<style scoped lang="scss">
.bottom {
justify-content: center;
}
</style>

View File

@@ -1,55 +1,89 @@
<template> <template>
<div> <div>
<el-form class="ai-form" :model="form" label-width="140px" ref="form"> <el-alert
<el-form-item label="来源:" style="width: 100%;" prop="type" :rules="[{ required: true, message: '请选择来源', trigger: 'blur' }]"> title="采集一个商品添加进草稿箱将消耗20金币"
<el-radio-group v-model="form.type" size="medium"> type="success"
<el-radio :label="1">TEMU</el-radio> :closable="false" style="margin-bottom: 10px;">
<!--<el-radio :label="2">速卖通</el-radio>--> </el-alert>
</el-radio-group> <el-form class="ai-form" :model="form" label-width="150px" ref="form">
<el-form-item v-if="!isMultiCopy" label="商品地址:" style="width: 100%;" prop="url" :rules="[{ required: true, message: '请输入商品地址', trigger: 'blur' }]">
<el-input type="textarea" :rows="5" v-model="form.url"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="商品地址:" style="width: 100%;" prop="url" :rules="[{ required: true, message: '请输入商品地址', trigger: 'blur' }]"> <el-form-item
<el-input type="textarea" :rows="5" v-model="form.url"></el-input> prop="isSemi"
label="是否半托管:"
:rules="[{ required: true, message: '请选择是否半托管', trigger: 'blur' }]">
<el-radio-group v-model="form.isSemi" size="medium">
<el-radio :label="false"></el-radio>
<el-radio :label="true"></el-radio>
</el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="店铺:" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择店铺', trigger: 'blur' }]"> <el-form-item label="店铺:" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择店铺', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择"> <el-select style="width: 380px" multiple v-model="form.targetMallId" placeholder="请选择">
<el-option <el-option
v-for="item in $store.state.mallList" v-for="item in mallList"
:key="item.mallId" :key="item.mallId"
:label="item.mallName" :label="item.mallName"
:value="item.mallId"> :value="item.mallId">
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="商品分类:" style="width: 100%;" prop="targetCatId" :rules="[{ required: true, message: '请选择商品分类', trigger: 'blur' }]"> <el-form-item v-if="form.isSemi" label="经营站点" style="width: 100%;" prop="siteId" :rules="[{ required: true, message: '请选择经营站点', trigger: 'blur' }]">
<el-cascader style="width: 380px" v-model="form.targetCatId" :props="props"></el-cascader> <el-select style="width: 380px" multiple v-model="form.siteId" placeholder="请选择">
<el-option
v-for="item in siteList"
:key="item.siteId"
:label="item.siteName"
:value="item.siteId">
</el-option>
</el-select>
</el-form-item>
<el-form-item
prop="isSameCategory"
label="是否保持相同类目:"
:rules="[{ required: true, message: '请选择是否保持相同类目', trigger: 'blur' }]">
<el-radio-group v-model="form.isSameCategory" size="medium">
<el-radio :label="false"></el-radio>
<el-radio :label="true"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="!form.isSameCategory" label="商品分类:" style="width: 100%;" prop="targetCatId" :rules="[{ required: true, message: '请选择商品分类', trigger: 'blur' }]">
<ai-lazy-cascader
style="width: 380px"
v-model="form.targetCatId"
filterable
:props="props"></ai-lazy-cascader>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="bottom flex-center"> <div class="bottom flex-center">
<el-button @click="$emit('onClose')"> </el-button> <el-button @click="$emit('onClose')"> </el-button>
<el-button type="primary" @click="addToDraft">确定</el-button> <el-button type="primary" @click="toAddToDraft" :loading="isCopying">确定</el-button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {sendChromeAPIMessage, sendChromeWebReqMessage} from '@/api/chromeApi' import {sendChromeAPIMessage, sendTemuAPIMessage, sendChromeWebReqMessage} from '@/api/chromeApi'
import AiLazyCascader from "@/components/AiLazyCascader.vue"
import { Message } from 'element-ui' import { Message } from 'element-ui'
import { MessageBox } from 'element-ui';
export default { export default {
name: 'AiCopyFromTemu', name: 'AiCopyFromTemu',
props: ['params'], props: ['params', 'isMultiCopy'],
components: {AiLazyCascader},
data() { data() {
return { return {
props: { props: {
value: 'catId', value: 'catId',
label: 'catName', label: 'catName',
lazy: true, lazy: true,
lazyLoad (node, resolve) { lazyLoad (value, resolve) {
sendChromeAPIMessage({ sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list', url: 'bg-anniston-mms/category/children/list',
needMallId: true, needMallId: true,
data: { data: {
parentCatId: node.value || '' parentCatId: value || ''
} }
}).then(res => { }).then(res => {
if (res.errorCode === 1000000) { if (res.errorCode === 1000000) {
@@ -61,171 +95,449 @@ export default {
})) }))
} }
}) })
},
lazySearch(queryString, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/search',
needMallId: true,
data: {
searchText: queryString || ''
}
}).then(res => {
if (res.errorCode === 1000000) {
resolve(res.result.categoryPaths.map(v => {
let value = []
let label = []
for (let i = 1; i <= 10; i++ ) {
if (v['cat'+i+'NodeVO']) {
value.push(v['cat'+i+'NodeVO'].catId)
label.push(v['cat'+i+'NodeVO'].catName)
}
}
return {
catId: value,
catName: label
}
}))
}
})
} }
}, },
form: { form: {
url: '', url: '',
type: 1, // 默认从temu复制 type: 1, // 默认从temu复制
targetMallId: '', targetMallId: '',
targetCatId: [] targetCatId: [],
isSameCategory: true,
isSemi: false,
siteId: []
}, },
goods: {}, goods: {},
sku: {}, sku: {},
productDetail: {} productDetail: {},
isCopying: false,
goodsId: '',
currentUrl: '',
goodsProperty: [],
catId: null,
currentIndex: 0,
successList: [],
siteList: []
} }
}, },
computed: {
mallList () {
const filteredData = this.$store.state.mallList.filter(item => {
return item.isSemiManagedMall == this.form.isSemi
})
return filteredData
}
},
created () { created () {
console.log(this.params?.url) this.getSiteList()
if (this.params?.url) { if (this.params?.url) {
this.form.url = this.params.url this.form.url = this.params.url
} }
}, },
methods: { methods: {
async addToDraft() { getSiteList() {
this.$refs.form.validate((valid) => { sendChromeAPIMessage({
if (valid) { url: 'bg-visage-mms/config/common/site/query',
this.$http.post('/api/copyProduct/check',null, {params: {type: 0}}).then(res => { needMallId: true,
if (res.code == 0) { mallId: this.$store.state.mallList[0].mallId,
let source data: {}}).then((res) => {
if (this.form.type == '1') { if (res.success) {
source = 'temu' this.siteList = res.result.siteBaseList.filter(item => {
} else if (this.form.type == '2') { return item.matchSemiManaged
source = 'aliexpress' })
}
sendChromeWebReqMessage({
type: source,
url: this.form.url,
}).then((res) => {
if (this.form.type == '1') {
if (res.indexOf("rawData") == -1) {
Message.error("请检查地址是否正确或者“TEMU”网站是否出现图形验证码")
return
}
let str = res.substring(res.indexOf("rawData"))
str = str.substring(0, str.indexOf("<\/script>"))
str = str.substring(str.indexOf("{"))
str = str.substring(0, str.lastIndexOf("}"))
str = str + "}"
let goodsObj = JSON.parse(str)
this.goods = goodsObj.store.goods
this.sku = goodsObj.store.sku
this.productDetail = goodsObj.store.productDetail
let specIds = []
this.sku.forEach(item => {
item.specs.forEach(item1 => {
let flag = false
specIds.forEach(item2 => {
if (item2.specValue == item1.specValue) {
flag = true
}
})
if (!flag) {
specIds.push({specKeyId: item1.specKeyId, specValue: item1.specValue})
}
})
})
Promise.all(specIds.map(item => this.getSpecId(item).then(res => {
this.sku.forEach(item1 => {
item1.specs.forEach(item2 => {
if (item2.specValue == item.specValue) {
item2.specValueId = res.result.specId
}
})
})
return 0
}))).then(() => {
this.$http.post('/api/copyProduct/translate',{type: 1, goods: this.goods, sku: this.sku, productDetail: this.productDetail}).then(res => {
if (res.code == 0) {
this.createDraft(res.data)
}
})
})
} else if (this.form.type == '2') {
/*if (res.indexOf("runParams") == -1) {
Message.error("请检查地址是否正确,或者“速卖通”网站是否出现滑动条")
return
}
let str = res.substring(res.indexOf("runParams"))
str = str.substring(0, str.indexOf("<\/script>"))
str = str.substring(str.indexOf("{"))
str = str.substring(0, str.lastIndexOf("}"))
str = str.substring(str.indexOf('data'))
str = str.substring(5)
let obj = JSON.parse(str)
sendChromeWebReqMessage({
type: source,
url: obj.productDescComponent.descriptionUrl,
}).then((res1) => {
res1 = res1.substring(0, res1.indexOf("<script>"))
let str = res1.replace(/<img[^>]+src="([^">]+)"[^>]+>/g, '$1\n').replace(/<.*?>/g, '[||]')
let arr = str.split('[||]')
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
})*/
}
})
}
})
} }
}) })
}, },
createDraft(data) { toAddToDraft() {
sendChromeAPIMessage({ this.$refs.form.validate((valid) => {
if (valid) {
if (this.isMultiCopy) {
this.successList = []
this.currentIndex = 0
this.currentUrl = this.params.urlList[this.currentIndex]
this.execAddToDraft()
} else {
this.currentUrl = this.form.url
this.execAddToDraft()
}
}
})
},
execAddToDraft() {
if (!this.currentUrl.startsWith("http")) {
this.goodsId = this.currentUrl
this.addToDraftNew()
} else {
let t = this.currentUrl
let urlParams = this.parseURL(t)
t = t.substring(0,t.indexOf(".html"))
if (t.lastIndexOf("-g-") > 0) {
t = t.substring(t.lastIndexOf("-g-"), t.length);
t = t.substring(3, t.length);
this.goodsId = t
this.addToDraftNew()
} else if(urlParams.params) {
let goodsId = urlParams.params.goods_id
if (!goodsId) {
this.addToDraft()
} else {
this.goodsId = goodsId
this.addToDraftNew()
}
} else {
this.addToDraft()
}
}
},
async addToDraftNew() {
this.isCopying = true
sendTemuAPIMessage({
url: 'api/oak/integration/render',
anti: true,
data: {
goods_id: this.goodsId
}}).then((res) => {
if (!res.goods || !res.goods.productName) {
//this.isCopying = false
//Message.error("获取商品信息失败,采集失败")
this.addToDraft()
return
}
this.goods = res.goods
this.sku = res.sku
this.productDetail = res.product_detail
this.catId = this.goods.cat_id
this.goodsProperty = this.goods.goods_property
let specIds = []
this.sku.forEach(item => {
item.specs.forEach(item1 => {
let flag = false
specIds.forEach(item2 => {
if (item2.spec_value == item1.spec_value) {
flag = true
}
})
/*if (!flag && (item1.specKeyId != 1001 && item1.specKeyId != 43404162)) {
specIds.push({spec_key_id: item1.spec_key_id, spec_value: item1.spec_value})
}*/
if (!flag) {
specIds.push({spec_key_id: item1.spec_key_id, spec_value: item1.spec_value})
}
})
})
/*Promise.all(specIds.map(item => this.getSpecIdNew(item).then(res => {
this.sku.forEach(item1 => {
item1.specs.forEach(item2 => {
if (item2.spec_value == item.spec_value) {
item2.spec_value_id = res.result.specId
}
})
})
return 0
}))).then(() => {
this.$http.post('/api/copyProduct/translateNew',{type: 1, goods: this.goods, sku: this.sku, productDetail: this.productDetail}).then(res => {
if (res.code == 0) {
this.createDraft(res.data)
}
})
})*/
this.toCreateDraftNew(specIds)
})
},
async addToDraft() {
this.isCopying = true
this.$http.post('/api/copyProduct/check',null, {params: {type: 0}}).then(res => {
if (res.code == 0) {
let source
if (this.form.type == '1') {
source = 'temu'
} else if (this.form.type == '2') {
source = 'aliexpress'
}
sendChromeWebReqMessage({
type: source,
url: this.currentUrl,
}).then((res) => {
if (this.form.type == '1') {
if (res.indexOf("window.rawData") == -1) {
this.isCopying = false
Message.error("请检查地址是否正确或者“TEMU”网站是否出现图形验证码")
return
}
let str = res.substring(res.indexOf("window.rawData"))
str = str.substring(0, str.indexOf("<\/script>"))
str = str.substring(str.indexOf("{"))
str = str.substring(0, str.lastIndexOf("}"))
str = str + "}"
let goodsObj = JSON.parse(str)
this.goods = goodsObj.store.goods
this.sku = goodsObj.store.sku
this.catId = this.goods.catId
this.goodsProperty = this.goods.goodsProperty
this.productDetail = goodsObj.store.productDetail
let specIds = []
this.sku.forEach(item => {
item.specs.forEach(item1 => {
let flag = false
specIds.forEach(item2 => {
if (item2.specValue == item1.specValue) {
flag = true
}
})
/*if (!flag && (item1.specKeyId != 1001 && item1.specKeyId != 43404162)) {
specIds.push({specKeyId: item1.specKeyId, specValue: item1.specValue})
}*/
if (!flag) {
specIds.push({specKeyId: item1.specKeyId, specValue: item1.specValue})
}
})
})
/*Promise.all(specIds.map(item => this.getSpecId(item).then(res => {
this.sku.forEach(item1 => {
item1.specs.forEach(item2 => {
if (item2.specValue == item.specValue) {
item2.specValueId = res.result.specId
}
})
})
return 0
}))).then(() => {
this.$http.post('/api/copyProduct/translate',{type: 1, goods: this.goods, sku: this.sku, productDetail: this.productDetail}).then(res => {
if (res.code == 0) {
this.createDraft(res.data)
}
})
})*/
this.toCreateDraft(specIds)
} else if (this.form.type == '2') {
}
})
} else {
this.isCopying = false
}
})
},
async toCreateDraft(specIds) {
for (let kk = 0; kk < this.form.targetMallId.length; kk++) {
await Promise.all(specIds.map(item => this.getSpecId(item, this.form.targetMallId[kk]).then(res => {
this.sku.forEach(item1 => {
item1.specs.forEach(item2 => {
if (item2.specValue == item.specValue) {
item2.specValueId = res.result.specId
}
})
})
return 0
})))
let res = await this.$http.post('/api/copyProduct/translate',{type: 1, goods: this.goods, sku: this.sku, productDetail: this.productDetail})
if (res.code == 0) {
await this.createDraft(res.data, this.form.targetMallId[kk])
}
await this.$sleepSync(500)
}
},
async toCreateDraftNew(specIds) {
for (let kk = 0; kk < this.form.targetMallId.length; kk++) {
await Promise.all(specIds.map(item => this.getSpecIdNew(item, this.form.targetMallId[kk]).then(res => {
this.sku.forEach(item1 => {
item1.specs.forEach(item2 => {
if (item2.spec_value == item.spec_value) {
item2.spec_value_id = res.result.specId
}
})
})
return 0
})))
let res = await this.$http.post('/api/copyProduct/translateNew',{type: 1, goods: this.goods, sku: this.sku, productDetail: this.productDetail})
if (res.code == 0) {
await this.createDraft(res.data, this.form.targetMallId[kk])
}
await this.$sleepSync(500)
}
},
async createDraft(data, mallId) {
let reqData = {}
let catId = null;
if (this.form.isSameCategory) {
reqData.catId = this.catId;
} else {
reqData.catId = this.form.targetCatId[this.form.targetCatId.length - 1]
}
if (this.form.isSemi) {
reqData.productSemiManagedReq = {
bindSiteIds: this.form.siteId
}
}
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/add', url: 'bg-visage-mms/product/draft/add',
needMallId: true, needMallId: true,
mallId: this.form.targetMallId, mallId: mallId,
data: { data: reqData})
catId: this.form.targetCatId[this.form.targetCatId.length - 1]
}}).then((res) => { if (res.errorCode == 1000000) {
if (res.errorCode == 1000000) { let draftId = res.result.productDraftId
let draftId = res.result.productDraftId let content = data
let content = data let i = 0
let i = 0 if (this.form.isSameCategory) {
/*let res2 = await this.$http.post('/api/innerCategory/fullById',null , {
params: {
id: reqData.catId
}
})*/
for (; i < 10; i++) {
if (content['cat' + (i+1) + 'Id']) {
continue
}else {
break
}
}
let res3 = await sendChromeAPIMessage({
url: 'bg-anniston-mms/category/template/query',
needMallId: true,
mallId: mallId,
data: {
catId: reqData.catId,
productCreateTime: null,
langList: [
"en"
]
}})
content.productPropertyReqs = []
for (let j = 0; j < this.goodsProperty.length; j++) {
let temp = {}
for (let k = 0; k < res3.result.properties.length; k++) {
if (this.goodsProperty[j].key == res3.result.properties[k].lang2Name.en) {
temp.templatePid = res3.result.properties[k].templatePid
temp.pid = res3.result.properties[k].pid
temp.refPid = res3.result.properties[k].refPid
temp.propName = res3.result.properties[k].name
for (let x = 0; x < this.goodsProperty[j].values.length; x++) {
if (null == res3.result.properties[k].values) break
for (let l = 0; l < res3.result.properties[k].values.length; l++) {
if (res3.result.properties[k].values[l].lang2Value.en == this.goodsProperty[j].values[x]) {
temp.vid = res3.result.properties[k].values[l].vid
temp.propValue = res3.result.properties[k].values[l].value
temp.valueUnit = ''
temp.valueExtendInfo = ''
temp.controlType = res3.result.properties[k].values[l].controlType
content.productPropertyReqs.push({...temp})
break
}
}
}
}
}
}
} else {
for (; i < this.form.targetCatId.length; i++) { for (; i < this.form.targetCatId.length; i++) {
content['cat' + (i+1) + 'Id'] = this.form.targetCatId[i] content['cat' + (i+1) + 'Id'] = this.form.targetCatId[i]
} }
for (; i < 10; i++) {
content['cat' + (i+1) + 'Id'] = ''
}
content.productDraftId = draftId
this.createProduct(content)
} else {
setTimeout(() => {
this.createDraft(data)
}, 500)
} }
}) if (content['cat'+(i)+'Id'] != reqData.catId) {
content['cat'+(++i)+'Id'] = reqData.catId
}
for (; i < 10; i++) {
content['cat' + (i+1) + 'Id'] = ''
}
content.personalizationSwitch = 0
content.productDraftId = draftId
await this.createProduct(content, mallId)
}
}, },
createProduct(content) { async createProduct(content, mallId) {
sendChromeAPIMessage({ if (this.form.isSemi) {
content.productSemiManagedReq = {
bindSiteIds: this.form.siteId
}
}
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save', url: 'bg-visage-mms/product/draft/save',
needMallId: true, needMallId: true,
mallId: this.form.targetMallId, mallId: mallId,
data: { data: {
...content ...content
}}).then((res) => { }})
if (res.errorCode == 1000000) { if (res.errorCode == 1000000) {
Message.success("成功添加到草稿箱") this.successList.push(this.currentUrl)
this.saveInfo() this.saveInfo(mallId)
if (this.isMultiCopy) {
this.currentIndex ++
if (this.currentIndex == this.params.urlList.length) {
this.isCopying = false
this.$emit('onSuccess')
MessageBox.alert(`成功添加${this.successList.length}个商品进入草稿箱`)
} else {
this.currentUrl = this.params.urlList[this.currentIndex]
this.execAddToDraft()
}
} else { } else {
setTimeout(() => { this.isCopying = false
this.createProduct(content) Message.success("成功添加到草稿箱")
}, 500)
} }
})
} else {
await this.$sleepSync(1000)
this.createProduct(content)
}
}, },
getSpecId(data) { getSpecIdNew(data) {
return sendChromeAPIMessage({ return sendChromeAPIMessage({
url: 'bg-anniston-mms/sku/spec/byName/queryOrAdd', url: 'bg-anniston-mms/sku/spec/byName/queryOrAdd',
needMallId: true, needMallId: true,
mallId: this.form.targetMallId, mallId: this.form.targetMallId,
data: {
parentSpecId: data.spec_key_id,
specName: data.spec_value
}}).then((res) => {
if (res.errorCode == 1000000) {
return res
} else {
this.getSpecIdNew(data)
}
})
},
getSpecId(data, mallId) {
return sendChromeAPIMessage({
url: 'bg-anniston-mms/sku/spec/byName/queryOrAdd',
needMallId: true,
mallId: mallId,
data: { data: {
parentSpecId: data.specKeyId, parentSpecId: data.specKeyId,
specName: data.specValue specName: data.specValue
@@ -233,25 +545,54 @@ export default {
if (res.errorCode == 1000000) { if (res.errorCode == 1000000) {
return res return res
} else { } else {
this.getSpecId(data) this.getSpecId(data, mallId)
} }
}) })
}, },
saveInfo() { saveInfo(mallId) {
let mallInfo = this.$store.state.mallList.filter(item => { let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.form.targetMallId return item.mallId == mallId
}) })
this.$http.post('/api/copyProduct/add', { this.$http.post('/api/copyProduct/add', {
mallId: mallInfo[0].mallId, mallId: mallInfo[0].mallId,
mallName: mallInfo[0].mallName, mallName: mallInfo[0].mallName,
url: this.form.url, url: this.currentUrl,
type: this.form.type type: this.form.type
}).then(res1 => { }).then(res1 => {
if (res1.code == 0) { if (res1.code == 0) {
this.$store.dispatch('getUserInfo') this.$store.dispatch('getUserInfo')
this.$emit('onSuccess') if (!this.isMultiCopy) {
this.$emit('onSuccess')
}
} }
}) })
},
parseURL(url) {
let a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
} }
} }
} }

View File

@@ -3,7 +3,7 @@
<el-dialog custom-class="ai-dialog" v-on="$listeners" v-bind="$attrs" :visible.sync="dialog"> <el-dialog custom-class="ai-dialog" v-on="$listeners" v-bind="$attrs" :visible.sync="dialog">
<div class="ai-dialog__header fill" slot="title" v-text="title"/> <div class="ai-dialog__header fill" slot="title" v-text="title"/>
<div class="ai-dialog__content"> <div class="ai-dialog__content">
<div class="ai-dialog__content--wrapper pad-r8"> <div class="ai-dialog__content--wrapper">
<slot/> <slot/>
</div> </div>
</div> </div>
@@ -69,13 +69,13 @@ export default {
.ai-dialog__content { .ai-dialog__content {
overflow-y: auto; overflow-y: auto;
padding-bottom: 4px; padding-bottom: 0px;
max-height: 500px; max-height: 550px;
.ai-dialog__content--wrapper { .ai-dialog__content--wrapper {
height: 100%; height: 100%;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: hidden;
} }
} }

View File

@@ -0,0 +1,93 @@
<template>
<div class="ai-download">
<a @click="onExport">
<slot slot v-if="isHasSlot"></slot>
</a>
<template v-if="!isHasSlot">
<el-button :disabled="disabled" type="primary" @click="onExport">导出</el-button>
</template>
</div>
</template>
<script>
export default {
name: 'AiDownload',
props: {
url: {
type: String,
default: '',
required: true
},
timeout: {
type: Number,
default: 80000
},
disabled: {
type: Boolean,
default: false
},
params: {
type: Object
},
fileName: {
type: String,
default: '文件'
},
suffixName: {
type: String,
default: 'xls'
}
},
computed: {
isHasSlot() {
return this.$slots.default
}
},
methods: {
onExport() {
if (this.disabled) {
return this.$message.error('暂无数据')
}
this.$http.post(this.url, this.params, {
responseType: 'blob',
params: this.params,
timeout: this.timeout
}).then(res => {
if (res?.type == "application/json") {
let reader = new FileReader()
reader.readAsText(res, "utf-8")
reader.onload = e => {
if (e.target.readyState === 2) {
let ret = JSON.parse(e.target.result)
if (ret?.code == 0) {
this.$message.success(ret.msg)
} else this.$message.error(ret.msg)
}
}
} else {
const link = document.createElement('a')
let blob = new Blob([res], {type: res.type})
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
link.setAttribute('download', this.fileName + '.' + this.suffixName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
})
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -28,7 +28,8 @@
background: url("../assets/images/empty.svg") no-repeat center; background: url("../assets/images/empty.svg") no-repeat center;
background-size: 120px 120px; background-size: 120px 120px;
height: 120px; height: 120px;
margin: 48px auto 0; margin: 0 auto;
padding-top: 40px;
} }
} }
</style> </style>

View File

@@ -0,0 +1,466 @@
<template>
<div class="lazy-cascader" :style="{ width: width }">
<!-- 禁用状态 -->
<div
v-if="disabled"
class="el-input__inner lazy-cascader-input lazy-cascader-input-disabled"
>
<span class="lazy-cascader-placeholder" v-show="placeholderVisible">
{{ placeholder }}
</span>
<div class="lazy-cascader-tags" v-if="props.multiple">
<el-tag
class="lazy-cascader-tag"
type="info"
disable-transitions
v-for="(item, index) in labelArray"
:key="index"
closable
>
<span> {{ item.label.join(separator) }}</span>
</el-tag>
</div>
<div class="lazy-cascader-label" v-else>
<el-tooltip
placement="top-start"
:content="labelObject.label.join(separator)"
>
<span>{{ labelObject.label.join(separator) }}</span>
</el-tooltip>
</div>
</div>
<!-- 禁用状态 -->
<!-- 可选状态 -->
<el-popover v-else trigger="click" placement="bottom-start" ref="popover">
<!-- 搜索 -->
<div class="lazy-cascader-search">
<el-autocomplete
:style="{ width: searchWidth || '100%' }"
:popper-class="suggestionsPopperClass"
v-if="filterable"
class="inline-input"
prefix-icon="el-icon-search"
label="name"
v-model="keyword"
:fetch-suggestions="querySearch"
:trigger-on-focus="false"
placeholder="请输入"
@select="handleSelect"
@blur="isSearchEmpty = false"
>
<template slot-scope="{ item }">
<div class="name" :class="isChecked(item[props.value])">
{{ item[props.label].join(separator) }}
</div>
</template>
</el-autocomplete>
<div class="empty" v-show="isSearchEmpty">{{ searchEmptyText }}</div>
</div>
<!-- 搜索 -->
<!-- 级联面板 -->
<div class="lazy-cascader-panel">
<el-cascader-panel
ref="panel"
v-model="current"
:options="options"
:props="currentProps"
@change="change"
></el-cascader-panel>
</div>
<!-- 级联面板 -->
<!--内容区域-->
<div
class="el-input__inner lazy-cascader-input"
:class="disabled ? 'lazy-cascader-input-disabled' : ''"
slot="reference"
>
<span class="lazy-cascader-placeholder" v-show="placeholderVisible">
{{ placeholder }}
</span>
<div class="lazy-cascader-tags" v-if="props.multiple">
<el-tag
class="lazy-cascader-tag"
type="info"
size="small"
disable-transitions
v-for="(item, index) in labelArray"
:key="index"
closable
@close="handleClose(item)"
>
<span> {{ item.label.join(separator) }}</span>
</el-tag>
</div>
<div class="lazy-cascader-label" v-else>
<el-tooltip
placement="top-start"
:content="labelObject.label.join(separator)"
>
<span>{{ labelObject.label.join(separator) }}</span>
</el-tooltip>
</div>
<span
class="lazy-cascader-clear"
v-if="clearable && current.length > 0"
@click.stop="clearBtnClick"
>
<i class="el-icon-close"></i>
</span>
</div>
<!--内容区域-->
</el-popover>
<!-- 可选状态 -->
</div>
</template>
<script>
export default {
props: {
value: {
type: Array,
default: () => {
return [];
}
},
separator: {
type: String,
default: " > "
},
placeholder: {
type: String,
default: "请选择"
},
width: {
type: String,
default: "400px"
},
filterable: Boolean,
clearable: Boolean,
disabled: Boolean,
props: {
type: Object,
default: () => {
return {};
}
},
suggestionsPopperClass: {
type: String,
default: "suggestions-popper-class"
},
searchWidth: {
type: String
},
searchEmptyText: {
type: String,
default: "暂无数据"
}
},
data() {
return {
isSearchEmpty: false,
keyword: "",
options: [],
current: [],
labelObject: { label: [], value: [] },
labelArray: [],
currentProps: {
multiple: this.props.multiple,
checkStrictly: this.props.checkStrictly,
value: this.props.value,
label: this.props.label,
leaf: this.props.leaf,
lazy: true,
lazyLoad: this.lazyLoad
}
};
},
computed: {
placeholderVisible() {
if (this.current) {
return this.current.length == 0;
} else {
return true;
}
}
},
watch: {
current() {
this.getLabelArray();
},
value(v) {
this.current = v;
},
keyword() {
this.isSearchEmpty = false;
}
},
created() {
this.initOptions();
},
methods: {
//搜索是否选中
isChecked(value) {
//多选
if (this.props.multiple) {
let index = this.current.findIndex(item => {
return item.join() == value.join();
});
if (index > -1) {
return "el-link el-link--primary";
} else {
return "";
}
} else {
if (value.join() == this.current.join()) {
return "el-link el-link--primary";
} else {
return "";
}
}
},
//搜索
querySearch(query, callback) {
this.props.lazySearch(query, list => {
callback(list);
if (!list || !list.length) this.isSearchEmpty = true;
});
},
//选中搜索下拉搜索项
handleSelect(item) {
if (this.props.multiple) {
let index = this.current.findIndex(obj => {
return obj.join() == item[this.props.value].join();
});
if (index == -1) {
this.$refs.panel.clearCheckedNodes();
this.current.push(item[this.props.value]);
this.$emit("change", this.current);
}
} else {
//选中下拉选变更值
if (
this.current == null ||
item[this.props.value].join() !== this.current.join()
) {
this.$refs.panel.activePath = [];
this.current = item[this.props.value];
this.$emit("change", this.current);
}
}
this.keyword = "";
},
//初始化数据
async initOptions() {
this.props.lazyLoad(0, list => {
this.$set(this, "options", list);
if (this.props.multiple) {
this.current = [...this.value];
} else {
this.current = this.value;
}
});
},
async getLabelArray() {
if (this.props.multiple) {
let array = [];
for (let i = 0; i < this.current.length; i++) {
let obj = await this.getObject(this.current[i]);
array.push(obj);
}
this.labelArray = array;
this.$emit("input", this.current);
if (!this.disabled) {
this.$nextTick(() => {
this.$refs.popover.updatePopper();
});
}
} else {
this.labelObject = await this.getObject(this.current || []);
this.$emit("input", this.current);
}
},
/**格式化id=>object */
async getObject(id) {
try {
let options = this.options;
let nameArray = [];
for (let i = 0; i < id.length; i++) {
let index = options.findIndex(item => {
return item[this.props.value] == id[i];
});
nameArray.push(options[index][this.props.label]);
if (i < id.length - 1 && options[index].children == undefined) {
let list = new Promise(resolve => {
this.props.lazyLoad(id[i], list => {
resolve(list);
});
});
this.$set(options[index], "children", await list);
options = options[index].children;
} else {
options = options[index].children;
}
}
return { value: id, label: nameArray };
} catch (e) {
this.current = [];
return { value: [], label: [] };
}
},
//懒加载数据
async lazyLoad(node, resolve) {
let current = this.current;
if (this.props.multiple) {
current = [...this.current];
}
if (node.root) {
resolve();
} else if (node.data[this.props.leaf]) {
resolve();
} else if (node.data.children) {
if (this.props.multiple) {
this.current = current;
}
resolve();
} else {
this.props.lazyLoad(node.value, list => {
node.data.children = list;
if (this.props.multiple) {
this.current = current;
}
resolve(list);
});
}
},
//删除多选值
/**删除**/
handleClose(item) {
let index = this.current.findIndex(obj => {
return obj.join() == item.value.join();
});
if (index > -1) {
this.$refs.panel.clearCheckedNodes();
this.current.splice(index, 1);
this.$emit("change", this.current);
}
},
//点击清空按钮
clearBtnClick() {
this.$refs.panel.clearCheckedNodes();
this.current = [];
this.$emit("change", this.current);
},
change() {
this.$emit("change", this.current);
}
}
};
</script>
<style lang="scss">
.lazy-cascader {
display: inline-block;
width: 300px;
.lazy-cascader-input {
position: relative;
width: 100%;
background: #fff;
height: auto;
min-height: 36px;
padding: 5px;
line-height: 1;
cursor: pointer;
.lazy-cascader-placeholder {
padding: 0 2px;
line-height: 28px;
color: #999;
font-size: 14px;
}
.lazy-cascader-label {
padding: 0 2px;
line-height: 28px;
color: #606266;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.lazy-cascader-clear {
position: absolute;
right: 0;
top: 0;
display: inline-block;
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
}
}
.lazy-cascader-input-disabled {
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
cursor: not-allowed;
.lazy-cascader-label {
color: #c0c4cc;
}
.lazy-cascader-placeholder {
color: #c0c4cc;
}
}
}
.lazy-cascader-tag {
display: inline-flex;
align-items: center;
max-width: 100%;
margin: 2px;
text-overflow: ellipsis;
background: #f0f2f5;
span {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.el-icon-close {
-webkit-box-flex: 0;
-ms-flex: none;
flex: none;
background-color: #c0c4cc;
color: #fff;
}
}
.lazy-cascader-panel {
margin-top: 10px;
display: inline-block;
}
.suggestions-popper-class {
width: auto !important;
min-width: 200px;
}
.lazy-cascader-search {
.empty {
width: calc(100% - 24px);
box-sizing: border-box;
background-color: #fff;
color: #999;
text-align: center;
position: absolute;
z-index: 999;
padding: 12px 0;
margin-top: 12px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
&:before {
content: "";
position: absolute;
top: -12px;
left: 36px;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid transparent;
border-bottom: 6px solid #fff;
filter: drop-shadow(0 -1px 2px rgba(0, 0, 0, 0.02));
}
}
}
</style>

View File

@@ -2,7 +2,7 @@
<section class="AiPayment"> <section class="AiPayment">
<el-tabs type="card" stretch v-model="search.module" @tab-click="getPayments"> <el-tabs type="card" stretch v-model="search.module" @tab-click="getPayments">
<el-tab-pane label="激活码兑换" name="2"/> <el-tab-pane label="激活码兑换" name="2"/>
<el-tab-pane label="基础会员" name="0"/> <el-tab-pane label="年度会员" name="0"/>
<el-tab-pane label="金币充值" name="1"/> <el-tab-pane label="金币充值" name="1"/>
</el-tabs> </el-tabs>
<div class="content"> <div class="content">
@@ -46,8 +46,8 @@
<el-form-item <el-form-item
prop="mallId" prop="mallId"
v-show="false" v-show="false"
:rules="[{ message: '请输入商城ID', trigger: 'blur' }]"> :rules="[{ message: '请输入店铺ID', trigger: 'blur' }]">
<el-input placeholder="请输入商城ID" v-model="form.mallId"></el-input> <el-input placeholder="请输入店铺ID" v-model="form.mallId"></el-input>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
prop="code" prop="code"
@@ -108,12 +108,12 @@ export default {
mallId: this.$store.state.mallId, mallId: this.$store.state.mallId,
mallName: this.$store.state.mallName mallName: this.$store.state.mallName
}, },
vipType: ["体验会员","年会员","年会员多店通用"], vipType: ["体验会员","单店年会员","年会员多店通用"],
search: {module: "0"}, search: {module: "0"},
show: true, show: true,
descriptionsModule0: ["抢仓发货", "数据下载", "复制商品", "会员服务"], descriptionsModule0: ["抢仓发货", "数据下载", "复制商品", "会员服务"],
descriptionsModule1: ["智能复制"], descriptionsModule1: ["商品采集", "店铺跟踪", "关键字跟踪", "单品跟踪", "新品跟踪"],
payments: [], payments: [],
qrcode: "", qrcode: "",
amount: 0, amount: 0,
@@ -134,6 +134,9 @@ export default {
} }
}) })
}, },
getMessage(type) {
return `你使用的是“${this.vipType[type]}”兑换券,确定兑换?`;
},
getQrcode(item) { getQrcode(item) {
if (item.module == '0' && !this.vipForm.mallId) { if (item.module == '0' && !this.vipForm.mallId) {
Message.error("请先登录拼多多垮境卖家中心") Message.error("请先登录拼多多垮境卖家中心")
@@ -141,7 +144,11 @@ export default {
} }
this.selected = item this.selected = item
this.$http.post("/api/order/createOrder", null, { this.$http.post("/api/order/createOrder", null, {
params: {priceConfigId: item.id} params: {
priceConfigId: item.id,
mallName: this.vipForm.mallName,
mallId: this.vipForm.mallId
}
}).then(res => { }).then(res => {
if (res?.data?.id) { if (res?.data?.id) {
return res.data.id return res.data.id

View File

@@ -9,14 +9,14 @@
<ai-wrapper <ai-wrapper
label-width="120px" class="fill"> label-width="120px" class="fill">
<ai-info-item label="价格:" :value="'$' + info.price"></ai-info-item> <ai-info-item label="价格:" :value="'$' + info.price"></ai-info-item>
<ai-info-item label="销量:" :value="info.saleTotal"></ai-info-item> <ai-info-item label="销量/评论数" :value="info.saleTotal"></ai-info-item>
</ai-wrapper> </ai-wrapper>
</div> </div>
</template> </template>
</ai-card> </ai-card>
<ai-card title="趋势信息"> <ai-card title="趋势信息">
<template #content> <template #content>
<div id="chart"></div> <div id="dataChart"></div>
</template> </template>
</ai-card> </ai-card>
</template> </template>
@@ -28,7 +28,7 @@ import { DualAxes } from '@antv/g2plot'
export default { export default {
name: "AiProductDetail", name: "AiProductDetail",
props: ['params'], props: ['params', 'url'],
components: { components: {
AiProductDropDown AiProductDropDown
}, },
@@ -39,19 +39,23 @@ export default {
}, },
computed: { computed: {
}, },
created() { mounted() {
this.getInfo() // this.info = this.params
this.$nextTick(() => {
this.init()
})
}, },
methods: { methods: {
getInfo() { init() {
this.$http.post('/api/monitorDetail/queryDetail',null,{ this.$http.post(this.url ? this.url: '/api/monitorDetail/queryProductDetail',null,{
params: { params: {
goodsId: this.params.goodsId goodsId: this.params.goodsId,
monitorId: this.params.monitorId
} }
}).then(res => { }).then(res => {
this.info = res.data this.info = res.data
const dualAxes = new DualAxes('chart', { const dualAxes = new DualAxes('dataChart', {
data: [this.info.priceAndSale, this.info.priceAndSale], data: [this.info.priceAndSale, this.info.priceAndSale],
xField: '日期', xField: '日期',
yField: ['价格', '销量'], yField: ['价格', '销量'],
@@ -65,14 +69,6 @@ export default {
color: '#5AD8A6', color: '#5AD8A6',
} }
], ],
smooth: true,
// @TODO 后续会换一种动画方式
animation: {
appear: {
animation: 'path-in',
duration: 5000,
},
},
}); });
dualAxes.render(); dualAxes.render();

View File

@@ -6,14 +6,17 @@
</span> </span>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="isShowDetail" :command="beforeGoDetail(params.goodsId)">查看详情</el-dropdown-item> <el-dropdown-item v-if="isShowDetail" :command="beforeGoDetail(params.goodsId)">查看详情</el-dropdown-item>
<el-dropdown-item divided :command="beforeCopy(params.url)">复制商品</el-dropdown-item> <el-dropdown-item v-if="isShowAddFavorite" :command="beforeAddFavorite(params.goodsId, params.monitorId)">加入收藏</el-dropdown-item>
<el-dropdown-item v-if="isShowDelFavorite" :command="beforeDelFavorite(params.id)">取消收藏</el-dropdown-item>
<el-dropdown-item divided v-if="isShowGroup" :command="beforeAddGroup(params.goodsId)">加入分组</el-dropdown-item>
<el-dropdown-item divided v-if="!isHideCopy" :command="beforeCopy(params.url)">商品采集</el-dropdown-item>
<el-dropdown-item divided :command="beforeGoWeb(params.url)">访问商品</el-dropdown-item> <el-dropdown-item divided :command="beforeGoWeb(params.url)">访问商品</el-dropdown-item>
<el-dropdown-item :command="beforeGoMal(params.mallId)">访问店铺</el-dropdown-item> <el-dropdown-item :command="beforeGoMal(params.mallId)">访问店铺</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<ai-dialog <ai-dialog
title="复制" title="商品采集"
:visible.sync="copyFromDlgShow" :visible.sync="copyFromDlgShow"
:close-on-click-modal="false" :close-on-click-modal="false"
width="790px" width="790px"
@@ -22,19 +25,53 @@
@close="handleClose"> @close="handleClose">
<ai-copy-from-temu v-if="copyFromDlgShow" :params="temuParams" @onClose="handleClose" @onSuccess="handleSuccess"></ai-copy-from-temu> <ai-copy-from-temu v-if="copyFromDlgShow" :params="temuParams" @onClose="handleClose" @onSuccess="handleSuccess"></ai-copy-from-temu>
</ai-dialog> </ai-dialog>
<ai-dialog
title="添加到分组"
:visible.sync="addGroupDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
:append-to-body="true"
@close="handleClose">
<el-form class="ai-form" :model="addGroupForm" label-width="120px" ref="addGroupForm">
<el-form-item label="分组" style="width: 100%;" prop="groupId" :rules="[{ required: true, message: '请选择分组', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="addGroupForm.groupId" placeholder="请选择分组">
<el-option
v-for="item in groupList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="addGroupDlgShow = false">取消</el-button>
<el-button type="primary" @click="addToGroup">确定</el-button>
</div>
</ai-dialog>
</div> </div>
</template> </template>
<script> <script>
import AiCopyFromTemu from "./AiCopyFromTemu.vue"; import AiCopyFromTemu from "./AiCopyFromTemu.vue";
import { Message } from 'element-ui'
export default { export default {
name: "AiProductDropDown", name: "AiProductDropDown",
components: {AiCopyFromTemu}, components: {AiCopyFromTemu},
props: ['params', 'isShowDetail'], props: ['params', 'source', 'isShowDetail', 'isShowAddFavorite', 'isShowDelFavorite', 'isShowGroup', 'isHideCopy'],
data() { data() {
return { return {
info: {}, info: {},
copyFromDlgShow: false, copyFromDlgShow: false,
temuParams: {} addGroupDlgShow: false,
temuParams: {},
addGroupForm: {
groupId: '',
goodsId: ''
},
groupList: []
} }
}, },
computed: { computed: {
@@ -46,18 +83,59 @@ export default {
if (e.type == 'detail') { if (e.type == 'detail') {
this.$emit('onGoDetail') this.$emit('onGoDetail')
} else if (e.type == 'copy') { } else if (e.type == 'copy') {
this.temuParams = {url: 'https://www.temu.com/' + e.url} if (e.url.startsWith('http')) {
this.temuParams = {url: e.url}
} else {
this.temuParams = {url: 'https://www.temu.com/' + e.url}
}
this.copyFromDlgShow = true this.copyFromDlgShow = true
} else if (e.type == 'addFavorite') {
this.$http.post('/api/monitorFavorite/add',{goodsId: e.goodsId, monitorId: e.monitorId}).then(res => {
if (res.code == 0) {
Message.success('收藏成功')
}
})
} else if (e.type == 'delFavorite') {
this.$http.post('/api/monitorFavorite/del?id=' + e.id).then(res => {
if (res.code == 0) {
Message.success('删除收藏成功')
this.$emit('onDelFavoriteSuccess')
}
})
} else if (e.type == 'addGroup') {
this.addGroupForm.goodsId = e.goodsId
this.$http.post('/api/newProductGroup/myPage?size=1000').then(res => {
if (res.code == 0) {
this.addGroupDlgShow = true
this.groupList = res.data.records
}
})
} else if (e.type == 'goMall') { } else if (e.type == 'goMall') {
window.open('https://www.temu.com/mall.html?mall_id=' + e.mallId, '_blank'); if (e.mallId.startsWith('http')) {
window.open(e.mallId, '_blank');
} else {
window.open('https://www.temu.com/mall.html?mall_id=' + e.mallId, '_blank');
}
} else if (e.type == 'goWeb') { } else if (e.type == 'goWeb') {
console.log(e.url) if (e.url.startsWith('http')) {
window.open('https://www.temu.com/' + e.url, '_blank'); window.open(e.url, '_blank');
} else {
window.open('https://www.temu.com/' + e.url, '_blank');
}
} }
}, },
beforeGoDetail(goodsId) { beforeGoDetail(goodsId) {
return {type: 'detail', goodsId: goodsId} return {type: 'detail', goodsId: goodsId}
}, },
beforeAddFavorite(goodsId, monitorId) {
return {type: 'addFavorite', goodsId: goodsId, monitorId: monitorId}
},
beforeDelFavorite(id) {
return {type: 'delFavorite', id: id}
},
beforeAddGroup(goodsId) {
return {type: 'addGroup', goodsId: goodsId}
},
beforeCopy(url) { beforeCopy(url) {
return {type: 'copy', url: url} return {type: 'copy', url: url}
}, },
@@ -69,9 +147,22 @@ export default {
}, },
handleClose() { handleClose() {
this.copyFromDlgShow = false this.copyFromDlgShow = false
this.addGroupDlgShow = false
}, },
handleSuccess() { handleSuccess() {
this.copyFromDlgShow = false this.copyFromDlgShow = false
},
addToGroup() {
this.$refs.addGroupForm.validate((valid) => {
if (valid) {
this.$http.post('/api/newProductGroupDetail/add', {...this.addGroupForm}).then(res => {
if (res.code == 0) {
this.addGroupDlgShow = false
Message.success("商品成功添加到分组")
}
})
}
})
} }
} }
} }

View File

@@ -0,0 +1,81 @@
<template>
<ai-detail class="audit">
<template slot="content">
<ai-card title="基本信息">
<ai-product-drop-down v-if="info" :params="info" slot="right"></ai-product-drop-down>
<template #content>
<div class="flex">
<ai-avatar v-model="info.imgUrl" :editable="false" :preview="true"/>
<ai-wrapper
label-width="120px" class="fill">
<ai-info-item label="价格:" :value="'$' + info.price"></ai-info-item>
<ai-info-item label="销量:" :value="info.saleTotal"></ai-info-item>
</ai-wrapper>
</div>
</template>
</ai-card>
<ai-card title="趋势信息">
<template #content>
<div id="dataChart"></div>
</template>
</ai-card>
</template>
</ai-detail>
</template>
<script>
import AiProductDropDown from './AiProductDropDown.vue'
import { DualAxes } from '@antv/g2plot'
export default {
name: "AiSingleProductDetail",
props: ['params', 'url'],
components: {
AiProductDropDown
},
data() {
return {
info: {}
}
},
computed: {
},
mounted() {
// this.info = this.params
this.$nextTick(() => {
this.init()
})
},
methods: {
init() {
this.$http.post(this.url ? this.url: '/api/singleGoodsDetail/queryDetail',null,{
params: {
goodsId: this.params.goodsId,
groupId: this.params.groupId
}
}).then(res => {
this.info = res.data
const dualAxes = new DualAxes('dataChart', {
data: [this.info.priceAndSale, this.info.priceAndSale],
xField: '日期',
yField: ['价格', '销量'],
geometryOptions: [
{
geometry: 'line',
color: '#5B8FF9',
},
{
geometry: 'line',
color: '#5AD8A6',
}
],
});
dualAxes.render();
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,81 @@
<template>
<ai-detail class="audit">
<template slot="content">
<ai-card title="基本信息">
<ai-product-drop-down v-if="info" :params="info" slot="right"></ai-product-drop-down>
<template #content>
<div class="flex">
<ai-avatar v-model="info.imgUrl" :editable="false" :preview="true"/>
<ai-wrapper
label-width="120px" class="fill">
<ai-info-item label="价格:" :value="'$' + info.price"></ai-info-item>
<ai-info-item label="销量:" :value="info.saleTotal"></ai-info-item>
</ai-wrapper>
</div>
</template>
</ai-card>
<ai-card title="趋势信息">
<template #content>
<div id="dataChart"></div>
</template>
</ai-card>
</template>
</ai-detail>
</template>
<script>
import AiProductDropDown from './AiProductDropDown.vue'
import { DualAxes } from '@antv/g2plot'
export default {
name: "AiProductDetail",
props: ['params'],
components: {
AiProductDropDown
},
data() {
return {
info: {}
}
},
computed: {
},
mounted() {
// this.info = this.params
this.$nextTick(() => {
this.init()
})
},
methods: {
init() {
this.$http.post('/api/specialDetail/queryProductDetail',null,{
params: {
goodsId: this.params.goodsId,
categoryId: this.params.categoryId
}
}).then(res => {
this.info = res.data
const dualAxes = new DualAxes('dataChart', {
data: [this.info.priceAndSale, this.info.priceAndSale],
xField: '日期',
yField: ['价格', '销量'],
geometryOptions: [
{
geometry: 'line',
color: '#5B8FF9',
},
{
geometry: 'line',
color: '#5AD8A6',
}
],
});
dualAxes.render();
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,498 @@
<template>
<div class="print">
<div class="print-wrapper">
<div class="left">
<div class="left-wrapper">
<div class="title">基础元素</div>
<div class="left-item__wrapper">
<div class="ep-draggable-item item" tid="defaultModule.text">
<i class="iconfont">&#xe649;</i>
<span>文本</span>
</div>
<div v-if="false" class="ep-draggable-item item" tid="defaultModule.image">
<i class="iconfont">&#xe61e;</i>
<span>图片</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.longText">
<i class="iconfont">&#xe7dc;</i>
<span>长文</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.table">
<i class="iconfont">&#xea3f;</i>
<span>表格</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.html">
<i class="iconfont">&#xe633;</i>
<span>html</span>
</div>
</div>
<div class="title">辅助元素</div>
<div class="left-item__wrapper">
<div class="ep-draggable-item item" tid="defaultModule.hline">
<i class="iconfont">&#xe7dd;</i>
<span>横线</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.vline">
<i class="iconfont">&#xe70f;</i>
<span>竖线</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.rect">
<i class="iconfont">&#xe620;</i>
<span>矩形</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.oval">
<i class="iconfont">&#xe76a;</i>
<span>圆形</span>
</div>
</div>
<div class="title">常用元素</div>
<div class="left-item__wrapper" id="custom-provider">
<div class="ep-draggable-item item" tid="defaultModule.hline">
<i class="iconfont">&#xe7dd;</i>
<span>横线</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.vline">
<i class="iconfont">&#xe70f;</i>
<span>竖线</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.rect">
<i class="iconfont">&#xe620;</i>
<span>矩形</span>
</div>
<div class="ep-draggable-item item" tid="defaultModule.oval">
<i class="iconfont">&#xe76a;</i>
<span>圆形</span>
</div>
</div>
</div>
</div>
<div class="center">
<div class="center-header">
<div class="paper">
<el-button-group size="small">
<template v-for="(value, type) in paperTypes">
<el-button size="small" :type="curPaperType === type ? 'primary' : ''" @click="setPaper(type,value)" :key="type">
{{ type }}
</el-button>
</template>
<el-popover v-model="paperPopVisible" placement="top" :width="260" trigger="click">
<div>
<div style="font-size: 16px; font-weight: bold">设置纸张宽高(mm)</div>
<div style="margin-top: 10px">
<el-input size="small" style="margin-bottom: 10px" v-model="paperWidth" type="number" placeholder="宽(mm)" />
<el-input size="small" v-model="paperHeight" type="number" placeholder="高(mm)" />
</div>
<el-button style="margin-top: 12px" size="small" @click.stop="setPaperOther">确定</el-button>
</div>
<el-button slot="reference" size="small" :type="'other' == curPaperType ? 'primary' : ''">自定义纸张</el-button>
</el-popover>
</el-button-group>
<!-- <div class="scale">
<el-button @click="changeScale(false)" size="small">
<el-icon size="18"><ZoomOut /></el-icon>
</el-button>
<div style="margin: 0 4px; width: 40px">{{ (scaleValue * 100).toFixed(0) }}%</div>
<el-button @click="changeScale(true)" size="small">
<el-icon size="18"><ZoomIn /></el-icon>
</el-button>
</div> -->
</div>
</div>
<div class="center-wrapper">
<div id="hiprint-printTemplate"></div>
</div>
</div>
<div class="right">
<div id="PrintElementOptionSetting"></div>
</div>
</div>
<ai-dialog :visible.sync="isShowPreview" title="预览" width="1200" customFooter>
<div class="print-viewer" v-html="html"></div>
<div class="dialog-footer" slot="footer">
<el-button @click="isShowPreview = false">取消</el-button>
</div>
</ai-dialog>
</div>
</template>
<script>
import { hiprint, defaultElementTypeProvider, disAutoConnect } from 'vue-plugin-hiprint'
import { newHiprintPrintTemplate } from '@/utils/template-helper'
import template from './template'
import printData from './printData'
import { customProvider } from './customProvider'
disAutoConnect()
export default {
props: {
list: {
type: Array,
default: () => {
return []
}
}
},
data () {
return {
html: '',
isShowPreview: false,
hiprintTemplate: null,
curPaper: {
type: 'other',
width: 200,
height: 200
},
paperTypes: {
'A3': {
width: 420,
height: 296.6
},
'A4': {
width: 210,
height: 296.6
},
'A5': {
width: 210,
height: 147.6
},
'B3': {
width: 500,
height: 352.6
},
'B4': {
width: 250,
height: 352.6
},
'B5': {
width: 250,
height: 175.6
}
},
paperPopVisible: false,
paperWidth: '80',
paperHeight: '60'
}
},
computed: {
curPaperType() {
let type = 'other'
let types = this.paperTypes
for (const key in types) {
let item = types[key]
let {width, height} = this.curPaper
if (item.width === width && item.height === height) {
type = key
}
}
return type
}
},
mounted() {
hiprint.init({
providers: [defaultElementTypeProvider(), customProvider({})]
})
this.buildLeftElement()
this.buildDesigner()
},
methods: {
buildLeftElement() {
hiprint.PrintElementTypeManager.buildByHtml($('.ep-draggable-item'))
$('#custom-provider').empty()
hiprint.PrintElementTypeManager.build($('#custom-provider'), 'customProvider')
},
buildDesigner() {
$('#hiprint-printTemplate').empty()
console.log(template)
this.hiprintTemplate = newHiprintPrintTemplate('temulables', {
template: template,
settingContainer: '#PrintElementOptionSetting',
onImageChooseClick: (target) => {
let input = document.createElement('input')
input.setAttribute('type', 'file')
input.click()
input.onchange = function () {
var file = this.files[0]
var reader = new FileReader()
if (file) {
var reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = function () {
target.refresh(reader.result)
}
}
}
input.remove()
}
})
this.$nextTick(() => {
this.hiprintTemplate.design('#hiprint-printTemplate', {
grid: true
})
})
},
setPaperOther () {
let value = {}
value.width = this.paperWidth
value.height = this.paperHeight
this.setPaper('other', value)
this.paperPopVisible = false
},
setPaper(type, value) {
try {
if (Object.keys(this.paperTypes).includes(type)) {
this.curPaper = {type: type, width: value.width, height: value.height}
this.hiprintTemplate.setPaper(value.width, value.height)
} else {
this.curPaper = {type: 'other', width: value.width, height: value.height}
this.hiprintTemplate.setPaper(value.width, value.height)
}
} catch (error) {
this.$message.error(`操作失败: ${error}`)
}
},
print() {
let options = { leftOffset: 0, topOffset: 0 }
let ext = {
callback: () => {
console.log('浏览器打印窗口已打开')
},
styleHandler: () => {
return '<style></style>'
}
}
const list = this.labels
this.hiprintTemplate.print(list)
},
elementToString(el) {
const node = document.createElement('div')
node.appendChild(el)
return node.innerHTML
},
savePdf() {
const list = this.labels
this.hiprintTemplate.toPdf(printData, '测试导出pdf')
},
getHtml() {
const list = this.labels
this.html = this.elementToString(this.hiprintTemplate.getHtml(list)[0])
console.log(this.html)
this.isShowPreview = true
},
clearPaper() {
this.hiprintTemplate.clear()
},
exportJson() {
return this.hiprintTemplate.getJson()
}
}
}
</script>
<style lang="scss" scoped>
.print {
height: 100%;
.print-wrapper {
display: flex;
height: calc(100vh - 180px);
::v-deep(.prop-tab-items) {
background-color: transparent !important;
.prop-tab-item {
background-color: transparent !important;
.tab-title {
font-size: 14px;
}
}
}
::v-deep(.hiprint-option-items .hiprint-option-item-label) {
width: 100%;
margin-bottom: 14px;
text-align: left;
font-size: 14px;
}
::v-deep(.hiprint-printPanel) {
display: block;
& > div {
// margin: 0 auto !important;
}
}
::v-deep(.minicolors) {
flex: 1;
width: inherit;
input {
width: 100% !important;
}
}
::v-deep(.hiprint-option-item-field) {
width: 100%;
font-size: 14px;
input {
height: 24px;
}
}
::v-deep(.hiprint-option-item-row) {
display: block;
}
::v-deep(.prop-tab-items) {
margin-bottom: 10px;
}
::v-deep(.hiprint-option-items),
::v-deep(.prop-tabs) {
background-color: transparent !important;
}
.left {
width: 350px;
height: 100%;
overflow-y: auto;
.title {
margin: 14px 0;
}
.left-item__wrapper {
display: flex;
flex-wrap: wrap;
margin-bottom: 10px;
.item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100px;
margin-bottom: 10px;
margin-right: 10px;
padding: 10px 0;
background-color: #eee;
border-radius: 4px;
&:nth-of-type(3n) {
margin-right: 0;
}
span {
margin-top: 10px;
}
}
::v-deep(ul) {
margin: 0;
padding: 0;
list-style: none;
.title {
display: none;
}
ul {
display: flex;
flex-wrap: wrap;
margin-bottom: 10px;
li {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100px;
margin-bottom: 10px;
margin-right: 10px;
background-color: #eee;
border-radius: 4px;
&:nth-of-type(3n) {
margin-right: 0;
}
a {
margin-top: 10px;
padding: 10px 0;
}
}
}
}
}
}
.center {
display: flex;
position: relative;
flex-direction: column;
flex: 1;
overflow: hidden;
padding: 0 10px;
color: #000;
.center-wrapper {
flex: 1;
overflow-x: auto;
overflow-y: auto;
width: 100%;
padding: 20px 20px 10px;
}
.center-header {
width: 100%;
padding-bottom: 10px;
.paper {
display: flex;
position: relative;
align-items: center;
justify-content: center;
}
.scale {
display: flex;
align-items: center;
margin: 0 10px;
}
}
}
.right {
width: 300px;
overflow-y: auto;
overflow-x: hidden;
}
}
.print-viewer {
color: #000;
}
}
</style>

View File

@@ -0,0 +1,66 @@
import { hiprint } from 'vue-plugin-hiprint'
export const customProvider = function (options) {
var addElementTypes = function (context) {
context.removePrintElementTypes('customProvider')
context.addPrintElementTypes('customProvider', [
new hiprint.PrintElementTypeGroup('', [
{
tid: 'providerModule1.date',
title: '业务日期',
data: '2020-01-01',
type: 'text',
options: {
field: 'date',
testData: '2020-01-01',
height: 16,
fontSize: 6.75,
fontWeight: '700',
textAlign: 'left',
textContentVerticalAlign: 'middle',
hideTitle: true
}
},
{
tid: 'providerModule1.barcode',
title: '条形码',
data: 'XS888888888',
type: 'text',
options: {
field: 'barcode',
testData: 'XS888888888',
height: 32,
fontSize: 12,
lineHeight: 18,
textAlign: 'left',
textType: 'barcode',
hideTitle: false
}
},
{
tid: 'providerModule1.qrcode',
title: '二维码',
data: 'XS888888888',
type: 'text',
options: {
field: '',
testData: '',
height: 32,
fontSize: 12,
lineHeight: 18,
textType: 'qrcode',
hideTitle: false
}
},
{
tid: 'providerModule1.image',
title: 'Logo',
data: 'https://foruda.gitee.com/avatar/1677050350324030848/5400665_ccsimple_1591166830.png!avatar200',
type: 'image'
}
])
])
}
return {
addElementTypes: addElementTypes
}
}

View File

@@ -0,0 +1,10 @@
export default {
name: '黄磊',
password: '12346',
barcode: 'XS888888888',
table: [
{ id: '1', name: '王小可', gender: '男', count: '120', amount: '9089元' },
{ id: '2', name: '梦之遥', gender: '女', count: '20', amount: '89元' },
{ id: '3', name: '梦之遥', gender: '女', count: '720', amount: '29089元' }
]
}

File diff suppressed because one or more lines are too long

View File

@@ -1,29 +1,82 @@
/** /**
利用chrome的fetch来避免跨域 利用chrome的fetch来避免跨域
**/ **/
import {getSign} from "@/api/aliExpress";
import qs from "query-string"
/**
* 根据图片URL获取Blob对象
* @param imageUrl
* @returns {Promise<unknown>}
*/
function getImageBlob(imageUrl) {
return new Promise((resolve) => {
fetch(imageUrl).then((response) => response.blob()) // 将响应转换为Blob对象
.then((blobData) => {
const fileName = imageUrl.match(/\/([^/]+)$/).at(-1)
const reader = new FileReader();
// 读取Blob对象的内容
reader.onloadend = function () {
const image = {blobData, fileName}
resolve({image});
};
reader.readAsArrayBuffer(blobData); // 将Blob对象作为参数传递给FileReader的readAsArrayBuffer()方法
})
});
}
/**
* 将cookie字符串转换为对象
* @param cookieString
* @returns {{}}
*/
function cookie2Obj(cookieString) {
const cookieArray = cookieString.split(';');
const cookieObj = {};
for (let i = 0; i < cookieArray.length; i++) {
const [key, value] = cookieArray[i].split('=');
cookieObj[key] = value;
}
return cookieObj;
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type == 'api') { if (request.type == 'api') {
let headers = {}, data;
new Promise((resolve) => { new Promise((resolve) => {
let headers = {};
if (request.needMallId) { if (request.needMallId) {
headers.Mallid = request.mallId; headers.Mallid = request.mallId;
} }
if (request.anti) { if (request.anti) {
headers["Anti-Content"] = request.anti headers["Anti-Content"] = request.anti
} }
headers['Content-Type'] = 'application/json'; if (!request.isFormData) {
headers['Content-Type'] = 'application/json';
data = JSON.stringify(request.data)
} else {
const formData = new FormData();
Object.keys(request.data).forEach(key => {
const value = request.data[key]
if (Array.isArray(value)) {
value.forEach((subValue, i) => {
formData.append(key + `[${i}]`, subValue)
})
} else {
if (key == "image") {
//跳过image的处理
} else formData.append(key, request.data[key])
}
})
data = formData
}
headers.cookie = getCookie(); headers.cookie = getCookie();
Promise.resolve().then(() => fetch(request.url, { if (request.isFormData && !!request.data.image) {//针对图片上传特殊的处理办法
'headers': headers, getImageBlob(request.data.image).then(res => data.append('image', res.image.blobData)).then(resolve)
'method': 'POST', } else resolve()
'referrerPolicy': 'no-referrer', }).then(() => fetch(request.url, {
'credentials': 'include', headers, 'method': 'POST', 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'body': data, 'mode': 'cors'
'body': JSON.stringify(request.data), })).then(res => res.json()).then(sendResponse);
'mode': 'cors'
})).then((res) => {
resolve(res.json());
});
}).then(sendResponse);
} else if (request.type == 'temuApi') { } else if (request.type == 'temuApi') {
new Promise((resolve) => { new Promise((resolve) => {
let headers = {}; let headers = {};
@@ -33,26 +86,20 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
headers['Content-Type'] = 'application/json'; headers['Content-Type'] = 'application/json';
headers.cookie = getTemuCookie(); headers.cookie = getTemuCookie();
Promise.resolve().then(() => fetch(request.url, { Promise.resolve().then(() => fetch(request.url, {
'headers': headers, 'headers': headers, 'method': 'POST', 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'body': JSON.stringify(request.data), 'mode': 'cors'
'method': 'POST',
'referrerPolicy': 'no-referrer',
'credentials': 'include',
'body': JSON.stringify(request.data),
'mode': 'cors'
})).then((res) => { })).then((res) => {
resolve(res.json()); resolve(res.json());
}).catch(() => {
resolve({success: false, errorCode: -1})
}); });
}).then(sendResponse); }).then(sendResponse);
} else if (request.type == 'temu') { } else if (request.type == 'temu') {
new Promise((resolve) => { new Promise((resolve) => {
let headers = {}; let headers = {};
headers['Content-Type'] = 'text/html'; headers['Content-Type'] = 'text/html';
headers.cookie = getTemuCookie(); //headers.cookie = getTemuCookie();
Promise.resolve().then(() => fetch(request.url, { Promise.resolve().then(() => fetch(request.url, {
'headers': headers, 'headers': headers, 'method': 'GET', 'referrerPolicy': 'no-referrer', //'credentials': 'include',
'method': 'GET',
'referrerPolicy': 'no-referrer',
'credentials': 'include',
'mode': 'cors' 'mode': 'cors'
})).then((res) => { })).then((res) => {
// 创建了一个数据读取器 // 创建了一个数据读取器
@@ -61,7 +108,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const decoder = new TextDecoder(); const decoder = new TextDecoder();
let text = "" let text = ""
reader.read().then(function processText({ done, value }) { reader.read().then(function processText({done, value}) {
// Result 对象包含了两个属性: // Result 对象包含了两个属性:
// done - 当 stream 传完所有数据时则变成 true // done - 当 stream 传完所有数据时则变成 true
// value - 数据片段。当 done 为 true 时始终为 undefined // value - 数据片段。当 done 为 true 时始终为 undefined
@@ -77,16 +124,18 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
}); });
}).then(sendResponse); }).then(sendResponse);
} else if (request.type == 'aliexpress') { } else if (request.type == 'aliexpress') {
new Promise((resolve) => { new Promise(async (resolve) => {
let headers = {}; let headers = {};
headers['Content-Type'] = 'text/html'; headers['Content-Type'] = 'text/html';
headers.cookie = getAliexpressCookie(); const cookie = await getAliexpressCookie(request.url);
Promise.resolve().then(() => fetch(request.url, { const {_m_h5_c, _m_h5_tk} = cookie2Obj(cookie)
'headers': headers, const {query: {data, appKey}} = qs.parseUrl(request.url)
'method': 'GET', const {formData = data} = request
'referrerPolicy': 'no-referrer', const {sign, t} = getSign(_m_h5_c || _m_h5_tk, appKey, formData)
'credentials': 'include', const url = qs.stringifyUrl({url: request.url, query: {sign, t}})
'mode': 'cors' headers.cookie = await getAliexpressCookie(url);
Promise.resolve().then(() => fetch(url, {
'headers': headers, 'method': 'POST', 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'mode': 'cors'
})).then((res) => { })).then((res) => {
// 创建了一个数据读取器 // 创建了一个数据读取器
const reader = res.body.getReader(); const reader = res.body.getReader();
@@ -94,7 +143,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const decoder = new TextDecoder(); const decoder = new TextDecoder();
let text = "" let text = ""
reader.read().then(function processText({ done, value }) { reader.read().then(function processText({done, value}) {
// Result 对象包含了两个属性: // Result 对象包含了两个属性:
// done - 当 stream 传完所有数据时则变成 true // done - 当 stream 传完所有数据时则变成 true
// value - 数据片段。当 done 为 true 时始终为 undefined // value - 数据片段。当 done 为 true 时始终为 undefined
@@ -109,15 +158,49 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
}); });
}); });
}).then(sendResponse); }).then(sendResponse);
} else if (request.type == 'sheinApi') {
new Promise((resolve) => {
let headers = {};
headers['Content-Type'] = 'application/json';
headers.cookie = getSheinCookie()
request.params = formatParams(request.params)
let _url = request.url + (request.params ? ('?' + request.params) : '')
Promise.resolve().then(() => fetch(_url, {
'headers': headers, 'method': request.method, 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'body': JSON.stringify(request.data), 'mode': 'cors'
})).then((res) => {
resolve(res.json());
});
}).then(sendResponse);
} else if (request.type == 'geiwohuoApi') {
new Promise((resolve) => {
let headers = {};
headers['Content-Type'] = 'application/json';
headers['X-Req-Zone-Id'] = 'Asia/Shanghai';
headers['X-Lt-Language'] = 'CN';
Promise.resolve().then(() => fetch(request.url, {
'headers': headers, 'method': request.method, 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'body': JSON.stringify(request.data), 'mode': 'cors'
})).then((res) => {
resolve(res.json());
}).catch(() => {
resolve({success: false, errorCode: -1})
});
}).then(sendResponse);
} else if (request.type == 'goodcangApi') {
new Promise((resolve) => {
let headers = {};
headers['Content-Type'] = 'application/json';
Promise.resolve().then(() => fetch(request.url, {
'headers': headers, 'method': request.method, 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'body': JSON.stringify(request.data), 'mode': 'cors'
})).then((res) => {
resolve(res.json());
}).catch(() => {
resolve({success: false, errorCode: -1})
});
}).then(sendResponse);
} else if (request.type == 'notify') { } else if (request.type == 'notify') {
chrome.notifications.create( chrome.notifications.create("" + Math.random(), {
"" + Math.random(), { type: "basic", title: "TEMU助手", message: "您店铺【" + request.mallName + "】的商品【" + request.productName + "】成功加入发货台,请尽快处理", iconUrl: "./icons/48.png"
type: "basic", }, null)
title: "TEMU助手",
message: "您店铺【" + request.mallName + "】的商品【" + request.productName + "】成功加入发货台,请尽快处理",
iconUrl: "./icons/48.png"
}, null
)
} }
return true; return true;
@@ -132,35 +215,32 @@ chrome.action.onClicked.addListener(function () {
}); });
chrome.webRequest.onSendHeaders.addListener(details => { chrome.webRequest.onSendHeaders.addListener(details => {
if (details.url && (details.url.indexOf('joinDeliveryGoodsOrderPlatform') != -1)) { if (details.url && (details.url.indexOf('joinDeliveryGoodsOrderPlatform') != -1)) {
details.requestHeaders.push({ details.requestHeaders.push({
name: 'Referer', name: 'Referer', value: 'https://seller.kuajingmaihuo.com/main/order-manage'
value: 'https://kuajing.pinduoduo.com/main/order-manage'
}) })
for (let i = 0 ; i < details.requestHeaders.length; i++) { for (let i = 0; i < details.requestHeaders.length; i++) {
if (details.requestHeaders[i].name == 'Origin') { if (details.requestHeaders[i].name == 'Origin') {
details.requestHeaders[i].value = 'https://kuajing.pinduoduo.com' details.requestHeaders[i].value = 'https://seller.kuajingmaihuo.com'
break; break;
} }
} }
} else if (details.url && (details.url.indexOf('mms/userInfo') != -1)) { } else if (details.url && (details.url.indexOf('mms/userInfo') != -1)) {
details.requestHeaders.push({ details.requestHeaders.push({
name: 'Referer', name: 'Referer', value: 'https://seller.kuajingmaihuo.com/main/order-manage'
value: 'https://kuajing.pinduoduo.com/main/order-manage'
}) })
for (let i = 0 ; i < details.requestHeaders.length; i++) { for (let i = 0; i < details.requestHeaders.length; i++) {
if (details.requestHeaders[i].name == 'Origin') { if (details.requestHeaders[i].name == 'Origin') {
details.requestHeaders[i].value = 'https://kuajing.pinduoduo.com' details.requestHeaders[i].value = 'https://seller.kuajingmaihuo.com'
break; break;
} }
} }
} }
}, }, {urls: ["<all_urls>"]}, ["requestHeaders", "extraHeaders"]);
{urls: ["<all_urls>"]},["requestHeaders", "extraHeaders"]);
function getCookie() { function getCookie() {
const url = new URL("https://kuajing.pinduoduo.com/"); const url = new URL("https://seller.kuajingmaihuo.com/");
let cStr = ''; let cStr = '';
chrome.cookies.getAll({domain: url.host}, (cookie) => { chrome.cookies.getAll({domain: url.host}, (cookie) => {
cookie.map((c) => { cookie.map((c) => {
@@ -181,8 +261,8 @@ function getTemuCookie() {
return cStr; return cStr;
} }
function getAliexpressCookie() { function getSheinCookie() {
const url = new URL("https://www.aliexpress.us/"); const url = new URL("https://www.shein.com/");
let cStr = ''; let cStr = '';
chrome.cookies.getAll({domain: url.host}, (cookie) => { chrome.cookies.getAll({domain: url.host}, (cookie) => {
cookie.map((c) => { cookie.map((c) => {
@@ -190,4 +270,25 @@ function getAliexpressCookie() {
}); });
}); });
return cStr; return cStr;
} }
function getAliexpressCookie(link = "https://csp.aliexpress.com/") {
let cStr = '';
return new Promise((resolve, reject) => {
chrome.cookies.getAll({url: link}, (cookie) => {
cookie.map((c) => {
cStr += c.name + '=' + c.value + ';';
});
resolve(cStr);
});
})
}
function formatParams(data) {
const arr = []
for (let name in data) {
arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]))
}
return arr.join("&")
}

View File

@@ -8,3 +8,4 @@ function injectScript(file, node) {
injectScript( chrome.runtime.getURL('/js/jszip.min.js'), 'body'); injectScript( chrome.runtime.getURL('/js/jszip.min.js'), 'body');
injectScript( chrome.runtime.getURL('/js/FileSaver.js'), 'body'); injectScript( chrome.runtime.getURL('/js/FileSaver.js'), 'body');
injectScript( chrome.runtime.getURL('/js/download.js'), 'body'); injectScript( chrome.runtime.getURL('/js/download.js'), 'body');
injectScript( chrome.runtime.getURL('/js/temuSeller.js'), 'body');

View File

@@ -17,7 +17,7 @@ var relativeTime = require('dayjs/plugin/relativeTime')
require('dayjs/locale/zh-cn') require('dayjs/locale/zh-cn')
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
Vue.prototype.$dayjs = dayjs Vue.prototype.$dayjs = dayjs
Vue.prototype.$base = "https://kuajing.pinduoduo.com" Vue.prototype.$base = "https://seller.kuajingmaihuo.com"
Object.keys(utils).forEach(v => Vue.prototype[`$${v}`] = utils[v]) Object.keys(utils).forEach(v => Vue.prototype[`$${v}`] = utils[v])
Vue.prototype.$http = instance Vue.prototype.$http = instance

View File

@@ -11,13 +11,18 @@
"48": "icons/48.png", "48": "icons/48.png",
"128": "icons/128.png" "128": "icons/128.png"
}, },
"action": { "action": {},
},
"host_permissions": [ "host_permissions": [
"*://*.pinduoduo.com/", "*://*.jjcp52.com/",
"*://*.kuajingmaihuo.com/",
"*://*.temu.com/", "*://*.temu.com/",
"*://*.aliexpress.us/", "*://*.aliexpress.com/",
"*://*.amazon.com/" "*://*.alicdn.com/",
"*://*.amazon.com/",
"*://*.shein.com/",
"*://*.geiwohuo.com/",
"*://*.ltwebstatic.com/",
"*://*.goodcang.com/"
], ],
"permissions": [ "permissions": [
"cookies", "cookies",
@@ -28,30 +33,98 @@
"declarativeNetRequest", "declarativeNetRequest",
"declarativeNetRequestWithHostAccess", "declarativeNetRequestWithHostAccess",
"declarativeNetRequestFeedback", "declarativeNetRequestFeedback",
"activeTab" "activeTab",
"fileSystemProvider"
], ],
"declarative_net_request": { "declarative_net_request": {
"rule_resources": [{ "rule_resources": [
"id": "1", {
"enabled": true, "id": "1",
"path": "rules_1.json" "enabled": true,
}] "path": "rules_1.json"
},
{
"id": "3",
"enabled": true,
"path": "rules_3.json"
},
{
"id": "5",
"enabled": true,
"path": "rules_5.json"
},
{
"id": "6",
"enabled": true,
"path": "rules_6.json"
},
{
"id": "7",
"enabled": true,
"path": "rules_7.json"
},
{
"id": "8",
"enabled": true,
"path": "rules_8.json"
},
{
"id": "9",
"enabled": true,
"path": "rules_9.json"
},
{
"id": "10",
"enabled": true,
"path": "rules_10.json"
},
{
"id": "11",
"enabled": true,
"path": "rules_11.json"
}
]
}, },
"content_scripts": [ "content_scripts": [
{ {
"matches": [ "matches": [
"*://*.aliexpress.us/item/*", "*://*.aliexpress.com/item/*",
"*://*.amazon.com/*" "*://*.amazon.com/*",
"*://*.shein.com/*"
], ],
"js": [ "js": [
"/content.js" "/content.js"
] ]
},
{
"matches": [
"*://*.kuajingmaihuo.com/*"
],
"js": [
"/js/temuSellerContent.js"
]
} }
], ],
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": [ "js/download.js","js/jszip.min.js","js/FileSaver.js" ], "resources": [
"matches": [ "*://*.aliexpress.us/*", "*://*.amazon.com/*" ] "js/download.js",
"js/jszip.min.js",
"js/FileSaver.js"
],
"matches": [
"*://*.aliexpress.com/*",
"*://*.amazon.com/*",
"*://*.shein.com/*"
]
},
{
"resources": [
"js/temuSeller.js"
],
"matches": [
"*://*.kuajingmaihuo.com/*"
]
} }
] ]
} }

View File

@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "TEMU助手", "name": "TEMU助手",
"description": "TEMU助手 - 自动化提高生产效率", "description": "TEMU助手 - 自动化提高生产效率",
"version": "3.0.0", "version": "3.3.1",
"background": { "background": {
"service_worker": "/background.js" "service_worker": "/background.js"
}, },
@@ -11,13 +11,17 @@
"48": "icons/48.png", "48": "icons/48.png",
"128": "icons/128.png" "128": "icons/128.png"
}, },
"action": { "action": {},
},
"host_permissions": [ "host_permissions": [
"*://*.pinduoduo.com/", "*://124.71.2.127:8888/",
"*://*.kuajingmaihuo.com/",
"*://*.temu.com/", "*://*.temu.com/",
"*://*.aliexpress.us/", "*://*.aliexpress.com/",
"*://*.amazon.com/" "*://*.alicdn.com/",
"*://*.amazon.com/",
"*://*.shein.com/",
"*://*.geiwohuo.com/",
"*://*.ltwebstatic.com/"
], ],
"permissions": [ "permissions": [
"cookies", "cookies",
@@ -28,30 +32,98 @@
"declarativeNetRequest", "declarativeNetRequest",
"declarativeNetRequestWithHostAccess", "declarativeNetRequestWithHostAccess",
"declarativeNetRequestFeedback", "declarativeNetRequestFeedback",
"activeTab" "activeTab",
"fileSystemProvider"
], ],
"declarative_net_request": { "declarative_net_request": {
"rule_resources": [{ "rule_resources": [
"id": "1", {
"enabled": true, "id": "1",
"path": "rules_1.json" "enabled": true,
}] "path": "rules_1.json"
},
{
"id": "3",
"enabled": true,
"path": "rules_3.json"
},
{
"id": "4",
"enabled": true,
"path": "rules_4.json"
},
{
"id": "5",
"enabled": true,
"path": "rules_5.json"
},
{
"id": "6",
"enabled": true,
"path": "rules_6.json"
},
{
"id": "7",
"enabled": true,
"path": "rules_7.json"
},
{
"id": "8",
"enabled": true,
"path": "rules_8.json"
},
{
"id": "9",
"enabled": true,
"path": "rules_9.json"
},
{
"id": "11",
"enabled": true,
"path": "rules_11.json"
}
]
}, },
"content_scripts": [ "content_scripts": [
{ {
"matches": [ "matches": [
"*://*.aliexpress.us/item/*", "*://*.aliexpress.com/item/*",
"*://*.amazon.com/*" "*://*.amazon.com/*",
"*://*.shein.com/*"
], ],
"js": [ "js": [
"/content.js" "/content.js"
] ]
},
{
"matches": [
"*://*.kuajingmaihuo.com/*"
],
"js": [
"/js/temuSellerContent.js"
]
} }
], ],
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": [ "js/download.js","js/jszip.min.js","js/FileSaver.js" ], "resources": [
"matches": [ "*://*.aliexpress.us/*", "*://*.amazon.com/*" ] "js/download.js",
"js/jszip.min.js",
"js/FileSaver.js"
],
"matches": [
"*://*.aliexpress.com/*",
"*://*.amazon.com/*",
"*://*.shein.com/*"
]
},
{
"resources": [
"js/temuSeller.js"
],
"matches": [
"*://*.kuajingmaihuo.com/*"
]
} }
] ]
} }

View File

@@ -1,6 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
import store from '@/store' import store from '@/store'
import media from "@/router/media";
Vue.use(VueRouter) Vue.use(VueRouter)
@@ -25,6 +26,11 @@ const router = new VueRouter({
name: 'changePwd', name: 'changePwd',
component: () => import('../view/login/ChangePwd') component: () => import('../view/login/ChangePwd')
}, },
{
path: 'waitCreate',
name: 'waitCreate',
component: () => import('../view/shipping/WaitCreate.vue')
},
{ {
path: 'normalSendGoods', path: 'normalSendGoods',
name: 'NormalSendGoods', name: 'NormalSendGoods',
@@ -50,23 +56,76 @@ const router = new VueRouter({
name: 'waitShippingList', name: 'waitShippingList',
component: () => import('../view/shipping/WaitShippingList.vue') component: () => import('../view/shipping/WaitShippingList.vue')
}, },
{
path: 'myNormalOrder',
name: 'myNormalOrder',
component: () => import('../view/shipping/MyNormalOrder.vue')
},
{
path: 'myUrgencyOrder',
name: 'myUrgencyOrder',
component: () => import('../view/shipping/MyUrgencyOrder.vue')
},
{
path: 'productLabel',
name: 'productLabel',
component: () => import('../view/shipping/ProductLabel.vue')
},
{
path: 'returnPackage',
name: 'returnPackage',
component: () => import('../view/stock/ReturnPackage.vue')
},
{
path: 'returnDetail',
name: 'returnDetail',
component: () => import('../view/stock/ReturnDetail.vue')
},
{
path: 'productList',
name: 'productList',
component: () => import('../view/product/ProductList.vue')
},
{ {
path: 'copyProduct', path: 'copyProduct',
name: 'copyProduct', name: 'copyProduct',
component: () => import('../view/product/CopyProduct.vue') component: () => import('../view/product/CopyProduct.vue')
}, },
{ {
path: 'reducePrice', path: 'sellerSelect',
name: 'reducePrice', name: 'sellerSelect',
component: () => import('../view/product/ReducePrice.vue') component: () => import('../view/product/SellerSelect.vue')
},
{
path: 'draft',
name: 'draft',
component: () => import('../view/product/Draft.vue')
},
{
path: 'findSeller',
name: 'findSeller',
component: () => import('../view/product/FindSeller.vue')
},
{
path: 'copyProductAliExpress',
name: 'copyProductAliExpress',
component: () => import('../view/product/CopyProductAliExpress.vue')
},
{
path: 'batchUpload',
name: 'batchUpload',
component: () => import('../view/product/BatchUpload.vue')
}, },
{ {
path: 'niubiCopy', path: 'niubiCopy',
name: 'niubiCopy', name: 'niubiCopy',
component: () => import('../view/selection/NiubiCopy.vue') component: () => import('../view/selection/NiubiCopy.vue')
}, },
{
path: 'aliExpressCopy',
name: 'aliExpressCopy',
component: () => import('../view/selection/AliExpressCopy.vue')
},
{ {
path: 'storeTrack', path: 'storeTrack',
name: 'storeTrack', name: 'storeTrack',
@@ -77,7 +136,46 @@ const router = new VueRouter({
name: 'keywordTrack', name: 'keywordTrack',
component: () => import('../view/selection/keywordtrack/Index.vue') component: () => import('../view/selection/keywordtrack/Index.vue')
}, },
{
path: 'favoriteTrack',
name: 'favoriteTrack',
component: () => import('../view/selection/favoritetrack/Index.vue')
},
{
path: 'indexTrack',
name: 'indexTrack',
component: () => import('../view/selection/indextrack/Index.vue')
},
{
path: 'newProduct',
name: 'newProduct',
component: () => import('../view/selection/newproducttrack/newproduct/Index.vue')
},
{
path: 'newProductGroup',
name: 'newProductGroup',
component: () => import('../view/selection/newproducttrack/newproductgroup/Index.vue')
},
{
path: 'bestSellers',
name: 'bestSellers',
component: () => import('../view/selection/bestsellers/Index.vue')
},
{
path: 'singleTrack',
name: 'singleTrack',
component: () => import('../view/selection/singletrack/Index.vue')
},
{
path: 'info',
name: 'info',
component: () => import('../view/Info.vue')
},
{
path: 'priceFollow',
name: 'priceFollow',
component: () => import('../view/PriceFollow.vue')
},
{ {
path: 'message', path: 'message',
name: 'message', name: 'message',
@@ -89,13 +187,108 @@ const router = new VueRouter({
name: 'coinFlow', name: 'coinFlow',
component: () => import('../view/CoinFlow.vue') component: () => import('../view/CoinFlow.vue')
}, },
{
path: 'costManageTemu',
name: 'costManageTemu',
component: () => import('../view/sale/CostManageTemu.vue')
},
{ {
path: 'saleData', path: 'saleData',
name: 'saleData', name: 'saleData',
component: () => import('../view/ExportSaleData.vue') component: () => import('../view/sale/ExportSaleData.vue')
}, },
{
path: 'saleOut',
name: 'saleOut',
component: () => import('../view/sale/ExportSaleOutData.vue')
},
{
path: 'afterSaleStat',
name: 'afterSaleStat',
component: () => import('../view/sale/AfterSaleStat.vue')
},
{
path: 'afterSaleDeductStat',
name: 'afterSaleDeductStat',
component: () => import('../view/sale/AfterSaleDeductStat.vue')
},
{
path: 'saleStatTemu',
name: 'saleStatTemu',
component: () => import('../view/sale/ExportSaleStatTemu.vue')
},
{
path: 'priceAdjustment',
name: 'priceAdjustment',
component: () => import('../view/sale/PriceAdjustment.vue')
},
{
path: 'logisticFee',
name: 'logisticFee',
component: () => import('../view/sale/LogisticFee.vue')
},
{
path: 'billStat',
name: 'billStat',
component: () => import('../view/sale/ExportBillStatTemu.vue')
},
{
path: 'costManageShein',
name: 'costManageShein',
component: () => import('../view/shein/CostManageShein.vue')
},
{
path: 'certCenterShein',
name: 'certCenterShein',
component: () => import('../view/shein/CertCenterShein.vue')
},
{
path: 'saleDataShein',
name: 'saleDataShein',
component: () => import('../view/shein/ExportSaleDataShein.vue')
},
{
path: 'saleStatShein',
name: 'saleStatShein',
component: () => import('../view/shein/ExportSaleStatShein.vue')
},
{
path: 'copyProductShein',
name: 'copyProductShein',
component: () => import('../view/shein/CopyProductShein.vue')
},
{
path: 'orderListShein',
name: 'orderListShein',
component: () => import('../view/shein/OrderListShein.vue')
},
{
path: 'productListOdm',
name: 'productListOdm',
component: () => import('../view/shein/ProductListOdm.vue')
},
{
path: 'returnRecordShein',
name: 'returnRecordShein',
component: () => import('../view/shein/ReturnRecordShein.vue')
},
{
path: 'labelInfoShein',
name: 'labelInfoShein',
component: () => import('../view/shein/LabelInfoShein.vue')
},
{
path: 'purchaseOrderListShein',
name: 'purchaseOrderListShein',
component: () => import('../view/shein/PurchaseOrderListShein.vue')
},
{
path: 'syncDataTemu',
name: 'syncDataTemu',
component: () => import('../view/data/SyncDataTemu.vue')
},
// { // {
// path: 'statistics', // path: 'statistics',
// name: 'statistics', // name: 'statistics',
@@ -105,7 +298,47 @@ const router = new VueRouter({
path: 'learning', path: 'learning',
name: 'learning', name: 'learning',
component: () => import('../view/Learning.vue') component: () => import('../view/Learning.vue')
} },
{
path: 'qualification',
name: 'qualification',
children: [
{
path: 'oushuitong',
name: 'oushuitong',
meta: {
activeMenu: '/qualification'
}
}
]
},
{
path: 'labelsTemplate',
name: 'labelsTemplate',
component: () => import('../view/lables/Template.vue')
},
{
path: 'addLabelsTemplate',
name: 'addLabelsTemplate',
component: () => import('../view/lables/AddTemplate.vue'),
meta: {
activeMenu: '/labelsTemplate'
}
},
{
path: 'labelsPrint',
name: 'labelsPrint',
component: () => import('../view/lables/Print.vue')
},
{
path: 'skuManage',
name: 'skuManage',
component: () => import('../view/lables/SkuManage.vue'),
meta: {
activeMenu: '/labelsTemplate'
}
},
...media
] ]
}, },
{ {
@@ -123,13 +356,21 @@ const router = new VueRouter({
title: '注册' title: '注册'
}, },
component: () => import('../view/login/Register.vue') component: () => import('../view/login/Register.vue')
},
{
path: '/forget',
name: 'forget',
meta: {
title: '注册'
},
component: () => import('../view/login/Forget.vue')
} }
], ],
scrollBehavior (to, from, savedPosition) { scrollBehavior(to, from, savedPosition) {
if (savedPosition) { if (savedPosition) {
return savedPosition return savedPosition
} else { } else {
return { x: 0, y: 0 } return {x: 0, y: 0}
} }
} }
}) })

3
src/router/media.js Normal file
View File

@@ -0,0 +1,3 @@
export default [
{name: "imageTranslate", path: "imageTranslate", component: () => import("@/view/media/imageTranslate.vue")}
]

View File

@@ -13,6 +13,8 @@ export default new Vuex.Store({
mallName: '', mallName: '',
mallList: [], mallList: [],
activeDlgShow: false, activeDlgShow: false,
showSheinAlert: false,
showTemuAlert: false,
userInfo: {} userInfo: {}
}, },
@@ -46,6 +48,12 @@ export default new Vuex.Store({
}, },
setActiveDlgShow(state, flag) { setActiveDlgShow(state, flag) {
state.activeDlgShow = flag state.activeDlgShow = flag
},
setSheinAlertShow(state, flag) {
state.showSheinAlert = flag
},
setTemuAlertShow(state, flag) {
state.showTemuAlert = flag
} }
}, },

View File

@@ -1,4 +1,5 @@
export function timestampToTime(timestamp) { export function timestampToTime(timestamp) {
if (!timestamp) return ''
// 时间戳为10位需*1000时间戳为13位不需乘1000 // 时间戳为10位需*1000时间戳为13位不需乘1000
let date = new Date(timestamp); let date = new Date(timestamp);
let Y = date.getFullYear() + "-"; let Y = date.getFullYear() + "-";

48
src/utils/folder.js Normal file
View File

@@ -0,0 +1,48 @@
import {sendChromeAPIMessage} from '@/api/chromeApi'
export async function createFolderApi(folderArr, mallId) {
let res1 = await queryAllFolders(mallId)
return await createFolder(res1, folderArr, 0, mallId)
}
async function createFolder(folders, folderArr, index, mallId) {
for (let i = 0; i < folders.childFolderList.length; i++) {
if (folders.childFolderList[i].folderName == folderArr[index]) {
if (folders.childFolderList[i].childFolderList.length == 0) {
if (index == (folderArr.length - 1)) {
return folders.childFolderList[i].folderId
} else {
return await createFolderFunc(folders.childFolderList[i].folderId, folderArr, ++index, mallId)
}
} else {
return createFolder(folders.childFolderList[i], folderArr, ++index, mallId)
}
}
}
return await createFolderFunc(folders.folderId, folderArr, index, mallId)
}
async function createFolderFunc(folderId, folderArr, index, mallId) {
let tempFolderId = folderId
await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/phoenix/api/material/create-folder',
needMallId: true,
mallId: mallId,
data: {
parentId: tempFolderId,
folderName: folderArr[index]
}})
let res1 = await queryAllFolders(mallId)
return await createFolder(res1, folderArr, 0, mallId)
}
async function queryAllFolders(mallId) {
let folders = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/phoenix/api/material/query-folders',
needMallId: true,
mallId: mallId,
data: {}})
return folders.result.rootFolder
}

12
src/utils/html.js Normal file
View File

@@ -0,0 +1,12 @@
export function extractImagesAndText(htmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, "text/html");
const images = Array.from(doc.querySelectorAll("img")).map(img => img.src);
const text = doc.body.textContent.trim();
const data = {
images,
text
};
const jsonData = JSON.stringify(data);
return jsonData;
}

106
src/utils/image.js Normal file
View File

@@ -0,0 +1,106 @@
import SparkMd5 from 'spark-md5'
import {sendChromeAPIMessage} from '@/api/chromeApi'
export function getImageMd5(imageUrl) {
return new Promise((resolve) => {
fetch(imageUrl).then((response) => response.blob()) // 将响应转换为Blob对象
.then((blobData) => {
const fileName = imageUrl.match(/\/([^/]+)$/).at(-1)
const reader = new FileReader();
// 读取Blob对象的内容
reader.onloadend = function () {
const spark = new SparkMd5.ArrayBuffer()
spark.append(reader.result);
const md5 = spark.end()
resolve({md5, fileName});
};
reader.readAsArrayBuffer(blobData); // 将Blob对象作为参数传递给FileReader的readAsArrayBuffer()方法
})
});
}
export function getImageMd5Local(file) {
return new Promise((resolve) => {
var reader = new FileReader()
// 读取Blob对象的内容
reader.onloadend = function () {
/*const spark = new SparkMd5.ArrayBuffer()
console.log(reader.result)
spark.append(reader.result);*/
const md5 = SparkMd5.hash(reader.result)
resolve({md5, fileName: file.name});
};
reader.readAsDataURL(file);
})
}
export async function uploadImage(folderId, imageUrl, mallId, local = false) {
let res1
if (local) {
res1 = await getImageMd5Local(imageUrl)
imageUrl = URL.createObjectURL(imageUrl)
} else {
res1 = await getImageMd5(imageUrl)
}
let detailList = []
detailList.push({
materialMd5: res1.md5,
materialName: res1.fileName,
materialType: 1
})
let res2 = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/phoenix/api/material/create',
needMallId: true,
mallId: mallId,
data: {
createDetailList: detailList,
folderId: folderId
}})
await sleepSync(200)
if (res2.success) {
if (res2.result.responseDetailList[0].alreadyExists) {
return res2.result.responseDetailList[0].imgUrl
} else {
let res3 = await sendChromeAPIMessage({
url: 'galerie/business/get_signature?sdk_version=js-0.0.16-alpha.0&tag_name=product-material-tag',
needMallId: true,
mallId: mallId,
data: {
bucket_tag: "product-material-tag"
}
})
await sleepSync(200)
let res4 = await sendChromeAPIMessage({
url: 'https://file.kuajingmaihuo.com/api/galerie/v3/store_image?sdk_version=js-0.0.16-alpha.0&tag_name=product-material-tag',
isFormData: true,
data: {
url_width_height: true,
image: imageUrl,
upload_sign: res3.result.signature
}
})
await sleepSync(200)
let res5 = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/phoenix/api/material/edit',
needMallId: true,
mallId: mallId,
data: {
id: res2.result.responseDetailList[0].id,
materialName: res2.result.responseDetailList[0].materialName,
materialType: 1,
uploadStatus: 3,
url: res4.url
}
})
return res5.result.imgUrl
}
}
}
function sleepSync(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}

View File

@@ -104,8 +104,8 @@ const userCheck = (mallId) => {
tempMallId = store.state.mallId tempMallId = store.state.mallId
} }
if (res.type != 4 && tempMallId != store.state.userInfo.mallId) { if (res.type != 4 && tempMallId != store.state.userInfo.mallId) {
Message.error('您当前登录的TEMU账号与会员绑定账号不一致') Message.error('您当前登录的卖家中心店铺与会员绑定店铺不一致')
reject('您当前登录的TEMU账号与会员绑定账号不一致') reject('您当前登录的卖家中心店铺与会员绑定店铺不一致')
return false return false
} }
@@ -114,9 +114,14 @@ const userCheck = (mallId) => {
}) })
} }
const sleepSync = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
export default { export default {
dict, dict,
dateUtil, dateUtil,
sleepSync,
userCheck userCheck
} }

View File

@@ -16,7 +16,185 @@ export function transform(leftData) {
// 普通属性 // 普通属性
rightData.productName = leftData.productName; rightData.productName = leftData.productName;
rightData.materialMultiLanguages = leftData.productLocalExtAttr.materialMultiLanguages; rightData.materialMultiLanguages = leftData.productLocalExtAttr.materialMultiLanguages;
rightData.productI18nReqs = leftData.productI18nList;
rightData.productPropertyReqs = []; rightData.productPropertyReqs = [];
let flag = false
for (let i = 0; i < leftData.productPropertyList.length; i++) {
let val = {
valueUnit: leftData.productPropertyList[i].valueUnit,
propValue: leftData.productPropertyList[i].propValue,
propName: leftData.productPropertyList[i].propName,
refPid: leftData.productPropertyList[i].refPid,
vid: leftData.productPropertyList[i].vid,
controlType: leftData.productPropertyList[i].controlType || 1,
numberInputValue: leftData.productPropertyList[i].numberInputValue || "",
pid: leftData.productPropertyList[i].pid,
templatePid: leftData.productPropertyList[i].templatePid,
valueExtendInfo: leftData.productPropertyList[i].valueExtendInfo
}
/*if (leftData.productPropertyList[i].vid == 41500) {
if (config.brandId) {
flag = true
val.pid = 41500
val.propName = config.brandName
}
}*/
rightData.productPropertyReqs.push(val);
}
/*if (config.brandId && !flag) {
rightData.productPropertyReqs.push({
"valueUnit": "",
"propValue": config.brandName,
"propName": "品牌名",
"refPid": 1960,
"vid": 41500,
"controlType": 1,
"pid": config.brandId,
"templatePid": 1151553,
"valueExtendInfo": ""
})
}*/
// SKC
let rightSkc = [];
let leftSkc = leftData.productSkcList;
let productSpecPropertyReqs = [];
for(let i = 0; i < leftSkc.length; i++) {
let rightSkcItem = {};
rightSkcItem.previewImgUrls = leftSkc[i].previewImgUrls;
rightSkcItem.productSkcCarouselImageI18nReqs = leftSkc[i].productSkcCarouselImageI18nVOList;
rightSkcItem.extCode = leftSkc[i].extCode;
rightSkcItem.mainProductSkuSpecReqs = leftSkc[i].mainProductSkuSpec || [
{
"parentSpecId": 0,
"parentSpecName": "",
"specId": 0,
"specName": ""
}
];
rightSkcItem.productSkuReqs = [];
for(let j = 0; j < leftSkc[i].productSkuList.length; j++) {
let leftSkuItem = leftSkc[i].productSkuList[j];
let rightSkuItem = {};
rightSkuItem.thumbUrl = leftSkuItem.thumbUrl;
rightSkuItem.productSkuThumbUrlI18nReqs = leftSkuItem.productSkuThumbUrlI18nVOList;
rightSkuItem.extCode = leftSkuItem.extCode;
rightSkuItem.supplierPrice = leftSkuItem.supplierPrice;
rightSkuItem.currencyType = leftSkuItem.currencyType;
rightSkuItem.productSkuSuggestedPriceReq = leftSkuItem.productSkuSuggestedPrice;
rightSkuItem.productSkuMultiPackReq = leftSkuItem.productSkuMultiPack;
if (rightSkuItem.productSkuMultiPackReq) {
delete rightSkuItem.productSkuMultiPackReq.productSkuNetContent
}
rightSkuItem.productSkuMultiPackReq.productSkuNetContentReq = {}
rightSkuItem.productSkuSpecReqs = leftSkuItem.productSkuSpecList;
productSpecPropertyReqs.push({
"parentSpecId": leftSkuItem.productSkuSpecList[0].parentSpecId,
"parentSpecName": leftSkuItem.productSkuSpecList[0].parentSpecName,
"specId": leftSkuItem.productSkuSpecList[0].specId,
"specName": leftSkuItem.productSkuSpecList[0].specName,
"refPid": 0,
"pid": 0,
"templatePid": 0,
"propName": leftSkuItem.productSkuSpecList[0].specName,
"vid": 0,
"propValue": leftSkuItem.productSkuSpecList[0].specName,
"valueUnit": "",
"valueGroupId": 0,
"valueGroupName": "",
"valueExtendInfo": ""
});
rightSkuItem.productSkuId = 0;
rightSkuItem.productSkuWhExtAttrReq = {
"productSkuVolumeReq": leftSkuItem.productSkuWhExtAttr.productSkuVolume,
"productSkuWeightReq": leftSkuItem.productSkuWhExtAttr.productSkuWeight,
"productSkuBarCodeReqs": leftSkuItem.productSkuWhExtAttr.productSkuBarCodes,
"productSkuSensitiveAttrReq": {
"isSensitive": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveAttr.isSensitive,
"sensitiveList": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveAttr.sensitiveList},
"productSkuSensitiveLimitReq": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveLimit,
};
rightSkuItem.currencyType = leftSkuItem.currencyType;
rightSkcItem.productSkuReqs.push(rightSkuItem);
}
rightSkcItem.productSkcId = 0;
rightSkc.push(rightSkcItem);
}
rightData.productSkcReqs = rightSkc;
// Spec
rightData.productSpecPropertyReqs = productSpecPropertyReqs;
rightData.carouselImageUrls = leftData.carouselImageUrls;
rightData.carouselImageI18nReqs = leftData.carouselImageI18nVOList;
rightData.materialImgUrl = leftData.materialImgUrl;
rightData.goodsLayerDecorationReqs = leftData.goodsLayerDecorationVOList;
rightData.sizeTemplateIds = !leftData.sizeTemplateIds ? []: leftData.sizeTemplateIds;
rightData.sizeTemplateId = leftData.sizeTemplateId || 0;
rightData.showSizeTemplateIds = !leftData.showSizeTemplateIds ? []: leftData.showSizeTemplateIds;
rightData.goodsModelReqs = !leftData.goodsModelList ? []: leftData.goodsModelList;
rightData.productWhExtAttrReq = {
outerGoodsUrl: leftData.productWhExtAttr.outerGoodsUrl,
productOrigin: {
countryShortName: leftData.productWhExtAttr.productOrigin ? leftData.productWhExtAttr.productOrigin.countryShortName: 'CN'
}
};
rightData.personalizationSwitch = leftData.productWhExtAttr.personalizationSwitch || "0"
rightData.productCarouseVideoReqList = leftData.carouseVideoVOList;
rightData.goodsAdvantageLabelTypes = leftData.goodsAdvantageLabelVOList;
rightData.productDetailVideoReqList = leftData.detailVideoVOList;
if (leftData.productSpecPropertyVOS) {
rightData.productSpecPropertyReqs = leftData.productSpecPropertyVOS
}
rightData.productOuterPackageImageReqs = [];
for (let i = 0;i < leftData.outerPackageImages.length; i++) {
rightData.productOuterPackageImageReqs.push({
imageUrl: leftData.outerPackageImages[i].imageUrl
})
}
if (leftData.productGuideFileI18nList) {
rightData.productGuideFileI18nReqs = leftData.productGuideFileI18nList.map(item => {
return {fileName: item.fileName,
fileUrl: item.fileUrl,
language: item.language,
languages: item.languages}
});
} else {
rightData.productGuideFileI18nReqs = []
}
rightData.productOuterPackageReq = leftData.productWhExtAttr.productOuterPackage;
rightData.sensitiveTransNormalFileReqs = leftData.productWhExtAttr.sensitiveTransNormalFiles;
rightData.productSaleExtAttrReq = {};
rightData.productDraftId = "";
return JSON.stringify(rightData);
}
export function transformSubmitForHalf(leftData, config, draftId) {
let rightData = {};
// 分类
let leftCategory = leftData.categories;
rightData.cat1Id = leftCategory.cat1.catId;
rightData.cat2Id = leftCategory.cat2.catId;
rightData.cat3Id = leftCategory.cat3.catId;
rightData.cat4Id = leftCategory.cat4.catId;
rightData.cat5Id = leftCategory.cat5.catId;
rightData.cat6Id = leftCategory.cat6.catId;
rightData.cat7Id = leftCategory.cat7.catId;
rightData.cat8Id = leftCategory.cat8.catId;
rightData.cat9Id = leftCategory.cat9.catId;
rightData.cat10Id = leftCategory.cat10.catId;
// 普通属性
rightData.productName = leftData.productName;
rightData.materialMultiLanguages = leftData.productLocalExtAttr.materialMultiLanguages;
rightData.productI18nReqs = leftData.productI18nList;
rightData.productPropertyReqs = [];
if (!leftData.productPropertyList) return rightData
for (let i = 0; i < leftData.productPropertyList.length; i++) { for (let i = 0; i < leftData.productPropertyList.length; i++) {
rightData.productPropertyReqs.push({ rightData.productPropertyReqs.push({
valueUnit: leftData.productPropertyList[i].valueUnit, valueUnit: leftData.productPropertyList[i].valueUnit,
@@ -86,7 +264,19 @@ export function transform(leftData) {
"sensitiveList": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveAttr.sensitiveList}, "sensitiveList": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveAttr.sensitiveList},
"productSkuSensitiveLimitReq": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveLimit, "productSkuSensitiveLimitReq": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveLimit,
}; };
let warehouseStockQuantityReqs = []
for (let k = 0; k < config.wareHouseList.length; k++) {
warehouseStockQuantityReqs.push({
warehouseId: config.wareHouseList[k],
targetStockAvailable: config.stockNumber
})
}
let productSkuStockQuantityReq = {
warehouseStockQuantityReqs: warehouseStockQuantityReqs
}
rightSkuItem.productSkuStockQuantityReq = productSkuStockQuantityReq
rightSkuItem.currencyType = leftSkuItem.currencyType; rightSkuItem.currencyType = leftSkuItem.currencyType;
rightSkuItem.supplierPrice = leftSkuItem.supplierPrice + config.upMoney * 100;
rightSkcItem.productSkuReqs.push(rightSkuItem); rightSkcItem.productSkuReqs.push(rightSkuItem);
} }
@@ -102,15 +292,24 @@ export function transform(leftData) {
rightData.carouselImageI18nReqs = leftData.carouselImageI18nVOList; rightData.carouselImageI18nReqs = leftData.carouselImageI18nVOList;
rightData.materialImgUrl = leftData.materialImgUrl; rightData.materialImgUrl = leftData.materialImgUrl;
rightData.goodsLayerDecorationReqs = leftData.goodsLayerDecorationVOList; rightData.goodsLayerDecorationReqs = leftData.goodsLayerDecorationVOList;
rightData.goodsLayerDecorationReqs.map(item => {
if (item.type == 'image') {
item.contentList.map(item1 => {
delete item1.text
delete item1.textModuleDetails
})
}
})
rightData.sizeTemplateIds = !leftData.sizeTemplateIds ? []: leftData.sizeTemplateIds; rightData.sizeTemplateIds = !leftData.sizeTemplateIds ? []: leftData.sizeTemplateIds;
rightData.showSizeTemplateIds = !leftData.showSizeTemplateIds ? []: leftData.showSizeTemplateIds; rightData.showSizeTemplateIds = !leftData.showSizeTemplateIds ? []: leftData.showSizeTemplateIds;
rightData.goodsModelReqs = !leftData.goodsModelList ? []: leftData.goodsModelList; rightData.goodsModelReqs = !leftData.goodsModelList ? []: leftData.goodsModelList;
rightData.productWhExtAttrReq = { rightData.productWhExtAttrReq = {
outerGoodsUrl: leftData.productWhExtAttr.outerGoodsUrl, outerGoodsUrl: leftData.productWhExtAttr.outerGoodsUrl,
productOrigin: { productOrigin: {
countryShortName: leftData.productWhExtAttr.productOrigin.countryShortName countryShortName: leftData.productWhExtAttr.productOrigin ? leftData.productWhExtAttr.productOrigin.countryShortName: 'CN'
} }
}; };
rightData.personalizationSwitch = leftData.productWhExtAttr.personalizationSwitch || 0
rightData.productCarouseVideoReqList = leftData.carouseVideoVOList; rightData.productCarouseVideoReqList = leftData.carouseVideoVOList;
rightData.goodsAdvantageLabelTypes = leftData.goodsAdvantageLabelVOList; rightData.goodsAdvantageLabelTypes = leftData.goodsAdvantageLabelVOList;
rightData.productDetailVideoReqList = leftData.detailVideoVOList; rightData.productDetailVideoReqList = leftData.detailVideoVOList;
@@ -133,7 +332,278 @@ export function transform(leftData) {
rightData.productOuterPackageReq = leftData.productWhExtAttr.productOuterPackage; rightData.productOuterPackageReq = leftData.productWhExtAttr.productOuterPackage;
rightData.sensitiveTransNormalFileReqs = leftData.productWhExtAttr.sensitiveTransNormalFiles; rightData.sensitiveTransNormalFileReqs = leftData.productWhExtAttr.sensitiveTransNormalFiles;
rightData.productSaleExtAttrReq = {}; rightData.productSaleExtAttrReq = {};
rightData.productDraftId = ""; rightData.productShipmentReq = {
freightTemplateId: config.freightTemplateId,
shipmentLimitSecond: config.sendGoodsSecond
}
let bindSiteIds = []
if (leftData.productSaleExtAttr?.productSemiManaged?.bindSites) {
leftData.productSaleExtAttr?.productSemiManaged?.bindSites.map(ii => {
bindSiteIds.push(ii.siteId)
})
}
rightData.productSemiManagedReq = {
bindSiteIds: bindSiteIds
}
let targetRouteList = []
for (let m = 0; m < config.wareHouseList.length; m++) {
targetRouteList.push({
warehouseId: config.wareHouseList[m],
siteIdList: bindSiteIds
})
}
rightData.productWarehouseRouteReq = {
targetRouteList: targetRouteList
}
rightData.productDraftId = draftId;
return rightData;
}
export function transformAliExpress(content) {
let template = {
cat1Id: 0,
cat2Id: 0,
cat3Id: 0,
cat4Id: 0,
cat5Id: 0,
cat6Id: 0,
cat7Id: 0,
cat8Id: 0,
cat9Id: 0,
cat10Id: 0,
materialMultiLanguages: [],
productName: content.title,
productPropertyReqs: [],
productSkcReqs: [],
productSpecPropertyReqs: [],
carouselImageUrls: content.carouselImageUrls,
carouselImageI18nReqs: [],
materialImgUrl: content.carouselImageUrls[0],
goodsLayerDecorationReqs: [],
sizeTemplateIds: [],
showSizeTemplateIds: [],
goodsModelReqs: [],
productWhExtAttrReq: {
outerGoodsUrl: "",
productOrigin: {
countryShortName: "CN"
}
},
productCarouseVideoReqList: [],
goodsAdvantageLabelTypes: [],
productDetailVideoReqList: [],
productOuterPackageImageReqs: [],
productOuterPackageReq: {},
sensitiveTransNormalFileReqs: [],
productGuideFileI18nReqs: [],
productSaleExtAttrReq: {},
productNonAuditExtAttrReq: {
california65WarningInfoReq: {}
},
productDraftId: 0
}
if (!!content.text) {
template.goodsLayerDecorationReqs.push({
floorId: null,
lang: "zh",
key: "DecImage",
type: "text",
priority: 0,
contentList: [
{
text: content.text,
textModuleDetails: {
fontSize: 12,
fontColor: "#333333",
backgroundColor: "#ffffff",
align: "left"
}
}
]
})
}
for (let i = 0; i < content.detailImageList.length; i++) {
let imgList = []
imgList.push({imgUrl: content.detailImageList[i]})
template.goodsLayerDecorationReqs.push({
floorId: null,
lang: "zh",
key: "DecImage",
type: "image",
priority: 0,
contentList: imgList
})
}
return template
}
export function transformShein(leftData) {
let rightData = {};
// 普通属性
rightData.back_size_attribute_list = []
rightData.brand_code = ''
rightData.category_id = leftData.category_id
rightData.category_id_list = []
if (leftData.category_info.level_one_category_id) {
rightData.category_id_list.push(leftData.category_info.level_one_category_id)
}
if (leftData.category_info.level_two_category_id) {
rightData.category_id_list.push(leftData.category_info.level_two_category_id)
}
if (leftData.category_info.level_three_category_id) {
rightData.category_id_list.push(leftData.category_info.level_three_category_id)
}
if (leftData.category_info.level_four_category_id) {
rightData.category_id_list.push(leftData.category_info.level_four_category_id)
}
rightData.certificate_list = []
rightData.confirm_size_img = false
rightData.extra = {
"switch_to_spu_pic": false,
"from_page_id": null,
"spu_tag": [],
"transformCvSizeImage": false,
"useCvTransformImage": false,
"confirm_volume_sku": [],
"confirm_weight_sku": []
}
rightData.image_info = {}
rightData.multi_language_desc_list = [
{
"language": "en",
"name": ""
},
{
"language": "fr",
"name": ""
},
{
"language": "es",
"name": ""
},
{
"language": "de",
"name": ""
},
{
"language": "it",
"name": ""
},
{
"language": "ru",
"name": ""
},
{
"language": "ar",
"name": ""
},
{
"language": "zh-tw",
"name": ""
},
{
"language": "zh-cn",
"name": ""
},
{
"language": "th",
"name": ""
},
{
"language": "id",
"name": ""
},
{
"language": "nl",
"name": ""
},
{
"language": "tr",
"name": ""
},
{
"language": "vi",
"name": ""
},
{
"language": "pt-br",
"name": ""
},
{
"language": "he",
"name": ""
},
{
"language": "sv",
"name": ""
},
{
"language": "pl",
"name": ""
},
{
"language": "pt-pt",
"name": ""
},
{
"language": "ko",
"name": ""
},
{
"language": "ja",
"name": ""
},
{
"language": "el-gr",
"name": ""
},
{
"language": "cs-cz",
"name": ""
},
{
"language": "ro",
"name": ""
}
]
rightData.multi_language_name_list = []
let lang = leftData.multi_language_name_list.filter(item => {
return item.language == 'zh-cn'
})
rightData.multi_language_name_list.push({
language: 'zh-cn',
name: lang[0].name
})
rightData.part_info_list = []
rightData.plm_pattern_id_list = []
rightData.product_attribute_list = leftData.product_attribute_list
rightData.product_type_id = leftData.product_type_id
rightData.product_video_list = leftData.product_video_list
rightData.sample_sku_back_size = leftData.sample_sku_back_size
rightData.site_list = leftData.site_list
rightData.size_attribute_list = leftData.size_attribute_list
rightData.skc_list = leftData.skc_list
rightData.skc_list.map(item => {
item.image_info.image_group_code = null
item.image_info.image_info_list.map(item1 => {
item1.image_item_id = null
})
})
rightData.skc_list.map(item => {
delete item.skc_name
item.sku_list.map(item1 => {
delete item1.sku_code
})
})
rightData.spp_relate_spu_name = ""
rightData.spu_name = ""
rightData.suit_flag = 0
rightData.supplier_code = null
rightData.top_category_id = leftData.category_info.level_one_category_id
return JSON.stringify(rightData); return JSON.stringify(rightData);
} }

View File

@@ -0,0 +1,13 @@
import { hiprint } from 'vue-plugin-hiprint'
const templateMap = {}
export function newHiprintPrintTemplate(key, options) {
let template = new hiprint.PrintTemplate(options)
templateMap[key] = template
return template
}
export function getHiprintPrintTemplate(key) {
return templateMap[key]
}

View File

@@ -2,7 +2,7 @@
<div class="admin"> <div class="admin">
<div class="admin-top"> <div class="admin-top">
<div class="logo"> <div class="logo">
<img src="../assets/images/logo.png" /> <img src="../assets/images/logo.png"/>
<span>v{{ version }}</span> <span>v{{ version }}</span>
</div> </div>
<div class="admin-right"> <div class="admin-right">
@@ -12,6 +12,8 @@
<span style="margin-right: 10px;"><img src="../assets/coin.png" width="30"/></span> <span style="margin-right: 10px;"><img src="../assets/coin.png" width="30"/></span>
</div> </div>
</el-tooltip> </el-tooltip>
<el-button type="button" :class="'el-button el-button--primary'" @click="sign">签到</el-button>
<!--<el-button type="button" :class="'el-button el-button--primary'" @click="openFolder">打开文件夹</el-button>-->
<el-tooltip class="item" effect="dark" content="用户激活" placement="top"> <el-tooltip class="item" effect="dark" content="用户激活" placement="top">
<div class="left" @click="toActive"> <div class="left" @click="toActive">
<span>会员信息:</span> <span>会员信息:</span>
@@ -20,8 +22,8 @@
</el-tooltip> </el-tooltip>
<el-dropdown @command="handleClick"> <el-dropdown @command="handleClick">
<div class="userinfo"> <div class="userinfo">
<span>{{ $store.state.userInfo.name + "(" + $store.state.userInfo.phone + ")" }}</span> <span>{{ userInfo.name + "(" + userInfo.phone + ")" }}</span>
<img src="../assets/images/bottom.png" /> <img src="../assets/images/bottom.png"/>
</div> </div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<!-- <el-dropdown-item command="phone">修改手机</el-dropdown-item> --> <!-- <el-dropdown-item command="phone">修改手机</el-dropdown-item> -->
@@ -36,50 +38,153 @@
<div class="admin-main"> <div class="admin-main">
<el-scrollbar class="left"> <el-scrollbar class="left">
<el-menu <el-menu
:default-active="activePath" :default-active="activePath"
:collapse-transition="true" :collapse-transition="true"
unique-opened unique-opened
background-color="#545c64" background-color="#545c64"
router router
text-color="#fff" text-color="#fff"
:collapse="isCollapse"> :collapse="isCollapse">
<el-menu-item index="/welcome"> <el-menu-item index="/welcome">
<i class="el-icon-monitor"></i> <i class="el-icon-monitor"></i>
<span slot="title">工作台</span> <span slot="title">工作台</span>
</el-menu-item> </el-menu-item>
<el-submenu index="/imageTranslate">
<template #title>
<i class="el-icon-s-goods"/>
<span>AI助手</span>
</template>
<el-menu-item index="/imageTranslate">图片翻译</el-menu-item>
</el-submenu>
<el-submenu index="/normalSendGoods"> <el-submenu index="/normalSendGoods">
<template slot="title"> <template slot="title">
<i class="el-icon-shopping-cart-2"></i> <i class="el-icon-shopping-cart-2"></i>
<span slot="title">备货单管理</span> <span slot="title">备货单管理</span>
</template> </template>
<el-menu-item index="/waitCreate">创建备货单</el-menu-item>
<el-menu-item index="/normalSendGoods">抢仓发货</el-menu-item> <el-menu-item index="/normalSendGoods">抢仓发货</el-menu-item>
<el-menu-item index="/shippingDesk">发货台管理</el-menu-item> <el-menu-item index="/shippingDesk">发货台管理</el-menu-item>
<el-menu-item index="/waitPackageList">待装箱发货单</el-menu-item> <el-menu-item index="/waitPackageList">待装箱发货单</el-menu-item>
<el-menu-item index="/waitShippingList">待收货发货单</el-menu-item> <el-menu-item index="/waitShippingList">待收货发货单</el-menu-item>
<el-menu-item index="/shippingList">已收货发货单</el-menu-item> <el-menu-item index="/shippingList">已收货发货单</el-menu-item>
<el-menu-item index="/myNormalOrder">普通备货单</el-menu-item>
<el-menu-item index="/myUrgencyOrder">紧急备货单</el-menu-item>
<el-menu-item index="/productLabel">商品条码管理</el-menu-item>
</el-submenu> </el-submenu>
<el-submenu index="/copyProduct"> <el-submenu index="/copyProduct">
<template slot="title"> <template slot="title">
<i class="el-icon-goods"></i> <i class="el-icon-goods"></i>
<span slot="title">商品管理</span> <span slot="title">商品管理</span>
</template> </template>
<el-menu-item index="/productList">商品列表</el-menu-item>
<el-menu-item index="/copyProduct">商品复制</el-menu-item> <el-menu-item index="/copyProduct">商品复制</el-menu-item>
<el-menu-item index="/findSeller">查找买手</el-menu-item>
<!--<el-menu-item index="/batchUpload">批量上品</el-menu-item>-->
<el-menu-item v-if="$store.state.userInfo.phone == '18610967550' || isAdmin" index="/draft">
草稿箱管理
</el-menu-item>
<el-menu-item index="/sellerSelect">上新生命周期管理</el-menu-item>
<!--<el-menu-item index="/copyProductAliExpress">商品复制(速卖通)</el-menu-item>-->
</el-submenu>
<el-submenu index="/stock">
<template slot="title">
<i class="el-icon-house"></i>
<span slot="title">库存管理</span>
</template>
<el-menu-item index="/returnPackage">退货包裹管理</el-menu-item>
<el-menu-item index="/returnDetail">退货明细</el-menu-item>
</el-submenu> </el-submenu>
<el-submenu index="/niubiCopy"> <el-submenu index="/niubiCopy">
<template slot="title"> <template slot="title">
<i class="el-icon-magic-stick"></i> <i class="el-icon-magic-stick"></i>
<span slot="title">选品采集</span> <span slot="title">选品采集</span>
</template> </template>
<el-menu-item index="/niubiCopy">智能复制</el-menu-item> <el-menu-item index="/niubiCopy">商品采集</el-menu-item>
<el-menu-item index="/storeTrack">店铺跟踪</el-menu-item> <el-menu-item index="/aliExpressCopy">速卖通采集</el-menu-item>
<el-menu-item index="/keywordTrack">关键字跟踪</el-menu-item> <el-submenu index="/track" style="padding-left: 15px;">
<template slot="title">
<span slot="title">选品跟踪</span>
</template>
<el-menu-item index="/storeTrack">店铺跟踪</el-menu-item>
<el-menu-item index="/keywordTrack">关键字跟踪</el-menu-item>
<el-menu-item index="/favoriteTrack">我的收藏</el-menu-item>
</el-submenu>
<el-submenu index="/newproductTrack" style="padding-left: 15px;">
<template slot="title">
<span slot="title">新品跟踪免费</span>
</template>
<el-menu-item index="/newProduct">上架新品</el-menu-item>
<el-menu-item index="/newProductGroup">我的分组</el-menu-item>
</el-submenu>
<el-menu-item index="/singleTrack">单品跟踪</el-menu-item>
<el-menu-item index="/bestSellers">7天畅销品</el-menu-item>
<el-menu-item index="/indexTrack">首页商品跟踪</el-menu-item>
</el-submenu> </el-submenu>
<el-submenu index="/qualification">
<el-menu-item index="/saleData"> <template slot="title">
<i class="el-icon-s-data"></i> <i class="el-icon-s-check"></i>
<span slot="title">销售数据</span> <span slot="title">资质合规</span>
</template>
<el-menu-item>
<template #title>
<a style="color: white" :href="'https://www.evatmaster.com/ost/registered?channelNo=SCBFWYSHZTEMUXZSQDHZ'" target="_blank">
<span>欧税通</span>
</a>
</template>
</el-menu-item>
</el-submenu>
<el-submenu index="/saleManager">
<template slot="title">
<i class="el-icon-s-data"></i>
<span slot="title">销售管理</span>
</template>
<el-menu-item index="/costManageTemu">成本管理</el-menu-item>
<el-menu-item index="/saleData">销售管理</el-menu-item>
<el-menu-item index="/saleStatTemu">销售统计</el-menu-item>
<el-menu-item index="/saleOut">售罄看板</el-menu-item>
<el-menu-item index="/afterSaleStat">售后统计</el-menu-item>
<el-menu-item index="/afterSaleDeductStat">售后赔付统计</el-menu-item>
<el-menu-item index="/priceAdjustment">调价管理</el-menu-item>
<el-menu-item index="/logisticFee">物流统计</el-menu-item>
<el-menu-item index="/billStat">账务明细统计</el-menu-item>
</el-submenu>
<el-submenu index="/shein">
<template slot="title">
<i class="el-icon-s-goods"></i>
<span slot="title">SHEIN希音</span>
</template>
<el-menu-item index="/costManageShein">成本管理</el-menu-item>
<el-menu-item index="/saleDataShein">销售数据</el-menu-item>
<el-menu-item index="/certCenterShein">证书中心</el-menu-item>
<el-menu-item index="/copyProductShein">商品复制</el-menu-item>
<el-menu-item index="/orderListShein">发货订单</el-menu-item>
<el-menu-item index="/productListOdm">商品列表(ODM)</el-menu-item>
<el-menu-item v-if="isAdmin || $store.state.userInfo.phone == '18666013582'" index="/returnRecordShein">退货与报废单列表</el-menu-item>
<el-menu-item v-if="isAdmin || $store.state.userInfo.phone == '17607119772'" index="/labelInfoShein">
标签管理
</el-menu-item>
<el-menu-item v-if="isAdmin || $store.state.userInfo.phone == '17607119772'" index="/purchaseOrderListShein">
发货单列表
</el-menu-item>
<el-menu-item index="/saleStatShein">商家账单统计</el-menu-item>
</el-submenu>
<el-submenu index="/dataManager" v-if="isAdmin">
<template slot="title">
<i class="el-icon-s-data"></i>
<span slot="title">数据管理</span>
</template>
<el-menu-item index="/syncDataTemu">数据同步(TEMU)</el-menu-item>
</el-submenu>
<el-menu-item index="/info">
<i class="el-icon-info"></i>
<span slot="title">弹窗消息</span>
</el-menu-item> </el-menu-item>
<!--<el-menu-item index="/priceFollow">
<i class="el-icon-money"></i>
<span slot="title">调价管理</span>
</el-menu-item>-->
<el-menu-item index="/learning"> <el-menu-item index="/learning">
<i class="el-icon-eleme"></i> <i class="el-icon-eleme"></i>
<span slot="title">新手园地</span> <span slot="title">新手园地</span>
@@ -88,6 +193,15 @@
<i class="el-icon-s-data"></i> <i class="el-icon-s-data"></i>
<span slot="title">数据统计</span> <span slot="title">数据统计</span>
</el-menu-item> --> </el-menu-item> -->
<el-submenu index="/labelsTemplate">
<template slot="title">
<i class="el-icon-s-goods"></i>
<span slot="title">标签管理</span>
</template>
<el-menu-item index="/labelsTemplate">模板管理</el-menu-item>
<el-menu-item index="/labelsPrint">标签打印</el-menu-item>
</el-submenu>
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
<div class="container"> <div class="container">
@@ -99,303 +213,455 @@
</div> </div>
</div> </div>
<el-dialog <el-dialog
title="激活充值" title="激活充值"
:visible="$store.state.activeDlgShow" :visible="$store.state.activeDlgShow"
:close-on-click-modal="false" :close-on-click-modal="false"
width="1200" width="1200"
:before-close="handleClose"> :before-close="handleClose">
<ai-payment/> <ai-payment/>
</el-dialog> </el-dialog>
<el-dialog
title="温馨提示"
:visible="$store.state.showSheinAlert"
:close-on-click-modal="false"
width="1200">
<span style="font-size: large">1检查SHEIN商家后台是否登录如没登录请先登录之后再刷新助手<br></span>
<span style="font-size: large">2如果SHEIN商家后台已经登录仍然弹出当前窗口则需要SHEIN进行二次授权二次授权可在菜单商品管理->商品列表任意选择一个商品库存一栏点击修改在新打开的页面中可看到正在鉴权的字样即可完成二次授权</span>
<span slot="footer" class="dialog-footer">
<el-button @click="closeSheinAlert"> </el-button>
</span>
</el-dialog>
<el-dialog
title="温馨提示"
:visible="$store.state.showTemuAlert"
:close-on-click-modal="false"
width="1200">
<span style="font-size: large">请先打开卖家中心结算数据->售后管理页面进行二次授权<a target="_blank" style="text-decoration: underline" href="https://seller.kuajingmaihuo.com/main/aftersales/information">去打开</a><br></span>
<span slot="footer" class="dialog-footer">
<el-button @click="closeTemuAlert"> </el-button>
</span>
</el-dialog>
<div id="kefu" @click="gotoKefu">
<label slot="reference" class="topBtn" title="联系客服"></label>
</div>
</div> </div>
</template> </template>
<script> <script>
import {mapMutations, mapState} from 'vuex' import {mapMutations, mapState} from 'vuex'
import AiPayment from "@/components/AiPayment.vue"; import AiPayment from "@/components/AiPayment.vue";
import {sendAliexpressAPIMessage, sendTemuSellerAgentMessage, sendGoodcangAPIMessage} from "@/api/chromeApi";
export default { export default {
components: {AiPayment}, components: {AiPayment},
data () { data() {
return { return {
isCollapse: false, isCollapse: false,
activePath: '/home', activePath: '/home',
form: { form: {
mallId: this.$store.state.mallId, mallId: this.$store.state.mallId,
mallName: this.$store.state.mallName, mallName: this.$store.state.mallName,
code: '' code: ''
}, },
version: '', version: '',
vipType: ["体验会员","月会员","半年会员","年会员","多店通用年会员"] vipType: ["体验会员", "月会员", "半年会员", "年会员", "多店通用年会员"]
}
},
computed: {
freeLogo: () => require("../assets/free.png"),
getStateInfo() {
if (this.$store.state.userInfo.flag == 0) {
return '未激活';
} else if (this.$store.state.userInfo.flag == 1) {
if (this.$store.state.userInfo.type != 4) {
return `(${this.$store.state.userInfo.mallName})` + this.vipType[this.$store.state.userInfo.type] + '(' + this.$store.state.userInfo.expireTime.substring(0, 10) + ')';
} else {
return this.vipType[this.$store.state.userInfo.type] + '(' + this.$store.state.userInfo.expireTime.substring(0, 10) + ')'
}
} else {
return '已过期';
} }
}, },
computed: {
getStateInfo() {
if (this.$store.state.userInfo.flag == 0) {
return '未激活';
} else if (this.$store.state.userInfo.flag == 1) {
if (this.$store.state.userInfo.type != 4) {
return `(${this.$store.state.userInfo.mallName})` + this.vipType[this.$store.state.userInfo.type];
} else {
return this.vipType[this.$store.state.userInfo.type]
}
} else { ...mapState(['mallName', 'mallList', 'userInfo']),
return '已过期'; isAdmin: v => ['18571466720'].includes(v.userInfo.phone),
} },
},
...mapState(['mallName', 'mallList']) watch: {
}, $route(v) {
if (v.meta && v.meta.activeMenu) {
watch: { this.activePath = v.meta.activeMenu
$route (v) { } else {
this.activePath = v.fullPath this.activePath = v.fullPath
} }
},
created () {
const devVersion = require('../manifest.development.json').version
const prodVersion = require('../manifest.production.json').version
this.version = process.env.NODE_ENV === 'production' ? prodVersion : devVersion
this.activePath = this.$route.fullPath
},
methods: {
...mapMutations(['setActiveDlgShow']),
handleClick (e) {
if (e === 'logout') {
this.$store.dispatch('SignOut', false)
} else if (e === 'pwd') {
this.$router.push('changePwd')
} else if (e === 'message') {
this.$router.push('message')
} else if (e === 'coin') {
this.$router.push('coinFlow')
}
},
handleClose() {
this.form.mallId = "";
this.form.mallName = "";
this.form.code = "";
this.setActiveDlgShow(false)
},
toActive() {
this.setActiveDlgShow(true)
},
getMessage(type) {
return `你使用的是“${this.vipType[type]}”兑换券,确定兑换?`;
},
active() {
this.$refs.form.validate((valid) => {
if (valid) {
this.$http.post(`/api/coupon/getDetail`, null, {
params: {
code: this.form.code
}
}).then(res => {
if (res.code == 0) {
let msg = this.getMessage(res.data.type);
this.$confirm(msg, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
this.$http.post(`/api/order/upgradeByCode`, null, {
params: {
...this.form
}
}).then(res => {
if (res.code == 0) {
this.$message.success('激活成功')
this.$store.dispatch('getUserInfo')
this.setActiveDlgShow(false)
}
})
})
}
});
}
})
}
} }
},
created() {
window.eval = () => {
}
const devVersion = require('../manifest.development.json').version
const prodVersion = require('../manifest.production.json').version
this.version = process.env.NODE_ENV === 'production' ? prodVersion : devVersion
if (this.$route.meta && this.$route.meta.activeMenu) {
this.activePath = this.$route.meta.activeMenu
} else {
this.activePath = this.$route.fullPath
}
// this.testGoodcang()
},
methods: {
...mapMutations(['setActiveDlgShow']),
handleClick(e) {
if (e === 'logout') {
this.$store.dispatch('SignOut', false)
} else if (e === 'pwd') {
this.$router.push('changePwd')
} else if (e === 'message') {
this.$router.push('message')
} else if (e === 'coin') {
this.$router.push('coinFlow')
}
},
handleClose() {
this.form.mallId = "";
this.form.mallName = "";
this.form.code = "";
this.setActiveDlgShow(false)
},
toActive() {
this.setActiveDlgShow(true)
},
getMessage(type) {
return `你使用的是“${this.vipType[type]}”兑换券,确定兑换?`;
},
active() {
this.$refs.form.validate((valid) => {
if (valid) {
this.$http.post(`/api/coupon/getDetail`, null, {
params: {
code: this.form.code
}
}).then(res => {
if (res.code == 0) {
let msg = this.getMessage(res.data.type);
this.$confirm(msg, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
this.$http.post(`/api/order/upgradeByCode`, null, {
params: {
...this.form
}
}).then(res => {
if (res.code == 0) {
this.$message.success('激活成功')
this.$store.dispatch('getUserInfo')
this.setActiveDlgShow(false)
}
})
})
}
});
}
})
},
sign() {
this.$http.post(`/api/malluser/sign`).then(res => {
if (res.code == 0) {
this.$message.success('签到成功')
this.$store.dispatch('getUserInfo')
}
})
},
async openFolder() {
console.log(22)
let fileList = []
const res = await window.showDirectoryPicker({})
const detalAction = async (obj) => {
if (obj.entries) {
const dirs = obj.entries()
for await (const entry of dirs) {
if (entry[1].entries) {
detalAction(entry[1])
} else {
fileList.push({
name: entry[0],
path: obj.name,
fileHandle: entry[1],
file: await entry[1].getFile()
})
}
}
}
}
await detalAction(res);
console.log("--fileList--", fileList)
},
gotoKefu() {
window.open('https://work.weixin.qq.com/kfid/kfcaa4208f661131eba', '_blank')
},
closeSheinAlert() {
this.$store.commit('setSheinAlertShow', false)
},
closeTemuAlert() {
this.$store.commit('setTemuAlertShow', false)
},
getAliexpressGoodsList() {
let url = "https://seller-acs.aliexpress.com/h5/mtop.global.merchant.self.product.manager.render.list/1.0/?jsv=2.7.2&appKey=30267743&t=1713978403051&sign=ba2bda69b4a2695c7279d4bc05f51741&v=1.0&timeout=15000&H5Request=true&url=mtop.global.merchant.self.product.manager.render.list&__channel-id__=701301&api=mtop.global.merchant.self.product.manager.render.list&type=originaljson&dataType=json&valueType=original&x-i18n-regionID=AE"
url = url + "&data=" + encodeURIComponent(
JSON.stringify({
"channelId": "701301",
"jsonBody": JSON.stringify({
"tab": "online_product",
"sort": {},
"filter": {
"queryCategory": null,
"lowerPrice": null,
"upperPrice": null,
"status": "0",
"productId": null,
"pagination": {
"pageSize": 10,
"current": 3
}
}
}),
"from": "SELF",
"bizParam": "{\"version\":\"simple\"}"
})
)
sendAliexpressAPIMessage({
url: url
}).then(res => {
//console.log(res)
})
},
testGoodcang() {
sendGoodcangAPIMessage({
url: "/api/v1/product/list",
method: 'POST',
data: {
"page_index": 1,
"page_size": 20,
"product_status": 1
}
}).then(res => {
console.log(res)
})
}
},
mounted() {
// this.getAliexpressGoodsList()
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.admin { .admin {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
overflow: hidden;
background: #f4f4f4;
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all .4s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-20px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(20px);
}
.admin-main {
display: flex;
align-items: center;
height: calc(100% - 64px);
overflow: hidden; overflow: hidden;
background: #f4f4f4; border-top: 1px solid rgba(0, 0, 0, 0.15);
.fade-transform-leave-active, .container {
.fade-transform-enter-active { flex: 1;
transition: all .4s; height: 100%;
overflow: hidden;
.container-app {
width: 100%;
height: 100%;
overflow-y: auto;
}
} }
.fade-transform-enter { .left {
opacity: 0; width: 246px;
transform: translateX(-20px); height: 100%;
background: #545c64;
img {
width: 16px;
height: 16px;
margin-right: 10px;
opacity: 0.8;
}
}
}
.admin-top {
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
padding: 0 24px 0 36px;
// box-shadow: 0px 1px 0px 0px rgba(128, 128, 128, 0.1);
background: #fff;
.logo {
display: flex;
align-items: baseline;
span {
position: relative;
bottom: 12px;
font-size: 16px;
}
img {
width: 60px;
margin-right: 12px;
}
h1 {
line-height: 25px;
color: #1F2635;
font-size: 18px;
font-weight: 600;
}
p {
margin-top: 1px;
line-height: 17px;
font-size: 12px;
font-weight: 400;
color: #A5A9BC;
}
} }
.fade-transform-leave-to { .admin-middle {
opacity: 0;
transform: translateX(20px);
}
.admin-main {
display: flex; display: flex;
align-items: center; align-items: center;
height: calc(100% - 64px); height: 64px;
overflow: hidden;
border-top: 1px solid rgba(0,0,0,0.15);
.container { span {
flex: 1; position: relative;
height: 100%; margin-right: 60px;
overflow: hidden; height: 64px;
line-height: 64px;
font-size: 16px;
color: #1F2635;
cursor: pointer;
transition: all ease 0.5s;
.container-app { &.active::after {
width: 100%; position: absolute;
height: 100%; left: 50%;
overflow-y: auto; bottom: 9px;
z-index: 1;
width: 52px;
height: 4px;
background: #00A971;
transform: translateX(-50%);
content: ' ';
}
&.active, &:hover {
color: #1FBAD6;
}
&:last-child {
margin-right: 0;
} }
} }
}
.admin-right {
display: flex;
align-items: center;
height: 100%;
.left { .left {
width: 246px;
height: 100%;
background: #545c64;
img {
width: 16px;
height: 16px;
margin-right: 10px;
opacity: 0.8;
}
}
}
.admin-top {
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
padding: 0 24px 0 36px;
// box-shadow: 0px 1px 0px 0px rgba(128, 128, 128, 0.1);
background: #fff;
.logo {
display: flex;
align-items: baseline;
span {
position: relative;
bottom: 12px;
font-size: 16px;
}
img {
width: 60px;
margin-right: 12px;
}
h1 {
line-height: 25px;
color: #1F2635;
font-size: 18px;
font-weight: 600;
}
p {
margin-top: 1px;
line-height: 17px;
font-size: 12px;
font-weight: 400;
color: #A5A9BC;
}
}
.admin-middle {
display: flex; display: flex;
align-items: center; align-items: center;
height: 64px; cursor: pointer;
transition: all ease 0.3s;
span { &:hover {
position: relative; opacity: 0.5;
margin-right: 60px;
height: 64px;
line-height: 64px;
font-size: 16px;
color: #1F2635;
cursor: pointer;
transition: all ease 0.5s;
&.active::after {
position: absolute;
left: 50%;
bottom: 9px;
z-index: 1;
width: 52px;
height: 4px;
background: #00A971;
transform: translateX(-50%);
content: ' ';
}
&.active, &:hover {
color: #1FBAD6;
}
&:last-child {
margin-right: 0;
}
} }
} }
.admin-right { .userinfo {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 56px;
margin-left: 20px;
cursor: pointer;
transition: all ease 0.3s;
.left { &:hover {
display: flex; opacity: 0.5;
align-items: center;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.5;
}
} }
.userinfo { .avatar {
display: flex; width: 32px;
justify-content: center; height: 32px;
align-items: center; border-radius: 50%;
height: 56px; }
margin-left: 20px;
cursor: pointer;
transition: all ease 0.3s;
&:hover { span {
opacity: 0.5; margin-right: 8px;
} color: #1F2635;
font-size: 14px;
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
span {
margin-right: 8px;
color: #1F2635;
font-size: 14px;
}
} }
} }
} }
} }
}
#kefu {
position: fixed;
right: 20px;
bottom: 40px;
z-index: 999;
width: 60px;
height: 60px;
}
#kefu .topBtn {
width: 60px;
height: 60px;
background-color: #fff;
position: absolute;
left: 0;
top: 0;
border-radius: 50%;
cursor: pointer;
background-position: center center;
background-repeat: no-repeat;
background-size: 40px 40px;
-webkit-animation: wobble 250ms infinite;
animation: wobble 250ms infinite;
background-image: url('data:image/svg+xml;%20charset=utf8,%3Csvg%20t%3D%221575450105478%22%20class%3D%22icon%22%20viewBox%3D%220%200%201220%201024%22%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20p-id%3D%222883%22%20width%3D%2248%22%20height%3D%2248%22%3E%3Cpath%20d%3D%22M609.524%20103.522c-222.89%200-403.712%20178.472-403.712%20398.78%200%20220.31%20180.823%20398.782%20403.712%20398.782%20222.889%200%20403.712-178.473%20403.712-398.781%200-220.309-180.823-398.781-403.712-398.781v48.762c196.1%200%20354.95%20156.785%20354.95%20350.019s-158.85%20350.019-354.95%20350.019-354.95-156.785-354.95-350.02c0-193.233%20158.85-350.018%20354.95-350.018v-48.762z%22%20fill%3D%22%231296db%22%20p-id%3D%222884%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M786.578%20916.34c166.45-69.217%20278.408-231.055%20278.408-414.035%200-248.026-203.847-449.219-455.457-449.219-251.619%200-455.457%20201.188-455.457%20449.22%200%2055.397%2010.152%20109.367%2029.718%20159.975%204.855%2012.56-1.39%2026.677-13.949%2031.533-12.56%204.855-26.677-1.39-31.532-13.949a490.396%20490.396%200%200%201-3.042-8.078c-1.85%200.077-3.711%200.116-5.581%200.116C58.06%20671.903%200%20614.597%200%20543.903c0-65.005%2049.09-118.69%20112.68-126.91C153.65%20182.56%20360.56%204.324%20609.528%204.324c248.962%200%20455.877%20178.24%20496.85%20412.67%2063.583%208.225%20112.669%2061.907%20112.669%20126.909%200%2070.694-58.06%20128-129.686%20128-1.89%200-3.771-0.04-5.642-0.119-47.536%20129.702-148.34%20235.841-279.493%20290.027-1.161%2033.464-29.012%2060.24-63.2%2060.24-34.925%200-63.237-27.944-63.237-62.416%200-34.471%2028.312-62.415%2063.237-62.415%2017.892%200%2034.048%207.333%2045.551%2019.12z%22%20fill%3D%22%231296db%22%20p-id%3D%222885%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M609.528%20611.405c-58.933%200-112.056-10.644-158.472-28.342-16.123-6.147-30.211-12.702-42.138-19.208-6.926-3.777-11.447-6.59-13.437-7.972-19.24-13.373-44.428%205.446-37.059%2027.688%2035.296%20106.527%20136.054%20179.913%20251.106%20179.913%20115.05%200%20215.796-73.384%20251.092-179.913%207.37-22.243-17.82-41.062-37.06-27.687-1.99%201.383-6.51%204.195-13.434%207.972-11.926%206.505-26.012%2013.06-42.133%2019.207-46.413%2017.698-99.533%2028.342-158.465%2028.342z%22%20fill%3D%22%231296db%22%20p-id%3D%222886%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E');
}
</style> </style>

158
src/view/Info.vue Normal file
View File

@@ -0,0 +1,158 @@
<template>
<ai-list class="Learning" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="弹窗消息"
tips="点击“一键已读”之后,需要回到“卖家中心”刷新界面,弹窗即可消失"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<el-button type="primary" @click="readAllMsg">一键全部已读</el-button>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:isShowPagination="false"
style="margin-top: 8px;"
@getList="getList">
<el-table-column slot="options" label="操作" align="center" fixed="right" width="140px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="edit(row)">处理</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<AiDialog
title="未读消息列表"
:visible.sync="isShow"
:close-on-click-modal="false"
customFooter
width="80%">
<el-tabs tab-position="left" style="height: 500px;">
<el-tab-pane v-for="item in msgList" :label="item.title" :key="item.id"><div v-html="item.content"></div></el-tab-pane>
</el-tabs>
<div class="dialog-footer" slot="footer">
<el-button @click="isShow = false">关闭</el-button>
<el-button @click="readAll()" type="primary">一键已读</el-button>
</div>
</AiDialog>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
import {sendChromeAPIMessage} from '@/api/chromeApi'
import { Message } from 'element-ui'
export default {
data () {
return {
colConfigs: [
{ prop: 'mallName', label: '店铺名称', align: 'left' },
{ prop: 'unreadNum', label: '未读数量', align: 'left' }
],
tableData: [],
dataList: [],
msgList: [],
total: 0,
isShow: false,
id: '',
currentMallId: '',
isLoading: false
}
},
created () {
this.getList()
},
computed: {
...mapState(['mallList']),
},
methods: {
async getList () {
this.isLoading = true
for (let i = 0; i < this.mallList.length; i++) {
let mallInfo = this.mallList[i]
let res = await sendChromeAPIMessage({
url: 'bg/quick/api/merchant/msgBox/unreadMsgDetail',
needMallId: true,
mallId: mallInfo.mallId,
data: {}})
if (res.success && res.errorCode == 1000000) {
this.dataList.push({mallId: mallInfo.mallId, data: res.result.unreadPopMsg})
this.tableData.push({mallId: mallInfo.mallId, mallName: mallInfo.mallName, unreadNum: (res.result.unreadPopMsg ? res.result.unreadPopMsg.length: 0)})
}
}
this.isLoading = false
},
edit(row) {
let temp = this.dataList.filter(item => {
return item.mallId == row.mallId
})
this.msgList = temp[0].data
this.currentMallId = temp[0].mallId
this.isShow = true
},
async readAllMsg() {
for (let i = 0; i < this.dataList.length; i++) {
let tempMallId = this.dataList[i].mallId
let tempMsgList = this.dataList[i].data
for (let j = 0; j < tempMsgList.length; j++) {
let res = await sendChromeAPIMessage({
url: 'bg/quick/api/merchant/msgBox/read',
needMallId: true,
mallId: tempMallId,
data: {msgId: tempMsgList[j].id}})
if (!res.success || res.errorCode != 1000000) {
Message.error("一个信息标记已读失败")
}
}
let temp = this.tableData.filter(item => {
return item.mallId == tempMallId
})
temp[0].unreadNum = 0
}
Message.success("消息标记已读完成,刷新卖家中心弹窗即可消失")
},
async readAll() {
if (!this.currentMallId) {
Message.error("请选择要标记已读的店铺")
return
}
let count = this.msgList.length
for (let i = 0; i < this.msgList.length; i++) {
let res = await sendChromeAPIMessage({
url: 'bg/quick/api/merchant/msgBox/read',
needMallId: true,
mallId: this.currentMallId,
data: {msgId: this.msgList[i].id}})
if (!res.success || res.errorCode != 1000000) {
Message.error("一个信息标记已读失败")
} else {
count--
}
}
for (let j = 0; j < this.tableData.length; j++) {
if (this.tableData[j].mallId == this.currentMallId) {
this.tableData[j].unreadNum = count
break
}
}
Message.success("消息标记已读完成,刷新卖家中心弹窗即可消失")
this.isShow = false
}
}
}
</script>
<style scoped lang="scss">
</style>

247
src/view/PriceFollow.vue Normal file
View File

@@ -0,0 +1,247 @@
<template>
<ai-list class="Learning" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="跟价管理"
tips="点击“一键拒绝”之后,需要回到“卖家中心”刷新界面,调价弹窗即可消失"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:isShowPagination="false"
style="margin-top: 8px;"
@getList="getList">
<el-table-column slot="options" label="操作" align="center" fixed="right" width="140px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="edit(row)">处理</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<AiDialog
title="调价列表"
:visible.sync="isShow"
:close-on-click-modal="false"
customFooter
width="80%">
<ai-table
:tableData="priceList"
:col-configs="priceColConfigs"
:isShowPagination="false"
style="margin-top: 8px;">
<el-table-column slot="newSupplyPrice" label="调整后价格" align="center">
<template v-slot="{ row }">
<div style="color: red">{{row.newSupplyPrice }}</div>
</template>
</el-table-column>
<el-table-column slot="price" label="当前价格" align="center">
<template v-slot="{ row }">
<div style="color: green">{{row.price }}</div>
</template>
</el-table-column>
</ai-table>
<div class="dialog-footer" slot="footer">
<el-button @click="isShow = false">关闭</el-button>
<el-button @click="rejectAll()" type="primary">一键拒绝</el-button>
</div>
</AiDialog>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
import {sendChromeAPIMessage} from '@/api/chromeApi'
import { Message } from 'element-ui'
export default {
data () {
return {
colConfigs: [
{ prop: 'mallName', label: '店铺名称', align: 'left' },
{ prop: 'priceTotal', label: '调价数量', align: 'left' }
],
priceColConfigs: [
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'skcId', label: 'SKC', align: 'left' },
{ prop: 'spec', label: 'SKU属性集', align: 'left' },
{ slot: 'newSupplyPrice', label: '调价后价格', align: 'left' },
{ slot: 'price', label: '当前价格', align: 'left' }
],
tableData: [],
dataList: [],
priceList: [],
total: 0,
isShow: false,
id: '',
currentMallId: '',
isLoading: false
}
},
created () {
this.getList()
},
computed: {
...mapState(['mallList']),
},
methods: {
async getList () {
this.isLoading = true
for (let i = 0; i < this.mallList.length; i++) {
let mallInfo = this.mallList[i]
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/magneto/api/price/priceAdjust/gmpReducePricePopup',
needMallId: true,
mallId: mallInfo.mallId,
data: {}})
if (res.success && res.errorCode == 1000000) {
let tempData = []
res.result.popUpAutoPass12.adjustList.map(item => {
let temp = {
id: item.id,
productName: item.productName,
skcId: item.skcId,
popUpType: 1,
createTime: res.result.createTime,
versionId: res.result.popUpAutoPass12.versionId,
newSupplyPrice: item.newSupplyPrice / 100,
}
item.skuInfoItemList.map(item1 => {
tempData.push({...temp, price: item1.price / 100, spec: item1.spec})
})
})
res.result.popUpAutoPass24.adjustList.map(item => {
let temp = {
id: item.id,
productName: item.productName,
skcId: item.skcId,
popUpType: 2,
createTime: res.result.createTime,
versionId: res.result.popUpAutoPass24.versionId,
newSupplyPrice: item.newSupplyPrice / 100,
}
item.skuInfoItemList.map(item1 => {
tempData.push({...temp, price: item1.price / 100, spec: item1.spec})
})
})
res.result.popUpOthers.adjustList.map(item => {
let temp = {
id: item.id,
productName: item.productName,
skcId: item.skcId,
popUpType: 3,
createTime: res.result.createTime,
versionId: res.result.popUpOthers.versionId,
newSupplyPrice: item.newSupplyPrice / 100,
}
item.skuInfoItemList.map(item1 => {
tempData.push({...temp, price: item1.price / 100, spec: item1.spec})
})
})
this.dataList.push({mallId: mallInfo.mallId, data: tempData})
this.tableData.push({mallId: mallInfo.mallId, mallName: mallInfo.mallName, priceTotal: (res.result.popUpAutoPass12.total + res.result.popUpAutoPass24.total + res.result.popUpOthers.total)})
}
}
this.isLoading = false
},
edit(row) {
let temp = this.dataList.filter(item => {
return item.mallId == row.mallId
})
this.priceList = temp[0].data
this.currentMallId = temp[0].mallId
this.isShow = true
},
async rejectAll() {
let check = await this.$userCheck(this.currentMallId)
if (!this.currentMallId) {
Message.error("请选择要操作的店铺")
return
}
if (this.priceList.length == 0) {
Message.success("该店铺无调价通知,无需处理")
return
}
let popUpType1VersionId = ''
let popUpType2VersionId = ''
let popUpType3VersionId = ''
let createTime = null
let ids = []
this.priceList.map(item => {
if (item.popUpType == 1) {
popUpType1VersionId = item.versionId
createTime = item.createTime
ids.push(item.id)
}
})
let res = null
if (ids.length > 0) {
res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/magneto/api/price/adjust/price-reduce-popup-click',
needMallId: true,
mallId: this.currentMallId,
data: {
"rejectOrderIdList": ids,
"maxOrderId": popUpType1VersionId,
"popUpType": 1,
"createTime": createTime
}})
}
ids = []
this.priceList.map(item => {
if (item.popUpType == 2) {
createTime = item.createTime
popUpType2VersionId = item.versionId
ids.push(item.id)
}
})
if (ids.length > 0) {
res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/magneto/api/price/adjust/price-reduce-popup-click',
needMallId: true,
mallId: this.currentMallId,
data: {
"rejectOrderIdList": ids,
"maxOrderId": popUpType2VersionId,
"popUpType": 2,
"createTime": createTime
}})
}
ids = []
this.priceList.map(item => {
if (item.popUpType == 3) {
createTime = item.createTime
popUpType3VersionId = item.versionId
ids.push(item.id)
}
})
if (ids.length > 0) {
res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/magneto/api/price/adjust/price-reduce-popup-click',
needMallId: true,
mallId: this.currentMallId,
data: {
"rejectOrderIdList": ids,
"maxOrderId": popUpType3VersionId,
"popUpType": 3,
"createTime": createTime
}})
}
if (res.success && res.errorCode == 1000000) {
Message.success("调价通知一键拒绝成功")
this.isShow = false
} else {
Message.error("调价通知一键拒绝失败")
}
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -32,9 +32,12 @@
</div> </div>
</ai-card> </ai-card>
</div> </div>
<ai-card title="常用工具" v-if="false"> <ai-card title="TEMU助手功能概览">
<div class=""> <template #right>
dsad <a href="https://www.yuque.com/liushiwei-f582m/kb/bmqqls0o5ybd6c8p?singleDoc" style="color: blue; font-size: 18px; text-decoration: underline;" target="_blank">在线帮助手册</a>
</template>
<div style="height: 1000px">
<BiVueMindmap ref="bi-vue-mindmap" :isReadonly="true" :flatNodes="mindmapNodes" />
</div> </div>
</ai-card> </ai-card>
<AiDialog <AiDialog
@@ -58,9 +61,13 @@
</template> </template>
<script> <script>
import BiVueMindmap from "bi-vue-mindmap";
export default { export default {
name: 'AdminHome', name: 'AdminHome',
components: {
BiVueMindmap,
},
data () { data () {
return { return {
noticeList: [], noticeList: [],
@@ -70,7 +77,278 @@
changeLogList: [], changeLogList: [],
isImportant: false, isImportant: false,
version: '', version: '',
isShowDownload: false isShowDownload: false,
mindmapNodes: [
{
id: "01",
parentId: null,
title: "TEMU助手"
},
{
id: "0101",
parentId: "01",
title: "TEMU"
},
{
id: "010101",
parentId: "0101",
title: "辅助运营"
},
{
id: "01010101",
parentId: "010101",
title: "备货单管理",
isFolded: true
},
{
id: "0101010101",
parentId: "01010101",
title: "创建备货单:批量领取系统下的备货单"
},
{
id: "0101010102",
parentId: "01010101",
title: "抢仓发货:同时操作多个店铺自动抢仓并创建发货单",
resources: [{
type: "VIDEO",
url: "http://temu.jjcp52.com/dist/qiangcang.mp4"
}]
},
{
id: "0101010103",
parentId: "01010101",
title: "发货台管理:批量从“发货台”创建发货单"
},
{
id: "0101010104",
parentId: "01010101",
title: "待装箱发货单:对应“备货单管理”->“发货单列表”,“待装箱发货”列表,支持导出"
},
{
id: "0101010105",
parentId: "01010101",
title: "待收货发货单:对应“备货单管理”->“发货单列表”,“待仓库收货”列表,支持导出"
},
{
id: "0101010106",
parentId: "01010101",
title: "已收货发货单:对应“备货单管理”->“发货单列表”,“已收货”列表,支持导出"
},
{
id: "0101010107",
parentId: "01010101",
title: "普通备货单:对应“备货单管理”->“我的备货单”,“普通备货单”列表,支持导出、成本统计"
},
{
id: "0101010108",
parentId: "01010101",
title: "紧急备货单:对应“备货单管理”->“我的备货单”,“紧急备货单”列表,支持导出、成本统计"
},
{
id: "0101010109",
parentId: "01010101",
title: "商品条码管理:对应“备货单管理”->“商品条码管理”,支持导出,可用于自定义生成标签,提高贴标效率"
},
{
id: "01010102",
parentId: "010101",
title: "商品管理",
isFolded: true
},
{
id: "0101010201",
parentId: "01010102",
title: "商品管理:对应“商品管理”->“商品列表”,支持导出,支持半托管"
},
{
id: "0101010202",
parentId: "01010102",
title: "商品复制:店铺之间快速商品拷贝"
},
{
id: "0101010203",
parentId: "01010102",
title: "查找买手根据SKC查询买手名字"
},
{
id: "0101010204",
parentId: "01010102",
title: "上新生命周期:对应“商品管理”->“上新生命周期管理”,支持导出未发布到站点、已下架的数据"
},
{
id: "01010103",
parentId: "010101",
title: "库存管理",
isFolded: true
},
{
id: "0101010301",
parentId: "01010103",
title: "退货包裹管理:对应“库存管理”->“退货包裹管理”,支持导出"
},
{
id: "0101010302",
parentId: "01010103",
title: "退货明细:对应“库存管理”->“退货明细”,支持导出"
},
{
id: "01010104",
parentId: "010101",
title: "销售管理",
isFolded: true
},
{
id: "0101010401",
parentId: "01010104",
title: "成本管理维护SKU的成本价格用于成本、利润的计算和统计"
},
{
id: "0101010402",
parentId: "01010104",
title: "销售管理:对应“销售管理”->“销售管理”,统计今日销量/销售额、今日利润/利润率、库存/在途库存,支持数据导出"
},
{
id: "0101010403",
parentId: "01010104",
title: "销售统计:统计过去一段时间内,总体销售额/单量/成本/利润/利润率的统计以及SKC/SKU维度的销售额/利润/单量的统计,支持导出"
},
{
id: "0101010404",
parentId: "01010104",
title: "售罄看板:对应“销售管理”->“售罄看板”,支持导出"
},
{
id: "0101010405",
parentId: "01010104",
title: "售后统计:对应“结算管理”->“售后管理”,计算预计扣款,白卖件数,支持导出"
},
{
id: "0101010406",
parentId: "01010104",
title: "售后赔付统计:对应“结算管理”->“对账中心”->“扣款详情”,统计过去一段时间内店铺的扣款总金额/次数,支持导出"
},
{
id: "0101010407",
parentId: "01010104",
title: "物流统计:对应“履约服务账单->明细->缴费记录”,以及“发货单列表->物流计费重核实”,统计一段时期内物流费用,以及货物重量,物流费用分布"
},
{
id: "0101010408",
parentId: "01010104",
title: "账务明细统计:对应“账户资金->对账中心->账务明细”,真实统计一段时间内的实际收入,以及各种类型的支出,汇总统计"
},
{
id: "01010105",
parentId: "010101",
title: "其他",
isFolded: true
},
{
id: "0101010501",
parentId: "01010105",
title: "弹窗消息:支持一键已读所有店铺的弹窗消息"
},
{
id: "0101010502",
parentId: "01010105",
title: "拒绝调价:辅助批量点击“我不接受”按钮,减轻工作量,避免遗漏",
resources: [{
type: "PIC",
url: "http://temu.jjcp52.com/dist/refuse-price.png"
}]
},
{
id: "010102",
parentId: "0101",
title: "选品采集"
},
{
id: "01010201",
parentId: "010102",
title: "商品采集根据TEMU前端地址采集商品信息到店铺草稿箱减轻上品工作量",
resources: [{
type: "VIDEO",
url: "http://temu.jjcp52.com/dist/xuanpin.mp4"
}]
},
{
id: "01010202",
parentId: "010102",
title: "速卖通采集:根据“速卖通”前端地址,采集商品信息到店铺草稿箱,减轻上品工作量"
},
{
id: "01010203",
parentId: "010102",
title: "选品跟踪",
isFolded: true
},
{
id: "0101020301",
parentId: "01010203",
title: "店铺跟踪:跟踪指定店铺的销售趋势、价格趋势,采集跟卖。(暂时不可用)"
},
{
id: "0101020302",
parentId: "01010203",
title: "关键字跟踪根据筛选关键字跟踪该关键字在TEMU前端搜索结果的销售趋势、价格趋势采集跟卖"
},
{
id: "0101020303",
parentId: "01010203",
title: "新品跟踪跟踪TEMU前端“Best Sellers”->“Within last 7 days”的新品采集跟卖"
},
{
id: "0101020304",
parentId: "01010203",
title: "单品跟踪根据TEMU前端地址采集销售趋势、价格趋势"
},
{
id: "0102",
parentId: "01",
title: "SHEIN(希音)"
},
{
id: "010201",
parentId: "0102",
title: "辅助运营",
isFolded: true
},
{
id: "01020101",
parentId: "010201",
title: "成本管理维护SKU的成本价格用于成本、利润的计算和统计"
},
{
id: "01020102",
parentId: "010201",
title: "销售管理对应SHEIN商家后台“商品管理”->“备货信息(新)”,统计当天的销售额、销量、库存等信息,计算成本和利润"
},
{
id: "01020103",
parentId: "010201",
title: "证书中心对应SHEIN商家后台“商品管理”->“证书中心”,导出已驳回和待补充的证书列表,线下跟踪"
},
{
id: "01020104",
parentId: "010201",
title: "商品复制:希音店铺间商品进行快速复制,节省上品时间"
},
{
id: "01020105",
parentId: "010201",
title: "发货订单对应SHEIN商家后台“订单管理”->“发货订单”,提供急采/备货订单的导出,同时带上成本价格,方便对账与结算"
},
{
id: "01020106",
parentId: "010201",
title: "退货与报废单列表对应SHEIN商家后台“质量管理”->“退货与报废单列表”,提供退货订单明细的导出,同时带上成本价格,方便对账与结算"
},
{
id: "01020107",
parentId: "010201",
title: "商家账单统计对应SHEIN商家后台“财务管理”->“商家账单”统计一段时期内销售额、单量、成本、利润、利润率SKC/SKU维度统计排名支持导出"
},
],
} }
}, },
@@ -82,6 +360,7 @@
this.getNoticeList() this.getNoticeList()
this.getChangelog() this.getChangelog()
this.getMyNewestNotice() this.getMyNewestNotice()
this.checkBindWx()
}, },
methods: { methods: {
@@ -112,7 +391,21 @@
link.click() link.click()
document.body.removeChild(link) document.body.removeChild(link)
}, },
checkBindWx() {
this.$http.post(`/api/malluser/bindWxCount`).then(res => {
if (res.code == 0) {
if (res.data == 0) {
this.$confirm('您尚未绑定微信消息推送,将不会收到消息通知,影响体验, 请前往绑定?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$router.push('message')
})
}
}
})
},
read () { read () {
if (this.isImportant) { if (this.isImportant) {
this.$http.post('/api/notice/read').then(res => { this.$http.post('/api/notice/read').then(res => {

View File

@@ -0,0 +1,359 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="数据同步(TEMU)"
isShowBottomBorder>
</ai-title>
<template slot="content">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>店铺列表</span>
</div>
<div>
<el-checkbox :indeterminate="isMallIndeterminate" v-model="checkAllMall" @change="handleCheckAllMallChange">全选</el-checkbox>
<div style="margin: 15px 0;"></div>
<el-checkbox-group v-model="checkMallList" @change="handleCheckedMallChange">
<el-checkbox v-for="mall in $store.state.mallList" style="width: 300px; margin-right: 0px" :label="mall.mallId" :key="mall.mallId">{{mall.isSemiManagedMall ? mall.mallName + '(半托管)': mall.mallName}}</el-checkbox>
</el-checkbox-group>
</div>
</el-card>
<el-card class="box-card" style="margin-top: 5px;">
<div slot="header" class="clearfix">
<span>同步操作</span>
</div>
<div>
<div>
<label style="width:90px">时间范围</label>
<el-date-picker
v-model="dateRange"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
:picker-options="pickerOptions">
</el-date-picker>
</div>
<br>
<el-checkbox :indeterminate="isOptionIndeterminate" v-model="checkAllOptions" @change="handleCheckAllOptionChange">全选</el-checkbox>
<div style="margin: 15px 0;"></div>
<el-checkbox-group v-model="checkOptionList" @change="handleCheckedOptionChange">
<el-checkbox v-for="option in options" style="width: 300px; margin-right: 0px" :label="option.value" :key="option.value">{{option.label}}</el-checkbox>
</el-checkbox-group>
</div>
</el-card>
<div style="display: grid; grid-template-columns: 1fr; gap: 16px; margin-top: 10px;">
<el-button type="button" :class="'el-button el-button--primary'" @click="sync()">同步</el-button>
<!--<el-button type="button" :class="'el-button el-button--primary'" @click="toSyncWaitDeliveryOrder()">同步待仓库收货发货单</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="toSyncDeliveryOrder()">同步历史发货单</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="toBeginCollectResource()">采集资源位</el-button>-->
</div>
<el-card class="box-card" style="margin-top: 5px;">
<div slot="header" class="clearfix">
<span>日志输出</span>
</div>
<div id="log-container" v-html="logsContent" style="height: 500px; background-color: black; overflow-y: scroll; border: 1px solid #000; padding: 5px; color: white"></div>
</el-card>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
export default {
name: 'SyncDataTemu',
data () {
return {
isLoading: false,
categoryList: [],
checkAllMall: false,
checkAllOptions: false,
checkMallList: [],
checkOptionList: [],
isMallIndeterminate: false,
isOptionIndeterminate: false,
mallList: [],
currentIndex: 0,
pageSize: 100,
pageNo: 1,
syncProductData: [],
deliveryOrderList: [],
currentWaitDeliveryIndex: 0,
waitDeliveryData: [],
options: [
{label: '同步已上架商品列表', value: 0},
{label: '同步今日待收货发货单', value: 1},
{label: '同步今日销售数据', value: 2},
{label: '同步SKU历史销售数据', value: 3}
],
dateRange: [new Date(), new Date()],
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now()
}
},
logsList: [],
reqListData: []
}
},
created() {
},
computed: {
logsContent() {
return this.logsList.join('<br>')
}
},
methods: {
handleCheckAllMallChange(val) {
if (val) {
this.checkMallList = this.mallList.map(item => {
return item.mallId
})
} else {
this.checkMallList = []
}
this.isMallIndeterminate = false;
},
handleCheckedMallChange(value) {
let checkedCount = value.length;
this.checkAllMall = checkedCount === this.mallList.length;
this.isMallIndeterminate = checkedCount > 0 && checkedCount < this.mallList.length;
},
handleCheckAllOptionChange(val) {
if (val) {
this.checkOptionList = this.options.map(item => {
return item.value
})
} else {
this.checkOptionList = []
}
this.isOptionIndeterminate = false;
},
handleCheckedOptionChange(value) {
let checkedCount = value.length;
this.checkAllOptions = checkedCount === this.options.length;
this.isOptionIndeterminate = checkedCount > 0 && checkedCount < this.options.length;
},
async sync() {
if (this.checkMallList.length == 0) {
Message.error("请选择要同步的店铺")
return
}
if (this.checkOptionList.length == 0) {
Message.error("请选择要同步的操作")
return
}
this.isLoading = true
for (let i = 0; i < this.checkOptionList.length; i++) {
this.pageNo = 1
if (this.checkOptionList[i] == 0) { // 同步商品
for (let j = 0; j < this.checkMallList.length; j++) {
let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.checkMallList[j]
})
let mallName = mallInfo[0].mallName
this.syncProductData = []
await this.syncProduct(this.checkMallList[j], mallName)
}
}
if (this.checkOptionList[i] == 1) { // 同步待发货发货单
for (let j = 0; j < this.checkMallList.length; j++) {
let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.checkMallList[j]
})
let mallName = mallInfo[0].mallName
this.deliveryOrderList = []
await this.syncDeliveryOrder(this.checkMallList[i], mallName)
}
}
if (this.checkOptionList[i] == 2) { // 同步今日销售数据
for (let j = 0; j < this.checkMallList.length; j++) {
this.reqListData = []
await this.syncTodaySaleData(this.checkMallList[i])
}
}
}
this.isLoading = false
},
async syncProduct(mallId, mallName) {
let res = await sendChromeAPIMessage({url: 'bg-visage-mms/product/skc/pageQuery',
anti:true,
needMallId: true,
mallId: mallId,
data: {
pageSize: this.pageSize,
page: this.pageNo,
skcSiteStatus: 1
}
})
if (res.errorCode == 1000000) {
res.result.pageItems.map(item => {
let temp = {
mallId: mallId,
mallName: mallName,
productName: item.productName,
spu: item.productId,
skc: item.productSkcId,
skcCode: item.extCode,
createTime: timestampToTime(item.createdAt)
}
item.productSkuSummaries.map(item1 => {
let temp1 = {
...temp,
skuCode: item1.extCode,
sku: item1.productSkuId,
mainPic: item1.thumbUrl,
price: item1.supplierPrice /100
}
let specArr = item1.productSkuSpecList.map(item2 => {
return item2.specName
})
temp1.skuSpec = specArr.join(',')
this.syncProductData.push(temp1)
})
})
if (res.result.pageItems.length == this.pageSize) {
this.pageNo ++
await this.syncProduct(mallId, mallName)
} else {
this.$http.post('/api/stock/product/addBatch', this.syncProductData
).then(res => {
if (res.code == 0) {
this.logs(mallName, '已上架商品', true)
} else {
this.logs(mallName, '已上架商品', false)
}
})
}
} else {
this.logs(mallName, '已上架商品', false)
}
},
async syncDeliveryOrder(mallId, mallName) {
let now = new Date()
now.setHours(0, 0, 0, 0)
let start = now.getTime()
let end = start + 86400*1000 - 1000
let res = await sendChromeAPIMessage({url: 'bgSongbird-api/supplier/deliverGoods/management/pageQueryDeliveryBatch',
anti:true,
needMallId: true,
mallId: mallId,
data: {
pageNo: this.pageNo,
pageSize: this.pageSize,
productLabelCodeStyle: 0,
onlyTaxWarehouseWaitApply: false,
deliverTimeFrom: start,
deliverTimeTo: end,
status: 1
}
})
if (res.errorCode == 1000000) {
res.result.list.map(item => {
let temp = {
mallId: mallId,
mallName: mallName,
subWarehouseName: item.subWarehouseName,
expressCompany: item.expressCompany,
deliveryOrderSn: item.deliveryOrderSn
}
item.deliveryOrderList.map(item1 => {
temp = {
...temp,
productName: item1.subPurchaseOrderBasicVO.productName,
skc: item1.productSkcId,
skcCode: item1.skcExtCode,
isFirst: item1.subPurchaseOrderBasicVO.isFirst ? 1: 0
}
item1.packageDetailList.map(item2 => {
temp = {
...temp,
sku: item2.productSkuId,
num: item2.skuNum
}
})
this.deliveryOrderList.push(temp)
})
})
if (res.result.list.length == this.pageSize) {
this.pageNo ++
await this.syncDeliveryOrder(mallId, mallName)
} else {
this.$http.post('/api/stock/orderInfo/addBatch', this.deliveryOrderList
).then(res => {
if (res.code == 0) {
this.logs(mallName, '今日待收货发货单', true)
} else {
this.logs(mallName, '今日待收货发货单', false)
}
})
}
} else {
this.logs(mallName, '今日待收货发货单', false)
}
},
async syncTodaySaleData(mallId) {
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sales/management/list',
needMallId: true,
mallId: mallId,
data: {
pageNo: this.pageNo,
pageSize: this.pageSize,
isLack: 0,
priceAdjustRecentDays: 7
}})
if (res.errorCode == 1000000) {
for(let i = 0;i < res.result.subOrderList.length; i++) {
let item = res.result.subOrderList[i]
for(let j = 0;j < item.skuQuantityDetailList.length; j++) {
this.reqListData.push({
mallId: mallId,
sku: item.skuQuantityDetailList[j].productSkuId,
saleNum: item.skuQuantityDetailList[j].todaySaleVolume,
salePrice: item.skuQuantityDetailList[j].supplierPrice / 100
})
}
}
if (this.pageSize == res.result.subOrderList.length) {
this.pageNo ++
await this.syncTodaySaleData(mallId)
} else {
await this.$http.post('http://192.168.1.199:8080/jeecg-boot/eshop/saleManage/addBatch', this.reqListData)
}
}
},
logs(mallName, subject, flag) {
if (flag) {
this.logsList.unshift(`${mallName}${subject}<b style='color: green'>同步成功</b>`)
} else {
this.logsList.unshift(`${mallName}${subject}<b style='color: red'>同步失败</b>`)
}
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,95 @@
<template>
<AiDetail class="add-label">
<template #title>
<ai-title title="添加标签" isShowBack :isShowBottomBorder="true" @onBackClick="cancel">
<template #rightBtn>
<el-button @click="preview" size="small" type="danger">预览</el-button>
<el-button @click="savePdf" size="small" type="primary">下载pdf</el-button>
<el-button @click="saveTemplate" size="small" type="primary">保存</el-button>
<el-button @click="print" size="small">打印</el-button>
<el-button @click="clearPaper" size="small" type="danger">清空纸张</el-button>
</template>
</ai-title>
</template>
<template #content>
<ai-card title="标签模板" class="card" :hideTitle="true">
<template #content>
<div class="add-label__wrapper">
<Print ref="printRef" :labels="labels"></Print>
</div>
</template>
</ai-card>
</template>
</AiDetail>
</template>
<script>
import Print from '@/components/print/Print'
export default {
components: {
Print
},
data () {
return {
labels: []
}
},
methods: {
cancel() {
this.$router.go(-1)
},
preview () {
this.$refs.printRef.getHtml()
},
savePdf () {
this.$refs.printRef.savePdf()
},
rotatePaper () {
this.$refs.printRef.rotatePaper()
},
saveTemplate () {
const json = this.$refs.printRef.exportJson()
console.log(json)
},
print () {
this.$refs.printRef.print()
},
clearPaper () {
this.$refs.printRef.clearPaper()
}
}
}
</script>
<style lang="scss" scoped>
.add-label {
::v-deep(.ai-detail__content--wrapper) {
max-width: 100%;
height: 100%;
margin: 0 20px;
}
.card {
height: 100%;
overflow: hidden;
::v-deep(.ai-card__body) {
flex: 1;
height: 100%;
}
.add-label__wrapper {
padding-bottom: 20px;
}
}
}
</style>

135
src/view/lables/Print.vue Normal file
View File

@@ -0,0 +1,135 @@
<template>
<ai-list class="Learning">
<ai-title
slot="title"
title="新手园地"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<el-radio-group v-model="search.categoryId" @change="onChange">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="isFavorite">我的收藏</el-radio-button>
<el-radio-button :label="item.id" :key="item.id" v-for="item in cateList">{{ item.name }}</el-radio-button>
</el-radio-group>
</template>
<template #right>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
style="margin-top: 8px;"
@getList="getList">
<el-table-column slot="options" label="操作" align="center" fixed="right" width="140px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="collection(row.id, row.isFavorite)">{{ row.isFavorite === '0' ? '收藏' : '取消收藏' }}</el-button>
<el-button type="text" @click="toDetail(row.url)">详情</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
export default {
name: 'Learning',
data () {
return {
colConfigs: [
{ prop: 'title', label: '标题', align: 'left' },
{ prop: 'createTime', label: '发布时间', align: 'center' },
],
tableData: [],
total: 0,
search: {
current: 1,
size: 10,
categoryId: ''
},
cateList: [],
isFavorite: 0
}
},
created () {
this.$store.dispatch('getUserInfo').then(e => {
console.log(e)
})
this.getCateList()
this.getList()
},
methods: {
toDetail (url) {
window.open(url)
},
onChange (e) {
if (e === 'isFavorite') {
this.$http.post('/api/learning/favoritePage', null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
} else {
this.search.current = 1
this.getList()
}
},
collection (id, isFavorite) {
this.$confirm(isFavorite === '0' ? '确定收藏该文章?' : '确定取消收藏?', '温馨提示', {
confirmButtonText: '确定',
callback: action => {
if (action === 'confirm') {
this.$http.post(isFavorite === '0' ? `/api/learning/addFavorite?id=${id}` : `/api/learning/delFavorite?id=${id}`).then(res => {
if (res.code === 0) {
this.$message.success(isFavorite === '0' ? '收藏成功' : '取消成功')
this.getList()
}
})
}
}
})
},
getCateList () {
this.$http.post('/api/learningCategory/page?size=50').then(res => {
if (res.code === 0) {
this.cateList = res.data.records
}
})
},
getList () {
this.$http.post('/api/learning/pluginPage', null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,238 @@
<template>
<ai-list class="Template">
<ai-title
slot="title"
title="管理SKU"
isShowBack
isShowBottomBorder
@onBackClick="$router.go(-1)">
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<el-button type="primary" size="small" @click="isShow = true">添加</el-button>
</template>
<template #right>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
style="margin-top: 8px;"
@getList="getList">
<el-table-column slot="productName" width="300px" :show-overflow-tooltip='true' label="商品名称" fixed="left">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.mainImageUrl" style="width: 40px; height: 40px" class="image" :preview-src-list="[scope.row.mainImageUrl]" />
{{ scope.row.productName }}
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" align="center" fixed="right" width="140px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="toAdd(row.url)">编辑</el-button>
<el-button type="text" @click="toDetail(row.url)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
title="添加SKU"
width="1400px"
@confirm="onConfirm">
<ai-search-bar>
<template #left>
<el-select v-model="skuSearch.mallId" placeholder="请选择店铺" size="small" @change="onMallChange">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</template>
<template #right>
</template>
</ai-search-bar>
<ai-table
height="400"
:tableData="skuList"
:col-configs="colConfigs"
:total="skuTotal"
:current.sync="skuSearch.current"
:size.sync="skuSearch.size"
style="margin-top: 8px;"
@getList="getSkuList"
:pageSizes="[10, 20, 50, 100, 500, 1000]"
v-loading="isLoading">
<el-table-column slot="productName" width="300px" :show-overflow-tooltip="true" label="商品名称" fixed="left">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.mainImageUrl" style="width: 40px; height: 40px" class="image" :preview-src-list="[scope.row.mainImageUrl]" />
{{ scope.row.productName }}
</div>
</template>
</el-table-column>
</ai-table>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import { sendChromeAPIMessage } from '@/api/chromeApi'
export default {
name: 'Template',
data () {
return {
colConfigs: [
// { slot: 'productName', label: '商品名称', width: '300px', align: 'left', fixed: 'left' },
// { prop: 'category', label: '分类', width: '140px', align: 'left', fixed: 'left' },
{ prop: 'mallName', label: '店铺名称', align: 'left' },
{ prop: 'labelCode', label: '条码编码', align: 'center' },
// { prop: 'productSkcId', label: 'SKC', align: 'left' },
{ prop: 'productSkuId', label: 'SKU', align: 'center' },
// { prop: 'extCode', label: 'SKC货号', align: 'left' },
{ prop: 'skuExtCode', label: 'SKU货号', align: 'center' },
{ prop: 'skuSpecName', label: '次销售属性', align: 'center' }
],
tableData: [],
total: 0,
search: {
current: 1,
size: 100
},
skuSearch: {
current: 1,
size: 100,
mallId: ''
},
skuTotal: 0,
skuList: [],
isShow: false,
skuReqParams: {
page: 1,
pageSize: 100
},
isLoading: false
}
},
computed: {
currMall () {
if (!this.$store.state.mallList.length) {
return {}
}
return this.$store.state.mallList.filter(v => v.mallId === this.skuSearch.mallId)[0]
}
},
created () {
this.getList()
},
methods: {
toAdd () {
this.$router.push('/addLabelsTemplate')
},
requestSKUList () {
sendChromeAPIMessage({
url: 'bg-visage-mms/labelcode/pageQuery',
needMallId: true,
mallId: this.skuSearch.mallId,
anti: true,
data: {
page: this.skuSearch.size > 100 ? this.skuReqParams.page : this.skuSearch.current,
pageSize: this.skuSearch.size > 100 ? 100 : this.skuSearch.size
}
}).then(async (res) => {
if (res.errorCode == 1000000) {
const list = res.result.pageItems.map(v => {
return {
mallName: this.currMall.mallName,
productName: v.productName,
productSkuId: v.labelCodeVO.productSkuId,
labelCode: v.labelCodeVO.labelCode,
skuExtCode: v.labelCodeVO.skuExtCode,
skuSpecName: v.productSkuSpecList.map(item => {
return item.specName
}).join(',')
}
})
this.skuTotal = res.result.total
this.skuList.push(...list)
if (this.skuSearch.size > 100 && (res.result.total > this.skuList.length)) {
this.skuReqParams.page++
await this.$sleepSync(5000)
this.requestSKUList()
} else {
this.isLoading = false
}
}
})
},
onMallChange (e) {
if (!e) {
this.skuList = []
return false
}
this.$userCheck(this.skuSearch.mallId).then(() => {
this.skuList = []
this.skuReqParams.page = 1
this.isLoading = true
this.requestSKUList()
}).catch(() => {
this.skuSearch.mallId = ''
})
},
getSkuList () {
if (!this.skuSearch.mallId) {
return this.$message.error('请选择店铺')
}
this.$userCheck(this.skuSearch.mallId).then(() => {
this.skuList = []
this.skuReqParams.page = 1
this.isLoading = true
this.requestSKUList()
}).catch(() => {
this.skuSearch.mallId = ''
})
},
getList () {
this.$http.post('/api/learning/pluginPage', null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
onConfirm () {
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,98 @@
<template>
<ai-list class="Template">
<ai-title
slot="title"
title="模板管理"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<el-button type="primary" size="small" @click="toAdd">添加</el-button>
</template>
<template #right>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
style="margin-top: 8px;"
@getList="getList">
<el-table-column slot="options" label="操作" align="center" fixed="right" width="220px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="toAddSku(row.url)">添加SKU</el-button>
<el-button type="text" @click="toAdd(row.url)">编辑</el-button>
<el-button type="text" @click="toDetail(row.url)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
export default {
name: 'Template',
data () {
return {
colConfigs: [
{ prop: 'name', label: '模板名称', align: 'left' },
{ prop: 'count', label: '绑定SKU数量', align: 'center' },
{ prop: 'createTime', label: '创建时间', align: 'center' },
],
tableData: [
{
name: '电池',
count: 2
}
],
total: 0,
search: {
current: 1,
size: 10
}
}
},
created () {
// this.getList()
},
methods: {
toAdd () {
this.$router.push('/addLabelsTemplate')
},
toAddSku () {
this.$router.push('/skuManage')
},
getList () {
console.log(this.tableData)
this.$http.post('/api/learning/pluginPage', null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
// this.tableData = res.data.records
// this.total = res.data.total
}
})
},
onConfirm () {
}
}
}
</script>
<style scoped lang="scss">
</style>

380
src/view/login/Forget.vue Normal file
View File

@@ -0,0 +1,380 @@
<template>
<div class="login">
<div class="body">
<div class="middle">
<div class="right">
<div class="tab">
<h2 class="active" @click="currIndex = 0">找回密码</h2>
</div>
<el-form :model="form" label-position="top" ref="form" label-width="100px" class="form">
<el-form-item
prop="phone"
:rules="[{ required: true, message: '请输入手机号', trigger: 'blur' }, { validator: phoneReg, trigger: 'blur' }]">
<el-input maxlength="11" placeholder="请输入手机号" v-model="form.phone"></el-input>
</el-form-item>
<el-form-item
prop="password"
:rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
<el-input placeholder="请输入密码" type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item
prop="repassword"
:rules="[{ required: true, message: '请再次输入密码', trigger: 'blur' }]">
<el-input placeholder="请再次输入密码" type="password" v-model="form.repassword"></el-input>
</el-form-item>
<el-form-item label="" prop="code" :rules="[{ required: true, message: '请输入验证码', trigger: 'blur' }]">
<div class="code-item">
<el-input style="width: 300px;" maxlength="4" placeholder="请输入验证码" v-model="form.code"></el-input>
<span @click="getCode" :loading="btnLoading">{{ isStart ? time + ' S' : '发送验证码' }}</span>
</div>
</el-form-item>
<el-button type="primary" style="width: 100%" @click="login" :loading="btnLoading">重置设置</el-button>
</el-form>
<div class="login-footer">
<div class="left">
<i class="hover" @click="$router.back()">返回登录</i>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import CryptoJS from 'crypto-js'
export default {
data () {
return {
form: {
password: '',
repassword: '',
code: '',
phone: ''
},
phoneReg: (rule, value, callback) => {
if (/^1[0-9]{10,10}$/.test(value)) {
return callback()
}
callback(new Error('手机号格式错误'))
},
timer: null,
time: 60,
isSend: false,
isStart: false,
isLoading: false,
currIndex: 0,
btnLoading: false,
loginType: '0'
}
},
mounted () {
},
methods: {
login () {
this.$refs.form.validate((valid) => {
if (valid) {
if (this.form.password != this.form.repassword) {
this.$message.success('两次密码输入不一致');
return;
}
this.btnLoading = true
this.$http.post(`/api/malluser/forget`, null, {
params: {
...this.form
}
}, {
headers: {
Authorization: 'Basic cGM6cGM='
}
}).then(res => {
if (res.code === 0) {
this.$message.success('注册成功')
setTimeout(() => {
this.$router.replace('/login')
}, 500)
}
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
} else {
return false;
}
})
},
encryptPhone (phone) {
const u = navigator.userAgent
const isIos = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
var key = 'thanks,temulll11'
var iv = CryptoJS.enc.Latin1.parse(key)
var encrypted = CryptoJS.AES.encrypt(phone, iv, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
})
if (isIos) {
return encodeURIComponent(encrypted.toString())
} else {
return encrypted.toString()
}
},
getCode () {
if (this.isSend) {
return this.$message.error('验证码已发送')
}
this.$refs.form.validateField('phone', e => {
if (!e) {
let phone = this.encryptPhone(this.form.phone)
this.isSend = true
this.btnLoading = true
this.$http.post(`/api/sms/getRegSmsCodeNew?phone=${phone}`, {}, {
withoutToken: true
}).then(res => {
if (res.code === 0) {
this.$message.success('验证码发送成功')
this.isStart = true
this.timer = setInterval(() => {
if (this.time === 0) {
this.isSend = false
this.isStart = false
this.time = 60
clearInterval(this.timer)
return false
}
this.time = this.time - 1
}, 1000)
}
this.btnLoading = false
this.isSend = false
}).catch(() => {
this.isSend = false
this.btnLoading = false
})
}
})
},
onChange (e) {
this.$emit('change', e)
}
}
}
</script>
<style lang="scss" scoped>
.login {
display: flex;
flex-direction: column;
height: 100vh;
.body {
position: relative;
flex: 1;
width: 100%;
background-color: #cdc8c8;
// background: url(../../assets/images/login/login.png) no-repeat;
background-size: 100% 100%;
.middle {
position: absolute;
display: flex;
justify-content: flex-end;
top: 50%;
left: 50%;
z-index: 11;
width: 1280px;
transform: translate(-50%, -50%);
& > .left {
padding-top: 42px;
font-family: MicrosoftYaHei;
p {
line-height: 24px;
margin-top: 12px;
margin-bottom: 13px;
font-size: 18px;
color: #fff;
}
span {
color: #fff;
}
}
& > .right {
width: 500px;
padding: 63px 40px 42px;
box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.06);
background: #fff;
border-radius: 16px;
backdrop-filter: blur(6px);
overflow: hidden;
.tab {
display: flex;
position: relative;
align-items: center;
margin-bottom: 36px;
padding-bottom: 17px;
border-bottom: 1px solid #DCDFE6;
&::after {
position: absolute;
bottom: -1px;
left: 0;
z-index: 1;
width: 81px;
height: 2px;
background: #1FBAD6;
content: ' ';
transition: all ease-in-out 0.3s;
}
&.tab-active:after {
transform: translateX(138px);
}
h2 {
width: 81px;
line-height: 24px;
text-align: center;
font-size: 20px;
font-family: SJsuqian;
color: #000000;
cursor: pointer;
user-select: none;
transition: all ease 0.4s;
&.active, &:hover {
color: #1FBAD6;
}
&:first-child {
margin-right: 58px;
}
}
}
.login-type {
font-size: 14px;
font-family: SJsuqian;
text-align: center;
color: #54657D;
cursor: pointer;
transition: all ease-in-out 0.4s;
&:hover {
color: #1FBAD6;
}
}
.login-footer {
display: flex;
align-items: center;
justify-content: center;
margin-top: 21px;
margin-bottom: 38px;
div {
display: flex;
align-items: center;
span {
font-family: SJsuqian;
color: #54657D;
}
i {
font-family: SJsuqian;
color: #1FBAD6;
}
}
}
}
}
::v-deep .el-form {
.el-input__inner {
height: 48px;
border-color: #DCDFE6;
background-color: transparent;
&:focus, &:hover {
border-color: #1FBAD6;
}
&::placeholder {
color: #666666;
}
}
.el-form-item.is-error .el-input__inner, .el-form-item.is-error .el-input__inner:focus, .el-form-item.is-error .el-textarea__inner, .el-form-item.is-error .el-textarea__inner:focus {
border-color: #F56C6C;
}
.el-form-item {
margin-bottom: 20px;
}
.el-button {
height: 48px;
margin-top: 28px;
font-size: 16px;
}
.code-item {
display: flex;
align-items: center;
justify-content: space-between;
span {
width: 110px;
height: 48px;
line-height: 48px;
text-align: center;
font-size: 14px;
color: #333;
cursor: pointer;
user-select: none;
border-radius: 4px;
transition: all cubic-bezier(0.215, 0.61, 0.355, 1) 0.3s;
border: 1px solid #DCDFE6;
&:hover {
border-color: #1FBAD6;
}
}
.el-form-item {
width: 300px;
}
}
}
.copyright {
position: fixed;
bottom: 24px;
left: 50%;
font-size: 14px;
font-weight: 400;
color: #fff;
letter-spacing: 1px;
transform: translateX(-50%);
}
}
}
</style>

View File

@@ -24,9 +24,16 @@
<span>没有账号</span> <span>没有账号</span>
<i class="hover" @click="$router.push('/register')">立即注册</i> <i class="hover" @click="$router.push('/register')">立即注册</i>
</div> </div>
<div class="left" style="margin-left: 10px;">
<span>忘记密码</span>
<i class="hover" @click="$router.push('/forget')">找回密码</i>
</div>
</div> </div>
</div> </div>
</div> </div>
<div id="kefu" @click="gotoKefu">
<label slot="reference" class="topBtn" title="联系客服"></label>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -139,6 +146,9 @@
onChange (e) { onChange (e) {
this.$emit('change', e) this.$emit('change', e)
},
gotoKefu() {
window.open('https://work.weixin.qq.com/kfid/kfcaa4208f661131eba', '_blank')
} }
} }
} }
@@ -348,4 +358,30 @@
} }
} }
} }
#kefu {
position: fixed;
right: 20px;
bottom: 40px;
z-index: 999;
width: 60px;
height: 60px;
}
#kefu .topBtn {
width: 60px;
height: 60px;
background-color: #fff;
position: absolute;
left: 0;
top: 0;
border-radius: 50%;
cursor: pointer;
background-position: center center;
background-repeat: no-repeat;
background-size: 40px 40px;
-webkit-animation: wobble 250ms infinite;
animation: wobble 250ms infinite;
background-image: url('data:image/svg+xml;%20charset=utf8,%3Csvg%20t%3D%221575450105478%22%20class%3D%22icon%22%20viewBox%3D%220%200%201220%201024%22%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20p-id%3D%222883%22%20width%3D%2248%22%20height%3D%2248%22%3E%3Cpath%20d%3D%22M609.524%20103.522c-222.89%200-403.712%20178.472-403.712%20398.78%200%20220.31%20180.823%20398.782%20403.712%20398.782%20222.889%200%20403.712-178.473%20403.712-398.781%200-220.309-180.823-398.781-403.712-398.781v48.762c196.1%200%20354.95%20156.785%20354.95%20350.019s-158.85%20350.019-354.95%20350.019-354.95-156.785-354.95-350.02c0-193.233%20158.85-350.018%20354.95-350.018v-48.762z%22%20fill%3D%22%231296db%22%20p-id%3D%222884%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M786.578%20916.34c166.45-69.217%20278.408-231.055%20278.408-414.035%200-248.026-203.847-449.219-455.457-449.219-251.619%200-455.457%20201.188-455.457%20449.22%200%2055.397%2010.152%20109.367%2029.718%20159.975%204.855%2012.56-1.39%2026.677-13.949%2031.533-12.56%204.855-26.677-1.39-31.532-13.949a490.396%20490.396%200%200%201-3.042-8.078c-1.85%200.077-3.711%200.116-5.581%200.116C58.06%20671.903%200%20614.597%200%20543.903c0-65.005%2049.09-118.69%20112.68-126.91C153.65%20182.56%20360.56%204.324%20609.528%204.324c248.962%200%20455.877%20178.24%20496.85%20412.67%2063.583%208.225%20112.669%2061.907%20112.669%20126.909%200%2070.694-58.06%20128-129.686%20128-1.89%200-3.771-0.04-5.642-0.119-47.536%20129.702-148.34%20235.841-279.493%20290.027-1.161%2033.464-29.012%2060.24-63.2%2060.24-34.925%200-63.237-27.944-63.237-62.416%200-34.471%2028.312-62.415%2063.237-62.415%2017.892%200%2034.048%207.333%2045.551%2019.12z%22%20fill%3D%22%231296db%22%20p-id%3D%222885%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M609.528%20611.405c-58.933%200-112.056-10.644-158.472-28.342-16.123-6.147-30.211-12.702-42.138-19.208-6.926-3.777-11.447-6.59-13.437-7.972-19.24-13.373-44.428%205.446-37.059%2027.688%2035.296%20106.527%20136.054%20179.913%20251.106%20179.913%20115.05%200%20215.796-73.384%20251.092-179.913%207.37-22.243-17.82-41.062-37.06-27.687-1.99%201.383-6.51%204.195-13.434%207.972-11.926%206.505-26.012%2013.06-42.133%2019.207-46.413%2017.698-99.533%2028.342-158.465%2028.342z%22%20fill%3D%22%231296db%22%20p-id%3D%222886%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E');
}
</style> </style>

View File

@@ -52,6 +52,7 @@
</template> </template>
<script> <script>
import CryptoJS from 'crypto-js'
export default { export default {
data () { data () {
return { return {
@@ -92,9 +93,12 @@
this.$message.success('两次密码输入不一致'); this.$message.success('两次密码输入不一致');
return; return;
} }
const devVersion = require('../../manifest.development.json').version
const prodVersion = require('../../manifest.production.json').version
this.btnLoading = true this.btnLoading = true
this.$http.post(`/api/malluser/reg`, { this.$http.post(`/api/malluser/reg`, {
...this.form ...this.form,
version: process.env.NODE_ENV === 'production' ? prodVersion : devVersion
}, { }, {
headers: { headers: {
Authorization: 'Basic cGM6cGM=' Authorization: 'Basic cGM6cGM='
@@ -118,6 +122,23 @@
} }
}) })
}, },
encryptPhone (phone) {
const u = navigator.userAgent
const isIos = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
var key = 'thanks,temulll11'
var iv = CryptoJS.enc.Latin1.parse(key)
var encrypted = CryptoJS.AES.encrypt(phone, iv, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
})
if (isIos) {
return encodeURIComponent(encrypted.toString())
} else {
return encrypted.toString()
}
},
getCode () { getCode () {
if (this.isSend) { if (this.isSend) {
@@ -126,9 +147,10 @@
this.$refs.form.validateField('phone', e => { this.$refs.form.validateField('phone', e => {
if (!e) { if (!e) {
let phone = this.encryptPhone(this.form.phone)
this.isSend = true this.isSend = true
this.btnLoading = true this.btnLoading = true
this.$http.post(`/api/sms/getRegSmsCode?phone=${this.form.phone}`, {}, { this.$http.post(`/api/sms/getRegSmsCodeNew?phone=${phone}`, {}, {
withoutToken: true withoutToken: true
}).then(res => { }).then(res => {
if (res.code === 0) { if (res.code === 0) {

View File

@@ -0,0 +1,31 @@
<script>
import AiDetail from "@/components/AiDetail.vue";
export default {
name: "imageTranslate",
components: {AiDetail},
data() {
return {
form: {}
}
}
}
</script>
<template>
<ai-detail class="imageTranslate">
<ai-title slot="title" title="图片翻译" isShowBottomBorder/>
<template #content>
<el-form>
</el-form>
</template>
</ai-detail>
</template>
<style scoped lang="scss">
.imageTranslate {
}
</style>

View File

@@ -0,0 +1,305 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="批量上品"
:tips="`上传一件商品将消耗${coin}金币`"
isShowBottomBorder>
</ai-title>
<template slot="content">
<el-form :model="form" ref="form" label-width="180px" class="form">
<el-divider content-position="left">模板来源</el-divider>
<el-form-item label="模板店铺来源" style="width: 100%;" prop="sourceMallId" :rules="[{ required: true, message: '请选择模板店铺来源', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="form.sourceMallId" placeholder="请选择模板店铺来源">
<el-option
v-for="item in mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</el-form-item>
<el-form-item
prop="sourceSpuId"
label="模板SPU ID"
style="width: 480px"
:rules="[{ required: true, message: '请输入模板SPU ID', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入模板SPU ID" v-model="form.sourceSpuId"></el-input>
</el-form-item>
<el-divider content-position="left">目标店铺</el-divider>
<el-form-item label="店铺" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择目标店铺', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择目标店铺">
<el-option
v-for="item in mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</el-form-item>
<el-button type="button" :class="'el-button el-button--primary'" @click="openFolder">选择文件夹</el-button>
</el-form>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import {transform} from '@/utils/product'
import { createFolderApi } from "@/utils/folder.js"
import { uploadImage } from "@/utils/image.js"
import { formatDate } from "@/utils/date.js"
export default {
name: 'BatchUpload',
data () {
return {
form: {
sourceMallId: '',
sourceSpuId: '',
targetMallId: ''
},
content: null,
mallObj: null,
folderId: null,
successCount: 0,
mainPicList: [],
mainPicUrl: null,
detailPicList: [],
coin: 150,
colConfigs: [
{ slot: 'productName', label: '商品名称', width: '180px', align: 'left', fixed: 'left' },
{ prop: 'category', label: '分类', width: '140px', align: 'left', fixed: 'left' },
{ prop: 'productId', label: 'SPU ID', width: '120px', align: 'left' },
{ prop: 'productSkcId', label: 'SKC ID', width: '120px', align: 'left' },
{ prop: 'productSkuId', label: 'SKU ID', width: '120px', align: 'left' },
{ prop: 'skcSiteStatus', label: '是否在售', width: '120px', align: 'left' },
{ prop: 'extCode', label: 'SKC货号', width: '100px', align: 'left' },
{ prop: 'skuExtCode', label: 'SKU货号', width: '160px', align: 'left' },
{ prop: 'specName', label: 'SKU属性', width: '100px', align: 'left' },
{ prop: 'skuStockQuantity', label: '库存', width: '100px', align: 'left' },
{ prop: 'productCertAuditStatus', label: '资质上传状态', width: '120px', align: 'left' },
{ prop: 'certPunishType', label: '合规下架风险', width: '120px', align: 'left' },
{ prop: 'supplierPrice', label: '申报价格(CNY)', width: '180px', align: 'left' },
{ prop: 'todaySalesVolume', label: '今日销量', width: '100px', align: 'left' },
{ prop: 'createdAt', label: '上架时间', width: '160px', align: 'left' }
],
isLoading: false,
tableData: [],
currentIndex: 0
}
},
computed: {
mallList () {
const filteredData = this.$store.state.mallList.filter(item => {
return item.isSemiManagedMall == false
})
return filteredData
}
},
created () {
},
methods: {
async openFolder() {
this.$refs.form.validate(async (valid) => {
if (valid) {
let res1 = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/query',
needMallId: true,
mallId: this.form.sourceMallId,
data: {
productEditTaskUid: '',
productId: this.form.sourceSpuId
}})
if (res1.errorCode == 1000000) {
this.content = JSON.parse(transform(res1.result))
this.mallObj = this.$store.state.mallList.filter(item => {
return item.mallId == this.form.targetMallId
})
}
this.successCount = 0
this.mainPicList = []
this.detailPicList = []
this.folderId = await createFolderApi(formatDate(new Date()).split('-'), this.form.targetMallId)
const res = await window.showDirectoryPicker({})
await this.dealAction("", res)
}
})
},
async dealAction (root, obj) {
console.log(obj)
if (obj.entries) {
let dirs = obj.entries()
if (root == "") {
let length = 0
for await (const temp of dirs) {
length ++
}
if (this.$store.state.userInfo.coin < length * this.coin) {
Message.error("金币不足,请先充值金币")
return
}
}
if (root == '') {
dirs = obj.entries()
for await (const entry of dirs) {
/*if (entry[1].kind == 'file') {
let prefix = entry[0].split('.').pop()
if (prefix == 'png' || prefix == 'jpg' || prefix == 'jpeg') {
let file = await entry[1].getFile()
let img = await uploadImage(this.folderId, file, this.form.targetMallId, true)
this.mainPicUrl = img
this.$sleepSync(500)
}
} else {*/
if (entry[1].kind == 'directory') {
await this.dealAction(obj.name + "/" + entry[0], entry[1])
}
//}
}
} else {
this.mainPicList = []
this.detailPicList = []
await this.toUploadProduct(root, obj)
}
}
},
async toUploadProduct(root, obj) {
const dirs = obj.entries()
for await (const entry of dirs) {
if (entry[1].kind == 'file') {
let prefix = entry[0].split('.').pop()
if (prefix == 'png' || prefix == 'jpg' || prefix == 'jpeg') {
let file = await entry[1].getFile()
let img = await uploadImage(this.folderId, file, this.form.targetMallId, true)
this.mainPicUrl = img
this.$sleepSync(200)
}
} else if (entry[0] != '详情图' && entry[1].kind == 'directory') {
let tempList = await this.getPictureList(root + '/' + entry[0], entry[1])
this.mainPicList.push({color: entry[0], imgList: tempList})
} else if (entry[0] == '详情图' && entry[1].kind == 'directory') {
this.detailPicList = await this.getPictureList(root + '/' + entry[0], entry[1])
}
}
if (this.mainPicList.length >= 0 && this.detailPicList.length >= 0) {
await this.beginAddToDraft(obj.name)
this.$sleepSync(200)
}
},
async getPictureList(root, obj) {
const dirs = obj.entries()
let images = []
for await (const entry of dirs) {
if (entry[1].kind == 'file') {
let prefix = entry[0].split('.').pop()
if (prefix == 'png' || prefix == 'jpg' || prefix == 'jpeg') {
let file = await entry[1].getFile()
let img = await uploadImage(this.folderId, file, this.form.targetMallId, true)
if (null == img) {
} else {
images.push({
name: entry[0].split('.')[0],
url: img
})
}
}
}
}
images.sort((a, b) => {
if (!isNaN(a.name) && !isNaN(b.name)) {
return (Number(a.name) - Number(b.name))
} else {
return a.name - b.name
}
})
return images
},
async beginAddToDraft(name) {
let category = null
let i = 1
while(true) {
if (!this.content['cat'+i+'Id']) {
break
}
i++
}
category = this.content['cat'+(i-1)+'Id']
let data = {catId: category}
this.content.personalizationSwitch = this.content.personalizationSwitch || 0
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/add',
needMallId: true,
mallId: this.form.targetMallId,
data: data})
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
this.content.productDraftId = draftId
// 变化部分
this.content.productName = name
this.content.materialImgUrl = this.mainPicList[0].imgList[0].url
for (let i = 0; i < this.content.productSkcReqs.length; i++) {
for (let j = 0; j < this.mainPicList.length; j++) {
if (this.content.productSkcReqs[i].mainProductSkuSpecReqs[0].specName == this.mainPicList[j].color) {
this.content.productSkcReqs[i].previewImgUrls = this.mainPicList[j].imgList
break
}
}
}
let res1 = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
mallId: this.form.targetMallId,
data: {
...this.content
}})
if (res1.errorCode == 1000000) {
Message.success("商品【" + name + "】成功添加到草稿箱")
} else {
Message.error(res1.errorMsg)
}
} else {
Message.error("【拼多多】" + res.errorMsg)
}
await this.$sleepSync(500)
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -26,6 +26,7 @@
style="margin-top: 8px;" style="margin-top: 8px;"
:current.sync="search.current" :size.sync="search.size" :current.sync="search.current" :size.sync="search.size"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
height="700"
@getList="getList"> @getList="getList">
</ai-table> </ai-table>
</div> </div>
@@ -45,7 +46,7 @@
<template #left> <template #left>
<div class="search-item"> <div class="search-item">
<label style="width:90px">店铺</label> <label style="width:90px">店铺</label>
<el-select v-model="productPage.mallId" @change="productTableData = [], productPage.total = 0, productPage.page =1, getProductList()" placeholder="请选择"> <el-select v-model="productPage.mallId" @change="productTableData = [], productPage.total = 0, productPage.page =1, getProductOrDraftList()" placeholder="请选择">
<el-option <el-option
v-for="item in $store.state.mallList" v-for="item in $store.state.mallList"
:key="item.mallId" :key="item.mallId"
@@ -54,6 +55,21 @@
</el-option> </el-option>
</el-select> </el-select>
</div> </div>
<div class="search-item">
<label style="width:90px">模板来源</label>
<el-select v-model="productPage.from" @change="productTableData = [], productPage.total = 0, productPage.page =1, getProductOrDraftList()" placeholder="请选择">
<el-option
key="0"
label="商品列表"
value="0">
</el-option>
<el-option
key="1"
label="草稿箱列表"
value="1">
</el-option>
</el-select>
</div>
<div class="search-item"> <div class="search-item">
<label style="width:90px">SKC</label> <label style="width:90px">SKC</label>
<el-input size="small" placeholder="请输入SKC多个用,隔开" v-model="productPage.productSkcIds" @keyup.enter.native="productPage.page =1, getProductList()"></el-input> <el-input size="small" placeholder="请输入SKC多个用,隔开" v-model="productPage.productSkcIds" @keyup.enter.native="productPage.page =1, getProductList()"></el-input>
@@ -67,10 +83,11 @@
<el-button @click="productPage= { <el-button @click="productPage= {
page: 1, page: 1,
pageSize: 10, pageSize: 10,
from: '0',
productName: '', productName: '',
productSkcIds: '' productSkcIds: ''
}, getProductList()">重置</el-button> }, getProductOrDraftList()">重置</el-button>
<el-button type="primary" @click="productPage.page =1, getProductList()">查询</el-button> <el-button type="primary" @click="productPage.page =1, getProductOrDraftList()">查询</el-button>
</template> </template>
</ai-search-bar> </ai-search-bar>
<ai-table <ai-table
@@ -80,7 +97,7 @@
:current.sync="productPage.page" :size.sync="productPage.pageSize" :current.sync="productPage.page" :size.sync="productPage.pageSize"
style="margin-top: 8px;" style="margin-top: 8px;"
@selection-change="productHandleSelectionChange" @selection-change="productHandleSelectionChange"
@getList="getProductList"> @getList="getProductOrDraftList">
</ai-table> </ai-table>
</div> </div>
</template> </template>
@@ -98,19 +115,51 @@
width="790px" width="790px"
customFooter customFooter
@close="handleClose"> @close="handleClose">
<el-form class="ai-form" :model="form" label-width="120px" ref="form"> <el-form class="ai-form" :model="form" label-width="160px" ref="form">
<el-form-item
prop="isSemi"
label="是否半托管:"
:rules="[{ required: true, message: '请选择是否半托管', trigger: 'blur' }]">
<el-radio-group v-model="form.isSemi" size="medium">
<el-radio :label="false"></el-radio>
<el-radio :label="true"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="店铺" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择店铺', trigger: 'blur' }]"> <el-form-item label="店铺" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择店铺', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择"> <el-select multiple style="width: 380px" v-model="form.targetMallId" placeholder="请选择">
<el-option <el-option
v-for="item in $store.state.mallList" v-for="item in mallList"
:key="item.mallId" :key="item.mallId"
:label="item.mallName" :label="item.mallName"
:value="item.mallId"> :value="item.mallId">
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="商品分类" style="width: 100%;" prop="targetCatId" :rules="[{ required: true, message: '请选择商品分类', trigger: 'blur' }]"> <el-form-item v-if="form.isSemi" label="经营站点" style="width: 100%;" prop="siteId" :rules="[{ required: true, message: '请选择经营站点', trigger: 'blur' }]">
<el-cascader style="width: 380px" v-model="form.targetCatId" :props="props"></el-cascader> <el-select style="width: 380px" multiple v-model="form.siteId" placeholder="请选择">
<el-option
v-for="item in siteList"
:key="item.siteId"
:label="item.siteName"
:value="item.siteId">
</el-option>
</el-select>
</el-form-item>
<el-form-item
prop="isSameCategory"
label="是否保持相同类目:"
:rules="[{ required: true, message: '请选择是否保持相同类目', trigger: 'blur' }]">
<el-radio-group v-model="form.isSameCategory" size="medium">
<el-radio :label="false"></el-radio>
<el-radio :label="true"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="!form.isSameCategory" label="商品分类" style="width: 100%;" prop="targetCatId" :rules="[{ required: true, message: '请选择商品分类', trigger: 'blur' }]">
<ai-lazy-cascader
style="width: 380px"
v-model="form.targetCatId"
filterable
:props="props"></ai-lazy-cascader>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="dialog-footer" slot="footer"> <div class="dialog-footer" slot="footer">
@@ -126,10 +175,11 @@ import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date' import {timestampToTime} from '@/utils/date'
import {transform} from '@/utils/product' import {transform} from '@/utils/product'
import { Message } from 'element-ui' import { Message } from 'element-ui'
import AiLazyCascader from "@/components/AiLazyCascader.vue"
export default { export default {
name: 'CopyProduct', name: 'CopyProduct',
components: {AiLazyCascader},
data () { data () {
return { return {
search: { search: {
@@ -144,12 +194,12 @@ import { Message } from 'element-ui'
value: 'catId', value: 'catId',
label: 'catName', label: 'catName',
lazy: true, lazy: true,
lazyLoad (node, resolve) { lazyLoad (value, resolve) {
sendChromeAPIMessage({ sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list', url: 'bg-anniston-mms/category/children/list',
needMallId: true, needMallId: true,
data: { data: {
parentCatId: node.value || '' parentCatId: value || ''
} }
}).then(res => { }).then(res => {
if (res.errorCode === 1000000) { if (res.errorCode === 1000000) {
@@ -161,6 +211,32 @@ import { Message } from 'element-ui'
})) }))
} }
}) })
},
lazySearch(queryString, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/search',
needMallId: true,
data: {
searchText: queryString || ''
}
}).then(res => {
if (res.errorCode === 1000000) {
resolve(res.result.categoryPaths.map(v => {
let value = []
let label = []
for (let i = 1; i <= 10; i++ ) {
if (v['cat'+i+'NodeVO']) {
value.push(v['cat'+i+'NodeVO'].catId)
label.push(v['cat'+i+'NodeVO'].catName)
}
}
return {
catId: value,
catName: label
}
}))
}
})
} }
}, },
colConfigs: [ colConfigs: [
@@ -176,7 +252,7 @@ import { Message } from 'element-ui'
dlgShow: false, dlgShow: false,
productTableData: [], productTableData: [],
productPage: {page: 1, pageSize: 10, mallId: '', productName: '', productSkcIds: '', total: 0}, productPage: {page: 1, pageSize: 10, mallId: '', from: '0', productName: '', productSkcIds: '', total: 0},
productColConfigs: [ productColConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'}, { type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'productSpu', label: 'SPU ID', align: 'left' }, { prop: 'productSpu', label: 'SPU ID', align: 'left' },
@@ -189,13 +265,27 @@ import { Message } from 'element-ui'
productIds: [], productIds: [],
form: { form: {
targetMallId: '', targetMallId: '',
targetCatId: [] targetCatId: [],
} isSemi: false,
isSameCategory: true,
siteId: []
},
siteList: []
}
},
computed: {
mallList () {
const filteredData = this.$store.state.mallList.filter(item => {
return item.isSemiManagedMall == this.form.isSemi
})
return filteredData
} }
}, },
created () { created () {
this.getList() this.getList()
this.getSiteList()
if (this.$store.state.mallList.length > 0) { if (this.$store.state.mallList.length > 0) {
this.productPage.mallId = this.$store.state.mallList[0].mallId this.productPage.mallId = this.$store.state.mallList[0].mallId
} }
@@ -203,7 +293,7 @@ import { Message } from 'element-ui'
methods: { methods: {
getList () { getList () {
this.$http.post('/api/product/myPage',null,{ this.$http.post('/api/product/myPage?type=0',null,{
params: { params: {
...this.search ...this.search
} }
@@ -212,6 +302,19 @@ import { Message } from 'element-ui'
this.total = res.data.total this.total = res.data.total
}) })
}, },
getSiteList() {
sendChromeAPIMessage({
url: 'bg-visage-mms/config/common/site/query',
needMallId: true,
mallId: this.$store.state.mallList[0].mallId,
data: {}}).then((res) => {
if (res.success) {
this.siteList = res.result.siteBaseList.filter(item => {
return item.matchSemiManaged
})
}
})
},
remove () { remove () {
if (this.ids.length <= 0) { if (this.ids.length <= 0) {
alert('请选择要删除的商品'); alert('请选择要删除的商品');
@@ -239,7 +342,7 @@ import { Message } from 'element-ui'
// 添加模板 // 添加模板
handleClose() { handleClose() {
this.productTableData = [] this.productTableData = []
this.productPage = {page: 1, pageSize: 10, total: 0} this.productPage = {page: 1, pageSize: 10, from: '0', total: 0}
this.dlgShow = false this.dlgShow = false
}, },
toAddTemplate() { toAddTemplate() {
@@ -252,10 +355,46 @@ import { Message } from 'element-ui'
return; return;
} }
this.dlgShow = true this.dlgShow = true
this.getProductList() this.getProductOrDraftList()
} }
}) })
}, },
getProductOrDraftList() {
if (this.productPage.from == '0') {
this.getProductList()
} else {
this.getDraftList()
}
},
getDraftList() {
let params = {};
params.page = this.productPage.page;
params.pageSize = this.productPage.pageSize;
if (this.productPage.productName) {
params.productName = this.productPage.productName
}
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/pageQuery',
needMallId: true,
mallId: this.productPage.mallId,
data: {
...params
}}).then((res) => {
if (res.errorCode == 1000000) {
this.productPage.total = res.result.total
this.productTableData = res.result.pageItems.map((item) => {
return {
productSpu: item.productDraftId,
productSkc: '',
productName: item.productName,
createTime: timestampToTime(item.updatedAt)
};
})
} else {
this.getDraftList()
}
});
},
getProductList() { getProductList() {
let params = {}; let params = {};
params.page = this.productPage.page; params.page = this.productPage.page;
@@ -301,11 +440,15 @@ import { Message } from 'element-ui'
} }
this.productIds.map((productSpu, index) => { this.productIds.map((productSpu, index) => {
setTimeout(() => { setTimeout(() => {
this.saveTemplate(productSpu, index) if (this.productPage.from == '0') {
this.saveProductTemplate(productSpu, index)
} else {
this.saveDraftTemplate(productSpu, index)
}
}, 200 * index) }, 200 * index)
}) })
}, },
saveTemplate(spu, index) { saveProductTemplate(spu, index) {
sendChromeAPIMessage({ sendChromeAPIMessage({
url: 'bg-visage-mms/product/query', url: 'bg-visage-mms/product/query',
needMallId: true, needMallId: true,
@@ -324,6 +467,7 @@ import { Message } from 'element-ui'
mallName: mallInfo[0].mallName, mallName: mallInfo[0].mallName,
productSpu: res.result.productId, productSpu: res.result.productId,
productName: res.result.productName, productName: res.result.productName,
type: 0,
content: content content: content
}).then(res1 => { }).then(res1 => {
if (res1.code == 0) { if (res1.code == 0) {
@@ -334,7 +478,40 @@ import { Message } from 'element-ui'
} }
}) })
} else { } else {
this.saveTemplate(spu, index) this.saveProductTemplate(spu, index)
}
})
},
saveDraftTemplate(spu, index) {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/query',
needMallId: true,
mallId: this.productPage.mallId,
data: {
productDraftId: spu
}}).then((res) => {
if (res.errorCode == 1000000) {
let content = transform(res.result)
let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.productPage.mallId
})
this.$http.post('/api/product/add', {
mallId: mallInfo[0].mallId,
mallName: mallInfo[0].mallName,
productSpu: spu,
productName: res.result.productName,
type: 0,
content: content
}).then(res1 => {
if (res1.code == 0) {
Message.success("商品【" + res.result.productName + "】成功添加为商品模板")
if (index == this.productIds.length - 1) {
this.getList()
}
}
})
} else {
this.saveDraftTemplate(spu, index)
} }
}) })
}, },
@@ -348,49 +525,82 @@ import { Message } from 'element-ui'
addToDraft() { addToDraft() {
this.$refs.form.validate((valid) => { this.$refs.form.validate((valid) => {
if (valid) { if (valid) {
this.ids.map((id, index) => { this.beginAddToDraft()
let product = this.tableData.filter((item) => {
return item.id == id
})
setTimeout(() => {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/add',
needMallId: true,
mallId: this.form.targetMallId,
data: {
catId: this.form.targetCatId[this.form.targetCatId.length - 1]
}}).then((res) => {
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
let content = JSON.parse(product[0].content)
let i = 0
for (; i < this.form.targetCatId.length; i++) {
content['cat' + (i+1) + 'Id'] = this.form.targetCatId[i]
}
for (; i < 10; i++) {
content['cat' + (i+1) + 'Id'] = ''
}
content.productDraftId = draftId
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
mallId: this.form.targetMallId,
data: {
...content
}}).then((res) => {
if (res.errorCode == 1000000) {
Message.success("商品【" + product[0].productName + "】成功添加到草稿箱")
}
})
} else {
Message.error("【拼多多】" + res.errorMsg)
}
})
}, 1000*index)
})
this.mallDlgShow = false this.mallDlgShow = false
} }
}) })
},
async beginAddToDraft() {
for (let xx = 0 ; xx < this.ids.length; xx++) {
let id = this.ids[xx]
let product = this.tableData.filter((item) => {
return item.id == id
})
let content = JSON.parse(product[0].content)
let category = null
if (this.form.isSameCategory) {
let i = 1
while(true) {
if (!content['cat'+i+'Id']) {
break
}
i++
}
category = content['cat'+(i-1)+'Id']
} else {
category = this.form.targetCatId[this.form.targetCatId.length - 1]
}
let data = {catId: category}
if (this.form.isSemi) {
data.productSemiManagedReq = {
bindSiteIds: this.form.siteId
}
}
content.personalizationSwitch = content.personalizationSwitch || 0
for (let kk = 0; kk < this.form.targetMallId.length; kk++) {
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/add',
needMallId: true,
mallId: this.form.targetMallId[kk],
data: data})
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
if (!this.form.isSameCategory) {
let i = 0
for (; i < this.form.targetCatId.length; i++) {
content['cat' + (i+1) + 'Id'] = this.form.targetCatId[i]
}
for (; i < 10; i++) {
content['cat' + (i+1) + 'Id'] = ''
}
}
content.productDraftId = draftId
if (this.form.isSemi) {
content.productSemiManagedReq = {
bindSiteIds: this.form.siteId
}
}
let res1 = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
mallId: this.form.targetMallId[kk],
data: {
...content
}})
if (res1.errorCode == 1000000) {
Message.success("商品【" + product[0].productName + "】成功添加到草稿箱")
} else {
Message.error(res1.errorMsg)
}
} else {
Message.error("【拼多多】" + res.errorMsg)
}
await this.$sleepSync(500)
}
}
} }
} }
} }

View File

@@ -0,0 +1,402 @@
<template>
<div>
<ai-list class="list" v-loading="isLoading" :element-loading-text="'数据正在加载中……'">
<ai-title
slot="title"
title="商品模板"
tips="请先在当前浏览器登录“速卖通跨境卖家中心”,期间保持登录状态"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<el-button type="button" :icon="'el-icon-delete'" :class="'el-button el-button--primary'" @click="remove()">删除</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="toAddTemplate()">添加商品模板</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="beforeAddToDraft">添加到草稿箱</el-button>
</template>
<template #right>
<el-button size="small" circle icon="el-icon-refresh-right" @click="getList()"></el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 8px;"
:current.sync="search.current" :size.sync="search.size"
@selection-change="handleSelectionChange"
@getList="getList">
</ai-table>
</div>
</template>
</ai-list>
<el-dialog
title="商品列表"
:visible.sync="dlgShow"
:close-on-click-modal="false"
width="80%"
:before-close="handleClose">
<ai-list class="list">
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label style="width:90px">模板来源</label>
<el-select v-model="productPage.from" @change="productTableData = [], productPage.total = 0, productPage.page =1, getProductOrDraftList()" placeholder="请选择">
<el-option
key="0"
label="商品列表"
value="0">
</el-option>
<el-option
key="1"
label="草稿箱列表"
value="1">
</el-option>
</el-select>
</div>
<div class="search-item">
<label style="width:90px">商品名称</label>
<el-input size="small" placeholder="请输入商品名称" v-model="productPage.productName" @keyup.enter.native="productPage.page =1, getProductList()"></el-input>
</div>
</template>
<template #right>
<el-button @click="productPage= {
page: 1,
pageSize: 10,
from: '0',
productName: '',
productSkcIds: ''
}, getProductOrDraftList()">重置</el-button>
<el-button type="primary" @click="productPage.page =1, getProductOrDraftList()">查询</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="productTableData"
:col-configs="productColConfigs"
:total="productPage.total"
:current.sync="productPage.page" :size.sync="productPage.pageSize"
style="margin-top: 8px;"
@selection-change="productHandleSelectionChange"
@getList="getProductOrDraftList">
<el-table-column slot="imageUrl" width="120px" label="图片" align="center">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.imageUrl" class="image" :preview-src-list="[scope.row.imageUrl]" />
</div>
</template>
</el-table-column>
</ai-table>
</div>
</template>
</ai-list>
<span slot="footer" class="dialog-footer">
<el-button @click="dlgShow = false"> </el-button>
<el-button type="primary" @click="saveProduct">添加到模板</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {sendChromeAPIMessage, sendAliexpressAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import {transformShein} from '@/utils/product'
import { Message } from 'element-ui'
import AiLazyCascader from "@/components/AiLazyCascader.vue"
export default {
name: 'CopyProduct',
components: {AiLazyCascader},
data () {
return {
search: {
current: 1,
size: 10,
productName: '',
mallName: '',
startDate: '',
endDate: ''
},
colConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'productSpu', label: 'SPU', width: 180, align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'createTime', label: '添加时间', width: '180px', fixed: 'right'}
],
tableData: [],
total: 0,
ids: [],
dlgShow: false,
productTableData: [],
productPage: {page: 1, pageSize: 10, mallId: '', from: '0', productName: '', productSkcIds: '', total: 0},
productColConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'productSpu', label: 'SPU ID', width: 180, align: 'left' },
{ slot: 'imageUrl', label: '图片', align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'createTime', label: '创建时间', width: 180, align: 'left' }
],
productIds: [],
siteList: [],
isLoading: false
}
},
created () {
this.getList()
},
methods: {
getList () {
this.$http.post('/api/product/myPage?type=1',null,{
params: {
...this.search
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
remove () {
if (this.ids.length <= 0) {
alert('请选择要删除的商品');
return;
}
this.$confirm('确定要删除?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post('/api/product/delByIds',this.ids
).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
handleSelectionChange(val) {
this.ids = [];
val.forEach(e => {
this.ids.push(e.id);
});
},
// 添加模板
handleClose() {
this.productTableData = []
this.productPage = {page: 1, pageSize: 10, from: '0', total: 0}
this.dlgShow = false
},
toAddTemplate() {
this.$http.post('/api/malluser/info').then(res => {
if (res.code == 0) {
this.$store.commit('setUserInfo', res.data)
if (res.data.flag != 1) {
Message.error('您的账号未激活或已失效,请激活后使用')
this.$store.commit('setActiveDlgShow', true)
return;
}
this.dlgShow = true
this.getProductOrDraftList()
}
})
},
getProductOrDraftList() {
if (this.productPage.from == '0') {
this.getProductList()
} else {
this.getDraftList()
}
},
getDraftList() {
let params = {};
params.page = this.productPage.page;
params.pageSize = this.productPage.pageSize;
if (this.productPage.productName) {
params.productName = this.productPage.productName
}
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/pageQuery',
needMallId: true,
mallId: this.productPage.mallId,
data: {
...params
}}).then((res) => {
if (res.errorCode == 1000000) {
this.productPage.total = res.result.total
this.productTableData = res.result.pageItems.map((item) => {
return {
productSpu: item.productDraftId,
productSkc: '',
productName: item.productName,
createTime: timestampToTime(item.updatedAt)
};
})
} else {
this.getDraftList()
}
});
},
async getProductList() {
this.isLoading = true
let url = "https://seller-acs.aliexpress.com/h5/mtop.global.merchant.self.product.manager.render.list/1.0/?jsv=2.7.2&appKey=30267743&t=1713978403051&sign=ba2bda69b4a2695c7279d4bc05f51741&v=1.0&timeout=15000&H5Request=true&url=mtop.global.merchant.self.product.manager.render.list&__channel-id__=701301&api=mtop.global.merchant.self.product.manager.render.list&type=originaljson&dataType=json&valueType=original&x-i18n-regionID=AE"
url = url + "&data=" + encodeURIComponent(
JSON.stringify({
"channelId": "701301",
"jsonBody": JSON.stringify({
"tab": "online_product",
"sort": {},
"filter": {
"queryCategory": null,
"lowerPrice": null,
"upperPrice": null,
"status": "0",
"productId": null,
"pagination": {
"pageSize": this.productPage.page,
"current": this.productPage.pageSize
}
}
}),
"from":"SELF",
"bizParam":"{\"version\":\"simple\"}"
})
)
let res = await sendAliexpressAPIMessage({
url: url
})
res = JSON.parse(res)
this.isLoading = false
if (res.ret.indexOf('SUCCESS::调用成功') >= 0) {
this.productPage.total = res.data.data.pagination.total
this.productTableData = res.data.data.table.dataSource.map((item) => {
return {
productSpu: item.productId,
productName: item.itemDesc.title,
imageUrl: item.itemDesc.imageUrl,
createTime: timestampToTime(item.selfCreateDesc)
};
})
}
},
productHandleSelectionChange(val) {
this.productIds = [];
val.forEach(e => {
this.productIds.push(e.productSpu);
});
},
saveProduct() {
if (this.productIds.length <= 0) {
Message.error('请选择商品');
return;
}
this.productIds.map((productSpu, index) => {
setTimeout(() => {
if (this.productPage.from == '0') {
this.saveProductTemplate(productSpu, index)
} else {
this.saveDraftTemplate(productSpu, index)
}
}, 200 * index)
})
},
saveProductTemplate(spu, index) {
sendGeiwohuoAPIMessage({
url: 'spmp-api-prefix/spmp/product/get_product_detail',
data: {
spu_name: spu
}}).then((res) => {
if (res.code == 0) {
let content = transformShein(res.info)
let productName = res.info.multi_language_name_list.filter(item => {
return item.language == 'zh-cn'
})
console.log(productName)
this.$http.post('/api/product/add', {
productSpu: res.info.spu_name,
productName: productName[0].name,
type: 2,
content: content
}).then(res1 => {
if (res1.code == 0) {
Message.success("商品【" + productName[0].name + "】成功添加为商品模板")
if (index == this.productIds.length - 1) {
this.getList()
}
}
})
} else {
this.saveProductTemplate(spu, index)
}
})
},
saveDraftTemplate(spu, index) {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/query',
needMallId: true,
mallId: this.productPage.mallId,
data: {
productDraftId: spu
}}).then((res) => {
if (res.errorCode == 1000000) {
let content = transformShein(res.info)
let productName = res.info.multi_language_name_list.filter(item => {
return item.language == 'zh-cn'
})
this.$http.post('/api/product/add', {
productSpu: spu,
productName: productName[0].name,
content: content,
type: 2
}).then(res1 => {
if (res1.code == 0) {
Message.success("商品【" + productName[0].name + "】成功添加为商品模板")
if (index == this.productIds.length - 1) {
this.getList()
}
}
})
} else {
this.saveDraftTemplate(spu, index)
}
})
},
beforeAddToDraft() {
if (this.ids.length <= 0) {
Message.error('请选择商品模板');
return;
}
this.addToDraft()
},
addToDraft() {
this.ids.map((id, index) => {
let product = this.tableData.filter((item) => {
return item.id == id
})
let content = JSON.parse(product[0].content)
setTimeout(() => {
sendGeiwohuoAPIMessage({
url: 'spmp-api-prefix/spmp/product/save_draft',
data: content
}).then((res) => {
if (res.code == 0) {
Message.success("商品【" + product[0].productName + "】成功添加到草稿箱")
} else {
Message.error(res.errorMsg)
}
})
}, 1000*index)
})
}
}
}
</script>
<style scoped lang="scss">
</style>

486
src/view/product/Draft.vue Normal file
View File

@@ -0,0 +1,486 @@
<template>
<div>
<ai-list class="list" v-loading="isLoading" :element-loading-text="loadingText">
<ai-title
slot="title"
title="草稿箱管理"
tips="请先在当前浏览器登录“拼多多跨境卖家中心”,期间保持登录状态"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
<label style="width:90px">站点</label>
<el-select v-model="siteId" placeholder="请选择" size="small">
<el-option
v-for="item in siteList"
:key="item.siteId"
:label="item.siteName"
:value="item.siteId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<el-button v-if="$store.state.mallName" type="button" :class="'el-button el-button--primary'" @click="batchSubmitConfig()">批量提交</el-button>
</template>
<template #right>
<label style="width:140px; text-align: right;">起始页</label>
<el-input size="small" placeholder="请输入起始页" style="width: 100px; display: inline" type="number" v-model="search.startPage"></el-input>
<label style="width:140px; text-align: right;">结束页</label>
<el-input size="small" placeholder="请输入起始页" style="width: 100px; display: inline" type="number" v-model="search.endPage"></el-input>
<label style="width:120px">商品名称</label>
<el-input clearable size="small" style="display: inline" placeholder="请输入商品名称" v-model="search.productName"></el-input>
<el-button type="primary" @click="toLoad">查询</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
style="margin-top: 8px;"
@selection-change="handleSelectionChange"
:isShowPagination="false">
</ai-table>
</div>
</template>
</ai-list>
<ai-dialog
title="配置参数"
:visible.sync="dlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="dlgShow = false">
<el-alert
title="默认“承诺发货时效”为“2个工作日内发货”"
type="success"
:closable="false">
</el-alert>
<el-form class="ai-form" :model="configForm" label-width="160px" ref="configForm">
<el-form-item label="运费模板" style="width: 100%;" prop="freightTemplateId" :rules="[{ required: true, message: '请选择运费模板', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="configForm.freightTemplateId" placeholder="请选择运费模板">
<el-option
v-for="item in freightTmplList"
:key="item.freightTemplateId"
:label="item.templateName"
:value="item.freightTemplateId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="发货仓" style="width: 100%;" prop="wareHouseList" :rules="[{ required: true, message: '请选择发货仓', trigger: 'blur' }]">
<el-select style="width: 380px" multiple v-model="configForm.wareHouseList" placeholder="请选择发货仓">
<el-option
v-for="item in wareHouseList"
:key="item.warehouseId"
:label="item.warehouseName"
:value="item.warehouseId">
</el-option>
</el-select>
</el-form-item>
<el-form-item
prop="stockNumber"
label="库存数量:"
:rules="[{ required: true, message: '请输入库存数量', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入库存数量" type="number" v-model="configForm.stockNumber"></el-input>
</el-form-item>
<el-form-item
prop="upMoney"
label="上浮金额:"
:rules="[{ required: true, message: '请输入上浮金额', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入上浮金额" type="number" v-model="configForm.upMoney"></el-input>
</el-form-item>
<el-form-item
prop="brand"
label="品牌:">
<el-select style="width: 380px" clearable v-model="configForm.brandId" placeholder="请选择品牌">
<el-option
v-for="item in brandList"
:key="item.vid"
:label="item.brandNameEn"
:value="item.vid">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="dlgShow = false"> </el-button>
<el-button type="primary" @click="toBtachSubmit">确定</el-button>
</div>
</ai-dialog>
</div>
</template>
<script>
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import {transformSubmitForHalf} from '@/utils/product'
import { Message, MessageBox } from 'element-ui'
export default {
name: 'Draft',
data () {
return {
mallId: '',
siteId: '',
siteList: [],
search: {
startPage: 1,
endPage: 10,
productName: ''
},
colConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'spuId', label: 'SPU ID', width: '100px', align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'category', label: '商品分类', align: 'left'},
{ prop: 'bindSites', label: '经营站点', width: '120px', align: 'left'},
{ prop: 'updatedAt', label: '修改时间', width: '180px', fixed: 'right'}
],
isLoading: false,
page: 1,
pageSize: 100,
tableData: [],
dlgShow: false,
configForm: {
sendGoodsSecond: 172800,
freightTemplateId: '',
stockNumber: 5,
siteList: [],
upMoney: 0.0,
wareHouseList: [],
brandId: '',
brandName: ''
},
wareHouseList: [],
freightTmplList: [],
ids: [],
sites: [],
loadingText: '拼命加载中……',
successNumber: 0,
errorNumber: 0,
brandList: [],
selectBrand: {}
}
},
created () {
this.getSiteList()
},
methods: {
toLoad() {
if (!this.mallId) {
Message.error("请选择店铺")
return
}
if (!this.siteId) {
Message.error("请选择站点")
return
}
if (!this.search.startPage || (this.search.startPage < 1)) {
Message.error("起始页不能为空且不能小于1")
return
}
if (!this.search.endPage || (this.search.startPage < 1)) {
Message.error("结束页不能为空")
return
}
if (this.search.startPage > this.search.endPage) {
Message.error("起始页不能大于结束页")
return
}
this.isLoading = true
this.loadingText = '拼命加载中……'
this.tableData = []
this.page = this.search.startPage
this.getDraftList()
},
async getDraftList () {
let sites = []
if (this.siteId) {
sites.push(this.siteId)
}
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/pageQuery',
needMallId: true,
mallId: this.mallId,
data: {
page: this.page,
pageSize: this.pageSize,
...this.search,
bindSiteIds: sites
}})
if (res.success && res.errorCode == 1000000) {
res.result.pageItems.map(item => {
let category = ''
for (let i = 1; i < 11; i++) {
if (item.categories['cat'+i].catName) {
category += '->' + item.categories['cat'+i].catName
} else {
break
}
}
category = category.substring(2)
let site = []
let siteId = []
item.copyFromFullyToSemiInfo.bindSites.map(item1 => {
site.push(item1.siteName)
siteId.push(item1.siteId)
})
this.tableData.push({
spuId: item.productDraftId,
productName: item.productName,
category: category,
bindSites: site.join(','),
bindSiteIds: siteId.join(','),
updatedAt: timestampToTime(item.updatedAt)
})
})
if (this.page == 1 && res.result.pageItems.length == 0) {
this.isLoading = false
}
else if (res.result.pageItems.length == this.pageSize && this.page < this.search.endPage) {
this.page ++
this.getDraftList()
} else {
this.isLoading = false
}
}
},
async batchSubmitConfig() {
if (this.ids.length <= 0) {
Message.error('请选择要提交的商品');
return;
}
this.sites = []
this.sites.push(this.siteId)
this.configForm.siteList = this.sites
this.freightTmplList = []
this.wareHouseList = []
this.configForm.freightTemplateId = ''
this.configForm.wareHouseList = []
await this.getFreightTmplList(this.sites)
await this.getWareahouseList(this.sites)
await this.getBrandList()
this.dlgShow = true
},
async toBtachSubmit() {
this.$refs.configForm.validate((valid) => {
if (valid) {
if (this.configForm.brandId) {
this.brandList.map(item => {
if (item.vid == this.configForm.brandId) {
this.selectBrand = item
}
})
}
this.isLoading = true
this.dlgShow = false
this.successNumber = 0
this.errorNumber = 0
this.submitSpu(0)
}
})
},
async submitSpu(idx) {
if (idx == this.ids.length) {
this.isLoading = false
MessageBox.alert('总共提交' + this.ids.length + '个商品,成功' + this.successNumber + '个,失败' + this.errorNumber + '个', '提示', {
confirmButtonText: '确定',
callback: action => {
this.toLoad()
}
})
return
} else {
this.loadingText = '正在提交第' + (idx+1) + '/' + this.ids.length + '个商品,成功' + this.successNumber + '个,失败' + this.errorNumber + '个'
}
let spuId = this.ids[idx]
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/query',
needMallId: true,
mallId: this.mallId,
data: {
productDraftId: spuId
}})
if (res.success && res.errorCode == 1000000) {
let catId = this.getCategoryId(res.result)
let brandProperty = await this.queryProperty(catId)
let content = transformSubmitForHalf(res.result, this.configForm, spuId)
if (this.configForm.brandId && brandProperty) {
let flag = false
content.productPropertyReqs.map(item => {
if (item.propName == '品牌名') {
flag = true
item.pid = brandProperty.pid
item.refPid = brandProperty.refPid
item.templatePid = brandProperty.templatePid
item.propValue = this.selectBrand.brandNameEn
item.vid = this.selectBrand.vid
}
})
if (!flag) {
content.productPropertyReqs.push({
"templatePid": brandProperty.templatePid,
"pid": brandProperty.pid,
"refPid": brandProperty.refPid,
"propName": "品牌名",
"vid": this.selectBrand.vid,
"propValue": this.selectBrand.brandNameEn,
"valueUnit": "",
"valueExtendInfo": "",
"numberInputValue": ""
})
}
}
let res1 = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/add',
needMallId: true,
mallId: this.mallId,
data: content})
if (res1.success && res1.errorCode == 1000000) {
this.successNumber += 1
} else {
this.errorNumber += 1
}
setTimeout(() => {
idx ++
this.submitSpu(idx)
}, 1000)
}
},
async getFreightTmplList(siteId) {
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/freight/template/list',
needMallId: true,
mallId: this.mallId,
data: {
siteIds: siteId
}})
if (res.success && res.errorCode == 1000000) {
res.result.freightTmplSimpleList.map(item => {
this.freightTmplList.push({
freightTemplateId: item.freightTemplateId,
templateName: item.templateName
})
})
}
},
async getWareahouseList(siteId) {
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/starlaod/btg/sales/stock/querySiteCanSelectWarehouseList',
needMallId: true,
mallId: this.mallId,
data: {
siteIdList: siteId
}})
if (res.success && res.errorCode == 1000000) {
res.result.warehouseDTOList.map(item => {
item.validWarehouseList.map(item1 => {
this.wareHouseList.push({
warehouseId: item1.warehouseId,
warehouseName: item1.warehouseName
})
})
})
}
},
handleSelectionChange(val) {
this.ids = [];
val.forEach(e => {
this.ids.push(e.spuId);
});
},
async getSiteList() {
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/config/common/site/query',
needMallId: true,
mallId: this.$store.state.mallList[0].mallId,
data: {}})
if (res.success && res.errorCode == 1000000) {
this.siteList = res.result.siteBaseList.filter(item => {
return item.matchSemiManaged
})
}
},
async getBrandList() {
let res = await sendChromeAPIMessage({
url: 'bg-anniston-mms/category/brand/property/value/pageQuery',
needMallId: true,
mallId: this.mallId,
data: {
"page": 1,
"pageSize": 50
}})
if (res.success && res.errorCode == 1000000) {
this.brandList = res.result.pageItems
}
},
async queryProperty(catId) {
let res = await sendChromeAPIMessage({
url: 'bg-anniston-mms/category/template/query',
needMallId: true,
mallId: this.mallId,
data: {
"catId": catId,
"productCreateTime": null,
"langList": [
"en"
]
}})
if (res.success && res.errorCode == 1000000) {
let properties = res.result.properties.filter(item => {
return item.name == '品牌名'
})
if (properties && properties.length > 0) {
return properties[0]
} else {
return null
}
}
},
getCategoryId(obj) {
let i = 1
while(true) {
if (!obj.categories['cat'+i].catId) {
break
}
i++
}
return obj.categories['cat'+(i-1)].catId
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,118 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="查找买手"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-card title="查找买手" style="padding-bottom: 40px;">
<div style="text-align: center; display: flex; flex-wrap: nowrap;justify-content: center;">
<label style="width:90px; height: 25px; font-size: 25px">店铺</label>
<el-select style="width:180px;" v-model="form.mallId" placeholder="请选择" clearable size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
<label style="width:120px; height: 25px; font-size: 25px">SKC ID</label>
<el-input style="width:250px; " clearable size="small" placeholder="请输入SKC ID" v-model="form.skc"></el-input>
<el-button style="margin-left: 5px" type="primary" @click="beforeFind">查找</el-button>
</div>
</ai-card>
</div>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage} from '@/api/chromeApi'
export default {
name: 'FindSeller',
data () {
return {
form: {
mallId: '',
skc: ''
},
isLoading: false,
}
},
created () {
},
methods: {
beforeFind() {
this.$userCheck(this.form.mallId).then(() => {
this.toFind()
}).catch((err) => {
this.form.mallId = ''
})
},
async toFind() {
if (!this.form.mallId) {
Message.error("请选择店铺")
return
}
if (!this.form.skc || (this.form.skc < 1)) {
Message.error("请输入SKC")
return
}
this.isLoading = true
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sales/management/listWarehouse',
needMallId: true,
mallId: this.form.mallId,
data: {
pageNo: 1,
pageSize: 10,
isLack: 0,
priceAdjustRecentDays: 7,
productSkcIdList: [this.form.skc]
}})
if (res.errorCode == 1000000) {
if (res.result.subOrderList.length == 0) {
Message.error("没有查询结果请检查店铺和SKC是否正确")
} else {
this.$confirm(res.result.subOrderList[0].buyerName, '查询结果', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
center: true
}).then(() => {
})
}
}
this.isLoading = false
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,364 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="商品列表"
tips="每页为100条商品数据"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="form.mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label>起始页</label>
<el-input size="small" placeholder="请输入起始页" type="number" v-model="startPage"></el-input>
</div>
<div class="search-item">
<label>结束页</label>
<el-input size="small" placeholder="请输入起始页" type="number" v-model="endPage"></el-input>
</div>
<div class="search-item">
<label>是否在售</label>
<el-select clearable v-model="reqData.skcSiteStatus" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<div class="search-item">
<label>站点</label>
<el-select v-model="reqData.bindSiteId" placeholder="请选择" size="small">
<el-option
v-for="item in siteList"
:key="item.siteId"
:label="item.siteName"
:value="item.siteId">
</el-option>
</el-select>
</div>
<div class="search-item">
<label>合规下架风险</label>
<el-select v-model="reqData.certPunishTypes" multiple placeholder="请选择" size="small">
<el-option
v-for="item in certPunishTypes"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</div>
<div class="search-item">
<label>资质上传状态</label>
<el-select v-model="reqData.productCertAuditStatuses" multiple placeholder="请选择" size="small">
<el-option
v-for="item in productCertAuditStatusList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</div>
<div class="search-item">
<label>商品名称</label>
<el-input clearable size="small" placeholder="请输入商品名称" v-model="reqData.productName"></el-input>
</div>
<div class="search-item">
<label>库存起始值</label>
<el-input size="small" placeholder="请输入库存起始值" type="number" v-model="reqData.jitStockQuantitySection.leftValue"></el-input>
</div>
<div class="search-item">
<label>库存结束值</label>
<el-input size="small" placeholder="请输入库存结束值" type="number" v-model="reqData.jitStockQuantitySection.rightValue"></el-input>
</div>
</template>
<template #right>
<el-button type="primary" @click="toLoad">加载</el-button>
</template>
</ai-search-bar>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<template #right>
<json-excel
:data="tableData"
:fields="jsonFields"
name="商品列表.xls"
worksheet="商品列表">
<el-button type="primary" :disabled="tableData.length == 0">下载数据</el-button>
</json-excel>
</template>
<ai-table
:isShowPagination="false"
:tableData="tableData"
:col-configs="colConfigs"
:total="tableData.length"
height="500"
style="margin-top: 8px;"
@getList="() => {}">
<el-table-column slot="productName" width="200px" :show-overflow-tooltip='true' label="商品名称" fixed="left">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.mainImageUrl" style="width: 40px; height: 40px" class="image" :preview-src-list="[scope.row.mainImageUrl]" />
{{ scope.row.productName }}
</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</div>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import JsonExcel from 'vue-json-excel'
export default {
name: 'MyNormalOrder',
data () {
return {
form: {
mallId: ''
},
startPage: 1,
endPage: 10,
reqData: {
productName: '',
page: 1,
pageSize: 100,
skcSiteStatus: '',
bindSiteId: '',
certPunishTypes: [],
productCertAuditStatuses: [],
jitStockQuantitySection: {
leftValue: '',
rightValue: ''
}
},
options: [
{label: '是', value: 1},
{label: '否', value: 0}
],
colConfigs: [
{ slot: 'productName', label: '商品名称', width: '180px', align: 'left', fixed: 'left' },
{ prop: 'category', label: '分类', width: '140px', align: 'left', fixed: 'left' },
{ prop: 'productId', label: 'SPU ID', width: '120px', align: 'left' },
{ prop: 'productSkcId', label: 'SKC ID', width: '120px', align: 'left' },
{ prop: 'productSkuId', label: 'SKU ID', width: '120px', align: 'left' },
{ prop: 'skcSiteStatus', label: '是否在售', width: '120px', align: 'left' },
{ prop: 'extCode', label: 'SKC货号', width: '100px', align: 'left' },
{ prop: 'skuExtCode', label: 'SKU货号', width: '160px', align: 'left' },
{ prop: 'specName', label: 'SKU属性', width: '100px', align: 'left' },
{ prop: 'skuStockQuantity', label: '库存', width: '100px', align: 'left' },
{ prop: 'productCertAuditStatus', label: '资质上传状态', width: '120px', align: 'left' },
{ prop: 'certPunishType', label: '合规下架风险', width: '120px', align: 'left' },
{ prop: 'supplierPrice', label: '申报价格(CNY)', width: '180px', align: 'left' },
{ prop: 'todaySalesVolume', label: '今日销量', width: '100px', align: 'left' },
{ prop: 'createdAt', label: '上架时间', width: '160px', align: 'left' }
],
isLoading: false,
tableData: [],
jsonFields: {
"商品名称": "productName",
"图片": "mainImageUrl",
"分类": "category",
"SPU ID": "productId",
"SKC ID": "productSkcId",
"SKU ID": "productSkuId",
"是否在售": "skcSiteStatus",
"SKC货号": "extCode",
"SKU货号": "skuExtCode",
"SKU属性": "specName",
"库存": "skuStockQuantity",
"资质上传状态": "productCertAuditStatus",
"合规下架风险": "certPunishType",
"申报价格(CNY)": "supplierPrice",
"今日销量": "todaySalesVolume",
"上架时间": "createdAt"
},
certPunishTypes: [
{id: 1, name: '已经处罚'},
{id: 2, name: '即将下架'},
{id: 3, name: '下架风险预警'},
],
productCertAuditStatusList: [
{id: 1, name: '待上传'},
{id: 2, name: '上传中'},
{id: 3, name: '上传失败'},
{id: 4, name: '上传成功'},
],
currentIndex: 0,
siteList: []
}
},
components: {
JsonExcel
},
created () {
this.getSiteList()
},
methods: {
async getSiteList() {
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/config/common/site/query',
needMallId: true,
mallId: this.$store.state.mallList[0].mallId,
data: {}})
if (res.success && res.errorCode == 1000000) {
this.siteList = res.result.siteBaseList.filter(item => {
return item.matchSemiManaged
})
}
},
beforeGetList() {
this.$userCheck(this.form.mallId).then(() => {
this.toLoad()
}).catch((err) => {
this.form.mallId = ''
})
},
toLoad() {
if (!this.form.mallId) {
Message.error("请选择店铺")
return
}
if (!this.startPage || (this.startPage < 1)) {
Message.error("起始页不能为空且不能小于1")
return
}
if (!this.endPage || (this.startPage < 1)) {
Message.error("结束页不能为空")
return
}
if (this.startPage > this.endPage) {
Message.error("起始页不能大于结束页")
return
}
this.reqData.page = this.startPage
this.tableData = []
this.currentIndex = 0
this.isLoading = true
this.load()
},
load() {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/skc/pageQuery',
needMallId: true,
mallId: this.form.mallId,
anti: true,
data: this.reqData}).then((res) => {
if (res.errorCode == 1000000) {
for(let i = 0;i < res.result.pageItems.length; i++) {
let item = res.result.pageItems[i];
let data = {};
data.productName = item.productName
data.mainImageUrl = item.mainImageUrl
data.productId = item.productId
data.productSkcId = item.productSkcId
data.skcSiteStatus = item.skcSiteStatus == '1'? '在售': ''
data.createdAt = timestampToTime(item.createdAt)
data.extCode = item.extCode
data.productCertAuditStatus = '-'
if (item.productCertAuditStatus == 1) {
data.productCertAuditStatus = '待上传'
} else if (item.productCertAuditStatus == 2) {
data.productCertAuditStatus = '上传中'
} else if (item.productCertAuditStatus == 3) {
data.productCertAuditStatus = '上传失败'
} else if (item.productCertAuditStatus == 4) {
data.productCertAuditStatus = '上传成功'
}
data.certPunishType = '-'
if (item.productCertPunish?.certPunishType == 1) {
data.certPunishType = '已经处罚'
} else if (item.productCertPunish?.certPunishType == 2) {
data.certPunishType = '即将下架'
} else if (item.productCertPunish?.certPunishType == 3) {
data.certPunishType = '下架风险预警'
}
data.category = ''
for (let i = 1; i < 11; i++) {
if (item.categories['cat'+i].catName) {
data.category += '->' + item.categories['cat'+i].catName
} else {
break
}
}
data.category = data.category.substring(2)
for(let k = 0; k < item.productSkuSummaries.length; k++) {
let temp = item.productSkuSummaries[k].productSkuSpecList.map(item2 => {
return item2.parentSpecName + "" + item2.specName
})
let skuStockQuantity = '-'
if (item.productSkuSummaries[k].productSkuSemiManagedStock) {
skuStockQuantity = item.productSkuSummaries[k].productSkuSemiManagedStock.skuStockQuantity
}
data = {...data,
productSkuId: item.productSkuSummaries[k].productSkuId,
skuExtCode: item.productSkuSummaries[k].extCode,
supplierPrice: item.productSkuSummaries[k].supplierPrice / 100,
specName: temp.join(""),
skuStockQuantity,
todaySalesVolume: item.productSkuSummaries[k].todaySalesVolume
}
this.tableData.push(data)
}
}
if (res.result.pageItems.length == this.reqData.pageSize && this.reqData.page < this.endPage) {
this.reqData.page ++
this.load()
} else {
this.isLoading = false
}
}
})
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -1,395 +0,0 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
title="商品模板"
tips="请先在当前浏览器登录“拼多多跨境卖家中心”,期间保持登录状态"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<el-button type="button" :icon="'el-icon-delete'" :class="'el-button el-button--primary'" @click="remove()">删除</el-button>
<el-button v-if="$store.state.mallName" type="button" :class="'el-button el-button--primary'" @click="toAddTemplate()">添加商品模板</el-button>
<el-button v-if="$store.state.mallName" type="button" :class="'el-button el-button--primary'" @click="beforeAddToDraft">添加到草稿箱</el-button>
</template>
<template #right>
<el-button size="small" circle icon="el-icon-refresh-right" @click="getList()"></el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 8px;"
:current.sync="search.current" :size.sync="search.size"
@selection-change="handleSelectionChange"
@getList="getList">
</ai-table>
</div>
</template>
</ai-list>
<el-dialog
title="商品列表"
:visible.sync="dlgShow"
:close-on-click-modal="false"
width="80%"
:before-close="handleClose">
<ai-list class="list">
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label style="width:90px">店铺</label>
<el-select v-model="productPage.mallId" @change="productTableData = [], productPage.total = 0, productPage.page =1, getProductList()" placeholder="请选择">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
<div class="search-item">
<label style="width:90px">SKC</label>
<el-input size="small" placeholder="请输入SKC多个用,隔开" v-model="productPage.productSkcIds" @keyup.enter.native="productPage.page =1, getProductList()"></el-input>
</div>
<div class="search-item">
<label style="width:90px">商品名称</label>
<el-input size="small" placeholder="请输入商品名称" v-model="productPage.productName" @keyup.enter.native="productPage.page =1, getProductList()"></el-input>
</div>
</template>
<template #right>
<el-button @click="productPage= {
page: 1,
pageSize: 10,
productName: '',
productSkcIds: ''
}, getProductList()">重置</el-button>
<el-button type="primary" @click="search.page =1, getProductList()">查询</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="productTableData"
:col-configs="productColConfigs"
:total="productPage.total"
:current.sync="productPage.page" :size.sync="productPage.pageSize"
style="margin-top: 8px;"
@selection-change="productHandleSelectionChange"
@getList="getProductList">
</ai-table>
</div>
</template>
</ai-list>
<span slot="footer" class="dialog-footer">
<el-button @click="dlgShow = false"> </el-button>
<el-button type="primary" @click="saveProduct">添加到模板</el-button>
</span>
</el-dialog>
<ai-dialog
title="请选择店铺"
:visible.sync="mallDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="handleClose">
<el-form class="ai-form" :model="form" label-width="120px" ref="form">
<el-form-item label="店铺" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择店铺', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品分类" style="width: 100%;" prop="targetCatId" :rules="[{ required: true, message: '请选择商品分类', trigger: 'blur' }]">
<el-cascader style="width: 380px" v-model="form.targetCatId" :props="props"></el-cascader>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="mallDlgShow = false"> </el-button>
<el-button type="primary" @click="addToDraft">确定</el-button>
</div>
</ai-dialog>
</div>
</template>
<script>
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import {transform} from '@/utils/product'
import { Message } from 'element-ui'
export default {
name: 'ReducePrice',
data () {
return {
search: {
current: 1,
size: 10,
productName: '',
mallName: '',
startDate: '',
endDate: ''
},
props: {
value: 'catId',
label: 'catName',
lazy: true,
lazyLoad (node, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list',
needMallId: true,
data: {
parentCatId: node.value || ''
}
}).then(res => {
if (res.errorCode === 1000000) {
resolve(res.result.categoryNodeVOS.map(v => {
return {
...v,
leaf: v.isLeaf
}
}))
}
})
}
},
colConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'productSpu', label: 'SPU', align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'mallName', label: '来源店铺', align: 'left'},
{ prop: 'createTime', label: '添加时间', width: '180px', fixed: 'right'}
],
tableData: [],
total: 0,
ids: [],
dlgShow: false,
productTableData: [],
productPage: {page: 1, pageSize: 10, mallId: '', productName: '', productSkcIds: '', total: 0},
productColConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'productSpu', label: 'SPU ID', align: 'left' },
{ prop: 'productSkc', label: 'SKC ID', align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'createTime', label: '创建时间', align: 'left' }
],
mallDlgShow: false,
productIds: [],
form: {
targetMallId: '',
targetCatId: []
}
}
},
created () {
this.getList()
if (this.$store.state.mallList.length > 0) {
this.productPage.mallId = this.$store.state.mallList[0].mallId
}
},
methods: {
getList () {
this.$http.post('/api/product/myPage',null,{
params: {
...this.search
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
remove () {
if (this.ids.length <= 0) {
alert('请选择要删除的商品');
return;
}
this.$confirm('确定要删除?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post('/api/product/delByIds',this.ids
).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
handleSelectionChange(val) {
this.ids = [];
val.forEach(e => {
this.ids.push(e.id);
});
},
// 添加模板
handleClose() {
this.productTableData = []
this.productPage = {page: 1, pageSize: 10, total: 0}
this.dlgShow = false
},
toAddTemplate() {
this.$http.post('/api/malluser/info').then(res => {
if (res.code == 0) {
this.$store.commit('setUserInfo', res.data)
if (res.data.flag != 1) {
Message.error('您的账号未激活或已失效,请激活后使用')
this.$store.commit('setActiveDlgShow', true)
return;
}
this.dlgShow = true
this.getProductList()
}
})
},
getProductList() {
let params = {};
params.page = this.productPage.page;
params.pageSize = this.productPage.pageSize;
if (this.productPage.productName) {
params.productName = this.productPage.productName
}
if (this.productPage.productSkcIds) {
params.productSkcIds = this.productPage.productSkcIds.split(',')
}
sendChromeAPIMessage({
url: 'bg-visage-mms/product/skc/pageQuery',
needMallId: true,
mallId: this.productPage.mallId,
data: {
...params
}}).then((res) => {
if (res.errorCode == 1000000) {
this.productPage.total = res.result.total
this.productTableData = res.result.pageItems.map((item) => {
return {
productSpu: item.productId,
productSkc: item.productSkcId,
productName: item.productName,
createTime: timestampToTime(item.createdAt)
};
})
} else {
Message.error("【拼多多】" + res.errorMsg)
}
});
},
productHandleSelectionChange(val) {
this.productIds = [];
val.forEach(e => {
this.productIds.push(e.productSpu);
});
},
saveProduct() {
if (this.productIds.length <= 0) {
Message.error('请选择商品');
return;
}
this.productIds.map((productSpu, index) => {
setTimeout(() => {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/query',
needMallId: true,
mallId: this.productPage.mallId,
data: {
productEditTaskUid: '',
productId: productSpu
}}).then((res) => {
if (res.errorCode == 1000000) {
let content = transform(res.result)
let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.productPage.mallId
})
this.$http.post('/api/product/add', {
mallId: mallInfo[0].mallId,
mallName: mallInfo[0].mallName,
productSpu: res.result.productId,
productName: res.result.productName,
content: content
}).then(res1 => {
if (res1.code == 0) {
Message.success("商品【" + res.result.productName + "】成功添加为商品模板")
if (index == this.productIds.length - 1) {
this.getList()
}
}
})
}
})
}, 200 * index)
})
},
beforeAddToDraft() {
if (this.ids.length <= 0) {
Message.error('请选择商品模板');
return;
}
this.mallDlgShow = true
},
addToDraft() {
this.$refs.form.validate((valid) => {
if (valid) {
this.ids.map((id, index) => {
let product = this.tableData.filter((item) => {
return item.id == id
})
setTimeout(() => {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/add',
needMallId: true,
mallId: this.form.targetMallId,
data: {
catId: this.form.targetCatId[this.form.targetCatId.length - 1]
}}).then((res) => {
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
let content = JSON.parse(product[0].content)
let i = 0
for (; i < this.form.targetCatId.length; i++) {
content['cat' + (i+1) + 'Id'] = this.form.targetCatId[i]
}
for (; i < 10; i++) {
content['cat' + (i+1) + 'Id'] = ''
}
content.productDraftId = draftId
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
mallId: this.form.targetMallId,
data: {
...content
}}).then((res) => {
if (res.errorCode == 1000000) {
Message.success("商品【" + product[0].productName + "】成功添加到草稿箱")
}
})
} else {
Message.error("【拼多多】" + res.errorMsg)
}
})
}, 1000*index)
})
this.mallDlgShow = false
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,272 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="上新生命周期管理"
tips="请先在当前浏览器登录“拼多多跨境卖家中心”期间保持登录状态。每页为100条商品数据"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="form.mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label>起始页</label>
<el-input size="small" placeholder="请输入起始页" type="number" v-model="startPage"></el-input>
</div>
<div class="search-item">
<label>结束页</label>
<el-input size="small" placeholder="请输入起始页" type="number" v-model="endPage"></el-input>
</div>
<div class="search-item">
<label>状态</label>
<el-select v-model="status" placeholder="请选择" size="small">
<el-option
v-for="item in statusList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</div>
<div class="search-item">
<label>是否合规待办</label>
<el-select v-model="isViolationInfo" placeholder="请选择" size="small">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</template>
<template #right>
<el-button type="primary" @click="toLoad">加载</el-button>
</template>
</ai-search-bar>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<template #right>
<json-excel
:data="tableData"
:fields="jsonFields"
name="商品列表.xls"
worksheet="商品列表">
<el-button type="primary" :disabled="tableData.length == 0">下载数据</el-button>
</json-excel>
</template>
<ai-table
:isShowPagination="false"
:tableData="tableData"
:col-configs="colConfigs"
:total="tableData.length"
height="800"
style="margin-top: 8px;"
@getList="() => {}">
<el-table-column slot="productName" width="200px" :show-overflow-tooltip='true' label="商品名称" fixed="left">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.mainImageUrl" style="width: 40px; height: 40px" class="image" :preview-src-list="[scope.row.mainImageUrl]" />
{{ scope.row.productName }}
</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</div>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import JsonExcel from 'vue-json-excel'
export default {
name: 'SellerSelect',
data () {
return {
form: {
mallId: ''
},
startPage: 1,
endPage: 10,
reqData: {
pageNum: 1,
pageSize: 100,
secondarySelectStatusList: []
},
colConfigs: [
{ slot: 'productName', label: '商品名称', width: '180px', align: 'left', fixed: 'left' },
{ prop: 'category', label: '分类', width: '220px', align: 'left', fixed: 'left' },
{ prop: 'siteName', label: '站点', width: '140px', align: 'left', fixed: 'left' },
{ prop: 'productId', label: 'SPU ID', width: '120px', align: 'left' },
{ prop: 'productSkcId', label: 'SKC ID', width: '120px', align: 'left' },
{ prop: 'extCode', label: 'SKC货号', width: '120px', align: 'left' },
{ prop: 'punishInfo', label: '违规信息', width: '120px', align: 'left' },
{ prop: 'violationInfo', label: '合规待办项', width: '200px', align: 'left' },
{ prop: 'supplierPrice', label: '申报价格(CNY)', width: '180px', align: 'left' },
{ prop: 'createdAt', label: '创建时间', width: '160px', align: 'left' }
],
isLoading: false,
tableData: [],
jsonFields: {
"商品名称": "productName",
"图片": "mainImageUrl",
"分类": "category",
"站点": "siteName",
"SPU ID": "productId",
"SKC ID": "productSkcId",
"SKC货号": "extCode",
"违规信息": "punishInfo",
"合规待办项": "violationInfo",
"申报价格(CNY)": "supplierPrice",
"创建时间": "createdAt"
},
statusList: [
{id: 1, name: '未发布到站点'},
{id: 2, name: '已下架/终止'}
],
options: [
{label: '是', value: 1},
{label: '否', value: 0}
],
status: 1,
isViolationInfo: ''
}
},
components: {
JsonExcel
},
methods: {
beforeGetList() {
this.$userCheck(this.form.mallId).then(() => {
this.toLoad()
}).catch((err) => {
this.form.mallId = ''
})
},
toLoad() {
if (!this.form.mallId) {
Message.error("请选择店铺")
return
}
if (!this.startPage || (this.startPage < 1)) {
Message.error("起始页不能为空且不能小于1")
return
}
if (!this.endPage || (this.startPage < 1)) {
Message.error("结束页不能为空")
return
}
if (this.startPage > this.endPage) {
Message.error("起始页不能大于结束页")
return
}
if (this.status == 1) {
this.reqData.secondarySelectStatusList = [10, 11]
} else if (this.status == 2) {
this.reqData.secondarySelectStatusList = [13]
}
if (this.isViolationInfo == 0) {
this.reqData.supplierTodoTypeList = []
} else if (this.isViolationInfo == 1) {
this.reqData.supplierTodoTypeList = [7]
}
this.reqData.pageNum = this.startPage
this.tableData = []
this.isLoading = true
this.load()
},
load() {
sendChromeAPIMessage({
url: 'marvel-supplier/api/xmen/select/search',
needMallId: true,
mallId: this.form.mallId,
anti: true,
data: this.reqData}).then((res) => {
if (res.errorCode == 1000000) {
for(let i = 0;i < res.result.dataList.length; i++) {
let item = res.result.dataList[i];
let data = {};
data.productName = item.productName
data.mainImageUrl = item.carouselImageUrlList[0]
data.productId = item.productId
data.productSkcId = item.productSkcId
data.siteName = item.siteName
data.createdAt = timestampToTime(item.productCreatedAt)
data.category = item.fullCategoryName.join('->')
for (let k = 0; k < item.skcList.length; k++) {
data = {...data, productSkcId: item.skcList[k].skcId, punishInfo: item.skcList[k].punishInfo, extCode: item.skcList[k].extCode, supplierPrice: item.skcList[k].supplierPrice}
if (item.skcList[k].addSiteViolationInfoVO && item.skcList[k].addSiteViolationInfoVO.violationCodeList?.length > 0) {
let temp = ''
for (let j = 0; j < item.skcList[k].addSiteViolationInfoVO.violationCodeList.length; j++) {
if (item.skcList[k].addSiteViolationInfoVO.violationCodeList[j] == 1) {
temp += ',商品资质'
}
if (item.skcList[k].addSiteViolationInfoVO.violationCodeList[j] == 2) {
temp += ',商品标签实拍图'
}
}
temp = temp.substring(1)
data.violationInfo = temp
}
this.tableData.push(data)
}
}
if (res.result.dataList.length == this.reqData.pageSize && this.reqData.pageNum < this.endPage) {
this.reqData.pageNum ++
this.load()
} else {
this.isLoading = false
}
}
})
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,251 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="售后赔付统计"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label>账务时间</label>
<el-date-picker
v-model="searchDate"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>
</div>
</template>
<template #right>
<el-button type="primary" @click="beforeGetList">加载</el-button>
</template>
</ai-search-bar>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<template #right>
<json-excel
:data="tableData"
:fields="jsonFields"
name="售后赔付统计列表.xls"
worksheet="售后赔付统计列表">
<el-button type="primary" :disabled="tableData.length == 0">下载数据</el-button>
</json-excel>
</template>
<ai-table
:isShowPagination="false"
:tableData="tableData"
:col-configs="colConfigs"
:total="tableData.length"
height="700"
style="margin-top: 8px;"
@getList="() => {}">
<el-table-column slot="productName" width="400px" :show-overflow-tooltip='true' label="商品名称" fixed="left">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.productUrl" style="width: 40px; height: 40px" class="image" :preview-src-list="[scope.row.productUrl]" />
{{ scope.row.productName }}
</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</div>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage } from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import JsonExcel from 'vue-json-excel'
export default {
name: 'AfterSaleStat',
data () {
return {
mallId: '',
searchDate: [],
reqData: {
pageNum: 1,
pageSize: 100
},
colConfigs: [
{ prop: 'productName', label: '商品名称', width: '400px', align: 'left', fixed: 'left' },
{ prop: 'productSkuId', label: 'SKU ID', align: 'left' },
{ prop: 'skuCode', label: 'SKU货号', align: 'left' },
{ prop: 'deductionAmount', label: '扣款总金额', align: 'left', sortable: true, 'sort-method': (a, b) => a.deductionAmount - b.deductionAmount },
{ prop: 'times', label: '扣款次数', align: 'left', sortable: true, 'sort-method': (a, b) => a.times - b.times },
/*{ prop: 'supplierPrice', label: '供货价', align: 'left', sortable: true, 'sort-method': (a, b) => a.supplierPrice - b.supplierPrice },
{ prop: 'cost', label: '成本', align: 'left', sortable: true, 'sort-method': (a, b) => a.cost - b.cost },
{ prop: 'profitTimes', label: '相当于X件白卖', align: 'left', sortable: true, 'sort-method': (a, b) => a.profitTimes - b.profitTimes }*/
],
isLoading: false,
tableData: [],
jsonFields: {
"商品名称": "productName",
"SKU ID": "productSkuId",
"SKU货号": "skuCode",
"扣款总金额": "deductionAmount",
"扣款次数": "times"
},
costList: []
}
},
components: {
JsonExcel
},
methods: {
beforeGetList() {
this.$userCheck(this.mallId).then(() => {
this.toLoad()
}).catch((err) => {
this.mallId = ''
})
},
async toLoad() {
if (!this.mallId) {
Message.error("请选择店铺")
return
}
if (!this.searchDate) {
Message.error("请选择时间范围")
return
}
this.reqData.pageNum = 1
this.tableData = []
this.isLoading = true
let startTime = this.searchDate[0].getTime()
let endTime = this.searchDate[1].getTime() + 86400000
while(true) {
if ((endTime - startTime) > 2592000000) {
this.reqData.pageNum = 1
await this.getAfterSaleDeductList(startTime, startTime + 2592000000)
startTime = startTime + 2592000000
} else {
this.reqData.pageNum = 1
await this.getAfterSaleDeductList(startTime, endTime)
break
}
}
this.isLoading = false
},
async getAfterSaleDeductList(startTime, endTime) {
let res = await sendChromeAPIMessage({
url: `api/merchant/fund/deduction/after-sales-compensation/query`,
needMallId: true,
mallId: this.mallId,
data: {
...this.reqData,
beginFundTime: startTime,
endFundTime: endTime
}})
if (res.errorCode == 1000000) {
for (let i = 0; i < res.result.resultList.length; i++) {
let item = res.result.resultList[i]
let flag = false
for (let j = 0; j < item.productSkuIdList.length; j++) {
for ( let k = 0; k < this.tableData.length; k++) {
if (item.productSkuIdList[j] == this.tableData[k].productSkuId) {
flag = true
this.tableData[k].times++
this.tableData[k].deductionAmount = Math.round((this.tableData[k].deductionAmount + new Number(item.deductionAmount.digitalText))*100)/100
if (!this.tableData[k].skuCode) {
this.tableData[k].skuCode = item.skuExtCodeList[j]
}
break
}
}
if (!flag) {
this.tableData.push({
productName: item.goodsNameList[j],
productSkuId: item.productSkuIdList[j],
deductionAmount: new Number(item.deductionAmount.digitalText),
skuCode: item.skuExtCodeList[j],
times: 1,
supplierPrice: ''
})
flag = true
}
}
}
if ((this.reqData.pageSize * this.reqData.pageNum) < res.result.total) {
this.reqData.pageNum ++
await this.getAfterSaleDeductList(startTime, endTime)
}
}
},
async getProductList(params) {
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/skc/pageQuery',
needMallId: true,
mallId: this.mallId,
data: {
...params
}})
if (res.errorCode == 1000000) {
res.result.pageItems.map((item) => {
item.productSkuSummaries.map(item1 => {
for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i].productSkuId == item1.productSkuId) {
this.tableData[i].supplierPrice = item1.supplierPrice / 100
this.tableData[i].productUrl = item1.thumbUrl
this.tableData[i].skcCode = item.extCode
this.tableData[i].skuCode = item1.extCode
break
}
}
})
})
if ((params.page * params.pageSize) < res.result.total) {
params.page ++
await this.getProductList(params)
}
}
},
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,310 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="售后统计"
tips="请事先在“成本管理”维护好成本,否则部分计算不准确"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label>售后申请年份</label>
<el-date-picker
v-model="reqData.afsApplyYear"
type="year"
value-format='yyyy'
placeholder="选择年">
</el-date-picker>
</div>
<div class="search-item">
<label>扣罚倍数</label>
<el-select v-model="reqData.punishRatioList" multiple placeholder="请选择" size="small">
<el-option
v-for="item in punishRatioOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</template>
<template #right>
<el-button type="primary" @click="beforeGetList">加载</el-button>
</template>
</ai-search-bar>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<template #right>
<json-excel
:data="tableData"
:fields="jsonFields"
name="售后统计列表.xls"
worksheet="售后统计列表">
<el-button type="primary" :disabled="tableData.length == 0">下载数据</el-button>
</json-excel>
</template>
<ai-table
:isShowPagination="false"
:tableData="tableData"
:col-configs="colConfigs"
:total="tableData.length"
height="700"
style="margin-top: 8px;"
@getList="() => {}">
<el-table-column slot="productName" width="400px" :show-overflow-tooltip='true' label="商品名称" fixed="left">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.productUrl" style="width: 40px; height: 40px" class="image" :preview-src-list="[scope.row.productUrl]" />
{{ scope.row.productName }}
</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</div>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage, sendTemuSellerAgentMessage } from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import JsonExcel from 'vue-json-excel'
export default {
name: 'AfterSaleStat',
data () {
return {
mallId: '',
reqData: {
pageNo: 1,
pageSize: 100,
afsApplyYear: '2024',
punishRatioList: []
},
punishRatioOptions: [
{label: '0', value: '0'},
{label: '1', value: '1'},
{label: '1.5', value: '1.5'},
{label: '2.5', value: '2.5'},
{label: '5', value: '5'},
],
colConfigs: [
{ slot: 'productName', label: '商品名称', width: '400px', align: 'left', fixed: 'left' },
{ prop: 'skcCode', label: 'SKC货号', align: 'left', height: '40px', fixed: 'left' },
{ prop: 'productSkuId', label: 'SKU ID', align: 'left' },
{ prop: 'skuCode', label: 'SKU货号', align: 'left' },
{ prop: 'rawQualityScore', label: '品质分', align: 'left', sortable: true, 'sort-method': (a, b) => a.rawQualityScore - b.rawQualityScore },
{ prop: 'punishRatio', label: '扣罚总倍数', align: 'left', sortable: true, 'sort-method': (a, b) => a.punishRatio - b.punishRatio },
{ prop: 'times', label: '扣罚次数', align: 'left', sortable: true, 'sort-method': (a, b) => a.times - b.times },
{ prop: 'supplierPrice', label: '供货价', align: 'left', sortable: true, 'sort-method': (a, b) => a.supplierPrice - b.supplierPrice },
{ prop: 'deductionAmount', label: '预计扣款', align: 'left', sortable: true, 'sort-method': (a, b) => a.deductionAmount - b.deductionAmount },
{ prop: 'cost', label: '成本', align: 'left', sortable: true, 'sort-method': (a, b) => a.cost - b.cost },
{ prop: 'profitTimes', label: '相当于X件白卖', align: 'left', sortable: true, 'sort-method': (a, b) => a.profitTimes - b.profitTimes }
],
isLoading: false,
tableData: [],
jsonFields: {
"商品名称": "productName",
"图片URL": "productUrl",
"SKC货号": "skcCode",
"SKU ID": "productSkuId",
"SKU货号": "skuCode",
"品质分": "rawQualityScore",
"扣罚总倍数": "punishRatio",
"扣罚次数": "times",
"供货价": "supplierPrice",
"预计扣款": "deductionAmount",
"成本": "cost",
"相当于X件白卖": "profitTimes"
},
costList: []
}
},
components: {
JsonExcel
},
methods: {
beforeGetList() {
this.$userCheck(this.mallId).then(() => {
this.toLoad()
}).catch((err) => {
this.mallId = ''
})
},
async toLoad() {
if (!this.mallId) {
Message.error("请选择店铺")
return
}
this.reqData.pageNo = 1
this.tableData = []
this.currentIndex = 0
this.isLoading = true
await this.getAfterSalesList('101')
this.reqData.pageNo = 1
await this.getAfterSalesList('202')
this.reqData.pageNo = 1
await this.getAfterSalesList('102')
let skuIds = []
this.tableData.map(item => {
skuIds.push(item.productSkuId)
})
this.reqData.pageNo = 1
await this.getProductList({page: this.reqData.pageNo,
pageSize: this.reqData.pageSize}, skuIds)
this.costList = []
await this.getSkuCostList()
this.isLoading = false
},
async getAfterSalesList(zone) {
this.reqData.bizDrType = zone
let res = await sendTemuSellerAgentMessage({
url: `mms/api/appalachian/afs/queryPageV2`,
needMallId: true,
mallId: this.mallId,
data: this.reqData})
if (res.errorCode == 1000000) {
for (let i = 0; i < res.result.list.length; i++) {
let item = res.result.list[i]
let flag = false
for (let j = 0; j < item.productSkuId.length; j++) {
for ( let k = 0; k < this.tableData.length; k++) {
if (item.productSkuId[j] == this.tableData[k].productSkuId) {
flag = true
this.tableData[k].times++
this.tableData[k].punishRatio = this.tableData[k].punishRatio + new Number(item.punishRatio)
break
}
}
if (!flag) {
this.tableData.push({
productName: item.goodsName,
productUrl: '',
skcCode: '',
productSkuId: item.productSkuId[j],
punishRatio: new Number(item.punishRatio),
rawQualityScore: item.rawQualityScore,
skuCode: '',
times: 1,
cost: '',
supplierPrice: '',
deductionAmount: null,
profitTimes: ''
})
flag = true
}
}
}
if ((this.reqData.pageSize * this.reqData.pageNo) < res.result.total) {
this.reqData.pageNo ++
await this.getAfterSalesList(zone)
}
} else if (res.error_code == 40001) {
this.isLoading = false
this.$store.commit("setTemuAlertShow", true)
}
},
async getProductList(params, skuIds) {
let productSkuIds = []
let i = (params.page - 1) * params.pageSize
let j = 0
for (; i < skuIds.length; i++) {
productSkuIds.push(skuIds[i])
j ++
if (j == params.pageSize) break
}
if (productSkuIds.length == 0) return
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/skc/pageQuery',
needMallId: true,
mallId: this.mallId,
data: {
page: 1,
pageSize: params.pageSize,
productSkuIds
}})
if (res.errorCode == 1000000) {
res.result.pageItems.map((item) => {
item.productSkuSummaries.map(item1 => {
for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i].productSkuId == item1.productSkuId) {
this.tableData[i].supplierPrice = item1.supplierPrice / 100
this.tableData[i].deductionAmount = Math.round((item1.supplierPrice / 100 * this.tableData[i].punishRatio) * 100)/ 100
this.tableData[i].productUrl = item1.thumbUrl
this.tableData[i].skcCode = item.extCode
this.tableData[i].skuCode = item1.extCode
break
}
}
})
})
params.page ++
await this.getProductList(params, skuIds)
}
},
async getSkuCostList() {
this.$http.post(`/api/skuCost/listAll`, null, {
params: {
mallId: this.mallId
}
}).then(res => {
if (res.code == 0) {
this.costList = res.data
this.tableData.map(item => {
for (let i = 0; i < this.costList.length; i++) {
if (item.productSkuId == this.costList[i].sku) {
item.cost = this.costList[i].costPrice
item.profitTimes = Math.round((item.supplierPrice * item.punishRatio + item.cost * item.times) / (item.supplierPrice - item.cost) * 100) / 100
}
}
})
}
})
},
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,366 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="成本管理"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" @change="beforeGetList" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<div style="margin-bottom: 5px">
<el-alert
title="郑重承诺由于业务逻辑需要TEMU助手将会以最小范围内存储SKU成本信息平台将会做好保密不泄露、不出售任何数据。使用与成本管理以及利润计算等相关功能将视为授权TEMU助手存储相关信息。"
type="error"
:closable="false">
</el-alert>
</div>
<ai-search-bar>
<template #left>
<div class="search-item">
<el-checkbox v-model="search.unSet">只显示未填写</el-checkbox>
</div>
<div class="search-item">
<label>SKC ID</label>
<el-input size="small" clearable placeholder="请输入SKC ID" v-model="search.skc"></el-input>
</div>
<div class="search-item">
<label>SKU ID</label>
<el-input size="small" clearable placeholder="请输入SKU ID" v-model="search.sku"></el-input>
</div>
<div class="search-item">
<label>SKC货号</label>
<el-input size="small" clearable placeholder="请输入SKC货号" v-model="search.skcCode"></el-input>
</div>
</template>
<template #right>
<!--<el-button type="primary" @click="toLoad">加载</el-button>-->
</template>
</ai-search-bar>
<ai-card title="SKU明细" style="padding-bottom: 40px;">
<template #right>
<el-button type="primary" @click="exportToExcel">导出</el-button>
<el-button type="primary" @click="toUpload">导入</el-button>
</template>
<ai-table
:isShowPagination="false"
:tableData="filteredData"
:col-configs="colConfigs"
height="600"
style="margin-top: 8px;"
@getList="() => {}">
<el-table-column slot="costPrice" label="成本价格" :sortable="true" :sort-method="(a, b) => a.costPrice - b.costPrice">
<template slot-scope="{ row }">
<el-input
v-if="row.edit"
v-model="row.editValue"
type="number"
size="small"
></el-input>
<span v-else>{{ row.costPrice }}</span>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" width="80px" show-overflow-tooltip align="center" fixed="right">
<template slot-scope="{ row }">
<el-button
v-if="!row.edit"
size="small"
icon="el-icon-edit"
@click="row.edit = true"
></el-button>
<el-button
v-if="row.edit"
size="small"
type="success"
icon="el-icon-circle-check"
@click="handleSave(row)"
></el-button>
</template>
</el-table-column>
</ai-table>
</ai-card>
<AiDialog
title="成本导入"
:visible.sync="costDlgShow"
:close-on-click-modal="false"
customFooter
width="1290px">
<el-form :model="costForm" ref="costForm" label-width="180px" class="form">
<el-form-item label="上传excel" prop="file" :rules="[{ required: true, message: '请上传文件', trigger: 'blur' }]" style="width: 100%;">
<ai-uploader isImport v-model="costForm.file" fileType="file" :limit="1"
acceptType=".xlsx," :clearable="false">
<template #trigger>
<el-button icon="iconfont iconfangda">选择文件</el-button>
</template>
<template #tips>最多上传1个文件,单个文件最大10MB仅支持Excel格式</template>
</ai-uploader>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="costDlgShow = false">关 闭</el-button>
<el-button type="primary" @click="importConfirm">确 定</el-button>
</span>
</AiDialog>
</template>
</ai-list>
</template>
<script>
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import { Message } from 'element-ui'
import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
export default {
name: 'CostManageTemu',
data () {
return {
isLoading: false,
list: [],
mallId: '',
colConfigs: [
{ prop: 'skc', label: 'SKC ID', align: 'left' },
{ prop: 'skcCode', label: 'SKC货号', align: 'left' },
{ prop: 'sku', label: 'SKU ID', align: 'left' },
{ prop: 'skuCode', label: 'SKU货号', align: 'left' },
{ prop: 'price', label: '申报价格', align: 'left', sortable: true, 'sort-method': (a, b) => a.price - b.price },
{ slot: 'costPrice', label: '成本价格', align: 'left' },
{ prop: 'profitPercent', label: '利润率(%)', align: 'left', sortable: true, 'sort-method': (a, b) => a.profitPercent - b.profitPercent }
],
search: {
unSet: false,
skc: '',
sku: '',
skcCode: ''
},
tableData: [],
currentPage: 1,
costList: [],
costDlgShow: false,
costForm: {
file: null
}
}
},
computed: {
filteredData() {
const filteredData = this.list.filter(item => {
let flag1 = true, flag2 = true, flag3 = true, flag4 = true
if (this.search.unSet) {
if (item.costPrice) flag1 = false
}
if (this.search.skc) {
if (!(item.skc == this.search.skc)) {
flag2 = false
}
}
if (this.search.sku) {
if (!(item.sku == this.search.sku)) {
flag3 = false
}
}
if (this.search.skcCode) {
if (!(item.skcCode == this.search.skcCode)) {
flag5 = false
}
}
return flag1 && flag2 && flag3 && flag4
})
return filteredData
}
},
methods: {
beforeGetList() {
this.list = []
this.currentPage = 1
this.costList = []
if (!this.mallId) {
Message.error("请先选择店铺")
return
}
this.$userCheck(this.mallId).then(() => {
this.isLoading = true
this.getList()
}).catch((err) => {
this.mallId = ''
this.isLoading = false
})
},
async getList () {
let res = await sendChromeAPIMessage({
url: `bg-visage-mms/product/skc/pageQuery`,
needMallId: true,
mallId: this.mallId,
data: {
page: this.currentPage,
pageSize: 100
}})
if (res.errorCode == 1000000) {
for(let i = 0;i < res.result.pageItems.length; i++) {
let item = res.result.pageItems[i];
let data = {skc: item.productSkcId,
skcCode: item.extCode
}
for (let j = 0; j < item.productSkuSummaries.length; j++) {
let sku = item.productSkuSummaries[j]
data = {...data, sku: sku.productSkuId,
skuAttr: null,
skuCode: sku.extCode,
price: sku.supplierPrice / 100,
costPrice: '',
profitPercent: null,
edit: false,
editValue: null
}
this.list.push(data)
}
}
if (res.result.pageItems.length == 100 && (res.result.total > 100*this.currentPage)) {
this.currentPage++
await this.sleepSync(200)
await this.getList()
} else {
this.getSkuCostList()
}
}
},
sleepSync(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
},
getSkuCostList() {
this.$http.post(`/api/skuCost/listAll`, null, {
params: {
mallId: this.mallId
}
}).then(res => {
if (res.code == 0) {
this.costList = res.data
for (let i = 0; i < this.costList.length; i++) {
for (let j = 0; j < this.list.length; j++) {
if (this.costList[i].sku == this.list[j].sku) {
this.list[j].costPrice = this.costList[i].costPrice
this.list[j].editValue = this.costList[i].costPrice
this.list[j].profitPercent = Math.round((this.list[j].price - this.list[j].costPrice) / this.list[j].price * 10000) /100
}
}
}
this.isLoading = false
} else {
this.isLoading = false
}
})
},
handleSave(row) {
this.$http.post(`/api/skuCost/addOrUpdate`, [{
mallId: this.mallId,
costPrice: row.editValue,
skc: row.skc,
sku: row.sku
}]).then((res) => {
if (res.code == 0) {
row.edit = false
row.costPrice = row.editValue
row.profitPercent = Math.round((row.price - row.costPrice) / row.price * 10000) / 100
Message.success("修改成功")
}
})
},
toUpload() {
this.costDlgShow = true
},
importConfirm() {
this.isLoading = true
this.$refs.costForm.validate((valid) => {
const data = new FormData()
data.append('file', this.costForm.file[0].raw);
this.$http.post(`/api/skuCost/importStock`, data).then(res => {
if (res.code === 0) {
this.costDlgShow = false
this.isLoading = false
this.$message.success('导入成功')
this.getList()
} else {
this.isLoading = false
}
})
})
},
exportToExcel() {
// 假设你有一个表格数据的数组
const data = [
["店铺ID", "SKC ID", "SKC货号", "SKU ID", "属性集", "SKU货号", "申报价格", "成本价格"]
]
this.filteredData.map(item => {
data.push([this.mallId, item.skc, item.skcCode, item.sku, item.skuAttr, item.skuCode, item.price, item.costPrice])
})
// 将数据转换为工作表
const worksheet = XLSX.utils.aoa_to_sheet(data);
// 创建工作簿并添加工作表
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
// 生成Excel文件
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
// 使用blob和FileReader创建一个Blob URL
const dataBlob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
const blobUrl = window.URL.createObjectURL(dataBlob);
// 使用saveAs下载文件
saveAs(dataBlob, 'SKU列表.xlsx');
// 清理
window.URL.revokeObjectURL(blobUrl);
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,428 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="账务明细统计"
tips="数据来源于“账户资金->对账中心->账务明细”"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">时间选择</label>
<el-date-picker
v-model="dateRange"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="yyyy-MM-dd">
</el-date-picker>
</div>
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
<el-button type="button" :class="'el-button el-button--primary'" @click="toBeginStat">开始统计</el-button>
<el-button type="primary" icon="el-icon-download" @click="downloadPicture">下载图片</el-button>
</div>
</template>
</ai-title>
<template slot="content">
<div id="app-content">
<ai-card title="数据概览" style="padding-bottom: 40px;">
<el-divider content-position="center">收入概览</el-divider>
<div>
<el-row :gutter="20">
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value-style="{color: 'red'}"
:value="transIncome + nonSllerDuty"
title="总收入"
>
</el-statistic>
</el-card>
</div>
</el-col>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value="transIncome"
title="交易收入"
>
</el-statistic>
</el-card>
</div>
</el-col>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:value="nonSllerDuty"
:precision="2"
title="非商责平台售后补贴金额"
>
</el-statistic>
</el-card>
</div>
</el-col>
</el-row>
</div>
<el-divider content-position="center">支出概览</el-divider>
<div>
<el-row>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value-style="{color: 'red'}"
:value="logisticAmount+customerOutcome+afterSaleAmount+eprAmount+withdrawAmount"
title="总支出"
></el-statistic>
</el-card>
</div>
</el-col>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value="logisticAmount"
title="仓储综合服务费"
></el-statistic>
</el-card>
</div>
</el-col>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value="customerOutcome"
title="消费者退款金额"
></el-statistic>
</el-card>
</div>
</el-col>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value="afterSaleAmount"
title="消费者及履约保障-售后问题"
>
</el-statistic>
</el-card>
</div>
</el-col>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value="eprAmount"
title="合规EPR物流包装环保费"
>
</el-statistic>
</el-card>
</div>
</el-col>
<el-col :span="4">
<div>
<el-card shadow="always" class="amountItem">
<el-statistic
group-separator=","
:precision="2"
:value="withdrawAmount"
title="提现"
>
</el-statistic>
</el-card>
</div>
</el-col>
</el-row>
</div>
</ai-card>
</div>
<ai-dialog
title="提示"
:visible.sync="isTipDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="isDlgShow = false">
<div style="display: block;height: 30px;">卖家中心出现图形验证码请前往卖家中心账户资金->对账中心->账务明细随机选择一条数据点击查看详情处理验证码</div>
<div class="dialog-footer" slot="footer">
<el-button @click="isTipDlgShow = false">取消</el-button>
<el-button style="width: 180px;" @click="toGoon">已验证继续统计</el-button>
<el-button type="primary" @click="gotoValid">前往验证</el-button>
</div>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import {sendChromeAPIMessage } from '@/api/chromeApi'
import { Message } from 'element-ui'
import html2canvas from 'html2canvas'
export default {
name: 'ExportBillStatTemu',
data () {
return {
mallId: null,
dateRange: null,
transIncome: 0.0,
customerOutcome: 0.0,
nonSllerDuty: 0.0,
afterSaleAmount: 0.0,
logisticAmount: 0.0,
eprAmount: 0.0,
withdrawAmount: 0.0,
currentPage: 1,
pageSize: 100,
flowList: [],
currentIndex: 0,
isTipDlgShow: false,
isLoading: false
}
},
mounted () {
},
methods: {
toBeginStat() {
if (!this.dateRange) {
Message.error("请选择统计时间范围")
return
}
if (!this.mallId) {
Message.error("请选择店铺")
return
}
this.$userCheck(this.mallId).then(async () => {
this.currentPage = 1
this.flowList = []
this.isLoading = true
this.transIncome = 0.0
this.customerOutcome = 0.0
this.nonSllerDuty = 0.0
this.afterSaleAmount = 0.0
this.logisticAmount = 0.0
this.eprAmount = 0.0
this.withdrawAmount = 0.0
this.currentIndex = 0
await this.beginStat()
this.nonSllerDuty = Math.round(this.nonSllerDuty * 100) / 100
this.isLoading = false
}).catch((err) => {
this.mallId = ''
this.isLoading = false
})
},
async beginStat() {
let res = await sendChromeAPIMessage({
url: 'api/merchant/fund/detail/pageSearch',
needMallId: true,
mallId: this.mallId,
data: {
moneyChangeTypeList: [],
fundType: [],
beginTime: this.dateRange[0].getTime(),
endTime: this.dateRange[1].getTime() + 86400*1000 - 1,
pageSize: this.pageSize,
pageNum: this.currentPage
}})
if (res.errorCode == 1000000) {
this.flowList = this.flowList.concat(res.result.resultList)
if (res.result.resultList.length == this.pageSize) {
this.currentPage ++
await this.beginStat()
} else {
this.currentIndex = 0
await this.stat()
}
}
},
async stat() {
for (; this.currentIndex < this.flowList.length; this.currentIndex++) {
if (this.isTipDlgShow) {
this.currentIndex --
break
}
if (this.flowList[this.currentIndex].fundType == 100) { // 结算
await this.$sleepSync(300)
await this.querySattement(this.flowList[this.currentIndex])
} else if (this.flowList[this.currentIndex].fundType == 200) { // 提现
this.withdrawAmount += this.flowList[this.currentIndex].originAmount / 100
} else if (this.flowList[this.currentIndex].fundType == 400 && this.flowList[this.currentIndex].bizType == "deduct_risk_punish") { // 消费者及履约保障-售后问题
this.afterSaleAmount += Number(this.flowList[this.currentIndex].amount.replace('¥', ''))
} else if (this.flowList[this.currentIndex].fundType == 400 && this.flowList[this.currentIndex].bizType == "deduct_storage_service_fee") { // 仓储综合服务费
this.logisticAmount += Number(this.flowList[this.currentIndex].amount.replace('¥', ''))
} else if (this.flowList[this.currentIndex].fundType == 400 && this.flowList[this.currentIndex].bizType == "deduct_epr") { // 合规EPR物流包装环保费
this.eprAmount += Number(this.flowList[this.currentIndex].amount.replace('¥', ''))
}
}
},
async toGoon() {
this.isTipDlgShow = false
this.isLoading = true
await this.stat()
},
async querySattement(obj) {
let res = await sendChromeAPIMessage({
url: 'api/merchant/fund/detail/item',
needMallId: true,
mallId: this.mallId,
anti: true,
data: {
fundType: obj.fundType,
batchId: obj.transSn,
createdTime: obj.createTime
}})
if (res.errorCode == 1000000) {
for (let i = 0; i < res.result.length; i++) {
if (res.result[i].fundItemType == 1) { // 交易收入
this.transIncome += Number(res.result[i].amount)
} else if (res.result[i].fundItemType == 3) { // 消费者退款金额
this.customerOutcome += Number(res.result[i].amount)
} else if (res.result[i].fundItemType == 5) { // 非商责平台售后补贴金额
this.nonSllerDuty += Number(res.result[i].amount)
}
}
} else if (res.error_code == '54001') {
this.isLoading = false
this.isTipDlgShow = true
}
},
async downloadPicture() {
try {
const element = document.getElementById('app-content');
const canvas = await html2canvas(element);
// 创建一个图片元素
const img = new Image();
img.src = canvas.toDataURL('image/png');
// 添加到body中以便下载
document.body.appendChild(img);
// 触发下载
const a = document.createElement('a');
a.style.display = 'none'
a.href = img.src;
a.download = '账务明细统计.png';
a.click();
document.body.removeChild(img);
} catch (error) {
console.error('Error saving image:', error);
}
},
gotoValid() {
window.open("https://seller.kuajingmaihuo.com/labor/bill", '_blank')
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
.top {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
.item {
flex: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
&:nth-of-type(1) {
color: #2266ff;
}
&:nth-of-type(2) {
color: #f8b426;
}
&:nth-of-type(3) {
color: #21aa99;
}
&:nth-of-type(4) {
color: #F46;
}
&:nth-of-type(5) {
color: #11A265;
}
h2 {
margin-bottom: 30px;
font-size: 16px;
color: #999;
}
p {
font-weight: 600;
font-size: 28px;
}
}
}
}
.like {
cursor: pointer;
font-size: 25px;
display: inline-block;
}
.amountItem {
margin: 10px;
}
</style>

View File

@@ -3,6 +3,7 @@
<ai-title <ai-title
slot="title" slot="title"
title="销售数据" title="销售数据"
tips="请事先在“成本管理”维护好成本,否则部分计算不准确"
isShowBottomBorder> isShowBottomBorder>
<template #rightBtn> <template #rightBtn>
<div class="title-right"> <div class="title-right">
@@ -38,53 +39,96 @@
</el-select> </el-select>
</div> </div>
</div> </div>
<ai-dialog
title="导出SKU历史销量"
:visible.sync="downloadSkuSaleNumberDlg"
:close-on-click-modal="false"
width="790px"
append-to-body
customFooter
@close="handleClose">
<el-form class="ai-form" :model="skuDownloadForm" label-width="160px" ref="skuDownloadForm">
<el-form-item label="日期范围" style="width: 100%;" prop="date" :rules="[{ required: true, message: '请选择时间范围', trigger: 'blur' }]">
<el-date-picker
v-model="skuDownloadForm.date"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="downloadSkuSaleNumberDlg = false"> </el-button>
<el-button type="primary" @click="toDownloadSkuSaleNumber">确定</el-button>
</div>
</ai-dialog>
</template> </template>
</ai-title> </ai-title>
<template slot="content" v-if="type === '0'"> <template slot="content" v-if="type === '0'">
<div class="top"> <div class="top">
<div class="item"> <div class="item">
<h2>今日销量</h2> <h2>今日销量/销售额</h2>
<p>{{ todayTotal }}</p> <p>{{ todayTotal }}/{{ todayMoney }}</p>
</div> </div>
<div class="item"> <div class="item">
<h2>今日销售额</h2> <h2>今日利润/利润率</h2>
<p>{{ todayMoney }}</p> <p>{{ profitMoney }}/{{ profitPercent }}</p>
</div> </div>
<div class="item"> <div class="item">
<h2>库存总量</h2> <h2>可用库存总量()</h2>
<p>{{ inventoryTotal }}</p> <p>{{ inventoryTotal }}</p>
</div> </div>
<div class="item"> <div class="item">
<h2>库存总额</h2> <h2>可用库存总额</h2>
<p>{{ inventoryMoeny }}</p> <p>{{ inventoryMoney }}</p>
</div> </div>
<div class="item"> <div class="item">
<h2>已收货总量</h2> <h2><span>在途库存总量()<el-tooltip
<p>{{ deliveryTotal }}</p> effect="dark"
content="包括暂不可用库存以及已发货库存"
placement="right">
<i class="el-icon-question"
style="font-size: 16px; vertical-align: middle;"></i>
</el-tooltip></span></h2>
<p>{{ inroadTotal }}</p>
</div> </div>
<div class="item"> <div class="item">
<h2>已收货总货值</h2> <h2><span>在途库存总额<el-tooltip
<p>{{ deliveryMoeny }}</p> effect="dark"
content="(暂不可用库存+已发货库存)*申报价格"
placement="right">
<i class="el-icon-question"
style="font-size: 16px; vertical-align: middle;"></i>
</el-tooltip></span></h2>
<p>{{ inroadTotalMoney }}</p>
</div> </div>
</div> </div>
<ai-card title="数据明细" style="padding-bottom: 40px;"> <ai-card title="数据明细" style="padding-bottom: 40px;">
<template #right> <template #right>
<div class="search-item" style="width: 160px;">
<el-checkbox v-model="showTodaySale">只显示今日有销量</el-checkbox>
</div>
<el-button style="margin-right: 10px;" v-if="type === '0'" type="primary" @click="downloadSaleData">导出数据</el-button>
<json-excel <json-excel
:data="list" :data="skuSaleNumberList"
v-if="type === '0'" v-show="false"
:fields="jsonFields" :fields="skuSaleNumberFields"
:before-generate = "startDownload" :before-generate = "startSkuSaleNumberDownload"
name="销售数据.xls" name="SKU历史销量.xls"
worksheet="销售统计"> worksheet="SKU历史销量">
<el-button type="primary">导出数据</el-button> <el-button type="primary" id="downloadSkuSaleNumber"></el-button>
</json-excel> </json-excel>
<el-button type="primary" :disabled="!mallId" @click="toDownload">导出SKU历史销量</el-button>
</template> </template>
<ai-table <ai-table
ref="table0"
:isShowPagination="false" :isShowPagination="false"
:tableData="list" :tableData="filteredData"
height="500" height="700"
:col-configs="colConfigs" :col-configs="colConfigs"
:total="list.length" :total="filteredData.length"
style="margin-top: 8px;" style="margin-top: 8px;"
@getList="() => {}"> @getList="() => {}">
</ai-table> </ai-table>
@@ -104,6 +148,7 @@
</json-excel> </json-excel>
</template> </template>
<ai-table <ai-table
ref="table1"
:isShowPagination="false" :isShowPagination="false"
:tableData="last30DaySkuList" :tableData="last30DaySkuList"
height="500" height="500"
@@ -128,6 +173,7 @@
</json-excel> </json-excel>
</template> </template>
<ai-table <ai-table
ref="table2"
:isShowPagination="false" :isShowPagination="false"
:tableData="last30DaySkcList" :tableData="last30DaySkcList"
height="500" height="500"
@@ -138,6 +184,8 @@
</ai-table> </ai-table>
</ai-card> </ai-card>
</template> </template>
</ai-list> </ai-list>
</template> </template>
@@ -147,6 +195,8 @@ import {sendChromeAPIMessage} from '@/api/chromeApi'
import JsonExcel from 'vue-json-excel' import JsonExcel from 'vue-json-excel'
import {formatDate} from '@/utils/date' import {formatDate} from '@/utils/date'
import { Message } from 'element-ui' import { Message } from 'element-ui'
import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
export default { export default {
name: 'ExportSaleData', name: 'ExportSaleData',
@@ -160,73 +210,43 @@ import { Message } from 'element-ui'
mallName: '', mallName: '',
type: '0', type: '0',
isLoading: false, isLoading: false,
pageSize: 100, pageSize: 50,
currentPage: 1, currentPage: 1,
todayTotal: 0, todayTotal: 0,
todayMoney: 0.0, todayMoney: 0.0,
profitMoney: 0.0,
profitPercent: 0,
inventoryTotal: 0, inventoryTotal: 0,
inventoryMoeny: 0.0, inventoryMoney: 0.0,
deliveryTotal: 0, inroadTotal: 0,
deliveryMoeny: 0.0, inroadTotalMoney: 0.0,
allProductList: [], costList: [],
startDate: '', startDate: '',
endDate: '', endDate: '',
skuIds: [], skuIds: [],
jsonFields: {
"商品名称": "productName", skuSaleNumberFields: {},
"店铺名称": "mallName", skuSaleNumberList: [],
"评分": "mark", downloadSkuSaleNumberDlg: false,
"SPU": "productId", skuDownloadForm: {
"SKC": "productSkcId", date: ''
"SKU ID": "productSkuId", },
"SKU属性": "className", showTodaySale: false
"SKU货号": "skuExtCode",
"加入站点时长": "onSalesDurationOffline",
"图片链接": "productSkcPicture",
"申报价格(CNY)": {
"field": "supplierPrice",
callback: (value) => {
return value /100;
}
},
"开款核价状态": {
"field": "isVerifyPrice",
callback: (value) => {
return value ? '核价通过': '核价未通过 / 无法备货';
}
},
"缺货数量": "lackQuantity",
"建议备货量": "adviceQuantity",
"可售天数": "availableSaleDays",
"库存可售天数": "availableSaleDaysFromInventory",
"仓内库存可售天数": "warehouseAvailableSaleDays",
"近7日用户加购数量": "inCartNumber7d",
"用户累计加购数量": "inCardNumber",
"已订阅待提醒到货": "nomsgSubsCntCntSth",
"销售数据 - 今日": "todaySaleVolume",
"销售数据 - 近7日": "lastSevenDaysSaleVolume",
"销售数据 - 近30天": "lastThirtyDaysSaleVolume",
"库存数据 - 仓内可用库存": "inventoryNumInfo.warehouseInventoryNum",
"库存数据 - 仓内暂不可用库存": "inventoryNumInfo.unavailableWarehouseInventoryNum",
"库存数据 - 已发货库存": "inventoryNumInfo.waitReceiveNum",
"库存数据 - 已下单待发货库存": "inventoryNumInfo.waitDeliveryInventoryNum",
"库存数据 - 待审核备货库存": "inventoryNumInfo.waitApproveInventoryNum",
"VMI备货单数 - 待发货": "vmiOrderInfo.waitDeliveryNum",
"VMI备货单数 - 在途单数": "vmiOrderInfo.transportationNum",
"VMI备货单数 - 发货延迟": "vmiOrderInfo.deliveryDelayNum",
"VMI备货单数 - 到货延迟": "vmiOrderInfo.arrivalDelayNum",
"非VMI备货单数 - 待发货": "notVmiOrderInfo.waitDeliveryNum",
"非VMI备货单数 - 在途单数": "notVmiOrderInfo.transportationNum",
"非VMI备货单数 - 发货延迟": "notVmiOrderInfo.deliveryDelayNum",
"非VMI备货单数 - 到货延迟": "notVmiOrderInfo.arrivalDelayNum",
"备货逻辑": "purchaseConfig",
"库存货值(CNY)": "productTotalPrice",
}
} }
}, },
computed: { computed: {
...mapState(['mallList']), ...mapState(['mallList']),
filteredData() {
const filteredData = this.list.filter(item => {
if (this.showTodaySale) {
return item.todaySaleVolume > 0
}
return true
})
return filteredData
},
colConfigs () { colConfigs () {
return [ return [
{ {
@@ -234,14 +254,16 @@ import { Message } from 'element-ui'
label: '商品名称', label: '商品名称',
"show-overflow-tooltip": true, "show-overflow-tooltip": true,
width: '280px', width: '280px',
align: 'left' align: 'left',
fixed: 'left'
}, },
{ {
prop: 'mallName', prop: 'mallName',
label: '店铺名称', label: '店铺名称',
"show-overflow-tooltip": true, "show-overflow-tooltip": true,
width: '120px', width: '120px',
align: 'left' align: 'left',
fixed: 'left'
}, },
{ {
prop: 'mark', prop: 'mark',
@@ -250,19 +272,29 @@ import { Message } from 'element-ui'
width: '80px', width: '80px',
align: 'left' align: 'left'
}, },
{
prop: 'hotTag',
label: '是否热销',
"show-overflow-tooltip": true,
width: '80px',
align: 'left'
},
{ {
prop: 'productId', prop: 'productId',
label: 'SPU', label: 'SPU',
width: '120px',
align: 'center' align: 'center'
}, },
{ {
prop: 'productSkcId', prop: 'productSkcId',
label: 'SKC', label: 'SKC',
width: '120px',
align: 'center' align: 'center'
}, },
{ {
prop: 'productSkuId', prop: 'productSkuId',
label: 'SKU ID', label: 'SKU ID',
width: '120px',
align: 'center' align: 'center'
}, },
{ {
@@ -273,22 +305,78 @@ import { Message } from 'element-ui'
{ {
prop: 'skuExtCode', prop: 'skuExtCode',
label: 'SKU货号', label: 'SKU货号',
width: '120px',
align: 'center'
},
{
prop: 'todaySaleVolume',
label: '今日销量',
width: '120px',
align: 'center',
sortable: true,
'sort-method': (a, b) => {
return a.todaySaleVolume - b.todaySaleVolume
}
},
{
prop: 'lastSevenDaysSaleVolume',
label: '近7天',
width: '120px',
align: 'center',
sortable: true,
'sort-method': (a, b) => {
return a.lastSevenDaysSaleVolume - b.lastSevenDaysSaleVolume
}
},
{
prop: 'lastThirtyDaysSaleVolume',
label: '近30天',
width: '120px',
align: 'center',
sortable: true,
'sort-method': (a, b) => {
return a.lastThirtyDaysSaleVolume - b.lastThirtyDaysSaleVolume
}
},
{
prop: 'saleMoney',
label: '销售额',
width: '120px',
align: 'center'
},
{
prop: 'profitMoney',
label: '利润',
width: '120px',
align: 'center',
sortable: true,
'sort-method': (a, b) => {
return a.profitMoney - b.profitMoney
}
},
{
prop: 'profitPercent',
label: '利润率(%)',
width: '120px',
align: 'center' align: 'center'
}, },
{ {
prop: 'onSalesDurationOffline', prop: 'onSalesDurationOffline',
label: '加入站点时长', label: '加入站点时长',
width: '160px',
align: 'center' align: 'center'
}, },
{ {
prop: 'isVerifyPrice', prop: 'isVerifyPrice',
label: '开款核价状态', label: '开款核价状态',
width: '160px',
align: 'center', align: 'center',
format: v => v ? '核价通过': '核价未通过 / 无法备货' format: v => v ? '核价通过': '核价未通过 / 无法备货'
}, },
{ {
prop: 'supplierPrice', prop: 'supplierPrice',
label: '申报价格(CNY)', label: '申报价格(CNY)',
width: '160px',
align: 'center', align: 'center',
format: v => v / 100, format: v => v / 100,
fixed: "right" fixed: "right"
@@ -296,8 +384,8 @@ import { Message } from 'element-ui'
{ {
prop: 'warehouseInventoryNum', prop: 'warehouseInventoryNum',
label: '仓内可用库存', label: '仓内可用库存',
width: '160px',
align: 'center', align: 'center',
fixed: "right",
sortable: true, sortable: true,
'sort-method': (a, b) => { 'sort-method': (a, b) => {
return a.warehouseInventoryNum - b.warehouseInventoryNum return a.warehouseInventoryNum - b.warehouseInventoryNum
@@ -305,7 +393,8 @@ import { Message } from 'element-ui'
}, },
{ {
prop: 'productTotalPrice', prop: 'productTotalPrice',
label: '库存货值(CNY)', label: '可用库存货值(CNY)',
width: '180px',
align: 'center', align: 'center',
fixed: "right", fixed: "right",
sortable: true, sortable: true,
@@ -317,6 +406,7 @@ import { Message } from 'element-ui'
prop: 'purchaseConfig', prop: 'purchaseConfig',
label: '备货逻辑', label: '备货逻辑',
align: 'center', align: 'center',
width: '120px',
fixed: "right", fixed: "right",
sortable: true, sortable: true,
'sort-method': (a, b) => { 'sort-method': (a, b) => {
@@ -328,7 +418,7 @@ import { Message } from 'element-ui'
return -1 return -1
} }
} }
}, }
] ]
@@ -457,6 +547,13 @@ import { Message } from 'element-ui'
align: 'center', align: 'center',
fixed: 'left' fixed: 'left'
}, },
{
prop: 'supplierPrice',
label: '申报价格',
width: '120px',
align: 'center',
fixed: 'left'
},
{ {
prop: 'productSkuId', prop: 'productSkuId',
label: 'SKU ID', label: 'SKU ID',
@@ -506,6 +603,7 @@ import { Message } from 'element-ui'
"SKC货号": "skcExtCode", "SKC货号": "skcExtCode",
"SKU ID": "productSkuId", "SKU ID": "productSkuId",
"SKU货号": "skuExtCode", "SKU货号": "skuExtCode",
"申报价格": "supplierPrice",
"图片链接": "productSkcPicture" "图片链接": "productSkcPicture"
} }
@@ -535,20 +633,22 @@ import { Message } from 'element-ui'
methods: { methods: {
changeDataType() { changeDataType() {
this.$nextTick(() => { //
this.$refs['table'+this.type].doLayout();
})
}, },
beforeGetList() { beforeGetList() {
this.list = [] this.list = []
this.currentPage = 1 this.currentPage = 1
this.todayMoney = 0.0 this.todayMoney = 0.0
this.todayTotal = 0 this.todayTotal = 0
this.inventoryMoeny = 0.0 this.profitMoney = 0.0
this.profitPercent = 0
this.inventoryMoney = 0.0
this.inventoryTotal = 0 this.inventoryTotal = 0
this.deliveryTotal = 0 this.inroadTotalMoney = 0.0
this.deliveryMoeny = 0.0 this.inroadTotal = 0
this.allProductList = []
if (!this.mallId) { if (!this.mallId) {
Message.error("请先选择店铺") Message.error("请先选择店铺")
return return
} }
@@ -560,34 +660,30 @@ import { Message } from 'element-ui'
this.isLoading = true this.isLoading = true
this.$userCheck(this.mallId).then(() => { this.$userCheck(this.mallId).then(() => {
this.last30DaySkcList = [] this.last30DaySkcList = []
this.getAllProductList() this.getSkuCostList()
this.getList() this.getList()
}).catch((err) => { }).catch((err) => {
this.mallId = ''
console.log(err) console.log(err)
this.isLoading = false this.isLoading = false
}) })
}, },
getAllProductList() { getSkuCostList() {
this.$http.post('/api/deliveryOrder/totalDelivery',null, { this.$http.post(`/api/skuCost/listAll`, null, {
params: {mallId: this.mallId} params: {
}).then(res => { mallId: this.mallId
if (res.code === 0) {
this.deliveryTotal = res.data
} }
})
this.$http.post('/api/deliveryOrder/getAllProductList', null, {
params: {mallId: this.mallId}
}).then(res => { }).then(res => {
if (res.code == 0) { if (res.code == 0) {
this.allProductList = res.data this.costList = res.data
} }
}) })
}, },
getList () { getList () {
sendChromeAPIMessage({ sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sales/management/list', url: 'marvel-mms/cn/api/kiana/venom/sales/management/listWarehouse',
needMallId: true, needMallId: true,
anti: true,
mallId: this.mallId, mallId: this.mallId,
data: { data: {
pageNo: this.currentPage, pageNo: this.currentPage,
@@ -600,12 +696,14 @@ import { Message } from 'element-ui'
let item = res.result.subOrderList[i]; let item = res.result.subOrderList[i];
let data = {}; let data = {};
data.productName = item.productName; data.productName = item.productName;
data.category = item.category;
data.productId = item.productId; data.productId = item.productId;
data.productSkcId = item.productSkcId; data.productSkcId = item.productSkcId;
data.skcExtCode = item.skcExtCode; data.skcExtCode = item.skcExtCode;
data.purchaseConfig = item.purchaseConfig; data.purchaseConfig = item.purchaseConfig;
data.productSkcPicture = item.productSkcPicture; data.productSkcPicture = item.productSkcPicture;
data.mark = item.mark.toFixed(1) data.mark = item.mark.toFixed(1)
data.hotTag = item.hotTag ? '是': '否'
data.mallName = this.mallName; data.mallName = this.mallName;
this.last30DaySkcList.push({ this.last30DaySkcList.push({
@@ -625,26 +723,30 @@ import { Message } from 'element-ui'
for(let j = 0;j < item.skuQuantityDetailList.length; j++) { for(let j = 0;j < item.skuQuantityDetailList.length; j++) {
let costPrice = this.getCostPrice(item.skuQuantityDetailList[j].productSkuId)
let saleMoney = Math.round(item.skuQuantityDetailList[j].todaySaleVolume * item.skuQuantityDetailList[j].supplierPrice) /100
let profitMoney = Math.round(item.skuQuantityDetailList[j].todaySaleVolume * (item.skuQuantityDetailList[j].supplierPrice / 100 - costPrice) * 100) /100
let profitPercent = Math.round(profitMoney / saleMoney * 10000) /100
data = {...data, ...item.skuQuantityDetailList[j], data = {...data, ...item.skuQuantityDetailList[j],
saleMoney: saleMoney,
profitMoney: profitMoney,
profitPercent: profitPercent,
productTotalPrice: ((item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum).toFixed(2), productTotalPrice: ((item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum).toFixed(2),
inroadTotalPrice: ((item.skuQuantityDetailList[j].supplierPrice / 100) * (item.skuQuantityDetailList[j].inventoryNumInfo.waitOnShelfNum + item.skuQuantityDetailList[j].inventoryNumInfo.waitReceiveNum)).toFixed(2),
warehouseInventoryNum: item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum} warehouseInventoryNum: item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum}
this.todayTotal += item.skuQuantityDetailList[j].todaySaleVolume this.todayTotal += item.skuQuantityDetailList[j].todaySaleVolume
this.todayMoney += new Number(((item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].todaySaleVolume).toFixed(2)) this.profitMoney += item.skuQuantityDetailList[j].todaySaleVolume * (item.skuQuantityDetailList[j].supplierPrice / 100 - costPrice)
this.todayMoney = new Number(this.todayMoney.toFixed(2)) this.todayMoney += (item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].todaySaleVolume
this.inventoryTotal += item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum this.inventoryTotal += item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum
this.inventoryMoeny += new Number(((item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum).toFixed(2)) this.inventoryMoney += (item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum
this.inventoryMoeny = new Number(this.inventoryMoeny.toFixed(2)) this.inroadTotal += (item.skuQuantityDetailList[j].inventoryNumInfo.waitOnShelfNum + item.skuQuantityDetailList[j].inventoryNumInfo.waitReceiveNum)
this.list.push(data); this.inroadTotalMoney += (item.skuQuantityDetailList[j].supplierPrice / 100) * (item.skuQuantityDetailList[j].inventoryNumInfo.waitOnShelfNum + item.skuQuantityDetailList[j].inventoryNumInfo.waitReceiveNum)
this.adviceProduceNum = item.skuQuantityDetailList[j].adviceProduceNum || '-'
// this.availableProduceNum = item.skuQuantityDetailList[j].availableProduceNum || '-'
for(let k = 0; k < this.allProductList.length; k++) { this.list.push(data);
if (this.allProductList[k].product_sku_id == data.productSkuId) {
this.deliveryMoeny += (item.skuQuantityDetailList[j].supplierPrice / 100) * this.allProductList[k].product_sku_number
this.deliveryMoeny = new Number(this.deliveryMoeny.toFixed(2))
}
}
} }
} }
if (this.pageSize == res.result.subOrderList.length) { if (this.pageSize == res.result.subOrderList.length) {
this.currentPage ++ this.currentPage ++
@@ -652,6 +754,12 @@ import { Message } from 'element-ui'
this.getList() this.getList()
}, 1500) }, 1500)
} else { } else {
this.profitMoney = Math.round(this.profitMoney * 100) / 100
this.todayMoney = Math.round(this.todayMoney * 100) / 100
this.profitPercent = Math.round((this.profitMoney / this.todayMoney) * 10000) /100 + '%'
this.inventoryMoney = Math.round(this.inventoryMoney * 100) / 100
this.inroadTotalMoney = Math.round(this.inroadTotalMoney * 100) / 100
this.isLoading = false this.isLoading = false
Message.success('销售数据加载完成,可进行导出') Message.success('销售数据加载完成,可进行导出')
@@ -677,7 +785,8 @@ import { Message } from 'element-ui'
productSkuId: item.productSkuId, productSkuId: item.productSkuId,
skuExtCode: item.skuExtCode, skuExtCode: item.skuExtCode,
skcExtCode: item.skcExtCode, skcExtCode: item.skcExtCode,
productSkcPicture: item.productSkcPicture productSkcPicture: item.productSkcPicture,
supplierPrice: this.getSupplierPrice(item.productSkuId)
} }
let date = new Date() let date = new Date()
date.setDate(date.getDate() - 31) date.setDate(date.getDate() - 31)
@@ -759,6 +868,178 @@ import { Message } from 'element-ui'
} }
} }
}) })
},
toDownload() {
this.downloadSkuSaleNumberDlg = true
},
startSkuSaleNumberDownload() {
},
handleClose() {
this.downloadSkuSaleNumberDlg = false
},
async toDownloadSkuSaleNumber() {
this.downloadSkuSaleNumberDlg = false
this.isLoading = true
if (!this.skuDownloadForm.date) {
Message.error("请选择时间")
return
}
let beginDateStr = formatDate(this.skuDownloadForm.date[0])
let endDateStr = formatDate(this.skuDownloadForm.date[1])
let temp = {}
temp = {
'SKC': 'skc',
'SKU': 'sku',
'SKU货号': 'skuExtCode',
'申报价格': 'supplierPrice'
}
let beginTime = new Date(this.skuDownloadForm.date[0])
let endTime = new Date(this.skuDownloadForm.date[1])
for (; beginTime.getTime() <= endTime.getTime(); ) {
let dateStr = formatDate(endTime)
temp[dateStr] = dateStr
endTime.setDate(endTime.getDate() - 1)
}
this.skuSaleNumberFields = temp
let tempSkuList = this.list.filter(item => {
return item.onSalesDurationOffline != '-天'
})
let tempSkuIds = []
tempSkuList.map(i => {
tempSkuIds.push(i.productSkuId)
})
this.skuSaleNumberList = []
this.list.map(item => {
if (item.onSalesDurationOffline != '-天') {
let temp = {
sku: item.productSkuId,
skc: item.productSkcId,
skuExtCode: item.skuExtCode,
supplierPrice: this.getSupplierPrice(item.productSkuId)
}
beginTime = new Date(this.skuDownloadForm.date[0])
endTime = new Date(this.skuDownloadForm.date[1])
for (; beginTime.getTime() <= endTime.getTime(); ) {
let dateStr = formatDate(endTime)
temp[dateStr] = 0
endTime.setDate(endTime.getDate() - 1)
}
this.skuSaleNumberList.push(temp)
}
})
let reqSkusIds = [], pageSize = 200
for (let i = 0; i < tempSkuIds.length; i++) {
reqSkusIds.push(tempSkuIds[i])
if (reqSkusIds.length % pageSize == 0 || ((i+1) == tempSkuIds.length)) {
let res = await sendChromeAPIMessage({
url: 'oms/bg/venom/api/supplier/sales/management/querySkuSalesNumber',
needMallId: true,
mallId: this.mallId,
data: {
"productSkuIds": reqSkusIds,
"startDate": beginDateStr,
"endDate": endDateStr
}})
if (res.success) {
res.result.map(item => {
for (let i = 0; i < this.skuSaleNumberList.length; i++) {
if (this.skuSaleNumberList[i].sku == item.prodSkuId) {
this.skuSaleNumberList[i][item.date] = item.salesNumber
break
}
}
})
} else {
this.isLoading = false
Message.error("获取SKU历史销量数据失败")
break
}
if ((i+1) == tempSkuIds.length) {
document.getElementById('downloadSkuSaleNumber').click()
this.isLoading = false
}
reqSkusIds = []
}
}
},
getSupplierPrice(productSkuId) {
for (let i = 0; i < this.list.length; i++) {
if (this.list[i].productSkuId == productSkuId) {
return this.list[i].supplierPrice / 100
}
}
return 0
},
getCostPrice(productSkuId) {
for (let i = 0; i < this.costList.length; i++) {
if (this.costList[i].sku == productSkuId) {
return this.costList[i].costPrice
}
}
return 0
},
async downloadSaleData() {
let res = await this.$http.post('/api/malluser/info')
if (res.code == 0) {
this.$store.commit('setUserInfo', res.data)
if (res.data.flag != 1) {
Message.error('您的账号未激活或已失效,请激活后使用')
this.$store.commit('setActiveDlgShow', true)
return;
}
}
//
const data = [
["商品名称", "SPU", "SKC", "SKU ID", "SKU属性", "SKU货号", "加入站点时长", "图片链接", "申报价格(CNY)", "开款核价状态", "缺货数量",
"建议备货量", "可售天数", "库存可售天数", "仓内库存可售天数","近7日用户加购数量", "用户累计加购数量", "已订阅待提醒到货", "销售数据 - 今日",
"销售数据 - 近7天", "销售数据 - 近30天", "销售数据 - 今日销售额", "销售数据 - 利润", "销售数据 - 利润率", "库存数据 - 仓内可用库存",
"库存数据 - 仓内暂不可用库存", "库存数据 - 已发货库存", "库存数据 - 已下单待发货库存", "库存数据 - 待审核备货库存", "VMI备货单数 - 待发货",
"VMI备货单数 - 在途单数", "VMI备货单数 - 发货延迟", "VMI备货单数 - 到货延迟", "非VMI备货单数 - 待发货", "非VMI备货单数 - 在途单数",
"非VMI备货单数 - 发货延迟", "非VMI备货单数 - 到货延迟", "备货逻辑", "可用库存货值(CNY)", "在途库存货值(CNY)", "店铺名称", "评分",
"是否热销", "生产建议信息 - 建议生产数", "生产建议信息 - 剩余件数", "类目"]
]
this.list.map(item => {
data.push([item.productName, item.productId, item.productSkcId, item.productSkuId, item.className, item.skuExtCode, item.onSalesDurationOffline, item.productSkcPicture, item.supplierPrice/ 100, item.isVerifyPrice? '核价通过': '核价未通过 / 无法备货', item.lackQuantity,
item.adviceQuantity, item.availableSaleDays, item.availableSaleDaysFromInventory, item.warehouseAvailableSaleDays, item.inCartNumber7d, item.inCardNumber, item.nomsgSubsCntCntSth, item.todaySaleVolume,
item.lastSevenDaysSaleVolume, item.lastThirtyDaysSaleVolume, item.saleMoney, item.profitMoney, item.profitPercent, item.inventoryNumInfo?.warehouseInventoryNum,
item.inventoryNumInfo?.waitOnShelfNum, item.inventoryNumInfo?.waitReceiveNum, item.inventoryNumInfo?.waitDeliveryInventoryNum, item.inventoryNumInfo?.waitApproveInventoryNum, item.vmiOrderInfo?.waitDeliveryNum,
item.vmiOrderInfo?.transportationNum, item.vmiOrderInfo?.deliveryDelayNum, item.vmiOrderInfo?.arrivalDelayNum, item.notVmiOrderInfo?.waitDeliveryNum, item.notVmiOrderInfo?.transportationNum,
item.notVmiOrderInfo?.deliveryDelayNum, item.notVmiOrderInfo?.arrivalDelayNum, item.purchaseConfig, item.productTotalPrice, item.inroadTotalPrice, item.mallName, item.mark,
item.hotTag, item.adviceProduceNum, item.availableProduceNum, item.category])
})
//
const worksheet = XLSX.utils.aoa_to_sheet(data);
// 簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
// Excel
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
// 使blobFileReaderBlob URL
const dataBlob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
const blobUrl = window.URL.createObjectURL(dataBlob);
// 使saveAs
saveAs(dataBlob, '销售数据.xlsx');
//
window.URL.revokeObjectURL(blobUrl);
} }
} }
} }

View File

@@ -0,0 +1,224 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="售罄看板"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" @change="beforeGetList" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<el-radio-group v-model="type" @change="onChange">
<el-radio-button label="0">即将售罄</el-radio-button>
<el-radio-button label="1">已售罄</el-radio-button>
</el-radio-group>
</template>
<template #right>
</template>
</ai-search-bar>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<template #right>
<json-excel
:data="tableData"
:fields="jsonFields"
:before-generate = "startDownload"
name="即将售罄明细.xls"
worksheet="即将售罄明细">
<el-button type="primary" :disabled="!mallId || (tableData.length == 0)">导出数据</el-button>
</json-excel>
</template>
<ai-table
:isShowPagination="false"
:tableData="tableData"
:col-configs="colConfigs"
:total="tableData.length"
height="700"
style="margin-top: 8px;"
@getList="() => {}">
</ai-table>
</ai-card>
</template>
</ai-list>
</template>
<script>
import {sendChromeAPIMessage} from '@/api/chromeApi'
import JsonExcel from 'vue-json-excel'
import { Message } from 'element-ui'
export default {
name: 'ExportSaleOutData',
data () {
return {
type: '0',
isLoading: false,
tableData: [],
mallId: '',
colConfigs: [
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'catName', label: '类目', align: 'left' },
{ prop: 'productSkcId', label: 'SKC ID', align: 'left' },
{ prop: 'skcExtCode', label: 'SKC货号', align: 'left' },
{ prop: 'productSkuId', label: 'SKU ID', align: 'left' },
{ prop: 'skuExtCode', label: 'SKU货号', align: 'left' },
{ prop: 'className', label: 'SKU属性', align: 'left' },
{ prop: 'prodSkuPayQtyTotal7d', label: '近7天SKU销量', align: 'left' },
{ prop: 'prodSkcPayQtyTotal7d', label: '近7天SKC销量', align: 'left' },
{ prop: 'totalStock', label: '仓内可用库存', align: 'left' },
{ prop: 'stockNotAvailable', label: '仓内暂不可用库存', align: 'left' },
{ prop: 'totalWaitReceiveNum', label: '已发货库存', align: 'left' },
{ prop: 'stockAvailable', label: '合计库存', align: 'left' },
{ prop: 'stockAvlbDays', label: '库存可售天数', align: 'left' },
{ prop: 'p7dSellOutSimuAmount', label: '近7天销售损失(CNY)', align: 'left' },
{ prop: 'p7dSellOutSimuAmountRatio', label: '近7天销售损失占比', align: 'left' },
{ prop: 'prodSkuPayQtyTotal7d2', label: '近7天sku已支付销量', align: 'left' },
{ prop: 'waitDeliverOrderSnList', label: '待发货备货单ID', align: 'left' }
],
jsonFields: {
"商品名称": "productName",
"类目": "catName",
"SKC ID": "productSkcId",
"SKC货号": "skcExtCode",
"SKU ID": "productSkuId",
"SKU货号": "skuExtCode",
"SKU属性": "className",
"近7天SKU销量": "prodSkuPayQtyTotal7d",
"近7天SKC销量": "prodSkcPayQtyTotal7d",
"仓内可用库存": "totalStock",
"仓内暂不可用库存": "stockNotAvailable",
"已发货库存": "totalWaitReceiveNum",
"合计库存": "stockAvailable",
"库存可售天数": "stockAvlbDays",
"近7天销售损失(CNY)": "p7dSellOutSimuAmount",
"近7天销售损失占比": "p7dSellOutSimuAmountRatio",
"近7天sku已支付销量": "prodSkuPayQtyTotal7d2",
"待发货备货单ID": "waitDeliverOrderSnList"
},
currentPage: 1
}
},
computed: {
},
components: {
JsonExcel
},
created () {
},
methods: {
onChange (e) {
this.tableData = []
this.currentPage = 1
if (e === '0') {
this.getList(1)
} else {
this.getList(2)
}
},
beforeGetList() {
if (!this.mallId) {
Message.error("请先选择店铺")
return
}
this.currentPage = 1
this.tableData = []
this.$userCheck(this.mallId).then(() => {
this.isLoading = true
if (this.type == '0') {
this.getList(1)
} else {
this.getList(2)
}
}).catch((err) => {
this.mallId = ''
this.isLoading = false
})
},
getList (detailType) {
sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sold/out/querySoldOutDetail',
needMallId: true,
mallId: this.mallId,
anti: true,
data: {
"pageNo": this.currentPage,
"pageSize": 200,
"detailType": detailType
}}).then((res) => {
if (res.errorCode == 1000000) {
res.result.soldOutDetailList.map(item => {
this.tableData.push({...item,
p7dSellOutSimuAmount: item.p7dSellOutSimuAmount/100,
p7dSellOutSimuAmountRatio: (item.p7dSellOutSimuAmountRatio * 100).toFixed(2) + '%',
waitDeliverOrderSnList: item.waitDeliverOrderSnList.join(',')})
})
if (200 == res.result.soldOutDetailList.length) {
this.currentPage ++
setTimeout(() => {
this.getList(detailType)
}, 1000)
} else {
this.isLoading = false
}
} else {
setTimeout(() => {
this.getList(detailType)
}, 1000)
}
})
},
startDownload() {
this.$http.post('/api/malluser/info').then(res => {
if (res.code == 0) {
this.$store.commit('setUserInfo', res.data)
if (res.data.flag != 1) {
Message.error('您的账号未激活或已失效,请激活后使用')
this.$store.commit('setActiveDlgShow', true)
return;
}
}
})
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,888 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="销售统计"
tips="最多只能统计近60天的销售数据"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">时间范围</label>
<el-date-picker
v-model="dateRange"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
<div>
<label style="width:90px">店铺</label>
<el-select v-model="mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
<el-button type="button" :class="'el-button el-button--primary'" @click="toBeginStat">开始统计</el-button>
<el-button type="primary" icon="el-icon-download" @click="downloadPicture">下载图片</el-button>
<json-excel
:data="skcList"
:fields="skcJsonFields"
name="SKC销售统计.xls"
worksheet="SKC销售统计">
<el-button type="primary" icon="el-icon-download">下载SKC统计</el-button>
</json-excel>
<json-excel
:data="skuList"
:fields="skuJsonFields"
name="SKU销售统计.xls"
worksheet="SKU销售统计">
<el-button type="primary" icon="el-icon-download">下载SKU统计</el-button>
</json-excel>
</div>
</template>
</ai-title>
<template slot="content">
<div id="app-content">
<ai-card title="数据概览" style="padding-bottom: 40px;">
<div>
<el-row :gutter="20">
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="saleAmount"
title="销售额"
>
</el-statistic>
</div>
</el-col>
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:value="saleCount"
title="单量"
>
</el-statistic>
</div>
</el-col>
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="costAmount"
title="成本"
></el-statistic>
</div>
</el-col>
<el-col :span="5">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="profitAmount"
title="利润"
></el-statistic>
</div>
</el-col>
<el-col :span="4">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="profitPercent"
title="利润率"
>
<template slot="suffix">%</template>
</el-statistic>
</div>
</el-col>
</el-row>
</div>
</ai-card>
<ai-card title="趋势分析" style="padding-bottom: 40px;">
<div>
<div id="chart1"></div>
</div>
</ai-card>
<div>
<el-row :gutter="20">
<el-col :span="12">
<ai-card title="SKC销售额TOP10排行">
<ai-table
ref="table0"
:isShowPagination="false"
:tableData="topSaleAmountSkcList"
:col-configs="skcColConfigs"
style="margin-top: 8px;">
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
<el-col :span="12">
<ai-card title="SKC利润TOP10排行">
<ai-table
ref="table1"
:isShowPagination="false"
:tableData="topSaleProfitSkcList"
:col-configs="skcColConfigs"
style="margin-top: 8px;">
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<ai-card title="SKC单量TOP10排行">
<ai-table
ref="table2"
:isShowPagination="false"
:tableData="topSaleCountSkcList"
:col-configs="skcColConfigs"
style="margin-top: 8px;">
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
<el-col :span="12">
<ai-card title="SKU销售额TOP10排行">
<ai-table
ref="table3"
:isShowPagination="false"
:tableData="topSaleAmountSkuList"
:col-configs="skuColConfigs"
style="margin-top: 8px;">
<el-table-column slot="skuExtCode" label="SKC属性/SKU属性" width="250px" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div>{{ scope.row.skcExtCode + '/' + scope.row.skuExtCode }}</div>
</template>
</el-table-column>
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<ai-card title="SKU利润TOP10排行">
<ai-table
ref="table4"
:isShowPagination="false"
:tableData="topSaleProfitSkuList"
:col-configs="skuColConfigs"
style="margin-top: 8px;">
<el-table-column slot="skuExtCode" label="SKC属性/SKU属性" width="250px" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div>{{ scope.row.skcExtCode + '/' + scope.row.skuExtCode }}</div>
</template>
</el-table-column>
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div>{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
<el-col :span="12">
<ai-card title="SKU单量TOP10排行">
<ai-table
ref="table5"
:isShowPagination="false"
:tableData="topSaleCountSkuList"
:col-configs="skuColConfigs"
style="margin-top: 8px;">
<el-table-column slot="skuExtCode" label="SKC属性/SKU属性" width="250px" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div>{{ scope.row.skcExtCode + '/' + scope.row.skuExtCode }}</div>
</template>
</el-table-column>
<el-table-column slot="saleAmount" label="销售额">
<template slot-scope="scope">
<div>{{ scope.row.saleAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="profitAmount" label="利润">
<template slot-scope="scope">
<div>{{ scope.row.profitAmount }}</div>
</template>
</el-table-column>
<el-table-column slot="saleCount" label="单量">
<template slot-scope="scope">
<div style="color: red">{{ scope.row.saleCount }}</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</el-col>
</el-row>
</div>
</div>
</template>
</ai-list>
</template>
<script>
import {sendChromeAPIMessage } from '@/api/chromeApi'
import JsonExcel from 'vue-json-excel'
import { Message } from 'element-ui'
import {formatDate} from '@/utils/date'
import html2canvas from 'html2canvas'
import { DualAxes } from '@antv/g2plot'
export default {
name: 'ExportSaleStatTemu',
data () {
return {
mallId: null,
storeCode: '',
dateRange: null,
currentPage: 1,
pageSize: 50,
productList: [],
productCostList: [],
saleAmount: 0.0,
saleCount: 0,
costAmount: 0.0,
profitAmount: 0.0,
profitPercent: 0.0,
deductionAmount: 0.0,
skcList: [],
skuList: [],
leftChartData: [],
rightChartData: [],
isLoading: false,
skcColConfigs: [
{ prop: 'skcExtCode', label: 'SKC货号', width: '250px' },
{ slot: 'saleAmount', label: '销售额', align: 'center' },
{ slot: 'saleCount', label: '单量', align: 'center' },
{ prop: 'costAmount', label: '成本', align: 'center'},
{ slot: 'profitAmount', label: '利润', align: 'center' }
],
skcJsonFields: {
"SKC": "productSkcId",
"SKC货号": "skcExtCode",
"销售额": "saleAmount",
"单量": "saleCount",
"成本": "costAmount",
"利润": "profitAmount"
},
skuColConfigs: [
{ slot: 'skuExtCode', label: 'SKC货号/SKU货号' },
{ slot: 'saleAmount', label: '销售额', align: 'center' },
{ slot: 'saleCount', label: '单量', align: 'center' },
{ prop: 'costAmount', label: '成本', align: 'center'},
{ slot: 'profitAmount', label: '利润', align: 'center' }
],
skuJsonFields: {
"SKC": "productSkcId",
"SKU": "productSkuId",
"SKC货号": "skcExtCode",
"SKU货号": "skuExtCode",
"销售额": "saleAmount",
"单量": "saleCount",
"成本": "costAmount",
"利润": "profitAmount"
},
chartObj: null
}
},
computed: {
topSaleAmountSkcList() {
const list = Object.assign([], this.skcList)
list.sort((a, b) => b.saleAmount - a.saleAmount)
return list.slice(0, 10)
},
topSaleProfitSkcList() {
const list = Object.assign([], this.skcList)
list.sort((a, b) => b.profitAmount - a.profitAmount)
return list.slice(0, 10)
},
topSaleCountSkcList() {
const list = Object.assign([], this.skcList)
list.sort((a, b) => b.saleCount - a.saleCount)
return list.slice(0, 10)
},
topSaleAmountSkuList() {
const list = Object.assign([], this.skuList)
list.sort((a, b) => b.saleAmount - a.saleAmount)
return list.slice(0, 10)
},
topSaleProfitSkuList() {
const list = Object.assign([], this.skuList)
list.sort((a, b) => b.profitAmount - a.profitAmount)
return list.slice(0, 10)
},
topSaleCountSkuList() {
const list = Object.assign([], this.skuList)
list.sort((a, b) => b.saleCount - a.saleCount)
return list.slice(0, 10)
}
},
components: {
JsonExcel
},
mounted () {
this.initChart1()
},
methods: {
initChart1 () {
this.chartObj = new DualAxes('chart1', {
data: [this.leftChartData, this.rightChartData],
xField: 'day',
yField: ['value', 'value1'],
yAxis: {
value: {
title: {
text: '金额'
},
label: {
formatter: (value) => {
return `${value}`;
}
}
},
value1: {
max: 100,
title: {
text: '利润率'
},
label: {
formatter: (value) => {
return `${value}%`;
}
}
}
},
geometryOptions: [
{
geometry: 'line',
color: ['#FF9A49', '#7A8AA1', '#1EEB73', '#C947AE' ],
isGroup: true,
smooth: true, // 是否平滑
seriesField: 'type'
},
{
geometry: 'line',
color: ['red' ],
lineStyle: {
fill: 'red',
fillOpacity: 0.5,
stroke: 'red',
lineWidth: 4,
strokeOpacity: 0.7,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 5,
shadowOffsetY: 5,
cursor: 'pointer'
},
isStack: true,
smooth: true, // 是否平滑
seriesField: 'type', // 指定使用第二个Y轴字段
}],
});
this.chartObj.render();
},
toBeginStat() {
if (!this.dateRange) {
Message.error("请选择统计时间范围")
return
}
if (!this.mallId) {
Message.error("请选择店铺")
return
}
this.$userCheck(this.mallId).then(() => {
this.beginStat()
}).catch((err) => {
this.mallId = ''
this.isLoading = false
})
},
async beginStat() {
this.currentPage = 1
this.isLoading = true
this.saleAmount = 0.0
this.saleCount = 0
this.costAmount = 0.0
this.profitAmount = 0.0
this.profitPercent = 0.0
this.productList = []
this.productCostList = []
this.skcList = []
this.skuList = []
this.leftChartData = []
this.rightChartData = []
this.chartObj.changeData([this.leftChartData, this.rightChartData])
await this.getProductList()
await this.getSkuCostList()
await this.getSkuSaleNumber(0)
this.leftChartData.sort((a, b) => {
let leftDate = new Date(a.day)
let rightDate = new Date(b.day)
return leftDate.getTime() - rightDate.getTime()
})
this.leftChartData.map(item => {
if (item.type == '销售额' || item.type == '成本' || item.type == '利润') {
item.value = Math.round(item.value * 100) / 100
}
if (item.type == '销售额') {
this.leftChartData.map(item1 => {
if (item1.type == '成本' && item1.day == item.day) {
this.rightChartData.push({
day: item1.day,
value1: Math.round((item.value - item1.value) / item.value * 10000) / 100,
type: '利润率'
})
}
})
}
})
this.rightChartData.sort((a, b) => {
let leftDate = new Date(a.day)
let rightDate = new Date(b.day)
return leftDate.getTime() - rightDate.getTime()
})
this.skuList = this.skuList.filter(item => {
return item.saleCount > 0
})
this.skuList.map(item => {
item.saleAmount = Math.round(item.saleAmount * 100) / 100
item.costAmount = Math.round(item.costAmount * 100) / 100
item.profitAmount = Math.round(item.profitAmount * 100) / 100
})
this.skuList.map(item => {
let flag = false
for (let i = 0; i < this.skcList.length; i++) {
if (item.productSkcId == this.skcList[i].productSkcId) {
this.skcList[i].saleAmount = this.skcList[i].saleAmount + item.saleAmount
this.skcList[i].costAmount = this.skcList[i].costAmount + item.costAmount
this.skcList[i].profitAmount = this.skcList[i].profitAmount + item.profitAmount
flag = true
break
}
}
if (!flag) {
this.skcList.push({
productSkcId: item.productSkcId,
skcExtCode: item.skcExtCode,
saleAmount: item.saleAmount,
saleCount: item.saleCount,
costAmount: item.costAmount,
profitAmount: item.profitAmount,
profitPercent: item.profitPercent
})
}
})
this.skcList.map(item => {
item.saleAmount = Math.round(item.saleAmount * 100) / 100
item.costAmount = Math.round(item.costAmount * 100) / 100
item.profitAmount = Math.round(item.profitAmount * 100) / 100
})
this.leftChartData.map(item => {
if (item.type == '销售额') {
this.saleAmount = this.saleAmount + item.value
}
if (item.type == '成本') {
this.costAmount = this.costAmount + item.value
}
if (item.type == '利润') {
this.profitAmount = this.profitAmount + item.value
}
if (item.type == '单量') {
this.saleCount = this.saleCount + item.value
}
})
this.saleAmount = Math.round(this.saleAmount * 100) / 100
this.costAmount = Math.round(this.costAmount * 100) / 100
this.profitAmount = Math.round(this.profitAmount * 100) / 100
this.profitPercent = Math.round((this.saleAmount - this.costAmount) / this.saleAmount * 10000) / 100
this.chartObj.changeData([this.leftChartData, this.rightChartData])
this.isLoading = false
},
getPorductList() {
this.$http.post(`/api/skuCost/listAll`, null, {
params: {
mallId: this.storeCode
}
}).then(res => {
if (res.code == 0) {
this.productList = res.data
}
})
},
async getProductList() {
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sales/management/listWarehouse',
needMallId: true,
mallId: this.mallId,
data: {
pageNo: this.currentPage,
pageSize: this.pageSize,
isLack: 0,
priceAdjustRecentDays: 7
}})
if (res.errorCode == 1000000) {
for(let i = 0;i < res.result.subOrderList.length; i++) {
let item = res.result.subOrderList[i]
let data = {}
data.productName = item.productName
data.productId = item.productId
data.productSkcId = item.productSkcId
data.skcExtCode = item.skcExtCode
for(let j = 0;j < item.skuQuantityDetailList.length; j++) {
data = {...data,
skuExtCode: item.skuQuantityDetailList[j].skuExtCode,
supplierPrice: item.skuQuantityDetailList[j].supplierPrice / 100,
productSkuId: item.skuQuantityDetailList[j].productSkuId,
className: item.skuQuantityDetailList[j].className
}
this.productList.push(data)
}
}
if ((this.currentPage * this.pageSize) < res.result.total) {
this.currentPage ++
await this.getProductList()
}
} else {
await this.getProductList()
}
},
async getSkuSaleNumber (page) {
let tempSkuId = []
let i = page * 500
let j = 0
for (; i < this.productList.length; i++) {
tempSkuId.push(this.productList[i].productSkuId)
j ++
if (j == 500) break
}
if (tempSkuId.length == 0) return
let res = await sendChromeAPIMessage({
url: 'oms/bg/venom/api/supplier/sales/management/querySkuSalesNumber',
needMallId: true,
mallId: this.mallId,
data: {
productSkuIds: tempSkuId,
startDate: this.dateRange[0],
endDate: this.dateRange[1]
}})
if (res.errorCode == 1000000) {
for (let i = 0; i < res.result.length; i++) {
let cost = this.getCost(res.result[i].prodSkuId)
let flag = false
// 计算每日销售额
let skuObj = 0
for (let k = 0; k < this.productList.length; k++) {
if (this.productList[k].productSkuId == res.result[i].prodSkuId) {
skuObj = this.productList[k]
break
}
}
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '销售额' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + skuObj.supplierPrice * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '销售额',
value: skuObj.supplierPrice * res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算每日销量
flag = false
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '单量' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '单量',
value: res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算每日成本
flag = false
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '成本' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + cost * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '成本',
value: cost * res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算每日利润
flag = false
for (let j = 0; j < this.leftChartData.length; j++) {
if (this.leftChartData[j].type == '利润' && this.leftChartData[j].day == res.result[i].date) {
this.leftChartData[j].value = this.leftChartData[j].value + (skuObj.supplierPrice - cost) * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.leftChartData.push({
type: '利润',
value: (skuObj.supplierPrice - cost) * res.result[i].salesNumber,
day: res.result[i].date
})
}
// 计算SKU维度销售额
flag = false
for (let j = 0; j < this.skuList.length; j++) {
if (this.skuList[j].productSkuId == res.result[i].prodSkuId) {
this.skuList[j].saleAmount = this.skuList[j].saleAmount + skuObj.supplierPrice * res.result[i].salesNumber
this.skuList[j].costAmount = this.skuList[j].costAmount + cost * res.result[i].salesNumber
this.skuList[j].saleCount = this.skuList[j].saleCount + res.result[i].salesNumber
this.skuList[j].profitAmount = this.skuList[j].profitAmount + (skuObj.supplierPrice - cost) * res.result[i].salesNumber
flag = true
break
}
}
if (!flag) {
this.skuList.push({
productSkcId: skuObj.productSkcId,
productSkuId: skuObj.productSkuId,
skcExtCode: skuObj.skcExtCode,
skuExtCode: skuObj.skuExtCode,
saleAmount: skuObj.supplierPrice * res.result[i].salesNumber,
costAmount: cost * res.result[i].salesNumber,
profitAmount: (skuObj.supplierPrice - cost) * res.result[i].salesNumber,
saleCount: res.result[i].salesNumber,
profitPercent: Math.round((skuObj.supplierPrice - cost) / skuObj.supplierPrice * 10000) / 100
})
}
}
await this.getSkuSaleNumber(page + 1)
} else {
await this.getSkuSaleNumber(page)
}
},
async getSkuCostList() {
let res = await this.$http.post(`/api/skuCost/listAll`, null, {
params: {
mallId: this.mallId
}
})
if (res.code == 0) {
this.productCostList = res.data
}
},
getCost(sku) {
for (let i = 0; i < this.productCostList.length; i++) {
if (sku == this.productCostList[i].sku) {
return this.productCostList[i].costPrice
}
}
return 0
},
sleepSync(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
},
async downloadPicture() {
try {
const element = document.getElementById('app-content');
const canvas = await html2canvas(element);
// 创建一个图片元素
const img = new Image();
img.src = canvas.toDataURL('image/png');
// 添加到body中以便下载
document.body.appendChild(img);
// 触发下载
const a = document.createElement('a');
a.style.display = 'none'
a.href = img.src;
a.download = '销售统计.png';
a.click();
document.body.removeChild(img);
} catch (error) {
console.error('Error saving image:', error);
}
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
.top {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
.item {
flex: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
&:nth-of-type(1) {
color: #2266ff;
}
&:nth-of-type(2) {
color: #f8b426;
}
&:nth-of-type(3) {
color: #21aa99;
}
&:nth-of-type(4) {
color: #F46;
}
&:nth-of-type(5) {
color: #11A265;
}
h2 {
margin-bottom: 30px;
font-size: 16px;
color: #999;
}
p {
font-weight: 600;
font-size: 28px;
}
}
}
}
.like {
cursor: pointer;
font-size: 25px;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,418 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="物流统计"
tips="数据来源于“履约服务账单->明细->缴费记录"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label>缴费完成时间</label>
<el-date-picker
v-model="searchDate"
type="daterange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间">
</el-date-picker>
</div>
</template>
<template #right>
<el-button type="primary" @click="beforeGetList">加载</el-button>
</template>
</ai-search-bar>
<ai-card title="数据概览" style="padding-bottom: 40px;">
<div>
<el-row :gutter="20">
<el-col :span="6">
<div>
<el-statistic
group-separator=","
:precision="2"
:value="totalLogisticFee"
title="物流总费用"
>
</el-statistic>
</div>
</el-col>
<el-col :span="6">
<div>
<el-statistic
title="备货单总数"
:value="totalOrders"
>
</el-statistic>
</div>
</el-col>
<el-col :span="6">
<div>
<el-statistic
title="发货时填写总重量(KG)"
:value="totalWriteWeight"
>
</el-statistic>
</div>
</el-col>
<el-col :span="6">
<div>
<el-statistic
title="最终计费总重量(KG)"
:value="totalFinalWeight"
>
</el-statistic>
</div>
</el-col>
</el-row>
</div>
</ai-card>
<ai-card title="店铺分布" style="padding-bottom: 40px;">
<ai-table
:isShowPagination="false"
:tableData="tableData"
:col-configs="colConfigs"
:total="tableData.length"
height="500"
style="margin-top: 8px;"
@getList="() => {}">
</ai-table>
</ai-card>
<ai-card title="运费分布" style="padding-bottom: 40px;">
<ai-table
:isShowPagination="false"
:tableData="logisticTableData"
:col-configs="logisticColConfigs"
:total="tableData.length"
height="500"
style="margin-top: 8px;"
@getList="() => {}">
</ai-table>
</ai-card>
</div>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage } from '@/api/chromeApi'
export default {
name: 'LogisticFee',
data () {
return {
searchDate: [],
reqData: {
pageNum: 1,
pageSize: 100
},
totalLogisticFee: 0.0,
totalOrders: 0,
totalWriteWeight: 0.0,
totalFinalWeight: 0.0,
colConfigs: [
{ prop: 'mallName', label: '店铺名称', align: 'left' },
{ prop: 'wayBillCount', label: '备货单数', align: 'left', sortable: true, 'sort-method': (a, b) => a.wayBillCount - b.wayBillCount },
{ prop: 'writeWeight', label: '填写重量(KG)', align: 'left', sortable: true, 'sort-method': (a, b) => a.writeWeight - b.writeWeight },
{ prop: 'weight', label: '实际重量(KG)', align: 'left', sortable: true, 'sort-method': (a, b) => a.weight - b.weight },
{ prop: 'amount', label: '物流费用', align: 'left', sortable: true, 'sort-method': (a, b) => a.amount - b.amount }
],
logisticColConfigs: [
{ prop: 'company', label: '物流公司', align: 'left' },
{ prop: 'writeWeight', label: '填写总重量(KG)', align: 'left', sortable: true, 'sort-method': (a, b) => a.writeWeight - b.writeWeight },
{ prop: 'weight', label: '实际总重量(KG)', align: 'left', sortable: true, 'sort-method': (a, b) => a.weight - b.weight },
{ prop: 'amount', label: '物流费用', align: 'left', sortable: true, 'sort-method': (a, b) => a.amount - b.amount }
],
isLoading: false,
tableData: [],
logisticTableData: [],
transList: [],
mallWayBillList: [],
logisticFeeList: []
}
},
methods: {
beforeGetList() {
this.$userCheck().then(() => {
this.toLoad()
}).catch((err) => {
})
},
async toLoad() {
if (!this.searchDate) {
Message.error("请选择时间范围")
return
}
this.reqData.pageNum = 1
this.tableData = []
this.logisticTableData = []
this.isLoading = true
this.totalLogisticFee = 0.0
this.totalOrders = 0
this.totalWriteWeight = 0.0
this.totalFinalWeight = 0.0
this.transList = []
this.mallWayBillList = []
this.logisticFeeList = []
let startTime = this.searchDate[0].getTime()
let endTime = this.searchDate[1].getTime() + 86400000 - 1
await this.getLogisticFee(startTime, endTime)
this.totalLogisticFee = Math.round(this.totalLogisticFee * 100)/100
this.totalFinalWeight = Math.round(this.totalFinalWeight * 100)/100
this.isLoading = false
},
async getLogisticFee(startTime, endTime) {
let res = await sendChromeAPIMessage({
url: `api/merchant/warehouse/express/pay/bill/list`,
needMallId: true,
mallId: this.$store.state.mallList[0].mallId,
data: {
...this.reqData,
sucTimeStart: startTime,
sucTimeEnd: endTime
}})
if (res.errorCode == 1000000) {
for (let i = 0; i < res.result.list.length; i++) {
let item = res.result.list[i]
this.totalLogisticFee += item.amount
this.transList.push(item.ptransId)
}
if ((this.reqData.pageSize * this.reqData.pageNum) < res.result.total) {
this.reqData.pageNum ++
await this.getLogisticFee(startTime, endTime)
} else {
await this.getLogisticDetail()
await this.getLogisticWeightDetail()
this.calcLogiticData()
}
}
},
async getLogisticDetail() {
for (let i = 0; i < this.transList.length; i++) {
let res = await sendChromeAPIMessage({
url: `api/merchant/warehouse/express/pay/bill/detail/list`,
needMallId: true,
mallId: this.$store.state.mallList[0].mallId,
data: {
chargeType: 0,
pageSize: 100,
pageNum: 1,
ptransId: this.transList[i]
}})
if (res.errorCode == 1000000) {
for (let k = 0; k < res.result.list.length; k++) {
let item = res.result.list[k]
let flag = false
for (let j = 0; j < this.tableData.length; j++) {
if (this.tableData[j].mallId == item.mallId) {
flag = true
this.tableData[j].amount += item.amount
this.tableData[j].wayBillCount += item.wayBillCount
this.tableData[j].amount = Math.round(this.tableData[j].amount * 100)/100
this.totalOrders += item.wayBillCount
break
}
}
if (!flag) {
this.tableData.push({
mallId: item.mallId,
mallName: item.mallName,
amount: item.amount,
wayBillCount: item.wayBillCount,
weight: 0.0,
writeWeight: 0.0
})
this.totalOrders += item.wayBillCount
}
await this.getWayBillList(item.transId, item.mallId)
}
}
}
},
async getWayBillList(transId, mallId) {
let pageNum = 1
let pageSize = 100
while(true) {
let res = await sendChromeAPIMessage({
url: `api/merchant/warehouse/express/bill/detail/list`,
needMallId: true,
mallId: this.$store.state.mallList[0].mallId,
data: {
pageNum: pageNum,
pageSize: pageSize,
billStatus: 2,
mallId: mallId,
transId: transId
}})
if (res.errorCode == 1000000) {
let flag = false
for (let i = 0; i < this.logisticFeeList.length; i++) {
if (this.logisticFeeList[i].mallId == mallId) {
flag = true
for (let j = 0; j < res.result.list.length; j++) {
this.logisticFeeList[i].billList.push({
sn: res.result.list[j].mainWayBillSn,
amount: res.result.list[j].amount,
company: null,
waight: 0.0
})
}
break
}
}
if (!flag) {
let temp = {
mallId: mallId,
billList: []
}
for (let j = 0; j < res.result.list.length; j++) {
temp.billList.push({
sn: res.result.list[j].mainWayBillSn,
amount: res.result.list[j].amount,
company: null,
weight: 0.0,
writeWeight: 0.0
})
}
this.logisticFeeList.push(temp)
}
if (pageNum * pageSize < res.result.total) {
pageNum ++
} else {
break
}
}
}
},
async getLogisticWeightDetail() {
for (let i = 0; i < this.logisticFeeList.length; i++) {
let page = 1
let weight = 0.0
let writeWeight = 0.0
while(true) {
let tempList = []
for (let j = (page-1)*20; j < this.logisticFeeList[i].billList.length; j++) {
tempList.push(this.logisticFeeList[i].billList[j].sn)
if (tempList.length % 20 == 0) {
break
}
}
if (tempList.length == 0) break
let res = await sendChromeAPIMessage({
url: `bgSongbird-api/supplier/delivery/feedback/queryWaitConfirmWeightExpressList`,
needMallId: true,
mallId: this.logisticFeeList[i].mallId,
anti: true,
data: {
pageNo: 1,
pageSize: 20,
platformExpressMainSnList: tempList,
displayStatus: 100
}})
if (res.errorCode == 1000000) {
for (let ii = 0; ii < res.result.list.length; ii++) {
for (let jj = 0; jj < this.logisticFeeList[i].billList.length; jj++) {
if (this.logisticFeeList[i].billList[jj].sn == res.result.list[ii].expressDeliverySn) {
this.logisticFeeList[i].billList[jj].company = res.result.list[ii].expressCompany
this.logisticFeeList[i].billList[jj].weight = res.result.list[ii].realExpressWeight / 1000
this.logisticFeeList[i].billList[jj].writeWeight = res.result.list[ii].predictTotalPackageWeight / 1000
this.totalFinalWeight += res.result.list[ii].realExpressWeight / 1000
this.totalWriteWeight += res.result.list[ii].predictTotalPackageWeight / 1000
writeWeight += res.result.list[ii].predictTotalPackageWeight / 1000
weight += res.result.list[ii].realExpressWeight / 1000
break
}
}
}
page++
}
}
for (let kk = 0; kk < this.tableData.length; kk++) {
if (this.tableData[kk].mallId == this.logisticFeeList[i].mallId) {
this.tableData[kk].weight = Math.round(weight * 100)/100
this.tableData[kk].writeWeight = Math.round(writeWeight * 100)/100
break
}
}
}
},
calcLogiticData() {
for (let i = 0; i < this.logisticFeeList.length; i++) {
for (let j = 0; j < this.logisticFeeList[i].billList.length; j++) {
let flag = false
for (let x = 0; x < this.logisticTableData.length; x++) {
if (this.logisticTableData[x].company == this.logisticFeeList[i].billList[j].company) {
flag = true
this.logisticTableData[x].writeWeight += this.logisticFeeList[i].billList[j].writeWeight
this.logisticTableData[x].weight += this.logisticFeeList[i].billList[j].weight
this.logisticTableData[x].amount += this.logisticFeeList[i].billList[j].amount
break
}
}
if (!flag) {
console.log(this.logisticFeeList[i].mallId, this.logisticFeeList[i].billList[j], this.logisticFeeList[i].billList[j].company)
this.logisticTableData.push({
company: this.logisticFeeList[i].billList[j].company,
writeWeight: this.logisticFeeList[i].billList[j].writeWeight,
weight: this.logisticFeeList[i].billList[j].weight,
amount: this.logisticFeeList[i].billList[j].amount,
weightPercent: 0.0,
amountPercent: 0.0
})
}
}
}
for (let j = 0; j < this.logisticTableData.length; j++) {
this.logisticTableData[j].weight = Math.round(this.logisticTableData[j].weight * 100)/100
this.logisticTableData[j].amount = Math.round(this.logisticTableData[j].amount * 100)/100
this.logisticTableData[j].company = this.logisticTableData[j].company || '退回服务费'
}
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,256 @@
<template>
<ai-list class="list" v-loading="isLoading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="调价管理"
tips="每页为100条商品数据"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<div>
<label style="width:90px">店铺</label>
<el-select v-model="form.mallId" placeholder="请选择" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</div>
</template>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label>起始页</label>
<el-input size="small" placeholder="请输入起始页" type="number" v-model="startPage"></el-input>
</div>
<div class="search-item">
<label>结束页</label>
<el-input size="small" placeholder="请输入起始页" type="number" v-model="endPage"></el-input>
</div>
</template>
<template #right>
<el-button type="primary" @click="toLoad">加载</el-button>
</template>
</ai-search-bar>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<template #right>
<json-excel
:data="tableData"
:fields="jsonFields"
name="调价列表.xls"
worksheet="调价列表">
<el-button type="primary" :disabled="tableData.length == 0">下载数据</el-button>
</json-excel>
</template>
<ai-table
:isShowPagination="false"
:tableData="tableData"
:col-configs="colConfigs"
:total="tableData.length"
height="700"
style="margin-top: 8px;"
@getList="() => {}">
<el-table-column slot="productName" width="250px" :show-overflow-tooltip='true' label="商品名称" fixed="left">
<template slot-scope="scope">
<div>
<el-image :src="scope.row.image" style="width: 40px; height: 40px" class="image" :preview-src-list="[scope.row.image]" />
{{ scope.row.productName }}
</div>
</template>
</el-table-column>
</ai-table>
</ai-card>
</div>
</template>
</ai-list>
</template>
<script>
import { Message } from 'element-ui'
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import JsonExcel from 'vue-json-excel'
export default {
name: 'MyNormalOrder',
data () {
return {
form: {
mallId: ''
},
startPage: 1,
endPage: 10,
reqData: {
pageInfo: {
pageSize: 100,
pageNo: 1
},
status: 2
},
colConfigs: [
{ slot: 'productName', label: '商品名称', width: '250px', align: 'left', fixed: 'left' },
{ prop: 'priceOrderSn', label: '调价单号', width: '140px', align: 'left', fixed: 'left' },
{ prop: 'skcId', label: 'SKC ID', width: '120px', align: 'left' },
{ prop: 'skcExtCode', label: 'SKC货号', width: '100px', align: 'left' },
{ prop: 'productSkuId', label: 'SKU ID', width: '120px', align: 'left' },
{ prop: 'skuExtCode', label: 'SKU货号', width: '100px', align: 'left' },
{ prop: 'spec', label: 'SKU属性', width: '100px', align: 'left' },
{ prop: 'priceBeforeExchange', label: '原申报价格', width: '100px', align: 'left' },
{ prop: 'newSupplyPrice', label: '调整后申报价格', width: '100px', align: 'left' },
{ prop: 'operateUserName', label: '操作人', width: '100px', align: 'left' },
{ prop: 'operateSourceName', label: '操作页面', width: '100px', align: 'left' },
{ prop: 'operateTime', label: '操作时间', width: '100px', align: 'left' },
{ prop: 'source', label: '申请来源', width: '120px', align: 'left' },
{ prop: 'adjustReason', label: '调价原因', align: 'left' }
],
isLoading: false,
tableData: [],
jsonFields: {
"商品名称": "productName",
"图片": "image",
"调价单号": "priceOrderSn",
"SKC ID": "skcId",
"SKC货号": "skcExtCode",
"SKU ID": "productSkuId",
"SKU货号": "skuExtCode",
"SKU属性": "spec",
"原申报价格": "priceBeforeExchange",
"调整后申报价格": "newSupplyPrice",
"操作人": "operateUserName",
"操作页面": "operateSourceName",
"操作时间": "operateTime",
"申请来源": "source",
"调价原因": "adjustReason"
},
currentIndex: 0
}
},
components: {
JsonExcel
},
created () {
},
methods: {
beforeGetList() {
this.$userCheck(this.form.mallId).then(() => {
this.toLoad()
}).catch((err) => {
this.form.mallId = ''
})
},
toLoad() {
if (!this.form.mallId) {
Message.error("请选择店铺")
return
}
if (!this.startPage || (this.startPage < 1)) {
Message.error("起始页不能为空且不能小于1")
return
}
if (!this.endPage || (this.startPage < 1)) {
Message.error("结束页不能为空")
return
}
if (this.startPage > this.endPage) {
Message.error("起始页不能大于结束页")
return
}
this.reqData.pageInfo.pageNo = this.startPage
this.tableData = []
this.currentIndex = 0
this.isLoading = true
this.load()
},
async load() {
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/magneto/price-adjust/page-query',
needMallId: true,
mallId: this.form.mallId,
anti: true,
data: this.reqData})
if (res.errorCode == 1000000) {
for(let i = 0;i < res.result.list.length; i++) {
let item = res.result.list[i];
let data = {};
data.id = item.id
data.productName = item.productName
data.image = item.image
data.priceOrderSn = item.priceOrderSn
data.skcId = item.skcId
data.skcExtCode = item.skcExtCode
data.source = item.source
data.adjustReason = item.adjustReason
data.newSupplyPrice = item.newSupplyPrice / 100
for(let k = 0; k < item.skuInfoItemList.length; k++) {
data = {...data,
productSkuId: item.skuInfoItemList[k].productSkuId,
priceBeforeExchange: item.skuInfoItemList[k].priceBeforeExchange / 100,
skuExtCode: item.skuInfoItemList[k].skuExtCode,
spec: item.skuInfoItemList[k].spec,
operateUserName: null,
operateTime: null,
operateSourceName: null
}
this.tableData.push(data)
}
}
if (res.result.list.length == this.reqData.pageInfo.pageSize && this.reqData.pageInfo.pageNo < this.endPage) {
this.reqData.pageInfo.pageNo ++
await this.load()
} else {
await this.getPriceAdjustLog()
this.isLoading = false
}
}
},
async getPriceAdjustLog() {
for (let i = 0; i < this.tableData.length; i++) {
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/magneto/api/price/adjust/log',
needMallId: true,
mallId: this.form.mallId,
anti: true,
data: {priceOrderId: this.tableData[i].id}})
if (res.errorCode == 1000000) {
if (res.result.list && res.result.list.length > 0) {
this.tableData[i].operateSourceName = res.result.list[res.result.list.length - 1].operateSourceName
this.tableData[i].operateTime = timestampToTime(res.result.list[res.result.list.length - 1].operateTime)
this.tableData[i].operateUserName = res.result.list[res.result.list.length - 1].operateUserName
}
}
}
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
::v-deep.ai-list {
.ai-list__content--right-wrapper {
background: transparent;
box-shadow: none;
padding: 0!important;
}
}
}
</style>

View File

@@ -0,0 +1,166 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
title="速卖通采集"
tips="请先在当前浏览器登录“拼多多跨境卖家中心”,期间保持登录状态"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label style="width:80px">店铺</label>
<el-select v-model="search.mallId" :clearable="true" @change="search.current =1, getList()" placeholder="请选择店铺" size="small">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</div>
</template>
<template #right>
<el-button type="primary" @click="search.current =1, getList()">查询</el-button>
</template>
</ai-search-bar>
<ai-search-bar>
<template #left>
<el-button type="button" :class="'el-button el-button--primary'" @click="beforeCopy()">开始采集</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 8px;"
:current.sync="search.current" :size.sync="search.size"
@getList="getList">
<el-table-column slot="url" label="商品地址" align="left">
<template slot-scope="scope">
<div v-if="scope.row.url.startsWith('http')"><a class="el-link el-link--primary" :href="scope.row.url" target="_blank">{{ scope.row.url }}</a></div>
<div v-else><a class="el-link el-link--primary" :href="'https://www.temu.com/goods.html?goods_id=' + scope.row.url" target="_blank">{{ scope.row.url }}</a></div>
</template>
</el-table-column>
<el-table-column slot="type" label="来源" width="100px" align="left">
<template slot-scope="scope">
速卖通
</template>
</el-table-column>
</ai-table>
</div>
</template>
</ai-list>
<ai-dialog
title="速卖通采集"
:visible.sync="copyFromDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="handleClose">
<ai-copy-from-ali-express v-if="copyFromDlgShow" @onClose="handleClose" @onSuccess="handleSuccess"></ai-copy-from-ali-express>
</ai-dialog>
</div>
</template>
<script>
import AiCopyFromAliExpress from "@/components/AiCopyFromAliExpress.vue";
import {sendAliexpressAPIMessage} from '@/api/chromeApi'
export default {
name: 'NiubiCopy',
components: {AiCopyFromAliExpress},
data() {
return {
search: {
current: 1,
size: 10,
type: '2',
mallId: ''
},
colConfigs: [
{slot: 'url', label: '商品地址', align: 'left'},
{slot: 'type', label: '来源', width: '100px', align: 'left'},
{prop: 'mallName', label: '店铺名称', width: '200px', align: 'left'},
{prop: 'createTime', label: '复制时间', width: '180px'}
],
tableData: [],
total: 0,
copyFromDlgShow: false,
}
},
created() {
this.getList()
},
methods: {
async getList() {
/*const image = 'http://temu.jjcp52.com/dist/test.png'
let res2 = await getImageMd5(image), res4
Promise.all([
sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/gmp/bg/phoenix/api/material/create',
needMallId: true,
mallId: '634418212443160',
data: {
folderId: 0,
createDetailList: [
{
materialType: 1,
materialMd5: res2.md5,
materialName: "test"
}
]
}
}),
sendChromeAPIMessage({
url: 'galerie/business/get_signature?sdk_version=js-0.0.16-alpha.0&tag_name=product-material-tag',
needMallId: true,
mallId: '634418212443160',
data: {
bucket_tag: "product-material-tag"
}
}).then(res => res4 = res)
]).then(() => sendChromeAPIMessage({
url: 'https://kuajing-file.pinduoduo.com/api/galerie/v3/store_image?sdk_version=js-0.0.16-alpha.0&tag_name=product-material-tag',
isFormData: true,
data: {
url_width_height: true,
image,
upload_sign: res4.result.signature
}
}))*/
this.$http.post('/api/copyProduct/myPage', null, {
params: {
...this.search
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
beforeCopy() {
this.copyFromDlgShow = true
},
handleClose() {
this.copyFromDlgShow = false
},
// 添加模板成功
handleSuccess() {
this.copyFromDlgShow = false
this.getList()
}
},
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,340 +0,0 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
title="店铺跟踪"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<el-button type="button" :icon="'el-icon-delete'" :class="'el-button el-button--primary'" @click="remove()">删除</el-button>
<el-button v-if="$store.state.mallName" type="button" :class="'el-button el-button--primary'" @click="toAddTemplate()">添加商品模板</el-button>
<el-button v-if="$store.state.mallName" type="button" :class="'el-button el-button--primary'" @click="beforeAddToDraft">添加到草稿箱</el-button>
</template>
<template #right>
<el-button size="small" circle icon="el-icon-refresh-right" @click="getList()"></el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 8px;"
:current.sync="search.current" :size.sync="search.size"
@selection-change="handleSelectionChange"
@getList="getList">
</ai-table>
</div>
</template>
</ai-list>
<ai-dialog
title="添加店铺"
:visible.sync="mallDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="handleClose">
<el-form class="ai-form" :model="form" label-width="120px" ref="form">
<el-form-item label="店铺" style="width: 100%;" prop="targetMallId" :rules="[{ required: true, message: '请选择店铺', trigger: 'blur' }]">
<el-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
:label="item.mallName"
:value="item.mallId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品分类" style="width: 100%;" prop="targetCatId" :rules="[{ required: true, message: '请选择商品分类', trigger: 'blur' }]">
<el-cascader style="width: 380px" v-model="form.targetCatId" :props="props"></el-cascader>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="mallDlgShow = false"> </el-button>
<el-button type="primary" @click="addToDraft">确定</el-button>
</div>
</ai-dialog>
</div>
</template>
<script>
import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import {transform} from '@/utils/product'
import { Message } from 'element-ui'
export default {
name: 'CopyProduct',
data () {
return {
search: {
current: 1,
size: 10,
productName: '',
mallName: '',
startDate: '',
endDate: ''
},
props: {
value: 'catId',
label: 'catName',
lazy: true,
lazyLoad (node, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list',
needMallId: true,
data: {
parentCatId: node.value || ''
}
}).then(res => {
if (res.errorCode === 1000000) {
resolve(res.result.categoryNodeVOS.map(v => {
return {
...v,
leaf: v.isLeaf
}
}))
}
})
}
},
colConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'productSpu', label: 'SPU', align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'mallName', label: '来源店铺', align: 'left'},
{ prop: 'createTime', label: '添加时间', width: '180px', fixed: 'right'}
],
tableData: [],
total: 0,
ids: [],
dlgShow: false,
productTableData: [],
productPage: {page: 1, pageSize: 10, mallId: '', productName: '', productSkcIds: '', total: 0},
productColConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'productSpu', label: 'SPU ID', align: 'left' },
{ prop: 'productSkc', label: 'SKC ID', align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'left' },
{ prop: 'createTime', label: '创建时间', align: 'left' }
],
mallDlgShow: false,
productIds: [],
form: {
targetMallId: '',
targetCatId: []
}
}
},
created () {
this.getList()
if (this.$store.state.mallList.length > 0) {
this.productPage.mallId = this.$store.state.mallList[0].mallId
}
},
methods: {
getList () {
this.$http.post('/api/product/myPage',null,{
params: {
...this.search
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
remove () {
if (this.ids.length <= 0) {
alert('请选择要删除的商品');
return;
}
this.$confirm('确定要删除?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post('/api/product/delByIds',this.ids
).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
handleSelectionChange(val) {
this.ids = [];
val.forEach(e => {
this.ids.push(e.id);
});
},
// 添加模板
handleClose() {
this.productTableData = []
this.productPage = {page: 1, pageSize: 10, total: 0}
this.dlgShow = false
},
toAddTemplate() {
this.$http.post('/api/malluser/info').then(res => {
if (res.code == 0) {
this.$store.commit('setUserInfo', res.data)
if (res.data.flag != 1) {
Message.error('您的账号未激活或已失效,请激活后使用')
this.$store.commit('setActiveDlgShow', true)
return;
}
this.dlgShow = true
this.getProductList()
}
})
},
getProductList() {
let params = {};
params.page = this.productPage.page;
params.pageSize = this.productPage.pageSize;
if (this.productPage.productName) {
params.productName = this.productPage.productName
}
if (this.productPage.productSkcIds) {
params.productSkcIds = this.productPage.productSkcIds.split(',')
}
sendChromeAPIMessage({
url: 'bg-visage-mms/product/skc/pageQuery',
needMallId: true,
mallId: this.productPage.mallId,
data: {
...params
}}).then((res) => {
if (res.errorCode == 1000000) {
this.productPage.total = res.result.total
this.productTableData = res.result.pageItems.map((item) => {
return {
productSpu: item.productId,
productSkc: item.productSkcId,
productName: item.productName,
createTime: timestampToTime(item.createdAt)
};
})
} else {
this.getProductList()
}
});
},
productHandleSelectionChange(val) {
this.productIds = [];
val.forEach(e => {
this.productIds.push(e.productSpu);
});
},
saveProduct() {
if (this.productIds.length <= 0) {
Message.error('请选择商品');
return;
}
this.productIds.map((productSpu, index) => {
setTimeout(() => {
this.saveTemplate(productSpu, index)
}, 200 * index)
})
},
saveTemplate(spu, index) {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/query',
needMallId: true,
mallId: this.productPage.mallId,
data: {
productEditTaskUid: '',
productId: spu
}}).then((res) => {
if (res.errorCode == 1000000) {
let content = transform(res.result)
let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.productPage.mallId
})
this.$http.post('/api/product/add', {
mallId: mallInfo[0].mallId,
mallName: mallInfo[0].mallName,
productSpu: res.result.productId,
productName: res.result.productName,
content: content
}).then(res1 => {
if (res1.code == 0) {
Message.success("商品【" + res.result.productName + "】成功添加为商品模板")
if (index == this.productIds.length - 1) {
this.getList()
}
}
})
} else {
this.saveTemplate(spu, index)
}
})
},
beforeAddToDraft() {
if (this.ids.length <= 0) {
Message.error('请选择商品模板');
return;
}
this.mallDlgShow = true
},
addToDraft() {
this.$refs.form.validate((valid) => {
if (valid) {
this.ids.map((id, index) => {
let product = this.tableData.filter((item) => {
return item.id == id
})
setTimeout(() => {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/add',
needMallId: true,
mallId: this.form.targetMallId,
data: {
catId: this.form.targetCatId[this.form.targetCatId.length - 1]
}}).then((res) => {
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
let content = JSON.parse(product[0].content)
let i = 0
for (; i < this.form.targetCatId.length; i++) {
content['cat' + (i+1) + 'Id'] = this.form.targetCatId[i]
}
for (; i < 10; i++) {
content['cat' + (i+1) + 'Id'] = ''
}
content.productDraftId = draftId
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
mallId: this.form.targetMallId,
data: {
...content
}}).then((res) => {
if (res.errorCode == 1000000) {
Message.success("商品【" + product[0].productName + "】成功添加到草稿箱")
}
})
} else {
Message.error("【拼多多】" + res.errorMsg)
}
})
}, 1000*index)
})
this.mallDlgShow = false
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -3,7 +3,7 @@
<ai-list class="list"> <ai-list class="list">
<ai-title <ai-title
slot="title" slot="title"
title="智能复制" title="商品采集"
tips="请先在当前浏览器登录“拼多多跨境卖家中心”,期间保持登录状态" tips="请先在当前浏览器登录“拼多多跨境卖家中心”,期间保持登录状态"
isShowBottomBorder> isShowBottomBorder>
</ai-title> </ai-title>
@@ -11,10 +11,6 @@
<div class="content"> <div class="content">
<ai-search-bar> <ai-search-bar>
<template #left> <template #left>
<div class="search-item">
<label style="width:80px">来源</label>
<ai-select :selectList="$dict.getDict('copy_from')" :clearable="true" v-model="search.type" @change="search.current =1, getList()"></ai-select>
</div>
<div class="search-item"> <div class="search-item">
<label style="width:80px">店铺</label> <label style="width:80px">店铺</label>
<el-select v-model="search.mallId" :clearable="true" @change="search.current =1, getList()" placeholder="请选择店铺" size="small"> <el-select v-model="search.mallId" :clearable="true" @change="search.current =1, getList()" placeholder="请选择店铺" size="small">
@@ -33,7 +29,7 @@
</ai-search-bar> </ai-search-bar>
<ai-search-bar> <ai-search-bar>
<template #left> <template #left>
<el-button type="button" :class="'el-button el-button--primary'" @click="beforeCopy()">开始复制</el-button> <el-button type="button" :class="'el-button el-button--primary'" @click="beforeCopy()">开始采集</el-button>
</template> </template>
</ai-search-bar> </ai-search-bar>
<ai-table <ai-table
@@ -45,7 +41,13 @@
@getList="getList"> @getList="getList">
<el-table-column slot="url" label="商品地址" align="left"> <el-table-column slot="url" label="商品地址" align="left">
<template slot-scope="scope"> <template slot-scope="scope">
<div><a :href="scope.row.url" target="_blank">{{ scope.row.url }}</a></div> <div v-if="scope.row.url.startsWith('http')"><a class="el-link el-link--primary" :href="scope.row.url" target="_blank">{{ scope.row.url }}</a></div>
<div v-else><a class="el-link el-link--primary" :href="'https://www.temu.com/goods.html?goods_id=' + scope.row.url" target="_blank">{{ scope.row.url }}</a></div>
</template>
</el-table-column>
<el-table-column slot="type" label="来源" width="100px" align="left">
<template slot-scope="scope">
TEMU
</template> </template>
</el-table-column> </el-table-column>
</ai-table> </ai-table>
@@ -54,7 +56,7 @@
</ai-list> </ai-list>
<ai-dialog <ai-dialog
title="复制" title="TEMU采集"
:visible.sync="copyFromDlgShow" :visible.sync="copyFromDlgShow"
:close-on-click-modal="false" :close-on-click-modal="false"
width="790px" width="790px"
@@ -76,15 +78,15 @@ import AiCopyFromTemu from "@/components/AiCopyFromTemu.vue";
search: { search: {
current: 1, current: 1,
size: 10, size: 10,
type: '', type: '1',
mallId: '' mallId: ''
}, },
colConfigs: [ colConfigs: [
{ slot: 'url', label: '商品地址', align: 'left' }, { slot: 'url', label: '商品地址', align: 'left' },
{ prop: 'type', label: '来源', width: '100px', align: 'left',format: v => this.$dict.getLabel('copy_from', v), }, { slot: 'type', label: '来源', width: '100px', align: 'left'},
{ prop: 'mallName', label: '店铺名称', width: '200px', align: 'left'}, { prop: 'mallName', label: '店铺名称', width: '200px', align: 'left'},
{ prop: 'createTime', label: '复制时间', width: '180px', fixed: 'right'} { prop: 'createTime', label: '复制时间', width: '180px'}
], ],
tableData: [], tableData: [],
total: 0, total: 0,
@@ -93,7 +95,6 @@ import AiCopyFromTemu from "@/components/AiCopyFromTemu.vue";
}, },
created () { created () {
this.$dict.load('copy_from');
this.getList() this.getList()
}, },

View File

@@ -0,0 +1,42 @@
<template>
<div style="height: 100%;">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="['Detail']">
<component ref="component" :is="component" @change="onChange" :params="params"></component>
</keep-alive>
</transition>
</div>
</template>
<script>
import Detail from './components/Detail.vue'
export default {
name: 'BestSellers',
label: '畅销品',
components: {
Detail
},
data () {
return {
component: 'Detail',
params: {}
}
},
methods: {
onChange (data) {
if (data.type === 'Detail') {
this.component = 'Detail'
this.params = data.params
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,430 @@
<template>
<div>
<ai-list class="list" v-loading="isLoading" :element-loading-text="loadingText">
<ai-title
slot="title"
title="7天畅销品"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
<el-checkbox v-model="isMutiSelect">开启多选</el-checkbox>&nbsp;
<el-button type="primary" round @click="toBatchCopy" v-if="isMutiSelect">批量采集</el-button>
<div>
<el-button type="button" :class="'el-button el-button--primary'" v-if="currentData.optId" @click="beginSync">同步</el-button>
</div>
</div>
</template>
</ai-title>
<template #left>
<div class="categoryTree">
<el-tree ref="tree" :data="categoryData" node-key="optId" :props="props"
:default-expanded-keys="defaultExpanded"
:expand-on-click-node="false"
:check-on-click-node="true"
:highlight-current="true"
@node-click="handleNodeClick"/>
</div>
</template>
<template slot="content">
<ai-search-bar>
<template #left>
<label style="width:80px">排序方式</label>
<el-select v-model="search.orderBy" :clearable="true" @change="search.current =1, getList()" placeholder="请选择排序方式" size="small">
<el-option
v-for="item in orderBys"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<label style="width:80px">价格区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceEnd" ></el-input>
<label style="width:80px">销量区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleEnd" ></el-input>
</template>
<template #right>
<el-button type="primary" @click="search.current =1, getList()">查询</el-button>
</template>
</ai-search-bar>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 16px;">
<el-card v-for="item in tableData" :key="item.id" :body-style="{ padding: '0px', margin: '5px' }">
<div :class="[{'img-background': item.isSelect}, 'img-box']" @click="onGoodsSelect(item)">
<div>
<el-image v-if="isMutiSelect" :src="item.imgUrl" :class="[{'mask': item.isSelect}, 'image']" />
<el-image v-else :src="item.imgUrl" class="image" :preview-src-list="[item.imgUrl]" />
</div>
<span class="icon-box" v-if="item.isSelect && isMutiSelect">
<img style="width: 20px; height: 20px" src="../../../../assets/right.png">
</span>
</div>
<div style="padding: 14px;">
<div class="bottom clearfix">
<div style="margin-bottom: 5px;">
<div style="display: inline; margin-left: 5px;">${{ item.price }}</div>
<div style="display: inline; margin-right: 5px; float: right;">{{ item.saleTotal }}</div>
</div>
<div style="margin-bottom: 5px;">
<div style="display: inline; margin-left: 5px;">{{ item.createTime.substring(0,10) }}</div>
<ai-product-drop-down :params="item" :isShowGroup="false" :isShowDetail="false" :isShowAddFavorite="false" :isShowDelFavorite="false"
style="float: right;"></ai-product-drop-down>
</div>
</div>
</div>
</el-card>
<el-pagination
layout="prev, pager, next"
style="position: absolute; bottom: 0px; right: 40px;"
:total="total"
:page-size.sync="search.size"
:current-page.sync="search.current"
@current-change="getList">
<div class="paginationPre-total"><label class="color-primary">{{ total }}</label>条记录
</div>
</el-pagination>
</div>
<ai-dialog
title="批量采集"
:visible.sync="copyFromDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
:append-to-body="true"
@close="copyFromDlgShow = false">
<ai-copy-from-temu v-if="copyFromDlgShow" :isMultiCopy="true" :params="temuParams" @onClose="copyFromDlgShow = false" @onSuccess="copyFromDlgShow = false"></ai-copy-from-temu>
</ai-dialog>
</template>
</ai-list>
</div>
</template>
<script>
import AiProductDropDown from '@/components/AiProductDropDown.vue'
import {sendTemuAPIMessage, sendChromeWebReqMessage} from '@/api/chromeApi'
import { Message } from 'element-ui'
export default {
name: 'DetailPage',
props: ['params'],
components: {AiProductDropDown},
data () {
return {
props: {
label: 'name',
isLeaf: data => data.parentId == 0
},
search: {
current: 1,
size: 120,
orderBy: '',
priceBegin: '',
priceEnd: '',
saleBegin: '',
saleEnd: ''
},
orderBys: [{
value: '0',
label: '按销量从多到少排序'
},{
value: '1',
label: '按销量从少到多排序'
},{
value: '2',
label: '按价格从高到底排序'
},{
value: '3',
label: '按价格从低到高排序'
}],
defaultExpanded: [],
tableData: [],
total: 0,
offset: 0,
pageSize: 120,
currentData: {},
categoryData: [],
isLoading: false,
loadingText: '拼命加载中',
isMutiSelect: false,
copyFromDlgShow: false,
temuParams: {}
}
},
created () {
this.loadCategory()
this.getList()
},
methods: {
loadCategory() {
this.$http.post('/api/category/listByParent_bestSellers',null,{
params: {
parentId: "0"
}
}).then(res => {
if (res?.data) {
let data = res.data.filter(item => {
return item.optId != -13
})
this.categoryData = data.map(item => {
return {
name: item.name + '(' + item.total + ')',
optId: item.optId,
parentId: item.parentId
}
})
}
})
},
beginSync() {
this.loadingText = '拼命加载中'
this.isLoading = true
this.offset = 0
this.getBestSellersData()
},
async getBestSellersData() {
let res = await sendTemuAPIMessage({
url: 'api/poppy/v1/title_bar_recommend?scene=home_title_bar_recommend',
anti: true,
data:
{
scene: "home_title_bar_recommend",
listId: 'best_sellers_list_vvz0h4',
pageSn: '10125',
offset: this.offset,
pageSize: this.pageSize,
filterItems: "1:1|7:7",
optId: this.currentData.optId,
optType: 1
}})
if (res.success && res.result.data.goods_list) {
let arr = []
for (let i = 0;i < res.result.data.goods_list.length; i++) {
let item = res.result.data.goods_list[i]
let total = 0
if (item.sales_tip_text[0]) {
total = item.sales_tip_text[0]
total = total.replace('+', '')
if (total.indexOf('K') != -1) {
total = total.replace('K', '')
total = total * 1000
} else if (total.indexOf('M') != -1) {
total = total.replace('M', '')
total = total * 1000000
}
}
let data = {
goodsId: item.goods_id,
price: item.price_info.price_schema,
saleTotal: item.sales_tip_text[0],
saleTotalNum: total,
url: 'https://www.temu.com/goods.html?goods_id=' + item.goods_id,
imgUrl: item.thumb_url,
category: this.currentData.optId,
mallId: item.mall_id
}
arr.push(data)
}
let res2 = await this.$http.post('/api/bestSellers/addBatch', arr)
this.offset = this.offset + this.pageSize
this.getBestSellersData()
} else {
this.loadCategory()
this.getList()
this.isLoading = false
}
},
getList() {
this.$http.post('/api/bestSellers/page',null,{
params: {
category: this.currentData?.optId || '',
...this.search
}
}).then(res => {
res.data.records.map(item => {
item.isSelect = false
})
this.tableData = res.data.records
this.total = res.data.total
})
},
handleNodeClick(data) {
this.currentData = data
this.search.current = 1
this.getList()
},
onGoodsSelect(item) {
item.isSelect = !item.isSelect
},
toBatchCopy() {
let goodsObj = this.tableData.filter(item => {
return item.isSelect
})
if (goodsObj.length > 0) {
let urlList = goodsObj.map(item => {
return 'https://www.temu.com/goods.html?goods_id=' + item.goodsId
})
this.temuParams = {
urlList: urlList
}
this.copyFromDlgShow = true
} else {
Message.error("请选择商品")
}
}
}
}
</script>
<style scoped lang="scss">
.list {
.title-right {
display: flex;
align-items: center;
& > div:first-child {
margin-right: 20px;
}
}
}
.categoryTree {
.gap {
width: 100%;
height: 17px;
}
:deep( .el-tree ) {
background: #fff;
.el-tree-node__expand-icon.is-leaf {
color: transparent !important;
}
.el-tree-node__content {
display: inline-flex;
min-width: 100%;
}
.el-tree-node__content > .el-tree-node__expand-icon {
padding: 4px;
}
.el-tree-node__content {
height: 32px;
}
.el-tree__empty-text {
color: #222;
font-size: 14px;
}
.el-tree-node__children .el-tree-node__content {
height: 32px;
}
.el-tree-node__content:hover {
background: #E8EFFF;
color: #222222;
border-radius: 2px;
}
.is-current > .el-tree-node__content {
&:hover {
background: #000;
color: #fff;
}
background: #000;
span {
color: #fff;
font-weight: bold;
}
}
}
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-icon-arrow-down {
font-size: 12px;
}
.img-box {
display: flex;
justify-content: center;
align-items: center;
//父级设置 相对定位,让 icon设置绝对定位时能够以该父级为准。
position: relative;
// icon 设置 绝对定位 让其固定在你想要的合适位置。 样式可调整,自己定位即可。
.icon-box {
position: absolute;
top: 35%;
}
}
.mask {
background-color: #000;
opacity: 0.6;
.el-image__inner {
opacity: 0.6;
}
}
::v-deep .mask > img {
opacity: 0.6;
}
::v-deep .el-loading-spinner {
top: 200px;
}
.el-icon {
display: inline-block;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<div style="height: 100%;">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="['Detail']">
<component ref="component" :is="component" @change="onChange" :params="params"></component>
</keep-alive>
</transition>
</div>
</template>
<script>
import Detail from './components/Detail.vue'
export default {
name: 'FavoriteTrack',
label: '我的跟踪收藏',
components: {
Detail
},
data () {
return {
component: 'Detail',
params: {}
}
},
methods: {
onChange (data) {
if (data.type === 'Detail') {
this.component = 'Detail'
this.params = data.params
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,221 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
title="收藏列表"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<label style="width:80px">排序方式</label>
<el-select v-model="search.orderBy" :clearable="true" @change="search.current =1, getList()" placeholder="请选择排序方式" size="small">
<el-option
v-for="item in orderBys"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<label style="width:80px">价格区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceEnd" ></el-input>
<label style="width:80px">销量区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleEnd" ></el-input>
</template>
<template #right>
<el-button type="primary" @click="search.current =1, getList()">查询</el-button>
</template>
</ai-search-bar>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 16px;">
<el-card v-for="item in tableData" :key="item.id" :body-style="{ padding: '0px', margin: '5px' }">
<el-image :src="item.imgUrl" class="image" :preview-src-list="[item.imgUrl]" />
<div style="padding: 14px;">
<div class="bottom clearfix">
<div style="margin-bottom: 5px;">
<div style="display: inline; margin-left: 5px;">${{ item.price }}<sub style="margin-left: 2px;" v-html="getPricePercent(item.priceChange)"></sub></div>
<div style="display: inline; margin-right: 5px; float: right;">{{ item.saleTotal }}<sub style="margin-left: 2px;" v-html="getSalePercent(item.saleChange)"></sub></div>
</div>
<div>
<div style="display: inline">日均销量: <span style="color: red; font-weight: bold">{{ item.averageSale }}</span></div>
<ai-product-drop-down :params="item" :isShowDetail="true" :isShowAddFavorite="false" :isShowDelFavorite="true"
@onDelFavoriteSuccess="deleteFavoriteSuccess()"
@onGoDetail="goDetail(item.goodsId, item.monitorId)"
style="float: right;"></ai-product-drop-down>
</div>
</div>
</div>
</el-card>
<el-pagination
layout="prev, pager, next"
style="position: absolute; bottom: 0px; right: 40px;"
:total="total"
:page-size.sync="search.size"
:current-page.sync="search.current"
@current-change="getList">
<div class="paginationPre-total"><label class="color-primary">{{ total }}</label>条记录
</div>
</el-pagination>
</div>
</template>
</ai-list>
<div class="productDetail">
<el-dialog
title="商品详情"
:visible="isShowDetailDlg"
:close-on-click-modal="false"
width="80%"
:before-close="handleClose">
<ai-product-detail v-if="isShowDetailDlg" :params="detailParams"/>
</el-dialog>
</div>
</div>
</template>
<script>
import AiProductDetail from "@/components/AiProductDetail.vue";
import AiProductDropDown from '@/components/AiProductDropDown.vue';
export default {
name: 'DetailPage',
props: ['params'],
components: {AiProductDetail, AiProductDropDown},
data () {
return {
monitorId: '',
search: {
current: 1,
type: '1',
size: 120,
orderBy: '',
priceBegin: '',
priceEnd: '',
saleBegin: '',
saleEnd: ''
},
orderBys: [{
value: '0',
label: '按销量涨辐排序'
},{
value: '1',
label: '按价格涨辐排序'
},{
value: '2',
label: '按销量排序'
},{
value: '3',
label: '按价格排序'
},{
value: '4',
label: '按日均销量排序'
}],
tableData: [],
total: 0,
isShowDetailDlg: false,
detailParams: {}
}
},
created () {
this.monitorId = this.params.id
this.getList()
},
methods: {
getList () {
this.$http.post('/api/monitorFavorite/myPage',null,{
params: {
...this.search
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
deleteFavoriteSuccess() {
this.getList()
},
cancel (isRefresh) {
this.$emit('change', {
type: 'List',
isRefresh: !!isRefresh
})
},
getPricePercent(data) {
if (data < 0) {
return '<div style="display: inline; color: green">↓' + data + '%</div>'
} else if (data > 0) {
return '<div style="display: inline; color: red">↑' + data + '%</div>'
}
return ''
},
getSalePercent(data) {
if (data < 0) {
return '<div style="display: inline; color: green">↓' + data + '%</div>'
} else if (data > 0) {
return '<div style="display: inline; color: red">↑' + data + '%</div>'
}
return ''
},
handleClose() {
this.isShowDetailDlg = false
},
goDetail (goodsId, monitorId) {
this.detailParams = {goodsId: goodsId, monitorId: monitorId}
this.isShowDetailDlg = true
}
}
}
</script>
<style scoped lang="scss">
.productDetail {
:deep(.el-dialog) {
height: 78vh;
overflow: auto;
}
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-icon-arrow-down {
font-size: 12px;
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<div style="height: 100%;">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="['List']">
<component ref="component" :is="component" @change="onChange" :params="params"></component>
</keep-alive>
</transition>
</div>
</template>
<script>
import List from './components/List.vue'
import Detail from './components/Detail.vue'
export default {
name: 'IndexTrack',
label: '首页跟踪',
components: {
List,
Detail
},
data () {
return {
component: 'List',
params: {}
}
},
methods: {
onChange (data) {
if (data.type === 'Detail') {
this.component = 'Detail'
this.params = data.params
}
if (data.type === 'List') {
this.component = 'List'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
setTimeout(() => {
this.$refs.component.getList()
}, 600)
}
})
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,212 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
title="商品列表"
isShowBottomBorder isShowBack @onBackClick="cancel(false)">
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<label style="width:80px">排序方式</label>
<el-select v-model="search.orderBy" :clearable="true" @change="search.current =1, getList()" placeholder="请选择排序方式" size="small">
<el-option
v-for="item in orderBys"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<label style="width:80px">价格区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceEnd" ></el-input>
<label style="width:80px">销量区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleEnd" ></el-input>
</template>
<template #right>
<el-button type="primary" @click="search.current =1, getList()">查询</el-button>
</template>
</ai-search-bar>
<div style="margin-right: 0px; margin-bottom: 10px;">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 16px;">
<el-card v-for="item in tableData" :key="item.id" :body-style="{ padding: '0px', margin: '5px' }">
<el-image :src="item.imgUrl" class="image" :preview-src-list="[item.imgUrl]" />
<div style="padding: 14px;">
<div class="bottom clearfix">
<div style="margin-bottom: 5px;">
<div style="display: inline; margin-left: 5px;">${{ item.price }}<sub style="margin-left: 2px;" v-html="getPricePercent(item.priceChange)"></sub></div>
<div style="display: inline; margin-right: 5px; float: right;">{{ item.saleTotal }}<sub style="margin-left: 2px;" v-html="getSalePercent(item.saleChange)"></sub></div>
</div>
<ai-product-drop-down :params="item" :isShowDetail="true" :isShowAddFavorite="false" :isShowDelFavorite="false" @onGoDetail="goDetail(item.goodsId, item.categoryId)" style="float: right;"></ai-product-drop-down>
</div>
</div>
</el-card>
<el-pagination
layout="prev, pager, next"
style="position: absolute; bottom: 0px; right: 40px;"
:total="total"
:page-size.sync="search.size"
:current-page.sync="search.current"
@current-change="getList">
</el-pagination>
</div>
</template>
</ai-list>
<div class="productDetail">
<el-dialog
title="商品详情"
:visible="isShowDetailDlg"
:close-on-click-modal="false"
width="80%"
:before-close="handleClose">
<ai-special-product-detail v-if="isShowDetailDlg" :params="detailParams"/>
</el-dialog>
</div>
</div>
</template>
<script>
import AiSpecialProductDetail from "@/components/AiSpecialProductDetail.vue";
import AiProductDropDown from '@/components/AiProductDropDown.vue';
export default {
name: 'DetailPage',
props: ['params'],
components: {AiSpecialProductDetail, AiProductDropDown},
data () {
return {
categoryId: '',
search: {
current: 1,
type: '1',
size: 120,
orderBy: '',
priceBegin: '',
priceEnd: '',
saleBegin: '',
saleEnd: ''
},
orderBys: [{
value: '0',
label: '按销量涨辐排序'
},{
value: '1',
label: '按价格涨辐排序'
},{
value: '2',
label: '按销量排序'
},{
value: '3',
label: '按价格排序'
}],
tableData: [],
total: 0,
isShowDetailDlg: false,
detailParams: {}
}
},
created () {
this.categoryId = this.params.id
this.getList()
},
methods: {
getList () {
this.$http.post('/api/specialDetail/myPageNew',null,{
params: {
categoryId: this.categoryId,
...this.search
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'List',
isRefresh: !!isRefresh
})
},
getPricePercent(data) {
if (data < 0) {
return '<div style="display: inline; color: green">↓' + data + '%</div>'
} else if (data > 0) {
return '<div style="display: inline; color: red">↑' + data + '%</div>'
}
return ''
},
getSalePercent(data) {
if (data < 0) {
return '<div style="display: inline; color: green">↓' + data + '%</div>'
} else if (data > 0) {
return '<div style="display: inline; color: red">↑' + data + '%</div>'
}
return ''
},
handleClose() {
this.isShowDetailDlg = false
},
goDetail (goodsId, categoryId) {
this.detailParams = {goodsId: goodsId, categoryId: categoryId}
this.isShowDetailDlg = true
}
}
}
</script>
<style scoped lang="scss">
.productDetail {
:deep(.el-dialog) {
height: 78vh;
overflow: auto;
}
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-icon-arrow-down {
font-size: 12px;
}
</style>

View File

@@ -0,0 +1,237 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
title="分类首页商品跟踪"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<el-button type="button" :class="'el-button el-button--primary'" @click="addStore()">添加分类</el-button>
</template>
<template #right>
<el-button size="small" circle icon="el-icon-refresh-right" @click="getList()"></el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 8px;"
@selection-change="onChooseChange"
:current.sync="search.current" :size.sync="search.size"
@getList="getList">
<el-table-column slot="options" label="操作" width="240px" show-overflow-tooltip align="center" fixed="right">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="deleteMonitor(row.id)">删除</el-button>
<el-button type="text" @click="renew(row.id)">续费</el-button>
<el-button type="text" @click="toDetail(row.categoryId)">详情</el-button>
</div>
</template>
</el-table-column>
<el-table-column slot="categoryId" label="分类" show-overflow-tooltip align="center">
<template slot-scope="{ row }">
<div><a class="el-link el-link--primary" :href="'https://www.temu.com' + row.categoryUrl" target="_blank">{{ row.categoryName }}</a></div>
</template>
</el-table-column>
</ai-table>
</div>
</template>
</ai-list>
<ai-dialog
title="添加分类"
:visible.sync="isDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="isDlgShow = false">
<el-alert
title="添加一个分类将消耗300金币"
type="success"
:closable="false">
</el-alert>
<el-form class="ai-form" style="margin-top: 20px" :model="form" label-width="120px" ref="form">
<el-form-item
prop="categoryId"
label="分类"
:rules="[{ required: true, message: '请选择分类', trigger: 'blur' }]">
<el-cascader style="width: 380px" v-model="form.categoryId" :props="props"></el-cascader>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="isDlgShow = false"> </el-button>
<el-button type="primary" @click="saveStore">确定</el-button>
</div>
</ai-dialog>
</div>
</template>
<script>
export default {
name: 'List',
data () {
let _this = this
return {
search: {
current: 1,
type: '1',
size: 10
},
colConfigs: [
{ type: "selection", width: '70px', align: 'left' },
{ slot: 'categoryId', label: '分类', align: 'left' },
{ prop: 'status', label: '状态', align: 'left', format: v => this.$dict.getLabel('monitor_status', v), },
{ prop: 'expireTime', label: '失效时间', align: 'left' },
{ prop: 'createTime', label: '添加时间', width: '180px', fixed: 'right'}
],
tableData: [],
total: 0,
form: {
categoryId: ''
},
props: {
value: 'optId',
label: 'name',
lazy: true,
lazyLoad (node, resolve) {
_this.$http.post('/api/category/listAllByParentId', null, {
params: {parentId: node.value || 0}
}).then(res => {
let parentId = node.value || 0
if (res.code === 0) {
resolve(res.data.map(v => {
return {
...v,
leaf: parentId == 0 ? false: true
}
}))
}
})
}
},
isDlgShow: false,
selectRows: []
}
},
created () {
this.$dict.load('monitor_status');
this.getList()
},
methods: {
getList () {
this.$http.post('/api/specialMonitor/myPage',null,{
params: {
...this.search
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
onChooseChange (val) {
this.selectRows = val;
},
saveStore () {
this.$refs.form.validate((valid) => {
if (valid) {
this.$http.post('/api/specialMonitor/check',null, {params: {type: 1}}).then(res => {
if (res.code == 0) {
this.$http.post(`/api/specialMonitor/add`, {categoryId: this.form.categoryId[this.form.categoryId.length - 1]}).then(res => {
if (res.code == 0) {
this.$message.success('添加成功!')
this.$store.dispatch('getUserInfo')
this.getList()
this.isDlgShow = false
}
})
}
})
}
})
},
batchRemove() {
if (this.selectRows.length <= 0) {
this.$message.error('请选择要删除的分类');
return;
}
let ids = this.selectRows.map(item => {
return item.id
})
this.$confirm('确定要删除?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post('/api/specialMonitor/delByIds', ids
).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
renew(id) {
this.$confirm('确定要续费?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post('/api/specialMonitor/renew',null, {
params: {
id: id
}
}
).then(res => {
if (res.code == 0) {
this.$message.success('续费成功!')
this.$store.dispatch('getUserInfo')
this.getList()
}
})
})
},
deleteMonitor(id) {
this.$confirm('确定要删除?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post('/api/specialMonitor/del',null, {
params: {
id: id
}
}
).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id: id || ''
}
})
},
addStore() {
this.form.mallId = ''
this.isDlgShow = true
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -3,23 +3,64 @@
<ai-list class="list"> <ai-list class="list">
<ai-title <ai-title
slot="title" slot="title"
title="商品列表" :title="'关键字('+`${content}` + ')商品列表'"
isShowBottomBorder isShowBack @onBackClick="cancel(false)"> isShowBottomBorder isShowBack @onBackClick="cancel(false)">
</ai-title> </ai-title>
<template slot="content"> <template slot="content">
<ai-search-bar>
<template #left>
<label style="width:80px">排序方式</label>
<el-select v-model="search.orderBy" :clearable="true" @change="search.current =1, getList()" placeholder="请选择排序方式" size="small">
<el-option
v-for="item in orderBys"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<label style="width:80px">价格区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceEnd" ></el-input>
<label style="width:80px">销量区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleEnd" ></el-input>
</template>
<template #right>
<el-button type="primary" @click="search.current =1, getList()">查询</el-button>
</template>
</ai-search-bar>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 16px;"> <div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 16px;">
<el-card v-for="item in tableData" :key="item.id" :body-style="{ padding: '0px', margin: '5px' }"> <el-card v-for="item in tableData" :key="item.id" :body-style="{ padding: '0px', margin: '5px' }">
<img :src="item.imgUrl" class="image"> <el-image :src="item.imgUrl" class="image" :preview-src-list="[item.imgUrl]" />
<div style="padding: 14px;"> <div style="padding: 14px;">
<div class="bottom clearfix"> <div class="bottom clearfix">
<div style="margin-bottom: 5px;"> <div style="margin-bottom: 5px;">
<div style="display: inline; margin-left: 5px;">${{ item.priceAndSale[0].price }}<sub style="margin-left: 2px;" v-html="getPricePercent(item.priceAndSale)"></sub></div> <div style="display: inline; margin-left: 5px;">${{ item.price }}<sub style="margin-left: 2px;" v-html="getPricePercent(item.priceChange)"></sub></div>
<div style="display: inline; margin-right: 5px; float: right;">{{ item.priceAndSale[0].sale_total }}<sub style="margin-left: 2px;" v-html="getSalePercent(item.priceAndSale)"></sub></div> <div style="display: inline; margin-right: 5px; float: right;">{{ item.saleTotal }}<sub style="margin-left: 2px;" v-html="getSalePercent(item.saleChange)"></sub></div>
</div>
<div>
<div v-if="search.orderBy == '5'" style="display: inline">近3次日均销量: <span style="color: red; font-weight: bold">{{ item.days3AverageSale }}</span></div>
<div v-else-if="search.orderBy == '6'" style="display: inline">近7次日均销量: <span style="color: red; font-weight: bold">{{ item.days7AverageSale }}</span></div>
<div v-else-if="search.orderBy == '7'" style="display: inline">近15次日均销量: <span style="color: red; font-weight: bold">{{ item.days15AverageSale }}</span></div>
<div v-else style="display: inline">日均销量: <span style="color: red; font-weight: bold">{{ item.averageSale }}</span></div>
<ai-product-drop-down :params="item" :isShowDetail="true" :isShowAddFavorite="true" :isShowDelFavorite="false" @onGoDetail="goDetail(item.goodsId, item.monitorId)" style="float: right;"></ai-product-drop-down>
</div> </div>
<ai-product-drop-down :params="item" :isShowDetail="true" @onGoDetail="goDetail(item.goodsId)" style="float: right;"></ai-product-drop-down>
</div> </div>
</div> </div>
</el-card> </el-card>
<el-pagination
layout="prev, pager, next"
style="position: absolute; bottom: 0px; right: 40px;"
:total="total"
:page-size.sync="search.size"
:current-page.sync="search.current"
@current-change="getList">
<div class="paginationPre-total"><label class="color-primary">{{ total }}</label>条记录
</div>
</el-pagination>
</div> </div>
</template> </template>
</ai-list> </ai-list>
@@ -48,11 +89,42 @@ import AiProductDropDown from '@/components/AiProductDropDown.vue';
data () { data () {
return { return {
monitorId: '', monitorId: '',
content: '',
search: { search: {
current: 1, current: 1,
type: '1', type: '1',
size: 120 size: 120,
orderBy: '',
priceBegin: '',
priceEnd: '',
saleBegin: '',
saleEnd: ''
}, },
orderBys: [{
value: '0',
label: '按销量涨辐排序'
},{
value: '1',
label: '按价格涨辐排序'
},{
value: '2',
label: '按销量排序'
},{
value: '3',
label: '按价格排序'
},{
value: '4',
label: '按日均销量排序'
},{
value: '5',
label: '按近3次采集平均销售量排序'
},{
value: '6',
label: '按近7次采集平均销售量排序'
},{
value: '7',
label: '按近15次采集平均销售量排序'
}],
tableData: [], tableData: [],
total: 0, total: 0,
isShowDetailDlg: false, isShowDetailDlg: false,
@@ -62,12 +134,13 @@ import AiProductDropDown from '@/components/AiProductDropDown.vue';
created () { created () {
this.monitorId = this.params.id this.monitorId = this.params.id
this.content = this.params.content
this.getList() this.getList()
}, },
methods: { methods: {
getList () { getList () {
this.$http.post('/api/monitorDetail/myPage',null,{ this.$http.post('/api/monitorDetail/myPageNew',null,{
params: { params: {
monitorId: this.monitorId, monitorId: this.monitorId,
...this.search ...this.search
@@ -84,38 +157,26 @@ import AiProductDropDown from '@/components/AiProductDropDown.vue';
}) })
}, },
getPricePercent(data) { getPricePercent(data) {
if (data.length == 2) { if (data < 0) {
let a = (data[0].price - data[1].price) / data[1].price return '<div style="display: inline; color: green">↓' + data + '%</div>'
if (a < 0) { } else if (data > 0) {
return '<div style="display: inline; color: green">' + (a*100).toFixed(2) + '%</div>' return '<div style="display: inline; color: red">' + data + '%</div>'
} else if (a == 0) {
return ''
} else if (a > 0) {
return '<div style="display: inline; color: red">↑' + (a*100).toFixed(2) + '%</div>'
}
} else {
return ''
} }
return ''
}, },
getSalePercent(data) { getSalePercent(data) {
if (data.length == 2) { if (data < 0) {
let a = (data[0].sale_total - data[1].sale_total) / data[1].sale_total return '<div style="display: inline; color: green">↓' + data + '%</div>'
if (a < 0) { } else if (data > 0) {
return '<div style="display: inline; color: green">' + (a*100).toFixed(2) + '%</div>' return '<div style="display: inline; color: red">' + data + '%</div>'
} else if (a == 0) {
return ''
} else if (a > 0) {
return '<div style="display: inline; color: red">↑' + (a*100).toFixed(2) + '%</div>'
}
} else {
return ''
} }
return ''
}, },
handleClose() { handleClose() {
this.isShowDetailDlg = false this.isShowDetailDlg = false
}, },
goDetail (goodsId) { goDetail (goodsId, monitorId) {
this.detailParams = {goodsId: goodsId} this.detailParams = {goodsId: goodsId, monitorId: monitorId}
this.isShowDetailDlg = true this.isShowDetailDlg = true
} }
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<ai-list class="list"> <ai-list class="list" v-loading="isLoading" element-loading-text="正在采集中……" element-loading-spinner="el-icon-loading">
<ai-title <ai-title
slot="title" slot="title"
title="关键字跟踪" title="关键字跟踪"
@@ -10,6 +10,8 @@
<div class="content"> <div class="content">
<ai-search-bar> <ai-search-bar>
<template #left> <template #left>
<el-button type="button" :icon="'el-icon-delete'" :class="'el-button el-button--primary'" @click="batchRemove()">删除</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="batchCollect()">批量采集</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="addStore()">添加关键字</el-button> <el-button type="button" :class="'el-button el-button--primary'" @click="addStore()">添加关键字</el-button>
</template> </template>
<template #right> <template #right>
@@ -22,47 +24,99 @@
:total="total" :total="total"
style="margin-top: 8px;" style="margin-top: 8px;"
:current.sync="search.current" :size.sync="search.size" :current.sync="search.current" :size.sync="search.size"
@selection-change="onChooseChange"
@getList="getList"> @getList="getList">
<el-table-column slot="options" label="操作" width="200px" show-overflow-tooltip align="center" fixed="right"> <el-table-column slot="options" label="操作" width="240px" show-overflow-tooltip align="center" fixed="right">
<template slot-scope="{ row }"> <template slot-scope="{ row }">
<div class="table-options"> <div class="table-options">
<el-button type="text" @click="deleteMonitor(row.id)">删除</el-button> <el-button type="text" @click="deleteMonitor(row.id)">删除</el-button>
<el-button type="text" @click="toUpdateMonitor(row)">修改</el-button>
<el-button type="text" @click="renew(row.id)">续费</el-button> <el-button type="text" @click="renew(row.id)">续费</el-button>
<el-button type="text" @click="toDetail(row.id)">详情</el-button> <el-button type="text" @click="toDetail(row.id, row.content)">详情</el-button>
<el-button type="text" @click="toBegin(row)">采集数据</el-button> <el-button type="text" @click="toBegin(row)">采集数据</el-button>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column slot="content" label="关键字" show-overflow-tooltip align="center">
<template slot-scope="{ row }">
<div><a class="el-link el-link--primary" :href="'https://www.temu.com/search_result.html?search_key='+row.content+'&search_method=user&filter_items='+row.orderType" target="_blank">{{ row.content }}</a></div>
</template>
</el-table-column>
</ai-table> </ai-table>
</div> </div>
</template> </template>
</ai-list> </ai-list>
<ai-dialog <ai-dialog
title="添加店铺" title="添加关键字"
:visible.sync="isDlgShow" :visible.sync="isDlgShow"
:close-on-click-modal="false" :close-on-click-modal="false"
width="790px" width="790px"
customFooter customFooter
@close="isDlgShow = false"> @close="isDlgShow = false">
<el-form class="ai-form" :model="form" label-width="120px" ref="form"> <el-alert
title="添加一个关键字跟踪将消耗200金币"
type="success"
:closable="false">
</el-alert>
<el-form class="ai-form" style="margin-top: 20px" :model="form" label-width="120px" ref="form">
<el-form-item <el-form-item
prop="content" prop="content"
label="关键字" label="关键字"
:rules="[{ required: true, message: '请输入关键字', trigger: 'blur' }]"> :rules="[{ required: true, message: '请输入关键字', trigger: 'blur' }]">
<el-input placeholder="请输入关键字" v-model="form.content"></el-input> <el-input placeholder="请输入关键字" v-model="form.content"></el-input>
</el-form-item> </el-form-item>
<el-form-item
prop="orderType"
label="排序方式"
:rules="[{ required: true, message: '请选择排序方式', trigger: 'blur' }]">
<ai-select :selectList="$dict.getDict('monitor_order_type')" v-model="form.orderType"></ai-select>
</el-form-item>
<el-form-item
prop="remark"
label="备注"
:rules="[{ required: true, message: '请输入备注', trigger: 'blur' }]">
<el-input placeholder="请输入备注" v-model="form.remark"></el-input>
</el-form-item>
</el-form> </el-form>
<div class="dialog-footer" slot="footer"> <div class="dialog-footer" slot="footer">
<el-button @click="isDlgShow = false"> </el-button> <el-button @click="isDlgShow = false"> </el-button>
<el-button type="primary" @click="saveStore">确定</el-button> <el-button type="primary" @click="saveStore">确定</el-button>
</div> </div>
</ai-dialog> </ai-dialog>
<ai-dialog
title="修改关键字"
:visible.sync="isUpdateDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="isDlgShow = false">
<el-form class="ai-form" :model="updateForm" label-width="120px" ref="updateForm">
<el-form-item
prop="content"
label="关键字"
:rules="[{ required: true, message: '请输入关键字', trigger: 'blur' }]">
<el-input placeholder="请输入关键字" disabled v-model="updateForm.content"></el-input>
</el-form-item>
<el-form-item
prop="remark"
label="备注"
:rules="[{ required: true, message: '请输入备注', trigger: 'blur' }]">
<el-input placeholder="请输入备注" v-model="updateForm.remark"></el-input>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="isUpdateDlgShow = false"> </el-button>
<el-button type="primary" @click="updateStore">确定</el-button>
</div>
</ai-dialog>
</div> </div>
</template> </template>
<script> <script>
import {sendTemuAPIMessage} from '@/api/chromeApi' import {sendChromeWebReqMessage} from '@/api/chromeApi'
import { Message } from 'element-ui'
export default { export default {
name: 'List', name: 'List',
@@ -75,7 +129,10 @@ import {sendTemuAPIMessage} from '@/api/chromeApi'
size: 10 size: 10
}, },
colConfigs: [ colConfigs: [
{ prop: 'content', label: '关键字', align: 'left' }, { type: "selection", width: '70px', align: 'left' },
{ slot: 'content', label: '关键字', align: 'left' },
{ prop: 'orderType', label: '排序方式', align: 'left', format: v => this.$dict.getLabel('monitor_order_type', v) },
{ prop: 'remark', label: '备注', align: 'left'},
{ prop: 'lastUpdateTime', label: '最后一次更新时间', align: 'left' }, { prop: 'lastUpdateTime', label: '最后一次更新时间', align: 'left' },
{ prop: 'status', label: '状态', align: 'left', format: v => this.$dict.getLabel('monitor_status', v), }, { prop: 'status', label: '状态', align: 'left', format: v => this.$dict.getLabel('monitor_status', v), },
{ prop: 'expireTime', label: '失效时间', align: 'left' }, { prop: 'expireTime', label: '失效时间', align: 'left' },
@@ -85,18 +142,33 @@ import {sendTemuAPIMessage} from '@/api/chromeApi'
total: 0, total: 0,
form: { form: {
content: '' content: '',
orderType: '0:1',
remark: ''
},
updateObj: {},
isUpdateDlgShow: false,
updateForm: {
id: '',
content: '',
remark: ''
}, },
isDlgShow: false, isDlgShow: false,
detailsVo: {}, detailsVo: {},
pageNo: 1, pageNo: 1,
pageSize: 120 pageSize: 120,
isLoading: false,
selectRows: [],
isBatchCollect: false
} }
}, },
created () { created () {
this.$dict.load('monitor_status'); this.$dict.load('monitor_status', 'monitor_order_type');
this.getList() this.getList()
}, },
@@ -111,12 +183,15 @@ import {sendTemuAPIMessage} from '@/api/chromeApi'
this.total = res.data.total this.total = res.data.total
}) })
}, },
onChooseChange (val) {
this.selectRows = val;
},
saveStore () { saveStore () {
this.$refs.form.validate((valid) => { this.$refs.form.validate((valid) => {
if (valid) { if (valid) {
this.$http.post('/api/monitor/check',null, {params: {type: 0}}).then(res => { this.$http.post('/api/monitor/check',null, {params: {type: 0}}).then(res => {
if (res.code == 0) { if (res.code == 0) {
this.$http.post(`/api/monitor/add`, {content: this.form.content, type: this.search.type}).then(res => { this.$http.post(`/api/monitor/add`, {content: this.form.content, remark: this.form.remark, orderType: this.form.orderType, type: this.search.type}).then(res => {
if (res.code == 0) { if (res.code == 0) {
this.$message.success('添加成功!') this.$message.success('添加成功!')
this.$store.dispatch('getUserInfo') this.$store.dispatch('getUserInfo')
@@ -129,6 +204,25 @@ import {sendTemuAPIMessage} from '@/api/chromeApi'
} }
}) })
}, },
toUpdateMonitor (obj) {
this.updateObj = obj
this.updateForm.content = obj.content
this.updateForm.remark = obj.remark
this.isUpdateDlgShow = true
},
updateStore () {
this.$refs.updateForm.validate((valid) => {
if (valid) {
this.$http.post(`/api/monitor/update`, {id: this.updateObj.id, remark: this.updateForm.remark}).then(res => {
if (res.code == 0) {
this.$message.success('修改成功!')
this.getList()
this.isUpdateDlgShow = false
}
})
}
})
},
renew(id) { renew(id) {
this.$confirm('确定要续费?', '温馨提示', { this.$confirm('确定要续费?', '温馨提示', {
type: 'warning' type: 'warning'
@@ -147,6 +241,46 @@ import {sendTemuAPIMessage} from '@/api/chromeApi'
}) })
}) })
}, },
batchRemove() {
if (this.selectRows.length <= 0) {
this.$message.error('请选择要删除的监测对象');
return;
}
let ids = this.selectRows.map(item => {
return item.id
})
this.$confirm('确定要删除?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post('/api/monitor/delByIds', ids
).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
batchCollect () {
if (this.selectRows.length <= 0) {
this.$message.error('请选择要采集的监测对象');
return;
}
this.isBatchCollect = true
this.isLoading = true
this.selectRows.map((item, index) => {
setTimeout(() => {
this.pageNo = 1
let data = {}
data.monitorId = item.id
data.details = []
this.beginCollect(item, data, index)
}, 1000*(index+1))
})
},
deleteMonitor(id) { deleteMonitor(id) {
this.$confirm('确定要删除?', '温馨提示', { this.$confirm('确定要删除?', '温馨提示', {
type: 'warning' type: 'warning'
@@ -164,11 +298,12 @@ import {sendTemuAPIMessage} from '@/api/chromeApi'
}) })
}) })
}, },
toDetail (id) { toDetail (id, content) {
this.$emit('change', { this.$emit('change', {
type: 'Detail', type: 'Detail',
params: { params: {
id: id || '' id: id || '',
content: content
} }
}) })
}, },
@@ -177,61 +312,69 @@ import {sendTemuAPIMessage} from '@/api/chromeApi'
this.isDlgShow = true this.isDlgShow = true
}, },
toBegin(row) { toBegin(row) {
this.detailsVo = {} this.isBatchCollect = false
this.detailsVo.monitorId = row.id
this.detailsVo.details = []
this.pageNo = 1 this.pageNo = 1
this.isLoading = true
this.beginCollect(row) let data = {}
data.monitorId = row.id
data.details = []
this.beginCollect(row, data, 0)
}, },
beginCollect(row) { beginCollect(row, reqData, index) {
sendTemuAPIMessage({ sendChromeWebReqMessage({
url: 'api/poppy/v1/search?scene=search', type: 'temu',
anti: true, url: 'https://www.temu.com/search_result.html?search_key='+row.content+'&search_method=user&filter_items='+row.orderType
data: }).then((res) => {
{ if (this.isBatchCollect && (index+1) == this.selectRows.length) {
"scene": "search", this.isLoading = false
"offset": (this.pageNo-1) * this.pageSize, this.isBatchCollect = false
"pageSize": 120, } else {
"query": row.content, this.isLoading = false
"filterItems": "0:1", }
"searchMethod": "suggest" if (res.indexOf("window.rawData") == -1) {
}}).then((res) => { Message.error("采集失败请检查https://www.temu.com是否能正常访问")
if (res.success) { return
res.result.data.goods_list.map(item => { }
console.log(item.sales_tip_text[0]) let str = res.substring(res.indexOf("window.rawData"))
let total = 0 str = str.substring(0, str.indexOf("<\/script>"))
if (item.sales_tip_text[0]) { str = str.substring(str.indexOf("{"))
total = item.sales_tip_text[0] str = str.substring(0, str.lastIndexOf("}"))
total = total.replace('+', '') str = str + "}"
if (total.indexOf('K') != -1) { let obj = JSON.parse(str)
total = total.replace('K', '') let goodsList = obj.store.goodsList
total = total * 1000 goodsList.map(t => {
} else if (total.indexOf('M') != -1) { let item = t.data
total = total.replace('M', '') let total = 0
total = total * 1000000 if (item.salesNum) {
} total = item.salesNum
} total = total.replace('+', '')
this.detailsVo.details.push({ if (total.indexOf('K') != -1) {
url: item.link_url, total = total.replace('K', '')
price: item.price_info.price_schema, total = total * 1000
saleTotal: total, } else if (total.indexOf('M') != -1) {
goodsId: item.goods_id, total = total.replace('M', '')
imgUrl: item.thumb_url, total = total * 1000000
mallId: item.mall_id }
}) }
}) reqData.details.push({
console.log(this.detailsVo) url: 'https://www.temu.com/goods.html?goods_id=' + item.goodsId,
price: item.priceInfo.priceSchema,
saleTotal: total,
goodsId: item.goodsId,
imgUrl: item.thumbUrl,
mallId: item.mallId
})
})
this.$http.post('/api/monitorDetail/addDetails',this.detailsVo this.$http.post('/api/monitorDetail/addDetails', reqData).then(res => {
).then(res => { if (res.code == 0) {
if (res.code == 0) { this.$message.success('关键字【' + row.content + '】数据采集成功!')
this.$message.success('关键字【' + row.content + '】数据采集成功!') this.getList()
this.getList()
}
})
} }
}) })
})
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More