mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-08-06 10:57:26 +08:00
feat(statistics): 添加店铺业绩、商品同比环比、销售订单明细及商品销售汇总报表视图和相关 API 接口
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="/config.js"></script>
|
<script src="<%= BASE_URL %>config.js"></script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const resolve = (dir) => path.join(__dirname, dir);
|
|||||||
const enableProduction = process.env.NODE_ENV === "production";
|
const enableProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
publicPath: process.env.VUE_APP_PUBLIC_PATH || "/",
|
||||||
outputDir: "dist",
|
outputDir: "dist",
|
||||||
assetsDir: "static",
|
assetsDir: "static",
|
||||||
css: {
|
css: {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function resolve(dir) {
|
|||||||
// vue.config.js
|
// vue.config.js
|
||||||
const vueConfig = {
|
const vueConfig = {
|
||||||
// 公共路径(必须有的)
|
// 公共路径(必须有的)
|
||||||
publicPath: '/',
|
publicPath: process.env.VUE_APP_PUBLIC_PATH || '/',
|
||||||
// 输出文件目录
|
// 输出文件目录
|
||||||
outputDir: 'dist',
|
outputDir: 'dist',
|
||||||
// 静态资源存放的文件夹(相对于ouputDir)
|
// 静态资源存放的文件夹(相对于ouputDir)
|
||||||
|
|||||||
@@ -72,8 +72,8 @@
|
|||||||
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
|
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
|
||||||
<script src="<%=js%>"></script>
|
<script src="<%=js%>"></script>
|
||||||
<% } %>
|
<% } %>
|
||||||
<script src="/config.js"></script>
|
<script src="<%= BASE_URL %>config.js"></script>
|
||||||
<script src="/tinymce/tinymce.min.js"></script>
|
<script src="<%= BASE_URL %>tinymce/tinymce.min.js"></script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong
|
<strong
|
||||||
>We're sorry but admin doesn't work properly without JavaScript
|
>We're sorry but admin doesn't work properly without JavaScript
|
||||||
|
|||||||
@@ -114,3 +114,39 @@ export const getDistributionRankDistributor = (params) => {
|
|||||||
export const getDistributionRankGoods = (params) => {
|
export const getDistributionRankGoods = (params) => {
|
||||||
return getRequest("/statistics/distribution/rank/goods", params);
|
return getRequest("/statistics/distribution/rank/goods", params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 店铺业绩报表 */
|
||||||
|
export const getStorePerformanceReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/store-performance", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportStorePerformanceReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/store-performance/export", params, "blob");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 商品同比环比报表 */
|
||||||
|
export const getGoodsComparisonReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/goods-comparison", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportGoodsComparisonReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/goods-comparison/export", params, "blob");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 销售订单明细报表 */
|
||||||
|
export const getSalesOrderDetailReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/sales-order-detail", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportSalesOrderDetailReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/sales-order-detail/export", params, "blob");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 商品销售汇总报表 */
|
||||||
|
export const getGoodsSalesSummaryReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/goods-sales-summary", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportGoodsSalesSummaryReport = (params) => {
|
||||||
|
return getRequest("/statistics/report/goods-sales-summary/export", params, "blob");
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import plugins from "./plugins";
|
import plugins from "./plugins";
|
||||||
import toobar from "./toolbar";
|
import toobar from "./toolbar";
|
||||||
|
|
||||||
const localCDN = window.location.origin + "/tinymce"; //本地引入
|
const localCDN = window.location.origin + process.env.BASE_URL + "tinymce"; //本地引入,兼容子路径部署(publicPath)
|
||||||
export const initEditor = {
|
export const initEditor = {
|
||||||
base_url: localCDN,
|
base_url: localCDN,
|
||||||
height: "400px",
|
height: "400px",
|
||||||
|
|||||||
@@ -426,6 +426,34 @@ export const otherRouter = {
|
|||||||
title: "钱包流水",
|
title: "钱包流水",
|
||||||
name: "finance-wallet-log",
|
name: "finance-wallet-log",
|
||||||
component: () => import("@/views/finance/walletLog.vue")
|
component: () => import("@/views/finance/walletLog.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/store-performance",
|
||||||
|
title: "店铺业绩报表",
|
||||||
|
name: "storePerformanceReport",
|
||||||
|
meta: { title: "店铺业绩报表" },
|
||||||
|
component: () => import("@/views/statistics/store-performance.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/goods-comparison",
|
||||||
|
title: "商品同比环比报表",
|
||||||
|
name: "goodsComparisonReport",
|
||||||
|
meta: { title: "商品同比环比报表" },
|
||||||
|
component: () => import("@/views/statistics/goods-comparison.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/sales-order-detail",
|
||||||
|
title: "销售订单明细报表",
|
||||||
|
name: "salesOrderDetailReport",
|
||||||
|
meta: { title: "销售订单明细报表" },
|
||||||
|
component: () => import("@/views/statistics/sales-order-detail.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/goods-sales-summary",
|
||||||
|
title: "商品销售汇总报表",
|
||||||
|
name: "goodsSalesSummaryReport",
|
||||||
|
meta: { title: "商品销售汇总报表" },
|
||||||
|
component: () => import("@/views/statistics/goods-sales-summary.vue")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
161
manager/src/views/statistics/data-report.vue
Normal file
161
manager/src/views/statistics/data-report.vue
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<div class="data-report">
|
||||||
|
<el-card>
|
||||||
|
<h4 class="page-title">数据报表</h4>
|
||||||
|
<p class="page-desc">选择报表模块查看销售数据统计与分析</p>
|
||||||
|
<div class="module-grid">
|
||||||
|
<div
|
||||||
|
v-for="item in modules"
|
||||||
|
:key="item.name"
|
||||||
|
class="module-card"
|
||||||
|
@click="goReport(item.name)"
|
||||||
|
>
|
||||||
|
<div class="module-icon">
|
||||||
|
<el-icon :size="32">
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="module-body">
|
||||||
|
<div class="module-title">{{ item.title }}</div>
|
||||||
|
<div class="module-desc">{{ item.desc }}</div>
|
||||||
|
</div>
|
||||||
|
<el-icon class="module-arrow"><ArrowRight /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
DataAnalysis,
|
||||||
|
Document,
|
||||||
|
Goods,
|
||||||
|
TrendCharts,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
const REPORT_MODULES = [
|
||||||
|
{
|
||||||
|
name: "storePerformanceReport",
|
||||||
|
title: "店铺业绩报表",
|
||||||
|
desc: "支付金额、营业额、转化率及同环比分析",
|
||||||
|
icon: TrendCharts,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodsComparisonReport",
|
||||||
|
title: "商品同比环比报表",
|
||||||
|
desc: "商品销售金额、销量占比及同环比对比",
|
||||||
|
icon: DataAnalysis,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "salesOrderDetailReport",
|
||||||
|
title: "销售订单明细报表",
|
||||||
|
desc: "订单流水、收款明细与优惠明细",
|
||||||
|
icon: Document,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodsSalesSummaryReport",
|
||||||
|
title: "商品销售汇总报表",
|
||||||
|
desc: "商品销量、净销售额及优惠汇总",
|
||||||
|
icon: Goods,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "data-report",
|
||||||
|
components: {
|
||||||
|
ArrowRight,
|
||||||
|
TrendCharts,
|
||||||
|
DataAnalysis,
|
||||||
|
Document,
|
||||||
|
Goods,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modules: REPORT_MODULES,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goReport(name) {
|
||||||
|
this.$router.push({ name });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.data-report {
|
||||||
|
.page-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--el-color-primary-light-5);
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-title {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-arrow {
|
||||||
|
color: #c0c4cc;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
287
manager/src/views/statistics/goods-comparison.vue
Normal file
287
manager/src/views/statistics/goods-comparison.vue
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline class="goods-comparison-form">
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="时间类型">
|
||||||
|
<el-radio-group v-model="dateType" @change="onDateTypeChange">
|
||||||
|
<el-radio label="DAY">日</el-radio>
|
||||||
|
<el-radio label="PERIOD">指定周期</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="dateType === 'DAY'" label="日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="选择日期"
|
||||||
|
style="width: 160px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-else label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="商品分类">
|
||||||
|
<el-cascader
|
||||||
|
v-model="params.categoryId"
|
||||||
|
:options="categoryList"
|
||||||
|
:props="categoryProps"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="全部分类"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="品牌">
|
||||||
|
<el-select v-model="params.brandId" clearable filterable placeholder="全部品牌" style="width: 160px">
|
||||||
|
<el-option v-for="item in brandList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品名称">
|
||||||
|
<el-input v-model="params.keyword" clearable placeholder="商品名称" style="width: 160px" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="订单来源">
|
||||||
|
<el-select v-model="params.clientType" clearable placeholder="全部来源" style="width: 140px">
|
||||||
|
<el-option v-for="item in clientTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="店铺">
|
||||||
|
<el-select v-model="params.storeId" clearable filterable placeholder="全部店铺" style="width: 200px">
|
||||||
|
<el-option v-for="item in shops" :key="item.id" :label="item.storeName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="排序">
|
||||||
|
<el-select v-model="params.sortType" style="width: 120px">
|
||||||
|
<el-option label="按销售额" value="PRICE" />
|
||||||
|
<el-option label="按销量" value="NUM" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="goodsName" label="商品名称" min-width="160" />
|
||||||
|
<el-table-column prop="reportTime" label="时间" width="200" />
|
||||||
|
<el-table-column label="商品销售金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salesAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="商品销售总金额" width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.totalSalesAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="占比" width="90" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.amountPercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.momAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比率" width="90" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.momRate)">{{ row.momRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.momDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.yoyAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比率" width="90" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.yoyRate)">{{ row.yoyRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.yoyDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="salesNum" label="商品销售数" width="110" align="right" />
|
||||||
|
<el-table-column label="销售占比" width="90" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.salesNumPercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="storeName" label="店铺名称" width="140" />
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { getShopListData } from "@/api/shops";
|
||||||
|
import { getBrandListData, getCategoryTree } from "@/api/goods";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
const CLIENT_TYPE_OPTIONS = [
|
||||||
|
{ label: "移动端", value: "H5" },
|
||||||
|
{ label: "PC端", value: "PC" },
|
||||||
|
{ label: "小程序", value: "WECHAT_MP" },
|
||||||
|
{ label: "移动应用端", value: "APP" },
|
||||||
|
{ label: "未知", value: "UNKNOWN" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "goods-comparison-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateType: "DAY",
|
||||||
|
queryDate: "",
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: {
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
storeId: "",
|
||||||
|
categoryId: "",
|
||||||
|
brandId: "",
|
||||||
|
clientType: "",
|
||||||
|
keyword: "",
|
||||||
|
sortType: "PRICE",
|
||||||
|
},
|
||||||
|
categoryList: [],
|
||||||
|
categoryProps: { value: "id", label: "name", children: "children", checkStrictly: true, emitPath: false },
|
||||||
|
brandList: [],
|
||||||
|
clientTypeOptions: CLIENT_TYPE_OPTIONS,
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
shops: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadOptions();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const today = new Date();
|
||||||
|
this.queryDate = this.formatDay(today);
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDay(date) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
},
|
||||||
|
onDateTypeChange() {
|
||||||
|
if (this.dateType === "DAY" && !this.queryDate) {
|
||||||
|
this.queryDate = this.formatDay(new Date());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadOptions() {
|
||||||
|
getCategoryTree().then((res) => {
|
||||||
|
if (res.success) this.categoryList = res.result || [];
|
||||||
|
});
|
||||||
|
getBrandListData().then((res) => {
|
||||||
|
if (res.success) this.brandList = res.result || [];
|
||||||
|
});
|
||||||
|
getShopListData({ pageNumber: 1, pageSize: 200 }).then((res) => {
|
||||||
|
if (res.success) this.shops = res.result.records || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params, reportDateType: this.dateType };
|
||||||
|
if (this.dateType === "DAY") {
|
||||||
|
if (this.queryDate) {
|
||||||
|
p.queryDate = `${this.queryDate} 00:00:00`;
|
||||||
|
}
|
||||||
|
} else if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getGoodsComparisonReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportGoodsComparisonReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "商品同比环比报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
formatPercent(val) {
|
||||||
|
if (val == null || val === "") return "0%";
|
||||||
|
return `${Number(val).toFixed(2)}%`;
|
||||||
|
},
|
||||||
|
rateClass(rate) {
|
||||||
|
if (!rate) return "";
|
||||||
|
if (rate.startsWith("+")) return "trend-up";
|
||||||
|
if (rate.startsWith("-") && rate !== "0%") return "trend-down";
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.goods-comparison-form .filter-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.goods-comparison-form .filter-row + .filter-row {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.trend-up {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
.trend-down {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
160
manager/src/views/statistics/goods-sales-summary.vue
Normal file
160
manager/src/views/statistics/goods-sales-summary.vue
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline>
|
||||||
|
<el-form-item label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品关键词">
|
||||||
|
<el-input v-model="params.keyword" clearable placeholder="商品名称" style="width: 180px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序">
|
||||||
|
<el-select v-model="params.sortType" style="width: 120px">
|
||||||
|
<el-option label="按销售额" value="PRICE" />
|
||||||
|
<el-option label="按销量" value="NUM" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="店铺">
|
||||||
|
<el-select v-model="params.storeId" clearable filterable placeholder="全部店铺" style="width: 200px">
|
||||||
|
<el-option v-for="item in shops" :key="item.id" :label="item.storeName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="goodsName" label="销售商品" min-width="160" />
|
||||||
|
<el-table-column prop="goodsId" label="商品ID" width="140" />
|
||||||
|
<el-table-column prop="salesNum" label="商品销售数量" width="110" />
|
||||||
|
<el-table-column prop="salesAmount" label="商品销售金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salesAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="refundNum" label="商品退货数量" width="110" />
|
||||||
|
<el-table-column prop="refundAmount" label="商品退款金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.refundAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="netNum" label="商品净销售数量" width="140" />
|
||||||
|
<el-table-column prop="netAmount" label="商品净销售金额" width="145">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.netAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="netAmountPercent" label="占净销售金额百分比" width="150">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.netAmountPercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="avgPrice" label="商品平均单价" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.avgPrice) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="salePriceAmount" label="商品售价金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salePriceAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="discountAmount" label="商品优惠金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { getShopListData } from "@/api/shops";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "goods-sales-summary-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: { pageNumber: 1, pageSize: 20, storeId: "", keyword: "", sortType: "PRICE" },
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
shops: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadShops();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
loadShops() {
|
||||||
|
getShopListData({ pageNumber: 1, pageSize: 200 }).then((res) => {
|
||||||
|
if (res.success) this.shops = res.result.records || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params };
|
||||||
|
if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getGoodsSalesSummaryReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportGoodsSalesSummaryReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "商品销售汇总报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
formatPercent(val) {
|
||||||
|
if (val == null || val === "") return "0%";
|
||||||
|
return `${Number(val).toFixed(2)}%`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
165
manager/src/views/statistics/sales-order-detail.vue
Normal file
165
manager/src/views/statistics/sales-order-detail.vue
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline>
|
||||||
|
<el-form-item label="发生日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="订单号">
|
||||||
|
<el-input v-model="params.orderSn" clearable placeholder="订单编号" style="width: 160px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="买家">
|
||||||
|
<el-input v-model="params.memberName" clearable placeholder="买家名称" style="width: 140px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品关键词">
|
||||||
|
<el-input v-model="params.keyword" clearable placeholder="商品名称" style="width: 160px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="店铺">
|
||||||
|
<el-select v-model="params.storeId" clearable filterable placeholder="全部店铺" style="width: 180px">
|
||||||
|
<el-option v-for="item in shops" :key="item.id" :label="item.storeName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="orderRefundSn" label="订单编号|售后单号" min-width="200" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="occurTime" label="发生时间" width="160" />
|
||||||
|
<el-table-column prop="flowTypeName" label="交易类型" width="90" />
|
||||||
|
<el-table-column prop="num" label="成交数量" width="90" align="right" />
|
||||||
|
<el-table-column label="成交金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.transactionAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="售价金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salePriceAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="优惠金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="paymentDetail" label="收款明细" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="discountDetail" label="优惠明细" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="订单来源" width="100">
|
||||||
|
<template #default="{ row }">{{ clientTypeText(row.clientType) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { getShopListData } from "@/api/shops";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "sales-order-detail-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: {
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
storeId: "",
|
||||||
|
orderSn: "",
|
||||||
|
memberName: "",
|
||||||
|
keyword: "",
|
||||||
|
},
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
shops: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadShops();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
loadShops() {
|
||||||
|
getShopListData({ pageNumber: 1, pageSize: 200 }).then((res) => {
|
||||||
|
if (res.success) this.shops = res.result.records || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params };
|
||||||
|
if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getSalesOrderDetailReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportSalesOrderDetailReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "销售订单明细报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
clientTypeText(val) {
|
||||||
|
const map = {
|
||||||
|
H5: "移动端",
|
||||||
|
PC: "PC端",
|
||||||
|
WECHAT_MP: "小程序",
|
||||||
|
APP: "移动应用端",
|
||||||
|
UNKNOWN: "未知",
|
||||||
|
};
|
||||||
|
return map[val] || val || "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
185
manager/src/views/statistics/store-performance.vue
Normal file
185
manager/src/views/statistics/store-performance.vue
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline>
|
||||||
|
<el-form-item label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="店铺">
|
||||||
|
<el-select v-model="params.storeId" clearable filterable placeholder="全部店铺" style="width: 200px">
|
||||||
|
<el-option v-for="item in shops" :key="item.id" :label="item.storeName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="storeName" label="店铺名称" min-width="140" />
|
||||||
|
<el-table-column prop="payOrderCount" label="订单支付笔数" width="120" align="right" />
|
||||||
|
<el-table-column label="支付金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.payAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="营业额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.turnover) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="优惠金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="折扣率" width="90" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.discountRate) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="营业收入" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.operatingIncome) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="营业收入占比" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.operatingIncomePercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="笔单价" width="100" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.avgOrderPrice) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="refundCount" label="退款笔数" width="100" align="right" />
|
||||||
|
<el-table-column label="订单退款金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.refundAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="orderConversionRate" label="下单转化率" width="110" align="right" />
|
||||||
|
<el-table-column prop="payConversionRate" label="支付转化率" width="110" align="right" />
|
||||||
|
<el-table-column label="环比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.momDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比增长率" width="110" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.momRate)">{{ row.momRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.yoyDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比增长率" width="110" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.yoyRate)">{{ row.yoyRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { getShopListData } from "@/api/shops";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "store-performance-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: { pageNumber: 1, pageSize: 20, storeId: "" },
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
shops: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadShops();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
loadShops() {
|
||||||
|
getShopListData({ pageNumber: 1, pageSize: 200 }).then((res) => {
|
||||||
|
if (res.success) this.shops = res.result.records || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params };
|
||||||
|
if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getStorePerformanceReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportStorePerformanceReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "店铺业绩报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
formatPercent(val) {
|
||||||
|
if (val == null || val === "") return "0%";
|
||||||
|
return `${Number(val).toFixed(2)}%`;
|
||||||
|
},
|
||||||
|
rateClass(rate) {
|
||||||
|
if (!rate) return "";
|
||||||
|
if (rate.startsWith("+")) return "trend-up";
|
||||||
|
if (rate.startsWith("-") && rate !== "0%") return "trend-down";
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.trend-up {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
.trend-down {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,6 +7,7 @@ const resolve = (dir) => path.join(__dirname, dir);
|
|||||||
const enableProduction = process.env.NODE_ENV === "production";
|
const enableProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
publicPath: process.env.VUE_APP_PUBLIC_PATH || "/",
|
||||||
css: {
|
css: {
|
||||||
loaderOptions: {
|
loaderOptions: {
|
||||||
less: {
|
less: {
|
||||||
|
|||||||
@@ -71,8 +71,8 @@
|
|||||||
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
|
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
|
||||||
<script src="<%=js%>"></script>
|
<script src="<%=js%>"></script>
|
||||||
<% } %>
|
<% } %>
|
||||||
<script src="/config.js"></script>
|
<script src="<%= BASE_URL %>config.js"></script>
|
||||||
<script src="/tinymce/tinymce.min.js"></script>
|
<script src="<%= BASE_URL %>tinymce/tinymce.min.js"></script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong
|
<strong
|
||||||
>We're sorry doesn't work properly without JavaScript
|
>We're sorry doesn't work properly without JavaScript
|
||||||
|
|||||||
@@ -16,5 +16,38 @@ export const getOrderStatistics = (params) => {
|
|||||||
return getRequest(`/statistics/order/getByPage`, params)
|
return getRequest(`/statistics/order/getByPage`, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 店铺业绩报表 */
|
||||||
|
export const getStorePerformanceReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/store-performance', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportStorePerformanceReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/store-performance/export', params, 'blob');
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 商品同比环比报表 */
|
||||||
|
export const getGoodsComparisonReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/goods-comparison', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportGoodsComparisonReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/goods-comparison/export', params, 'blob');
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 销售订单明细报表 */
|
||||||
|
export const getSalesOrderDetailReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/sales-order-detail', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportSalesOrderDetailReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/sales-order-detail/export', params, 'blob');
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 商品销售汇总报表 */
|
||||||
|
export const getGoodsSalesSummaryReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/goods-sales-summary', params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportGoodsSalesSummaryReport = (params) => {
|
||||||
|
return getRequest('/statistics/report/goods-sales-summary/export', params, 'blob');
|
||||||
|
};
|
||||||
|
|||||||
@@ -368,6 +368,15 @@ export const result = [{
|
|||||||
component: "statistics/traffic",
|
component: "statistics/traffic",
|
||||||
children: null
|
children: null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "dataReport",
|
||||||
|
level: 2,
|
||||||
|
type: 0,
|
||||||
|
title: "数据报表",
|
||||||
|
path: "dataReport",
|
||||||
|
component: "statistics/data-report",
|
||||||
|
children: null
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -169,6 +169,34 @@ export const otherRouter = {
|
|||||||
title: "财务汇总",
|
title: "财务汇总",
|
||||||
name: "shop-finance-summary",
|
name: "shop-finance-summary",
|
||||||
component: () => import("@/views/shop/finance/summary.vue")
|
component: () => import("@/views/shop/finance/summary.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/store-performance",
|
||||||
|
title: "店铺业绩报表",
|
||||||
|
name: "storePerformanceReport",
|
||||||
|
meta: { title: "店铺业绩报表" },
|
||||||
|
component: () => import("@/views/statistics/store-performance.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/goods-comparison",
|
||||||
|
title: "商品同比环比报表",
|
||||||
|
name: "goodsComparisonReport",
|
||||||
|
meta: { title: "商品同比环比报表" },
|
||||||
|
component: () => import("@/views/statistics/goods-comparison.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/sales-order-detail",
|
||||||
|
title: "销售订单明细报表",
|
||||||
|
name: "salesOrderDetailReport",
|
||||||
|
meta: { title: "销售订单明细报表" },
|
||||||
|
component: () => import("@/views/statistics/sales-order-detail.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "statistics/goods-sales-summary",
|
||||||
|
title: "商品销售汇总报表",
|
||||||
|
name: "goodsSalesSummaryReport",
|
||||||
|
meta: { title: "商品销售汇总报表" },
|
||||||
|
component: () => import("@/views/statistics/goods-sales-summary.vue")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import plugins from "./plugins";
|
import plugins from "./plugins";
|
||||||
import toobar from "./toolbar";
|
import toobar from "./toolbar";
|
||||||
const localCDN = window.location.origin + "/tinymce"; //本地引入
|
const localCDN = window.location.origin + process.env.BASE_URL + "tinymce"; //本地引入,兼容子路径部署(publicPath)
|
||||||
export const initEditor = {
|
export const initEditor = {
|
||||||
base_url: localCDN,
|
base_url: localCDN,
|
||||||
height: "400px",
|
height: "400px",
|
||||||
|
|||||||
161
seller/src/views/statistics/data-report.vue
Normal file
161
seller/src/views/statistics/data-report.vue
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<div class="data-report">
|
||||||
|
<el-card>
|
||||||
|
<h4 class="page-title">数据报表</h4>
|
||||||
|
<p class="page-desc">选择报表模块查看本店销售数据统计与分析</p>
|
||||||
|
<div class="module-grid">
|
||||||
|
<div
|
||||||
|
v-for="item in modules"
|
||||||
|
:key="item.name"
|
||||||
|
class="module-card"
|
||||||
|
@click="goReport(item.name)"
|
||||||
|
>
|
||||||
|
<div class="module-icon">
|
||||||
|
<el-icon :size="32">
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="module-body">
|
||||||
|
<div class="module-title">{{ item.title }}</div>
|
||||||
|
<div class="module-desc">{{ item.desc }}</div>
|
||||||
|
</div>
|
||||||
|
<el-icon class="module-arrow"><ArrowRight /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
DataAnalysis,
|
||||||
|
Document,
|
||||||
|
Goods,
|
||||||
|
TrendCharts,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
const REPORT_MODULES = [
|
||||||
|
{
|
||||||
|
name: "storePerformanceReport",
|
||||||
|
title: "店铺业绩报表",
|
||||||
|
desc: "支付金额、营业额、转化率及同环比分析",
|
||||||
|
icon: TrendCharts,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodsComparisonReport",
|
||||||
|
title: "商品同比环比报表",
|
||||||
|
desc: "商品销售金额、销量占比及同环比对比",
|
||||||
|
icon: DataAnalysis,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "salesOrderDetailReport",
|
||||||
|
title: "销售订单明细报表",
|
||||||
|
desc: "订单流水、收款明细与优惠明细",
|
||||||
|
icon: Document,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodsSalesSummaryReport",
|
||||||
|
title: "商品销售汇总报表",
|
||||||
|
desc: "商品销量、净销售额及优惠汇总",
|
||||||
|
icon: Goods,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "seller-data-report",
|
||||||
|
components: {
|
||||||
|
ArrowRight,
|
||||||
|
TrendCharts,
|
||||||
|
DataAnalysis,
|
||||||
|
Document,
|
||||||
|
Goods,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modules: REPORT_MODULES,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goReport(name) {
|
||||||
|
this.$router.push({ name });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.data-report {
|
||||||
|
.page-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--el-color-primary-light-5);
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-title {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-arrow {
|
||||||
|
color: #c0c4cc;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
276
seller/src/views/statistics/goods-comparison.vue
Normal file
276
seller/src/views/statistics/goods-comparison.vue
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline class="goods-comparison-form">
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="时间类型">
|
||||||
|
<el-radio-group v-model="dateType" @change="onDateTypeChange">
|
||||||
|
<el-radio label="DAY">日</el-radio>
|
||||||
|
<el-radio label="PERIOD">指定周期</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="dateType === 'DAY'" label="日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryDate"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="选择日期"
|
||||||
|
style="width: 160px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-else label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="商品分类">
|
||||||
|
<el-cascader
|
||||||
|
v-model="params.categoryId"
|
||||||
|
:options="categoryList"
|
||||||
|
:props="categoryProps"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="全部分类"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="品牌">
|
||||||
|
<el-select v-model="params.brandId" clearable filterable placeholder="全部品牌" style="width: 160px">
|
||||||
|
<el-option v-for="item in brandList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品名称">
|
||||||
|
<el-input v-model="params.keyword" clearable placeholder="商品名称" style="width: 160px" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="订单来源">
|
||||||
|
<el-select v-model="params.clientType" clearable placeholder="全部来源" style="width: 140px">
|
||||||
|
<el-option v-for="item in clientTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-form-item label="排序">
|
||||||
|
<el-select v-model="params.sortType" style="width: 120px">
|
||||||
|
<el-option label="按销售额" value="PRICE" />
|
||||||
|
<el-option label="按销量" value="NUM" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="goodsName" label="商品名称" min-width="160" />
|
||||||
|
<el-table-column prop="reportTime" label="时间" width="200" />
|
||||||
|
<el-table-column label="商品销售金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salesAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="商品销售总金额" width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.totalSalesAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="占比" width="90" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.amountPercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.momAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比率" width="90" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.momRate)">{{ row.momRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.momDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.yoyAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比率" width="90" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.yoyRate)">{{ row.yoyRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.yoyDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="salesNum" label="商品销售数" width="110" align="right" />
|
||||||
|
<el-table-column label="销售占比" width="90" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.salesNumPercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="storeName" label="店铺名称" width="140" />
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { getBrandListData, getCategoryTree } from "@/api/goods";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
const CLIENT_TYPE_OPTIONS = [
|
||||||
|
{ label: "移动端", value: "H5" },
|
||||||
|
{ label: "PC端", value: "PC" },
|
||||||
|
{ label: "小程序", value: "WECHAT_MP" },
|
||||||
|
{ label: "移动应用端", value: "APP" },
|
||||||
|
{ label: "未知", value: "UNKNOWN" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "seller-goods-comparison-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateType: "DAY",
|
||||||
|
queryDate: "",
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: {
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
categoryId: "",
|
||||||
|
brandId: "",
|
||||||
|
clientType: "",
|
||||||
|
keyword: "",
|
||||||
|
sortType: "PRICE",
|
||||||
|
},
|
||||||
|
categoryList: [],
|
||||||
|
categoryProps: { value: "id", label: "name", children: "children", checkStrictly: true, emitPath: false },
|
||||||
|
brandList: [],
|
||||||
|
clientTypeOptions: CLIENT_TYPE_OPTIONS,
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadOptions();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const today = new Date();
|
||||||
|
this.queryDate = this.formatDay(today);
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDay(date) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
},
|
||||||
|
onDateTypeChange() {
|
||||||
|
if (this.dateType === "DAY" && !this.queryDate) {
|
||||||
|
this.queryDate = this.formatDay(new Date());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadOptions() {
|
||||||
|
getCategoryTree().then((res) => {
|
||||||
|
if (res.success) this.categoryList = res.result || [];
|
||||||
|
});
|
||||||
|
getBrandListData().then((res) => {
|
||||||
|
if (res.success) this.brandList = res.result || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params, reportDateType: this.dateType };
|
||||||
|
if (this.dateType === "DAY") {
|
||||||
|
if (this.queryDate) {
|
||||||
|
p.queryDate = `${this.queryDate} 00:00:00`;
|
||||||
|
}
|
||||||
|
} else if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getGoodsComparisonReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportGoodsComparisonReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "商品同比环比报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
formatPercent(val) {
|
||||||
|
if (val == null || val === "") return "0%";
|
||||||
|
return `${Number(val).toFixed(2)}%`;
|
||||||
|
},
|
||||||
|
rateClass(rate) {
|
||||||
|
if (!rate) return "";
|
||||||
|
if (rate.startsWith("+")) return "trend-up";
|
||||||
|
if (rate.startsWith("-") && rate !== "0%") return "trend-down";
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.goods-comparison-form .filter-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.goods-comparison-form .filter-row + .filter-row {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.trend-up {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
.trend-down {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
147
seller/src/views/statistics/goods-sales-summary.vue
Normal file
147
seller/src/views/statistics/goods-sales-summary.vue
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline>
|
||||||
|
<el-form-item label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品关键词">
|
||||||
|
<el-input v-model="params.keyword" clearable placeholder="商品名称" style="width: 180px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序">
|
||||||
|
<el-select v-model="params.sortType" style="width: 120px">
|
||||||
|
<el-option label="按销售额" value="PRICE" />
|
||||||
|
<el-option label="按销量" value="NUM" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="goodsName" label="销售商品" min-width="160" />
|
||||||
|
<el-table-column prop="goodsId" label="商品ID" width="140" />
|
||||||
|
<el-table-column prop="salesNum" label="商品销售数量" width="110" />
|
||||||
|
<el-table-column prop="salesAmount" label="商品销售金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salesAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="refundNum" label="商品退货数量" width="110" />
|
||||||
|
<el-table-column prop="refundAmount" label="商品退款金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.refundAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="netNum" label="商品净销售数量" width="140" />
|
||||||
|
<el-table-column prop="netAmount" label="商品净销售金额" width="145">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.netAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="netAmountPercent" label="占净销售金额百分比" width="150">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.netAmountPercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="avgPrice" label="商品平均单价" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.avgPrice) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="salePriceAmount" label="商品售价金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salePriceAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="discountAmount" label="商品优惠金额" width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "seller-goods-sales-summary-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: { pageNumber: 1, pageSize: 20, keyword: "", sortType: "PRICE" },
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params };
|
||||||
|
if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getGoodsSalesSummaryReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportGoodsSalesSummaryReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "商品销售汇总报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
formatPercent(val) {
|
||||||
|
if (val == null || val === "") return "0%";
|
||||||
|
return `${Number(val).toFixed(2)}%`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
151
seller/src/views/statistics/sales-order-detail.vue
Normal file
151
seller/src/views/statistics/sales-order-detail.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline>
|
||||||
|
<el-form-item label="发生日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="订单号">
|
||||||
|
<el-input v-model="params.orderSn" clearable placeholder="订单编号" style="width: 160px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="买家">
|
||||||
|
<el-input v-model="params.memberName" clearable placeholder="买家名称" style="width: 140px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品关键词">
|
||||||
|
<el-input v-model="params.keyword" clearable placeholder="商品名称" style="width: 160px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="orderRefundSn" label="订单编号|售后单号" min-width="200" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="occurTime" label="发生时间" width="160" />
|
||||||
|
<el-table-column prop="flowTypeName" label="交易类型" width="90" />
|
||||||
|
<el-table-column prop="num" label="成交数量" width="90" align="right" />
|
||||||
|
<el-table-column label="成交金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.transactionAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="售价金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.salePriceAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="优惠金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="paymentDetail" label="收款明细" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="discountDetail" label="优惠明细" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="订单来源" width="100">
|
||||||
|
<template #default="{ row }">{{ clientTypeText(row.clientType) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "seller-sales-order-detail-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: {
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
orderSn: "",
|
||||||
|
memberName: "",
|
||||||
|
keyword: "",
|
||||||
|
},
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params };
|
||||||
|
if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getSalesOrderDetailReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportSalesOrderDetailReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "销售订单明细报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
clientTypeText(val) {
|
||||||
|
const map = {
|
||||||
|
H5: "移动端",
|
||||||
|
PC: "PC端",
|
||||||
|
WECHAT_MP: "小程序",
|
||||||
|
APP: "移动应用端",
|
||||||
|
UNKNOWN: "未知",
|
||||||
|
};
|
||||||
|
return map[val] || val || "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
172
seller/src/views/statistics/store-performance.vue
Normal file
172
seller/src/views/statistics/store-performance.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search">
|
||||||
|
<el-card>
|
||||||
|
<el-form inline>
|
||||||
|
<el-form-item label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mt_10">
|
||||||
|
<el-table v-loading="loading" border :data="data" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (params.pageNumber - 1) * params.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="storeName" label="店铺名称" min-width="140" />
|
||||||
|
<el-table-column prop="payOrderCount" label="订单支付笔数" width="120" align="right" />
|
||||||
|
<el-table-column label="支付金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.payAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="营业额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.turnover) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="优惠金额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.discountAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="折扣率" width="90" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.discountRate) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="营业收入" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.operatingIncome) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="营业收入占比" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatPercent(row.operatingIncomePercent) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="笔单价" width="100" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.avgOrderPrice) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="refundCount" label="退款笔数" width="100" align="right" />
|
||||||
|
<el-table-column label="订单退款金额" width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.refundAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="orderConversionRate" label="下单转化率" width="110" align="right" />
|
||||||
|
<el-table-column prop="payConversionRate" label="支付转化率" width="110" align="right" />
|
||||||
|
<el-table-column label="环比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.momDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="环比增长率" width="110" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.momRate)">{{ row.momRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比差额" width="110" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.yoyDiff) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="同比增长率" width="110" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="rateClass(row.yoyRate)">{{ row.yoyRate || "0%" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
class="mt_10"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadData"
|
||||||
|
@size-change="loadData"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as API_Statistics from "@/api/statistics";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "seller-store-performance-report",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dateRange: [],
|
||||||
|
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)],
|
||||||
|
params: { pageNumber: 1, pageSize: 20 },
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initDefaultRange();
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initDefaultRange() {
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date();
|
||||||
|
start.setDate(start.getDate() - 6);
|
||||||
|
this.dateRange = [this.formatDate(start, true), this.formatDate(end, false)];
|
||||||
|
},
|
||||||
|
formatDate(date, startOfDay) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
return startOfDay ? `${y}-${m}-${d} 00:00:00` : `${y}-${m}-${d} 23:59:59`;
|
||||||
|
},
|
||||||
|
buildParams() {
|
||||||
|
const p = { ...this.params };
|
||||||
|
if (this.dateRange && this.dateRange.length === 2) {
|
||||||
|
p.startTime = this.dateRange[0];
|
||||||
|
p.endTime = this.dateRange[1];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
loadData() {
|
||||||
|
this.loading = true;
|
||||||
|
API_Statistics.getStorePerformanceReport(this.buildParams()).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
API_Statistics.exportStorePerformanceReport(this.buildParams()).then((blob) => {
|
||||||
|
downloadBlob(blob, "店铺业绩报表.xlsx");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatMoney(val) {
|
||||||
|
return this.$filters.unitPrice(val, "¥");
|
||||||
|
},
|
||||||
|
formatPercent(val) {
|
||||||
|
if (val == null || val === "") return "0%";
|
||||||
|
return `${Number(val).toFixed(2)}%`;
|
||||||
|
},
|
||||||
|
rateClass(rate) {
|
||||||
|
if (!rate) return "";
|
||||||
|
if (rate.startsWith("+")) return "trend-up";
|
||||||
|
if (rate.startsWith("-") && rate !== "0%") return "trend-down";
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.trend-up {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
.trend-down {
|
||||||
|
color: #67c23a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,6 +7,7 @@ const resolve = (dir) => path.join(__dirname, dir);
|
|||||||
const enableProduction = process.env.NODE_ENV === "production";
|
const enableProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
publicPath: process.env.VUE_APP_PUBLIC_PATH || "/",
|
||||||
outputDir: "dist",
|
outputDir: "dist",
|
||||||
assetsDir: "static",
|
assetsDir: "static",
|
||||||
css: {
|
css: {
|
||||||
|
|||||||
Reference in New Issue
Block a user