Bläddra i källkod

chore: 更新依赖和优化组件,添加图表自适应功能。修改了多个组件的样式和逻辑,确保在窗口大小变化时图表能够正确响应。移除不再使用的路由和组件,提升代码整洁性。

master
lizhuang 3 dagar sedan
förälder
incheckning
c5acc51a55

+ 13
- 11
package.json Visa fil

@@ -21,11 +21,16 @@
]
},
"dependencies": {
"@babel/parser": "^7.7.4",
"@babel/runtime": "^7.27.6",
"@riophae/vue-treeselect": "0.4.0",
"axios": "0.24.0",
"beautifier": "^0.1.7",
"bpmn-js": "^7.2.1",
"china-area-data": "^5.0.1",
"clipboard": "2.0.8",
"core-js": "3.25.3",
"core-js": "^3.44.0",
"css-loader": "^3.5.3",
"echarts": "5.4.0",
"element-ui": "2.15.14",
"file-saver": "2.0.5",
@@ -34,27 +39,24 @@
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"moment": "^2.30.1",
"npm": "^6.13.7",
"nprogress": "0.2.0",
"quill": "1.3.7",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"voca": "^1.4.0",
"vue": "2.6.12",
"vue-barcode": "^1.3.0",
"vue-codemirror": "^4.0.6",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-meta": "2.4.0",
"vue-quill-editor": "^3.0.6",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0",
"workflow-bpmn-modeler": "^0.2.8",
"@babel/parser": "^7.7.4",
"beautifier": "^0.1.7",
"china-area-data": "^5.0.1",
"css-loader": "^3.5.3",
"npm": "^6.13.7",
"voca": "^1.4.0",
"vue-barcode": "^1.3.0",
"vue-codemirror": "^4.0.6",
"vue-quill-editor": "^3.0.6"
"workflow-bpmn-modeler": "^0.2.8"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",

+ 1
- 1
src/components/ProductCodeAutocomplete/index.vue Visa fil

@@ -71,7 +71,7 @@ export default {
this.isLoading = true
const response = await getProductCodeSuggestions({ keyword: queryString.trim() })

if (response.success) {
if (response.code === 200) {
callback(response.data || [])
} else {
callback([])

+ 0
- 72
src/router/index.js Visa fil

@@ -93,78 +93,6 @@ export const constantRoutes = [
component: () => import("@/views/m/checkin"),
meta: { title: "考勤打卡", icon: "date" },
},
{
path: "/sales-analysis",
component: Layout,
meta: { title: "销售分析", icon: "dashboard", noCache: true },
children: [
// 基准数据管理
{
path: "/sales-analysis/categories",
name: "Categories",
component: () =>
import("@/views/sales-analysis/categories/Categories.vue"),
meta: { title: "分类管理", noCache: true },
},
{
path: "/sales-analysis/base-data",
name: "BaseData",
component: () =>
import("@/views/sales-analysis/base-data/BaseData.vue"),
meta: { title: "基准数据", noCache: true },
},
{
path: "/sales-analysis/shop-customer",
name: "ShopCustomer",
component: () =>
import("@/views/sales-analysis/shop-customer/ShopCustomer.vue"),
meta: { title: "店铺客户关联", noCache: true },
},
// 分析数据管理
{
path: "/sales-analysis/analysis-data/import",
name: "ImportData",
component: () =>
import("@/views/sales-analysis/analysis-data/ImportData.vue"),
meta: { title: "导入数据", noCache: true },
},
{
path: "/sales-analysis/analysis-data/list",
name: "DataList",
component: () =>
import("@/views/sales-analysis/analysis-data/DataList.vue"),
meta: { title: "数据展示", noCache: true },
},
{
path: "/sales-analysis/reports/overall",
name: "OverallAnalysis",
component: () =>
import("@/views/sales-analysis/reports/OverallAnalysis.vue"),
meta: { title: "整体销售分析", noCache: true },
},
{
path: "/sales-analysis/reports/shop",
name: "ShopReports",
component: () =>
import("@/views/sales-analysis/reports/ShopAnalysis.vue"),
meta: { title: "店铺销售分析", noCache: true },
},
{
path: "/sales-analysis/reports/category",
name: "CategoryReports",
component: () =>
import("@/views/sales-analysis/reports/CategoryAnalysis.vue"),
meta: { title: "品类销售分析", noCache: true },
},
{
path: "/sales-analysis/reports/product",
name: "ProductReports",
component: () =>
import("@/views/sales-analysis/reports/ProductAnalysis.vue"),
meta: { title: "单品销售分析", noCache: true },
},
],
},
];

// 动态路由,基于用户权限动态去加载

+ 1
- 0
src/views/sales-analysis/analysis-data/DataList.vue Visa fil

@@ -189,6 +189,7 @@
:total="pagination.total"
:page.sync="pagination.page"
:limit.sync="pagination.pageSize"
:page-sizes="[50, 100, 300, 500, 1000]"
@pagination="fetchData"
/>


+ 7
- 7
src/views/sales-analysis/analysis-data/ImportData.vue Visa fil

@@ -154,8 +154,8 @@
show-icon
:closable="false"
>
<p>请确保Excel文件包含以下列:</p>
<p>
<p style="color: #303030;">当前选择分类是:<u>{{ selectedCategory.name }}</u>,请确保Excel文件包含以下列:</p>
<p style="color: #303030;">
<strong
>出库日期、目的地、店铺名称、出库类型、发送方式、发送番号、注文番号、商品编号、商品名称、数量、单价、送料、代引、客户名称、备注</strong
>
@@ -170,8 +170,8 @@
show-icon
:closable="false"
>
<p>请确保Excel文件包含以下列:</p>
<p>
<p style="color: #303030;">当前选择的店铺是:<u>{{ selectedShopName }}</u>,请确保Excel文件包含以下列:</p>
<p style="color: #303030;">
<strong
>出荷日、出品者SKU、FNSKU、ASIN、FC、数量、Amazon注文番号、通貨、商品金額(商品1点ごと)、配送料、ギフト包装手数料、配送先(市区町村)、都道府県名、配送先(郵便番号)、付与されたAmazon
ポイント</strong
@@ -197,7 +197,7 @@
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
支持多选Excel文件(.xlsx/.xls),单文件不超过10MB
支持多选Excel文件(.xlsx/.xls),单文件不超过10MB,文件数量不超过40个
<el-button @click="clearAllFiles" type="text"
>清空文件列表</el-button
>
@@ -261,8 +261,8 @@
:data="processResult.data.slice(0, 50)"
stripe
border
size="small"
height="calc(100vh - 470px)"
size="mini"
height="calc(100vh - 490px)"
>
<el-table-column
prop="rowNumber"

+ 98
- 44
src/views/sales-analysis/reports/CategoryAnalysis.vue Visa fil

@@ -1,5 +1,5 @@
<template>
<div class="overall-analysis-page">
<div class="app-container">
<!-- 筛选 -->
<div class="filter-card">
<div class="filter-section">
@@ -56,35 +56,13 @@
:key="dimension"
class="dimension-section"
>
<div class="dimension-header">
<div class="dimension-title">
<i class="el-icon-data-analysis"></i>
<span>{{ dimension }}维度分析</span>
</div>
<div class="dimension-stats">
<span class="stat-item category-stat">
<i class="el-icon-folder-opened"></i>
归属分类: <strong>{{ currentCategory }}</strong>
</span>
<span class="stat-item">
<i class="el-icon-money"></i>
金额项: {{ chartData.amountData.length }}
</span>
<span class="stat-item">
<i class="el-icon-s-goods"></i>
数量项: {{ chartData.quantityData.length }}
</span>
</div>
</div>

<el-row :gutter="24">
<el-row :gutter="10">
<el-col :span="12">
<!-- 销售金额占比 -->
<div class="chart-card">
<div class="card-header">
<div class="header-left">
<i class="el-icon-money header-icon-amount"></i>
<span>销售金额占比</span>
<span>{{ currentCategory }} {{ dimension }} 销售金额占比</span>
</div>
<div class="header-right">
<span class="total-amount"
@@ -108,8 +86,7 @@
<div class="chart-card">
<div class="card-header">
<div class="header-left">
<i class="el-icon-s-goods header-icon-quantity"></i>
<span>销售数量占比</span>
<span>{{ currentCategory }} {{ dimension }} 销售数量占比</span>
</div>
<div class="header-right">
<span class="total-quantity"
@@ -163,14 +140,35 @@ export default {
dimensionCharts: {},
chartInstances: new Map(), // 存储图表实例
currentCategory: "全部分类",
resizeTimer: null, // 防抖定时器
resizeObserver: null, // ResizeObserver 实例
};
},
mounted() {
this.initDateRange();
this.fetchFilterOptions();
// this.fetchData();
// 添加窗口大小变化监听
window.addEventListener('resize', this.handleResize);
// 使用 ResizeObserver 监听容器尺寸变化
this.initResizeObserver();
},
beforeDestroy() {
// 移除窗口大小变化监听
window.removeEventListener('resize', this.handleResize);
// 清除防抖定时器
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
// 断开 ResizeObserver
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
// 销毁所有图表实例
this.chartInstances.forEach((chart) => {
if (chart) {
@@ -249,6 +247,11 @@ export default {

this.$nextTick(() => {
this.renderAllCharts();
// 数据更新后,确保图表能够响应 resize
setTimeout(() => {
this.handleResize();
}, 200);
});
} else {
this.$message.error(response.message || "获取数据失败");
@@ -296,6 +299,10 @@ export default {
if (!chartDom || !data || data.length === 0) return;

const chart = echarts.init(chartDom);
// 为图表添加 resize 监听
chart.resize = chart.resize.bind(chart);
this.chartInstances.set(chartId, chart);
const totalAmount = this.getTotalAmount(data);
const processedData = this.processChartData(data);
@@ -437,6 +444,10 @@ export default {
if (!chartDom || !data || data.length === 0) return;

const chart = echarts.init(chartDom);
// 为图表添加 resize 监听
chart.resize = chart.resize.bind(chart);
this.chartInstances.set(chartId, chart);
const totalQuantity = this.getTotalQuantity(data);
const processedData = this.processChartData(data, false);
@@ -657,19 +668,62 @@ export default {
getTotalQuantity(data) {
return data.reduce((sum, item) => sum + (item.value || 0), 0);
},

// 处理窗口大小变化
handleResize() {
console.log('窗口大小变化,触发 resize 处理');
// 使用防抖处理,避免频繁调用
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
this.resizeTimer = setTimeout(() => {
console.log('执行图表 resize');
// 强制触发 DOM 重新计算
this.$forceUpdate();
// 遍历所有图表实例并执行 resize
this.chartInstances.forEach((chart, chartId) => {
if (chart && chart.getDom()) {
console.log(`resize 图表: ${chartId}`);
chart.resize();
}
});
}, 100);
},

// 初始化 ResizeObserver
initResizeObserver() {
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver((entries) => {
console.log('容器尺寸变化检测到');
this.handleResize();
});
// 监听图表容器
this.$nextTick(() => {
const chartContainers = document.querySelectorAll('.chart-container');
chartContainers.forEach(container => {
this.resizeObserver.observe(container);
});
});
}
},
},
};
</script>

<style scoped>
.overall-analysis-page {
height: 100vh;
overflow-y: auto;
margin: -20px;
padding: 10px;
.app-container {
display: flex;
flex-direction: column;
gap: 15px;
gap: 10px;
padding: 10px;
width: 100%;
min-height: 100vh;
box-sizing: border-box;
}

.filter-card {
@@ -714,7 +768,7 @@ export default {
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
gap: 10px;
}

.stat-card {
@@ -723,7 +777,7 @@ export default {
border-radius: 8px;
display: flex;
align-items: center;
gap: 15px;
gap: 10px;
border: 1px solid #e8e8e8;
transition: background-color 0.2s ease;
}
@@ -793,15 +847,17 @@ export default {
}

.charts-section {
width: 100%;
min-width: 0;
display: flex;
flex-direction: column;
gap: 30px;
gap: 10px;
}

.dimension-section {
display: flex;
flex-direction: column;
gap: 20px;
gap: 10px;
overflow: hidden;
}

@@ -809,10 +865,7 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: #f8f9fa;
border-radius: 8px;
border-bottom: 1px solid #e8e8e8;
padding: 15px 0;
}

.dimension-title {
@@ -820,7 +873,6 @@ export default {
align-items: center;
gap: 10px;
font-size: 18px;
font-weight: 600;
color: #333;
}

@@ -831,7 +883,7 @@ export default {

.dimension-stats {
display: flex;
gap: 20px;
gap: 10px;
}

.stat-item {
@@ -862,6 +914,9 @@ export default {
.chart-container {
padding: 15px 0;
width: 100%;
height: auto;
min-height: 0;
overflow: hidden;
}

.card-header {
@@ -877,7 +932,6 @@ export default {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
font-size: 16px;
color: #333;
}

+ 99
- 10
src/views/sales-analysis/reports/OverallAnalysis.vue Visa fil

@@ -1,5 +1,5 @@
<template>
<div class="overall-analysis-page">
<div class="app-container">
<!-- 筛选 -->
<div class="filter-card">
<div class="filter-section">
@@ -267,6 +267,8 @@ export default {
trendChart: null,
topProductsChart: null,
brandChart: null,
resizeTimer: null, // 防抖定时器
resizeObserver: null, // ResizeObserver 实例
};
},
filters: {
@@ -292,8 +294,26 @@ export default {
this.initDateRange();
this.fetchFilterOptions();
this.fetchData();
// 添加窗口大小变化监听
window.addEventListener('resize', this.handleResize);
// 使用 ResizeObserver 监听容器尺寸变化
this.initResizeObserver();
},
beforeDestroy() {
// 移除窗口大小变化监听
window.removeEventListener('resize', this.handleResize);
// 清除防抖定时器
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
// 断开 ResizeObserver
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.trendChart) {
this.trendChart.dispose();
}
@@ -401,6 +421,11 @@ export default {
this.renderTrendChart();
this.renderTopProductsChart();
this.renderBrandChart();
// 数据更新后,确保图表能够响应 resize
setTimeout(() => {
this.handleResize();
}, 200);
});
} else {
this.$message.error(response.message || "获取数据失败");
@@ -431,6 +456,9 @@ export default {
}

this.trendChart = echarts.init(chartDom);
// 为图表添加 resize 监听
this.trendChart.resize = this.trendChart.resize.bind(this.trendChart);

const dates = this.statsData.trendData.map((item) => item.date);
const amounts = this.statsData.trendData.map((item) => item.amount);
@@ -451,7 +479,7 @@ export default {
tooltipText += `${param.marker} ${param.seriesName}: `;
const value = param.value || 0;
if (param.seriesName === "销售额") {
tooltipText += `¥${this.formatNumber(value.toFixed(0))}`;
tooltipText += `¥${this.formatNumber(value)}`;
} else {
tooltipText += `${this.formatNumber(value)}`;
}
@@ -605,6 +633,10 @@ export default {
}

this.topProductsChart = echarts.init(chartDom);
// 为图表添加 resize 监听
this.topProductsChart.resize = this.topProductsChart.resize.bind(this.topProductsChart);
const data = [...this.statsData.topProducts]
.sort((a, b) => a.amount - b.amount)
.map((item) => ({
@@ -710,6 +742,9 @@ export default {
}

this.brandChart = echarts.init(chartDom);
// 为图表添加 resize 监听
this.brandChart.resize = this.brandChart.resize.bind(this.brandChart);

const brands = this.statsData.brandData.map(
(item) => item.brand
@@ -885,19 +920,68 @@ export default {
if (rate < 0) return "growth-negative";
return "growth-neutral";
},

// 处理窗口大小变化
handleResize() {
console.log('窗口大小变化,触发 resize 处理');
// 使用防抖处理,避免频繁调用
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
this.resizeTimer = setTimeout(() => {
console.log('执行图表 resize');
// 强制触发 DOM 重新计算
this.$forceUpdate();
// 检查图表是否存在且已初始化
if (this.trendChart && this.trendChart.getDom()) {
console.log('resize 趋势图');
this.trendChart.resize();
}
if (this.topProductsChart && this.topProductsChart.getDom()) {
console.log('resize TOP20商品图');
this.topProductsChart.resize();
}
if (this.brandChart && this.brandChart.getDom()) {
console.log('resize 品牌图');
this.brandChart.resize();
}
}, 100);
},

// 初始化 ResizeObserver
initResizeObserver() {
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver((entries) => {
console.log('容器尺寸变化检测到');
this.handleResize();
});
// 监听图表容器
this.$nextTick(() => {
const chartContainers = document.querySelectorAll('.chart-container');
chartContainers.forEach(container => {
this.resizeObserver.observe(container);
});
});
}
},
},
};
</script>

<style scoped>
.overall-analysis-page {
height: 100vh;
overflow-y: auto;
margin: -20px;
padding: 10px;
.app-container {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
width: 100%;
min-height: 100vh;
box-sizing: border-box;
}
.filter-card {
background-color: #fff;
@@ -1016,9 +1100,11 @@ export default {
}

.charts-section {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
width: 100%;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10px;
}

.chart-card {
@@ -1027,6 +1113,9 @@ export default {

.chart-container {
width: 100%;
height: auto;
min-height: 0;
overflow: hidden;
}

.card-header {

+ 161
- 16
src/views/sales-analysis/reports/ProductAnalysis.vue Visa fil

@@ -1,5 +1,5 @@
<template>
<div class="product-analysis-page">
<div class="app-container">
<!-- 筛选 -->
<div class="filter-card">
<div class="filter-section">
@@ -99,6 +99,8 @@ export default {
},
trendChart: null,
rankingChart: null,
resizeTimer: null, // 防抖定时器
resizeObserver: null, // ResizeObserver 实例
};
},
computed: {
@@ -114,8 +116,32 @@ export default {
this.initDateRange();
this.fetchFilterOptions();
// this.fetchData();
// 添加窗口大小变化监听
window.addEventListener('resize', this.handleResize);
// 使用 ResizeObserver 监听容器尺寸变化
this.initResizeObserver();
// 初始化图表实例,即使没有数据也要创建
this.$nextTick(() => {
this.initCharts();
});
},
beforeDestroy() {
// 移除窗口大小变化监听
window.removeEventListener('resize', this.handleResize);
// 清除防抖定时器
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
// 断开 ResizeObserver
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.trendChart) {
this.trendChart.dispose();
}
@@ -184,6 +210,11 @@ export default {
this.$nextTick(() => {
this.renderTrendChart();
this.renderRankingChart();
// 数据更新后,确保图表能够响应 resize
setTimeout(() => {
this.handleResize();
}, 200);
});
} else {
this.$message.error(response.message || "获取数据失败");
@@ -211,13 +242,15 @@ export default {
const chartDom = document.getElementById("trendChart");
if (!chartDom) return;

if (this.trendChart) {
this.trendChart.dispose();
// 如果图表实例不存在,先初始化
if (!this.trendChart) {
this.trendChart = echarts.init(chartDom);
this.trendChart.resize = this.trendChart.resize.bind(this.trendChart);
}
this.trendChart = echarts.init(chartDom);

if (!this.statsData.trendData || this.statsData.trendData.length === 0) {
this.trendChart.clear();
// 即使没有数据也要保持图表实例,以便 resize 功能正常工作
return;
}

@@ -296,16 +329,18 @@ export default {
const chartDom = document.getElementById("rankingChart");
if (!chartDom) return;

if (this.rankingChart) {
this.rankingChart.dispose();
// 如果图表实例不存在,先初始化
if (!this.rankingChart) {
this.rankingChart = echarts.init(chartDom);
this.rankingChart.resize = this.rankingChart.resize.bind(this.rankingChart);
}
this.rankingChart = echarts.init(chartDom);

if (
!this.statsData.rankingData ||
this.statsData.rankingData.length === 0
) {
this.rankingChart.clear();
// 即使没有数据也要保持图表实例,以便 resize 功能正常工作
return;
}

@@ -413,19 +448,124 @@ export default {
if (num === null || num === undefined) return "0";
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},

// 处理窗口大小变化
handleResize() {
console.log('窗口大小变化,触发 resize 处理');
// 使用防抖处理,避免频繁调用
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
this.resizeTimer = setTimeout(() => {
console.log('执行图表 resize');
// 强制触发 DOM 重新计算
this.$forceUpdate();
// 延迟执行 resize,确保 DOM 更新完成
this.$nextTick(() => {
// 检查图表是否存在且已初始化
if (this.trendChart && this.trendChart.getDom()) {
console.log('resize 趋势图');
this.trendChart.resize();
} else {
console.log('趋势图不存在或未初始化');
}
if (this.rankingChart && this.rankingChart.getDom()) {
console.log('resize 排行图');
this.rankingChart.resize();
} else {
console.log('排行图不存在或未初始化');
}
// 额外延迟执行强制 resize,确保大变小的情况也能处理
setTimeout(() => {
this.forceChartResize();
}, 200);
});
}, 100);
},

// 初始化 ResizeObserver
initResizeObserver() {
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver((entries) => {
console.log('容器尺寸变化检测到');
this.handleResize();
});
// 监听图表容器
this.$nextTick(() => {
const chartContainers = document.querySelectorAll('.chart-container');
chartContainers.forEach(container => {
this.resizeObserver.observe(container);
});
});
}
},

// 强制图表 resize
forceChartResize() {
console.log('强制图表 resize');
// 强制所有图表重新计算尺寸
if (this.trendChart && this.trendChart.getDom()) {
const container = this.trendChart.getDom();
// 临时改变容器尺寸,然后恢复,强制触发 resize
const originalWidth = container.style.width;
container.style.width = '99%';
setTimeout(() => {
container.style.width = originalWidth;
this.trendChart.resize();
}, 10);
}
if (this.rankingChart && this.rankingChart.getDom()) {
const container = this.rankingChart.getDom();
const originalWidth = container.style.width;
container.style.width = '99%';
setTimeout(() => {
container.style.width = originalWidth;
this.rankingChart.resize();
}, 10);
}
},

// 初始化图表实例
initCharts() {
console.log('初始化图表实例');
// 初始化趋势图
const trendChartDom = document.getElementById("trendChart");
if (trendChartDom && !this.trendChart) {
this.trendChart = echarts.init(trendChartDom);
this.trendChart.resize = this.trendChart.resize.bind(this.trendChart);
console.log('趋势图实例已创建');
}
// 初始化排行图
const rankingChartDom = document.getElementById("rankingChart");
if (rankingChartDom && !this.rankingChart) {
this.rankingChart = echarts.init(rankingChartDom);
this.rankingChart.resize = this.rankingChart.resize.bind(this.rankingChart);
console.log('排行图实例已创建');
}
},
},
};
</script>

<style scoped>
.product-analysis-page {
height: 100vh;
overflow-y: auto;
margin: -20px;
padding: 10px;
.app-container {
display: flex;
flex-direction: column;
gap: 15px;
gap: 10px;
padding: 10px;
width: 100%;
min-height: 100vh;
box-sizing: border-box;
}
.filter-card {
background-color: #fff;
@@ -456,9 +596,11 @@ export default {
}

.charts-section {
width: 100%;
min-width: 0;
display: flex;
flex-direction: column;
gap: 20px;
gap: 10px;
}

.chart-card {
@@ -470,13 +612,16 @@ export default {
.card-header {
display: flex;
align-items: center;
font-weight: 600;
justify-content: center;
font-size: 18px;
padding: 15px 20px;
border-bottom: 1px solid #e8e8e8;
}
.chart-container {
padding: 20px;
width: 100%;
height: auto;
min-height: 0;
overflow: hidden;
}
.no-data-section {
display: flex;

+ 114
- 37
src/views/sales-analysis/reports/ShopAnalysis.vue Visa fil

@@ -1,5 +1,5 @@
<template>
<div class="overall-analysis-page">
<div class="app-container">
<!-- 筛选 -->
<div class="filter-card">
<div class="filter-section">
@@ -24,8 +24,6 @@
</el-select>
</div>



<div class="filter-item">
<label>日期范围:</label>
<el-date-picker
@@ -43,7 +41,12 @@
</div>

<div class="filter-item">
<el-button type="primary" @click="fetchData" :loading="loading" size="small">
<el-button
type="primary"
@click="fetchData"
:loading="loading"
size="small"
>
查询分析
</el-button>
<el-button @click="resetFilter" size="small">重置筛选</el-button>
@@ -52,10 +55,9 @@
</div>
</div>


<!-- 图表区域 -->
<div class="charts-section">
<el-row :gutter="20">
<el-row :gutter="10">
<el-col :span="12">
<!-- 各店铺销售金额占比 -->
<div class="chart-card">
@@ -86,25 +88,25 @@
</el-col>
</el-row>

<!-- 各品类销售趋势 -->
<div class="chart-card">
<div class="card-header">
<span>各品类销售数量和销售额趋势</span>
</div>
<div class="chart-container">
<div id="categoryTrendChart" style="width: 100%; height: 500px"></div>
</div>
<!-- 各品类销售趋势 -->
<div class="chart-card">
<div class="card-header">
<span>各品类销售数量和销售额趋势</span>
</div>
<div class="chart-container">
<div id="categoryTrendChart" style="width: 100%; height: 500px"></div>
</div>



</div>
</div>
</div>
</template>

<script>
import * as echarts from "echarts";
import { getReportFilterOptions, getShopAnalysisReport } from '@/api/sales-analysis'
import {
getReportFilterOptions,
getShopAnalysisReport,
} from "@/api/sales-analysis";

export default {
name: "ShopAnalysis",
@@ -128,6 +130,8 @@ export default {
shopAmountChart: null,
shopQuantityChart: null,
categoryTrendChart: null,
resizeTimer: null, // 防抖定时器
resizeObserver: null, // ResizeObserver 实例
};
},
filters: {
@@ -153,8 +157,26 @@ export default {
this.initDateRange();
this.fetchFilterOptions();
this.fetchData();
// 添加窗口大小变化监听
window.addEventListener('resize', this.handleResize);
// 使用 ResizeObserver 监听容器尺寸变化
this.initResizeObserver();
},
beforeDestroy() {
// 移除窗口大小变化监听
window.removeEventListener('resize', this.handleResize);
// 清除防抖定时器
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
// 断开 ResizeObserver
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.shopAmountChart) {
this.shopAmountChart.dispose();
}
@@ -164,7 +186,6 @@ export default {
if (this.categoryTrendChart) {
this.categoryTrendChart.dispose();
}

},
methods: {
// 初始化日期范围(默认最近30天)
@@ -193,8 +214,6 @@ export default {
this.fetchData();
},



// 获取筛选选项
async fetchFilterOptions() {
try {
@@ -233,19 +252,20 @@ export default {
params.shop = this.filters.shop;
}



const response = await getShopAnalysisReport(params);

if (response.code === 200) {
this.statsData = response.data;

this.$nextTick(() => {
this.renderShopAmountChart();
this.renderShopQuantityChart();
this.renderCategoryTrendChart();
// 数据更新后,确保图表能够响应 resize
setTimeout(() => {
this.handleResize();
}, 200);
});
} else {
this.$message.error(response.message || "获取数据失败");
@@ -279,6 +299,9 @@ export default {
}

this.shopAmountChart = echarts.init(chartDom);
// 为图表添加 resize 监听
this.shopAmountChart.resize = this.shopAmountChart.resize.bind(this.shopAmountChart);

const data = this.statsData.shopAmountData.map((item) => ({
name: item.shopName,
@@ -382,6 +405,9 @@ export default {
}

this.shopQuantityChart = echarts.init(chartDom);
// 为图表添加 resize 监听
this.shopQuantityChart.resize = this.shopQuantityChart.resize.bind(this.shopQuantityChart);

const data = this.statsData.shopQuantityData.map((item) => ({
name: item.shopName,
@@ -485,6 +511,9 @@ export default {
}

this.categoryTrendChart = echarts.init(chartDom);
// 为图表添加 resize 监听
this.categoryTrendChart.resize = this.categoryTrendChart.resize.bind(this.categoryTrendChart);

const categories = this.statsData.categoryTrendData.map(
(item) => item.category
@@ -648,28 +677,73 @@ export default {
this.categoryTrendChart.setOption(option);
},



// 格式化数字
formatNumber(num) {
if (!num) return "0";
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},

// 处理窗口大小变化
handleResize() {
console.log('窗口大小变化,触发 resize 处理');
// 使用防抖处理,避免频繁调用
if (this.resizeTimer) {
clearTimeout(this.resizeTimer);
}
this.resizeTimer = setTimeout(() => {
console.log('执行图表 resize');
// 强制触发 DOM 重新计算
this.$forceUpdate();
// 检查图表是否存在且已初始化
if (this.shopAmountChart && this.shopAmountChart.getDom()) {
console.log('resize 店铺金额图');
this.shopAmountChart.resize();
}
if (this.shopQuantityChart && this.shopQuantityChart.getDom()) {
console.log('resize 店铺数量图');
this.shopQuantityChart.resize();
}
if (this.categoryTrendChart && this.categoryTrendChart.getDom()) {
console.log('resize 品类趋势图');
this.categoryTrendChart.resize();
}
}, 100);
},

// 初始化 ResizeObserver
initResizeObserver() {
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver((entries) => {
console.log('容器尺寸变化检测到');
this.handleResize();
});
// 监听图表容器
this.$nextTick(() => {
const chartContainers = document.querySelectorAll('.chart-container');
chartContainers.forEach(container => {
this.resizeObserver.observe(container);
});
});
}
},
},
};
</script>

<style scoped>
.overall-analysis-page {
height: 100vh;
overflow-y: auto;
margin: -20px;
padding: 10px;
.app-container {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
width: 100%;
min-height: 100vh;
box-sizing: border-box;
}
.filter-card {
background-color: #fff;
@@ -772,9 +846,11 @@ export default {
}

.charts-section {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
width: 100%;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10px;
}

.chart-card {
@@ -783,6 +859,9 @@ export default {

.chart-container {
width: 100%;
height: auto;
min-height: 0;
overflow: hidden;
}

.card-header {
@@ -794,8 +873,6 @@ export default {
justify-content: center;
}



.chart-card {
background-color: #fff;
border-radius: 8px;

Laddar…
Avbryt
Spara