1
This commit is contained in:
@@ -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