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:
@@ -16,5 +16,38 @@ export const getOrderStatistics = (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",
|
||||
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: "财务汇总",
|
||||
name: "shop-finance-summary",
|
||||
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 toobar from "./toolbar";
|
||||
const localCDN = window.location.origin + "/tinymce"; //本地引入
|
||||
const localCDN = window.location.origin + process.env.BASE_URL + "tinymce"; //本地引入,兼容子路径部署(publicPath)
|
||||
export const initEditor = {
|
||||
base_url: localCDN,
|
||||
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>
|
||||
Reference in New Issue
Block a user