版本迭代

This commit is contained in:
liuye
2023-08-06 16:50:17 +08:00
parent 590fb406cc
commit 5c0b1afe69
63 changed files with 14131 additions and 40 deletions

31
.gitignore vendored
View File

@@ -1,9 +1,24 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
.DS_Store
node_modules
/dist
/artifacts
package-lock.json
# Local History for Visual Studio Code
.history/
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,37 +1,24 @@
# temu-plugin
# demo
#### 介绍
TEMU助手
## Project setup
```
yarn install
```
#### 软件架构
软件架构说明
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
#### 安装教程
### Lints and fixes files
```
yarn lint
```
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

19
jsconfig.json Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

56
package.json Normal file
View File

@@ -0,0 +1,56 @@
{
"name": "pdd",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"build-watch": "vue-cli-service --env.NODE_ENV=development build-watch --mode development"
},
"dependencies": {
"@antv/g2plot": "^2.4.31",
"axios": "^1.4.0",
"core-js": "^3.8.3",
"dayjs": "^1.11.9",
"element-ui": "^2.15.13",
"vue": "^2.6.14",
"vue-json-excel": "^0.3.0",
"vue-router": "^3.2.0",
"vuex": "^3.4.0",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"sass": "^1.62.1",
"sass-loader": "7.2.0",
"vue-cli-plugin-chrome-extension-cli": "~1.1.4",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {
"root": true,
"env": {
"node": true,
"webextensions": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
public/favicon_32.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/icons/128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
public/icons/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

BIN
public/icons/48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

17
public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!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_32.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>
</html>

29
src/api/chromeApi.js Normal file
View File

@@ -0,0 +1,29 @@
import store from '@/store'
/**
* 向Chrome发送消息
* @param message 消息
*/
export function sendChromeAPIMessage(message) {
message.type = 'api'
message.url = "https://kuajing.pinduoduo.com/" + message.url;
if (message.needMallId) {
message.mallId = store.state.mallId;
}
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
}
/**
* 向Chrome发送消息
* @param message 消息
*/
export function sendChromeNotification(message) {
message.type = 'notify'
return new Promise((resolve) => {
// @ts-ignore
chrome.runtime.sendMessage(message, resolve)
})
}

42
src/api/index.js Normal file
View File

@@ -0,0 +1,42 @@
import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
var instance = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? 'http://temu.jjcp52.com' : 'http://temu.jjcp52.com',
timeout: 50000,
validateStatus: function (status) {
return status < 500
}
})
instance.interceptors.request.use(request => {
if (!request.withoutToken && !request.headers.Authorization) {
request.headers.Authorization = store.state.token || ''
}
return request
})
instance.interceptors.response.use(
(response) => {
if (response.data.code === 401 || response.data.code === 403) {
Message.error('登录失效,请重新登录')
store.commit('SignOut', true)
return response.data
} else if (response.data.code === 1 && response.data.msg) {
Message.error(response.data.msg)
} else {
return response.data
}
},
(err) => {
console.log(err)
Message.error('后台报错啦!请联系管理员')
return err
}
)
export default instance

View File

@@ -0,0 +1,36 @@
$--color-primary: #1FBAD6;
$borderColor: #DCDFE6 !default;
$--color-danger: #FA5555 !default;
$--button-active-shade-percent: 20% !default;
$--button-hover-tint-percent: 50% !default;
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
.el-form {
font-family: SJsuqian;
}
// .el-button:focus {
// background: #FFFFFF;
// border: 1px solid #DCDFE6;
// }
.el-button.el-button--primary.is-link:not(.is-disabled):focus {
color:var(--el-color-primary)!important;
}
// .el-button.el-button--warning.is-link:not(.is-disabled):focus, .el-button.el-button--warning.is-link:not(.is-disabled):hover{
// color:var(--el-color-warning)!important;
// }
// .el-button.el-button--success.is-link:not(.is-disabled):focus, .el-button.el-button--success.is-link:not(.is-disabled):hover{
// color:var(--el-color-success)!important;
// }
// .el-button.el-button--info.is-link:not(.is-disabled):focus, .el-button.el-button--info.is-link:not(.is-disabled):hover{
// color:var(--el-color-info)!important;
// }
.el-button.el-button--danger.is-link:not(.is-disabled):focus, .el-button.el-button--danger.is-link:not(.is-disabled):hover{
color:var(--el-color-danger)!important;
}

450
src/assets/css/index.scss Normal file
View File

@@ -0,0 +1,450 @@
@font-face {
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');
}
.iconfont{
font-family: "iconfont"!important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
}
html {
line-height: 1; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
body {
margin: 0;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
hr {
-webkit-box-sizing: content-box;
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
a {
background-color: transparent;
}
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted; /* 2 */
}
b,
strong {
font-weight: bolder;
}
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
img {
border-style: none;
}
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
button,
input { /* 1 */
overflow: visible;
font-family: SJsuqian;
}
input::placeholder {
color: #B3B3B3;
font-size: 14px;
font-family: SJsuqian;
}
button,
select { /* 1 */
text-transform: none;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
padding: 0.35em 0.75em 0.625em;
}
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 */
}
progress {
vertical-align: baseline;
}
textarea {
overflow: auto;
}
[type="checkbox"],
[type="radio"] {
-webkit-box-sizing: border-box;
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
details {
display: block;
}
summary {
display: list-item;
}
template {
display: none;
}
[hidden] {
display: none;
}
h1, h2, h3, h4 {
font-weight: normal;
font-size: 14px;
padding: 0;
margin: 0;
}
p {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
-webkit-transition: all .3s ease;
transition: all .3s ease;
}
ul, li {
list-style: none;
margin: 0;
padding: 0;
}
i {
font-style: normal;
}
img {
vertical-align: middle;
}
html, body {
width: 100%;
min-height: 100vh;
}
body {
position: relative;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 14px;
color: #000;
background: #fff;
}
* {
box-sizing: border-box;
}
img {
-webkit-user-drag: none;
}
.middle-container__item {
overflow: hidden;
}
.flex {
display: flex;
}
.flex-align {
display: flex;
align-items: center;
}
.flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex-justify_center {
display: flex;
align-items: center;
justify-content: center;
&.active-wrapper {
img {
left: inherit;
}
}
}
.flex1 {
flex: 1;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .3s ease-in-out;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wrapper {
min-height: calc(100vh - 308px);
padding-top: 158px;
.wrapper-content {
width: 1280px;
margin: 0 auto;
}
}
.hover {
cursor: pointer;
transition: all ease-in-out .5s;
&:hover {
opacity: 0.4;
}
}
.el-button {
transition: all ease .5s;
}
.el-button--primary:focus {
background: #1FBAD6!important;
border-color: #1FBAD6!important;
}
.link-hover {
cursor: pointer;
transition: all ease .5s;
&:hover {
color: #1FBAD6!important;
}
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
height: 200px;
}
.info-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
.info-item {
display: flex;
align-items: center;
width: 50%;
line-height: 17px;
margin-bottom: 14px;
font-size: 14px;
font-family: SJsuqian;;
label {
margin-right: 6px;
color: #666666;
}
span {
color: #000;
}
}
}
.el-pagination {
margin-top: 40px;
text-align: center;
}
.text-disabled {
color: #bbb!important;
cursor: not-allowed!important;
}
.btn-disabled {
opacity: 0.3;
cursor: not-allowed;
&:hover {
opacity: 0.3;
}
}
.btn-disabled1 {
opacity: 0.3;
background: #fff;
cursor: not-allowed;
&:hover {
opacity: 0.3;
color: #1FBAD6!important;
background: #fff!important;
}
}
.el-button--primary {
color: #fff!important;
border-color: #1FBAD6;
background-color: #1FBAD6;
}
.el-button--danger:focus, .el-button.el-button--danger.is-link:not(.is-disabled):hover{
color: #fff!important;
border-color: #FA5555;
background-color: #FA5555;
}
.el-input-group__append .el-button {
background-color: #1FBAD6!important;
}
.admin-main {
.left {
&.el-scrollbar .el-scrollbar__wrap {
overflow-x: hidden;
}
}
}
.admin-main .el-submenu__title i {
color: #fff;
}
.el-menu--inline .el-menu-item {
padding-left: 55px!important;
}
.el-menu {
border: none!important;
}
.el-menu .el-menu-item i {
color: #fff;
}

1028
src/assets/css/var.scss Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="160px" height="160px" viewBox="0 0 160 160" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 58 (84663) - https://sketch.com -->
<title>DefaultPage/NoData@2x</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="-1.11022302e-14%" y1="50%" x2="100%" y2="50%" id="linearGradient-1">
<stop stop-color="#EEEEEE" stop-opacity="0" offset="0%"></stop>
<stop stop-color="#CFCFCF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-2">
<stop stop-color="#F2F4F8" offset="0%"></stop>
<stop stop-color="#A0A0A0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="20.244898%" y1="66.7940946%" x2="79.755102%" y2="100%" id="linearGradient-3">
<stop stop-color="#E7E7E7" offset="0%"></stop>
<stop stop-color="#F2F2F2" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-4">
<stop stop-color="#ECECEC" offset="0%"></stop>
<stop stop-color="#FFFFFF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="87.1018058%" y1="60.9391259%" x2="0%" y2="51.9656779%" id="linearGradient-5">
<stop stop-color="#EEEEEE" offset="0%"></stop>
<stop stop-color="#D8D8D8" offset="100%"></stop>
</linearGradient>
<linearGradient x1="68.22872%" y1="123.238849%" x2="33.166207%" y2="22.0385879%" id="linearGradient-6">
<stop stop-color="#F5F5F5" offset="0%"></stop>
<stop stop-color="#D8D8D8" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="linearGradient-7">
<stop stop-color="#F2F2F2" offset="0%"></stop>
<stop stop-color="#D6D6D6" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0%" y1="39.2801795%" x2="16.8660114%" y2="70.0555556%" id="linearGradient-8">
<stop stop-color="#E9E9E9" offset="0%"></stop>
<stop stop-color="#F0F0F0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-9">
<stop stop-color="#5777D4" offset="0%"></stop>
<stop stop-color="#101B3E" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="81.4402452%" y2="56.408967%" id="linearGradient-10">
<stop stop-color="#164898" offset="0%"></stop>
<stop stop-color="#223762" offset="100%"></stop>
</linearGradient>
<path d="M0.692684439,9.43167978 C1.038713,9.68406083 1.46715785,9.03996696 1.92500086,8.75189963 C2.82917603,8.1830074 3.05602226,6.14182671 2.99566429,6.00418021 C2.86370082,5.70323714 1.63363165,4.13284399 1.75185981,3.629798 C1.87008797,3.12675201 2.75996609,4.2529312 2.71853333,3.9450838 C2.67710057,3.63723639 1.65860147,2.14944807 2.03173003,1.61764841 C2.4048586,1.08584875 3.20874322,0.781310852 3.46157268,1.61764841 C4.0498272,1.9917884 4.68398473,1.05714111 5.01137544,0.781310852 C5.33876615,0.505480598 6.04721636,0.06510516 6.81229001,0.06510516 C7.57736365,0.06510516 8.5052774,0.131064853 8.52936011,0.310000564 C8.55344283,0.488936275 9.85049474,3.58207339 9.42444215,3.629798 C8.99838956,3.67752261 8.55839311,3.84551454 8.52936011,3.629798 C8.50032712,3.41408147 8.52395619,5.5702089 8.00031054,6.16034326 C7.59261269,6.61980761 6.90464724,6.61980761 6.81229001,6.70051253 C6.92864366,6.92703467 8.00682266,10.9089185 9.42444215,10.9099572 C10.8420616,10.910996 10.5339947,16.4526524 8.52936011,15.7958809 C7.0572883,15.313591 4.70544072,14.5952614 1.47381736,13.640892 C-0.140677622,11.7612238 -0.401055261,10.3968483 0.692684439,9.54776539 C0.745126983,9.50705363 0.301852492,9.58449006 0.355223454,9.54776539 L0.692684439,9.43167978 Z" id="path-11"></path>
<linearGradient x1="81.2218693%" y1="3.46944695e-16%" x2="18.7781307%" y2="105.693594%" id="linearGradient-13">
<stop stop-color="#0074FF" offset="0%"></stop>
<stop stop-color="#009BFF" offset="100%"></stop>
</linearGradient>
</defs>
<g id="DefaultPage/NoData" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M87,133 C32.3333333,129.212407 5,123.679002 5,116.399786 C5,105.480961 56.1964855,98.5697269 59.4353967,101.797298 C61.5946709,103.949012 70.7828719,114.349912 87,133 Z" id="路径-10" fill="url(#linearGradient-1)"></path>
<g id="编组-6" transform="translate(25.000000, 55.000000)">
<polygon id="矩形" fill="url(#linearGradient-2)" opacity="0.600000024" points="8 8 62 -8.79504802e-15 62 78 8 55"></polygon>
<polygon id="矩形" fill="url(#linearGradient-3)" points="8 8 62 14 62 78 8 55"></polygon>
<polygon id="矩形" fill="url(#linearGradient-4)" points="14 14 56 23 56 67 14 50"></polygon>
<polygon id="矩形" fill="url(#linearGradient-5)" points="8 8 62 14 54 37 -2.27366737e-13 25"></polygon>
<polygon id="矩形" fill="url(#linearGradient-6)" points="112 5 62 -8.79504802e-15 62 78 112 55"></polygon>
<polygon id="矩形" fill="url(#linearGradient-7)" points="112 5 62 14 62 78 112 55"></polygon>
<polygon id="矩形" fill="url(#linearGradient-8)" points="112 5 62 14 72 43 122 25"></polygon>
</g>
<g id="编组-8" transform="translate(99.000000, 29.000000)">
<path d="M7.01014176,64 C5.81563314,64 5.39853322,64.9231563 5.98625682,67.1419148 C6.57398041,69.3606732 6.57398041,73.099898 8.21442955,73.6840461 C9.85487869,74.2681943 10.2675495,72.4172628 10.0306673,70.2729153 C9.79378519,68.1285679 10.5969285,64.8486097 10.0306673,64.8486097 C9.46440621,64.8486097 8.20465038,64 7.01014176,64 Z" id="路径-23" fill="#27234B"></path>
<path d="M8.67858722,60.8068485 C7.57745052,63.7962121 7.02688217,65.515603 7.02688217,65.9650211 C7.02688217,66.6391482 8.14770042,67.9959336 8.67858722,67.7290608 C9.03251176,67.5511456 9.82225562,65.4781591 11.0478188,61.5101011 L8.67858722,60.8068485 Z" id="路径-24" fill="#FFD6A7"></path>
<path d="M17.6525712,58.3128036 C16.4580626,58.3128036 15.5791956,59.5723043 16.1669192,61.7910627 C16.7546428,64.0098211 17.1206498,67.1792185 18.7610989,67.7633667 C20.4015481,68.3475148 20.5184284,66.429455 20.2815463,64.2851076 C20.0446641,62.1407602 20.4116365,58.7211343 19.8453754,58.7211343 C19.2791143,58.7211343 18.8470799,58.3128036 17.6525712,58.3128036 Z" id="路径-23" fill="#27234B"></path>
<path d="M19.0373505,54.8345445 C17.9362138,57.8239082 17.3856455,59.5432991 17.3856455,59.9927171 C17.3856455,60.6668442 18.5064637,62.0236296 19.0373505,61.7567569 C19.391275,61.5788417 20.1810189,59.5058551 21.4065821,55.5377972 L19.0373505,54.8345445 Z" id="路径-24" fill="#FFD6A7"></path>
<path d="M6.83909623,33.9320553 C6.05741636,38.4828718 11.5516213,43.9593332 11.5516213,47.2130169 C11.5516213,50.4667006 6.71818349,62.5891228 7.18189188,63.8904979 C7.64560028,65.1918731 9.53550857,66.7841621 10.2070863,65.7711218 C10.8786639,64.7580814 18.8563568,48.1367918 18.8563568,45.6549977 C18.8563568,43.1732035 15.8102723,39.9517455 16.595327,39.1690119 C17.3803817,38.3862783 21.1620037,42.8944111 21.1620037,44.4578151 C21.1620037,46.021219 17.6522108,56.7723936 18.0269429,57.9559074 C18.401675,59.1394212 20.3181472,59.7449758 20.8053247,58.9477997 C21.2925022,58.1506236 28.5668954,45.0375795 27.8295527,42.1054879 C27.0922099,39.1733962 21.2623277,32.5968868 19.8162693,32.5968868 C18.3702109,32.5968868 7.6207761,29.3812388 6.83909623,33.9320553 Z" id="路径-20" fill="url(#linearGradient-9)"></path>
<g id="编组-7" transform="translate(8.722445, 0.000000)">
<path d="M4.30855025,10.9942019 C3.35685956,10.810352 1.77386943,8.86553992 1.18202581,7.80054593 C0.81387515,7.13807662 0,5.96438949 0,4.61947329 C0,3.27455708 1.10573563,1.77660133 1.92584455,2.48051045 C1.92584455,0.20260789 7.956092,-0.67811348 9.65271137,0.566356728 C11.3493307,1.81082694 11.5461677,4.61947329 9.65271137,4.61947329 C7.75925506,4.61947329 8.65716044,5.86566186 6.49698059,6.39903511 C4.33680074,6.93240835 5.26024095,11.1780519 4.30855025,10.9942019 Z" id="路径-45" fill="url(#linearGradient-10)"></path>
<g id="路径-46" transform="translate(0.000000, 4.000000)">
<mask id="mask-12" fill="white">
<use xlink:href="#path-11"></use>
</mask>
<use id="Mask" fill="#FFD6A7" xlink:href="#path-11"></use>
<path d="M6.75437134,6.70359449 C6.31581737,6.70359449 4.23395853,6.06113278 4.07998644,5.91934988 C3.92601435,5.77756697 4.30907101,7.95013098 7.03325627,7.24651446 C7.13970229,6.88456781 7.04674065,6.70359449 6.75437134,6.70359449 Z" fill="#F0A53D" style="mix-blend-mode: multiply;" opacity="0.5" mask="url(#mask-12)"></path>
</g>
</g>
<path d="M5.07311779,23.0073693 C4.68808094,24.5256706 3.22544506,29.9836152 3.22544506,30.9108125 C3.22544506,31.8380098 2.92548182,40.2897051 2.92548182,40.732508 C2.92548182,41.1753109 3.95854849,42.4516251 3.22544506,43.0507113 C2.49234162,43.6497976 1.81188398e-13,43.8485127 1.81188398e-13,42.6287042 C1.81188398e-13,41.4088957 0.388607838,40.8243035 0.388607838,40.400826 C0.388607838,39.9773485 0.0426418331,30.584592 0.388607838,29.2082901 C0.734573844,27.8319881 0.991265075,22.1473907 1.40697043,22.1473907 C1.82267579,22.1473907 5.45815463,21.489068 5.07311779,23.0073693 Z" id="路径-21" fill="#FFD6A7"></path>
<path d="M19.7224453,18.0973977 C21.3690975,22.7100392 22.2650744,25.5022607 22.4103758,26.474062 C22.8314797,29.2904788 23.8498607,34.1020694 23.6765651,35.4562685 C23.6120851,35.9601407 22.9354543,34.8946698 22.4103758,34.6262885 C22.1090393,34.4722675 22.4103758,35.9216374 21.5628858,35.0855846 C20.7153959,34.2495318 20.3888906,31.3824333 19.3961138,28.7784414 C18.342038,26.0136663 18.3283189,21.5355261 18,20.6187136 C17.5749702,19.4318413 18.1491186,18.5914027 19.7224453,18.0973977 Z" id="路径-22" fill="#FFD6A7"></path>
<path d="M4.9703812,15.6721901 C2.78306286,18.2789727 0.132716487,22.6538702 1.45788967,23.1495059 C2.34133846,23.4799296 3.51216897,23.6121861 4.9703812,23.5462753 C4.3797922,33.6429819 4.72325901,38.9853255 6.00078161,39.5733058 C7.91706551,40.4552764 7.91706551,36.3848035 13.2610612,35.0974552 C18.6050568,33.8101068 21.0174981,34.5887765 21.885643,33.5544631 C22.753788,32.5201498 18.4263671,33.4138591 18.4263671,28.6761041 C18.4263671,23.9383491 19.0106954,23.2361117 21.2666202,22.1723211 C23.5225449,21.1085305 18.0095821,13.2694007 16.5633695,13.2694007 C16.0714732,13.2694007 15.200047,14.1569229 12.9725581,14.1569229 C11.2453371,14.1569229 10.5766396,13.099353 9.28971537,13.2694007 C7.42163987,13.5162388 5.82498987,14.6536923 4.9703812,15.6721901 Z" id="路径-8" fill="url(#linearGradient-13)" fill-rule="nonzero"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
src/assets/images/kjfs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/assets/qrcode.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -0,0 +1,333 @@
<template>
<div class="ai-article">
<div v-html="value"></div>
</div>
</template>
<script>
export default {
name: 'AiArticle',
props: {
value: {
type: String
}
}
}
</script>
<style lang="scss" scoped>
.ai-article {
width: 100%;
line-height: 1.75;
font-weight: 400;
color: #333;
font-size: 14px;
text-align: justify;
overflow-x: auto;
word-break: break-word;
:deep( h1 ){
margin: 1.3rem 0;
line-height: 1.2
}
:deep( p ){
line-height: 2.27rem
}
:deep( hr ){
border: none;
border-top: 1px solid #ddd;
margin-top: 2.7rem;
margin-bottom: 2.7rem
}
:deep( img:not(.equation)), :deep( iframe), :deep( embed), :deep( video ){
display: block;
margin: 18px auto;
max-width: 100% !important;
}
:deep( img.equation ){
margin: 0 .1em;
max-width: 100% !important;
vertical-align: middle
}
:deep( figure ){
margin: 2.7rem auto;
text-align: center
}
:deep( img:not(.equation) ){
cursor: zoom-in
}
:deep( figure figcaption ){
text-align: center;
font-size: 1rem;
line-height: 2.7rem;
color: #909090
}
:deep( pre ){
line-height: 1.93rem;
overflow: auto
}
:deep( code),
:deep( pre ){
font-family: Menlo, Monaco, Consolas, Courier New, monospace
}
:deep( code ){
font-size: 1rem;
padding: .26rem .53em;
word-break: break-word;
color: #4e5980;
background-color: #f8f8f8;
border-radius: 2px;
overflow-x: auto
}
:deep( pre>code ){
font-size: 1rem;
padding: .67rem 1.3rem;
margin: 0;
word-break: normal;
display: block
}
:deep( a ){
color: #259
}
:deep( a:active),
:deep( a:hover ){
color: #275b8c
}
:deep( table ){
width: 100%;
margin-top: 18px;
margin-bottom: 18px;
overflow: auto;
font-size: 1rem;
text-align: center;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
:deep( thead ){
background: #f6f6f6;
color: #000;
text-align: left
}
:deep( td),
:deep( th ){
padding: 3px 5px;
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
}
:deep( td ){
min-width: 10rem
}
:deep( blockquote ){
margin: 1em 0;
border-left: 4px solid #ddd;
padding: 0 1.3rem
}
:deep( blockquote>p ){
margin: .6rem 0
}
:deep( ol),
:deep( ul ){
padding-left: 2.7rem
}
:deep( ol li),
:deep( ul li ){
margin-bottom: .6rem
}
:deep( ol ol),
:deep( ol ul),
:deep( ul ol),
:deep( ul ul ){
margin-top: .27rem
}
:deep( pre>code ){
overflow-x: auto;
-webkit-overflow-scrolling: touch;
color: #333;
background: #f8f8f8
}
:deep( p ){
line-height: inherit;
margin-top: 18px;
margin-bottom: 18px
}
:deep( p:first-child){
margin-top: 0!important;
}
:deep( img ){
max-height: none
}
:deep( a ){
color: #0269c8;
border-bottom: 1px solid #d1e9ff
}
:deep( code ){
background-color: #fff5f5;
color: #ff502c;
font-size: .87em;
padding: .065em .4em
}
:deep( blockquote ){
color: #666;
padding: 1px 23px;
margin: 18px 0;
border-left: 4px solid #cbcbcb;
background-color: #f8f8f8
}
:deep( blockquote:after ){
display: block;
content: ""
}
:deep( blockquote>p ){
margin: 10px 0
}
:deep( blockquote.warning ){
position: relative;
border-left-color: #f75151;
margin-left: 8px
}
:deep( blockquote.warning:before ){
position: absolute;
top: 14px;
left: -12px;
background: #f75151;
border-radius: 50%;
content: "!";
width: 20px;
height: 20px;
color: #fff;
display: flex;
align-items: center;
justify-content: center
}
:deep( ol),
:deep( ul ){
padding-left: 28px
}
:deep( ol li),
:deep( ul li ){
margin-bottom: 0;
list-style: inherit
}
:deep( ol li.task-list-item),
:deep( ul li.task-list-item ){
list-style: none
}
:deep( ol li.task-list-item ol),
:deep( ol li.task-list-item ul),
:deep( ul li.task-list-item ol),
:deep( ul li.task-list-item ul ){
margin-top: 0
}
:deep( ol li ){
padding-left: 6px
}
:deep( pre ){
position: relative;
line-height: 1.75
}
:deep( pre>code ){
padding: 15px 12px
}
:deep( pre>code.hljs[lang] ){
padding: 18px 15px 12px
}
:deep( h1),
:deep( h2),
:deep( h3),
:deep( h4),
:deep( h5),
:deep( h6 ){
color: #333;
line-height: 1.5;
margin-top: 35px;
margin-bottom: 10px;
padding-bottom: 5px;
font-weight: 500;
}
:deep( h1 ){
font-size: 30px;
margin-bottom: 5px
}
:deep( h2 ){
padding-bottom: 12px;
font-size: 24px;
border-bottom: 1px solid #ececec
}
:deep( h3 ){
font-size: 18px;
padding-bottom: 0
}
:deep( h4 ){
font-size: 16px
}
:deep( h5 ){
font-size: 15px
}
:deep( h6 ){
margin-top: 5px
}
:deep( h1.heading+h2.heading ){
margin-top: 20px
}
:deep( h1.heading+h3.heading ){
margin-top: 15px
}
:deep( .heading+.heading ){
margin-top: 0
}
:deep( h1+:not(.heading) ){
margin-top: 25px
}
}
</style>

114
src/components/AiBar.vue Normal file
View File

@@ -0,0 +1,114 @@
<template>
<div class="aibar" :style="{ marginBottom }" :class="{'aibar-center':titlePosition === 'center',card}">
<div v-if="titlePosition === 'center'"/>
<div class="aibar-left" :class="[titlePosition === 'center' ? 'aibar-left__center' : '']">
<template v-if="!$slots.title">{{ title }}</template>
<slot name="title" v-else></slot>
</div>
<div class="aibar-right">
<slot v-if="$slots.right" name="right"/>
<slot v-else/>
</div>
</div>
</template>
<script>
export default {
name: 'AiBar',
props: {
title: {
type: String
},
customCliker: {
type: Boolean,
default: false
},
marginBottom: {
type: String,
default: '16px'
},
titlePosition: {
type: String,
default: 'left'
},
card: Boolean
},
}
</script>
<style lang="scss" scoped>
.aibar {
display: flex;
position: relative;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 16px;
box-sizing: border-box;
border-bottom: 1px solid #EEEEEE;
&.card {
background: #fff;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
}
.aibar-left {
color: #222;
font-size: 16px;
font-weight: 700;
}
.aibar-left__center {
position: relative;
width: 556px;
text-align: center;
word-break: break-all;
line-height: 24px;
}
.aibar-right {
display: flex;
align-items: center;
color: #5088FF;
font-size: 12px;
i {
line-height: 1;
color: #5088FF;
}
span {
font-size: 12px;
}
& > div, & > a {
display: flex;
align-items: center;
margin-right: 20px;
&:last-child {
margin-right: 0;
}
}
}
}
.aibar-center {
height: auto;
padding: 10px 0;
h2 {
margin: 0 0 10px 0;
}
p {
color: #888;
font-size: 14px;
}
}
</style>

64
src/components/AiCard.vue Normal file
View File

@@ -0,0 +1,64 @@
<template>
<section class="ai-card" :class="{panel,headerPanel}">
<ai-bar v-if="!hideHeader" :title="title" v-bind="$attrs">
<template #title>
<slot name="title"></slot>
</template>
<template #right>
<slot name="right"></slot>
</template>
</ai-bar>
<div class="ai-card__body" :style="{paddingTop: hideHeader ? '20px' : 0}">
<slot v-if="$scopedSlots.content" name="content"/>
<slot v-else/>
</div>
</section>
</template>
<script>
export default {
name: 'AiCard',
props: {
title: {
type: String
},
hideTitle: Boolean,
panel: Boolean,
headerPanel: Boolean
},
computed: {
hideHeader: v => v.hideTitle || (v.panel && !v.title)
}
}
</script>
<style scoped lang="scss">
.ai-card {
margin-bottom: 20px;
padding-bottom: 20px;
background: #FFFFFF;
overflow: hidden;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
.ai-card__body {
height: calc(100% - 72px);
padding: 0 20px 0;
overflow-y: auto;
}
&.panel, &.headerPanel {
margin-bottom: 0;
.ai-card__body {
padding: 12px 16px;
}
.aibar {
margin-bottom: 0 !important;
}
}
}
</style>

131
src/components/AiDetail.vue Normal file
View File

@@ -0,0 +1,131 @@
<template>
<div class="ai-detail">
<div class="ai-detail__title">
<slot name="title"></slot>
</div>
<div class="ai-detail__step" v-if="isHasStepSlot">
<slot name="step"></slot>
</div>
<div class="ai-detail__content" :class="className">
<div class="ai-detail__content--wrapper" :class="{'ai-detail__content--side':isHasSidebar,list}">
<slot name="content"></slot>
</div>
</div>
<div class="ai-detail__footer" v-if="isHasFooterSlot">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'AiDetail',
props: {
isHasSidebar: {
type: Boolean,
default: false
},
list: Boolean
},
computed: {
isHasFooterSlot() {
return this.$slots.footer
},
isHasStepSlot() {
return this.$slots.step
},
className() {
if (this.isHasFooterSlot) {
if (this.isHasStepSlot) {
return 'ai-detail__content--active-step'
}
return 'ai-detail__content--active'
} else {
if (this.isHasStepSlot) {
return 'ai-detail__content--step'
}
}
return ''
}
}
}
</script>
<style lang="scss" scoped>
.ai-detail {
height: 100%;
background: #F5F6F9;
overflow: hidden;
.ai-detail__title {
margin: 0 20px;
}
.ai-detail__step {
height: 72px;
}
.ai-detail__content {
height: calc(100% - 48px);
padding: 0 0 20px 0;
overflow-x: hidden;
overflow-y: overlay;
}
@media screen and (max-width: 1740px) {
.ai-detail__content {
padding: 0 20px 20px;
}
}
.ai-detail__content--wrapper {
position: relative;
max-width: 1200px;
margin: 0 auto;
padding-top: 20px;
&.list {
max-width: unset;
margin: 0 20px;
}
}
@media screen and (max-width: 1740px) {
.ai-detail__content--side {
margin-left: 128px;
}
}
.ai-detail__content--active {
height: calc(100% - 48px - 64px);
}
.ai-detail__content--active-step {
height: calc(100% - 48px - 64px - 72px);
}
.ai-detail__content--step {
height: calc(100% - 48px - 72px);
}
.ai-detail__footer {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 64px;
background: #F5F5F5;
box-shadow: 0px 1px 0px 0px #E5E5E5;
.el-button {
width: 92px;
}
}
}
</style>

121
src/components/AiDialog.vue Normal file
View File

@@ -0,0 +1,121 @@
<template>
<section class="ai-dialog__wrapper">
<el-dialog custom-class="ai-dialog" v-on="$listeners" v-bind="$attrs" :visible.sync="dialog">
<div class="ai-dialog__header fill" slot="title" v-text="title"/>
<div class="ai-dialog__content">
<div class="ai-dialog__content--wrapper pad-r8">
<slot/>
</div>
</div>
<template v-if="customFooter" slot="footer">
<slot name="footer"/>
</template>
<div v-else class="dialog-footer" slot="footer">
<el-button @click="onCancel">取消</el-button>
<el-button @click="onConfirm" type="primary">确认</el-button>
</div>
</el-dialog>
</section>
</template>
<script>
export default {
name: 'AiDialog',
model: {
prop: "visible",
event: "input"
},
props: {
visible: Boolean,
title: {type: String, default: ''},
customFooter: Boolean
},
data() {
return {
dialog: false,
}
},
watch: {
dialog(v) {
this.visible != v && this.$emit("input", v)
},
visible(v) {
this.dialog = v
}
},
methods: {
onCancel() {
this.$emit('input', false)
this.$emit('update:visible', false)
this.$emit('cancel')
},
onConfirm() {
this.$emit('confirm')
}
}
}
</script>
<style lang="scss">
.ai-dialog {
margin: unset !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.el-dialog__body {
padding: 20px 40px 20px;
}
.ai-dialog__content {
overflow-y: auto;
padding-bottom: 4px;
max-height: 500px;
.ai-dialog__content--wrapper {
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
}
.ai-dialog__header {
flex: 1;
height: 48px;
line-height: 48px;
padding: 0 16px;
font-size: 16px;
font-weight: 700;
}
.el-dialog__footer {
padding: 16px 20px;
box-sizing: border-box;
background: #F3F6F9;
text-align: center;
& + .el-button {
margin-left: 8px;
}
.el-button {
width: 92px;
}
}
.el-dialog__header {
padding: 0;
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
}
.el-dialog__headerbtn {
position: relative;
flex-shrink: 0;
top: unset;
right: unset;
margin: 0 16px;
}
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<div class="ai-empty">
<div class="ai-empty__bg"></div>
<template v-if="!isHasTitleSlot">暂无数据</template>
<slot v-else></slot>
</div>
</template>
<script>
export default {
name: 'AiEmpty',
computed: {
isHasTitleSlot () {
return this.$slots.default
}
}
}
</script>
<style lang="scss">
.ai-empty {
margin-bottom: 10px;
text-align: center;
color: #acaaad;
.ai-empty__bg {
background: url("../assets/images/empty.svg") no-repeat center;
background-size: 120px 120px;
height: 120px;
margin: 48px auto 0;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div class="ai-info-item" :style="contentStyle">
<label class="ai-info-item__left" :style="contentLabelStyle">
<template v-if="$slots.label">
<slot name="label"/>
</template>
<template v-else>{{ label }}</template>
</label>
<div class="ai-info-item__right">
<slot v-if="$scopedSlots.default"/>
<template v-else-if="!!openType">
<ai-open-data :type="openType" :openid="value"/>
</template>
<template v-else>{{ value || value === 0 ? value : '-' }}</template>
</div>
</div>
</template>
<script>
export default {
name: 'AiInfoItem',
inject: ['AiWrapper'],
props: {
label: {
type: String
},
value: {
type: [String, Number]
},
'label-width': {
type: String
},
isLine: {
type: Boolean,
default: false
},
openType: {default: ""}
},
computed: {
contentStyle() {
let width = this.AiWrapper.autoWidth
if (this.isLine) {
width = '100%'
}
return {
width
}
},
contentLabelStyle() {
return {
width: this.labelWidth || this.AiWrapper.autoLableWidth
}
},
},
methods: {}
}
</script>
<style lang="scss" scoped>
.ai-info-item {
display: flex;
line-height: 1.4;
margin-bottom: 16px;
label {
flex-shrink: 0;
width: 96px;
margin-right: 40px;
text-align: right;
color: #888;
font-size: 14px;
}
.ai-info-item__right {
flex: 1;
color: #222;
font-size: 14px;
word-break: break-all;
}
}
</style>

263
src/components/AiList.vue Normal file
View File

@@ -0,0 +1,263 @@
<template>
<div class="ai-list" :class="listClass">
<div class="ai-list__title" v-if="$slots.title">
<slot name="title"></slot>
</div>
<div class="ai-list__tabs" v-if="$slots.tabs">
<slot name="tabs"></slot>
</div>
<el-row type="flex" class="ai-list__blank" v-else-if="$scopedSlots.blank" :class="{hasLeft:$scopedSlots.left}">
<div class="ai-list__content--left" v-if="$scopedSlots.left">
<slot name="left"/>
</div>
<div class="fill">
<slot name="blank"/>
</div>
</el-row>
<div class="ai-list__content" v-else :class="contentClass">
<div class="ai-list__content--wrapper">
<slot name="custom" v-if="!!$slots.custom"/>
<template v-else>
<div class="ai-list__content--left" v-if="$slots.left">
<slot name="left"></slot>
</div>
<div class="ai-list__content--right" :style="{width: !$slots.left ? '100%' : 'auto' }">
<div class="ai-list__content--right-wrapper" :style="{minHeight: $slots.left ? '100%' : 'auto' }">
<slot name="content"></slot>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AiList',
props: {
isTabs: {
type: Boolean,
default: false
}
},
computed: {
listClass() {
if (this.$slots.left || this.$slots.tabs) {
return 'ai-left__list'
}
if (this.isTabs) {
return 'ai-left__list ai-list__notab'
}
if (!this.isTabs && !this.$slots.tabs && !this.$slots.left) {
return 'ai-list__single'
}
return ''
},
contentClass() {
if (this.isTabs) {
return 'ai-list__tab--content'
}
return ''
}
}
}
</script>
<style lang="scss" scoped>
//全局tab css
:deep( .ai-list__tabs ) {
margin-top: 0 !important;
.el-tabs__item {
position: relative;
width: auto !important;;
padding: 0 16px !important;
}
.el-tabs__item.is-active {
color: #222;
}
.el-tabs__nav-wrap::after {
height: 1px;
background-color: #D8DCE3;
}
.el-tabs__active-bar {
// left: -16px;
// width: 50%!important;
}
.el-tabs__nav {
border-radius: 0 !important;
}
.el-tabs__header {
padding: 0;
margin: 0 !important;
}
.el-tab-pane {
overflow-y: auto;
}
}
.ai-list {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 0 20px;
overflow: hidden;
background: #f3f6f9;
&.ai-list__single {
height: 100%;
padding: 0;
.ai-list__title {
margin: 0 20px;
}
.ai-list__content {
flex: 1;
padding: 20px 20px 20px;
overflow: hidden;
}
.ai-list__content--wrapper {
height: 100%;
margin: 0 !important;
overflow: hidden;
}
.ai-list__content--right-wrapper {
// margin: 0 20px;
padding: 20px 20px !important;
}
}
div {
box-sizing: border-box;
}
.ai-list__tabs {
flex: 1;
overflow: hidden;
:deep( .el-tabs__item ) {
min-width: 80px;
height: 32px;
line-height: 32px;
font-size: 14px;
color: #222;
text-align: center;
border-bottom: 1px solid transparent !important;
}
:deep(.el-tabs__header ) {
margin: 0;
}
:deep(.el-tabs ) {
height: 100%;
}
:deep(.el-tabs__content ) {
height: calc(100% - 32px);
background: #f3f6f9;
& > div {
height: 100%;
}
}
}
.ai-list__content {
.ai-list__content--wrapper {
display: flex;
margin: 20px 0;
}
}
.ai-list__content--left {
display: flex;
flex-shrink: 0;
margin-right: 20px;
padding-left: 1px;
height: 100%;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
min-width: 264px;
max-width: 50%;
}
.ai-list__content--right {
flex: 1;
overflow-y: auto;
.ai-list__content--right-wrapper {
padding: 12px 16px 12px;
background: #FFFFFF;
border-radius: 2px;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
}
}
.ai-list__tab--content {
height: 100% !important;
& > div {
overflow-y: auto !important;
}
}
&.ai-list__notab {
padding: 0;
}
.ai-list__blank {
flex: 1;
padding: 16px;
box-sizing: border-box;
height: calc(100% - 50px);
&.hasLeft {
padding: 16px 0;
}
& > .fill {
height: 100%;
overflow: auto;
}
}
}
.ai-left__list {
height: 100%;
background: #f3f6f9;
overflow: hidden;
.ai-list__content {
flex: 1;
overflow: hidden;
.ai-list__content--wrapper {
height: calc(100% - 32px);
}
.ai-list__content--right {
// margin-left: 11px;
overflow: auto;
}
}
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<section class="AiPullDown">
<div class="line"/>
<div class="down-content" @click="handleExpand">
<i :class="expandIcon"/>
<span>{{ btnText }}</span>
</div>
<div class="line"/>
</section>
</template>
<script>
export default {
name: "AiPullDown",
props: {
target: String,
height: {default: 4},
},
data() {
return {
expand: false
}
},
methods: {
handleExpand() {
this.expand = !this.expand
if (this.target) {
} else this.$emit('change', this.expandStyle)
}
},
computed: {
btnText() {
return this.expand ? '收起高级搜索' : '展开高级搜索'
},
expandStyle() {
let initStyle = {overflow: 'hidden', height: `${this.height}px`}
this.expand && (initStyle.height = "auto")
return initStyle
},
expandIcon() {
return this.expand ? 'iconfont iconDouble_Up' : 'iconfont iconDouble_Down'
}
},
mounted() {
this.$emit("change", this.expandStyle)
}
}
</script>
<style lang="scss" scoped>
.AiPullDown {
display: flex;
.line {
flex: 1;
min-width: 0;
border-top: 1px solid #eee;
}
.down-content {
cursor: pointer;
padding: 0 8px;
height: 24px;
border-radius: 0 0 8px 8px;
border: 1px solid #eee;
border-top: 0;
box-sizing: border-box;
color: #333;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -0,0 +1,52 @@
<template>
<section class="AiSearch">
<slot v-if="$slots.label" name="label"/>
<span v-else>{{ label }}</span>
<div class="content">
<slot/>
</div>
</section>
</template>
<script>
export default {
name: "AiSearch",
props: {
label: {default: ""}
}
}
</script>
<style lang="scss" scoped>
.AiSearch {
display: flex;
span {
background: #F5F5F5;
font-size: 14px;
color: #666;
border: 1px solid #D0D4DC;
border-right: none;
padding: 0 8px;
white-space: nowrap;
display: flex;
align-items: center;
justify-content: center;
}
:deep( .el-input__inner ){
border-radius: 0;
}
.content {
display: flex;
align-items: center;
* + * {
.el-input__inner {
margin-left: -1px;
}
}
}
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<section>
<div ref="AiSearchBarZone" class="AiSearchBar" :class="{bottomBorder}" :style="searchBarStyle">
<div class="searchLeftZone">
<slot name="left"/>
</div>
<div class="searchRightZone" ref="searchRightZone">
<slot name="right"/>
</div>
</div>
<ai-pull-down v-if="!isSingleRow" @change="handlePullDown" :height="rightHeight"/>
</section>
</template>
<script>
export default {
name: "AiSearchBar",
props: {
bottomBorder: Boolean,
size: {default: "small"}
},
computed: {
isSingleRow() {
return this.height <= 55
},
},
data() {
return {
height: 0,
rightHeight: 0,
searchBarStyle: {}
}
},
methods: {
handlePullDown(style) {
this.searchBarStyle = style
if (style.height == 'auto') {
this.searchBarStyle.marginBottom = '16px'
} else {
this.searchBarStyle.marginBottom = '0'
}
}
},
mounted() {
this.$nextTick(() => {
this.height = this.$refs?.AiSearchBarZone?.offsetHeight
this.rightHeight = this.$refs?.searchRightZone?.offsetHeight
})
}
}
</script>
<style lang="scss" scoped>
.AiSearchBar {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 10px;
padding-bottom: 12px;
&.bottomBorder {
border-bottom: 1px solid #eee;
}
:deep(.searchLeftZone ){
flex: 1;
min-width: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
:deep(.searchRightZone ){
display: flex;
align-items: center;
flex-shrink: 0;
align-self: flex-start;
.el-input {
width: 280px;
}
* + button, * + div, * + section {
margin-left: 8px;
}
}
.el-input {
width: auto;
}
}
.AiPullDown {
margin-top: 8px;
}
:deep( .searchLeftZone > .el-button), :deep( .searchRightZone > .el-button ){
margin-left: 0;
}
</style>

View File

@@ -0,0 +1,98 @@
<template>
<div class="ai-sidebar">
<div class="ai-sidebar__tab">
<span
:class="[currIndex === index ? 'ai-sidebar__tab--active' : '']"
v-for="(item, index) in tabTitle"
:key="index"
@click="changeTab(index)">
{{ item }}
</span>
</div>
</div>
</template>
<script>
export default {
name: 'AiSidebar',
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: Number,
require: true,
default: 0
},
tabTitle: {
type: Array,
default: () => [],
required: true
}
},
watch: {
value (v) {
if (v >= 0 && this.currIndex !== v) {
this.currIndex = v
}
}
},
data() {
return {
currIndex: 0
}
},
mounted () {
if (this.value > 0) {
this.currIndex = this.value
}
},
methods: {
changeTab(index) {
this.currIndex = index
this.$emit('change', index)
}
}
}
</script>
<style lang="scss" scoped>
.ai-sidebar {
position: fixed;
margin-left: -20px;
font-size: 14px;
transform: translateX(-100%);
.ai-sidebar__tab {
min-width: 88px;
overflow: hidden;
span {
display: block;
height: 40px;
line-height: 40px;
padding: 0 16px;
text-align: center;
font-size: 14px;
font-weight: normal;
font-stretch: normal;
letter-spacing: 0;
color: #666666;
cursor: pointer;
border-right: 3px solid #D8DCE3;
}
.ai-sidebar__tab--active {
border-right: 3px solid #1FBAD6;
color: #1FBAD6;
}
}
}
</style>

400
src/components/AiTable.vue Normal file
View File

@@ -0,0 +1,400 @@
<template>
<div class="ai-table" :class="[isShowBorder ? 'ai-table__border' : 'ai-table__noborder']">
<el-table :data="tableData"
header-cell-class-name="ai-table__header"
cell-class-name="ai-table__cell"
row-class-name="ai-table__row"
:class="{'ai-header__border': isShowBorder}"
:ref="refName"
:size="tableSize"
:stripe="stripe"
:tooltip-effect="tooltipEffect"
@selection-change="handleSelectionChange"
v-on="$listeners"
v-bind="$attrs"
v-loading="loading">
<template v-for="colConfig in colConfigs.filter(e=>!e.hide)">
<slot v-if="colConfig.slot && colConfig.slot !== 'options'" :name="colConfig.slot"/>
<component
:key="colConfig.id"
v-else-if="colConfig.component"
:is="colConfig.component"
:col-config="colConfig">
</component>
<el-table-column
v-else-if="colConfig.dict"
:key="colConfig.id"
v-bind="colConfig">
<span slot-scope="{row}" :style="{color:colConfig.color||dict.getColor(colConfig.dict, row[colConfig.prop])}">
{{ $dict.getLabel(colConfig.dict, row[colConfig.prop]) }}
</span>
</el-table-column>
<el-table-column
v-else-if="colConfig.openType"
:key="colConfig.id"
v-bind="colConfig">
<template v-slot="{row}">
<ai-open-data :type="colConfig.openType" :openid="row[colConfig.prop]"/>
</template>
</el-table-column>
<el-table-column
v-else-if="colConfig.type"
:key="colConfig.id"
v-bind="colConfig"
:width="colConfig.width || 100"/>
<el-table-column v-else v-bind="colConfig" :key="colConfig.id"
:show-overflow-tooltip="colConfig['show-overflow-tooltip'] != false">
<template slot-scope="scope">
<render-slot v-if="colConfig.render" :render="colConfig.render" :row="scope.row" :index="scope.$index"
:column="colConfig"/>
<span v-else>{{ getValue(colConfig, scope.row) }}</span>
</template>
</el-table-column>
</template>
<slot class="table-options" name="options"/>
<template #empty>
<slot v-if="$scopedSlots.empty" name="empty"/>
<AiEmpty v-else class="no-data" style="height:160px;"/>
</template>
</el-table>
<div class="pagination newPagination" v-if="isShowPagination">
<el-pagination
background
:current-page="page.current"
:total="page.total"
:page-size="page.size"
v-bind="$attrs"
:page-sizes="pageSizes"
:layout="layout"
:pager-count="page.pagerCount"
@size-change="handleSizeChange"
@current-change="handleChange">
<div class="paginationPre">
<el-checkbox v-if="isHasPaginationBtnsSlot" :disabled="!tableData.length" :indeterminate="isIndeterminate"
:value="checkAll" @click.native="toggleAllSelection">全选
</el-checkbox>
<slot name="pagination"/>
<div class="pagination-btns">
<slot name="paginationBtns"></slot>
</div>
<div class="paginationPre-total" :style="{marginLeft: isHasPaginationBtnsSlot ? '24px' : 0}"><label class="color-primary">{{ page.total }}</label>条记录
</div>
</div>
</el-pagination>
</div>
</div>
</template>
<script>
import moment from 'dayjs'
export default {
name: 'AiTable',
model: {
prop: "tableData",
event: "change"
},
props: {
colConfigs: {type: Array, required: true},
tableData: {type: Array, required: true, default: () => []},
current: {default: 1},
size: {default: 10},
isShowPagination: {type: Boolean, default: true},
total: Number,
layout: {type: String, default: 'slot,->, prev, pager, next, sizes, jumper'},
stripe: {type: Boolean, default: true},
loading: {type: Boolean, default: false},
tooltipEffect: String,
tableSize: String,
tableRef: String,
pagerCount: {default: 5},
pageSizes: {default: () => [10, 20, 50, 100]},
pageConfig: Object
},
data() {
return {
name: '',
chooseList: [],
defaultPage: {}
}
},
components: {
renderSlot: {
functional: true,
props: {
row: Object,
render: Function,
index: Number,
column: {type: Object, default: null},
},
render: (h, data) => {
const params = {
row: data.props.row,
index: data.props.index
}
if (data.props.column) {
params.column = data.props.column
}
return data.props.render(h, params)
}
}
},
computed: {
refName: v => v.tableRef || `aiTable${new Date().getTime()}`,
isShowBorder: v => !!v.$attrs.border || v.$attrs.border === '',
isHasPaginationBtnsSlot: v => v.$scopedSlots.paginationBtns,
isIndeterminate: v => v.chooseList.length > 0 && v.chooseList.length < v.tableData.length,
checkAll: v => v.chooseList.length == v.tableData.length && v.tableData !== 0,
page() {
return {
current: this.current,
size: this.size,
total: this.total,
pagerCount: this.pagerCount,
...this.pageConfig
}
},
},
methods: {
handleChange(current) {
this.$emit('update:current', current)
this.$emit('update:pageConfig', {...this.pageConfig, current})
this.$nextTick(() => {
this.$emit('getList')
})
},
handleSizeChange(size) {
this.$emit('update:size', size)
this.$emit('update:pageConfig', {...this.pageConfig, size})
this.$nextTick(() => {
this.$emit('getList')
})
},
handleSelectionChange(e) {
this.chooseList = e
this.$emit('handleSelectionChange', e)
},
getValue(colConfig, row) {
if (typeof colConfig.format === 'function') {
return colConfig.format.call(this, row[colConfig.prop])
}
if (colConfig.dateFormat) {
return moment(row[colConfig.prop]).format(colConfig.dateFormat)
}
return row[colConfig.prop] === 0 || row[colConfig.prop] ? row[colConfig.prop] : '-'
},
/**
* 表格方法代理
*/
clearSelection() {
this.$refs[this.refName].clearSelection()
},
toggleRowSelection() {
this.$refs[this.refName].toggleRowSelection(...arguments)
},
toggleAllSelection() {
this.$refs[this.refName].toggleAllSelection()
},
toggleRowExpansion() {
this.$refs[this.refName].toggleRowExpansion(...arguments)
},
setCurrentRow() {
this.$refs[this.refName].setCurrentRow(...arguments)
},
clearSort() {
this.$refs[this.refName].clearSort()
},
clearFilter() {
this.$refs[this.refName].clearFilter(...arguments)
},
doLayout() {
this.$refs[this.refName].doLayout()
},
sort() {
this.$refs[this.refName].sort(...arguments)
},
}
}
</script>
<style lang="scss" scoped>
.ai-table {
.color-primary {
// color: $primaryColor;
}
:deep( .ai-header__border .ai-table__header ) {
border-bottom: 1px solid #d0d4dc !important;
border-right: 1px solid #d0d4dc !important;
}
:deep( .el-table--border ) {
border: 1px solid #d0d4dc;
border-right: none;
border-bottom: none;
}
:deep( .el-table ) {
color: #222;
.caret-wrapper {
height: 24px;
.ascending {
top: 1px;
}
.descending {
bottom: 1px;
}
}
thead {
color: #555
}
}
:deep( .cell ) {
line-height: 24px;
}
:deep( .el-table__header ) {
th {
padding: 8px 0;
}
tr {
.cell {
font-weight: 700;
}
th:first-child {
.cell {
padding-left: 40px !important;
}
}
}
}
:deep( .el-table__body ) {
tr td:first-child .cell {
padding-left: 40px !important;
}
}
:deep( .el-table__fixed-right ) {
.el-table__body {
tr td:first-child .cell {
padding-left: 0 !important;
padding-right: 0;
}
}
}
:deep( .ai-table__header ) {
border-bottom: none;
background: #F3F4F5;
}
:deep(.el-pager ) {
li.active + li {
border-left: 1px solid #d0d4dc;
}
}
.newPagination {
width: 100%;
display: flex;
align-items: center;
height: 64px;
padding: 0 40px !important;
.el-pagination {
width: 100%;
padding: 0;
}
.paginationPre {
display: flex;
height: 28px;
line-height: 1;
font-size: 14px;
font-weight: normal;
align-items: center;
.pagination-btns {
display: flex;
align-items: center;
gap: 8px;
color: #1FBAD6 !important;
:deep( span), :deep( div ) {
font-size: 12px;
cursor: pointer;
color: #1FBAD6 !important;
&:hover {
opacity: 0.8;
}
}
}
.paginationPre-total {
font-size: 12px;
color: #555;
label {
padding: 0 2px;
font-weight: 700;
}
}
& > * + * {
margin-left: 24px;
}
:deep( .el-pagination button), .el-pagination span:not([class*=suffix]) {
line-height: 1 !important;
}
:deep(.el-checkbox ) {
display: flex;
align-items: center;
.el-checkbox__input, .el-checkbox__inner {
width: 14px;
height: 14px;
min-width: 0 !important;
line-height: 1 !important;
}
.el-checkbox__label {
font-size: 12px;
color: #222222;
height: auto !important;
line-height: 1 !important;
padding-left: 3px !important;
}
}
}
}
}
.ai-table__noborder {
:deep( .el-table td), :deep( .el-table th.is-center ) {
border: none;
}
.el-table::before {
display: none;
}
:deep( .el-table--striped .el-table__body tr.el-table__row--striped td ) {
background: #F5F6F9;
}
:deep( .el-table__fixed-right::before), :deep( .el-table__fixed::before ) {
display: none;
}
}
</style>

140
src/components/AiTitle.vue Normal file
View File

@@ -0,0 +1,140 @@
<template>
<section class="AiTitle" :class="{ 'bottomBorder': isShowBottomBorder, AiTitleSub: isHasSub}">
<i class="iconfont iconBack_Large" v-if="isShowBack" @click="onBackBtnClick"/>
<div class="fill">
<div class="ailist-title">
<div class="ailist-title__left">
<h2>{{ title }}</h2>
<p>{{ tips }}</p>
</div>
<div class="ailist-title__right">
<div class="aititle-right__btns">
<slot name="rightBtn"/>
</div>
</div>
</div>
<div class="subtitle" v-if="$scopedSlots.sub">
<slot name="sub"/>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'AiTitle',
model: {
prop: 'value',
event: 'change'
},
props: {
title: {
type: String,
required: true
},
tips: {
type: String
},
instance: {
type: Function
},
value: {
type: String
},
isShowBack: {
type: Boolean,
default: false
},
isShowBottomBorder: {
type: Boolean,
default: false
}
},
computed: {
isHasSub() {
return this.$slots.sub
}
},
methods: {
onBackBtnClick() {
this.closePage()
this.$emit('onBackClick')
this.$emit('back')
}
}
}
</script>
<style scoped lang="scss">
.AiTitle {
display: flex;
.ailist-title {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
.ailist-title__left {
display: flex;
align-items: center;
p {
margin-left: 20px;
font-size: 14px;
color: #FA5555;
}
& > i {
width: auto;
height: auto;
margin-right: 8px;
font-size: 16px;
}
h2 {
color: #222;
font-size: 16px;
font-weight: 600;
}
}
.ailist-title__right {
display: flex;
align-items: center;
}
:deep(.el-button ) {
margin-left: 8px !important;
}
}
&.AiTitleSub {
height: auto;
padding: 16px 0;
.ailist-title {
height: auto;
margin-bottom: 3px;
}
}
&.bottomBorder {
border-bottom: 1px solid #D8DCE3;
}
.subtitle {
width: 100%;
color: #888888;
font-size: 12px;
margin-top: 4px;
}
.iconBack_Large {
line-height: 48px;
margin-right: 8px;
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div class="ai-wrapper">
<ai-title class="w100" v-if="title" :title="title"/>
<div class="ai-wrapper-content" :class="{border}">
<slot/>
</div>
</div>
</template>
<script>
import AiTitle from "./AiTitle";
export default {
name: 'AiWrapper',
components: {AiTitle},
componentName: 'AiWrapper',
provide() {
return {
AiWrapper: this
}
},
props: {
'label-width': {
type: String
},
columnsNumber: {
type: Number,
default: 2
},
border: Boolean,
title: String
},
computed: {
autoWidth() {
return ((1 / this.columnsNumber) * 100).toFixed(2) + '%'
},
autoLableWidth() {
return this.labelWidth
}
},
}
</script>
<style lang="scss" scoped>
.ai-wrapper {
.w100 {
width: 100%;
}
.ai-wrapper-content {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
&.border {
border-left: 1px solid #d0d4dc;
border-top: 1px solid #d0d4dc;
.ai-info-item {
border-bottom: 1px solid #d0d4dc;
border-right: 1px solid #d0d4dc;
margin-bottom: 0;
line-height: 32px;
.ai-info-item__left {
background: rgba(0, 0, 0, .03);
padding-right: 16px;
border-right: 1px solid #d0d4dc;
margin-right: 16px;
}
.el-textarea__inner {
border: none;
padding-left: 0;
}
}
}
}
}
</style>

61
src/entry/background.js Normal file
View File

@@ -0,0 +1,61 @@
/**
利用chrome的fetch来避免跨域
**/
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type == 'api') {
new Promise((resolve, reject) => {
let headers = {};
if (request.needMallId) {
headers.Mallid = request.mallId;
}
headers['Content-Type'] = 'application/json';
headers.cookie = getCookie();
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((res) => {
sendResponse(res);
});
} else if (request.type == 'notify') {
chrome.notifications.create(
"" + Math.random(), {
type: "basic",
title: "TEMU助手",
message: "您的商品【" + request.productName + "】成功加入发货台,请尽快处理",
iconUrl: "./icons/48.png"
}, null
)
}
return true;
});
chrome.action.onClicked.addListener(function () {
chrome.tabs.create({
url: "./popup.html"
}, function (tab) {
console.log('tab is:' + tab);
});
});
function getCookie() {
const url = new URL("https://kuajing.pinduoduo.com/");
let cStr = '';
chrome.cookies.getAll({domain: url.host}, (cookie) => {
cookie.map((c) => {
cStr += c.name + '=' + c.value + ';';
});
});
return cStr;
}

1
src/entry/content.js Normal file
View File

@@ -0,0 +1 @@
console.log('hello world content todo something~')

27
src/entry/popup.js Normal file
View File

@@ -0,0 +1,27 @@
import Vue from 'vue'
import App from '../view/popup.vue'
import store from '../store'
import router from '../router'
import ElementUI from 'element-ui'
import '../assets/css/element-variables.scss'
import instance from '../api'
import utils from '../utils'
import install from '../utils/install'
import dayjs from 'dayjs'
Vue.use(ElementUI)
Vue.config.productionTip = false
var relativeTime = require('dayjs/plugin/relativeTime')
require('dayjs/locale/zh-cn')
dayjs.extend(relativeTime)
Vue.prototype.$dayjs = dayjs
Object.keys(utils).forEach(v => Vue.prototype[`$${v}`] = utils[v])
Vue.prototype.$http = instance
new Vue({
store,
router,
render: (h) => h(App)
}).$mount('#app')

View File

@@ -0,0 +1,33 @@
{
"manifest_version": 3,
"name": "TEMU助手",
"description": "TEMU助手 - 自动化提高生产效率",
"version": "0.0.1",
"background": {
"service_worker": "/background.js"
},
"icons": {
"16": "icons/16.png",
"48": "icons/48.png",
"128": "icons/128.png"
},
"action": {
},
"host_permissions": [
"*://*.pinduoduo.com/"
],
"permissions": [
"cookies",
"notifications"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"/content.js"
]
}
]
}

View File

@@ -0,0 +1,33 @@
{
"manifest_version": 3,
"name": "TEMU助手",
"description": "TEMU助手 - 自动化提高生产效率",
"version": "1.0.0",
"background": {
"service_worker": "/background.js"
},
"icons": {
"16": "icons/16.png",
"48": "icons/48.png",
"128": "icons/128.png"
},
"action": {
},
"host_permissions": [
"*://*.pinduoduo.com/"
],
"permissions": [
"cookies",
"notifications"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"/content.js"
]
}
]
}

96
src/router/index.js Normal file
View File

@@ -0,0 +1,96 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
name: 'Home',
meta: {
isAuth: true
},
redirect: '/Welcome',
component: () => import('../view/Home.vue'),
children: [
{
path: 'welcome',
name: 'welcome',
component: () => import('../view/Welcome.vue')
},
{
path: 'normalSendGoods',
name: 'NormalSendGoods',
component: () => import('../view/NormalSendGoods.vue')
},
{
path: 'copyProduct',
name: 'copyProduct',
component: () => import('../view/CopyProduct.vue')
},
{
path: 'saleData',
name: 'saleData',
component: () => import('../view/ExportSaleData.vue')
},
// {
// path: 'statistics',
// name: 'statistics',
// component: () => import('../view/Statistics.vue')
// },
{
path: 'learning',
name: 'learning',
component: () => import('../view/Learning.vue')
}
]
},
{
path: '/login',
name: 'login',
meta: {
title: '登录'
},
component: () => import('../view/login/Login.vue')
},
{
path: '/register',
name: 'register',
meta: {
title: '注册'
},
component: () => import('../view/login/Register.vue')
}
],
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
router.beforeEach((to, from, next) => {
const isLogin = !!store.state.token
if (to.meta.isAuth && !isLogin) {
next({
path: '/login',
query: {
redirect: to.fullPath
}
})
} else {
next()
}
})
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
export default router

68
src/store/index.js Normal file
View File

@@ -0,0 +1,68 @@
import Vue from 'vue'
import Vuex from 'vuex'
import preState from 'vuex-persistedstate'
import request from '@/api'
import router from '@/router'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token: '',
mallId: '',
mallName: '',
mallList: [],
activeDlgShow: false,
userInfo: {},
routes: [],
addRoutes: []
},
mutations: {
setToken (state, token) {
state.token = token
},
setMallId (state, mallId) {
state.mallId = mallId
},
setMallName (state, mallName) {
state.mallName = mallName
},
setMallList (state, mallList) {
state.mallList = mallList
},
SignOut (state) {
state.token = ''
state.userInfo = {}
setTimeout(() => {
router.push('/login')
}, 600)
},
setUserInfo (state, userInfo) {
state.userInfo = userInfo
},
setActiveDlgShow (state, flag) {
state.activeDlgShow = flag
}
},
actions: {
getUserInfo (store) {
request.post('/api/malluser/info').then(res => {
if (res.code === 0) {
store.commit('setUserInfo', res.data)
}
})
}
},
getters: {
isLogin: state => {
return !!state.token
}
},
plugins: [preState()]
})

14
src/utils/date.js Normal file
View File

@@ -0,0 +1,14 @@
export function timestampToTime(timestamp) {
// 时间戳为10位需*1000时间戳为13位不需乘1000
let date = new Date(timestamp);
let Y = date.getFullYear() + "-";
let M =
(date.getMonth() + 1 < 10
? "0" + (date.getMonth() + 1)
: date.getMonth() + 1) + "-";
let D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " ";
let h = (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":";
let m = (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":";
let s = (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds());
return Y + M + D + h + m + s;
}

96
src/utils/index.js Normal file
View File

@@ -0,0 +1,96 @@
import request from '../api'
const dict = {
url: "/dictionary/queryValsByCodeList",
loading: [],
resolves: [],
getStorage() {
const dicts = JSON.parse(localStorage.getItem('dicts') || null)
return dicts?.data || dicts || [];
},
setUrl(v) {
this.url = v
},
getData(codeList) {
codeList = [codeList].flat().filter(Boolean).toString()
return request.post(this.url, null, {
withoutToken: true, params: {codeList}
}).then(res => res?.data && this.setStorage(res.data))
},
load(...code) {
return new Promise(resolve => {
this.resolves.push(resolve)
if (this.loading.length == 2) {
const [timer, codes] = this.loading;
clearTimeout(timer)
code = Array.from(new Set([codes, code].flat()))
}
const timer = setTimeout(() => {
this.getData(code).then(() => Promise.all(this.resolves.map(e => e())).then(() => this.resolves = []))
}, 500)
this.loading = [timer, code]
})
},
setStorage(data) {
let ds = this.getStorage()
data.map(p => {
if (ds.some(d => d.key == p.key)) {
const index = ds.findIndex(d => d.key == p.key)
ds.splice(index, 1, p)
} else {
ds.push(p)
}
})
localStorage.setItem("dicts", JSON.stringify([ds].flat()))
},
getDict(key) {
let dict = this.getStorage().find(e => e.key == key)
!dict && console.warn("字典%s缺少加载...", key)
return dict ? dict.values : []
},
getValue(key, label) {
let dict = this.getDict(key)
if (dict) {
let item = dict.find(v => v.dictName == label)
return item ? item.dictValue : label
} else return label
},
getLabel(key, value) {
let dict = this.getDict(key)
if (dict) {
let item = dict.find(v => v.dictValue == value)
return item ? item.dictName : value
} else return value
},
getColor(key, value) {
let dict = this.getDict(key)
if (dict) {
let item = dict.find(v => v.dictValue == value)
return item ? item.dictColor : value
} else return value
},
}
const dateUtil = {
timestampToTime(timestamp) {
// 时间戳为10位需*1000时间戳为13位不需乘1000
let date = new Date(timestamp);
let Y = date.getFullYear() + "-";
let M =
(date.getMonth() + 1 < 10
? "0" + (date.getMonth() + 1)
: date.getMonth() + 1) + "-";
let D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " ";
let h = date.getHours() + ":";
let m = date.getMinutes() + ":";
let s = date.getSeconds();
return Y + M + D + h + m + s;
}
}
export default {
dict,
dateUtil,
}

16
src/utils/install.js Normal file
View File

@@ -0,0 +1,16 @@
import Vue from 'vue' // 引入vue
function changeStr(str){
return str.charAt(0).toUpperCase() + str.slice(1)
}
const requireComponent = require.context('../components', false, /\.vue$/)
requireComponent.keys().forEach(fileName => {
const config = requireComponent(fileName)
const componentName = changeStr(
fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
)
Vue.component(componentName, config.default || config)
})

131
src/utils/product.js Normal file
View File

@@ -0,0 +1,131 @@
export function transform(leftData) {
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 = [];
for (let i = 0; i < leftData.productPropertyList.length; i++) {
rightData.productPropertyReqs.push({
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: 1,
pid: leftData.productPropertyList[i].pid,
templatePid: leftData.productPropertyList[i].templatePid,
valueExtendInfo: leftData.productPropertyList[i].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 = [
{
"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.productSkuSpecReqs = leftSkuItem.productSkuSpecList;
productSpecPropertyReqs.push({
"parentSpecId": leftSkuItem.productSkuSpecList[0].parentSpecId,
"parentSpecName": leftSkuItem.productSkuSpecList[0].parentSpecName,
"specId": leftSkuItem.productSkuSpecList[0].specId,
"specName": leftSkuItem.productSkuSpecList[0].specName,
"refPid": 0,
"pid": 0,
"templatePid": 0,
"propName": leftSkuItem.productSkuSpecList[0].specName,
"vid": 0,
"propValue": leftSkuItem.productSkuSpecList[0].specName,
"valueUnit": "",
"valueGroupId": 0,
"valueGroupName": "",
"valueExtendInfo": ""
});
rightSkuItem.productSkuId = 0;
rightSkuItem.productSkuWhExtAttrReq = {
"productSkuVolumeReq": leftSkuItem.productSkuWhExtAttr.productSkuVolume,
"productSkuWeightReq": leftSkuItem.productSkuWhExtAttr.productSkuWeight,
"productSkuBarCodeReqs": leftSkuItem.productSkuWhExtAttr.productSkuBarCodes,
"productSkuSensitiveAttrReq": {
"isSensitive": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveAttr.isSensitive,
"sensitiveList": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveAttr.sensitiveList},
"productSkuSensitiveLimitReq": leftSkuItem.productSkuWhExtAttr.productSkuSensitiveLimit,
};
rightSkuItem.currencyType = leftSkuItem.currencyType;
rightSkcItem.productSkuReqs.push(rightSkuItem);
}
rightSkcItem.productSkcId = 0;
rightSkc.push(rightSkcItem);
}
rightData.productSkcReqs = rightSkc;
// Spec
rightData.productSpecPropertyReqs = productSpecPropertyReqs;
rightData.carouselImageUrls = leftData.carouselImageUrls;
rightData.carouselImageI18nReqs = leftData.carouselImageI18nVOList;
rightData.materialImgUrl = leftData.materialImgUrl;
rightData.goodsLayerDecorationReqs = leftData.goodsLayerDecorationVOList;
rightData.sizeTemplateIds = !leftData.sizeTemplateIds ? []: leftData.sizeTemplateIds;
rightData.showSizeTemplateIds = !leftData.showSizeTemplateIds ? []: leftData.showSizeTemplateIds;
rightData.goodsModelReqs = !leftData.goodsModelList ? []: leftData.goodsModelList;
rightData.productWhExtAttrReq = {
outerGoodsUrl: leftData.productWhExtAttr.outerGoodsUrl,
productOrigin: {
countryShortName: leftData.productWhExtAttr.productOrigin.countryShortName
}
};
rightData.productCarouseVideoReqList = leftData.carouseVideoVOList;
rightData.goodsAdvantageLabelTypes = leftData.goodsAdvantageLabelVOList;
rightData.productDetailVideoReqList = leftData.detailVideoVOList;
rightData.productOuterPackageImageReqs = [];
for (let i = 0;i < leftData.outerPackageImages.length; i++) {
rightData.productOuterPackageImageReqs.push({
imageUrl: leftData.outerPackageImages[i].imageUrl
})
}
rightData.productOuterPackageReq = leftData.productWhExtAttr.productOuterPackage;
rightData.sensitiveTransNormalFileReqs = leftData.productWhExtAttr.sensitiveTransNormalFiles;
rightData.productGuideFileI18nReqs = leftData.productGuideFileI18nList;
rightData.productSaleExtAttrReq = {};
rightData.productDraftId = "";
return JSON.stringify(rightData);
}

260
src/view/CopyProduct.vue Normal file
View File

@@ -0,0 +1,260 @@
<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()">{{$store.state.mallName}}添加商品模板</el-button>
<el-button v-if="$store.state.mallName" type="button" :class="'el-button el-button--primary'" @click="addToDraft()">将商品模板添加到{{$store.state.mallName}}草稿箱</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="70%"
:before-close="handleClose">
<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>
<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} from '@/api/chromeApi'
import {timestampToTime} from '@/utils/date'
import {transform} from '@/utils/product'
import { Message } from 'element-ui'
export default {
name: 'List',
data () {
return {
search: {
current: 1,
size: 10,
productName: '',
mallName: '',
startDate: '',
endDate: ''
},
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, 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' }
],
productIds: [],
}
},
created () {
this.getList()
},
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() {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/skc/pageQuery',
needMallId: true,
data: {
...this.productPage
}}).then((res) => {
if (res.errorCode == 1000000) {
this.productPage.total = res.result.total
this.productTableData = res.result.pageItems.map((item) => {
console.log(item.createdAt)
return {
productSpu: item.productId,
productSkc: item.productSkcId,
productName: item.productName,
createTime: timestampToTime(item.createdAt)
};
})
}
});
},
productHandleSelectionChange(val) {
this.productIds = [];
val.forEach(e => {
this.productIds.push(e.productSpu);
});
},
saveProduct() {
if (this.productIds.length <= 0) {
alert('请选择商品');
return;
}
this.productIds.map((productSpu, index) => {
sendChromeAPIMessage({
url: 'bg-visage-mms/product/query',
needMallId: true,
data: {
productEditTaskUid: '',
productId: productSpu
}}).then((res) => {
if (res.errorCode == 1000000) {
let content = transform(res.result)
this.$http.post('/api/product/add', {
mallName: this.$store.state.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()
}
}
})
}
})
})
},
addToDraft() {
if (this.ids.length <= 0) {
alert('请选择商品模板');
return;
}
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,
data: {
catId: 26117
}}).then((res) => {
if (res.errorCode == 1000000) {
let draftId = res.result.productDraftId
let content = JSON.parse(product[0].content)
content.productDraftId = draftId
sendChromeAPIMessage({
url: 'bg-visage-mms/product/draft/save',
needMallId: true,
data: {
...content
}}).then((res) => {
if (res.errorCode == 1000000) {
Message.success("商品【" + product[0].productName + "】成功添加到草稿箱")
}
})
}
})
}, 1000*index)
})
}
}
}
</script>
<style scoped lang="scss">
</style>

206
src/view/ExportSaleData.vue Normal file
View File

@@ -0,0 +1,206 @@
<template>
<ai-list class="list">
<ai-title
slot="title"
title="销售数据"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
</template>
<template #right>
<json-excel
:data="list"
:fields="jsonFields"
:before-generate = "startDownload"
name="销售数据.xls"
worksheet="销售统计">
<el-button size="small">导出销售数据</el-button>
</json-excel>
</template>
</ai-search-bar>
<ai-table
:isShowPagination="false"
:tableData="list"
:col-configs="colConfigs"
:total="list.length"
style="margin-top: 8px;"
@getList="() => {}">
</ai-table>
</template>
</ai-list>
</template>
<script>
import {sendChromeAPIMessage} from '@/api/chromeApi'
import JsonExcel from 'vue-json-excel'
import { Message } from 'element-ui'
export default {
name: 'ExportSaleData',
data () {
return {
list: [],
pageSize: 10,
currentPage: 1,
colConfigs: [
{
prop: 'productName',
label: '商品名称',
align: 'left'
},
{
prop: 'productId',
label: 'SPU',
align: 'center'
},
{
prop: 'productSkcId',
label: 'SKC',
align: 'center'
},
{
prop: 'className',
label: 'SKU属性',
align: 'center'
},
{
prop: 'supplierPrice',
label: '申报价格(CNY)',
align: 'center',
format: v => v / 100
},
{
prop: 'isVerifyPrice',
label: '开款核价状态',
align: 'center',
format: v => v ? '核价通过': '核价未通过 / 无法备货'
},
],
jsonFields: {
"商品名称": "productName",
"SPU": "productId",
"SKC": "productSkcId",
"SKU属性": "className",
"申报价格(CNY)": {
"field": "supplierPrice",
callback: (value) => {
return value /100;
}
},
"SKU货号": "skuExtCode",
"开款核价状态": {
"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"
}
}
},
components: {
JsonExcel
},
created () {
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;
} else {
this.getList()
}
}
})
},
methods: {
getList () {
sendChromeAPIMessage({
url: 'marvel-mms/cn/api/kiana/venom/sales/management/list',
needMallId: true,
data: {
pageNo: this.currentPage,
pageSize: this.pageSize,
isLack: 0,
priceAdjustRecentDays: 7
}}).then((res) => {
if (res.errorCode == 1000000) {
// this.list = res.result.subOrderList
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.purchaseConfig = item.purchaseConfig;
for(let j = 0;j < item.skuQuantityDetailList.length; j++) {
data = {...data, ...item.skuQuantityDetailList[j]}
this.list.push(data);
}
}
if (this.pageSize == res.result.subOrderList.length) {
this.currentPage ++
this.getList()
} else {
Message.success('销售数据加载完成,可进行导出')
}
}
console.log(this.list)
});
},
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">
</style>

387
src/view/Home.vue Normal file
View File

@@ -0,0 +1,387 @@
<template>
<div class="admin">
<div class="admin-top">
<div class="logo">
<!-- <img src="../../assets/images/logo.png" /> -->
<div>
<h1>TEMU助手</h1>
</div>
</div>
<div class="admin-right">
<div class="left" @click="toActive">
<span>{{ $store.state.userInfo.name + "(" + $store.state.userInfo.phone + ")" }}</span>
<div :style="{marginLeft: '10px', color: $store.state.userInfo.flag == 1? 'green': 'red'}">({{ getStateInfo }})</div>
</div>
<el-dropdown @command="onMallChange" v-if="mallName">
<div class="userinfo">
<span>{{ mallName }}</span>
<img src="../assets/images/bottom.png" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="(item, index) in mallList"
:command="item"
:key="index">
{{ item.mallName }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<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">
<el-menu-item index="/welcome">
<i class="el-icon-monitor"></i>
<span slot="title">工作台</span>
</el-menu-item>
<el-menu-item index="/normalSendGoods">
<i class="el-icon-shopping-cart-2"></i>
<span slot="title">普通备货单</span>
</el-menu-item>
<el-menu-item index="/copyProduct">
<i class="el-icon-document-copy"></i>
<span slot="title">商品复制</span>
</el-menu-item>
<el-menu-item index="/saleData">
<i class="el-icon-s-data"></i>
<span slot="title">销售数据</span>
</el-menu-item>
<el-menu-item index="/learning">
<i class="el-icon-s-data"></i>
<span slot="title">学习园地</span>
</el-menu-item>
<!-- <el-menu-item index="/statistics">
<i class="el-icon-s-data"></i>
<span slot="title">数据统计</span>
</el-menu-item> -->
</el-menu>
<div style="position: absolute; bottom: 20px; padding-left: 20px; ">
<div style="cursor: pointer; font-size: 16px;" @click="$store.commit('SignOut', true)">
<i class="el-icon-back" style="color: white; margin-right: 5px;"></i>
<span style="color: white">退出</span>
</div>
</div>
</el-scrollbar>
<div class="container">
<transition name="fade-transform" mode="out-in">
<keep-alive include="NormalSendGoods">
<router-view class="container-app"></router-view>
</keep-alive>
</transition>
</div>
</div>
<el-dialog
title="用户激活"
:visible="$store.state.activeDlgShow"
:close-on-click-modal="false"
width="30%"
:before-close="handleClose">
<el-form :model="form" label-position="top" ref="form" label-width="100px" class="form">
<el-form-item
prop="mallName"
label="当前绑定账号"
:rules="[{ required: true, message: '请先登录拼多多跨境卖家中心', trigger: 'blur' }]">
<el-input readonly placeholder="请先登录拼多多跨境卖家中心" v-model="form.mallName"></el-input>
</el-form-item>
<el-form-item
prop="mallId"
v-show="false"
:rules="[{ message: '请输入商城ID', trigger: 'blur' }]">
<el-input placeholder="请输入商城ID" v-model="form.mallId"></el-input>
</el-form-item>
<el-form-item
prop="code"
label="激活码"
:rules="[{ required: true, message: '请输入激活码', trigger: 'blur' }]">
<el-input placeholder="请输入激活码" v-model="form.code"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="$store.commit('setActiveDlgShow', false)"> </el-button>
<el-button type="primary" @click="active"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
isCollapse: false,
activePath: '/home',
form: {
mallId: this.$store.state.mallId,
mallName: this.$store.state.mallName,
code: ''
},
vipType: ["体验会员","月会员","半年会员","年会员","年会员多店通用"]
}
},
computed: {
getStateInfo() {
if (this.$store.state.userInfo.flag == 0) {
return '未激活';
} else if (this.$store.state.userInfo.flag == 1) {
return this.vipType[this.$store.state.userInfo.type] + '/有效期至' + this.$store.state.userInfo.expireTime;
} else {
return '已过期';
}
},
...mapState(['mallName', 'mallList'])
},
watch: {
$route (v) {
this.activePath = v.fullPath
}
},
created () {
this.activePath = this.$route.fullPath
},
methods: {
onMallChange (e) {
this.$store.commit('setMallName', e.mallName)
this.$store.commit('setMallId', e.mallId)
location.reload()
},
handleClose() {
this.form.mallId = "";
this.form.mallName = "";
this.form.code = "";
this.$store.commit('setActiveDlgShow', false)
},
toActive() {
this.$store.commit('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.$store.commit('setActiveDlgShow', false)
}
})
})
}
});
}
})
}
}
}
</script>
<style lang="scss" scoped>
.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;
border-top: 1px solid rgba(0,0,0,0.15);
.container {
flex: 1;
height: 100%;
overflow: hidden;
.container-app {
width: 100%;
height: 100%;
overflow-y: auto;
}
}
.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: center;
cursor: pointer;
&:hover {
opacity: 0.6;
}
img {
width: 42px;
height: 42px;
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;
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;
}
}
}
.admin-right {
display: flex;
align-items: center;
height: 100%;
.left {
display: flex;
align-items: center;
cursor: pointer;
transition: all ease 0.3s;
&: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;
&:hover {
opacity: 0.5;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
span {
margin-right: 8px;
color: #1F2635;
font-size: 14px;
}
}
}
}
}
</style>

97
src/view/Learning.vue Normal file
View File

@@ -0,0 +1,97 @@
<template>
<ai-list class="Learning">
<ai-title
slot="title"
title="学习园地"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<el-radio-group v-model="search.categoryId">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="学院">我的收藏</el-radio-button>
<el-radio-button :label="item.id" :key="item.id" v-for="item in cateList">{{ item.name }}</el-radio-button>
</el-radio-group>
</template>
<template #right>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
style="margin-top: 8px;"
@getList="getList">
<el-table-column slot="options" label="操作" align="center" fixed="right" width="90px">
<template v-slot="{row}">
<div class="table-options">
<el-button type="text">收藏</el-button>
<el-button type="text" @click="toDetail(row.url)">详情</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
export default {
name: 'Learning',
data () {
return {
colConfigs: [
{ prop: 'title', label: '标题', align: 'left' },
{ prop: 'subPurchaseOrderSn', label: '是否收藏', align: 'center' },
],
tableData: [],
total: 0,
search: {
current: 1,
size: 10,
categoryId: ''
},
cateList: []
}
},
created () {
this.getCateList()
this.getList()
},
methods: {
toDetail (url) {
window.open(url)
},
getCateList () {
this.$http.post('/api/learningCategory/page?size=50').then(res => {
if (res.code === 0) {
this.cateList = res.data.records
}
})
},
getList () {
this.$http.post('/api/learning/pluginPage', null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,336 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
title="普通备货单"
tips="请先在当前浏览器登录“拼多多跨境卖家中心”,期间保持登录状态"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<el-button type="button" :icon="isBegin? 'el-icon-loading': ''" :class="isBegin ? 'el-button el-button--danger': 'el-button el-button--primary'" @click="beginRob()">{{isBegin ? '结束抢仓': '开始抢仓'}}</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"
:isShowPagination="false"
style="margin-top: 8px;"
@selection-change="handleSelectionChange"
@getList="getList">
<el-table-column slot="productName" width="400px" label="商品信息" show-overflow-tooltip align="left">
<template slot-scope="scope">
<div>
<div class="order-manage_productInfo__1pD83">
<img :src="scope.row.productSkcPicture">
<div>备货母单号: {{ scope.row.originalPurchaseOrderSn }}</div>
<div>{{ scope.row.productName }}</div>
<div>SKC: {{ scope.row.productSkcId }}</div>
<div>货号: {{ scope.row.productSn }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column slot="robInfo" label="抢仓信息" show-overflow-tooltip align="left">
<template slot-scope="scope">
<div data-testid="beast-core-box" style="margin-right: 2px;">
已抢仓 <div style="color: red; display: inline;">{{ scope.row.robTotal }}</div>
</div>
</template>
</el-table-column>
<el-table-column slot="status" label="状态" show-overflow-tooltip align="left">
<template slot-scope="scope">
<div data-testid="beast-core-box" class="outerWrapper-1-3-1 outerWrapper dot-module__dot___M-RuH " style="margin-right: 2px;">
<div class="dot-module__circle___2l2UV" style="background-color: var(--pc-dot-warn-bg-color,#ff6800);"></div>待发货
</div>
</template>
</el-table-column>
<el-table-column slot="className" label="SKU信息" width="400px" show-overflow-tooltip align="left">
<template slot-scope="scope">
<div class="order-manage_skuInfo__FW-Nd" v-for="(item, index) in scope.row.detailList">
<div>
<div data-testid="beast-core-box" class="outerWrapper-1-3-1 outerWrapper-d18-1-3-20 index-module__image-preview___2fiZX">
<div class="index-module__img___p3B1N" :style="getStyle(item.thumbUrlList[0])"></div>
</div>
</div>
<div class="order-manage_contentInfo__1Cjd6" style="max-width: 150px;">
<div class="order-manage_title__1VTO5">属性{{ item.className }}下单数量{{ item.purchaseQuantity }}</div>
<div>SKU ID {{ item.productSkuId }}</div>
<div class="order-manage_productSku__XP_ke">SKU 货号 {{ item.extCode }}</div>
</div>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</div>
</template>
<script>
import {sendChromeAPIMessage, sendChromeNotification} from '@/api/chromeApi'
import { Message } from 'element-ui'
export default {
name: 'NormalSendGoods',
data () {
return {
colConfigs: [
{ type: "selection", width: '70px', align: 'left', fixed: 'left'},
{ prop: 'subPurchaseOrderSn', label: '备货单号', width: '150px', align: 'left', fixed: 'left' },
{ slot: 'productName', fixed: 'left'},
{ slot: 'className'},
{ slot: 'robInfo'},
{ slot: 'status' }
],
tableData: [],
total: 0,
ids: [],
isBegin: false,
timer: null
}
},
created () {
this.getList()
},
activated () {
this.getList()
},
methods: {
getList () {
sendChromeAPIMessage({
url: 'oms/bg/venom/api/supplier/purchase/manager/querySubOrderList',
needMallId: true,
data: {
"pageNo": 1,
"pageSize": 100,
"urgencyType": 0,
"isCustomGoods": false,
"statusList": [
1
]
}}).then((res) => {
if (res.errorCode == 1000000) {
res.result.subOrderForSupplierList = res.result.subOrderForSupplierList.filter((item) => {
return item.isCanJoinDeliverPlatform;
})
this.tableData = res.result.subOrderForSupplierList.map((item) => {
return {
robTotal: 0,
subPurchaseOrderSn: item.subPurchaseOrderSn,
originalPurchaseOrderSn: item.originalPurchaseOrderSn,
productName: item.productName,
productSn: item.productSn,
productSkcId: item.productSkcId,
productSkcPicture: item.productSkcPicture,
status: item.status,
detailList: item.skuQuantityDetailList
};
})
} else {
Message.error("【拼多多】" + res.error_msg)
}
})
},
handleSelectionChange(val) {
this.ids = [];
val.forEach(e => {
this.ids.push(e.subPurchaseOrderSn);
});
},
getStyle(url) {
return "background-image: url(" + url + "); width: 72px; height: 72px; cursor: pointer; border-radius: 6px; border: 1px solid rgba(0, 0, 0, 0.14);";
},
beginRob() {
if (this.isBegin) {
this.isBegin = false;
for (let i = 0;i < this.timer.length; i++) {
clearInterval(this.timer[i].timer);
}
return;
}
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;
}
if (res.data.type != 4 && this.$store.state.mallId != this.$store.state.userInfo.mallId) {
Message.error('您当前登录的TEMU账号与会员绑定账号不一致')
return;
}
if (this.ids.length <= 0) {
Message.error('请选择抢仓商品');
return;
}
this.isBegin = true;
this.timer = [];
for (let i = 0;i < this.ids.length; i++) {
let t = setInterval(this.robFunc(this.ids[i]), 1000);
this.timer.push({id: this.ids[i], timer: t})
}
let _this = this;
let tt = setInterval(function() {
if (_this.ids.length == 0) {
_this.isBegin = false;
clearInterval(tt);
}
}, 1000)
} else {
console.log("获取用户信息失败")
}
});
},
robFunc(sn) {
let _this = this;
return function() {
_this.rob(sn);
}
},
rob(sn) {
sendChromeAPIMessage({
url: 'oms/bg/venom/api/supplier/purchase/manager/joinDeliveryGoodsOrderPlatform',
needMallId: true,
data: {
"subPurchaseOrderSn": sn
}}).then((res) => {
if (res.errorCode == 1000000) {
for (let i = 0;i < this.ids.length; i++) {
if (this.ids[i] == sn) {
this.ids.splice(i, 1)
break;
}
}
let t = this.timer.filter((item) => {
return item.id == sn;
})
if (t.length > 0) {
clearInterval(t[0].timer)
}
for (let j = 0; j < this.tableData.length; j++) {
if (this.tableData[j].subPurchaseOrderSn == sn) {
this.sendSms(this.tableData[j].productName);
this.sendNotification(this.tableData[j].productName);
this.tableData.splice(j, 1);
break;
}
}
} else {
for (let j = 0; j < this.tableData.length; j++) {
if (this.tableData[j].subPurchaseOrderSn == sn) {
this.tableData[j].robTotal ++;
break;
}
}
}
})
},
sendSms(productName) {
this.$http.post(`/api/sms/sendSuccessSms`, null, {
params: {
productName: productName
}
}).then(res => {
console.log(res)
})
},
sendNotification(productName) {
sendChromeNotification({productName: productName})
}
}
}
</script>
<style scoped lang="scss">
.order-manage_productInfo__1pD83>img {
left: 0;
position: absolute;
top: 0;
width: 60px;
}
.order-manage_productInfo__1pD83 {
min-height: 60px;
padding-left: 70px;
position: relative;
}
.order-manage_productInfo__1pD83>div:nth-child(2) {
color: rgba(0,0,0,.8);
}
.outerWrapper {
margin: 4px 4px 0px 0px;
}
.dot-module__dot___M-RuH .dot-module__circle___2l2UV {
flex-shrink: 0;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
}
.dot-module__dot___M-RuH {
display: inline-flex;
align-items: center;
}
.index-module__image-preview___2fiZX .index-module__img___p3B1N {
width: 60px;
height: 60px;
background-repeat: no-repeat;
background-size: cover;
background-color: #f5f5f5;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
color: var(--bc-Table-emptyTextColor);
position: relative;
}
.order-manage_skuInfo__FW-Nd, .order-manage_skuInfo__FW-Nd .order-manage_contentInfo__1Cjd6 {
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
}
.order-manage_skuInfo__FW-Nd .order-manage_contentInfo__1Cjd6 {
-webkit-flex-direction: column;
-moz-box-orient: vertical;
-moz-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
color: rgba(0,0,0,.4);
-webkit-flex: 1 0 auto;
-moz-box-flex: 1;
-ms-flex: 1 0 auto;
flex: 1 0 auto;
}
.order-manage_skuInfo__FW-Nd .order-manage_contentInfo__1Cjd6 .order-manage_title__1VTO5 {
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
color: rgba(0,0,0,.8);
}
</style>

69
src/view/Statistics.vue Normal file
View File

@@ -0,0 +1,69 @@
<template>
<ai-list class="list">
<ai-title
slot="title"
title="数据统计"
isShowBottomBorder>
</ai-title>
<template slot="content">
<ai-card title="数据统计">
<div id="chart1"></div>
</ai-card>
</template>
</ai-list>
</template>
<script>
import { Line } from '@antv/g2plot'
import {sendChromeAPIMessage} from '@/api/chromeApi'
export default {
name: 'ExportSaleData',
data () {
return {
}
},
mounted () {
this.$nextTick(() => {
this.initChart1()
})
},
methods: {
initChart1 () {
const data = [
{ year: '1991', value: 3 },
{ year: '1992', value: 4 },
{ year: '1993', value: 3.5 },
{ year: '1994', value: 5 },
{ year: '1995', value: 4.9 },
{ year: '1996', value: 6 },
{ year: '1997', value: 7 },
{ year: '1998', value: 9 },
{ year: '1999', value: 13 }
]
const line = new Line('chart1', {
data,
xField: 'year',
yField: 'value',
});
line.render();
}
}
}
</script>
<style scoped lang="scss">
.list {
::v-deep .ai-list__content .ai-list__content--right-wrapper {
border-radius: 0;
padding: 0!important;
box-shadow: none;
background: transparent;
}
}
</style>

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

@@ -0,0 +1,247 @@
<template>
<div class="admin-home" v-loading="isLoading">
<ai-card hideTitle>
<div class="content">
<p>TEMU助手是为致力于拼多多跨境卖家中心通过自动化的方式提高TEMU运营管理效率</p>
<p>在使用卖家中心过程中有觉得不方便之处或者重复劳动力的地方可与我们沟通我们竭尽全力为大家打造实用的智能化工具</p>
<p>欢迎大家扫描下方二维码与我们沟通交流获取最新智能化研发进展一起经营跨境电商让中国货走向全世界</p>
</div>
</ai-card>
<div class="middle">
<ai-card title="通知公告">
<div class="list">
<div class="item" v-for="(item, index) in noticeList" :key="index" @click="info = item, isShow = true">
<div class="left">
<el-tag size="small" effect="dark" :type="item.level === '0' ? '' : 'danger'">{{ item.level === '0' ? '一般' : '重要' }}</el-tag>
<h2 :title="item.title">{{ item.title }}</h2>
</div>
<span>{{ item.createTime }}</span>
<!-- <img src="../assets/images/right-b.png" /> -->
</div>
<AiEmpty v-if="!noticeList.length"></AiEmpty>
</div>
</ai-card>
<ai-card title="更新记录">
<div class="list">
<div class="item" v-for="(item, index) in changeLogList" :key="index" @click="info = item, isShow = true">
<div class="left">
<el-tag v-if="version !== item.varsion" size="small" effect="dark" type="danger">新版本</el-tag>
<h2 :title="item.title">{{ item.title }}</h2>
</div>
<span>{{ item.createTime }}</span>
</div>
<AiEmpty v-if="!changeLogList.length"></AiEmpty>
</div>
</ai-card>
</div>
<ai-card title="常用工具" v-if="false">
<div class="">
dsad
</div>
</ai-card>
<AiDialog
title="详情"
:visible.sync="isShow"
:close-on-click-modal="false"
customFooter
:show-close="!isImportant"
width="690">
<div class="news">
<h2>{{ info.title }}</h2>
<p>{{ info.content }}</p>
</div>
<div class="dialog-footer" slot="footer">
<el-button @click="read" type="primary" v-if="!info.varsion">{{ isImportant ? '我已阅读' : '关闭' }}{{ info.version }}</el-button>
<el-button @click="download" type="primary" style="width: 140px" v-if="info.varsion && info.varsion !== version">下载新版本</el-button>
</div>
</AiDialog>
</div>
</template>
<script>
export default {
name: 'AdminHome',
data () {
return {
noticeList: [],
isLoading: false,
isShow: false,
info: {},
changeLogList: [],
isImportant: false,
version: ''
}
},
created () {
const devVersion = require('../manifest.development.json').version
const prodVersion = require('../manifest.production.json').version
this.version = process.env.NODE_ENV === 'production' ? prodVersion : devVersion
console.log(this.version)
this.getNoticeList()
this.getChangelog()
this.getMyNewestNotice()
},
methods: {
getNoticeList () {
this.$http.post('/api/notice/page?size=20').then(res => {
if (res.code === 0) {
this.noticeList = res.data.records
}
})
},
download () {
const link = document.createElement('a')
link.style.display = 'none'
link.href = this.info.downloadUrl
link.setAttribute('target', '_blank')
link.setAttribute('download', `TEMU助手-${this.info.varsion}.zip`)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
},
read () {
if (this.isImportant) {
this.$http.post('/api/notice/read').then(res => {
if (res.code === 0) {
this.isImportant = false
this.isShow = false
}
})
} else {
this.isShow = false
}
},
getMyNewestNotice () {
this.$http.post('/api/notice/getMyNewestNotice').then(res => {
if (res.code === 0) {
if (res.data) {
this.isImportant = true
this.info = res.data
this.isShow = true
}
}
})
},
getChangelog () {
this.$http.post('/api/changelog/page?size=100').then(res => {
if (res.code === 0) {
this.changeLogList = res.data.records
}
})
}
}
}
</script>
<style lang="scss" scoped>
.admin-home {
padding: 20px 20px;
.news {
min-height: 250px;
h2 {
margin-bottom: 20px;
text-align: center;
font-size: 18px;
font-weight: 600;
color: #000;
}
span {
display: block;
margin-bottom: 20px;
text-align: right;
color: #999;
}
p {
line-height: 1.5;
margin-bottom: 14px;
color: #333;
font-size: 15px;
text-indent: 2em;
}
}
.content {
p {
line-height: 1.3;
margin-bottom: 14px;
color: #333;
font-size: 14px;
&:last-child {
margin-bottom: 0;
}
}
}
.list {
overflow: auto;
padding-bottom: 20px;
.item {
display: flex;
align-items: center;
justify-content: space-between;
height: 49px;
cursor: pointer;
color: #1f2635;
transition: all .3s ease-in-out;
border-bottom: 1px solid #f4f4f4;
.left, .right {
display: flex;
align-items: center;
}
.left {
max-width: 70%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
h2 {
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.el-tag {
margin-right: 10px;
}
}
& > span {
color: #999;
}
&:hover {
color: #1FBAD6;
}
}
}
.middle {
display: flex;
height: 400px;
& > section {
flex: 1;
&:first-child {
margin-right: 20px;
}
}
}
}
</style>

347
src/view/login/Login.vue Normal file
View File

@@ -0,0 +1,347 @@
<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="mobile"
:rules="[{ required: true, message: '请输入手机号', trigger: 'blur' }, { validator: phoneReg, trigger: 'blur' }]">
<el-input maxlength="11" placeholder="请输入手机号" v-model="form.mobile" @keyup.enter.native="login"></el-input>
</el-form-item>
<el-form-item
prop="password"
:rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
<el-input placeholder="请输入密码" v-model="form.password" type="password" @keyup.enter.native="login"></el-input>
</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">
<span>没有账号</span>
<i class="hover" @click="$router.push('/register')">立即注册</i>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
form: {
mobile: '',
password: ''
},
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) {
this.btnLoading = true
this.$http.post(`/api/mobile/token`, this.form, {
params: {
...this.form
},
headers: {
Authorization: 'Basic YXBwOmFwcA=='
}
}).then(res => {
if (res.access_token) {
this.$store.commit('setToken', `${res.token_type} ${res.access_token}`)
this.$message.success('登录成功')
this.$store.dispatch('getUserInfo')
setTimeout(() => {
this.$router.push('/')
}, 800)
}
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
} else {
console.log('error submit!!')
return false;
}
})
},
getCode () {
if (this.isSend) {
return this.$message.error('验证码已发送')
}
this.$refs.form.validateField('loginname', e => {
if (!e) {
this.isSend = true
this.btnLoading = true
this.$http.post(`/api/sms/getRegSmsCode?phone=${this.form.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;
font-size: 20px;
text-align: center;
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>

367
src/view/login/Register.vue Normal file
View File

@@ -0,0 +1,367 @@
<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="name"
:rules="[{ required: true, message: '请输入姓名', trigger: 'blur' }]">
<el-input placeholder="请输入姓名" v-model="form.name"></el-input>
</el-form-item>
<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">
<span>有账号</span>
<i class="hover" @click="$router.back()">返回登录</i>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
form: {
name: '',
password: '',
repassword: '',
code: '',
phone: ''
},
phoneReg: (rule, value, callback) => {
if (/^1[0-9]{10,10}$/.test(value)) {
return callback()
}
callback(new Error('手机号格式错误'))
},
timer: null,
time: 60,
isSend: false,
isStart: false,
isLoading: false,
currIndex: 0,
btnLoading: false,
loginType: '0'
}
},
mounted () {
},
methods: {
login () {
this.$refs.form.validate((valid) => {
if (valid) {
if (this.form.password != this.form.repassword) {
this.$message.success('两次密码输入不一致');
return;
}
this.btnLoading = true
this.$http.post(`/api/malluser/reg`, {
...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 {
console.log('error submit!!')
return false;
}
})
},
getCode () {
if (this.isSend) {
return this.$message.error('验证码已发送')
}
this.$refs.form.validateField('phone', e => {
if (!e) {
this.isSend = true
this.btnLoading = true
this.$http.post(`/api/sms/getRegSmsCode?phone=${this.form.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>

56
src/view/popup.vue Normal file
View File

@@ -0,0 +1,56 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
import { sendChromeAPIMessage } from '@/api/chromeApi'
import { Message } from 'element-ui'
export default {
data () {
return {
component: 'Home'
}
},
created () {
if (!this.$store.state.token) {
this.$router.push('/login')
} else {
this.$store.dispatch('getUserInfo');
sendChromeAPIMessage({url: 'bg/quiet/api/mms/userInfo', needMallId: false, data: {}}).then((res) => {
if (res.errorCode == 1000000) {
if (res.result.companyList[0].malInfoList.length == 1) {
this.$store.commit('setMallId', `${res.result.companyList[0].malInfoList[0].mallId}`)
this.$store.commit('setMallName', `${res.result.companyList[0].malInfoList[0].mallName}`)
} else {
if (!this.$store.state.mallId) {
this.$store.commit('setMallId', `${res.result.companyList[0].malInfoList[0].mallId}`)
this.$store.commit('setMallName', `${res.result.companyList[0].malInfoList[0].mallName}`)
} else {
let temp = res.result.companyList[0].malInfoList.filter(item => {
return item.mallId == this.$store.state.mallId
})
if (temp.length == 0) {
this.$store.commit('setMallId', `${res.result.companyList[0].malInfoList[0].mallId}`)
this.$store.commit('setMallName', `${res.result.companyList[0].malInfoList[0].mallName}`)
}
}
}
this.$store.commit('setMallList', res.result.companyList[0].malInfoList)
} else {
Message.error("【拼多多】" + res.error_msg)
}
})
}
},
methods: {
}
}
</script>
<style>
@import '../assets/css/index.scss';
</style>

77
vue.config.js Normal file
View File

@@ -0,0 +1,77 @@
const path = require('path')
const fs = require('fs')
// Generate pages object
const pages = {}
function getEntryFile (entryPath) {
let files = fs.readdirSync(entryPath)
return files
}
const chromeName = getEntryFile(path.resolve(`src/entry`))
function getFileExtension (filename) {
return /[.]/.exec(filename) ? /[^.]+$/.exec(filename)[0] : undefined
}
chromeName.forEach((name) => {
const fileExtension = getFileExtension(name)
const fileName = name.replace('.' + fileExtension, '')
pages[fileName] = {
entry: `src/entry/${name}`,
template: 'public/index.html',
filename: `${fileName}.html`
}
})
const isDevMode = process.env.NODE_ENV === 'development'
module.exports = {
pages,
filenameHashing: false,
chainWebpack: (config) => {
config.plugin('copy').use(require('copy-webpack-plugin'), [
{
patterns: [
{
from: path.resolve(`src/manifest.${process.env.NODE_ENV}.json`),
to: `${path.resolve('dist')}/manifest.json`
},
{
from: path.resolve(`public/`),
to: `${path.resolve('dist')}/`
}
]
}
])
},
devServer: {
port: 8080,
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
'/api': {
target: 'http://pdd.jjcp52.com',
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': '/'
}
}
}
},
lintOnSave: false,
configureWebpack: {
output: {
filename: `[name].js`,
chunkFilename: `[name].js`
},
devtool: isDevMode ? 'inline-source-map' : false
},
css: {
extract: false // Make sure the css is the same
}
}

7193
yarn.lock Normal file

File diff suppressed because it is too large Load Diff