|
|
|
@@ -254,7 +254,7 @@
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<span>畅销品TOP10</span>
|
|
|
|
|
<!-- <el-button size="small" @click="viewAllTopProducts">查看全部</el-button> -->
|
|
|
|
|
<el-button size="small" @click="viewAllTopProducts">查看全部</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<el-table :data="topSellingProducts" stripe v-loading="loading">
|
|
|
|
@@ -299,7 +299,7 @@
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<span>滞销品TOP10</span>
|
|
|
|
|
<!-- <el-button size="small" @click="viewAllSlowProducts">查看全部</el-button> -->
|
|
|
|
|
<el-button size="small" @click="viewAllSlowProducts">查看全部</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<el-table :data="slowSellingProducts" stripe v-loading="loading">
|
|
|
|
@@ -415,20 +415,11 @@
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<!-- 商品排名详情弹窗 -->
|
|
|
|
|
<!-- <ProductRankingDialog
|
|
|
|
|
:visible="dialogVisible"
|
|
|
|
|
:type="dialogType"
|
|
|
|
|
:query-params="queryForm"
|
|
|
|
|
@close="handleDialogClose"
|
|
|
|
|
@view-detail="handleViewProductDetail"
|
|
|
|
|
/> -->
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup name="RealtimeSalesReport">
|
|
|
|
|
import { ref, reactive, onMounted, nextTick, watch } from 'vue';
|
|
|
|
|
import { ref, reactive, onMounted, nextTick } from 'vue';
|
|
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
|
import {
|
|
|
|
|
Refresh,
|
|
|
|
@@ -451,21 +442,15 @@ import {
|
|
|
|
|
getCustomerAnalysis,
|
|
|
|
|
refreshRealtimeData,
|
|
|
|
|
} from '/@/api/supermarketerp/realtimeSalesReport';
|
|
|
|
|
// import ProductRankingDialog from './components/ProductRankingDialog.vue';
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
const refreshing = ref(false);
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const isInitialized = ref(false);
|
|
|
|
|
const debounceTimer = ref<number | null>(null);
|
|
|
|
|
const debounceTimer = ref<NodeJS.Timeout | null>(null);
|
|
|
|
|
const salesTrendChart = ref<HTMLElement>();
|
|
|
|
|
const customerAnalysisChart = ref<HTMLElement>();
|
|
|
|
|
|
|
|
|
|
// 弹窗相关数据(已注释)
|
|
|
|
|
// const dialogVisible = ref(false);
|
|
|
|
|
// const dialogType = ref<'top' | 'slow'>('top');
|
|
|
|
|
// const selectedProduct = ref(null);
|
|
|
|
|
|
|
|
|
|
// 查询表单
|
|
|
|
|
const queryForm = reactive({
|
|
|
|
|
queryType: 'day',
|
|
|
|
@@ -479,16 +464,16 @@ const queryForm = reactive({
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 高级选项显示状态
|
|
|
|
|
const advancedOptionsVisible = ref<string[]>([]);
|
|
|
|
|
const advancedOptionsVisible = ref([]);
|
|
|
|
|
|
|
|
|
|
// 数据
|
|
|
|
|
const salesSummary = ref<any>();
|
|
|
|
|
const topSellingProducts = ref<any[]>([]);
|
|
|
|
|
const slowSellingProducts = ref<any[]>([]);
|
|
|
|
|
const timePeriodSales = ref<any[]>([]);
|
|
|
|
|
const customerAnalysis = ref<any>();
|
|
|
|
|
const salesTrend = ref<any[]>([]);
|
|
|
|
|
const expandedRows = ref<string[]>([]);
|
|
|
|
|
const salesSummary = ref();
|
|
|
|
|
const topSellingProducts = ref([]);
|
|
|
|
|
const slowSellingProducts = ref([]);
|
|
|
|
|
const timePeriodSales = ref([]);
|
|
|
|
|
const customerAnalysis = ref();
|
|
|
|
|
const salesTrend = ref([]);
|
|
|
|
|
const expandedRows = ref([]);
|
|
|
|
|
|
|
|
|
|
// 图表实例
|
|
|
|
|
let salesTrendChartInstance: echarts.ECharts | null = null;
|
|
|
|
@@ -507,11 +492,9 @@ const initQueryForm = () => {
|
|
|
|
|
queryForm.startDate = today.toISOString().slice(0, 10);
|
|
|
|
|
queryForm.endDate = now.toISOString().slice(0, 10);
|
|
|
|
|
} else if (queryForm.queryType === 'month') {
|
|
|
|
|
// 确保按月查询时设置正确的默认值
|
|
|
|
|
const thisMonth = now.toISOString().slice(0, 7);
|
|
|
|
|
queryForm.startDate = thisMonth;
|
|
|
|
|
queryForm.endDate = thisMonth;
|
|
|
|
|
console.log('初始化按月查询默认值:', { startDate: queryForm.startDate, endDate: queryForm.endDate });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -613,99 +596,20 @@ watch(
|
|
|
|
|
{ deep: true }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 处理查询参数,确保日期格式正确
|
|
|
|
|
const processQueryParams = (params: any) => {
|
|
|
|
|
const processedParams = { ...params };
|
|
|
|
|
|
|
|
|
|
// 如果是按月查询,需要将YYYY-MM格式转换为完整的日期范围
|
|
|
|
|
if (params.queryType === 'month') {
|
|
|
|
|
if (params.startDate) {
|
|
|
|
|
try {
|
|
|
|
|
// 确保日期格式正确,处理可能的格式问题
|
|
|
|
|
let dateStr = params.startDate;
|
|
|
|
|
if (typeof dateStr === 'string' && dateStr.length === 7 && dateStr.includes('-')) {
|
|
|
|
|
// 已经是 YYYY-MM 格式
|
|
|
|
|
const startDate = new Date(dateStr + '-01');
|
|
|
|
|
if (isNaN(startDate.getTime())) {
|
|
|
|
|
throw new Error('Invalid start date');
|
|
|
|
|
}
|
|
|
|
|
processedParams.startDate = startDate.toISOString().slice(0, 10);
|
|
|
|
|
console.log(`按月查询 - 开始日期转换: ${params.startDate} -> ${processedParams.startDate}`);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('按月查询 - 开始日期格式不正确:', params.startDate);
|
|
|
|
|
// 如果格式不正确,尝试使用当前月份
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const currentMonth = now.toISOString().slice(0, 7);
|
|
|
|
|
const fallbackDate = new Date(currentMonth + '-01');
|
|
|
|
|
processedParams.startDate = fallbackDate.toISOString().slice(0, 10);
|
|
|
|
|
console.log(`按月查询 - 使用默认开始日期: ${processedParams.startDate}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('按月查询 - 开始日期转换失败:', error);
|
|
|
|
|
// 使用当前月份作为默认值
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const currentMonth = now.toISOString().slice(0, 7);
|
|
|
|
|
const fallbackDate = new Date(currentMonth + '-01');
|
|
|
|
|
processedParams.startDate = fallbackDate.toISOString().slice(0, 10);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (params.endDate) {
|
|
|
|
|
try {
|
|
|
|
|
// 确保日期格式正确,处理可能的格式问题
|
|
|
|
|
let dateStr = params.endDate;
|
|
|
|
|
if (typeof dateStr === 'string' && dateStr.length === 7 && dateStr.includes('-')) {
|
|
|
|
|
// 已经是 YYYY-MM 格式
|
|
|
|
|
const endDate = new Date(dateStr + '-01');
|
|
|
|
|
if (isNaN(endDate.getTime())) {
|
|
|
|
|
throw new Error('Invalid end date');
|
|
|
|
|
}
|
|
|
|
|
endDate.setMonth(endDate.getMonth() + 1, 0); // 设置为下个月的第0天,即当前月的最后一天
|
|
|
|
|
processedParams.endDate = endDate.toISOString().slice(0, 10);
|
|
|
|
|
console.log(`按月查询 - 结束日期转换: ${params.endDate} -> ${processedParams.endDate}`);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('按月查询 - 结束日期格式不正确:', params.endDate);
|
|
|
|
|
// 如果格式不正确,尝试使用当前月份
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const currentMonth = now.toISOString().slice(0, 7);
|
|
|
|
|
const fallbackDate = new Date(currentMonth + '-01');
|
|
|
|
|
fallbackDate.setMonth(fallbackDate.getMonth() + 1, 0);
|
|
|
|
|
processedParams.endDate = fallbackDate.toISOString().slice(0, 10);
|
|
|
|
|
console.log(`按月查询 - 使用默认结束日期: ${processedParams.endDate}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('按月查询 - 结束日期转换失败:', error);
|
|
|
|
|
// 使用当前月份作为默认值
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const currentMonth = now.toISOString().slice(0, 7);
|
|
|
|
|
const fallbackDate = new Date(currentMonth + '-01');
|
|
|
|
|
fallbackDate.setMonth(fallbackDate.getMonth() + 1, 0);
|
|
|
|
|
processedParams.endDate = fallbackDate.toISOString().slice(0, 10);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('处理后的查询参数:', processedParams);
|
|
|
|
|
return processedParams;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 加载数据
|
|
|
|
|
const loadData = async () => {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
// 处理查询参数
|
|
|
|
|
const processedParams = processQueryParams(queryForm);
|
|
|
|
|
|
|
|
|
|
// 加载完整报表数据
|
|
|
|
|
const reportData = await getRealtimeSalesReport(processedParams);
|
|
|
|
|
const reportData = await getRealtimeSalesReport(queryForm);
|
|
|
|
|
|
|
|
|
|
if (reportData.code === 0 && reportData.data) {
|
|
|
|
|
// 使用后端返回的数据
|
|
|
|
|
salesSummary.value = reportData.data.salesSummary || {};
|
|
|
|
|
salesSummary.value = reportData.data.salesSummary || getMockSalesSummary();
|
|
|
|
|
topSellingProducts.value = reportData.data.topSellingProducts || [];
|
|
|
|
|
slowSellingProducts.value = reportData.data.slowSellingProducts || [];
|
|
|
|
|
timePeriodSales.value = reportData.data.timePeriodSales || [];
|
|
|
|
|
customerAnalysis.value = reportData.data.customerAnalysis || {};
|
|
|
|
|
customerAnalysis.value = reportData.data.customerAnalysis || getMockCustomerAnalysis();
|
|
|
|
|
salesTrend.value = reportData.data.salesTrend || [];
|
|
|
|
|
|
|
|
|
|
ElMessage.success('数据加载成功');
|
|
|
|
@@ -718,31 +622,245 @@ const loadData = async () => {
|
|
|
|
|
renderCharts();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载数据失败:', error);
|
|
|
|
|
// 清空数据
|
|
|
|
|
salesSummary.value = {};
|
|
|
|
|
topSellingProducts.value = [];
|
|
|
|
|
slowSellingProducts.value = [];
|
|
|
|
|
timePeriodSales.value = [];
|
|
|
|
|
customerAnalysis.value = {};
|
|
|
|
|
salesTrend.value = [];
|
|
|
|
|
// 使用模拟数据
|
|
|
|
|
salesSummary.value = getMockSalesSummary();
|
|
|
|
|
topSellingProducts.value = getMockTopProducts();
|
|
|
|
|
slowSellingProducts.value = getMockSlowProducts();
|
|
|
|
|
timePeriodSales.value = getMockTimePeriodSales();
|
|
|
|
|
customerAnalysis.value = getMockCustomerAnalysis();
|
|
|
|
|
salesTrend.value = []; // 空数组,不显示模拟数据
|
|
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
renderCharts();
|
|
|
|
|
|
|
|
|
|
// 根据错误类型显示不同的提示信息
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
|
|
if (errorMessage.includes('权限')) {
|
|
|
|
|
if (error.message && error.message.includes('权限')) {
|
|
|
|
|
ElMessage.error('权限不足,无法访问即时销售报表');
|
|
|
|
|
} else if (errorMessage.includes('网络')) {
|
|
|
|
|
} else if (error.message && error.message.includes('网络')) {
|
|
|
|
|
ElMessage.error('网络连接异常,请检查网络设置');
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error('数据加载失败,请稍后重试');
|
|
|
|
|
ElMessage.warning('数据加载失败,使用模拟数据展示');
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 模拟数据生成函数 - 根据后端API文档调整
|
|
|
|
|
const getMockSalesSummary = () => ({
|
|
|
|
|
totalSalesAmount: 12500.50,
|
|
|
|
|
totalSalesCount: 45,
|
|
|
|
|
averageCustomerValue: 277.79,
|
|
|
|
|
productVarietyCount: 23,
|
|
|
|
|
totalSalesQuantity: 156.5,
|
|
|
|
|
salesGrowthRate: 15.5,
|
|
|
|
|
salesGrowthRateMonth: 8.2,
|
|
|
|
|
realtimeStatus: 'MEDIUM',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const getMockTopProducts = () => [
|
|
|
|
|
{
|
|
|
|
|
productId: 1001,
|
|
|
|
|
productName: '苹果',
|
|
|
|
|
productCode: 'P001',
|
|
|
|
|
salesAmount: 1250.00,
|
|
|
|
|
salesQuantity: 50.0,
|
|
|
|
|
salesCount: 12,
|
|
|
|
|
averagePrice: 25.00,
|
|
|
|
|
ranking: 1,
|
|
|
|
|
stockQuantity: 200.0,
|
|
|
|
|
stockStatus: 'SUFFICIENT',
|
|
|
|
|
growthRate: 12.5
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 1002,
|
|
|
|
|
productName: '牛奶',
|
|
|
|
|
productCode: 'M002',
|
|
|
|
|
salesAmount: 980.00,
|
|
|
|
|
salesQuantity: 49.0,
|
|
|
|
|
salesCount: 10,
|
|
|
|
|
averagePrice: 20.00,
|
|
|
|
|
ranking: 2,
|
|
|
|
|
stockQuantity: 150.0,
|
|
|
|
|
stockStatus: 'SUFFICIENT',
|
|
|
|
|
growthRate: 8.3
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 1003,
|
|
|
|
|
productName: '面包',
|
|
|
|
|
productCode: 'B003',
|
|
|
|
|
salesAmount: 850.00,
|
|
|
|
|
salesQuantity: 34.0,
|
|
|
|
|
salesCount: 8,
|
|
|
|
|
averagePrice: 25.00,
|
|
|
|
|
ranking: 3,
|
|
|
|
|
stockQuantity: 5.0,
|
|
|
|
|
stockStatus: 'LOW',
|
|
|
|
|
growthRate: 15.2
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 1004,
|
|
|
|
|
productName: '矿泉水',
|
|
|
|
|
productCode: 'W004',
|
|
|
|
|
salesAmount: 720.00,
|
|
|
|
|
salesQuantity: 36.0,
|
|
|
|
|
salesCount: 7,
|
|
|
|
|
averagePrice: 20.00,
|
|
|
|
|
ranking: 4,
|
|
|
|
|
stockQuantity: 0.0,
|
|
|
|
|
stockStatus: 'OUT',
|
|
|
|
|
growthRate: 6.7
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 1005,
|
|
|
|
|
productName: '鸡蛋',
|
|
|
|
|
productCode: 'E005',
|
|
|
|
|
salesAmount: 680.00,
|
|
|
|
|
salesQuantity: 68.0,
|
|
|
|
|
salesCount: 6,
|
|
|
|
|
averagePrice: 10.00,
|
|
|
|
|
ranking: 5,
|
|
|
|
|
stockQuantity: 100.0,
|
|
|
|
|
stockStatus: 'SUFFICIENT',
|
|
|
|
|
growthRate: 4.2
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const getMockSlowProducts = () => [
|
|
|
|
|
{
|
|
|
|
|
productId: 2001,
|
|
|
|
|
productName: '高级红酒',
|
|
|
|
|
productCode: 'W001',
|
|
|
|
|
salesAmount: 120.00,
|
|
|
|
|
salesQuantity: 1.2,
|
|
|
|
|
salesCount: 1,
|
|
|
|
|
averagePrice: 100.00,
|
|
|
|
|
ranking: 1,
|
|
|
|
|
stockQuantity: 50.0,
|
|
|
|
|
stockStatus: 'SUFFICIENT',
|
|
|
|
|
growthRate: -15.3
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 2002,
|
|
|
|
|
productName: '进口巧克力',
|
|
|
|
|
productCode: 'C002',
|
|
|
|
|
salesAmount: 80.00,
|
|
|
|
|
salesQuantity: 2.0,
|
|
|
|
|
salesCount: 2,
|
|
|
|
|
averagePrice: 40.00,
|
|
|
|
|
ranking: 2,
|
|
|
|
|
stockQuantity: 8.0,
|
|
|
|
|
stockStatus: 'LOW',
|
|
|
|
|
growthRate: -8.7
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 2003,
|
|
|
|
|
productName: '有机蔬菜',
|
|
|
|
|
productCode: 'V003',
|
|
|
|
|
salesAmount: 60.00,
|
|
|
|
|
salesQuantity: 3.0,
|
|
|
|
|
salesCount: 1,
|
|
|
|
|
averagePrice: 20.00,
|
|
|
|
|
ranking: 3,
|
|
|
|
|
stockQuantity: 15.0,
|
|
|
|
|
stockStatus: 'SUFFICIENT',
|
|
|
|
|
growthRate: -12.1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 2004,
|
|
|
|
|
productName: '高端茶叶',
|
|
|
|
|
productCode: 'T004',
|
|
|
|
|
salesAmount: 40.00,
|
|
|
|
|
salesQuantity: 0.8,
|
|
|
|
|
salesCount: 1,
|
|
|
|
|
averagePrice: 50.00,
|
|
|
|
|
ranking: 4,
|
|
|
|
|
stockQuantity: 0.0,
|
|
|
|
|
stockStatus: 'OUT',
|
|
|
|
|
growthRate: -20.5
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
productId: 2005,
|
|
|
|
|
productName: '进口咖啡',
|
|
|
|
|
productCode: 'C005',
|
|
|
|
|
salesAmount: 30.00,
|
|
|
|
|
salesQuantity: 0.6,
|
|
|
|
|
salesCount: 1,
|
|
|
|
|
averagePrice: 50.00,
|
|
|
|
|
ranking: 5,
|
|
|
|
|
stockQuantity: 2.0,
|
|
|
|
|
stockStatus: 'LOW',
|
|
|
|
|
growthRate: -25.8
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const getMockTimePeriodSales = () => {
|
|
|
|
|
const data = [];
|
|
|
|
|
const products = [
|
|
|
|
|
{ name: '苹果', code: 'P001', category: '水果', unit: 'kg', unitName: 'kg' },
|
|
|
|
|
{ name: '牛奶', code: 'M002', category: '乳制品', unit: '瓶', unitName: '瓶' },
|
|
|
|
|
{ name: '面包', code: 'B003', category: '烘焙', unit: '个', unitName: '个' },
|
|
|
|
|
{ name: '矿泉水', code: 'W004', category: '饮料', unit: '瓶', unitName: '瓶' },
|
|
|
|
|
{ name: '鸡蛋', code: 'E005', category: '蛋类', unit: '个', unitName: '个' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 24; i++) {
|
|
|
|
|
const productCount = Math.floor(Math.random() * 3) + 1;
|
|
|
|
|
const productDetails = [];
|
|
|
|
|
let totalAmount = 0;
|
|
|
|
|
let totalQuantity = 0;
|
|
|
|
|
|
|
|
|
|
for (let j = 0; j < productCount; j++) {
|
|
|
|
|
const product = products[Math.floor(Math.random() * products.length)];
|
|
|
|
|
const quantity = Math.floor(Math.random() * 5) + 1;
|
|
|
|
|
const unitPrice = Math.floor(Math.random() * 50) + 10;
|
|
|
|
|
const amount = quantity * unitPrice;
|
|
|
|
|
|
|
|
|
|
productDetails.push({
|
|
|
|
|
productId: `mock_${i}_${j}`,
|
|
|
|
|
productName: product.name,
|
|
|
|
|
productCode: product.code,
|
|
|
|
|
categoryId: `cat_${j}`,
|
|
|
|
|
categoryName: product.category,
|
|
|
|
|
unit: product.unit,
|
|
|
|
|
unitName: product.unitName,
|
|
|
|
|
quantity: quantity,
|
|
|
|
|
unitPrice: unitPrice,
|
|
|
|
|
totalAmount: amount,
|
|
|
|
|
salesCount: 1,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
totalAmount += amount;
|
|
|
|
|
totalQuantity += quantity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.push({
|
|
|
|
|
timeLabel: `${i.toString().padStart(2, '0')}:00`,
|
|
|
|
|
salesAmount: totalAmount,
|
|
|
|
|
salesCount: productCount,
|
|
|
|
|
customerValue: totalAmount / productCount,
|
|
|
|
|
salesQuantity: totalQuantity,
|
|
|
|
|
growthRate: Math.floor(Math.random() * 20) - 10,
|
|
|
|
|
productDetails: productDetails,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getMockCustomerAnalysis = () => ({
|
|
|
|
|
averageCustomerValue: 277.79,
|
|
|
|
|
maxCustomerValue: 850.00,
|
|
|
|
|
minCustomerValue: 15.50,
|
|
|
|
|
medianCustomerValue: 250.00,
|
|
|
|
|
highValueOrderRatio: 25.5,
|
|
|
|
|
valueRanges: [
|
|
|
|
|
{ rangeLabel: '0-50元', orderCount: 8, percentage: 17.78 },
|
|
|
|
|
{ rangeLabel: '50-100元', orderCount: 12, percentage: 26.67 },
|
|
|
|
|
{ rangeLabel: '100-200元', orderCount: 15, percentage: 33.33 },
|
|
|
|
|
{ rangeLabel: '200-500元', orderCount: 8, percentage: 17.78 },
|
|
|
|
|
{ rangeLabel: '500+元', orderCount: 2, percentage: 4.44 },
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新数据
|
|
|
|
@@ -774,48 +892,30 @@ const refreshCharts = async () => {
|
|
|
|
|
// 刷新时段数据
|
|
|
|
|
const refreshTimePeriodData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const processedParams = processQueryParams(queryForm);
|
|
|
|
|
const timePeriodData = await getTimePeriodSales({
|
|
|
|
|
...processedParams,
|
|
|
|
|
...queryForm,
|
|
|
|
|
current: 1,
|
|
|
|
|
size: 50,
|
|
|
|
|
});
|
|
|
|
|
timePeriodSales.value = timePeriodData.data?.records || [];
|
|
|
|
|
timePeriodSales.value = timePeriodData.data?.records || getMockTimePeriodSales();
|
|
|
|
|
ElMessage.success('时段数据刷新成功');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('刷新时段数据失败:', error);
|
|
|
|
|
timePeriodSales.value = [];
|
|
|
|
|
ElMessage.error('时段数据刷新失败');
|
|
|
|
|
timePeriodSales.value = getMockTimePeriodSales();
|
|
|
|
|
ElMessage.warning('使用模拟数据');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查看全部畅销品(已注释)
|
|
|
|
|
// const viewAllTopProducts = () => {
|
|
|
|
|
// dialogType.value = 'top';
|
|
|
|
|
// dialogVisible.value = true;
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// 查看全部滞销品(已注释)
|
|
|
|
|
// const viewAllSlowProducts = () => {
|
|
|
|
|
// dialogType.value = 'slow';
|
|
|
|
|
// dialogVisible.value = true;
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// 关闭弹窗(已注释)
|
|
|
|
|
// const handleDialogClose = () => {
|
|
|
|
|
// dialogVisible.value = false;
|
|
|
|
|
// selectedProduct.value = null;
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// 查看商品详情(已注释)
|
|
|
|
|
// const handleViewProductDetail = (product: any) => {
|
|
|
|
|
// selectedProduct.value = product;
|
|
|
|
|
// ElMessage.info(`查看商品详情: ${product.productName}`);
|
|
|
|
|
// // 这里可以打开商品详情弹窗或跳转到详情页面
|
|
|
|
|
// console.log('商品详情:', product);
|
|
|
|
|
// };
|
|
|
|
|
// 查看全部畅销品
|
|
|
|
|
const viewAllTopProducts = () => {
|
|
|
|
|
ElMessage.info('查看全部畅销品功能开发中...');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 查看全部滞销品
|
|
|
|
|
const viewAllSlowProducts = () => {
|
|
|
|
|
ElMessage.info('查看全部滞销品功能开发中...');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 渲染销售趋势图表
|
|
|
|
|
const renderSalesTrendChart = () => {
|
|
|
|
@@ -826,9 +926,9 @@ const renderSalesTrendChart = () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用后端返回的销售趋势数据
|
|
|
|
|
let xAxisData: string[] = [];
|
|
|
|
|
let salesData: number[] = [];
|
|
|
|
|
let countData: number[] = [];
|
|
|
|
|
let xAxisData = [];
|
|
|
|
|
let salesData = [];
|
|
|
|
|
let countData = [];
|
|
|
|
|
|
|
|
|
|
if (salesTrend.value && salesTrend.value.length > 0) {
|
|
|
|
|
// 使用后端返回的真实数据
|
|
|
|
@@ -920,22 +1020,28 @@ const renderCustomerAnalysisChart = () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用后端数据结构
|
|
|
|
|
let data: any[] = [];
|
|
|
|
|
let data = [];
|
|
|
|
|
if (customerAnalysis.value && customerAnalysis.value.valueRanges) {
|
|
|
|
|
data = customerAnalysis.value.valueRanges.map((range: any) => ({
|
|
|
|
|
data = customerAnalysis.value.valueRanges.map(range => ({
|
|
|
|
|
name: range.rangeLabel,
|
|
|
|
|
value: range.orderCount,
|
|
|
|
|
percentage: range.percentage
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
// 没有数据时显示空数组
|
|
|
|
|
data = [];
|
|
|
|
|
// 模拟数据
|
|
|
|
|
data = [
|
|
|
|
|
{ name: '0-50元', value: 25, percentage: 15.5 },
|
|
|
|
|
{ name: '50-100元', value: 35, percentage: 21.7 },
|
|
|
|
|
{ name: '100-200元', value: 20, percentage: 12.4 },
|
|
|
|
|
{ name: '200-500元', value: 15, percentage: 9.3 },
|
|
|
|
|
{ name: '500+元', value: 5, percentage: 3.1 },
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
formatter: function(params: any) {
|
|
|
|
|
formatter: function(params) {
|
|
|
|
|
return `${params.name}<br/>订单数: ${params.value}<br/>占比: ${params.data.percentage}%`;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
@@ -1065,6 +1171,68 @@ const getNumericValue = (value: any) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 根据类型生成模拟时段数据
|
|
|
|
|
const getMockTimePeriodSalesByType = (type: string) => {
|
|
|
|
|
const data = [];
|
|
|
|
|
|
|
|
|
|
if (type === 'hour') {
|
|
|
|
|
// 按小时生成24小时数据
|
|
|
|
|
for (let i = 0; i < 24; i++) {
|
|
|
|
|
data.push({
|
|
|
|
|
timePeriod: `${i}:00`,
|
|
|
|
|
timePeriodName: `${i}:00-${i + 1}:00`,
|
|
|
|
|
salesAmount: Math.floor(Math.random() * 5000) + 1000,
|
|
|
|
|
salesCount: Math.floor(Math.random() * 50) + 10,
|
|
|
|
|
salesQuantity: Math.floor(Math.random() * 100) + 20,
|
|
|
|
|
averageCustomerPrice: Math.floor(Math.random() * 50) + 50,
|
|
|
|
|
customerCount: Math.floor(Math.random() * 20) + 5,
|
|
|
|
|
productVarietyCount: Math.floor(Math.random() * 30) + 10,
|
|
|
|
|
yearOverYearSalesAmount: Math.floor(Math.random() * 1000) - 500,
|
|
|
|
|
monthOverMonthSalesAmount: Math.floor(Math.random() * 500) - 250,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (type === 'day') {
|
|
|
|
|
// 按天生成7天数据
|
|
|
|
|
for (let i = 6; i >= 0; i--) {
|
|
|
|
|
const date = new Date();
|
|
|
|
|
date.setDate(date.getDate() - i);
|
|
|
|
|
const dateStr = date.toISOString().slice(0, 10);
|
|
|
|
|
data.push({
|
|
|
|
|
timePeriod: dateStr,
|
|
|
|
|
timePeriodName: dateStr,
|
|
|
|
|
salesAmount: Math.floor(Math.random() * 20000) + 5000,
|
|
|
|
|
salesCount: Math.floor(Math.random() * 200) + 50,
|
|
|
|
|
salesQuantity: Math.floor(Math.random() * 500) + 100,
|
|
|
|
|
averageCustomerPrice: Math.floor(Math.random() * 100) + 50,
|
|
|
|
|
customerCount: Math.floor(Math.random() * 50) + 20,
|
|
|
|
|
productVarietyCount: Math.floor(Math.random() * 50) + 30,
|
|
|
|
|
yearOverYearSalesAmount: Math.floor(Math.random() * 5000) - 2500,
|
|
|
|
|
monthOverMonthSalesAmount: Math.floor(Math.random() * 2000) - 1000,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (type === 'month') {
|
|
|
|
|
// 按月生成12个月数据
|
|
|
|
|
for (let i = 11; i >= 0; i--) {
|
|
|
|
|
const date = new Date();
|
|
|
|
|
date.setMonth(date.getMonth() - i);
|
|
|
|
|
const monthStr = date.toISOString().slice(0, 7);
|
|
|
|
|
data.push({
|
|
|
|
|
timePeriod: monthStr,
|
|
|
|
|
timePeriodName: monthStr,
|
|
|
|
|
salesAmount: Math.floor(Math.random() * 100000) + 50000,
|
|
|
|
|
salesCount: Math.floor(Math.random() * 1000) + 500,
|
|
|
|
|
salesQuantity: Math.floor(Math.random() * 2000) + 1000,
|
|
|
|
|
averageCustomerPrice: Math.floor(Math.random() * 200) + 100,
|
|
|
|
|
customerCount: Math.floor(Math.random() * 200) + 100,
|
|
|
|
|
productVarietyCount: Math.floor(Math.random() * 100) + 50,
|
|
|
|
|
yearOverYearSalesAmount: Math.floor(Math.random() * 20000) - 10000,
|
|
|
|
|
monthOverMonthSalesAmount: Math.floor(Math.random() * 10000) - 5000,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 组件挂载
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|