This commit is contained in:
2025-09-30 09:51:39 +08:00
parent fa7edc0cc2
commit 1494c61541
18 changed files with 0 additions and 3735 deletions

View File

@@ -1 +0,0 @@
DEVICE-C705AA3904F84D998D03B5CD83EEBBD7

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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": []
}
}

View File

@@ -1 +0,0 @@
DEVICE-CD31378598644EAA8C0E8153A9D80959

Binary file not shown.

View File

@@ -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'
})
}

View File

@@ -1,9 +0,0 @@
import request from '@/utils/request'
// 爬取图片
export function scrapeImages() {
return request({
url: '/prod/ozon/scrapeImages',
method: 'get'
})
}

View File

@@ -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'
})
}

View File

@@ -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'
})
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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