Compare commits

...

125 Commits

Author SHA1 Message Date
aixianling
b1d1f60f96 调整Home.vue页面布局和功能 2024-11-06 09:31:58 +08:00
aixianling
191edd1eb4 Merge remote-tracking branch 'gitee/master'
# Conflicts:
#	src/router/index.js
#	src/view/Home.vue
#	yarn.lock
2024-11-06 09:31:25 +08:00
juanmao2009
0cbe017a95 !10 调整文字
Merge pull request !10 from juanmao2009/label
2024-11-05 13:45:05 +00:00
liushiwei
b1abecfb29 调整 2024-11-05 21:44:30 +08:00
juanmao2009
2511778c9f !9 调整
Merge pull request !9 from juanmao2009/label
2024-11-05 13:29:24 +00:00
liushiwei
fea7848653 调整 2024-11-05 21:28:49 +08:00
juanmao2009
3cdaba63b7 !8 调整
Merge pull request !8 from juanmao2009/label
2024-11-05 13:28:27 +00:00
liushiwei
5ece8b562f 调整 2024-11-05 21:27:16 +08:00
juanmao2009
15614d9489 !7 标签管理
Merge pull request !7 from juanmao2009/label
2024-11-05 13:21:56 +00:00
liushiwei
7b43c97f5f Merge branch 'label' of https://gitee.com/three-make-money/temu-plugin into label 2024-11-05 21:20:17 +08:00
liushiwei
74043c55ae 调整 2024-11-05 21:20:05 +08:00
aixianling
f69c86f92b 管理权限统一 2024-11-05 09:35:31 +08:00
aixianling
c7cb7c103d 先提交一下,打个钉子 2024-11-04 17:47:02 +08:00
yanran200730
1285b0644c 新增全局提示 2024-11-03 20:27:58 +08:00
yanran200730
b6e721826f 修改模板库样式 2024-11-02 23:16:59 +08:00
yanran200730
0f6f894f84 修复bug 2024-11-02 15:47:45 +08:00
yanran200730
0baccbf650 完善zhifu 2024-11-02 12:07:29 +08:00
yanran200730
ffcebb2913 修复bug 2024-10-31 21:31:51 +08:00
yanran200730
a2bbe798b8 修复弹窗bug 2024-10-31 21:20:33 +08:00
yanran200730
57d58cdad3 模板库 图片预览 2024-10-31 21:03:16 +08:00
yanran200830
b3c7dde44f 修复bug 2024-10-31 18:07:09 +08:00
yanran200830
3574f8ce83 修复模板bug 2024-10-31 13:39:09 +08:00
yanran200830
11a9972329 修复图片组件 2024-10-31 11:37:50 +08:00
yanran200730
56ee90ed30 修复bug和更改会员界面 2024-10-30 22:01:57 +08:00
liushiwei
e920251718 调整 2024-10-30 19:53:17 +08:00
yanran200830
d38206a1a0 Merge branch 'label' of https://gitee.com/three-make-money/temu-plugin into label 2024-10-30 17:29:42 +08:00
yanran200830
4bfb4c2d1a 【新增】支付弹窗和修复bug 2024-10-30 17:29:34 +08:00
liushiwei
aa36ce497a 调整 2024-10-30 09:56:08 +08:00
liushiwei
81d4f10c14 调整 2024-10-30 09:50:06 +08:00
liushiwei
fdac7fc7d3 调整 2024-10-30 09:45:12 +08:00
liushiwei
bbde4e3f3f 调整 2024-10-30 09:42:08 +08:00
yanran200730
4b6e221369 修复bug 2024-10-29 21:36:51 +08:00
yanran200830
b4c3e3796f 打印组件修改 2024-10-29 18:08:50 +08:00
yanran200730
6618242f7f 【新增】模板库 2024-10-28 22:10:07 +08:00
yanran200830
8225f21d14 1 2024-10-28 18:30:27 +08:00
liushiwei
86f4256eb4 Merge branch 'label' of https://gitee.com/three-make-money/temu-plugin into label 2024-10-27 18:30:22 +08:00
liushiwei
5a74b0d382 调整 2024-10-27 18:30:18 +08:00
yanran200730
6261be7807 Merge branch 'label' of https://gitee.com/three-make-money/temu-plugin into label 2024-10-27 18:29:43 +08:00
yanran200730
275f5f6abd 【新增】打印 2024-10-27 18:29:16 +08:00
yanran200730
244d84d45d 【删除】多余代码 2024-10-26 23:15:17 +08:00
yanran200730
8900853066 【新增】模板接口对接 2024-10-26 22:49:23 +08:00
yanran200730
4f4f303867 打印组件 2024-10-26 21:13:12 +08:00
yanran200730
05f63c7af5 【新增】晚上打印页面和标签组件 2024-10-26 13:56:07 +08:00
liushiwei
49b9b2c280 Merge branch 'label' of https://gitee.com/three-make-money/temu-plugin into label 2024-10-25 18:33:21 +08:00
liushiwei
d6e3d7bbe1 新增功能 2024-10-25 18:33:12 +08:00
yanran200830
a51a0d49bc 【新增】打印组件 2024-10-25 15:31:13 +08:00
yanran200830
4d0cc14001 修复bug 2024-10-24 18:02:31 +08:00
yanran200730
d763045c94 新增代码添加添加元素 2024-10-23 22:13:17 +08:00
yanran200730
b4cbdce74a 【新增】加入依赖 2024-10-23 20:09:26 +08:00
yanran200830
9d7ed6f701 【修复】vue-plugin-hiprint.js组件eval适配问题 2024-10-23 13:58:08 +08:00
yanran200730
f7530ed1f5 修复bug 2024-10-22 22:41:36 +08:00
yanran200830
28264e43b5 【修复】模板内temu标签组件bug 2024-10-22 15:11:48 +08:00
yanran200830
432d4351d3 【新增】完成temu标签组件 2024-10-22 11:10:27 +08:00
yanran200730
d9fe8dea7b 调样式 2024-10-21 21:56:40 +08:00
yanran200830
441510a4e4 修复样式bug 2024-10-21 18:11:08 +08:00
yanran200830
5a7058c036 【新增】temu标签组件 2024-10-21 18:02:06 +08:00
yanran200730
5157d601cc 【新增】1.temu条码组件 2.添加SKU 2024-10-20 21:52:29 +08:00
liushiwei
649ddc2504 调整 2024-10-19 18:21:46 +08:00
liushiwei
006f84ccbb 接口地址调整 2024-10-19 17:50:14 +08:00
juanmao2009
2d14cb7c6d !6 111
Merge pull request !6 from juanmao2009/master
2024-10-19 09:48:10 +00: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
146 changed files with 154055 additions and 4938 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
/dist
/artifacts
package-lock.json
/node_modules
# local env files
.env.local

62364
node_modules/vue-plugin-hiprint/dist/vue-plugin-hiprint.js generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -3,23 +3,34 @@
"version": "0.1.0",
"private": true,
"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"
},
"dependencies": {
"@antv/g2plot": "^2.4.31",
"@antv/g2plot": "^2.4.25",
"@babel/standalone": "^7.25.8",
"axios": "^1.4.0",
"bi-vue-mindmap": "^0.6.12",
"core-js": "^3.8.3",
"crypto-js": "^4.0.0",
"dayjs": "^1.11.9",
"element-ui": "^2.15.13",
"eval5": "^1.4.8",
"file-saver": "^2.0.5",
"html2canvas": "^1.4.1",
"jsbarcode": "^3.11.6",
"query-string": "^9.0.0",
"spark-md5": "^3.0.2",
"v-viewer": "^1.6.4",
"vue": "^2.6.14",
"vue-cropper": "^0.6.4",
"vue-json-excel": "^0.3.0",
"vue-plugin-hiprint": "^0.0.56",
"vue-qr": "^4.0.9",
"vue-router": "^3.2.0",
"vuex": "^3.4.0",
"vuex-persistedstate": "^4.1.0"
"vuex-persistedstate": "^4.1.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@babel/core": "^7.12.16",
@@ -31,7 +42,7 @@
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"javascript-obfuscator": "2.6.0",
"sass": "^1.68.0",
"sass": "1.32.13",
"sass-loader": "^7.3.1",
"vue-cli-plugin-chrome-extension-cli": "~1.1.4",
"vue-template-compiler": "^2.6.14",

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

@@ -0,0 +1,383 @@
@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;
}
.temuBarCode {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
padding: 10pt 10pt;
border: 2pt solid #000;
}
.temuBarCode-code {
width: 100%;
height: 100%;
}
.temuBarCode-code svg {
width: 100%;
}
.temuBarCode-top,
.temuBarCode-bottom {
display: flex;
align-items: center;
justify-content: space-between;
width: 94%;
font-weight: 700;
}
.temuBarCode-middle {
width: 88%;
height: 60%;
}

View File

@@ -1,17 +1,57 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>TEMU助手</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<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>
<style>
.temuBarCode {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
padding: 10pt 10pt;
border: 2pt solid #000;
}
.temuBarCode-code {
width: 100%;
height: 100%;
}
.temuBarCode-code svg {
width: 100%;
}
.temuBarCode-top,
.temuBarCode-bottom {
display: flex;
align-items: center;
justify-content: space-between;
width: 94%;
font-weight: 700;
}
.temuBarCode-middle {
width: 88%;
height: 60%;
}
</style>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

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() {
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")
popup.innerText = "下载图片"
const styles = {
@@ -148,7 +153,7 @@ function init() {
var imgObjList = document.querySelectorAll('div.aplus-v2 img')
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++) {
@@ -187,6 +192,111 @@ function init() {
})
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 < models3.length; i++) {
let tipsObj = models3[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 = models3[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",
"operation": "set",
"value": "https://kuajing.pinduoduo.com"
"value": "https://seller.kuajingmaihuo.com"
},
{
"header": "Referer",
"operation": "set",
"value": "https://kuajing.pinduoduo.com/main/order-manage"
"value": "https://seller.kuajingmaihuo.com/main/order-manage"
}
]
},
"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_12.json Normal file
View File

@@ -0,0 +1,24 @@
[
{
"id": 23,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "http://xc.rqlis.com:888"
},
{
"header": "Host",
"operation": "set",
"value": "xc.rqlis.com:888"
}
]
},
"condition": {
"urlFilter": "||xc.rqlis.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

@@ -1,27 +1,63 @@
import store from '@/store'
import {genAnti} from "@/api/genAnti";
import { genAnti } from "@/api/genAnti";
import { Message } from 'element-ui'
/**
* 向Chrome发送消息
* @param message 消息
*/
export async function sendChromeAPIMessage(message) {
message.type = 'api'
message.url = "https://kuajing.pinduoduo.com/" + message.url;
message.anti = message.anti || false
if (message.needMallId) {
// 如果参数中没有携带MallId则从state中获取
if (!message.mallId) {
message.mallId = store.state.mallId;
}
message.type = 'api'
if (!message.url.startsWith('http')) {
message.url = "https://seller.kuajingmaihuo.com/" + message.url;
}
message.anti = message.anti || false
if (message.needMallId) {
// 如果参数中没有携带MallId则从state中获取
if (!message.mallId) {
message.mallId = store.state.mallId;
}
if (message.anti) {
message.anti = await genAnti.a()
}
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
}
if (message.anti) {
message.anti = await genAnti.a()
}
return new Promise((resolve) => {
// @ts-ignore
// chrome.runtime.sendMessage(message, resolve)
chrome.runtime.sendMessage(message, res => {
if (res.error_code === 40001) {
Message.error('请先登录拼多多跨境卖家中心')
}
resolve(res)
})
})
}
/**
* 向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;
}
}
if (message.anti) {
message.anti = await genAnti.a()
}
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
}
/**
@@ -29,16 +65,17 @@ export async function sendChromeAPIMessage(message) {
* @param message 消息
*/
export async function sendTemuAPIMessage(message) {
message.type = 'temuApi'
message.url = "https://www.temu.com/" + message.url;
message.anti = message.anti || false
if (message.anti) {
message.anti = await genAnti.a()
}
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
message.type = 'temuApi'
message.url = "https://www.temu.com/" + message.url;
message.anti = message.anti || false
if (message.anti) {
message.anti = await genAnti.a()
message.data.anti_content = message.anti
}
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
}
/**
@@ -46,10 +83,40 @@ export async function sendTemuAPIMessage(message) {
* @param message 消息
*/
export async function sendChromeWebReqMessage(message) {
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
return new Promise((resolve) => {
// @ts-ignore
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 +124,52 @@ export async function sendChromeWebReqMessage(message) {
* @param message 消息
*/
export function sendChromeNotification(message) {
message.type = 'notify'
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
message.type = 'notify'
return new Promise((resolve) => {
// @ts-ignore
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)
})
}
/**
* 向Chrome发送消息
* @param message 消息
*/
export async function sendXcAPIMessage(message) {
message.type = 'xcApi'
message.url = "http://xc.rqlis.com:888/" + message.url;
return new Promise((resolve) => {
// @ts-ignore
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({
baseURL: process.env.NODE_ENV === 'production' ? 'http://temu.jjcp52.com' : 'http://temu.jjcp52.com',
baseURL: process.env.NODE_ENV === 'production' ? 'http://temu.jjcp52.com' : 'http://124.71.2.127:10248',
timeout: 50000,
validateStatus: function (status) {
return status < 500

View File

@@ -53133,7 +53133,7 @@ export default[function(e, t, r) {
))
}
, 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.withCredentials = !0,
r.onreadystatechange = function() {

BIN
src/assets/code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,13 +1,22 @@
@import "./styles.scss";
@font-face {
font-family: 'iconfont'; /* project id 1995974 */
font-family: 'iconfont';
/* project id 1995974 */
src: url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.eot');
src: url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.eot?#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.woff2') format('woff2'),
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.woff') format('woff'),
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.ttf') format('truetype'),
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.svg#iconfont') format('svg');
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.woff2') format('woff2'),
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.woff') format('woff'),
url('https://at.alicdn.com/t/font_1995974_ihzpmuv4lpk.ttf') format('truetype'),
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 {
@@ -20,12 +29,15 @@
}
html {
line-height: 1; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
line-height: 1;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
}
body {
margin: 0;
overflow: hidden;
}
h1 {
@@ -35,14 +47,19 @@ h1 {
hr {
-webkit-box-sizing: content-box;
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
box-sizing: content-box;
/* 1 */
height: 0;
/* 1 */
overflow: visible;
/* 2 */
}
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
a {
@@ -50,10 +67,13 @@ a {
}
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
border-bottom: none;
/* 1 */
text-decoration: underline;
/* 2 */
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted; /* 2 */
text-decoration: underline dotted;
/* 2 */
}
b,
@@ -64,8 +84,10 @@ strong {
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
small {
@@ -97,14 +119,19 @@ input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
line-height: 1.15;
/* 1 */
margin: 0;
/* 2 */
}
button,
input { /* 1 */
input {
/* 1 */
overflow: visible;
font-family: SJsuqian;
}
@@ -116,7 +143,8 @@ input::placeholder {
}
button,
select { /* 1 */
select {
/* 1 */
text-transform: none;
}
@@ -148,12 +176,18 @@ fieldset {
legend {
-webkit-box-sizing: border-box;
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
box-sizing: border-box;
/* 1 */
color: inherit;
/* 2 */
display: table;
/* 1 */
max-width: 100%;
/* 1 */
padding: 0;
/* 3 */
white-space: normal;
/* 1 */
}
progress {
@@ -167,8 +201,10 @@ textarea {
[type="checkbox"],
[type="radio"] {
-webkit-box-sizing: border-box;
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
[type="number"]::-webkit-inner-spin-button,
@@ -177,8 +213,10 @@ textarea {
}
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
[type="search"]::-webkit-search-decoration {
@@ -186,8 +224,10 @@ textarea {
}
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
details {
@@ -206,7 +246,10 @@ template {
display: none;
}
h1, h2, h3, h4 {
h1,
h2,
h3,
h4 {
font-weight: normal;
font-size: 14px;
padding: 0;
@@ -224,7 +267,8 @@ a {
transition: all .3s ease;
}
ul, li {
ul,
li {
list-style: none;
margin: 0;
padding: 0;
@@ -238,7 +282,8 @@ img {
vertical-align: middle;
}
html, body {
html,
body {
width: 100%;
min-height: 100vh;
}
@@ -268,7 +313,8 @@ img {
display: flex;
}
.flex-align, .flex-center {
.flex-align,
.flex-center {
display: flex;
align-items: center;
}
@@ -304,11 +350,13 @@ img {
flex: 1;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .3s ease-in-out;
.fade-enter-active,
.fade-leave-active {
transition: opacity .2s ease-in-out;
}
.fade-enter, .fade-leave-to {
.fade-enter,
.fade-leave-to {
opacity: 0;
}
@@ -379,7 +427,8 @@ img {
line-height: 17px;
margin-bottom: 14px;
font-size: 14px;
font-family: SJsuqian;;
font-family: SJsuqian;
;
label {
margin-right: 6px;
@@ -393,7 +442,7 @@ img {
}
.el-pagination {
margin-top: 40px;
margin-top: 20px;
text-align: center;
}
@@ -429,7 +478,8 @@ img {
background-color: #1FBAD6;
}
.el-button--danger:focus, .el-button.el-button--danger.is-link:not(.is-disabled):hover {
.el-button--danger:focus,
.el-button.el-button--danger.is-link:not(.is-disabled):hover {
color: #fff !important;
border-color: #FA5555;
background-color: #FA5555;
@@ -470,16 +520,60 @@ img {
.search-item {
display: flex;
align-items: center;
margin: 0 16px 12px;
margin: 0 25px 10px 0;
label {
&>label {
width: 80px;
font-size: 14px;
color: #666;
font-weight: 500;
width: 100px;
}
input {
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;
}
.print-images {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: 420px;
max-height: 400px;
padding: 0 10px;
overflow-y: auto;
img {
width: 180px;
height: 80px;
margin-bottom: 10px;
cursor: pointer;
object-fit: contain;
}
p {
width: 100%;
line-height: 1.3;
margin-bottom: 10px;
font-size: 14px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 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

BIN
src/assets/tab_middle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -37,7 +37,7 @@
margin-bottom: 20px;
padding-bottom: 20px;
background: #FFFFFF;
overflow: hidden;
overflow: auto;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
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>
<div>
<el-form class="ai-form" :model="form" label-width="140px" ref="form">
<el-form-item label="来源:" style="width: 100%;" prop="type" :rules="[{ required: true, message: '请选择来源', trigger: 'blur' }]">
<el-radio-group v-model="form.type" size="medium">
<el-radio :label="1">TEMU</el-radio>
<!--<el-radio :label="2">速卖通</el-radio>-->
</el-radio-group>
<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="url" :rules="[{ required: true, message: '请输入商品地址', trigger: 'blur' }]">
<el-input type="textarea" :rows="5" v-model="form.url"></el-input>
<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-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择">
<el-select style="width: 380px" multiple v-model="form.targetMallId" placeholder="请选择">
<el-option
v-for="item in $store.state.mallList"
v-for="item in 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 v-if="form.isSemi" label="经营站点" style="width: 100%;" prop="siteId" :rules="[{ required: true, message: '请选择经营站点', trigger: 'blur' }]">
<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>
<div class="bottom flex-center">
<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>
</template>
<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 { MessageBox } from 'element-ui';
export default {
name: 'AiCopyFromTemu',
props: ['params'],
props: ['params', 'isMultiCopy'],
components: {AiLazyCascader},
data() {
return {
props: {
value: 'catId',
label: 'catName',
lazy: true,
lazyLoad (node, resolve) {
lazyLoad (value, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list',
needMallId: true,
data: {
parentCatId: node.value || ''
parentCatId: value || ''
}
}).then(res => {
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: {
url: '',
type: 1, // 默认从temu复制
targetMallId: '',
targetCatId: []
targetCatId: [],
isSameCategory: true,
isSemi: false,
siteId: []
},
goods: {},
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 () {
console.log(this.params?.url)
if (this.params?.url) {
this.form.url = this.params.url
}
this.getSiteList()
if (this.params?.url) {
this.form.url = this.params.url
}
},
methods: {
async addToDraft() {
this.$refs.form.validate((valid) => {
if (valid) {
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.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])
}
})*/
}
})
}
})
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
})
}
})
},
createDraft(data) {
sendChromeAPIMessage({
toAddToDraft() {
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',
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 = data
let i = 0
mallId: mallId,
data: reqData})
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
let content = data
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++) {
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) {
sendChromeAPIMessage({
async createProduct(content, mallId) {
if (this.form.isSemi) {
content.productSemiManagedReq = {
bindSiteIds: this.form.siteId
}
}
let res = await sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
mallId: this.form.targetMallId,
mallId: mallId,
data: {
...content
}}).then((res) => {
if (res.errorCode == 1000000) {
Message.success("成功添加到草稿箱")
this.saveInfo()
}})
if (res.errorCode == 1000000) {
this.successList.push(this.currentUrl)
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 {
setTimeout(() => {
this.createProduct(content)
}, 500)
this.isCopying = false
Message.success("成功添加到草稿箱")
}
})
} else {
await this.$sleepSync(1000)
this.createProduct(content)
}
},
getSpecId(data) {
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, mallId) {
return sendChromeAPIMessage({
url: 'bg-anniston-mms/sku/spec/byName/queryOrAdd',
needMallId: true,
mallId: mallId,
data: {
parentSpecId: data.specKeyId,
specName: data.specValue
@@ -233,25 +545,54 @@ export default {
if (res.errorCode == 1000000) {
return res
} else {
this.getSpecId(data)
this.getSpecId(data, mallId)
}
})
},
saveInfo() {
saveInfo(mallId) {
let mallInfo = this.$store.state.mallList.filter(item => {
return item.mallId == this.form.targetMallId
return item.mallId == mallId
})
this.$http.post('/api/copyProduct/add', {
mallId: mallInfo[0].mallId,
mallName: mallInfo[0].mallName,
url: this.form.url,
url: this.currentUrl,
type: this.form.type
}).then(res1 => {
if (res1.code == 0) {
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

@@ -1,9 +1,9 @@
<template>
<section class="ai-dialog__wrapper">
<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" :close-on-click-modal="false">
<div class="ai-dialog__header fill" slot="title" v-text="title"/>
<div class="ai-dialog__content">
<div class="ai-dialog__content--wrapper pad-r8">
<div class="ai-dialog__content--wrapper">
<slot/>
</div>
</div>
@@ -69,13 +69,13 @@ export default {
.ai-dialog__content {
overflow-y: auto;
padding-bottom: 4px;
max-height: 500px;
padding-bottom: 0px;
max-height: 550px;
.ai-dialog__content--wrapper {
height: 100%;
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-size: 120px 120px;
height: 120px;
margin: 48px auto 0;
margin: 0 auto;
padding-top: 40px;
}
}
</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">
<el-tabs type="card" stretch v-model="search.module" @tab-click="getPayments">
<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-tabs>
<div class="content">
@@ -46,8 +46,8 @@
<el-form-item
prop="mallId"
v-show="false"
:rules="[{ message: '请输入商城ID', trigger: 'blur' }]">
<el-input placeholder="请输入商城ID" v-model="form.mallId"></el-input>
:rules="[{ message: '请输入店铺ID', trigger: 'blur' }]">
<el-input placeholder="请输入店铺ID" v-model="form.mallId"></el-input>
</el-form-item>
<el-form-item
prop="code"
@@ -108,12 +108,12 @@ export default {
mallId: this.$store.state.mallId,
mallName: this.$store.state.mallName
},
vipType: ["体验会员","年会员","年会员多店通用"],
vipType: ["体验会员","单店年会员","年会员多店通用"],
search: {module: "0"},
show: true,
descriptionsModule0: ["抢仓发货", "数据下载", "复制商品", "会员服务"],
descriptionsModule1: ["智能复制"],
descriptionsModule1: ["商品采集", "店铺跟踪", "关键字跟踪", "单品跟踪", "新品跟踪"],
payments: [],
qrcode: "",
amount: 0,
@@ -134,6 +134,9 @@ export default {
}
})
},
getMessage(type) {
return `你使用的是“${this.vipType[type]}”兑换券,确定兑换?`;
},
getQrcode(item) {
if (item.module == '0' && !this.vipForm.mallId) {
Message.error("请先登录拼多多垮境卖家中心")
@@ -141,7 +144,11 @@ export default {
}
this.selected = item
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 => {
if (res?.data?.id) {
return res.data.id

View File

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

View File

@@ -6,14 +6,17 @@
</span>
<el-dropdown-menu slot="dropdown">
<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 :command="beforeGoMal(params.mallId)">访问店铺</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<ai-dialog
title="复制"
title="商品采集"
:visible.sync="copyFromDlgShow"
:close-on-click-modal="false"
width="790px"
@@ -22,19 +25,53 @@
@close="handleClose">
<ai-copy-from-temu v-if="copyFromDlgShow" :params="temuParams" @onClose="handleClose" @onSuccess="handleSuccess"></ai-copy-from-temu>
</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>
</template>
<script>
import AiCopyFromTemu from "./AiCopyFromTemu.vue";
import { Message } from 'element-ui'
export default {
name: "AiProductDropDown",
components: {AiCopyFromTemu},
props: ['params', 'isShowDetail'],
props: ['params', 'source', 'isShowDetail', 'isShowAddFavorite', 'isShowDelFavorite', 'isShowGroup', 'isHideCopy'],
data() {
return {
info: {},
copyFromDlgShow: false,
temuParams: {}
addGroupDlgShow: false,
temuParams: {},
addGroupForm: {
groupId: '',
goodsId: ''
},
groupList: []
}
},
computed: {
@@ -46,18 +83,59 @@ export default {
if (e.type == 'detail') {
this.$emit('onGoDetail')
} 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
} 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') {
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') {
console.log(e.url)
window.open('https://www.temu.com/' + e.url, '_blank');
if (e.url.startsWith('http')) {
window.open(e.url, '_blank');
} else {
window.open('https://www.temu.com/' + e.url, '_blank');
}
}
},
beforeGoDetail(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) {
return {type: 'copy', url: url}
},
@@ -69,9 +147,22 @@ export default {
},
handleClose() {
this.copyFromDlgShow = false
this.addGroupDlgShow = false
},
handleSuccess() {
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

@@ -7,6 +7,9 @@
<h2>{{ title }}</h2>
<p>{{ tips }}</p>
</div>
<div class="ailist-title__middle">
<slot name="center"/>
</div>
<div class="ailist-title__right">
<div class="aititle-right__btns">
<slot name="rightBtn"/>
@@ -75,6 +78,11 @@
justify-content: space-between;
height: 48px;
.ailist-title__middle {
display: flex;
align-items: center;
}
.ailist-title__left {
display: flex;
align-items: center;

View File

@@ -0,0 +1,511 @@
<template>
<transition name="fade">
<div class="LablesMember" v-if="isShow">
<div class="mask"></div>
<div class="LablesMember-wrapper">
<i class="el-icon-close" @click="hide"></i>
<div class="top">
<img src="../assets/images/avatar.png" />
<div class="top-right">
<div class="top-user">
<h2>用户{{ $store.state.userInfo.name }}</h2>
<span></span>
</div>
<p>会员中心</p>
</div>
</div>
<div class="middle">
<div class="middle-top">
<div class="tab">
<div @click="tabIndex = 0, currIndex = 0, getPriceList(1)" :class="[tabIndex === 0 ? 'active' : '']">
<span>金币充值</span>
</div>
<div @click="tabIndex = 1, currIndex = 0, getPriceList(0)" :class="[tabIndex === 1 ? 'active' : '']">
<span>年度会员</span>
</div>
<div @click="tabIndex = 2, currIndex = 0, getPriceList(2)" :class="[tabIndex === 2 ? 'active' : '']">
<span>高级功能</span>
</div>
</div>
<div class="tab-content" v-if="tabIndex === 0">
<div class="title">金币充值</div>
<div class="tab-content__item--wrapper">
<div
class="tab-content__item"
:class="[currIndex === i ? 'active' : '']"
v-for="(price, i) in priceList"
:key="i"
@click="currIndex = i, getQrcode(price)">
<h3>{{ price.remark }}</h3>
<div class="price">
<i>¥</i>
<span>{{ price.price }}</span>
</div>
<div class="original-price">
<i>¥</i>
<span>{{ price.originPrice }}</span>
</div>
<p>{{ price.coin }}/每年</p>
</div>
</div>
</div>
<div class="tab-content" v-if="tabIndex === 1">
<div class="title">年度会员</div>
<div class="tab-content__item--wrapper">
<div
class="tab-content__item"
:class="[currIndex === i ? 'active' : '']"
v-for="(price, i) in priceList"
:key="i"
@click="currIndex = i, getQrcode(price)">
<h3>{{ price.title }}</h3>
<div class="price">
<i>¥</i>
<span>{{ price.price }}</span>
</div>
<div class="original-price">
<i>¥</i>
<span>{{ price.originPrice }}</span>
</div>
<!-- <p>{{ price.coin }}/每年</p> -->
</div>
</div>
</div>
<div class="tab-content" v-if="tabIndex === 2">
<div class="title">标签合成</div>
<div class="tab-content__item--wrapper">
<div
class="tab-content__item"
:class="[currIndex === i ? 'active' : (Number(labelInfo.level) >= Number(price.type) ? 'disabled' : '')]"
:data-level="price.type"
:data-index="labelInfo.level"
v-for="(price, i) in priceList"
:key="i"
@click="getQrcode(price, i)">
<h3>{{ price.remark }}</h3>
<div class="price">
<i>¥</i>
<span>{{ price.price }}</span>
</div>
<div class="original-price">
<i>¥</i>
<span>{{ price.originPrice }}</span>
</div>
<p>{{ price.coin }}/每年</p>
</div>
</div>
</div>
</div>
<div class="title">支付方式</div>
<div class="bottom">
<div class="bottom-left">
<div class="code">
<vue-qr v-if="qrcode" :text="qrcode" :size="110" :margin="0" :logoSrc="wechatLogo"/>
</div>
<div class="paytype">
<svg width="13" height="13" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg">
<path d="M12.11 0H.89A.89.89 0 0 0 0 .89v11.22c0 .491.399.89.89.89h11.22a.89.89 0 0 0 .89-.89V.89a.89.89 0 0 0-.89-.89zM6.5 10.65a5.592 5.592 0 0 1-1.848-.311c-.364.241-.975.639-1.215.75-.352.162-.25-.188-.25-.188L3.395 9.7C2.24 8.895 1.499 7.654 1.499 6.26c0-2.426 2.239-4.392 5-4.392 1.693 0 3.188.74 4.093 1.869l-4.905 2.27s-.4.152-.75-.062c-.35-.213-.813-.564-.813-.564s-.538-.458-.25.376l.751 1.756s.089.524.687.189c.468-.262 4.103-2.454 5.666-3.397a3.94 3.94 0 0 1 .523 1.954c0 2.425-2.239 4.391-5.001 4.391z" fill="#09BB07" fill-rule="nonzero"></path>
</svg>
<span>微信支付</span>
</div>
</div>
<div class="bottom-right">
<div class="right-price">
<i>¥</i>
<span class="price">{{ currGoods.price || '' }}</span>
</div>
<div class="pay-btn">
<el-button round size="mini" style="margin-right: 10px;" @click="isShow = false">取消支付</el-button>
<el-button round size="mini" type="warning" @click="$store.dispatch('getUserInfo'), isShow = false">我已支付</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
import VueQr from 'vue-qr'
import { mapState } from 'vuex'
export default {
components: {
VueQr
},
data() {
return {
form: {
mallId: this.$store.state.mallId,
mallName: this.$store.state.mallName,
code: ''
},
isShow: false,
tabIndex: 0,
currIndex: 0,
qrcode: '',
priceList: []
}
},
computed: {
wechatLogo: () => require(`../assets/wechat_logo.png`),
...mapState(['labelInfo']),
currGoods () {
if (!this.priceList.length) {
return {}
}
return this.priceList[this.currIndex]
}
},
mounted() {
this.getPriceList(1)
},
methods: {
getPriceList(type) {
this.$http.post(`/api/priceConfig/page?module=${type}`).then(res => {
if (res.code === 0) {
this.priceList = res.data.records
if (res.data.records.length) {
if (this.tabIndex !== 2) {
this.getQrcode(res.data.records[0])
} else {
for (let i = 0; i < res.data.records.length; i++) {
if (res.data.records[i].type > this.labelInfo.level) {
this.getQrcode(res.data.records[i], i)
break
}
}
}
}
}
})
},
getQrcode(item, index) {
if (this.tabIndex === 2 && item.type <= this.labelInfo.level) {
return false
}
if (this.tabIndex === 2) {
this.currIndex = index
}
this.$http.post(`/api/order/createOrder`, null, {
params: {
priceConfigId: item.id
}
}).then(res => {
if (res?.data?.id) {
return res.data.id
}
}).then(id => this.$http.post(`/api/order/createPrepayOrder?id=${id}`)).then(res => {
if (res?.data) {
this.qrcode = res.data.codeUrl
}
})
},
show(i) {
this.currIndex = 0
this.tabIndex = {
'0': 1,
'1': 0,
'2': 2
}[i]
this.getPriceList(i)
this.isShow = true
},
hide () {
this.isShow = false
}
}
}
</script>
<style lang="scss" scoped>
.LablesMember {
position: fixed;
left: 0;
top: 0;
z-index: 1111;
width: 100%;
height: 100%;
.title {
margin: 20px 0 10px 40px;
font-size: 16px;
text-align: left;
font-weight: 600;
color: #2a2b2e;
font-weight: normal;
}
.mask {
position: absolute;
left: 0;
top: 0;
z-index: 1;
width: 100%;
height: 100%;
background: rgba($color: #000000, $alpha: 0.8);
}
.LablesMember-wrapper {
position: absolute;
top: 50%;
left: 50%;
z-index: 11;
min-width: 916px;
// height: 620px;
padding: 40px 40px;
transform: translate(-50%, -50%);
border-radius: 12px;
overflow: hidden;
background: linear-gradient(41deg, #ffebd3, #fff8e3 61%, #fae2c4 99%);
.top {
display: flex;
align-items: center;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
margin-right: 10px;
}
p {
color: #632e2e;
opacity: 0.8;
}
.top-user {
display: flex;
align-items: center;
margin-bottom: 10px;
h2 {
margin-right: 6px;
font-size: 16px;
color: #333;
}
span {
font-size: 12px;
color: #666;
}
}
}
.el-icon-close {
position: absolute;
top: 12px;
right: 12px;
z-index: 11;
font-size: 32px;
color: #333;
cursor: pointer;
transform-origin: center center;
&:hover {
opacity: 0.6;
// transform: translate(50%, -50%) rotate(180deg);
}
}
.middle {
padding-bottom: 20px;
border-radius: 20px;
overflow: hidden;
background-color: #fff;
box-shadow: 0 0 20px 0 hsla(0, 39%, 56%, .1), 0 0 16px 0 rgba(0, 0, 0, .08);
.middle-top {
flex: 1;
padding: 0 0;
.tab {
display: flex;
align-items: center;
height: 50px;
background: #fff3ee;
div {
position: relative;
flex: 1;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 22px;
font-weight: 600;
cursor: pointer;
color: #b69593;
span {
position: relative;
z-index: 2;
}
&.active {
color: #6f3333;
&::after {
position: absolute;
left: 0;
top: 0;
z-index: 1;
width: 100%;
height: 100%;
background: url(../assets/tab_middle.png) no-repeat center;
background-size: contain;
content: '';
}
}
}
}
.tab-content {
display: flex;
flex-direction: column;
padding: 0 40px;
.title {
margin-left: 0;
}
&__item--wrapper {
display: flex;
align-items: center;
}
&__item {
display: flex;
align-items: center;
flex-direction: column;
width: 140px;
height: 170px;
margin-right: 14px;
padding-top: 20px;
background-color: #fff;
border-radius: 10px;
cursor: pointer;
border: 1px solid rgba(0, 0, 0, .08);
&.active {
border: 1px solid #ff7548;
background: linear-gradient(33deg, #ffd1c5 -10%, #ffd1c5 21%, #ffe8e5 85%);
}
&.disabled {
cursor: no-drop;
background: #f4f4f4;
}
&:last-child {
margin-right: 0;
}
h3 {
margin-bottom: 20px;
font-size: 15px;
font-weight: 600;
}
p {
margin-top: 10px;
color: #d9451e;
}
.original-price {
display: flex;
align-items: center;
font-size: 12px;
color: #888;
text-decoration: line-through;
}
.price {
display: flex;
align-items: baseline;
margin-bottom: 6px;
color: #f13d3d;
font-size: 16px;
span {
font-weight: 700;
font-size: 36px;
}
}
}
}
}
.bottom {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 40px;
.bottom-right {
display: flex;
flex-direction: column;
align-items: center;
}
.bottom-left {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 20px;
}
.paytype {
display: flex;
align-items: center;
margin-top: 6px;
span {
margin-left: 8px;
font-size: 14px;
color: #666;
}
}
.pay-btn {
display: flex;
align-items: center;
margin-top: 40px;
}
.right-price {
display: flex;
align-items: baseline;
margin-bottom: 16px;
color: #f13d3d;
font-size: 18px;
font-weight: 600;
span {
margin-left: 4px;
font-size: 30px;
font-weight: 900;
}
}
.code {
width: 110px;
height: 110px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,936 @@
<template>
<div class="print">
<div class="print-wrapper" v-if="!isPrint">
<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 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>
<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>
<div class="title">
<span>素材</span>
</div>
<div class="left-item__wrapper">
<div
class="item"
style="cursor: pointer;"
@click="search.type = 1, search.current = 1, isShowImage = true, getConfig()">
<div>图片素材</div>
</div>
<div
class="item"
style="cursor: pointer;"
@click="search.type = 0, search.current = 1, isShowImage = true, getConfig()">
<div>文字素材</div>
</div>
</div>
<div class="title">
<span>动态数据如日期</span>
<el-button type="primary" size="mini" @click="addField">添加</el-button>
</div>
<div class="left-item__wrapper">
<div
class="item"
v-for="(item, index) in dynamicFromList"
:key="index"
style="cursor: pointer;"
@click="addItemToCanvas(item.fieldName)">
<div>{{ item.fieldName }}</div>
<span class="el-icon-error" @click.stop="removeField(index)"></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>
<el-button @click="isShowTemplate = true" type="warning" size="small" style="margin-left: 10px;">模板库</el-button>
</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>
<ai-dialog :visible.sync="isShowImage" title="图片/文字素材" width="960" customFooter>
<el-select v-model="search.type" placeholder="请选择图片/文字素材" size="small" @change="search.current = 1, getConfig()">
<el-option label="图片素材" :value="1"></el-option>
<el-option label="文字素材" :value="0"></el-option>
</el-select>
<ai-table
v-if="search.type === 1"
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
style="margin-top: 8px;"
height="400"
@getList="getConfig">
<el-table-column slot="image" label="图片" align="left">
<template v-slot="{ row }">
<el-image
v-if="search.type === 1"
style="width: 80px; height: 80px"
:src="row.imgUrl"
:preview-src-list="[row.imgUrl]">
</el-image>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" align="center">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="row.type === '1' ? addImage(row.imgUrl) : addText(row.contents), isShowImage = false">添加</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-table
v-if="search.type === 0"
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
style="margin-top: 8px;"
height="400"
@getList="getConfig">
<el-table-column slot="options" label="操作" align="center">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="row.type === '1' ? addImage(row.imgUrl) : addText(row.contents), isShowImage = false">添加</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<div class="dialog-footer" slot="footer">
<el-button @click="isShowImage = false">取消</el-button>
</div>
</ai-dialog>
<ai-dialog
:visible.sync="isShowDynamicForm"
title="动态数据"
width="590px"
@confirm="onConfirm">
<el-form :model="dynamicFrom" ref="form" label-width="100px">
<el-form-item
label="数据名称:"
:prop="`field_${dynamicFromList.length > 9 ? dynamicFromList.length + 1 : '0' + (dynamicFromList.length + 1)}`"
:rules="[{ required: true, message: '请输入数据名称', trigger: 'blur' }]">
<el-input placeholder="请输入数据名称" type="text" v-model="dynamicFrom[`field_${dynamicFromList.length > 9 ? dynamicFromList.length + 1 : '0' + (dynamicFromList.length + 1)}`]"></el-input>
</el-form-item>
</el-form>
</ai-dialog>
<ai-dialog :visible.sync="isShowTemplate" title="模板库" width="1060px" customFooter>
<div class="templateList-wrapper" v-infinite-scroll="getTemplateList" :infinite-scroll-distance="20">
<div class="templateList">
<div class="templateList-item" v-for="(item, index) in templateList" :key="index">
<!-- <img :src="item.previewUrl" /> -->
<el-image
style="height: 180px;"
fit="contain"
:src="item.previewUrl"
:preview-src-list="[item.previewUrl]">
</el-image>
<h2>{{ item.name }}</h2>
<el-button type="warning" size="mini" @click="updateTempate(item)">使用模板</el-button>
</div>
</div>
</div>
<!-- <ai-table
:tableData="templateList"
:col-configs="templateColConfigs"
:total="templateTotal"
:current.sync="searchTemplate.current"
:size.sync="searchTemplate.size"
height="480"
@getList="getTemplateList"
v-loading="templateLoading">
<el-table-column slot="img" label="预览图" align="center">
<template v-slot="{ row }">
<el-image
style="width: 200px; height: 200px"
fit="contain"
:src="row.previewUrl"
:preview-src-list="[row.previewUrl]">
</el-image>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" align="center" width="200">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="showTemplate(row)">模板预览</el-button>
<el-button type="text" @click="updateTempate(row)">使用</el-button>
</div>
</template>
</el-table-column>
</ai-table> -->
<div class="dialog-footer" slot="footer">
<el-button @click="isShowTemplate = false">取消</el-button>
</div>
</ai-dialog>
</div>
</template>
<script>
import { hiprint, defaultElementTypeProvider, disAutoConnect } from 'vue-plugin-hiprint'
import { newHiprintPrintTemplate } from '@/utils/template-helper'
import { customProvider } from './customProvider'
disAutoConnect()
export default {
props: {
list: {
type: Array,
default: () => {
return []
}
},
params: {
type: Array,
default: () => {
return []
}
},
printData: {
type: Object,
default: () => {
return {
}
}
},
template: {
type: Object,
default: () => {
return {
"panels": [{
"index": 0,
"name": 1,
"height": 200,
"width": 200,
"printElements": [],
"paperNumberDisabled": true,
"paperNumberContinue": true,
"fontFamily": "Microsoft YaHei",
"watermarkOptions": {}
}]
}
}
},
isPrint: {
type: Boolean,
default: false
}
},
data () {
return {
html: '',
isShowPreview: false,
hiprintTemplate: null,
isShowTemplate: false,
templateList: [],
curPaper: {
type: 'other',
width: 200,
height: 200
},
paperTypes: {
'100 * 100': {
width: 200,
height: 200
},
'100 * 80': {
width: 200,
height: 160
},
'80 * 60': {
width: 160,
height: 120
},
'60 * 40': {
width: 120,
height: 80
}
},
paperPopVisible: false,
paperWidth: 200,
paperHeight: 200,
isShowDynamicForm: false,
dynamicFrom: {
},
dynamicFromList: [],
panel: null,
contents: [],
images: [],
search: {
current: 1,
size: 10,
type: 1
},
searchTemplate: {
current: 1,
size: 8,
type: 1
},
templateTotal: 0,
isShowImage: false,
tableData: [],
total: 0,
templateColConfigs: [
{ prop: 'name', label: '模板名称', align: 'left' },
{ slot: 'img'}
],
templateLoading: false,
hasMore: true
}
},
watch: {
template: {
handler(value) {
if (value && this.hiprintTemplate && !this.isPrint) {
const config = value.panels[0]
this.hiprintTemplate.update(value)
this.curPaper = {type: 'other', width: config.width, height: config.height}
this.hiprintTemplate.setPaper(config.width, config.height)
if (this.params) {
this.dynamicFromList = this.params
}
}
},
deep: true
}
},
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
},
colConfigs () {
if (this.search.type === 1) {
return [
{ slot: 'image' },
{ prop: 'remark', label: '描述', align: 'center' }
]
}
return [
{ prop: 'contents', label: '文本', align: 'center' },
{ prop: 'remark', label: '描述', align: 'center' }
]
}
},
mounted() {
if (this.isPrint) {
this.hiprintTemplate = newHiprintPrintTemplate('temulables')
} else {
hiprint.init({
providers: [defaultElementTypeProvider(), customProvider({})]
})
this.buildLeftElement()
this.buildDesigner()
this.getConfig()
this.getTemplateList()
}
},
methods: {
addField() {
const num = this.dynamicFromList.length > 9 ? this.dynamicFromList.length + 1 : `0${this.dynamicFromList.length + 1}`
// eslint-disable-next-line no-empty
if (this.dynamicFromList.length && !this.dynamicFromList.at(-1)[`field_${num}`]) {
} else {
this.$set(this.dynamicFrom, `field_${num}`, '')
}
this.isShowDynamicForm = true
},
removeField(index) {
this.dynamicFromList.splice(index, 1)
},
updateTempate(row) {
this.templateLoading = true
this.$http.post(`/api/templateRecommend/detail?id=${row.id}`).then(res => {
if (res.code === 0) {
const config = JSON.parse(res.data.content)
this.hiprintTemplate.update(config)
this.hiprintTemplate.setPaper(config.panels[0].width, config.panels[0].height)
this.dynamicFromList = JSON.parse(res.data.params)
this.isShowTemplate = false
}
this.templateLoading = false
})
},
getTemplateList() {
if (!this.hasMore) return
if (this.templateLoading) return
this.templateLoading = true
this.$http.post(`/api/templateRecommend/getRecommendPage`, null, {
params: this.searchTemplate
}).then(res => {
if (res.code === 0) {
this.templateList = [...this.templateList, ...res.data.records]
this.templateTotal = res.data.total
if (res.data.records.length < this.searchTemplate.size) {
this.hasMore = false
} else {
this.searchTemplate.current = this.searchTemplate.current + 1
}
this.templateLoading = false
}
})
},
showTemplate (row) {
this.templateLoading = true
this.$http.post(`/api/templateRecommend/detail?id=${row.id}`).then(res => {
this.templateLoading = false
if (res.code === 0) {
this.html = res.data.codes
this.isShowPreview = true
}
})
},
getConfig() {
this.$http.post(`/api/material/getPage?current=${this.search.current}&size=${this.search.size}&type=${this.search.type}`).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
addImage(src) {
this.panel.addPrintImage({
options: {
title: '',
left: 70.5,
top: 58.5,
src: src,
width: 100,
height: 100,
fit: 'contain'
}
})
const el = this.hiprintTemplate.printPanels[0].printElements.at(-1)
const designPaper = this.hiprintTemplate.printPanels[0].designPaper
this.hiprintTemplate.printPanels[0].appendDesignPrintElement(designPaper, el, true)
el.design(void 0, designPaper)
},
addText(text, isSetField = false) {
this.panel.addPrintText({
options: {
field: isSetField ? text : '',
testData: isSetField ? text : '',
title: isSetField ? '' : text,
left: 70.5,
top: 58.5,
width: 140,
height: 20,
coordinateSync: true,
contentPaddingLeft: 5.25,
textContentVerticalAlign: 'middle',
widthHeightSync: true,
hideTitle: true,
fontFamily: 'Microsoft YaHei',
fontWeight: '700'
}
})
const el = this.hiprintTemplate.printPanels[0].printElements.at(-1)
const designPaper = this.hiprintTemplate.printPanels[0].designPaper
this.hiprintTemplate.printPanels[0].appendDesignPrintElement(designPaper, el, true)
el.design(void 0, designPaper)
},
addItemToCanvas(name) {
this.addText(name, true)
},
onConfirm() {
this.$refs.form.validate((valid) => {
if (valid) {
const num = this.dynamicFromList.length > 9 ? this.dynamicFromList.length + 1 : `0${this.dynamicFromList.length + 1}`
this.dynamicFromList.push({
fieldValue: `field_${num}`,
fieldName: this.dynamicFrom[`field_${num}`]
})
this.isShowDynamicForm = false
}
})
},
buildLeftElement() {
// eslint-disable-next-line no-undef
hiprint.PrintElementTypeManager.buildByHtml($('.ep-draggable-item'))
// eslint-disable-next-line no-undef
$('#custom-provider').empty()
// eslint-disable-next-line no-undef
hiprint.PrintElementTypeManager.build($('#custom-provider'), 'customProvider')
},
buildDesigner() {
// eslint-disable-next-line no-undef
$('#hiprint-printTemplate').empty()
this.hiprintTemplate = newHiprintPrintTemplate('temulables', {
template: this.template,
settingContainer: '#PrintElementOptionSetting',
onImageChooseClick: (target) => {
let input = document.createElement('input')
input.setAttribute('type', 'file')
input.click()
input.onchange = function () {
var file = this.files[0]
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
})
this.panel = this.hiprintTemplate.printPanels[0]
})
},
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() {
this.hiprintTemplate.print(this.printData)
},
// js打印不显示设计界面
toPrint (template, printData) {
this.hiprintTemplate = newHiprintPrintTemplate('temulables')
setTimeout(() => {
this.hiprintTemplate.update(template)
this.hiprintTemplate.print(printData)
}, 100)
},
elementToString(el) {
const node = document.createElement('div')
node.innerHTML = el.html()
document.querySelector('body').appendChild(node)
const html = node.innerHTML
document.querySelector('body').removeChild(node)
return html
},
savePdf() {
this.hiprintTemplate.toPdf(this.printData, '测试导出pdf',{scale:2 }).then(v => {
console.log(v)
})
},
save() {
const html = this.elementToString(this.hiprintTemplate.getHtml(this.printData))
const json = this.hiprintTemplate.getJson()
return {
html,
json,
params: JSON.stringify(this.dynamicFromList)
}
},
preview() {
this.html = this.elementToString(this.hiprintTemplate.getHtml(this.printData))
this.isShowPreview = true
},
getHtml(template, printData) {
this.hiprintTemplate = newHiprintPrintTemplate('temulables')
this.hiprintTemplate.update(template)
const html = this.elementToString(this.hiprintTemplate.getHtml(printData))
return html
},
clearPaper() {
this.hiprintTemplate.clear()
},
exportJson() {
return this.hiprintTemplate.getJson()
}
}
}
</script>
<style lang="scss" scoped>
.print {
height: 100%;
.temuBarCode {
display: flex;
flex-direction: column;
padding: 1pt 3pt;
}
.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;
.dynamicField {
background-color: #bfc2e9;
border-color: #bfc2e9;
}
}
::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 {
display: flex;
align-items: center;
justify-content: space-between;
width: 320px;
margin: 14px 0;
}
.left-item__wrapper {
display: flex;
flex-wrap: wrap;
margin-bottom: 10px;
.item {
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
left: 0!important;
top: 0!important;
width: 100px;
margin-bottom: 10px;
margin-right: 10px;
padding: 10px 0;
background-color: #eee;
border-radius: 4px;
&:nth-of-type(3n) {
margin-right: 0;
}
i {
margin-bottom: 10px;
}
.el-icon-error {
position: absolute;
top: -6px;
right: -6px;
color: red;
font-size: 18px;
cursor: pointer;
}
}
::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;
}
.templateList-wrapper {
padding-right: 10px;
.templateList {
display: flex;
flex-wrap: wrap;
.templateList-item {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
width: 24%;
margin-bottom: 20px;
margin-right: 1.333%;
padding: 10px;
border-radius: 3px;
overflow: hidden;
background-color: #f4f4f4;
.el-button {
position: absolute;
top: 0;
right: 0;
z-index: 1;
}
h2 {
margin-top: 12px;
font-size: 14px;
font-weight: 600;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&:nth-of-type(4n) {
margin-right: 0;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,132 @@
/* eslint-disable no-undef */
import { hiprint } from 'vue-plugin-hiprint'
export const customProvider = function () {
const addElementTypes = function (context) {
context.removePrintElementTypes('customProvider')
context.addPrintElementTypes('customProvider', [
new hiprint.PrintElementTypeGroup('', [
{
tid: 'providerModule1.html',
title: 'TEMU条码',
data: 'XS888888888',
type: 'html',
formatter: function (data, options, sukData) {
const elId = options.elId || 'barCode-' + new Date().getTime()
const codeWidth = parseInt((options.width - 22) * 0.85)
let printData = {
labelCode: 123456789,
productSkuId: `XXXXXXXXXXXXXX`,
skuExtCode: 'XXXXXXXXXXXXXX',
skuSpecName: 'XXXXX'
}
options.elId = `${elId}`
if (sukData) {
printData = sukData
}
$('body').append(`<div id="codewrapper-${elId}" style="width: ${codeWidth}pt">
<svg id="${elId}" width="100%" display="block" height="100%" class="hibarcode_imgcode" preserveAspectRatio="none slice"></svg>
</div>`)
JsBarcode('#' + elId, printData.labelCode, {
format: 'CODE128B',
width: 2,
height: parseInt(hinnn.pt.toPx(options.height - 22).toString()) * 0.6,
margin: 0,
displayValue: false
})
const codeHtml = $(`#codewrapper-${elId}`).html()
$(`body>#codewrapper-${elId}`).remove()
const resizeObserver = new ResizeObserver(() => {
JsBarcode('#' + elId, printData.labelCode, {
format: "CODE128B",
width: 2,
height: parseInt(hinnn.pt.toPx(options.height - 22).toString()) * 0.6,
margin: 0,
displayValue: false
})
})
setTimeout(() => {
const node = document.getElementById(`temuBarCode-${elId}`)
if (node) {
resizeObserver.observe(node)
}
}, 0)
var html = `
<div class="temuBarCode" id="temuBarCode-${elId}">
<div class="temuBarCode-top">
<div class="hiprint-printElement-text-content hiprint-printElement-content">${printData.skuExtCode || printData.productSkcId}</div>
<div class="hiprint-printElement-text-content hiprint-printElement-content">${printData.skuSpecName}</div>
</div>
<div class="temuBarCode-middle">
<div class="hiprint-printElement-text-content hiprint-printElement-content temuBarCode-code" style="width: 100%">
${codeHtml}
</div>
</div>
<div class="temuBarCode-bottom">
<div class="hiprint-printElement-text-content hiprint-printElement-content">${printData.productSkuId}</div>
<div class="hiprint-printElement-text-content hiprint-printElement-content">Made in China</div>
</div>
</div>
`
return html
},
options: {
width: 316,
height: 120,
elId: ''
},
printElementType: {
}
},
// {
// 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: '图片',
type: 'image',
options: {
contain: 'contain',
src: 'http://lyshunong.oss-cn-beijing.aliyuncs.com/image/material/a6e14259412606502e49f1abd866039.png',
}
}
])
])
}
return {
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

File diff suppressed because one or more lines are too long

View File

@@ -1,29 +1,82 @@
/**
利用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) => {
if (request.type == 'api') {
let headers = {}, data;
new Promise((resolve) => {
let headers = {};
if (request.needMallId) {
headers.Mallid = request.mallId;
}
if (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();
Promise.resolve().then(() => fetch(request.url, {
'headers': headers,
'method': 'POST',
'referrerPolicy': 'no-referrer',
'credentials': 'include',
'body': JSON.stringify(request.data),
'mode': 'cors'
})).then((res) => {
resolve(res.json());
});
}).then(sendResponse);
if (request.isFormData && !!request.data.image) {//针对图片上传特殊的处理办法
getImageBlob(request.data.image).then(res => data.append('image', res.image.blobData)).then(resolve)
} else resolve()
}).then(() => fetch(request.url, {
headers, 'method': 'POST', 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'body': data, 'mode': 'cors'
})).then(res => res.json()).then(sendResponse);
} else if (request.type == 'temuApi') {
new Promise((resolve) => {
let headers = {};
@@ -33,26 +86,20 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
headers['Content-Type'] = 'application/json';
headers.cookie = getTemuCookie();
Promise.resolve().then(() => fetch(request.url, {
'headers': headers,
'method': 'POST',
'referrerPolicy': 'no-referrer',
'credentials': 'include',
'body': JSON.stringify(request.data),
'mode': 'cors'
'headers': headers, 'method': 'POST', '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 == 'temu') {
new Promise((resolve) => {
let headers = {};
headers['Content-Type'] = 'text/html';
headers.cookie = getTemuCookie();
//headers.cookie = getTemuCookie();
Promise.resolve().then(() => fetch(request.url, {
'headers': headers,
'method': 'GET',
'referrerPolicy': 'no-referrer',
'credentials': 'include',
'headers': headers, 'method': 'GET', 'referrerPolicy': 'no-referrer', //'credentials': 'include',
'mode': 'cors'
})).then((res) => {
// 创建了一个数据读取器
@@ -61,7 +108,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const decoder = new TextDecoder();
let text = ""
reader.read().then(function processText({ done, value }) {
reader.read().then(function processText({done, value}) {
// Result 对象包含了两个属性:
// done - 当 stream 传完所有数据时则变成 true
// value - 数据片段。当 done 为 true 时始终为 undefined
@@ -77,16 +124,18 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
});
}).then(sendResponse);
} else if (request.type == 'aliexpress') {
new Promise((resolve) => {
new Promise(async (resolve) => {
let headers = {};
headers['Content-Type'] = 'text/html';
headers.cookie = getAliexpressCookie();
Promise.resolve().then(() => fetch(request.url, {
'headers': headers,
'method': 'GET',
'referrerPolicy': 'no-referrer',
'credentials': 'include',
'mode': 'cors'
const cookie = await getAliexpressCookie(request.url);
const {_m_h5_c, _m_h5_tk} = cookie2Obj(cookie)
const {query: {data, appKey}} = qs.parseUrl(request.url)
const {formData = data} = request
const {sign, t} = getSign(_m_h5_c || _m_h5_tk, appKey, formData)
const url = qs.stringifyUrl({url: request.url, query: {sign, t}})
headers.cookie = await getAliexpressCookie(url);
Promise.resolve().then(() => fetch(url, {
'headers': headers, 'method': 'POST', 'referrerPolicy': 'no-referrer', 'credentials': 'include', 'mode': 'cors'
})).then((res) => {
// 创建了一个数据读取器
const reader = res.body.getReader();
@@ -94,7 +143,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const decoder = new TextDecoder();
let text = ""
reader.read().then(function processText({ done, value }) {
reader.read().then(function processText({done, value}) {
// Result 对象包含了两个属性:
// done - 当 stream 传完所有数据时则变成 true
// value - 数据片段。当 done 为 true 时始终为 undefined
@@ -109,15 +158,81 @@ chrome.runtime.onMessage.addListener((request, sender, 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 == 'xcApi') {
new Promise(async (resolve) => {
let headers = {}, data;
headers.cookie = await getXcCookie();
console.log(headers)
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
}
Promise.resolve().then(() => fetch(request.url, {
'headers': headers, 'method': request.method, 'referrerPolicy': 'strict-origin-when-cross-origin', 'credentials': 'include', 'body': data, 'mode': 'cors'
})).then((res) => {
resolve(res.json());
}).catch(() => {
resolve({success: false, errorCode: -1})
});
}).then(sendResponse);
} else if (request.type == 'notify') {
chrome.notifications.create(
"" + Math.random(), {
type: "basic",
title: "TEMU助手",
message: "您店铺【" + request.mallName + "】的商品【" + request.productName + "】成功加入发货台,请尽快处理",
iconUrl: "./icons/48.png"
}, null
)
chrome.notifications.create("" + Math.random(), {
type: "basic", title: "TEMU助手", message: "您店铺【" + request.mallName + "】的商品【" + request.productName + "】成功加入发货台,请尽快处理", iconUrl: "./icons/48.png"
}, null)
}
return true;
@@ -132,35 +247,32 @@ chrome.action.onClicked.addListener(function () {
});
chrome.webRequest.onSendHeaders.addListener(details => {
if (details.url && (details.url.indexOf('joinDeliveryGoodsOrderPlatform') != -1)) {
if (details.url && (details.url.indexOf('joinDeliveryGoodsOrderPlatform') != -1)) {
details.requestHeaders.push({
name: 'Referer',
value: 'https://kuajing.pinduoduo.com/main/order-manage'
name: 'Referer', value: 'https://seller.kuajingmaihuo.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') {
details.requestHeaders[i].value = 'https://kuajing.pinduoduo.com'
details.requestHeaders[i].value = 'https://seller.kuajingmaihuo.com'
break;
}
}
} else if (details.url && (details.url.indexOf('mms/userInfo') != -1)) {
} else if (details.url && (details.url.indexOf('mms/userInfo') != -1)) {
details.requestHeaders.push({
name: 'Referer',
value: 'https://kuajing.pinduoduo.com/main/order-manage'
name: 'Referer', value: 'https://seller.kuajingmaihuo.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') {
details.requestHeaders[i].value = 'https://kuajing.pinduoduo.com'
details.requestHeaders[i].value = 'https://seller.kuajingmaihuo.com'
break;
}
}
}
},
{urls: ["<all_urls>"]},["requestHeaders", "extraHeaders"]);
}, {urls: ["<all_urls>"]}, ["requestHeaders", "extraHeaders"]);
function getCookie() {
const url = new URL("https://kuajing.pinduoduo.com/");
const url = new URL("https://seller.kuajingmaihuo.com/");
let cStr = '';
chrome.cookies.getAll({domain: url.host}, (cookie) => {
cookie.map((c) => {
@@ -181,8 +293,8 @@ function getTemuCookie() {
return cStr;
}
function getAliexpressCookie() {
const url = new URL("https://www.aliexpress.us/");
function getSheinCookie() {
const url = new URL("https://www.shein.com/");
let cStr = '';
chrome.cookies.getAll({domain: url.host}, (cookie) => {
cookie.map((c) => {
@@ -190,4 +302,37 @@ function getAliexpressCookie() {
});
});
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 getXcCookie(link = "http://xc.rqlis.com:888/") {
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/FileSaver.js'), 'body');
injectScript( chrome.runtime.getURL('/js/download.js'), 'body');
injectScript( chrome.runtime.getURL('/js/temuSeller.js'), 'body');

View File

@@ -17,10 +17,28 @@ var relativeTime = require('dayjs/plugin/relativeTime')
require('dayjs/locale/zh-cn')
dayjs.extend(relativeTime)
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])
Vue.prototype.$http = instance
utils.initWindow()
Vue.directive('throttle', {
bind: function (el, obj) {
let timerId = null
let flag = true
el.addEventListener('input', function () {
if (!flag) return
flag = false
timerId && clearTimeout(timerId)
timerId = setTimeout(function () {
flag = true
obj.value()
}, 800)
})
}
})
new Vue({
store,

View File

@@ -11,13 +11,19 @@
"48": "icons/48.png",
"128": "icons/128.png"
},
"action": {
},
"action": {},
"host_permissions": [
"*://*.pinduoduo.com/",
"*://*.jjcp52.com/",
"*://*.kuajingmaihuo.com/",
"*://*.temu.com/",
"*://*.aliexpress.us/",
"*://*.amazon.com/"
"*://*.aliexpress.com/",
"*://*.alicdn.com/",
"*://*.amazon.com/",
"*://*.shein.com/",
"*://*.geiwohuo.com/",
"*://*.ltwebstatic.com/",
"*://*.goodcang.com/",
"*://*.rqlis.com/"
],
"permissions": [
"cookies",
@@ -28,30 +34,103 @@
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"declarativeNetRequestFeedback",
"activeTab"
"activeTab",
"fileSystemProvider"
],
"declarative_net_request": {
"rule_resources": [{
"id": "1",
"enabled": true,
"path": "rules_1.json"
}]
"rule_resources": [
{
"id": "1",
"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"
},
{
"id": "12",
"enabled": true,
"path": "rules_12.json"
}
]
},
"content_scripts": [
{
"matches": [
"*://*.aliexpress.us/item/*",
"*://*.amazon.com/*"
"*://*.aliexpress.com/item/*",
"*://*.amazon.com/*",
"*://*.shein.com/*"
],
"js": [
"/content.js"
]
},
{
"matches": [
"*://*.kuajingmaihuo.com/*"
],
"js": [
"/js/temuSellerContent.js"
]
}
],
"web_accessible_resources": [
{
"resources": [ "js/download.js","js/jszip.min.js","js/FileSaver.js" ],
"matches": [ "*://*.aliexpress.us/*", "*://*.amazon.com/*" ]
"resources": [
"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,
"name": "TEMU助手",
"description": "TEMU助手 - 自动化提高生产效率",
"version": "3.0.0",
"version": "3.4.0",
"background": {
"service_worker": "/background.js"
},
@@ -11,13 +11,18 @@
"48": "icons/48.png",
"128": "icons/128.png"
},
"action": {
},
"action": {},
"host_permissions": [
"*://*.pinduoduo.com/",
"*://124.71.2.127:8888/",
"*://*.kuajingmaihuo.com/",
"*://*.temu.com/",
"*://*.aliexpress.us/",
"*://*.amazon.com/"
"*://*.aliexpress.com/",
"*://*.alicdn.com/",
"*://*.amazon.com/",
"*://*.shein.com/",
"*://*.geiwohuo.com/",
"*://*.ltwebstatic.com/",
"*://*.rqlis.com/"
],
"permissions": [
"cookies",
@@ -28,30 +33,103 @@
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"declarativeNetRequestFeedback",
"activeTab"
"activeTab",
"fileSystemProvider"
],
"declarative_net_request": {
"rule_resources": [{
"id": "1",
"enabled": true,
"path": "rules_1.json"
}]
"rule_resources": [
{
"id": "1",
"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"
},
{
"id": "12",
"enabled": true,
"path": "rules_12.json"
}
]
},
"content_scripts": [
{
"matches": [
"*://*.aliexpress.us/item/*",
"*://*.amazon.com/*"
"*://*.aliexpress.com/item/*",
"*://*.amazon.com/*",
"*://*.shein.com/*"
],
"js": [
"/content.js"
]
},
{
"matches": [
"*://*.kuajingmaihuo.com/*"
],
"js": [
"/js/temuSellerContent.js"
]
}
],
"web_accessible_resources": [
{
"resources": [ "js/download.js","js/jszip.min.js","js/FileSaver.js" ],
"matches": [ "*://*.aliexpress.us/*", "*://*.amazon.com/*" ]
"resources": [
"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 VueRouter from 'vue-router'
import store from '@/store'
import media from "@/router/media";
Vue.use(VueRouter)
@@ -25,6 +26,11 @@ const router = new VueRouter({
name: 'changePwd',
component: () => import('../view/login/ChangePwd')
},
{
path: 'waitCreate',
name: 'waitCreate',
component: () => import('../view/shipping/WaitCreate.vue')
},
{
path: 'normalSendGoods',
name: 'NormalSendGoods',
@@ -50,23 +56,81 @@ const router = new VueRouter({
name: 'waitShippingList',
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',
name: 'copyProduct',
component: () => import('../view/product/CopyProduct.vue')
},
{
path: 'reducePrice',
name: 'reducePrice',
component: () => import('../view/product/ReducePrice.vue')
path: 'sellerSelect',
name: 'sellerSelect',
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: 'priceDown',
name: 'priceDown',
component: () => import('../view/product/PriceDown.vue')
},
{
path: 'batchUpload',
name: 'batchUpload',
component: () => import('../view/product/BatchUpload.vue')
},
{
path: 'niubiCopy',
name: 'niubiCopy',
component: () => import('../view/selection/NiubiCopy.vue')
},
{
path: 'aliExpressCopy',
name: 'aliExpressCopy',
component: () => import('../view/selection/AliExpressCopy.vue')
},
{
path: 'storeTrack',
name: 'storeTrack',
@@ -77,7 +141,46 @@ const router = new VueRouter({
name: 'keywordTrack',
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',
name: 'message',
@@ -89,13 +192,114 @@ const router = new VueRouter({
name: 'coinFlow',
component: () => import('../view/CoinFlow.vue')
},
{
path: 'costManageTemu',
name: 'costManageTemu',
component: () => import('../view/sale/CostManageTemu.vue')
},
{
path: '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: 'sendGoods_xc',
name: 'sendGoods_xc',
component: () => import('../view/semi/SemiSendGoods_XC.vue')
},
// {
// path: 'statistics',
// name: 'statistics',
@@ -105,7 +309,47 @@ const router = new VueRouter({
path: 'learning',
name: 'learning',
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 +367,21 @@ const router = new VueRouter({
title: '注册'
},
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) {
return savedPosition
} 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,7 +13,10 @@ export default new Vuex.Store({
mallName: '',
mallList: [],
activeDlgShow: false,
userInfo: {}
showSheinAlert: false,
showTemuAlert: false,
userInfo: {},
labelInfo: {}
},
mutations: {
@@ -46,6 +49,15 @@ export default new Vuex.Store({
},
setActiveDlgShow(state, flag) {
state.activeDlgShow = flag
},
setSheinAlertShow(state, flag) {
state.showSheinAlert = flag
},
setTemuAlertShow(state, flag) {
state.showTemuAlert = flag
},
setLabelInfo(state, info) {
state.labelInfo = info
}
},
@@ -58,8 +70,25 @@ export default new Vuex.Store({
resolve(res.data)
}
})
store.dispatch('getLabelInfo')
})
},
getLabelInfo(store) {
return new Promise(resolve => {
request.post('/api/userExtend/getLabelDetail').then(res => {
if (res.code === 0) {
const isExpires = new Date().getTime() > new Date(res.data.expireTime).getTime() || res.data.skuUsed > res.data.skuTotal
store.commit('setLabelInfo', {
...res.data,
isExpires
})
resolve(res.data)
}
})
})
},
SignOut(store, isClear) {
if (isClear) {
store.commit('logout')

View File

@@ -1,4 +1,5 @@
export function timestampToTime(timestamp) {
if (!timestamp) return ''
// 时间戳为10位需*1000时间戳为13位不需乘1000
let date = new Date(timestamp);
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

@@ -1,6 +1,9 @@
import request from '../api'
import store from '../store'
import { Message } from 'element-ui'
import JsBarcode from 'jsbarcode'
import { Interpreter } from 'eval5'
import { transform } from "@babel/standalone"
const dict = {
url: "/api/dictionary/queryValsByCodeList",
@@ -104,8 +107,8 @@ const userCheck = (mallId) => {
tempMallId = store.state.mallId
}
if (res.type != 4 && tempMallId != store.state.userInfo.mallId) {
Message.error('您当前登录的TEMU账号与会员绑定账号不一致')
reject('您当前登录的TEMU账号与会员绑定账号不一致')
Message.error('您当前登录的卖家中心店铺与会员绑定店铺不一致')
reject('您当前登录的卖家中心店铺与会员绑定店铺不一致')
return false
}
@@ -114,10 +117,34 @@ const userCheck = (mallId) => {
})
}
const sleepSync = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
const initWindow = () => {
const transformCode = (codeStr) => {
return transform(codeStr, { presets: ['env'] }).code
}
window.JsBarcode = JsBarcode
window.eval = (code, context) => {
const interpreter = new Interpreter(context || window, {
timeout: 1000,
})
try {
interpreter.evaluate(transformCode(code))
} catch (err) {
console.log(err)
}
}
}
export default {
dict,
dateUtil,
userCheck
sleepSync,
userCheck,
initWindow
}

View File

@@ -16,7 +16,189 @@ export function transform(leftData) {
// 普通属性
rightData.productName = leftData.productName;
rightData.materialMultiLanguages = leftData.productLocalExtAttr.materialMultiLanguages;
rightData.productI18nReqs = leftData.productI18nList;
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;
if (!leftSkuItem.productSkuWhExtAttr) {
rightSkuItem.productSkuWhExtAttrReq = null
} else {
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++) {
rightData.productPropertyReqs.push({
valueUnit: leftData.productPropertyList[i].valueUnit,
@@ -86,7 +268,19 @@ export function transform(leftData) {
"sensitiveList": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveAttr.sensitiveList},
"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.supplierPrice = leftSkuItem.supplierPrice + config.upMoney * 100;
rightSkcItem.productSkuReqs.push(rightSkuItem);
}
@@ -102,15 +296,24 @@ export function transform(leftData) {
rightData.carouselImageI18nReqs = leftData.carouselImageI18nVOList;
rightData.materialImgUrl = leftData.materialImgUrl;
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.showSizeTemplateIds = !leftData.showSizeTemplateIds ? []: leftData.showSizeTemplateIds;
rightData.goodsModelReqs = !leftData.goodsModelList ? []: leftData.goodsModelList;
rightData.productWhExtAttrReq = {
outerGoodsUrl: leftData.productWhExtAttr.outerGoodsUrl,
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.goodsAdvantageLabelTypes = leftData.goodsAdvantageLabelVOList;
rightData.productDetailVideoReqList = leftData.detailVideoVOList;
@@ -133,7 +336,278 @@ export function transform(leftData) {
rightData.productOuterPackageReq = leftData.productWhExtAttr.productOuterPackage;
rightData.sensitiveTransNormalFileReqs = leftData.productWhExtAttr.sensitiveTransNormalFiles;
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);
}

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,26 +2,28 @@
<div class="admin">
<div class="admin-top">
<div class="logo">
<img src="../assets/images/logo.png" />
<img src="../assets/images/logo.png"/>
<span>v{{ version }}</span>
</div>
<div class="admin-right">
<el-tooltip class="item" effect="dark" content="金币信息" placement="top">
<div class="left" @click="toActive">
<div class="left" @click="openMember(1)">
<div :style="{marginLeft: '0px'}">{{ $store.state.userInfo.coin }}</div>
<span style="margin-right: 10px;"><img src="../assets/coin.png" width="30"/></span>
</div>
</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">
<div class="left" @click="toActive">
<div class="left" @click="openMember(0)">
<span>会员信息:</span>
<div :style="{marginLeft: '10px', color: $store.state.userInfo.flag == 1? 'green': 'red'}">{{ getStateInfo }}</div>
</div>
</el-tooltip>
<el-dropdown @command="handleClick">
<div class="userinfo">
<span>{{ $store.state.userInfo.name + "(" + $store.state.userInfo.phone + ")" }}</span>
<img src="../assets/images/bottom.png" />
<span>{{ userInfo.name + "(" + userInfo.phone + ")" }}</span>
<img src="../assets/images/bottom.png"/>
</div>
<el-dropdown-menu slot="dropdown">
<!-- <el-dropdown-item command="phone">修改手机</el-dropdown-item> -->
@@ -36,50 +38,161 @@
<div class="admin-main">
<el-scrollbar class="left">
<el-menu
:default-active="activePath"
:collapse-transition="true"
unique-opened
background-color="#545c64"
router
text-color="#fff"
:collapse="isCollapse">
:default-active="activePath"
:collapse-transition="true"
unique-opened
background-color="#545c64"
router
text-color="#fff"
:collapse="isCollapse">
<el-menu-item index="/welcome">
<i class="el-icon-monitor"></i>
<span slot="title">工作台</span>
</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">
<template slot="title">
<i class="el-icon-shopping-cart-2"></i>
<span slot="title">备货单管理</span>
</template>
<el-menu-item index="/waitCreate">创建备货单</el-menu-item>
<el-menu-item index="/normalSendGoods">抢仓发货</el-menu-item>
<el-menu-item index="/shippingDesk">发货台管理</el-menu-item>
<el-menu-item index="/waitPackageList">待装箱发货单</el-menu-item>
<el-menu-item index="/waitShippingList">待收货发货单</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 index="/copyProduct">
<template slot="title">
<i class="el-icon-goods"></i>
<span slot="title">商品管理</span>
</template>
<el-menu-item index="/productList">商品列表</el-menu-item>
<el-menu-item index="/copyProduct">商品复制</el-menu-item>
<el-menu-item index="/findSeller">查找买手</el-menu-item>
<el-menu-item index="/priceDown">拒绝调价</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 index="/niubiCopy">
<template slot="title">
<i class="el-icon-magic-stick"></i>
<span slot="title">选品采集</span>
</template>
<el-menu-item index="/niubiCopy">智能复制</el-menu-item>
<el-menu-item index="/storeTrack">店铺跟踪</el-menu-item>
<el-menu-item index="/keywordTrack">关键字跟踪</el-menu-item>
<el-menu-item index="/niubiCopy">商品采集</el-menu-item>
<el-menu-item index="/aliExpressCopy">速卖通采集</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-menu-item index="/saleData">
<i class="el-icon-s-data"></i>
<span slot="title">销售数据</span>
<el-submenu index="/qualification">
<template slot="title">
<i class="el-icon-s-check"></i>
<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-submenu index="/semi" v-if="$store.state.userInfo.phone == '18571466720'">
<template slot="title">
<i class="el-icon-s-data"></i>
<span slot="title">半托管</span>
</template>
<el-menu-item index="/sendGoods_xc">发货(芯仓)</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 index="/priceFollow">
<i class="el-icon-money"></i>
<span slot="title">调价管理</span>
</el-menu-item>-->
<el-menu-item index="/learning">
<i class="el-icon-eleme"></i>
<span slot="title">新手园地</span>
@@ -88,6 +201,25 @@
<i class="el-icon-s-data"></i>
<span slot="title">数据统计</span>
</el-menu-item> -->
<el-submenu index="labels">
<template slot="title">
<i class="el-icon-s-goods"></i>
<div slot="title" style="display: inline-block;">
<span>高级功能</span>
</div>
</template>
<el-submenu index="/labelsTemplate" style="padding-left: 15px;">
<template slot="title">
<div slot="title" style="display: inline-block; ">
<span>标签管理</span>
<el-button type="text" style="margin-left: 57px; padding: 0;" @click.stop="openMember(2)">开通</el-button>
</div>
</template>
<el-menu-item index="/labelsTemplate">模板管理</el-menu-item>
<el-menu-item index="/labelsPrint">标签打印</el-menu-item>
</el-submenu>
</el-submenu>
</el-menu>
</el-scrollbar>
<div class="container">
@@ -99,303 +231,459 @@
</div>
</div>
<el-dialog
title="激活充值"
:visible="$store.state.activeDlgShow"
:close-on-click-modal="false"
width="1200"
:before-close="handleClose">
title="激活充值"
:visible="$store.state.activeDlgShow"
:close-on-click-modal="false"
width="1200"
:before-close="handleClose">
<ai-payment/>
</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>
<LablesMember ref="LablesMember"></LablesMember>
</div>
</template>
<script>
import {mapMutations, mapState} from 'vuex'
import AiPayment from "@/components/AiPayment.vue";
import LablesMember from '@/components/LablesMember'
import {sendAliexpressAPIMessage, sendGoodcangAPIMessage} from "@/api/chromeApi";
export default {
components: {AiPayment},
data () {
return {
isCollapse: false,
activePath: '/home',
form: {
mallId: this.$store.state.mallId,
mallName: this.$store.state.mallName,
code: ''
},
version: '',
vipType: ["体验会员","月会员","半年会员","年会员","多店通用年会员"]
export default {
components: {AiPayment, LablesMember},
data() {
return {
isCollapse: false,
activePath: '/home',
form: {
mallId: this.$store.state.mallId,
mallName: this.$store.state.mallName,
code: ''
},
version: '',
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 {
return '已过期';
}
},
...mapState(['mallName', 'mallList', 'userInfo']),
isAdmin: v => ['18571466720'].includes(v.userInfo.phone),
},
...mapState(['mallName', 'mallList'])
},
watch: {
$route (v) {
watch: {
$route(v) {
if (v.meta && v.meta.activeMenu) {
this.activePath = v.meta.activeMenu
} else {
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() {
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')
}
},
openMember(index) {
this.$refs.LablesMember.show(index)
},
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>
<style lang="scss" scoped>
.admin {
width: 100%;
height: 100vh;
.admin {
width: 100%;
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;
background: #f4f4f4;
border-top: 1px solid rgba(0, 0, 0, 0.15);
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all .4s;
.container {
flex: 1;
height: 100%;
overflow: hidden;
.container-app {
width: 100%;
height: 100%;
overflow-y: auto;
}
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-20px);
.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;
}
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(20px);
}
.admin-main {
.admin-middle {
display: flex;
align-items: center;
height: calc(100% - 64px);
overflow: hidden;
border-top: 1px solid rgba(0,0,0,0.15);
height: 64px;
.container {
flex: 1;
height: 100%;
overflow: hidden;
span {
position: relative;
margin-right: 60px;
height: 64px;
line-height: 64px;
font-size: 16px;
color: #1F2635;
cursor: pointer;
transition: all ease 0.5s;
.container-app {
width: 100%;
height: 100%;
overflow-y: auto;
&.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 {
display: flex;
align-items: center;
height: 100%;
.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;
align-items: center;
height: 64px;
cursor: pointer;
transition: all ease 0.3s;
span {
position: relative;
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;
}
&:hover {
opacity: 0.5;
}
}
.admin-right {
.userinfo {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
height: 56px;
margin-left: 20px;
cursor: pointer;
transition: all ease 0.3s;
.left {
display: flex;
align-items: center;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.5;
}
&:hover {
opacity: 0.5;
}
.userinfo {
display: flex;
justify-content: center;
align-items: center;
height: 56px;
margin-left: 20px;
cursor: pointer;
transition: all ease 0.3s;
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
&:hover {
opacity: 0.5;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
span {
margin-right: 8px;
color: #1F2635;
font-size: 14px;
}
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>

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>
</ai-card>
</div>
<ai-card title="常用工具" v-if="false">
<div class="">
dsad
<ai-card title="TEMU助手功能概览">
<template #right>
<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>
</ai-card>
<AiDialog
@@ -58,9 +61,13 @@
</template>
<script>
import BiVueMindmap from "bi-vue-mindmap";
export default {
name: 'AdminHome',
components: {
BiVueMindmap,
},
data () {
return {
noticeList: [],
@@ -70,7 +77,278 @@
changeLogList: [],
isImportant: false,
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.getChangelog()
this.getMyNewestNotice()
this.checkBindWx()
},
methods: {
@@ -112,7 +391,21 @@
link.click()
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 () {
if (this.isImportant) {
this.$http.post('/api/notice/read').then(res => {

View File

@@ -0,0 +1,361 @@
<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,
subPurchaseOrderSn: item1.subPurchaseOrderSn,
createTime: timestampToTime(item1.deliverTime)
}
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,181 @@
<template>
<AiDetail class="add-label">
<template #title>
<ai-title title="添加模板" isShowBack :isShowBottomBorder="true" @onBackClick="cancel">
<template #center>
<label>模板名称</label>
<el-input placeholder="请输入模板名称" size="small" v-model="name" style="width: 200px;"></el-input>
</template>
<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="print" size="small">打印</el-button>
<el-button @click="clearPaper" size="small" type="danger">清空纸张</el-button>
<el-button @click="saveTemplate" size="small" type="primary" :loading="isLoading">保存</el-button>
</template>
</ai-title>
</template>
<template #content>
<ai-card title="标签模板" class="card" :hideTitle="true" v-loading="isLoading">
<template #content>
<div class="add-label__wrapper">
<Print
ref="printRef"
:template="template"
:printData="printData"
:isPrint="false"
:params="params">
</Print>
</div>
</template>
</ai-card>
</template>
</AiDetail>
</template>
<script>
import Print from '@/components/print/Print'
export default {
components: {
Print
},
data () {
return {
name: '',
template: {
"panels": [{
"index": 0,
"name": 1,
"height": 200,
"width": 200,
"paperHeader": 0,
"paperFooter": 547,
"printElements": [],
"paperNumberLeft": 500,
"paperNumberTop": 530,
"paperNumberDisabled": true,
"paperNumberContinue": true,
"fontFamily": "Microsoft YaHei",
"scale": 1,
"watermarkOptions": {}
}]
},
printData: {},
id: '',
info: {},
params: [],
isLoading: false
}
},
mounted() {
this.printData = {
labelCode: 123456789,
productSkuId: `XXXXXXXXXXXXXX`,
skuExtCode: 'XXXXXXXXXXXXXX',
skuSpecName: 'XXXXX'
}
if (this.$route.query.id) {
this.id = this.$route.query.id
this.getInfo()
}
},
methods: {
cancel() {
this.$router.go(-1)
},
getInfo() {
this.isLoading = true
this.$http.post(`/api/template/detail?id=${this.$route.query.id}`).then(res => {
if (res.code === 0) {
this.info = res.data
this.name = res.data.name
this.params = JSON.parse(res.data.params)
this.$nextTick(() => {
this.template = JSON.parse(res.data.content)
})
}
this.isLoading = false
})
},
preview () {
this.$refs.printRef.preview()
},
savePdf () {
this.$refs.printRef.savePdf()
},
rotatePaper () {
this.$refs.printRef.rotatePaper()
},
saveTemplate () {
if (!this.name) {
return this.$message.error('模板名称不能为空')
}
const data = this.$refs.printRef.save()
this.isLoading = true
const url = this.id ? `/api/template/modify` : `/api/template/addTemplate`
this.$http.post(url, {
name: this.name,
codes: data.html,
content: JSON.stringify(data.json),
params: data.params,
id: this.id || ''
}).then(res => {
if (res.code === 0) {
this.isLoading = false
this.$message.success('模板创建成功')
this.cancel()
} else {
this.isLoading = false
}
})
},
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>

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

@@ -0,0 +1,343 @@
<template>
<ai-list class="Print-page">
<ai-title
slot="title"
title="标签打印"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="search-item__wrapper">
<div class="left">
<div class="search-item">
<el-radio-group v-model="addType" @click="lableList = [], search.productSkuId = '', search.productSkcId = ''">
<el-radio-button label="1">按发货单打印</el-radio-button>
<el-radio-button label="2">按SKC打印</el-radio-button>
<el-radio-button label="3">按SKU打印</el-radio-button>
</el-radio-group>
</div>
</div>
<div class="right"></div>
</div>
<div class="search-item__wrapper">
<div class="left">
<div class="search-item" v-show="addType === '1'">
<label>发货单</label>
<el-select v-model="search.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>
<el-button style="margin-left: 10px;" size="small" :disabled="!search.mallId" :loading="isLoading" @click="searchSkuList">查询</el-button>
</div>
<div class="search-item" v-show="addType === '2' || addType === '3'">
<label>{{ addType === '2' ? 'SKC' : 'SKU' }}</label>
<el-input
v-if="addType === '3'"
v-model="search.productSkuId"
style="width: 250px"
size="small"
clearable
placeholder="多个查询请用户逗号分割"
suffix-icon="iconfont iconSearch">
</el-input>
<el-input
v-else
v-model="search.productSkcId"
style="width: 250px"
size="small"
placeholder="多个查询请用户逗号分割"
clearable
suffix-icon="iconfont iconSearch">
</el-input>
<el-button style="margin-left: 10px;" @click="getList" size="small" :loading="isLoading">查询</el-button>
</div>
</div>
</div>
<ai-table
:tableData="lableList"
:col-configs="colConfigs"
:isShowPagination="false"
:height="height"
@getList="() => {}"
:loading="isLoading">
<el-table-column slot="options" label="操作" align="center" fixed="right" width="120px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="toPrint(row)">打印</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<Print ref="printRef" :isPrint="true"></Print>
<ai-dialog
:visible.sync="isShow"
title="打印数量"
width="890px"
@confirm="onConfirm">
<el-form class="ai-form" :model="form" ref="form" label-width="100px">
<el-form-item label="打印数量" style="width: 100%;" prop="count" :rules="[{ required: true, message: '请输入打印数量', trigger: 'change' }]">
<el-input-number v-model="form.count" :min="1" :max="500" label="请输入打印数量"></el-input-number>
</el-form-item>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import Print from '@/components/print/Print'
import template from '@/components/print/template'
import { sendChromeAPIMessage } from '@/api/chromeApi'
export default {
name: 'PrintPage',
components: {
Print
},
data () {
return {
colConfigs: [
{ prop: 'mallName', label: '店铺名称', align: 'left' },
{ prop: 'labelCode', label: '条码编码', align: 'center' },
{ prop: 'productSkcId', label: 'SKC', align: 'center' },
{ prop: 'productSkuId', label: 'SKU', align: 'center' },
{ prop: 'skuExtCode', label: 'SKU货号', align: 'center' },
{ prop: 'skuSpecName', label: '次销售属性', align: 'center' },
{ prop: 'deliveryNum', label: '发货数', align: 'center' }
],
search: {
mallId: '',
productSkuId: '',
productSkcId: '',
page: 1,
size: -1,
templateId: ''
},
addType: '1',
isLoading: false,
lableList: [],
height: 600,
skuChoosedList: [],
isShow: false,
form: {
count: 1
},
printData: [],
templateList: [],
page: 1
}
},
computed: {
currMall () {
if (!this.$store.state.mallList.length) {
return {}
}
return this.$store.state.mallList.filter(v => v.mallId === this.search.mallId)[0]
}
},
mounted() {
this.$nextTick(() => {
this.height = document.querySelector('.ai-list__content--right').clientHeight - 140
})
},
methods: {
toPrint(row) {
this.isLoading = true
if (this.addType === '1') {
this.$http.post(`/api/template/myTemplate?productSkuId=${row.productSkuId}`).then(res => {
if (!res.data.records.length) {
this.isLoading = false
return this.$message.error('该SKU未配置模板')
}
this.$http.post(`/api/template/detail?id=${res.data.records[0].id}`).then(res => {
if (res.code === 0) {
const params = JSON.parse(res.data.params)
const getValue = v => params.filter(e => e.fieldValue === v)[0].fieldName
Object.keys(row).forEach(key => {
if (params.findIndex(v => v.fieldValue === key) > -1) {
row[getValue(key)] = row[key]
}
})
this.isLoading = false
this.$refs.printRef.toPrint(JSON.parse(res.data.content), new Array(row.deliveryNum).fill(row))
} else {
this.isLoading = false
}
})
})
} else {
this.$http.post(`/api/template/detail?id=${row.templateId}`).then(res => {
this.isLoading = false
if (res.code === 0) {
if (!res.data) {
this.isLoading = false
return this.$message.error('该SKU未配置模板')
}
const params = JSON.parse(res.data.params)
const getValue = v => params.filter(e => e.fieldValue === v)[0].fieldName
Object.keys(row).forEach(key => {
if (params.findIndex(v => v.fieldValue === key) > -1) {
row[getValue(key)] = row[key]
}
})
this.printData = {
template: JSON.parse(res.data.content),
data: row
}
this.isShow = true
}
})
}
},
onConfirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.isShow = false
this.$refs.printRef.toPrint(this.printData.template, new Array(this.form.count).fill(this.printData.data))
}
})
},
printAll() {
this.$refs.printRef.toPrint(template, this.skuChoosedList)
},
getSkuList () {
if (!this.search.mallId) {
return this.$message.error('请选择店铺')
}
},
getList () {
this.$http.post('/api/templateSku/getMySkuPage', null, {
params: this.search
}).then(res => {
if (res.code === 0) {
this.lableList = res.data.records
}
})
},
getDeliveryOrderSn (page) {
return new Promise(resolve => {
sendChromeAPIMessage({
url: 'bgSongbird-api/supplier/deliverGoods/management/pageQueryDeliveryBatch',
needMallId: true,
mallId: this.search.mallId,
anti: true,
data: {
pageNo: page,
status: 1,
onlyTaxWarehouseWaitApply: false,
productLabelCodeStyle: 0,
pageSize: 200
}
}).then(res => {
if (res.errorCode == 1000000) {
resolve({
list: [].concat(res.result.list.map(v => v.deliveryOrderList.map(e => e.deliveryOrderSn)).flat()),
isHasNext: res.result.total && res.result.list.length && (res.result.list.length < 200 && res.result.total > 200)
})
} else {
resolve({ list: [], isHasNext: false })
}
}).catch(() => {
resolve({ list: [], isHasNext: false })
})
})
},
getLabels (deliveryOrderSnList) {
return new Promise(resolve => {
sendChromeAPIMessage({
url: 'bgSongbird-api/supplier/deliverGoods/management/printProductSkuLabel',
needMallId: true,
mallId: this.search.mallId,
anti: true,
data: {
deliveryOrderSnList: deliveryOrderSnList
}
}).then(res => {
if (res.errorCode == 1000000) {
resolve(res.result.map(v => {
return {
mallName: this.$store.state.mallList.filter(v => v.mallId === this.search.mallId)[0].mallName,
productName: v.productName,
productSkcId: v.productSkcId,
productSkuId: v.productSkuId,
labelCode: v.labelCode,
skuExtCode: v.skuExtCode,
deliveryNum: v.deliveryNum,
skuSpecName: v.secondarySpecVOList.map(item => {
return item.specName
}).join(',')
}
}))
} else {
resolve([])
}
}).catch(() => {
resolve([])
})
})
},
async searchSkuList () {
let page = 1
let isHasNext = true
let list = []
let deliveryOrderSnList = []
this.isLoading = true
while (isHasNext) {
const result = await this.getDeliveryOrderSn(page)
page = page + 1
isHasNext = result.isHasNext ? true : false
deliveryOrderSnList.push(...result.list)
await this.$sleepSync(1000)
}
const len = Math.ceil(deliveryOrderSnList.length / 100)
for (let i = 0; i < len; i++) {
this.page = 1
const ids = [...new Set(deliveryOrderSnList)].slice(i * 100, i * 100 + 100)
const res = await this.getLabels(ids)
list.push(...res)
await this.$sleepSync(500)
}
this.isLoading = false
this.lableList = list
}
}
}
</script>
<style scoped lang="scss">
.Print-page {
.search-item__wrapper {
display: flex;
align-items: center;
justify-content: space-between;
&>div {
display: flex;
align-items: center;
}
}
}
</style>

View File

@@ -0,0 +1,573 @@
<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="chooseSkuList = [], isShow = true">添加</el-button>
<el-upload
action
:limit="1"
:show-file-list="false"
accept=".xls,.xlsx"
:auto-upload="false"
:file-list="fileList"
:on-change="onExcelChange">
<el-button size="small" type="danger" :disabled="!skuList.length">Excel导入</el-button>
</el-upload>
<json-excel
:data="skuList"
:fields="jsonFields"
name="SKU列表.xls"
worksheet="SKU列表">
<el-button size="small" type="warning" :disabled="!skuList.length">Excel导出</el-button>
</json-excel>
</template>
</ai-search-bar>
<ai-search-bar>
<template #left>
<div class="search-item" style="margin-bottom: 0;">
<label>SKU</label>
<el-input
v-model="search.productSkuId"
style="width: 250px"
size="small"
clearable
placeholder="请输入SKU"
suffix-icon="iconfont iconSearch"
@clear="getList">
</el-input>
</div>
<div class="search-item" style="margin-bottom: 0;">
<label>SKC</label>
<el-input
v-model="search.productSkcId"
style="width: 250px"
size="small"
placeholder="请输入SKC"
clearable
suffix-icon="iconfont iconSearch"
@clear="getList">
</el-input>
</div>
<div class="search-item" style="margin-bottom: 0;">
<label style="width: 100px;">仅显示未填写</label>
<el-select v-model="search.isShowWhite" placeholder="请选择" clearable size="small">
<el-option label="是" value="1"></el-option>
<el-option label="否" value="0"></el-option>
</el-select>
</div>
<el-button style="margin-left: 10px;" @click="getList" size="small" :loading="pageShow">查询</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="list"
:col-configs="colConfigs"
style="margin-top: 8px;"
@getList="getList"
@selection-change="handleSelectionChange"
v-loading="pageShow"
:isShowPagination="false">
<el-table-column
v-for="(item, index) in relationList"
:key="index"
:prop="item.field"
:show-overflow-tooltip="true"
:label="item.name"
align="center">
</el-table-column>
<el-table-column slot="options" label="操作" align="center" fixed="right" width="120px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
title="添加SKU"
width="1400px"
customFooter
@confirm="onConfirm">
<div class="search-item__wrapper">
<div class="left">
<div class="search-item">
<label>添加方式</label>
<el-radio-group v-model="addType" size="small" @change="onSearchRest">
<el-radio-button label="1">按类目添加</el-radio-button>
<el-radio-button label="2">按SKC添加</el-radio-button>
<el-radio-button label="3">按SKU添加</el-radio-button>
</el-radio-group>
</div>
<div class="search-item">
<label>店铺</label>
<el-select v-model="lableSearch.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>
<div class="right"></div>
</div>
<div class="search-item__wrapper">
<div class="left">
<div class="search-item" v-show="addType === '1'">
<label>商品分类</label>
<el-cascader
style="width: 280px;"
v-model="targetCatId"
:props="props"
size="small"
filterable
:show-all-levels="false"
collapse-tags
clearable>
</el-cascader>
<el-button style="margin-left: 10px;" @click="onCateChange" size="small" :disabled="!lableSearch.mallId" :loading="isLoading">查询</el-button>
</div>
<div class="search-item" v-show="addType === '2'">
<label>SKC</label>
<el-input
v-model="skuReqParams.SKC"
style="width: 250px"
size="small"
placeholder="多个查询请用户逗号分割"
clearable
@clear="getSkuList()"
suffix-icon="iconfont iconSearch">
</el-input>
<el-button style="margin-left: 10px;" @click="getSkuList" size="small" :disabled="!lableSearch.mallId" :loading="isLoading">查询</el-button>
</div>
<div class="search-item" v-show="addType === '3'">
<label>SKU</label>
<el-input
v-if="addType === '3'"
v-model="skuReqParams.SKU"
style="width: 250px"
size="small"
placeholder="多个查询请用户逗号分割"
clearable
@clear="getSkuList()"
suffix-icon="iconfont iconSearch">
</el-input>
<el-button style="margin-left: 10px;" @click="getSkuList" size="small" :disabled="!lableSearch.mallId" :loading="isLoading">查询</el-button>
</div>
</div>
</div>
<ai-table
height="370"
:tableData="lableList"
:col-configs="colConfigs"
:total="lableTotal"
:current.sync="lableSearch.current"
:size.sync="lableSearch.size"
style="margin-top: 8px;"
:pageSizes="[10, 20, 50, 100, 500, 1000]"
v-loading="isLoading"
:isShowPagination="false"
@getList="() => {}"
@selection-change="handleSelectionChange">
<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>
<template #footer>
<el-button @click="isShow = false">取消</el-button>
<el-button @click="onConfirm" type="primary" :loading="btnLoading">确认</el-button>
</template>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import { sendChromeAPIMessage } from '@/api/chromeApi'
import * as XLSX from 'xlsx'
import JsonExcel from 'vue-json-excel'
export default {
name: 'SkuManage',
components: {
JsonExcel
},
data () {
return {
total: 0,
search: {
current: 1,
size: -1,
productSkuId: '',
productSkcId: '',
isShowWhite: ''
},
lableSearch: {
current: 1,
size: 100,
mallId: ''
},
lableTotal: 0,
lableList: [],
isShow: false,
skuReqParams: {
page: 1,
pageSize: 100,
SKC: '',
SKU: ''
},
isLoading: false,
addType: '1',
props: {
value: 'catId',
label: 'catName',
multiple: true,
checkStrictly: true,
lazy: true,
lazyLoad (value, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list',
needMallId: true,
data: {
parentCatId: value.level === 0 ? '' : value.value
}
}).then(res => {
if (res.errorCode === 1000000) {
resolve(res.result.categoryNodeVOS.map(v => {
return {
...v,
leaf: v.isLeaf
}
}))
}
})
}
},
targetCatId: [],
skuList: [],
chooseSkuList: [],
id: '',
fileList: [],
pageShow: false,
relationList: [],
btnLoading: false
}
},
computed: {
list () {
if (!this.skuList.length) {
return []
}
if (this.search.isShowWhite !== '1') {
return this.skuList
}
const keys = this.relationList.map(v => v.field)
return this.skuList.filter(v => keys.some(e => !v[e]))
},
currMall () {
if (!this.$store.state.mallList.length) {
return {}
}
return this.$store.state.mallList.filter(v => v.mallId === this.lableSearch.mallId)[0]
},
colConfigs () {
const fields = this.isShow ? [] : this.relationList.map(v => {
return {
prop: v.field,
label: v.name,
align: 'center'
}
})
return [
{ type: 'selection' },
{ prop: 'mallName', label: '店铺名称', align: 'left' },
{ prop: 'productName', label: '商品名称', align: 'center' },
{ prop: 'labelCode', label: '条码编码', align: 'center' },
{ prop: 'productSkcId', label: 'SKC', align: 'center' },
{ prop: 'productSkuId', label: 'SKU', align: 'center' },
{ prop: 'skuExtCode', label: 'SKU货号', align: 'center' },
{ prop: 'skuSpecName', label: '次销售属性', align: 'center' },
...fields
]
},
jsonFields () {
const obj = {}
this.colConfigs.filter(v => !!v.prop).forEach(v => {
obj[v.label] = v.prop
})
return {
...obj
}
}
},
created () {
this.id = this.$route.query.id || ''
this.getRelation()
this.getList()
},
methods: {
toAdd () {
this.$router.push('/addLabelsTemplate')
},
getRelation () {
this.$http.post(`/api/templateRelation/getRelation?templateId=${this.$route.query.id}`).then(res => {
if (res.code === 0) {
this.relationList = res.data
}
})
},
readXLSX(file) {
return new Promise(resolve => {
const reader = new FileReader()
reader.readAsBinaryString(file)
reader.onload = evt => {
const data = evt.target.result
const workbook = XLSX.read(data, { type: 'binary' })
const ws = workbook.Sheets[workbook.SheetNames[0]]
const jsonData = XLSX.utils.sheet_to_json(ws)
resolve(jsonData)
this.fileList = []
}
})
},
onExcelChange (file) {
this.pageShow = true
this.readXLSX(file.raw).then(res => {
this.$http.post(`/api/templateSku/updateBatchSku`, res.map(v => {
const result = {
templateId: this.id
}
Object.keys(this.jsonFields).forEach(item => {
result[this.jsonFields[item]] = v[item]
})
return result
})).then(res => {
if (res.code === 0) {
this.$message.success('导入成功')
this.isShow = false
this.getList()
}
this.pageShow = false
})
})
},
onSearchRest() {
this.skuReqParams.SKC = ''
this.skuReqParams.SKU = ''
},
handleSelectionChange(e) {
this.chooseSkuList = e
},
getSKCList(catIds, page) {
return new Promise(resolve => {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/skc/pageQuery',
needMallId: true,
mallId: this.lableSearch.mallId,
anti: true,
data: {
page,
pageSize: 200,
catIds: catIds
}
}).then(res => {
if (res.errorCode == 1000000) {
resolve({
list: res.result.pageItems.map(v => v.productSkcId),
isHasNext: res.result.total && res.result.pageItems.length && (res.result.pageItems.length < 200 && res.result.total > 200)
})
} else {
resolve({ list: [], isHasNext: false })
}
}).catch(() => {
resolve({ list: [], isHasNext: false })
})
})
},
async onCateChange() {
let page = 1
let list = []
let isHasNext = true
this.lableList = []
this.isLoading = true
while (isHasNext) {
const result = await this.getSKCList([].concat(this.targetCatId.flat()), page)
page = page + 1
isHasNext = result.isHasNext ? true : false
list.push(...result.list)
await this.$sleepSync(1000)
}
const skcList = [...new Set(list)]
const len = Math.ceil(skcList.length / 100)
for (let i = 0; i < len; i++) {
this.skuReqParams.page = 1
this.skuReqParams.SKC = [...new Set(list)].slice(i * 100, i * 100 + 100).join(',')
await this.requestSKUList(true)
await this.$sleepSync(500)
}
this.isLoading = false
},
requestSKUList(flag) {
return sendChromeAPIMessage({
url: 'bg-visage-mms/labelcode/pageQuery',
needMallId: true,
mallId: this.lableSearch.mallId,
anti: true,
data: {
page: this.skuReqParams.page,
pageSize: 200,
productSkcIdList: (['2', '1'].includes(this.addType)) ? this.skuReqParams.SKC.split(',') : [],
productSkuIdList: this.addType === '3' ? this.skuReqParams.SKU.split(',') : []
}
}).then(async (res) => {
if (res.errorCode == 1000000) {
const list = res.result.pageItems.map(v => {
return {
mallId: this.lableSearch.mallId,
mallName: this.currMall.mallName,
productName: v.productName,
productSkcId: v.labelCodeVO.productSkcId,
productSkuId: v.labelCodeVO.productSkuId,
labelCode: v.labelCodeVO.labelCode,
skuExtCode: v.labelCodeVO.skuExtCode,
skuSpecName: v.productSkuSpecList.map(item => {
return item.specName
}).join(',')
}
})
this.lableTotal = res.result.total
this.lableList.push(...list)
if (res.result.total > this.lableList.length) {
this.skuReqParams.page++
await this.$sleepSync(500)
await this.requestSKUList()
} else {
!flag && (this.isLoading = false)
}
} else {
this.isLoading = false
}
})
},
getSkuList () {
if (!this.lableSearch.mallId) {
return this.$message.error('请选择店铺')
}
this.lableList = []
this.skuReqParams.page = 1
this.isLoading = true
this.requestSKUList()
},
getList () {
this.pageShow = true
this.$http.post(`/api/templateSku/getMySkuPage`, null, {
params: {
...this.search,
templateId: this.id
}
}).then(res => {
if (res.code === 0) {
this.skuList = res.data.records
}
this.pageShow = false
})
},
onConfirm () {
if (!this.chooseSkuList.length) {
return this.$message.error('请选择SKU')
}
this.btnLoading = true
this.$http.post(`/api/templateSku/addBatchSku`, this.chooseSkuList.map(v => {
return {
...v,
templateId: this.id
}
})).then(res => {
if (res.code === 0) {
this.$message.success('添加成功')
this.isShow = false
this.getList()
}
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
},
remove (id) {
this.$confirm('确定删除该数据?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post(`/api/templateSku/removeById?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功')
this.getList()
}
})
})
},
}
}
</script>
<style scoped lang="scss">
.Template {
.search-item__wrapper {
display: flex;
align-items: center;
justify-content: space-between;
&>div {
display: flex;
align-items: center;
}
}
}
</style>

View File

@@ -0,0 +1,205 @@
<template>
<ai-list class="Template">
<ai-title
slot="title"
title="模板管理"
isShowBottomBorder>
<template #rightBtn>
<div class="tips" slot="rightBtn" :class="[$store.state.labelInfo.isExpires ? 'active' : '']">
<p>有效期{{ $store.state.labelInfo.expireTime }}</p>
<p>已使用SKU数{{ $store.state.labelInfo.skuUsed }}/{{ $store.state.labelInfo.skuTotal }}</p>
</div>
</template>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<div class="search-item">
<label>SKU</label>
<el-input
v-model="search.productSkuId"
style="width: 250px"
size="small"
clearable
placeholder="请输入SKU"
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<div class="search-item">
<label>SKC</label>
<el-input
v-model="search.productSkcId"
style="width: 250px"
size="small"
placeholder="请输入SKC"
clearable
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<div class="search-item">
<label>模板名称</label>
<el-input
v-model="search.name"
style="width: 250px"
size="small"
placeholder="请输入模板名称"
clearable
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<el-button type="primary" @click="getList" size="small" :loading="isLoading">查询</el-button>
</template>
</ai-search-bar>
<ai-search-bar>
<template #left>
<el-button type="button" class="el-button el-button--primary" @click="toAdd('')">添加</el-button>
</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"
:loading="isLoading">
<el-table-column slot="options" label="操作" align="center" fixed="right" width="240px">
<template v-slot="{ row }">
<div class="table-options">
<el-button type="text" @click="toAddSku(row.id)">管理SKU</el-button>
<!-- <el-button type="text" @click="savePDF(row.id)">保存PDF</el-button> -->
<el-button type="text" @click="toAdd(row.id)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<Print ref="printRef" :isPrint="true"></Print>
</template>
</ai-list>
</template>
<script>
import Print from '@/components/print/Print'
export default {
name: 'PringTemplate',
components: {
Print
},
data () {
return {
colConfigs: [
{ prop: 'name', label: '模板名称', align: 'left' },
{ prop: 'skuTotal', label: '绑定SKU数量', align: 'center' },
{ prop: 'createTime', label: '创建时间', align: 'center' }
],
isLoading: false,
tableData: [],
total: 0,
search: {
current: 1,
size: 10,
name: '',
productSkuId: '',
productSkcId: ''
}
}
},
created () {
this.getList()
},
methods: {
toAdd (id = '') {
this.$router.push(`/addLabelsTemplate?id=${id}`)
},
savePDF (id) {
this.isLoading = true
this.$http.post(`/api/template/detail?id=${id}`).then(res => {
if (res.code === 0) {
const template = JSON.parse(res.data.content)
this.$http.post(`/api/templateSku/getMySkuPage?size=-1&templateId=${id}`).then(res => {
if (res.code === 0) {
const html = this.$refs.printRef.toPrint(template, res.data.records)
console.log(html)
this.isLoading = false
}
})
}
})
},
toAddSku (id) {
this.$router.push(`/skuManage?id=${id}`)
},
getList () {
this.isLoading = true
this.$http.post('/api/template/myTemplate', null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
this.isLoading = false
})
},
remove (id) {
this.$confirm('确定删除该模板?', '温馨提示', {
type: 'warning'
}).then(() => {
this.$http.post(`/api/template/removeById?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功')
this.getList()
}
})
})
},
onConfirm () {
}
}
}
</script>
<style scoped lang="scss">
.Template {
.search-item {
margin-bottom: 0;
}
.tips {
display: flex;
align-items: center;
color: green;
&.active {
color: red;
}
p {
font-size: 15px;
font-weight: 600;
&:first-child {
margin-right: 20px;
}
}
}
}
</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.error('两次密码输入不一致');
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>
<i class="hover" @click="$router.push('/register')">立即注册</i>
</div>
<div class="left" style="margin-left: 10px;">
<span>忘记密码</span>
<i class="hover" @click="$router.push('/forget')">找回密码</i>
</div>
</div>
</div>
</div>
<div id="kefu" @click="gotoKefu">
<label slot="reference" class="topBtn" title="联系客服"></label>
</div>
</div>
</div>
</template>
@@ -139,6 +146,9 @@
onChange (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>

View File

@@ -52,6 +52,7 @@
</template>
<script>
import CryptoJS from 'crypto-js'
export default {
data () {
return {
@@ -92,9 +93,12 @@
this.$message.success('两次密码输入不一致');
return;
}
const devVersion = require('../../manifest.development.json').version
const prodVersion = require('../../manifest.production.json').version
this.btnLoading = true
this.$http.post(`/api/malluser/reg`, {
...this.form
...this.form,
version: process.env.NODE_ENV === 'production' ? prodVersion : devVersion
}, {
headers: {
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 () {
if (this.isSend) {
@@ -126,9 +147,10 @@
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/getRegSmsCode?phone=${this.form.phone}`, {}, {
this.$http.post(`/api/sms/getRegSmsCodeNew?phone=${phone}`, {}, {
withoutToken: true
}).then(res => {
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;"
:current.sync="search.current" :size.sync="search.size"
@selection-change="handleSelectionChange"
height="700"
@getList="getList">
</ai-table>
</div>
@@ -45,7 +46,7 @@
<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-select v-model="productPage.mallId" @change="productTableData = [], productPage.total = 0, productPage.page =1, getProductOrDraftList()" placeholder="请选择">
<el-option
v-for="item in $store.state.mallList"
:key="item.mallId"
@@ -54,6 +55,21 @@
</el-option>
</el-select>
</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">
<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>
@@ -67,10 +83,11 @@
<el-button @click="productPage= {
page: 1,
pageSize: 10,
from: '0',
productName: '',
productSkcIds: ''
}, getProductList()">重置</el-button>
<el-button type="primary" @click="productPage.page =1, getProductList()">查询</el-button>
}, getProductOrDraftList()">重置</el-button>
<el-button type="primary" @click="productPage.page =1, getProductOrDraftList()">查询</el-button>
</template>
</ai-search-bar>
<ai-table
@@ -80,7 +97,7 @@
:current.sync="productPage.page" :size.sync="productPage.pageSize"
style="margin-top: 8px;"
@selection-change="productHandleSelectionChange"
@getList="getProductList">
@getList="getProductOrDraftList">
</ai-table>
</div>
</template>
@@ -98,19 +115,51 @@
width="790px"
customFooter
@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-select style="width: 380px" v-model="form.targetMallId" placeholder="请选择">
<el-select multiple style="width: 380px" v-model="form.targetMallId" placeholder="请选择">
<el-option
v-for="item in $store.state.mallList"
v-for="item in 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 v-if="form.isSemi" label="经营站点" style="width: 100%;" prop="siteId" :rules="[{ required: true, message: '请选择经营站点', trigger: 'blur' }]">
<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>
<div class="dialog-footer" slot="footer">
@@ -126,10 +175,11 @@ import {sendChromeAPIMessage} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import {transform} from '@/utils/product'
import { Message } from 'element-ui'
import AiLazyCascader from "@/components/AiLazyCascader.vue"
export default {
name: 'CopyProduct',
components: {AiLazyCascader},
data () {
return {
search: {
@@ -144,12 +194,12 @@ import { Message } from 'element-ui'
value: 'catId',
label: 'catName',
lazy: true,
lazyLoad (node, resolve) {
lazyLoad (value, resolve) {
sendChromeAPIMessage({
url: 'bg-anniston-mms/category/children/list',
needMallId: true,
data: {
parentCatId: node.value || ''
parentCatId: value || ''
}
}).then(res => {
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: [
@@ -176,7 +252,7 @@ import { Message } from 'element-ui'
dlgShow: false,
productTableData: [],
productPage: {page: 1, pageSize: 10, mallId: '', productName: '', productSkcIds: '', total: 0},
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', align: 'left' },
@@ -189,13 +265,27 @@ import { Message } from 'element-ui'
productIds: [],
form: {
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 () {
this.getList()
this.getSiteList()
if (this.$store.state.mallList.length > 0) {
this.productPage.mallId = this.$store.state.mallList[0].mallId
}
@@ -203,7 +293,7 @@ import { Message } from 'element-ui'
methods: {
getList () {
this.$http.post('/api/product/myPage',null,{
this.$http.post('/api/product/myPage?type=0',null,{
params: {
...this.search
}
@@ -212,6 +302,19 @@ import { Message } from 'element-ui'
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 () {
if (this.ids.length <= 0) {
alert('请选择要删除的商品');
@@ -239,7 +342,7 @@ import { Message } from 'element-ui'
// 添加模板
handleClose() {
this.productTableData = []
this.productPage = {page: 1, pageSize: 10, total: 0}
this.productPage = {page: 1, pageSize: 10, from: '0', total: 0}
this.dlgShow = false
},
toAddTemplate() {
@@ -252,10 +355,46 @@ import { Message } from 'element-ui'
return;
}
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() {
let params = {};
params.page = this.productPage.page;
@@ -301,11 +440,15 @@ import { Message } from 'element-ui'
}
this.productIds.map((productSpu, index) => {
setTimeout(() => {
this.saveTemplate(productSpu, index)
if (this.productPage.from == '0') {
this.saveProductTemplate(productSpu, index)
} else {
this.saveDraftTemplate(productSpu, index)
}
}, 200 * index)
})
},
saveTemplate(spu, index) {
saveProductTemplate(spu, index) {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/query',
needMallId: true,
@@ -324,6 +467,7 @@ import { Message } from 'element-ui'
mallName: mallInfo[0].mallName,
productSpu: res.result.productId,
productName: res.result.productName,
type: 0,
content: content
}).then(res1 => {
if (res1.code == 0) {
@@ -334,7 +478,40 @@ import { Message } from 'element-ui'
}
})
} 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() {
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.beginAddToDraft()
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,269 @@
<template>
<ai-list class="list" v-loading="isLoading" :element-loading-text="loadingText" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="拒绝调价"
isShowBottomBorder>
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #right>
<el-button type="primary" @click="toLoad">加载</el-button>
<el-button type="primary" @click="beforeRejct">拒绝调价</el-button>
</template>
</ai-search-bar>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<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>
<AiDialog
title="拒绝原因"
:visible.sync="rejectShow"
:close-on-click-modal="false"
customFooter
height="500px"
width="500px">
<el-form :model="rejectForm" ref="rejectForm" class="form">
<el-form-item
prop="reason"
label="拒绝原因:"
:rules="[{ required: true, message: '请输入拒绝原因', trigger: 'blur' }]">
<el-input :rows="3" placeholder="请输入拒绝原因" type="textarea" v-model="rejectForm.reason"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="rejectShow = false"> </el-button>
<el-button type="primary" @click="toReject">拒绝</el-button>
</span>
</AiDialog>
</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: 'PriceDown',
data () {
return {
reqData: {
pageInfo: {
pageSize: 100,
pageNo: 1
},
status: 1
},
colConfigs: [
{ slot: 'productName', label: '商品名称', width: '250px', align: 'left', fixed: 'left' },
{ prop: 'mallName', 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: 'priceBeforeExchange', label: '原申报价格', width: '100px', align: 'left' },
{ prop: 'newSupplyPrice', label: '调整后申报价格', width: '100px', align: 'left' },
{ prop: 'source', label: '申请来源', width: '120px', align: 'left' },
{ prop: 'adjustReason', label: '调价原因', align: 'left' }
],
loadingText: '',
isLoading: false,
tableData: [],
currentIndex: 0,
rejectForm: {
reason: ''
},
rejectShow: false
}
},
components: {
JsonExcel
},
created () {
},
methods: {
beforeGetList() {
this.$userCheck().then(() => {
this.toLoad()
}).catch((err) => {
})
},
async toLoad() {
if (this.$store.state.mallList.length == 0) {
Message.error("请登录卖家中心,如已登录,请刷新助手")
return
}
this.reqData.pageInfo.pageNo = this.startPage
this.tableData = []
this.currentIndex = 0
this.isLoading = true
for (let i = 0; i < this.$store.state.mallList.length; i++) {
this.reqData.pageInfo.pageNo = 1
this.loadingText = `正在加载店铺【${this.$store.state.mallList[i].mallName}】的调价信息`
await this.load(this.$store.state.mallList[i])
}
this.isLoading = false
},
async load(mallObj) {
let res = await sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/magneto/price-adjust/page-query',
needMallId: true,
mallId: mallObj.mallId,
anti: true,
data: this.reqData})
if (res.errorCode == 1000000 && res.result.total > 0) {
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.mallName = mallObj.mallName
data.mallId = mallObj.mallId
data.adjustReason = item.adjustReason
data.newSupplyPrice = item.newSupplyPrice / 100
data.num = item.skuInfoItemList.length
let skus = []
item.skuInfoItemList.map(item => {
skus.push(item.productSkuId)
})
data.skus = skus.join(',')
/*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
}
this.tableData.push(data)
}*/
this.tableData.push(data)
}
if (res.result.list.length == this.reqData.pageInfo.pageSize) {
this.reqData.pageInfo.pageNo ++
await this.load(mallObj)
}
}
},
beforeRejct() {
if (this.tableData.length <= 0) {
Message.error('请先加载待拒绝调价');
return;
}
this.rejectShow = true
},
async toReject() {
this.$refs.rejectForm.validate(async (valid) => {
if (valid) {
this.loadingText = '正在拼命处理中~'
this.isLoading = true
this.rejectShow = false
await this.reject()
this.isLoading = false
}
})
},
async reject() {
let rejectIds = []
for (let i = 0; i < this.tableData.length; i++) {
rejectIds.push(this.tableData[i].id)
}
for (let j = 0; j < rejectIds.length; j++) {
let obj = this.tableData.filter(item => {
return item.id == rejectIds[j]
})
if (obj.length == 0) continue
obj = this.tableData.filter(item => {
return item.skus == obj[0].skus
})
let params = {}
if (obj.length > 1) {
params.adjustId = obj[0].id
params.adjustResultMap = {}
for (let k = 0; k < obj.length; k++) {
params.adjustResultMap[obj[k].id] = 2
}
params.result = 2
} else {
params = {
adjustId: obj[0].id,
reason: this.rejectForm.reason,
result: 2
}
}
// console.log(obj[0].mallName, params)
let res = await sendChromeAPIMessage({
url: 'gmp/bg/magneto/api/price/purchase-adjust/review',
needMallId: true,
mallId: obj[0].mallId,
anti: true,
data: params})
if (res.success) {
this.removeByRejectId(obj)
// this.remove(skuIds[j])
}
await this.$sleepSync(300)
}
},
removeByRejectId(objArr) {
for (let j = 0; j < objArr.length; j++) {
for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i].id == objArr[j].id) {
this.tableData.splice(i, 1)
break
}
}
}
}
}
}
</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
slot="title"
title="销售数据"
tips="请事先在“成本管理”维护好成本,否则部分计算不准确"
isShowBottomBorder>
<template #rightBtn>
<div class="title-right">
@@ -38,53 +39,96 @@
</el-select>
</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>
</ai-title>
<template slot="content" v-if="type === '0'">
<div class="top">
<div class="item">
<h2>今日销量</h2>
<p>{{ todayTotal }}</p>
<h2>今日销量/销售额</h2>
<p>{{ todayTotal }}/{{ todayMoney }}</p>
</div>
<div class="item">
<h2>今日销售额</h2>
<p>{{ todayMoney }}</p>
<h2>今日利润/利润率</h2>
<p>{{ profitMoney }}/{{ profitPercent }}</p>
</div>
<div class="item">
<h2>库存总量</h2>
<h2>可用库存总量()</h2>
<p>{{ inventoryTotal }}</p>
</div>
<div class="item">
<h2>库存总额</h2>
<p>{{ inventoryMoeny }}</p>
<h2>可用库存总额</h2>
<p>{{ inventoryMoney }}</p>
</div>
<div class="item">
<h2>已收货总量</h2>
<p>{{ deliveryTotal }}</p>
<h2><span>在途库存总量()<el-tooltip
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 class="item">
<h2>已收货总货值</h2>
<p>{{ deliveryMoeny }}</p>
<h2><span>在途库存总额<el-tooltip
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>
<ai-card title="数据明细" style="padding-bottom: 40px;">
<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
:data="list"
v-if="type === '0'"
:fields="jsonFields"
:before-generate = "startDownload"
name="销售数据.xls"
worksheet="销售统计">
<el-button type="primary">导出数据</el-button>
:data="skuSaleNumberList"
v-show="false"
:fields="skuSaleNumberFields"
:before-generate = "startSkuSaleNumberDownload"
name="SKU历史销量.xls"
worksheet="SKU历史销量">
<el-button type="primary" id="downloadSkuSaleNumber"></el-button>
</json-excel>
<el-button type="primary" :disabled="!mallId" @click="toDownload">导出SKU历史销量</el-button>
</template>
<ai-table
ref="table0"
:isShowPagination="false"
:tableData="list"
height="500"
:tableData="filteredData"
height="700"
:col-configs="colConfigs"
:total="list.length"
:total="filteredData.length"
style="margin-top: 8px;"
@getList="() => {}">
</ai-table>
@@ -104,6 +148,7 @@
</json-excel>
</template>
<ai-table
ref="table1"
:isShowPagination="false"
:tableData="last30DaySkuList"
height="500"
@@ -128,6 +173,7 @@
</json-excel>
</template>
<ai-table
ref="table2"
:isShowPagination="false"
:tableData="last30DaySkcList"
height="500"
@@ -138,6 +184,8 @@
</ai-table>
</ai-card>
</template>
</ai-list>
</template>
@@ -147,6 +195,8 @@ import {sendChromeAPIMessage} from '@/api/chromeApi'
import JsonExcel from 'vue-json-excel'
import {formatDate} from '@/utils/date'
import { Message } from 'element-ui'
import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
export default {
name: 'ExportSaleData',
@@ -160,73 +210,43 @@ import { Message } from 'element-ui'
mallName: '',
type: '0',
isLoading: false,
pageSize: 100,
pageSize: 50,
currentPage: 1,
todayTotal: 0,
todayMoney: 0.0,
profitMoney: 0.0,
profitPercent: 0,
inventoryTotal: 0,
inventoryMoeny: 0.0,
deliveryTotal: 0,
deliveryMoeny: 0.0,
allProductList: [],
inventoryMoney: 0.0,
inroadTotal: 0,
inroadTotalMoney: 0.0,
costList: [],
startDate: '',
endDate: '',
skuIds: [],
jsonFields: {
"商品名称": "productName",
"店铺名称": "mallName",
"评分": "mark",
"SPU": "productId",
"SKC": "productSkcId",
"SKU ID": "productSkuId",
"SKU属性": "className",
"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",
}
skuSaleNumberFields: {},
skuSaleNumberList: [],
downloadSkuSaleNumberDlg: false,
skuDownloadForm: {
date: ''
},
showTodaySale: false
}
},
computed: {
...mapState(['mallList']),
filteredData() {
const filteredData = this.list.filter(item => {
if (this.showTodaySale) {
return item.todaySaleVolume > 0
}
return true
})
return filteredData
},
colConfigs () {
return [
{
@@ -234,14 +254,16 @@ import { Message } from 'element-ui'
label: '商品名称',
"show-overflow-tooltip": true,
width: '280px',
align: 'left'
align: 'left',
fixed: 'left'
},
{
prop: 'mallName',
label: '店铺名称',
"show-overflow-tooltip": true,
width: '120px',
align: 'left'
align: 'left',
fixed: 'left'
},
{
prop: 'mark',
@@ -250,19 +272,29 @@ import { Message } from 'element-ui'
width: '80px',
align: 'left'
},
{
prop: 'hotTag',
label: '是否热销',
"show-overflow-tooltip": true,
width: '80px',
align: 'left'
},
{
prop: 'productId',
label: 'SPU',
width: '120px',
align: 'center'
},
{
prop: 'productSkcId',
label: 'SKC',
width: '120px',
align: 'center'
},
{
prop: 'productSkuId',
label: 'SKU ID',
width: '120px',
align: 'center'
},
{
@@ -273,22 +305,78 @@ import { Message } from 'element-ui'
{
prop: 'skuExtCode',
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'
},
{
prop: 'onSalesDurationOffline',
label: '加入站点时长',
width: '160px',
align: 'center'
},
{
prop: 'isVerifyPrice',
label: '开款核价状态',
width: '160px',
align: 'center',
format: v => v ? '核价通过': '核价未通过 / 无法备货'
},
{
prop: 'supplierPrice',
label: '申报价格(CNY)',
width: '160px',
align: 'center',
format: v => v / 100,
fixed: "right"
@@ -296,8 +384,8 @@ import { Message } from 'element-ui'
{
prop: 'warehouseInventoryNum',
label: '仓内可用库存',
width: '160px',
align: 'center',
fixed: "right",
sortable: true,
'sort-method': (a, b) => {
return a.warehouseInventoryNum - b.warehouseInventoryNum
@@ -305,7 +393,8 @@ import { Message } from 'element-ui'
},
{
prop: 'productTotalPrice',
label: '库存货值(CNY)',
label: '可用库存货值(CNY)',
width: '180px',
align: 'center',
fixed: "right",
sortable: true,
@@ -317,6 +406,7 @@ import { Message } from 'element-ui'
prop: 'purchaseConfig',
label: '备货逻辑',
align: 'center',
width: '120px',
fixed: "right",
sortable: true,
'sort-method': (a, b) => {
@@ -328,7 +418,7 @@ import { Message } from 'element-ui'
return -1
}
}
},
}
]
@@ -457,6 +547,13 @@ import { Message } from 'element-ui'
align: 'center',
fixed: 'left'
},
{
prop: 'supplierPrice',
label: '申报价格',
width: '120px',
align: 'center',
fixed: 'left'
},
{
prop: 'productSkuId',
label: 'SKU ID',
@@ -506,6 +603,7 @@ import { Message } from 'element-ui'
"SKC货号": "skcExtCode",
"SKU ID": "productSkuId",
"SKU货号": "skuExtCode",
"申报价格": "supplierPrice",
"图片链接": "productSkcPicture"
}
@@ -535,20 +633,22 @@ import { Message } from 'element-ui'
methods: {
changeDataType() {
this.$nextTick(() => { //
this.$refs['table'+this.type].doLayout();
})
},
beforeGetList() {
this.list = []
this.currentPage = 1
this.todayMoney = 0.0
this.todayTotal = 0
this.inventoryMoeny = 0.0
this.profitMoney = 0.0
this.profitPercent = 0
this.inventoryMoney = 0.0
this.inventoryTotal = 0
this.deliveryTotal = 0
this.deliveryMoeny = 0.0
this.allProductList = []
this.inroadTotalMoney = 0.0
this.inroadTotal = 0
if (!this.mallId) {
Message.error("请先选择店铺")
return
}
@@ -560,34 +660,30 @@ import { Message } from 'element-ui'
this.isLoading = true
this.$userCheck(this.mallId).then(() => {
this.last30DaySkcList = []
this.getAllProductList()
this.getSkuCostList()
this.getList()
}).catch((err) => {
this.mallId = ''
console.log(err)
this.isLoading = false
})
},
getAllProductList() {
this.$http.post('/api/deliveryOrder/totalDelivery',null, {
params: {mallId: this.mallId}
}).then(res => {
if (res.code === 0) {
this.deliveryTotal = res.data
getSkuCostList() {
this.$http.post(`/api/skuCost/listAll`, null, {
params: {
mallId: this.mallId
}
})
this.$http.post('/api/deliveryOrder/getAllProductList', null, {
params: {mallId: this.mallId}
}).then(res => {
if (res.code == 0) {
this.allProductList = res.data
this.costList = res.data
}
})
},
getList () {
sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sales/management/list',
url: 'marvel-mms/cn/api/kiana/venom/sales/management/listWarehouse',
needMallId: true,
anti: true,
mallId: this.mallId,
data: {
pageNo: this.currentPage,
@@ -600,12 +696,14 @@ import { Message } from 'element-ui'
let item = res.result.subOrderList[i];
let data = {};
data.productName = item.productName;
data.category = item.category;
data.productId = item.productId;
data.productSkcId = item.productSkcId;
data.skcExtCode = item.skcExtCode;
data.purchaseConfig = item.purchaseConfig;
data.productSkcPicture = item.productSkcPicture;
data.mark = item.mark.toFixed(1)
data.hotTag = item.hotTag ? '是': '否'
data.mallName = this.mallName;
this.last30DaySkcList.push({
@@ -625,26 +723,30 @@ import { Message } from 'element-ui'
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],
saleMoney: saleMoney,
profitMoney: profitMoney,
profitPercent: profitPercent,
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}
this.todayTotal += item.skuQuantityDetailList[j].todaySaleVolume
this.todayMoney += new Number(((item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].todaySaleVolume).toFixed(2))
this.todayMoney = new Number(this.todayMoney.toFixed(2))
this.inventoryTotal += item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum
this.inventoryMoeny += new Number(((item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum).toFixed(2))
this.inventoryMoeny = new Number(this.inventoryMoeny.toFixed(2))
this.list.push(data);
//
for(let k = 0; k < this.allProductList.length; k++) {
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))
}
}
this.todayTotal += item.skuQuantityDetailList[j].todaySaleVolume
this.profitMoney += item.skuQuantityDetailList[j].todaySaleVolume * (item.skuQuantityDetailList[j].supplierPrice / 100 - costPrice)
this.todayMoney += (item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].todaySaleVolume
this.inventoryTotal += item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum
this.inventoryMoney += (item.skuQuantityDetailList[j].supplierPrice / 100) * item.skuQuantityDetailList[j].inventoryNumInfo.warehouseInventoryNum
this.inroadTotal += (item.skuQuantityDetailList[j].inventoryNumInfo.waitOnShelfNum + item.skuQuantityDetailList[j].inventoryNumInfo.waitReceiveNum)
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 || '-'
this.list.push(data);
}
}
if (this.pageSize == res.result.subOrderList.length) {
this.currentPage ++
@@ -652,6 +754,12 @@ import { Message } from 'element-ui'
this.getList()
}, 1500)
} 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
Message.success('销售数据加载完成,可进行导出')
@@ -677,7 +785,8 @@ import { Message } from 'element-ui'
productSkuId: item.productSkuId,
skuExtCode: item.skuExtCode,
skcExtCode: item.skcExtCode,
productSkcPicture: item.productSkcPicture
productSkcPicture: item.productSkcPicture,
supplierPrice: this.getSupplierPrice(item.productSkuId)
}
let date = new Date()
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,889 @@
<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].saleCount = this.skcList[i].saleCount + item.saleCount
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({ptransId: item.ptransId, chargeType: item.chargeType} )
}
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: this.transList[i].chargeType,
pageSize: 100,
pageNum: 1,
ptransId: this.transList[i].ptransId
}})
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>

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