1
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
DEVICE-C705AA3904F84D998D03B5CD83EEBBD7
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCMgaMZlZOiAK7e
|
|
||||||
FWjhWuyIg+8XBIrF9/kZYGtX+3tn53T+sCPBjAIukouyP41H5g6IZDeTP1WXsNuQ
|
|
||||||
kVIvbZwRJKTSO7EiqE1T3V9jPzTXOXvq0SPnjaMhmUKxkmgRSs9DBZUqrElZ5mzw
|
|
||||||
1ua/9ISlwCLqFRrVL5Nzc9+TO8IIRjjPBPYssYCDpz4NpdQb4OQYhaTRrj1++wo4
|
|
||||||
N+LuS+wKb0Ps8SH1+udYaUWR6NteR6hhj0+0AVK0cyqsbHYQE4z+gAJr+Aicd1ZX
|
|
||||||
pON8jsFiSNdTgGIVKCb8haVkgkXwe1inBwhyVcWk8xaz2aKa+TsNI03WfBi8DY39
|
|
||||||
+WeisqnrAgMBAAECggEABJA3TF7rxxCvnT3jxKHv2bUzQDOhEDHwEK9tfROJXAQL
|
|
||||||
7DOrTZ9u+LVAvT7MJ2Ak67AZj/o4HO+dCfJ2UV0FexcOFVfj9mSx8j3X2cDVRgIz
|
|
||||||
cJpvSJd0i2RPYrYHFDyyQ5J8WED1NurBcgcAwo49+qYlXCXoU7EyYEcMpVsE/8C/
|
|
||||||
xvW0GShTzXOpO5emQOkPixxNW8i6VHte23igUULn29eyePB+pvRdEuuKC+bEZ9cJ
|
|
||||||
ZXJ0p/yS2sNUxvBAmdVZTTs88E3gu/ARRJgyHE//Ax+fHmlSpPGeJlEPivntu0So
|
|
||||||
vrJan9uIA3mS7eE1i7M+JoZjIgwpHKfG07AipRmklQKBgQDEQAGgpMo3AtRMmYFi
|
|
||||||
PP3Sb5p3cETekWXJBijlhE69XBZg/VBSpNrtmD4iXuYbBb3d140nIvESowpBN3gn
|
|
||||||
uY6nNqJWSzhKzQhU8ienJS8PZPeeRE2mMI8sRxl+VnhMraa/E8k/hzqP0RCo+cfC
|
|
||||||
uu1be7AJ0+W6WB0d9g6zSj99HQKBgQC3SOV/4i1KXybuaphJJrZF5cuMoD5cE5Ck
|
|
||||||
vXTTc9MSkUtwLf5PiJGRzRWNd5amS0FMb3IuC8YfEqZ8k+vDDVbgigqDeIKEpJSQ
|
|
||||||
3K8A/F/094cyMyqrQ2aiUPg6Da57QH+Q0Ab2S7n8RstZC6h28sYxgG1acuL2jqY7
|
|
||||||
S7HlVAZ8pwKBgQC3UqMykT1kjfwLYgn+3sKsZRyCHhn3XxMZ6esiG6oCMZemGnuB
|
|
||||||
+AWalPDV4phI/eAS71woBvfzVOIrccmIMkoT4XFb8wAuv8DcuShZdt6zHrpA2cU/
|
|
||||||
TXUxA2nJHrVZy41MSQthkM0fs0hA0LPOMBexsaUMSSj8HXt1lXi9+sm78QKBgAsE
|
|
||||||
0udRTa++8LQ8rFMZhLPHEOmvaJBYjMWarj9YI0Rmf8aKvVNCvp2pWrZajjAJLi/O
|
|
||||||
M2sZQhv0HxY2PmJHlwWAxwkIYbBfxJ7A5bSFd69egj4+XT5WmwD/JS04TVkTk5e9
|
|
||||||
Ke38t323M9pynPoptkibk/dwGL0B7nR6JIPI/WrZAoGBALYitr4UK/H8I/XvYaUJ
|
|
||||||
q0A93fIa9ACVHshZEAwY7o1PIZu9bLScGilkbkvdATwgkfrBZXFCDIAFu2Ta/yMF
|
|
||||||
NEFXG1szFg0aDzEN0pm8o2dndQ9dKTPswytvR5DphgCmxhuJDm9Qwwjc1UQuWePp
|
|
||||||
Q3mDFK0eVcWWdA03UbY2TEeK
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjIGjGZWTogCu3hVo4Vrs
|
|
||||||
iIPvFwSKxff5GWBrV/t7Z+d0/rAjwYwCLpKLsj+NR+YOiGQ3kz9Vl7DbkJFSL22c
|
|
||||||
ESSk0juxIqhNU91fYz801zl76tEj542jIZlCsZJoEUrPQwWVKqxJWeZs8Nbmv/SE
|
|
||||||
pcAi6hUa1S+Tc3PfkzvCCEY4zwT2LLGAg6c+DaXUG+DkGIWk0a49fvsKODfi7kvs
|
|
||||||
Cm9D7PEh9frnWGlFkejbXkeoYY9PtAFStHMqrGx2EBOM/oACa/gInHdWV6TjfI7B
|
|
||||||
YkjXU4BiFSgm/IWlZIJF8HtYpwcIclXFpPMWs9mimvk7DSNN1nwYvA2N/flnorKp
|
|
||||||
6wIDAQAB
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(mvn:*)",
|
|
||||||
"Bash(del:*)",
|
|
||||||
"Bash(rm:*)",
|
|
||||||
"Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\controller\\AuthController.java\")",
|
|
||||||
"Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\resources\\static\\html\\login.html\")",
|
|
||||||
"Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\java\\com\\tashow\\erp\\controller\\AuthProxyController.java\")",
|
|
||||||
"Bash(java:*)",
|
|
||||||
"Bash(del \"C:\\Users\\ZiJIe\\Desktop\\wox\\erp_client_sb\\src\\main\\resources\\static\\html\\shopee-platform.html\")",
|
|
||||||
"Bash(mkdir:*)",
|
|
||||||
"Bash(\"taskkill\" \"/f\" \"/pid\" \"9804\")",
|
|
||||||
"Bash(dir:*)",
|
|
||||||
"Bash(find:*)"
|
|
||||||
],
|
|
||||||
"deny": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DEVICE-CD31378598644EAA8C0E8153A9D80959
|
|
||||||
Binary file not shown.
@@ -1,18 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
// 查询1688爬取风控监控数据列表
|
|
||||||
export function listAlibaba1688Monitor(query) {
|
|
||||||
return request({
|
|
||||||
url: '/monitor/client/alibaba1688/list',
|
|
||||||
method: 'get',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询1688爬取风控监控统计数据
|
|
||||||
export function getAlibaba1688Statistics() {
|
|
||||||
return request({
|
|
||||||
url: '/monitor/client/alibaba1688/statistics',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
// 爬取图片
|
|
||||||
export function scrapeImages() {
|
|
||||||
return request({
|
|
||||||
url: '/prod/ozon/scrapeImages',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
// 查询prod列表
|
|
||||||
export function listProducts(query) {
|
|
||||||
return request({
|
|
||||||
url: '/prod/products/list',
|
|
||||||
method: 'get',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询prod详细
|
|
||||||
export function getProducts(productId) {
|
|
||||||
return request({
|
|
||||||
url: '/prod/products/' + productId,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增prod
|
|
||||||
export function addProducts(data) {
|
|
||||||
return request({
|
|
||||||
url: '/prod/products',
|
|
||||||
method: 'post',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改prod
|
|
||||||
export function updateProducts(data) {
|
|
||||||
return request({
|
|
||||||
url: '/prod/products',
|
|
||||||
method: 'put',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除prod
|
|
||||||
export function delProducts(productId) {
|
|
||||||
return request({
|
|
||||||
url: '/prod/products/' + productId,
|
|
||||||
method: 'delete'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
// 获取乐天市场商品数据
|
|
||||||
export function scrapeRakutenProducts(query) {
|
|
||||||
return request({
|
|
||||||
url: '/prod/rakuten/scrapeProducts',
|
|
||||||
method: 'get',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取代理服务器列表
|
|
||||||
export function getProxies() {
|
|
||||||
return request({
|
|
||||||
url: '/tool/webmagic/proxies',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置当前使用的代理
|
|
||||||
export function setProxy(proxyName) {
|
|
||||||
return request({
|
|
||||||
url: '/tool/webmagic/proxy/set',
|
|
||||||
method: 'post',
|
|
||||||
params: { proxyName }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动代理服务
|
|
||||||
export function startProxy() {
|
|
||||||
return request({
|
|
||||||
url: '/tool/webmagic/clash/start',
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止代理服务
|
|
||||||
export function stopProxy() {
|
|
||||||
return request({
|
|
||||||
url: '/tool/webmagic/clash/stop',
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新1688 API Cookie
|
|
||||||
export function refresh1688Cookie() {
|
|
||||||
return request({
|
|
||||||
url: '/figre/refreshCookie',
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量上传图片URL到1688搜索
|
|
||||||
export function batchUpload1688Images(imageUrls) {
|
|
||||||
return request({
|
|
||||||
url: '/figre/batchUpload1688Images',
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
imageUrls: imageUrls
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 爬取1688商品信息(包含ID、链接、标题等)
|
|
||||||
export function scrape1688Products(imageUrl) {
|
|
||||||
return request({
|
|
||||||
url: '/prod/rakuten/scrape1688Products',
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
imageUrl: imageUrl
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片代理接口 - 用于获取外部图片
|
|
||||||
export function getProxyImage(imageUrl) {
|
|
||||||
return request({
|
|
||||||
url: '/tool/banma/image-proxy',
|
|
||||||
method: 'get',
|
|
||||||
params: { url: imageUrl },
|
|
||||||
responseType: 'blob'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传文件到七牛云
|
|
||||||
export function uploadFile(file) {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('files', file)
|
|
||||||
|
|
||||||
return request({
|
|
||||||
url: '/file/uploads',
|
|
||||||
method: 'post',
|
|
||||||
data: formData,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止所有爬虫活动
|
|
||||||
export function stopAllCrawling() {
|
|
||||||
return request({
|
|
||||||
url: '/tool/webmagic/stop-crawling',
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,858 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-card class="box-card">
|
|
||||||
<template #header>
|
|
||||||
<div class="clearfix">
|
|
||||||
<span class="card-title">斑马订单数据</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 时间范围选择器和操作按钮区域 -->
|
|
||||||
<div class="filter-container">
|
|
||||||
<el-form :inline="true" class="filter-form">
|
|
||||||
<el-form-item label="时间范围" class="date-range-item">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="dateRange"
|
|
||||||
type="daterange"
|
|
||||||
range-separator="至"
|
|
||||||
start-placeholder="开始日期"
|
|
||||||
end-placeholder="结束日期"
|
|
||||||
value-format="yyyy-MM-dd"
|
|
||||||
:picker-options="pickerOptions"
|
|
||||||
class="date-picker">
|
|
||||||
</el-date-picker>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item class="action-item">
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
icon="el-icon-search"
|
|
||||||
size="small"
|
|
||||||
@click="loadData"
|
|
||||||
:loading="loading"
|
|
||||||
class="action-button">
|
|
||||||
查询数据
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="orderData.length > 0"
|
|
||||||
type="success"
|
|
||||||
icon="el-icon-download"
|
|
||||||
size="small"
|
|
||||||
@click="handleExport"
|
|
||||||
class="action-button">
|
|
||||||
导出Excel
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="orderData.length > 0"
|
|
||||||
type="info"
|
|
||||||
icon="el-icon-refresh"
|
|
||||||
size="small"
|
|
||||||
@click="clearCache"
|
|
||||||
class="action-button">
|
|
||||||
清除缓存
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="warning"
|
|
||||||
icon="el-icon-key"
|
|
||||||
size="small"
|
|
||||||
@click="refreshToken"
|
|
||||||
:loading="tokenLoading"
|
|
||||||
class="action-button">
|
|
||||||
刷新Token
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 数据统计信息 -->
|
|
||||||
<el-alert
|
|
||||||
v-if="orderData.length > 0"
|
|
||||||
:title="`已加载 ${orderData.length} 条订单数据${loading ? '(加载中...)' : hasMore ? '' : '(已加载完成)'}`"
|
|
||||||
:type="loading ? 'warning' : hasMore ? 'info' : 'success'"
|
|
||||||
:closable="false"
|
|
||||||
show-icon
|
|
||||||
class="data-stats-alert">
|
|
||||||
<template v-slot:description>
|
|
||||||
<div v-if="dateRange && dateRange.length === 2" class="filter-info">
|
|
||||||
筛选时间范围:{{ dateRange[0] }} 至 {{ dateRange[1] }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-alert>
|
|
||||||
|
|
||||||
<!-- 加载进度条 -->
|
|
||||||
<el-row v-if="loading">
|
|
||||||
<el-col :span="24">
|
|
||||||
<div class="progress-container">
|
|
||||||
<el-progress
|
|
||||||
:percentage="loadProgress.percentage"
|
|
||||||
:stroke-width="12"
|
|
||||||
class="load-progress">
|
|
||||||
</el-progress>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
|
||||||
<el-table v-loading="loading" :data="orderData" border style="width: 100%">
|
|
||||||
<el-table-column label="下单时间" align="center" prop="orderedAt" min-width="140" header-align="center" />
|
|
||||||
<el-table-column label="商品图片" align="center" prop="productImage" min-width="90" header-align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-image
|
|
||||||
v-if="scope.row.productImage"
|
|
||||||
:src="scope.row.productImage"
|
|
||||||
style="width: 60px; height: 60px; object-fit: contain;"
|
|
||||||
:preview-src-list="[scope.row.productImage]"
|
|
||||||
@error="handleImageError($event, scope.row)">
|
|
||||||
</el-image>
|
|
||||||
<span v-else>无图片</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="商品名称" align="center" prop="productTitle" min-width="180" show-overflow-tooltip header-align="center" />
|
|
||||||
<el-table-column label="乐天订单号" align="center" prop="shopOrderNumber" min-width="140" header-align="center" />
|
|
||||||
<el-table-column label="下单距今时间" align="center" prop="timeSinceOrder" min-width="100" header-align="center" />
|
|
||||||
<el-table-column label="乐天订单金额/日元" align="center" prop="priceJpy" min-width="120" header-align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<span class="price-tag">¥{{ scope.row.priceJpy || '0' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="购买数量" align="center" prop="productQuantity" min-width="80" header-align="center" />
|
|
||||||
<el-table-column label="税费/日元" align="center" prop="shippingFeeJpy" min-width="100" header-align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<span class="fee-tag">¥{{ scope.row.shippingFeeJpy || '0' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="服务商回款抽点rmb" align="center" min-width="140" header-align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<span>{{ scope.row.serviceFee || '-' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="商品番号" align="center" prop="productNumber" min-width="120" header-align="center" />
|
|
||||||
<el-table-column label="1688采购订单号" align="center" prop="poNumber" min-width="140" header-align="center" />
|
|
||||||
<el-table-column label="采购金额/rmb" align="center" prop="shippingFeeCny" min-width="110" header-align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<span class="fee-tag">¥{{ scope.row.shippingFeeCny || '0' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="国际运费/rmb" align="center" prop="internationalShippingFee" min-width="110" header-align="center" />
|
|
||||||
<el-table-column label="国内物流公司" align="center" prop="poLogisticsCompany" min-width="120" header-align="center" />
|
|
||||||
<el-table-column label="国内物流单号" align="center" prop="poTrackingNumber" min-width="120" header-align="center" />
|
|
||||||
<el-table-column label="日本物流单号" align="center" prop="internationalTrackingNumber" min-width="120" header-align="center" />
|
|
||||||
<el-table-column label="地址状态" align="center" prop="trackInfo" min-width="160" header-align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tooltip v-if="scope.row.trackInfo" :content="scope.row.trackInfo" placement="top" :enterable="false">
|
|
||||||
<el-tag type="success" class="track-info-tag">{{ scope.row.trackInfo }}</el-tag>
|
|
||||||
</el-tooltip>
|
|
||||||
<span v-else>暂无物流信息</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 无更多数据提示 -->
|
|
||||||
<div v-if="orderData.length > 0 && !hasMore && !loading" class="no-more-data">
|
|
||||||
已加载全部数据
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import request from '@/utils/request'
|
|
||||||
import { parseTime } from '@/utils/ruoyi'
|
|
||||||
import * as XLSX from 'xlsx'
|
|
||||||
import ExcelJS from 'exceljs'
|
|
||||||
import { saveAs } from 'file-saver'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "BanmaOrders",
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
orderData: [],
|
|
||||||
hasMore: false,
|
|
||||||
nextPage: 1,
|
|
||||||
totalPages: 1,
|
|
||||||
currentPage: 0,
|
|
||||||
totalRecords: 0,
|
|
||||||
loadProgress: {
|
|
||||||
current: 0,
|
|
||||||
total: 100,
|
|
||||||
percentage: 0
|
|
||||||
},
|
|
||||||
dateRange: [], // 时间范围
|
|
||||||
pickerOptions: {
|
|
||||||
shortcuts: [{
|
|
||||||
text: '最近一周',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
text: '最近一个月',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
text: '最近三个月',
|
|
||||||
onClick(picker) {
|
|
||||||
const end = new Date();
|
|
||||||
const start = new Date();
|
|
||||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
|
||||||
picker.$emit('pick', [start, end]);
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
tokenLoading: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.loadFromCache();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/** 从本地缓存加载数据 */
|
|
||||||
loadFromCache() {
|
|
||||||
try {
|
|
||||||
const cachedData = localStorage.getItem('banma_orders_data');
|
|
||||||
if (cachedData) {
|
|
||||||
const parsedData = JSON.parse(cachedData);
|
|
||||||
this.orderData = parsedData.orderData || [];
|
|
||||||
this.totalRecords = parsedData.totalRecords || 0;
|
|
||||||
this.totalPages = parsedData.totalPages || 1;
|
|
||||||
this.hasMore = parsedData.hasMore || false;
|
|
||||||
this.nextPage = parsedData.nextPage || 1;
|
|
||||||
this.currentPage = parsedData.currentPage || 0;
|
|
||||||
this.dateRange = parsedData.dateRange || [];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载缓存数据失败:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 保存数据到本地缓存 */
|
|
||||||
saveToCache() {
|
|
||||||
try {
|
|
||||||
if (this.orderData.length > 0) {
|
|
||||||
const cacheData = {
|
|
||||||
orderData: this.orderData,
|
|
||||||
totalRecords: this.totalRecords,
|
|
||||||
totalPages: this.totalPages,
|
|
||||||
hasMore: this.hasMore,
|
|
||||||
nextPage: this.nextPage,
|
|
||||||
currentPage: this.currentPage,
|
|
||||||
dateRange: this.dateRange,
|
|
||||||
timestamp: new Date().getTime()
|
|
||||||
};
|
|
||||||
localStorage.setItem('banma_orders_data', JSON.stringify(cacheData));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.$message.warning('保存数据到本地缓存失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 清空缓存数据 */
|
|
||||||
clearCache() {
|
|
||||||
localStorage.removeItem('banma_orders_data');
|
|
||||||
this.$message.success('缓存数据已清除');
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 获取订单列表 */
|
|
||||||
loadData() {
|
|
||||||
this.loading = true;
|
|
||||||
this.orderData = [];
|
|
||||||
this.nextPage = 1;
|
|
||||||
this.currentPage = 0;
|
|
||||||
this.totalPages = 1;
|
|
||||||
this.totalRecords = 0;
|
|
||||||
this.loadProgress = {
|
|
||||||
current: 0,
|
|
||||||
total: 100,
|
|
||||||
percentage: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
this.clearCache();
|
|
||||||
this.loadFirstPage();
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 加载第一页数据 */
|
|
||||||
loadFirstPage() {
|
|
||||||
// 准备请求参数,添加时间范围
|
|
||||||
const params = {};
|
|
||||||
if (this.dateRange && this.dateRange.length === 2) {
|
|
||||||
params.startDate = this.dateRange[0];
|
|
||||||
params.endDate = this.dateRange[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
request({
|
|
||||||
url: '/tool/banma/orders/all',
|
|
||||||
method: 'get',
|
|
||||||
params,
|
|
||||||
timeout: 99999999
|
|
||||||
}).then(response => {
|
|
||||||
if (response.code === 200) {
|
|
||||||
this.orderData = response.data.orders;
|
|
||||||
this.hasMore = response.data.hasMore;
|
|
||||||
this.nextPage = response.data.nextPage;
|
|
||||||
this.currentPage = 1;
|
|
||||||
this.totalRecords = response.data.total || 0;
|
|
||||||
this.totalPages = response.data.totalPages || 1;
|
|
||||||
|
|
||||||
this.updateProgress();
|
|
||||||
this.saveToCache();
|
|
||||||
|
|
||||||
if (this.hasMore) {
|
|
||||||
this.autoLoadNextPage();
|
|
||||||
} else {
|
|
||||||
this.loading = false;
|
|
||||||
if (this.orderData.length > 0) {
|
|
||||||
this.$message.success(`共加载 ${this.orderData.length} 条订单数据`);
|
|
||||||
} else {
|
|
||||||
this.$message.warning('未找到符合条件的订单数据');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.loading = false;
|
|
||||||
this.$message.error(response.msg || "获取订单失败");
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
this.loading = false;
|
|
||||||
this.$message.error("获取订单失败: " + (error.message || error));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 自动加载下一页 */
|
|
||||||
autoLoadNextPage() {
|
|
||||||
if (!this.hasMore || !this.loading) return;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// 准备请求参数,添加时间范围
|
|
||||||
const params = { page: this.nextPage };
|
|
||||||
if (this.dateRange && this.dateRange.length === 2) {
|
|
||||||
params.startDate = this.dateRange[0];
|
|
||||||
params.endDate = this.dateRange[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
request({
|
|
||||||
url: '/tool/banma/orders/next',
|
|
||||||
method: 'get',
|
|
||||||
params,
|
|
||||||
timeout: 99999999
|
|
||||||
}).then(response => {
|
|
||||||
if (response.code === 200) {
|
|
||||||
// 添加当前页数据
|
|
||||||
this.orderData = [...this.orderData, ...(response.data.orders || [])];
|
|
||||||
this.hasMore = response.data.hasMore;
|
|
||||||
this.nextPage = response.data.nextPage;
|
|
||||||
this.currentPage++;
|
|
||||||
this.totalPages = response.data.totalPages || this.totalPages;
|
|
||||||
|
|
||||||
this.updateProgress();
|
|
||||||
this.saveToCache();
|
|
||||||
if (!this.hasMore && this.currentPage < this.totalPages) {
|
|
||||||
this.hasMore = true;
|
|
||||||
this.autoLoadNextPage();
|
|
||||||
} else if (this.hasMore) {
|
|
||||||
this.autoLoadNextPage();
|
|
||||||
} else {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.loading = false;
|
|
||||||
this.$message.error(response.msg || "获取更多订单失败");
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
|
|
||||||
this.nextPage++;
|
|
||||||
this.currentPage++;
|
|
||||||
|
|
||||||
// 更新进度条,即使加载失败也显示进度
|
|
||||||
this.updateProgress();
|
|
||||||
this.saveToCache();
|
|
||||||
|
|
||||||
// 如果还有更多页,继续尝试加载
|
|
||||||
if (this.currentPage < this.totalPages) {
|
|
||||||
this.$message.warning(`加载第${this.currentPage}页失败,尝试继续加载后续页面`);
|
|
||||||
this.autoLoadNextPage();
|
|
||||||
} else {
|
|
||||||
this.loading = false;
|
|
||||||
this.$message.error("获取更多订单失败: " + (error.message || error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 更新进度条 */
|
|
||||||
updateProgress() {
|
|
||||||
let percentage;
|
|
||||||
if (this.totalRecords > 0) {
|
|
||||||
percentage = Math.min(Math.round((this.orderData.length / this.totalRecords) * 100), 99);
|
|
||||||
} else if (this.totalPages > 1) {
|
|
||||||
percentage = Math.min(Math.round((this.currentPage / this.totalPages) * 100), 99);
|
|
||||||
} else {
|
|
||||||
percentage = this.currentPage > 0 ? 50 : 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadProgress = {
|
|
||||||
current: this.currentPage,
|
|
||||||
total: this.totalPages,
|
|
||||||
percentage: percentage
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** 导出Excel */
|
|
||||||
handleExport() {
|
|
||||||
if (!this.orderData.length) {
|
|
||||||
this.$message.warning("没有数据可导出");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$message.info("正在准备导出数据,请稍候...");
|
|
||||||
|
|
||||||
// 创建工作簿
|
|
||||||
const workbook = new ExcelJS.Workbook();
|
|
||||||
workbook.creator = 'RuoYi Admin';
|
|
||||||
workbook.lastModifiedBy = 'RuoYi Admin';
|
|
||||||
workbook.created = new Date();
|
|
||||||
workbook.modified = new Date();
|
|
||||||
|
|
||||||
// 添加工作表
|
|
||||||
const worksheet = workbook.addWorksheet('订单数据');
|
|
||||||
|
|
||||||
// 设置列宽和表头
|
|
||||||
const columns = [
|
|
||||||
{ header: '下单时间', key: 'orderedAt', width: 18 },
|
|
||||||
{ header: '商品图片', key: 'productImage', width: 12 },
|
|
||||||
{ header: '商品名称', key: 'productTitle', width: 25 },
|
|
||||||
{ header: '乐天订单号', key: 'shopOrderNumber', width: 18 },
|
|
||||||
{ header: '下单距今时间', key: 'timeSinceOrder', width: 15 },
|
|
||||||
{ header: '乐天订单金额/日元', key: 'priceJpy', width: 15 },
|
|
||||||
{ header: '购买数量', key: 'productQuantity', width: 10 },
|
|
||||||
{ header: '税费/日元', key: 'shippingFeeJpy', width: 15 },
|
|
||||||
{ header: '服务商回款抽点rmb', key: 'serviceFee', width: 18 },
|
|
||||||
{ header: '商品番号', key: 'productNumber', width: 15 },
|
|
||||||
{ header: '1688采购订单号', key: 'poNumber', width: 18 },
|
|
||||||
{ header: '采购金额/rmb', key: 'shippingFeeCny', width: 15 },
|
|
||||||
{ header: '国际运费/rmb', key: 'internationalShippingFee', width: 15 },
|
|
||||||
{ header: '国内物流公司', key: 'poLogisticsCompany', width: 15 },
|
|
||||||
{ header: '国内物流单号', key: 'poTrackingNumber', width: 18 },
|
|
||||||
{ header: '日本物流单号', key: 'internationalTrackingNumber', width: 18 },
|
|
||||||
{ header: '地址状态', key: 'trackInfo', width: 25 }
|
|
||||||
];
|
|
||||||
|
|
||||||
worksheet.columns = columns;
|
|
||||||
|
|
||||||
// 设置表头样式
|
|
||||||
worksheet.getRow(1).eachCell(cell => {
|
|
||||||
cell.font = { bold: true };
|
|
||||||
cell.fill = {
|
|
||||||
type: 'pattern',
|
|
||||||
pattern: 'solid',
|
|
||||||
fgColor: { argb: 'FFEEEEEE' }
|
|
||||||
};
|
|
||||||
cell.alignment = {
|
|
||||||
vertical: 'middle',
|
|
||||||
horizontal: 'center',
|
|
||||||
wrapText: true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
worksheet.getRow(1).height = 22;
|
|
||||||
|
|
||||||
// 添加数据行
|
|
||||||
this.orderData.forEach((item, index) => {
|
|
||||||
const rowData = {
|
|
||||||
orderedAt: item.orderedAt || '',
|
|
||||||
productImage: '', // 图片单元格,稍后添加图片
|
|
||||||
productTitle: item.productTitle || '',
|
|
||||||
shopOrderNumber: item.shopOrderNumber || '',
|
|
||||||
timeSinceOrder: item.timeSinceOrder || '',
|
|
||||||
priceJpy: item.priceJpy || '',
|
|
||||||
productQuantity: item.productQuantity || '',
|
|
||||||
shippingFeeJpy: item.shippingFeeJpy || '',
|
|
||||||
serviceFee: item.serviceFee || '-',
|
|
||||||
productNumber: item.productNumber || '',
|
|
||||||
poNumber: item.poNumber || '',
|
|
||||||
shippingFeeCny: item.shippingFeeCny || '',
|
|
||||||
internationalShippingFee: item.internationalShippingFee || '',
|
|
||||||
poLogisticsCompany: item.poLogisticsCompany || '',
|
|
||||||
poTrackingNumber: item.poTrackingNumber || '',
|
|
||||||
internationalTrackingNumber: item.internationalTrackingNumber || '',
|
|
||||||
trackInfo: item.trackInfo || '暂无物流信息'
|
|
||||||
};
|
|
||||||
|
|
||||||
const row = worksheet.addRow(rowData);
|
|
||||||
row.height = 60; // 设置行高,给图片留出空间
|
|
||||||
|
|
||||||
// 设置所有单元格居中对齐
|
|
||||||
row.eachCell(cell => {
|
|
||||||
cell.alignment = {
|
|
||||||
vertical: 'middle',
|
|
||||||
horizontal: 'center',
|
|
||||||
wrapText: true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理图片
|
|
||||||
const processImages = async () => {
|
|
||||||
const loadImage = (url) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!url) {
|
|
||||||
resolve(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是代理URL,如果不是,转换为代理URL
|
|
||||||
let imageUrl = url;
|
|
||||||
if (!url.includes('/tool/banma/image-proxy')) {
|
|
||||||
imageUrl = process.env.VUE_APP_BASE_API + '/tool/banma/image-proxy?url=' + encodeURIComponent(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const img = new Image();
|
|
||||||
img.crossOrigin = "Anonymous";
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
try {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// 设置合适的尺寸
|
|
||||||
const size = 60;
|
|
||||||
canvas.width = size;
|
|
||||||
canvas.height = size;
|
|
||||||
|
|
||||||
// 绘制图片保持比例
|
|
||||||
const scale = Math.min(size / img.width, size / img.height);
|
|
||||||
const x = (size - img.width * scale) / 2;
|
|
||||||
const y = (size - img.height * scale) / 2;
|
|
||||||
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
|
|
||||||
|
|
||||||
// 获取Base64
|
|
||||||
const base64 = canvas.toDataURL('image/png');
|
|
||||||
resolve(base64);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('处理图片失败:', err);
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
img.onerror = () => resolve(null);
|
|
||||||
img.src = imageUrl;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < this.orderData.length; i++) {
|
|
||||||
const item = this.orderData[i];
|
|
||||||
if (item.productImage) {
|
|
||||||
const base64 = await loadImage(item.productImage);
|
|
||||||
if (base64) {
|
|
||||||
try {
|
|
||||||
const imageId = workbook.addImage({
|
|
||||||
base64: base64,
|
|
||||||
extension: 'png',
|
|
||||||
});
|
|
||||||
|
|
||||||
// 将图片添加到单元格
|
|
||||||
worksheet.addImage(imageId, {
|
|
||||||
tl: { col: 1, row: i + 1 },
|
|
||||||
br: { col: 2, row: i + 2 },
|
|
||||||
editAs: 'oneCell'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('添加图片到Excel失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理图片并导出
|
|
||||||
processImages().then(() => {
|
|
||||||
// 导出文件
|
|
||||||
const fileName = this.dateRange && this.dateRange.length === 2
|
|
||||||
? `斑马订单数据_${this.dateRange[0]}_${this.dateRange[1]}.xlsx`
|
|
||||||
: `斑马订单数据_${parseTime(new Date())}.xlsx`;
|
|
||||||
|
|
||||||
workbook.xlsx.writeBuffer().then(buffer => {
|
|
||||||
const blob = new Blob([buffer], {
|
|
||||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
||||||
});
|
|
||||||
saveAs(blob, fileName);
|
|
||||||
this.$message.success("导出成功");
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('导出Excel失败:', error);
|
|
||||||
this.fallbackExport();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 备用导出方法(不含图片) */
|
|
||||||
fallbackExport() {
|
|
||||||
this.$message.warning("图片导出失败,将导出不含图片的数据");
|
|
||||||
|
|
||||||
// 准备表头和数据
|
|
||||||
const headers = [
|
|
||||||
'下单时间', '商品名称', '乐天订单号', '下单距今时间', '乐天订单金额/日元',
|
|
||||||
'购买数量', '税费/日元', '服务商回款抽点rmb', '商品番号', '1688采购订单号',
|
|
||||||
'采购金额/rmb', '国际运费/rmb', '国内物流公司', '国内物流单号', '日本物流单号',
|
|
||||||
'地址状态'
|
|
||||||
];
|
|
||||||
|
|
||||||
const data = this.orderData.map(item => [
|
|
||||||
item.orderedAt || '',
|
|
||||||
item.productTitle || '',
|
|
||||||
item.shopOrderNumber || '',
|
|
||||||
item.timeSinceOrder || '',
|
|
||||||
item.priceJpy || '',
|
|
||||||
item.productQuantity || '',
|
|
||||||
item.shippingFeeJpy || '',
|
|
||||||
item.serviceFee || '-',
|
|
||||||
item.productNumber || '',
|
|
||||||
item.poNumber || '',
|
|
||||||
item.shippingFeeCny || '',
|
|
||||||
item.internationalShippingFee || '',
|
|
||||||
item.poLogisticsCompany || '',
|
|
||||||
item.poTrackingNumber || '',
|
|
||||||
item.internationalTrackingNumber || '',
|
|
||||||
item.trackInfo || '暂无物流信息'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 创建工作簿并导出
|
|
||||||
const wb = XLSX.utils.book_new();
|
|
||||||
const ws = XLSX.utils.aoa_to_sheet([headers, ...data]);
|
|
||||||
|
|
||||||
// 设置列宽和样式
|
|
||||||
const colWidths = [15, 18, 18, 25, 15, 12, 10, 12, 15, 15, 12, 15, 15, 15, 12, 25];
|
|
||||||
ws['!cols'] = colWidths.map(width => ({ width }));
|
|
||||||
|
|
||||||
// 设置行高
|
|
||||||
const rowHeights = [22]; // 表头高度
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
rowHeights.push(20); // 数据行高度
|
|
||||||
}
|
|
||||||
ws['!rows'] = rowHeights.map(height => ({ hpt: height }));
|
|
||||||
|
|
||||||
// 设置所有单元格居中对齐
|
|
||||||
const range = XLSX.utils.decode_range(ws['!ref']);
|
|
||||||
for (let row = range.s.r; row <= range.e.r; row++) {
|
|
||||||
for (let col = range.s.c; col <= range.e.c; col++) {
|
|
||||||
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
|
|
||||||
if (!ws[cellAddress]) continue;
|
|
||||||
|
|
||||||
if (!ws[cellAddress].s) ws[cellAddress].s = {};
|
|
||||||
ws[cellAddress].s.alignment = { horizontal: 'center', vertical: 'center' };
|
|
||||||
|
|
||||||
if (row === 0) {
|
|
||||||
ws[cellAddress].s.font = { bold: true };
|
|
||||||
ws[cellAddress].s.fill = { fgColor: { rgb: "EEEEEE" } };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
XLSX.utils.book_append_sheet(wb, ws, '订单数据');
|
|
||||||
|
|
||||||
// 导出文件
|
|
||||||
const fileName = this.dateRange && this.dateRange.length === 2
|
|
||||||
? `斑马订单数据_${this.dateRange[0]}_${this.dateRange[1]}_无图片.xlsx`
|
|
||||||
: `斑马订单数据_${parseTime(new Date())}_无图片.xlsx`;
|
|
||||||
|
|
||||||
XLSX.writeFile(wb, fileName);
|
|
||||||
this.$message.success("导出成功");
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理图片加载失败
|
|
||||||
*/
|
|
||||||
handleImageError(event, item) {
|
|
||||||
if (!item.productImage || item.imageProxied) return;
|
|
||||||
|
|
||||||
item.imageProxied = true;
|
|
||||||
const proxyUrl = process.env.VUE_APP_BASE_API + '/tool/banma/image-proxy?url=' +
|
|
||||||
encodeURIComponent(item.productImage);
|
|
||||||
|
|
||||||
item.originalImage = item.productImage;
|
|
||||||
item.productImage = proxyUrl;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 刷新Token */
|
|
||||||
refreshToken() {
|
|
||||||
this.tokenLoading = true;
|
|
||||||
request({
|
|
||||||
url: '/tool/banma/refresh-token',
|
|
||||||
method: 'get',
|
|
||||||
timeout: 99999999
|
|
||||||
}).then(response => {
|
|
||||||
if (response.code === 200) {
|
|
||||||
this.$message.success("Token刷新成功");
|
|
||||||
// 移除自动加载数据
|
|
||||||
} else {
|
|
||||||
this.$message.error(response.msg || "刷新Token失败");
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
this.$message.error("刷新Token失败: " + (error.message || error));
|
|
||||||
}).finally(() => {
|
|
||||||
this.tokenLoading = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.mb8 {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 筛选区域样式优化 */
|
|
||||||
.filter-container {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
background-color: #f9fafc;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 16px;
|
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-form {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-range-item {
|
|
||||||
margin-right: 20px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-picker {
|
|
||||||
width: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-item {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button {
|
|
||||||
margin-right: 10px;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 数据统计信息 */
|
|
||||||
.data-stats-alert {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-info {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606266;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 进度条样式 */
|
|
||||||
.progress-container {
|
|
||||||
margin: 15px 0;
|
|
||||||
padding: 12px;
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-progress {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表格样式 */
|
|
||||||
.el-table {
|
|
||||||
margin-top: 15px;
|
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table :deep(th) {
|
|
||||||
background-color: #f5f7fa !important;
|
|
||||||
color: #303133;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box-card {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.count-number {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #409EFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-text {
|
|
||||||
color: #E6A23C;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.complete-text {
|
|
||||||
color: #67C23A;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-more-data {
|
|
||||||
text-align: center;
|
|
||||||
color: #909399;
|
|
||||||
margin: 20px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price-tag {
|
|
||||||
color: #409eff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fee-tag {
|
|
||||||
color: #e6a23c;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-info-tag {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 150px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table .cell {
|
|
||||||
word-break: break-all;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.5;
|
|
||||||
padding: 4px 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table ::v-deep .el-table__row {
|
|
||||||
height: 65px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table ::v-deep .el-table__header th {
|
|
||||||
padding: 8px 0;
|
|
||||||
height: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table ::v-deep .el-table-column--selection .cell {
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-image {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-card class="box-card">
|
|
||||||
<div slot="header" class="clearfix">
|
|
||||||
<span>1688爬取风控监控</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 统计数据卡片 -->
|
|
||||||
<el-row :gutter="15" style="margin-bottom: 20px;">
|
|
||||||
<el-col :span="4">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-title">总事件数</div>
|
|
||||||
<div class="stat-value">{{ statistics.totalEvents }}</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="4">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-title">移动端被风控次数</div>
|
|
||||||
<div class="stat-value">{{ statistics.mobileBlockedCount }}</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="4">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-title">桌面端被风控次数</div>
|
|
||||||
<div class="stat-value">{{ statistics.desktopBlockedCount }}</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="4">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-title">移动端平均时长</div>
|
|
||||||
<div class="stat-value">{{ statistics.avgMobileDuration }} 秒</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="4">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-title">桌面端平均时长</div>
|
|
||||||
<div class="stat-value">{{ statistics.avgDesktopDuration }} 秒</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="4">
|
|
||||||
<el-card shadow="hover" class="stat-card">
|
|
||||||
<div class="stat-title">独立IP数量</div>
|
|
||||||
<div class="stat-value">{{ statistics.uniqueIpCount }}</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 搜索与操作区域 -->
|
|
||||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
|
||||||
<el-form-item label="IP地址" prop="ipAddress">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.ipAddress"
|
|
||||||
placeholder="请输入IP地址"
|
|
||||||
clearable
|
|
||||||
style="width: 240px"
|
|
||||||
@keyup.enter.native="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="事件类型" prop="eventType">
|
|
||||||
<el-select v-model="queryParams.eventType" placeholder="请选择事件类型" clearable style="width: 240px">
|
|
||||||
<el-option
|
|
||||||
v-for="dict in eventTypeOptions"
|
|
||||||
:key="dict.value"
|
|
||||||
:label="dict.label"
|
|
||||||
:value="dict.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="时间范围" prop="timeRange">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="dateRange"
|
|
||||||
style="width: 240px"
|
|
||||||
value-format="yyyy-MM-dd"
|
|
||||||
type="daterange"
|
|
||||||
range-separator="-"
|
|
||||||
start-placeholder="开始日期"
|
|
||||||
end-placeholder="结束日期"
|
|
||||||
></el-date-picker>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|
||||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|
||||||
<el-button type="success" icon="el-icon-refresh" size="mini" @click="refreshData">刷新数据</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-table
|
|
||||||
v-loading="loading"
|
|
||||||
:data="monitorList"
|
|
||||||
style="width: 100%;"
|
|
||||||
border
|
|
||||||
>
|
|
||||||
<el-table-column label="ID" align="center" prop="id" />
|
|
||||||
<el-table-column label="IP地址" align="center" prop="ip_address" width="140" />
|
|
||||||
<el-table-column label="事件类型" align="center" prop="event_type" width="150">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<el-tag :type="getEventTypeTag(scope.row.event_type)">{{ getEventTypeName(scope.row.event_type) }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="持续时间" align="center" prop="duration" width="120">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ formatDuration(scope.row.duration) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="发生时间" align="center" prop="event_time" width="180">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ formatTimestamp(scope.row.event_time) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="记录时间" align="center" prop="create_time" width="180" />
|
|
||||||
<el-table-column label="用户名" align="center" prop="username" />
|
|
||||||
<el-table-column label="主机名" align="center" prop="hostname" />
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<pagination
|
|
||||||
v-show="total>0"
|
|
||||||
:total="total"
|
|
||||||
:page.sync="queryParams.pageNum"
|
|
||||||
:limit.sync="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { listAlibaba1688Monitor, getAlibaba1688Statistics } from "@/api/monitor/alibaba1688";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "Alibaba1688Monitor",
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
// 遮罩层
|
|
||||||
loading: true,
|
|
||||||
// 显示搜索条件
|
|
||||||
showSearch: true,
|
|
||||||
// 总条数
|
|
||||||
total: 0,
|
|
||||||
// 表格数据
|
|
||||||
monitorList: [],
|
|
||||||
// 统计数据
|
|
||||||
statistics: {
|
|
||||||
totalEvents: 0,
|
|
||||||
mobileBlockedCount: 0,
|
|
||||||
desktopBlockedCount: 0,
|
|
||||||
avgMobileDuration: 0,
|
|
||||||
avgDesktopDuration: 0,
|
|
||||||
uniqueIpCount: 0
|
|
||||||
},
|
|
||||||
// 日期范围
|
|
||||||
dateRange: [],
|
|
||||||
// 事件类型选项
|
|
||||||
eventTypeOptions: [
|
|
||||||
{ value: "MOBILE_FIRST_ACCESS", label: "移动端首次访问" },
|
|
||||||
{ value: "MOBILE_BLOCKED", label: "移动端被风控" },
|
|
||||||
{ value: "DESKTOP_BLOCKED", label: "桌面端被风控" }
|
|
||||||
],
|
|
||||||
// 查询参数
|
|
||||||
queryParams: {
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
ipAddress: undefined,
|
|
||||||
eventType: undefined
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getList();
|
|
||||||
this.getStatistics();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/** 查询监控列表 */
|
|
||||||
getList() {
|
|
||||||
this.loading = true;
|
|
||||||
listAlibaba1688Monitor(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
|
||||||
this.monitorList = response.rows || [];
|
|
||||||
this.total = response.total || 0;
|
|
||||||
this.loading = false;
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('获取监控列表失败:', error);
|
|
||||||
this.monitorList = [];
|
|
||||||
this.total = 0;
|
|
||||||
this.loading = false;
|
|
||||||
this.$message.error('获取监控数据失败,请稍后重试');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/** 获取统计信息 */
|
|
||||||
getStatistics() {
|
|
||||||
getAlibaba1688Statistics().then(response => {
|
|
||||||
this.statistics = response.data || {
|
|
||||||
totalEvents: 0,
|
|
||||||
mobileBlockedCount: 0,
|
|
||||||
desktopBlockedCount: 0,
|
|
||||||
avgMobileDuration: 0,
|
|
||||||
avgDesktopDuration: 0,
|
|
||||||
uniqueIpCount: 0
|
|
||||||
};
|
|
||||||
// 保留小数点后两位
|
|
||||||
if (this.statistics.avgMobileDuration) {
|
|
||||||
this.statistics.avgMobileDuration = this.statistics.avgMobileDuration.toFixed(2);
|
|
||||||
}
|
|
||||||
if (this.statistics.avgDesktopDuration) {
|
|
||||||
this.statistics.avgDesktopDuration = this.statistics.avgDesktopDuration.toFixed(2);
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('获取统计信息失败:', error);
|
|
||||||
this.statistics = {
|
|
||||||
totalEvents: 0,
|
|
||||||
mobileBlockedCount: 0,
|
|
||||||
desktopBlockedCount: 0,
|
|
||||||
avgMobileDuration: 0,
|
|
||||||
avgDesktopDuration: 0,
|
|
||||||
uniqueIpCount: 0
|
|
||||||
};
|
|
||||||
this.$message.error('获取统计数据失败,请稍后重试');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// 事件类型标签
|
|
||||||
getEventTypeTag(type) {
|
|
||||||
switch (type) {
|
|
||||||
case "MOBILE_FIRST_ACCESS": return "info";
|
|
||||||
case "MOBILE_BLOCKED": return "warning";
|
|
||||||
case "DESKTOP_BLOCKED": return "danger";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 事件类型名称
|
|
||||||
getEventTypeName(type) {
|
|
||||||
switch (type) {
|
|
||||||
case "MOBILE_FIRST_ACCESS": return "移动端首次访问";
|
|
||||||
case "MOBILE_BLOCKED": return "移动端被风控";
|
|
||||||
case "DESKTOP_BLOCKED": return "桌面端被风控";
|
|
||||||
default: return type;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 格式化持续时间
|
|
||||||
formatDuration(duration) {
|
|
||||||
if (!duration || duration <= 0) return "无";
|
|
||||||
if (duration < 1000) return duration + "毫秒";
|
|
||||||
return (duration / 1000).toFixed(2) + "秒";
|
|
||||||
},
|
|
||||||
// 格式化时间戳
|
|
||||||
formatTimestamp(timestamp) {
|
|
||||||
if (!timestamp) return "";
|
|
||||||
const date = new Date(Number(timestamp));
|
|
||||||
return date.toLocaleString();
|
|
||||||
},
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
handleQuery() {
|
|
||||||
this.queryParams.pageNum = 1;
|
|
||||||
this.getList();
|
|
||||||
},
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
resetQuery() {
|
|
||||||
this.dateRange = [];
|
|
||||||
this.resetForm("queryForm");
|
|
||||||
this.handleQuery();
|
|
||||||
},
|
|
||||||
/** 刷新数据 */
|
|
||||||
refreshData() {
|
|
||||||
this.getList();
|
|
||||||
this.getStatistics();
|
|
||||||
this.$message.success("数据刷新成功");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.stat-card {
|
|
||||||
text-align: center;
|
|
||||||
height: 120px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.stat-title {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.stat-value {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #409EFF;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-card class="box-card">
|
|
||||||
<template #header>
|
|
||||||
<div class="clearfix">
|
|
||||||
<span class="card-title">图片爬取</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
icon="el-icon-search"
|
|
||||||
@click="handleScrape"
|
|
||||||
:loading="loading"
|
|
||||||
>开始爬取</el-button>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row v-if="imageUrls.length > 0" class="image-container">
|
|
||||||
<el-col :xs="8" :sm="6" :md="4" :lg="4" :xl="3" v-for="(url, index) in imageUrls" :key="index">
|
|
||||||
<el-image :src="url" :preview-src-list="[url]" fit="cover" class="image-item" />
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-empty v-else description="暂无图片数据" />
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { scrapeImages } from "@/api/prod/ozon";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "Ozon",
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
imageUrls: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleScrape() {
|
|
||||||
this.loading = true;
|
|
||||||
this.imageUrls = [];
|
|
||||||
|
|
||||||
scrapeImages().then(response => {
|
|
||||||
if (response.code === 200) {
|
|
||||||
this.imageUrls = response.data || [];
|
|
||||||
if (this.imageUrls.length === 0) {
|
|
||||||
this.$message.warning("未找到图片");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$message.error(response.msg || "爬取失败");
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
}).catch(() => {
|
|
||||||
this.$message.error("爬取请求失败");
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.card-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.image-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.image-item {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user