This commit is contained in:
刘仕伟
2024-01-20 01:37:46 +08:00
parent 492e6b83ea
commit 21b754497b
10 changed files with 593 additions and 554 deletions

View File

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

View File

@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "TEMU助手",
"description": "TEMU助手 - 自动化提高生产效率",
"version": "3.1.6",
"version": "3.1.7",
"background": {
"service_worker": "/background.js"
},

View File

@@ -138,7 +138,12 @@ const router = new VueRouter({
{
path: 'saleData',
name: 'saleData',
component: () => import('../view/ExportSaleData.vue')
component: () => import('../view/sale/ExportSaleData.vue')
},
{
path: 'saleOut',
name: 'saleOut',
component: () => import('../view/sale/ExportSaleOutData.vue')
},
// {

View File

@@ -98,7 +98,7 @@
<el-menu-item index="/newProduct">上架新品</el-menu-item>
<el-menu-item index="/newProductGroup">我的分组</el-menu-item>
</el-submenu>
<!-- <el-menu-item index="/singleTrack">单品跟踪</el-menu-item> -->
<el-menu-item index="/singleTrack">单品跟踪</el-menu-item>
<el-menu-item index="/bestSellers">7天畅销品</el-menu-item>
<el-menu-item index="/indexTrack">首页商品跟踪</el-menu-item>
</el-submenu>
@@ -115,10 +115,14 @@
</template>
</el-menu-item>
</el-submenu>
<el-menu-item index="/saleData">
<i class="el-icon-s-data"></i>
<span slot="title">销售数据</span>
</el-menu-item>
<el-submenu index="/saleManager">
<template slot="title">
<i class="el-icon-s-data"></i>
<span slot="title">销售管理</span>
</template>
<el-menu-item index="/saleData">销售管理</el-menu-item>
<el-menu-item index="/saleOut">售罄看板</el-menu-item>
</el-submenu>
<el-menu-item index="/learning">
<i class="el-icon-eleme"></i>
<span slot="title">新手园地</span>

View File

@@ -851,7 +851,7 @@ import { Message } from 'element-ui'
return
}
let beginDateStr = formatDate(this.skuDownloadForm.date[0])
let endDateStyr = formatDate(this.skuDownloadForm.date[1])
let endDateStr = formatDate(this.skuDownloadForm.date[1])
let temp = {}
temp = {
@@ -872,9 +872,9 @@ import { Message } from 'element-ui'
let tempSkuList = this.list.filter(item => {
return item.onSalesDurationOffline != '-天'
})
this.tempSkuIds = []
let tempSkuIds = []
tempSkuList.map(i => {
this.tempSkuIds.push(i.productSkuId)
tempSkuIds.push(i.productSkuId)
})
this.skuSaleNumberList = []
@@ -898,34 +898,43 @@ import { Message } from 'element-ui'
this.skuSaleNumberList.push(temp)
}
})
let res = await sendChromeAPIMessage({
url: 'oms/bg/venom/api/supplier/sales/management/querySkuSalesNumber',
needMallId: true,
mallId: this.mallId,
data: {
"productSkuIds": this.tempSkuIds,
"startDate": beginDateStr,
"endDate": endDateStyr
}})
let reqSkusIds = [], pageSize = 200
for (let i = 0; i < tempSkuIds.length; i++) {
reqSkusIds.push(tempSkuIds[i])
if (reqSkusIds.length % pageSize == 0 || ((i+1) == tempSkuIds.length)) {
let res = await sendChromeAPIMessage({
url: 'oms/bg/venom/api/supplier/sales/management/querySkuSalesNumber',
needMallId: true,
mallId: this.mallId,
data: {
"productSkuIds": reqSkusIds,
"startDate": beginDateStr,
"endDate": endDateStr
}})
if (res.success) {
res.result.map(item => {
for (let i = 0; i < this.skuSaleNumberList.length; i++) {
if (this.skuSaleNumberList[i].sku == item.prodSkuId) {
this.skuSaleNumberList[i][item.date] = item.salesNumber
break
if (res.success) {
res.result.map(item => {
for (let i = 0; i < this.skuSaleNumberList.length; i++) {
if (this.skuSaleNumberList[i].sku == item.prodSkuId) {
this.skuSaleNumberList[i][item.date] = item.salesNumber
break
}
}
}
})
document.getElementById('downloadSkuSaleNumber').click()
} else {
Message.error("获取SKU历史销量数据失败")
})
} else {
this.isLoading = false
Message.error("获取SKU历史销量数据失败")
break
}
if ((i+1) == tempSkuIds.length) {
document.getElementById('downloadSkuSaleNumber').click()
this.isLoading = false
}
reqSkusIds = []
}
this.isLoading = false
}
}
}
}

View File

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

View File

@@ -11,7 +11,6 @@
<script>
import List from './components/List.vue'
import Detail from './components/Detail.vue'
import SheinDetail from './components/SheinDetail.vue'
export default {
name: 'SingleTrack',
@@ -19,8 +18,7 @@
components: {
List,
Detail,
SheinDetail
Detail
},
data () {
@@ -37,11 +35,6 @@
this.params = data.params
}
if (data.type === 'SheinDetail') {
this.component = 'SheinDetail'
this.params = data.params
}
if (data.type === 'List') {
this.component = 'List'
this.params = data.params

View File

@@ -3,7 +3,7 @@
<ai-list class="list">
<ai-title
slot="title"
:title="'店铺ID(' + `${content}` + ')商品列表'"
:title="'分组(' + `${content}` + ')商品列表'"
isShowBottomBorder isShowBack @onBackClick="cancel(false)">
</ai-title>
<template slot="content">
@@ -26,10 +26,6 @@
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleEnd" ></el-input>
<label style="width:100px">仅显示最新</label>
<ai-select :selectList="$dict.getDict('mall_yesno')" v-model="search.isNew"></ai-select>
<label style="width:100px">仅显示下架</label>
<ai-select :selectList="$dict.getDict('mall_yesno')" v-model="search.isOff"></ai-select>
</template>
<template #right>
<el-button type="primary" @click="search.current =1, getList()">查询</el-button>
@@ -37,16 +33,7 @@
</ai-search-bar>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 16px;">
<el-card v-for="item in tableData" :key="item.id" :body-style="{ padding: '0px', margin: '5px' }">
<div :class="[{'img-background': item.isSelect}, 'img-box']" v-if="item.isOff == '1'">
<div>
<el-image :src="item.imgUrl" class="mask image" :preview-src-list="[item.imgUrl]" />
</div>
<span class="icon-box">
<img style="width: 50px; height: 50px" src="../../../../assets/off.png">
</span>
</div>
<span class="nav-label labelBadge" v-else>
<span class="ii" v-if="item.isNew == 1"></span>
<span class="nav-label labelBadge">
<el-image :src="item.imgUrl" class="image" :preview-src-list="[item.imgUrl]" />
</span>
<div style="padding: 14px;">
@@ -56,11 +43,8 @@
<div style="display: inline; margin-right: 5px; float: right;">{{ item.saleTotal }}<sub style="margin-left: 2px;" v-html="getSalePercent(item.saleChange)"></sub></div>
</div>
<div>
<div v-if="search.orderBy == '5'" style="display: inline">近3次日均销量: <span style="color: red; font-weight: bold">{{ item.days3AverageSale }}</span></div>
<div v-else-if="search.orderBy == '6'" style="display: inline">近7次日均销量: <span style="color: red; font-weight: bold">{{ item.days7AverageSale }}</span></div>
<div v-else-if="search.orderBy == '7'" style="display: inline">近15次日均销量: <span style="color: red; font-weight: bold">{{ item.days15AverageSale }}</span></div>
<div v-else style="display: inline">日均销量: <span style="color: red; font-weight: bold">{{ item.averageSale }}</span></div>
<ai-product-drop-down :params="item" :isShowDetail="true" :isShowAddFavorite="true" :isShowDelFavorite="false" @onGoDetail="goDetail(item.goodsId, item.monitorId)" style="float: right;"></ai-product-drop-down>
<div style="display: inline">日均销量: <span style="color: red; font-weight: bold">{{ item.averageSale }}</span></div>
<ai-product-drop-down :params="item" :isShowDetail="true" :isShowAddFavorite="true" :isShowDelFavorite="false" @onGoDetail="goDetail(item.goodsId, item.groupId)" style="float: right;"></ai-product-drop-down>
</div>
</div>
</div>
@@ -84,23 +68,23 @@
:close-on-click-modal="false"
width="80%"
:before-close="handleClose">
<ai-product-detail v-if="isShowDetailDlg" :params="detailParams"/>
<ai-single-product-detail v-if="isShowDetailDlg" :params="detailParams"/>
</el-dialog>
</div>
</div>
</template>
<script>
import AiProductDetail from "@/components/AiProductDetail.vue";
import AiSingleProductDetail from "@/components/AiSingleProductDetail.vue";
import AiProductDropDown from '@/components/AiProductDropDown.vue';
export default {
name: 'DetailPage',
props: ['params'],
components: {AiProductDetail, AiProductDropDown},
components: {AiSingleProductDetail, AiProductDropDown},
data () {
return {
monitorId: '',
groupId: '',
content: '',
search: {
current: 1,
@@ -110,9 +94,7 @@ import AiProductDropDown from '@/components/AiProductDropDown.vue';
priceBegin: '',
priceEnd: '',
saleBegin: '',
saleEnd: '',
isNew: '',
isOff: ''
saleEnd: ''
},
orderBys: [{
value: '0',
@@ -129,15 +111,6 @@ import AiProductDropDown from '@/components/AiProductDropDown.vue';
},{
value: '4',
label: '按日均销量排序'
},{
value: '5',
label: '按近3次采集平均销售量排序'
},{
value: '6',
label: '按近7次采集平均销售量排序'
},{
value: '7',
label: '按近15次采集平均销售量排序'
}],
tableData: [],
total: 0,
@@ -147,17 +120,16 @@ import AiProductDropDown from '@/components/AiProductDropDown.vue';
},
created () {
this.$dict.load('mall_yesno');
this.monitorId = this.params.id
this.groupId = this.params.id
this.content = this.params.content
this.getList()
},
methods: {
getList () {
this.$http.post('/api/monitorDetail/myPageNew',null,{
this.$http.post('/api/singleGoodsDetail/myPageNew',null,{
params: {
monitorId: this.monitorId,
groupId: this.groupId,
...this.search
}
}).then(res => {
@@ -190,8 +162,8 @@ import AiProductDropDown from '@/components/AiProductDropDown.vue';
handleClose() {
this.isShowDetailDlg = false
},
goDetail (goodsId, monitorId) {
this.detailParams = {goodsId: goodsId, monitorId: monitorId}
goDetail (goodsId, groupId) {
this.detailParams = {goodsId: goodsId, groupId: groupId}
this.isShowDetailDlg = true
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div>
<ai-list class="list" v-loading="isLoading" element-loading-text="正在采集中……" element-loading-spinner="el-icon-loading">
<ai-list class="list" v-loading="isLoading" :element-loading-text="loadingText" element-loading-spinner="el-icon-loading">
<ai-title
slot="title"
title="单品跟踪"
@@ -11,7 +11,6 @@
<ai-search-bar>
<template #left>
<el-button type="button" :icon="'el-icon-delete'" :class="'el-button el-button--primary'" @click="batchRemove()">删除</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="batchCollect()">批量采集</el-button>
<el-button type="button" :class="'el-button el-button--primary'" @click="addGroup()">添加分组</el-button>
</template>
<template #right>
@@ -26,12 +25,13 @@
@selection-change="onChooseChange"
:current.sync="search.current" :size.sync="search.size"
@getList="getList">
<el-table-column slot="options" label="操作" width="240px" show-overflow-tooltip align="center" fixed="right">
<el-table-column slot="options" label="操作" width="280px" show-overflow-tooltip align="center" fixed="right">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="deleteGroup(row.id)">删除</el-button>
<el-button type="text" @click="toUpdateGroup(row)">修改</el-button>
<el-button type="text" @click="toDetail(row.id, row.content, row.source)">详情</el-button>
<el-button type="text" @click="toAddUrl(row)">添加商品</el-button>
<el-button type="text" @click="toDetail(row.id, row.name)">详情</el-button>
<el-button type="text" @click="toBegin(row)">采集数据</el-button>
</div>
</template>
@@ -82,11 +82,53 @@
<el-button type="primary" @click="updateGroup">确定</el-button>
</div>
</ai-dialog>
<ai-dialog
title="添加商品"
:visible.sync="isAddUrlDlgShow"
:close-on-click-modal="false"
width="80%"
customFooter
@close="isDlgShow = false">
<el-alert
title="每成功添加一个商品将消耗10金币"
type="success"
:closable="false" style="margin-bottom: 10px;">
</el-alert>
<el-form class="ai-form" style="margin-top: 20px" :model="addUrlForm" label-width="120px" ref="addUrlForm">
<el-form-item
prop="urls"
label="商品地址"
:rules="[{ required: true, message: '请输入TEMU商品地址多个用英文,号或换行隔开', trigger: 'blur' }]">
<el-input type="textarea" placeholder="请输入TEMU商品地址多个用英文,号或换行隔开" :rows="20" v-model="addUrlForm.urls"></el-input>
</el-form-item>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="isAddUrlDlgShow = false"> </el-button>
<el-button type="primary" @click="addUrl">确定</el-button>
</div>
</ai-dialog>
<ai-dialog
title="提示"
:visible.sync="isTipDlgShow"
:close-on-click-modal="false"
width="790px"
customFooter
@close="isDlgShow = false">
<div style="display: block;height: 30px;">商品链接可能出现图形验证码导致采集失败是否前往验证</div>
<div class="dialog-footer" slot="footer">
<el-button @click="isTipDlgShow = false">取消</el-button>
<el-button style="width: 180px;" @click="isTipDlgShow = false, isLoading = true, beginCollect()">已验证继续采集</el-button>
<el-button style="width: 180px;" @click="isTipDlgShow = false, isLoading = true, currentIndex ++, beginCollect()">跳过</el-button>
<el-button type="primary" @click="gotoValid">前往验证</el-button>
</div>
</ai-dialog>
</div>
</template>
<script>
import {sendTemuAPIMessage, sendSheinAPIMessage} from '@/api/chromeApi'
import {sendTemuAPIMessage, sendSheinAPIMessage, sendChromeWebReqMessage} from '@/api/chromeApi'
export default {
name: 'List',
@@ -101,6 +143,7 @@ import {sendTemuAPIMessage, sendSheinAPIMessage} from '@/api/chromeApi'
colConfigs: [
{ type: "selection", width: '70px', align: 'left' },
{ prop: 'name', label: '分组名称', align: 'left' },
{ prop: 'total', label: '商品个数', align: 'left' },
{ prop: 'lastUpdateTime', label: '上一次更新时间', align: 'left' },
{ prop: 'createTime', label: '添加时间', width: '180px', fixed: 'right'}
],
@@ -126,7 +169,22 @@ import {sendTemuAPIMessage, sendSheinAPIMessage} from '@/api/chromeApi'
isLoading: false,
selectRows: [],
isBatchCollect: false
isBatchCollect: false,
isAddUrlDlgShow: false,
addUrlForm: {
groupId: '',
urls: ''
},
loadingText: '正在采集中……',
isTipDlgShow: false,
errorUrl: '',
collectList: [],
currentIndex: 0,
collectData: {
groupId: ''
}
}
},
@@ -200,26 +258,6 @@ import {sendTemuAPIMessage, sendSheinAPIMessage} from '@/api/chromeApi'
})
})
},
batchCollect () {
if (this.selectRows.length <= 0) {
this.$message.error('请选择要采集的分组');
return;
}
this.isBatchCollect = true
this.isLoading = true
this.selectRows.map((item, index) => {
setTimeout(() => {
this.pageNo = 1
let data = {}
data.monitorId = item.id
data.source = item.source
data.details = []
this.beginCollect(item, data, index)
}, 1000*(index+1))
})
},
deleteGroup(id) {
this.$confirm('确定要删除?', '温馨提示', {
type: 'warning'
@@ -237,166 +275,182 @@ import {sendTemuAPIMessage, sendSheinAPIMessage} from '@/api/chromeApi'
})
})
},
toDetail (id, content, source) {
if (source == '0') {
this.$emit('change', {
type: 'Detail',
params: {
id: id || '',
content: content
}
})
} else {
this.$emit('change', {
type: 'SheinDetail',
params: {
id: id || '',
content: content
}
})
}
toDetail (id, content) {
this.$emit('change', {
type: 'Detail',
params: {
id: id || '',
content: content
}
})
},
addGroup() {
this.form.mallId = ''
this.isDlgShow = true
},
toBegin(row) {
this.isBatchCollect = false
this.pageNo = 1
this.isLoading = true
let data = {}
data.monitorId = row.id
data.details = []
this.beginCollect(row, data, 0)
},
beginCollect(row, reqData, index) {
if (row.source == '0') {
this.beginTemuCollect(row, reqData, index)
} else if (row.source == '1') {
this.beginSheinCollect(row, reqData, index)
}
toAddUrl(row) {
this.isAddUrlDlgShow = true
this.addUrlForm.groupId = row.id
this.addUrlForm.urls = ''
},
beginTemuCollect(row, reqData, index) {
sendTemuAPIMessage({
url: 'api/bg/circle/c/mall/newGoodsList',
anti: true,
data: {
"mall_id": row.content,
"filter_items": "0:1",
"page_number": this.pageNo,
"page_size": this.pageSize
}}).then((res) => {
if (this.isBatchCollect && (index+1) == this.selectRows.length) {
this.isLoading = false
this.isBatchCollect = false
} else {
this.isLoading = false
}
if (res.errorCode == 1000000) {
res.result.data.goods_list.map(item => {
let total = 0
if (item.sales_tip_text[0]) {
total = item.sales_tip_text[0]
total = total.replace('+', '')
if (total.indexOf('K') != -1) {
total = total.replace('K', '')
total = total * 1000
} else if (total.indexOf('M') != -1) {
total = total.replace('M', '')
total = total * 1000000
}
}
reqData.details.push({
url: 'https://www.temu.com/goods.html?goods_id=' + item.goods_id,
price: item.price_info.price_schema,
saleTotal: total,
goodsId: item.goods_id,
imgUrl: item.thumb_url,
mallId: item.mall_id
})
})
addUrl() {
this.$refs.addUrlForm.validate((valid) => {
if (valid) {
let params = []
let arr = this.addUrlForm.urls.split(/[\n,]/).map((value) => value.trim());
arr.map(url => {
let urlParams = this.parseURL(url)
let tmpUrl = url.substring(0,url.indexOf(".html"))
if (res.result.data.goods_list.length == this.pageSize) {
this.pageNo = this.pageNo + 1
this.beginCollect(row, reqData, index)
if (tmpUrl.lastIndexOf("-g-") > 0) {
tmpUrl = tmpUrl.substring(tmpUrl.lastIndexOf("-g-"), tmpUrl.length);
tmpUrl = tmpUrl.substring(3, tmpUrl.length);
params.push({
groupId: this.addUrlForm.groupId,
goodsUrl: url,
goodsId: tmpUrl
})
} else {
this.isLoading = false
this.$http.post('/api/monitorDetail/addDetails', reqData
).then(res => {
if (res.code == 0) {
this.$message.success('店铺ID【' + row.content + '】数据采集成功!')
this.getList()
let urlParams = this.parseURL(url)
if (urlParams.params) {
let goodsId = urlParams.params.goods_id
if (goodsId) {
params.push({
groupId: this.addUrlForm.groupId,
goodsUrl: url,
goodsId: goodsId
})
}
})
}
} else {
this.isLoading = false
this.$message.error("采集失败请检查https://www.temu.com是否能正常访问")
}
})
},
async beginSheinCollect(row, reqData, index) {
let res = await sendSheinAPIMessage({
url: 'api/store/items/get',
method: 'GET',
params: {
_ver: '1.1.8',
_lang: 'en',
scene_id: '10188',
rule_poskey: "shoprecommend",
requestType: "pageChange",
search_type: "store",
store_code: row.content,
viewed_goods: '',
limit: 120,
page: this.pageNo
}})
if (this.isBatchCollect && (index+1) == this.selectRows.length) {
this.isLoading = false
this.isBatchCollect = false
} else {
this.isLoading = false
}
if (res.goods && res.goods.length > 0) {
res.goods.map(item => {
let total = 0
if (item.pretreatInfo?.sellingPointUniversalLabels) {
for (let i = 0; i < item.pretreatInfo.sellingPointUniversalLabels.length; i++) {
if (item.pretreatInfo.sellingPointUniversalLabels[i].starComment) {
total = item.pretreatInfo.sellingPointUniversalLabels[i].starComment?.comment_num || 0
break
}
}
}
reqData.details.push({
url: 'https://www.shein.com/'+item.goods_url_name.replace(/ /g, '-')+'-p-'+item.goods_id+'-cat-'+item.cat_id+'.html',
price: item.salePrice.amount,
saleTotal: total,
goodsId: item.goods_id,
imgUrl: 'https:'+item.goods_img,
mallId: item.store_code
})
})
if (res.goods.length == this.pageSize) {
this.pageNo = this.pageNo + 1
this.beginCollect(row, reqData, index)
} else {
this.isLoading = false
this.$http.post('/api/monitorDetail/addDetails', reqData
).then(res => {
this.$http.post(`/api/singleGroup/saveGoods`, params).then(res => {
if (res.code == 0) {
this.$message.success('店铺ID【' + row.content + '】数据采集成功!')
this.$message.success(res.msg)
this.getList()
this.isAddUrlDlgShow = false
}
})
}
})
},
toBegin(row) {
this.isLoading = true
this.collectData.groupId = row.id
this.collectData.details = []
this.$http.post(`/api/singleGroup/getGoodsList`, null, {params: {
id: row.id
}}).then(res => {
if (res.code == 0) {
this.collectList = res.data
this.currentIndex = 0
this.beginCollect()
}
})
},
async beginCollect() {
this.loadingText = `正在采集第(${this.currentIndex}/${this.collectList.length})个商品的商品信息`
if (this.currentIndex == this.collectList.length) {
let res = await this.$http.post(`/api/singleGoodsDetail/addDetails`, this.collectData)
if (res.code == 0) {
this.getList()
}
this.isLoading = false
this.$message.success('采集成功')
return
}
let res = await sendTemuAPIMessage({
url: 'api/oak/integration/render',
anti: true,
data: {
goods_id: this.collectList[this.currentIndex].goodsId
}})
if (!res.goods || !res.goods.productName) {
res = await sendChromeWebReqMessage({
type: 'temu',
url: this.collectList[this.currentIndex].goodsUrl,
})
if (res.indexOf("window.rawData") == -1) {
this.$message.error("请检查地址是否正确或者“TEMU”网站是否出现图形验证码")
this.isLoading = false
this.errorUrl = this.collectList[this.currentIndex].goodsUrl
this.isTipDlgShow = true
return
} else {
let str = res.substring(res.indexOf("window.rawData"))
str = str.substring(0, str.indexOf("<\/script>"))
str = str.substring(str.indexOf("{"))
str = str.substring(0, str.lastIndexOf("}"))
str = str + "}"
let goodsObj = JSON.parse(str)
let goods = goodsObj.store.goods
this.collectData.details.push({
groupId: this.collectData.groupId,
price: goods.minOnSalePrice / 100,
saleTotal: goods.soldQuantity,
goodsId: goods.goodsId,
url: 'https://www.temu.com/goods.html?goods_id=' +goods.goodsId,
imgUrl: goods.hdThumbUrl,
mallId: goods.mallId
})
this.currentIndex ++
this.beginCollect()
}
} else {
this.collectData.details.push({
groupId: this.collectData.groupId,
price: res.goods.min_on_sale_price / 100,
saleTotal: res.goods.sold_quantity,
goodsId: res.goods.goods_id,
url: 'https://www.temu.com/goods.html?goods_id=' + res.goods.goods_id,
imgUrl: res.goods.hd_thumb_url,
mallId: res.goods.mall_id
})
this.currentIndex ++
this.beginCollect()
}
},
gotoValid() {
window.open(this.errorUrl, '_blank');
},
parseURL(url) {
let a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
}
}
}

View File

@@ -1,302 +0,0 @@
<template>
<div>
<ai-list class="list">
<ai-title
slot="title"
:title="'店铺ID(' + `${content}` + ')商品列表'"
isShowBottomBorder isShowBack @onBackClick="cancel(false)">
</ai-title>
<template slot="content">
<ai-search-bar>
<template #left>
<label style="width:80px">排序方式</label>
<el-select v-model="search.orderBy" :clearable="true" @change="search.current =1, getList()" placeholder="请选择排序方式" size="small">
<el-option
v-for="item in orderBys"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<label style="width:80px">价格区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.priceEnd" ></el-input>
<label style="width:80px">评论数区间</label>
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleBegin" ></el-input>
~
<el-input :clearable="true" size="small" style="width: 80px" v-model="search.saleEnd" ></el-input>
<label style="width:100px">仅显示最新</label>
<ai-select :selectList="$dict.getDict('mall_yesno')" v-model="search.isNew"></ai-select>
<label style="width:100px">仅显示下架</label>
<ai-select :selectList="$dict.getDict('mall_yesno')" v-model="search.isOff"></ai-select>
</template>
<template #right>
<el-button type="primary" @click="search.current =1, getList()">查询</el-button>
</template>
</ai-search-bar>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 16px;">
<el-card v-for="item in tableData" :key="item.id" :body-style="{ padding: '0px', margin: '5px' }">
<div :class="[{'img-background': item.isSelect}, 'img-box']" v-if="item.isOff == '1'">
<div>
<el-image :src="item.imgUrl" class="mask image" :preview-src-list="[item.imgUrl]" />
</div>
<span class="icon-box">
<img style="width: 50px; height: 50px" src="../../../../assets/off.png">
</span>
</div>
<span class="nav-label labelBadge" v-else>
<span class="ii" v-if="item.isNew == 1"></span>
<el-image :src="item.imgUrl" class="image" :preview-src-list="[item.imgUrl]" />
</span>
<div style="padding: 14px;">
<div class="bottom clearfix">
<div style="margin-bottom: 5px;">
<div style="display: inline; margin-left: 5px;">${{ item.price }}<sub style="margin-left: 2px;" v-html="getPricePercent(item.priceChange)"></sub></div>
<div style="display: inline; margin-right: 5px; float: right;">{{ item.saleTotal }}<sub style="margin-left: 2px;" v-html="getSalePercent(item.saleChange)"></sub></div>
</div>
<div>
<div v-if="search.orderBy == '5'" style="display: inline">近3次日均评论数: <span style="color: red; font-weight: bold">{{ item.days3AverageSale }}</span></div>
<div v-else-if="search.orderBy == '6'" style="display: inline">近7次日均评论数: <span style="color: red; font-weight: bold">{{ item.days7AverageSale }}</span></div>
<div v-else-if="search.orderBy == '7'" style="display: inline">近15次日均评论数: <span style="color: red; font-weight: bold">{{ item.days15AverageSale }}</span></div>
<div v-else style="display: inline">日均评论数: <span style="color: red; font-weight: bold">{{ item.averageSale }}</span></div>
<ai-product-drop-down :params="item" :isHideCopy="true" :isShowDetail="true" :isShowAddFavorite="true" :isShowDelFavorite="false" @onGoDetail="goDetail(item.goodsId, item.monitorId)" style="float: right;"></ai-product-drop-down>
</div>
</div>
</div>
</el-card>
<el-pagination
layout="prev, pager, next"
style="position: absolute; bottom: 0px; right: 40px;"
:total="total"
:page-size.sync="search.size"
:current-page.sync="search.current"
@current-change="getList">
</el-pagination>
</div>
</template>
</ai-list>
<div class="productDetail">
<el-dialog
title="商品详情"
:visible="isShowDetailDlg"
:close-on-click-modal="false"
width="80%"
:before-close="handleClose">
<ai-product-detail v-if="isShowDetailDlg" :params="detailParams"/>
</el-dialog>
</div>
</div>
</template>
<script>
import AiProductDetail from "@/components/AiProductDetail.vue";
import AiProductDropDown from '@/components/AiProductDropDown.vue';
export default {
name: 'DetailPage',
props: ['params'],
components: {AiProductDetail, AiProductDropDown},
data () {
return {
monitorId: '',
content: '',
search: {
current: 1,
type: '1',
size: 120,
orderBy: '',
priceBegin: '',
priceEnd: '',
saleBegin: '',
saleEnd: '',
isNew: '',
isOff: ''
},
orderBys: [{
value: '0',
label: '按评论数涨辐排序'
},{
value: '1',
label: '按价格涨辐排序'
},{
value: '2',
label: '按评论数排序'
},{
value: '3',
label: '按价格排序'
},{
value: '4',
label: '按日均评论数排序'
},{
value: '5',
label: '按近3次采集平均评论数排序'
},{
value: '6',
label: '按近7次采集平均评论数排序'
},{
value: '7',
label: '按近15次采集平均评论数排序'
}],
tableData: [],
total: 0,
isShowDetailDlg: false,
detailParams: {}
}
},
created () {
this.$dict.load('mall_yesno');
this.monitorId = this.params.id
this.content = this.params.content
this.getList()
},
methods: {
getList () {
this.$http.post('/api/monitorDetail/myPageNew',null,{
params: {
monitorId: this.monitorId,
...this.search
}
}).then(res => {
res.data.records = res.data.records.map(item => {
item.mallId = 'https://www.shein.com/store/home?store_code=' + item.mallId
return item
})
this.tableData = res.data.records
this.total = res.data.total
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'List',
isRefresh: !!isRefresh
})
},
getPricePercent(data) {
if (data < 0) {
return '<div style="display: inline; color: green">↓' + data + '%</div>'
} else if (data > 0) {
return '<div style="display: inline; color: red">↑' + data + '%</div>'
}
return ''
},
getSalePercent(data) {
if (data < 0) {
return '<div style="display: inline; color: green">↓' + data + '%</div>'
} else if (data > 0) {
return '<div style="display: inline; color: red">↑' + data + '%</div>'
}
return ''
},
handleClose() {
this.isShowDetailDlg = false
},
goDetail (goodsId, monitorId) {
this.detailParams = {goodsId: goodsId, monitorId: monitorId}
this.isShowDetailDlg = true
}
}
}
</script>
<style scoped lang="scss">
.productDetail {
:deep(.el-dialog) {
height: 78vh;
overflow: auto;
}
}
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-icon-arrow-down {
font-size: 12px;
}
/*徽标*/
.labelBadge {
position: relative;
}
.ii {
background: #f00;
border-radius: 50%;
width: 20px;
height: 20px;
top: -3px;
right: -5px;
position: absolute;
text-align: center;
color: #FFF;
z-index: 999;
font-size: 14px;
}
.img-box {
display: flex;
justify-content: center;
align-items: center;
//父级设置 相对定位,让 icon设置绝对定位时能够以该父级为准。
position: relative;
// icon 设置 绝对定位 让其固定在你想要的合适位置。 样式可调整,自己定位即可。
.icon-box {
position: absolute;
top: 35%;
}
}
.mask {
background-color: #000;
opacity: 0.6;
.el-image__inner {
opacity: 0.6;
}
}
::v-deep .mask > img {
opacity: 0.6;
}
.el-icon {
display: inline-block;
}
</style>