forked from admin/deShanXiao
初始版本,目前线上可用
This commit is contained in:
713
frontEnd/src/pages/statistics/dayIncome/dayIncome.vue
Normal file
713
frontEnd/src/pages/statistics/dayIncome/dayIncome.vue
Normal file
@@ -0,0 +1,713 @@
|
||||
<template>
|
||||
<div style="background-color: #fff; padding: 15px">
|
||||
<baseTableHeader
|
||||
title="公司日收入统计"
|
||||
@resetSearch="reset"
|
||||
@search="query">
|
||||
<template #content>
|
||||
<el-form v-model="searchForm">
|
||||
<el-row :gutter="15">
|
||||
<el-col>
|
||||
<el-form-item label="购买日期">
|
||||
<el-date-picker
|
||||
v-model="searchForm.purchaseDate"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="选择日期"
|
||||
end-placeholder="选择日期"
|
||||
@change="dataChange" />
|
||||
</el-form-item>
|
||||
<!-- <div>
|
||||
<el-button
|
||||
type="primary"
|
||||
style="margin-left: 15px"
|
||||
@click="query"
|
||||
>查询</el-button
|
||||
><el-button
|
||||
type="warning"
|
||||
style="margin-left: 15px"
|
||||
@click="reset"
|
||||
>重置</el-button
|
||||
>
|
||||
<el-button v-print="print">打印单据</el-button>
|
||||
</div> -->
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<template #operateBtns>
|
||||
<el-button v-print="print"
|
||||
><img
|
||||
src="/assets/icon/打印机.svg"
|
||||
style="margin-right: 5px"
|
||||
width="20"
|
||||
height="20" />打印单据</el-button
|
||||
>
|
||||
<el-button @click="exportFile"
|
||||
><img
|
||||
src="/assets/images/Excel.svg"
|
||||
alt=""
|
||||
width="20"
|
||||
height="20" />导出</el-button
|
||||
>
|
||||
</template>
|
||||
</baseTableHeader>
|
||||
|
||||
<div id="print-container">
|
||||
<h2
|
||||
style="
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-size: 20pt;
|
||||
padding: 15px;
|
||||
font-weight: bold;
|
||||
">
|
||||
殡仪服务公司日收入统计表
|
||||
</h2>
|
||||
<table
|
||||
id="print-table"
|
||||
class=""
|
||||
style="
|
||||
font-family: 仿宋;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
colspan="4"
|
||||
class="l"
|
||||
style="
|
||||
text-align: left;
|
||||
|
||||
font-weight: bold;
|
||||
font-size: 25px;
|
||||
font-family: 宋体;
|
||||
">
|
||||
<span>
|
||||
统计时间:{{
|
||||
dayjs(searchForm.startDate).format("YYYY-MM-DD HH:mm:ss")
|
||||
}}
|
||||
至
|
||||
{{ dayjs(searchForm.endDate).format("YYYY-MM-DD HH:mm:ss") }}
|
||||
</span>
|
||||
<!-- <span v-else>
|
||||
开始时间:{{ dayjs(searchForm.startDate).format("YYYY-MM-DD") }}
|
||||
</span> -->
|
||||
</td>
|
||||
<td
|
||||
colspan="2"
|
||||
class="l"
|
||||
style="
|
||||
text-align: left;
|
||||
|
||||
font-weight: bold;
|
||||
font-size: 25px;
|
||||
font-family: 宋体;
|
||||
">
|
||||
单位:元
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b></b>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>小计</b>
|
||||
</td>
|
||||
<td
|
||||
colspan="2"
|
||||
style="height: 30px; border: 1px solid black; text-align: center">
|
||||
<b>总计</b>
|
||||
</td>
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>备注</b>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
现金
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{ statsData.service.cashAmount + statsData.retail.cashAmount }}
|
||||
</td>
|
||||
<td
|
||||
rowspan="6"
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{
|
||||
statsData.service.cashAmount +
|
||||
statsData.service.unionPayAmount +
|
||||
statsData.service.cardAmount +
|
||||
statsData.service.publicTransferAmount +
|
||||
statsData.service.workshopPayment +
|
||||
statsData.retail.cashAmount +
|
||||
statsData.retail.unionPayAmount +
|
||||
statsData.retail.cardAmount +
|
||||
statsData.retail.publicTransferAmount +
|
||||
statsData.retail.workshopPayment
|
||||
}}
|
||||
</td>
|
||||
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
银联
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{
|
||||
statsData.service.unionPayAmount +
|
||||
statsData.retail.unionPayAmount
|
||||
}}
|
||||
</td>
|
||||
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
银行卡
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{ statsData.service.cardAmount + statsData.retail.cardAmount }}
|
||||
</td>
|
||||
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
车间支付
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{
|
||||
statsData.service.workshopPayment +
|
||||
statsData.retail.workshopPayment
|
||||
}}
|
||||
</td>
|
||||
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
对公转账
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{
|
||||
statsData.service.publicTransferAmount +
|
||||
statsData.retail.publicTransferAmount
|
||||
}}
|
||||
</td>
|
||||
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>现金合计</b>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>微信合计</b>
|
||||
</td>
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>银行卡合计</b>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>车间支付合计</b>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>对公转账合计</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{ statsData.total.cashAmount }}
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{ statsData.total.unionPayAmount }}
|
||||
</td>
|
||||
|
||||
<td
|
||||
colspan="2"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{ statsData.total.cardAmount }}
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{ statsData.total.workshopPayment }}
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{ statsData.total.publicTransferAmount }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
<b>总合计:</b>
|
||||
</td>
|
||||
<td
|
||||
colspan="5"
|
||||
style="
|
||||
height: 30px;
|
||||
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
">
|
||||
{{
|
||||
statsData.total.cashAmount +
|
||||
statsData.total.unionPayAmount +
|
||||
statsData.total.cardAmount +
|
||||
statsData.total.workshopPayment +
|
||||
statsData.total.publicTransferAmount
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref } from "vue";
|
||||
import request from "@/lib/request";
|
||||
import dayjs from "dayjs";
|
||||
import ExcelJS from "exceljs";
|
||||
|
||||
export type PaymentStats = {
|
||||
cashAmount: number;
|
||||
unionPayAmount: number;
|
||||
cardAmount: number;
|
||||
publicTransferAmount: number;
|
||||
workshopPayment: number;
|
||||
};
|
||||
|
||||
// 完整响应类型
|
||||
export type StatsResponse = {
|
||||
retail: PaymentStats;
|
||||
service: PaymentStats;
|
||||
total: PaymentStats;
|
||||
};
|
||||
|
||||
let searchForm = ref({
|
||||
startDate: dayjs().startOf("day").toDate(),
|
||||
endDate: new Date(),
|
||||
purchaseDate: [dayjs().startOf("day").toDate(), new Date()],
|
||||
});
|
||||
|
||||
let statsData = ref<StatsResponse>({
|
||||
retail: {
|
||||
cashAmount: 0,
|
||||
unionPayAmount: 0,
|
||||
cardAmount: 0,
|
||||
publicTransferAmount: 0,
|
||||
workshopPayment: 0,
|
||||
},
|
||||
service: {
|
||||
cashAmount: 0,
|
||||
unionPayAmount: 0,
|
||||
cardAmount: 0,
|
||||
publicTransferAmount: 0,
|
||||
workshopPayment: 0,
|
||||
},
|
||||
total: {
|
||||
cashAmount: 0,
|
||||
unionPayAmount: 0,
|
||||
cardAmount: 0,
|
||||
publicTransferAmount: 0,
|
||||
workshopPayment: 0,
|
||||
},
|
||||
});
|
||||
let searched = ref(false);
|
||||
|
||||
request()
|
||||
.get("/stats/dayIncome", {
|
||||
params: {
|
||||
startDate: dayjs().startOf("day").toDate(),
|
||||
endDate: new Date(),
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
statsData.value = res.data;
|
||||
});
|
||||
|
||||
const print = {
|
||||
id: "print-container",
|
||||
};
|
||||
|
||||
function query() {
|
||||
searched.value = false;
|
||||
request()
|
||||
.get("/stats/dayIncome", {
|
||||
params: {
|
||||
startDate: dayjs(searchForm.value.startDate).format(
|
||||
"YYYY-MM-DD HH:mm:ss"
|
||||
),
|
||||
endDate: dayjs(searchForm.value.endDate).format("YYYY-MM-DD HH:mm:ss"),
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
statsData.value = res.data;
|
||||
});
|
||||
}
|
||||
|
||||
function reset() {
|
||||
searchForm.value = {
|
||||
startDate: dayjs().startOf("day").toDate(),
|
||||
endDate: new Date(),
|
||||
purchaseDate: [dayjs().startOf("day").toDate(), new Date()],
|
||||
};
|
||||
query();
|
||||
}
|
||||
|
||||
function dataChange(val: Date[]) {
|
||||
searchForm.value.startDate = val[0];
|
||||
searchForm.value.endDate = val[1];
|
||||
}
|
||||
|
||||
async function exportFile() {
|
||||
try {
|
||||
// 获取模板文件
|
||||
const response = await fetch("/excelTemple/日收入统计.xlsx");
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.load(buffer);
|
||||
const sheet = workbook.getWorksheet(1); // 默认第一个工作表
|
||||
|
||||
const startDate = dayjs(searchForm.value.startDate).format(
|
||||
"YYYY-MM-DD HH:mm:ss"
|
||||
);
|
||||
const endDate = dayjs(searchForm.value.endDate).format(
|
||||
"YYYY-MM-DD HH:mm:ss"
|
||||
);
|
||||
|
||||
// 工具函数:填充值并保留样式
|
||||
const setCellValueWithStyle = (cellAddress, value) => {
|
||||
const cell = sheet.getCell(cellAddress);
|
||||
const style = { ...cell.style };
|
||||
cell.value = value;
|
||||
cell.style = style;
|
||||
};
|
||||
|
||||
// 填充各项内容(保持样式)
|
||||
setCellValueWithStyle("C2", `${startDate} 至 ${endDate}`);
|
||||
|
||||
setCellValueWithStyle("B4", statsData.value.total.cashAmount);
|
||||
|
||||
setCellValueWithStyle("B5", statsData.value.total.unionPayAmount);
|
||||
setCellValueWithStyle("B6", statsData.value.total.cardAmount);
|
||||
setCellValueWithStyle("B7", statsData.value.total.workshopPayment);
|
||||
setCellValueWithStyle("B8", statsData.value.total.publicTransferAmount);
|
||||
|
||||
const total =
|
||||
statsData.value.service.cashAmount +
|
||||
statsData.value.service.unionPayAmount +
|
||||
statsData.value.service.cardAmount +
|
||||
statsData.value.service.publicTransferAmount +
|
||||
statsData.value.service.workshopPayment +
|
||||
statsData.value.retail.cashAmount +
|
||||
statsData.value.retail.unionPayAmount +
|
||||
statsData.value.retail.cardAmount +
|
||||
statsData.value.retail.publicTransferAmount +
|
||||
statsData.value.retail.workshopPayment;
|
||||
setCellValueWithStyle("C4", total);
|
||||
|
||||
setCellValueWithStyle("A11", statsData.value.total.cashAmount);
|
||||
setCellValueWithStyle("B11", statsData.value.total.unionPayAmount);
|
||||
setCellValueWithStyle("C11", statsData.value.total.cardAmount);
|
||||
setCellValueWithStyle("D11", statsData.value.total.workshopPayment);
|
||||
setCellValueWithStyle("E11", statsData.value.total.publicTransferAmount);
|
||||
|
||||
const grandTotal =
|
||||
statsData.value.total.cashAmount +
|
||||
statsData.value.total.unionPayAmount +
|
||||
statsData.value.total.cardAmount +
|
||||
statsData.value.total.workshopPayment +
|
||||
statsData.value.total.publicTransferAmount;
|
||||
setCellValueWithStyle("B12", grandTotal);
|
||||
|
||||
// 导出文件
|
||||
const newBuffer = await workbook.xlsx.writeBuffer();
|
||||
const blob = new Blob([newBuffer], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "日收入统计.xlsx";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("导出 Excel 文件时出错:", error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
tr,
|
||||
td {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
#print-container {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 5mm !important;
|
||||
font-size: 14pt;
|
||||
color: #000 !important;
|
||||
font-family: Microsoft YaHei, "SimSun", serif !important;
|
||||
|
||||
/* 添加表格布局优化 */
|
||||
table {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse !important;
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 5px 2mm !important;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* 重置body和html的布局方式 */
|
||||
body,
|
||||
html {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 15pt;
|
||||
}
|
||||
|
||||
#print-container {
|
||||
min-height: 297mm; /* A4纸高度 */
|
||||
width: 100% !important;
|
||||
vertical-align: top !important;
|
||||
position: relative;
|
||||
top: 0;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* 隐藏不需要打印的元素 */
|
||||
.el-form,
|
||||
.el-button {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div>引导员销售统计</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<style lang="scss" scoped></style>
|
||||
452
frontEnd/src/pages/statistics/saleDetail/saleDetail.vue
Normal file
452
frontEnd/src/pages/statistics/saleDetail/saleDetail.vue
Normal file
@@ -0,0 +1,452 @@
|
||||
<template>
|
||||
<div style="background-color: #fff; padding: 15px">
|
||||
<baseTableHeader title="公司销售明细" @resetSearch="reset" @search="query">
|
||||
<template #content>
|
||||
<el-form v-model="searchForm" label-width="100">
|
||||
<el-row :gutter="25">
|
||||
<el-col :span="10">
|
||||
<el-form-item label="统计日期">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="选择开始日期"
|
||||
end-placeholder="选择结束日期"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
@change="dateChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="逝者姓名">
|
||||
<el-input
|
||||
v-model="searchForm.deceasedName"
|
||||
placeholder="输入逝者名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="购买人">
|
||||
<el-input
|
||||
v-model="searchForm.familyName"
|
||||
placeholder="输入购买人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="服务名称">
|
||||
<el-input
|
||||
v-model="searchForm.serviceName"
|
||||
placeholder="输入服务名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6" label="引导员">
|
||||
<el-form-item label="引导员">
|
||||
<guideList v-model="searchForm.guide"></guideList>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<template #operateBtns>
|
||||
<el-button @click="exportSalesDetailsExcel('当前')"
|
||||
><img
|
||||
src="/assets/images/Excel.svg"
|
||||
alt=""
|
||||
width="20"
|
||||
height="20" />导出当前</el-button
|
||||
>
|
||||
<el-button @click="exportSalesDetailsExcel('所有')"
|
||||
><img
|
||||
src="/assets/images/Excel.svg"
|
||||
alt=""
|
||||
width="20"
|
||||
height="20" />导出所有</el-button
|
||||
>
|
||||
</template>
|
||||
</baseTableHeader>
|
||||
<el-card style="margin-top: 8px">
|
||||
<base-table
|
||||
:option="tableOption"
|
||||
ref="table"
|
||||
border
|
||||
show-summary
|
||||
:summary-method="getSummaries">
|
||||
<template #colunm>
|
||||
<el-table-column
|
||||
type="index"
|
||||
width="80"
|
||||
fixed="left"
|
||||
label="序号"
|
||||
align="center"></el-table-column>
|
||||
<el-table-column
|
||||
width="120"
|
||||
fixed="left"
|
||||
prop="deceasedRetail.checkoutDate"
|
||||
label="结账日期"
|
||||
align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.deceasedRetail.checkoutDate.split(" ")[0] }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="deceased.name"
|
||||
label="逝者姓名"
|
||||
width="120"
|
||||
fixed="left"
|
||||
align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{
|
||||
row.deceased.name ||
|
||||
row.deceased.familyName ||
|
||||
row.deceasedRetail.deceasedName
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="deceasedRetail.guide"
|
||||
label="引导员"
|
||||
fixed="left"
|
||||
align="center" />
|
||||
<el-table-column prop="deceased.gender" label="性别" align="center" />
|
||||
<el-table-column prop="deceased.age" label="年龄" align="center" />
|
||||
<el-table-column
|
||||
prop="deceased.idNumber"
|
||||
label="身份证"
|
||||
align="center"
|
||||
width="180" />
|
||||
<el-table-column label="地址" align="center" width="280">
|
||||
<template #default="{ row }">
|
||||
<div style="text-align: left">
|
||||
<span
|
||||
v-if="row.deceased.province || row.deceasedRetail.province"
|
||||
>{{
|
||||
row.deceased.province || row.deceasedRetail.province
|
||||
}}/</span
|
||||
>
|
||||
<span v-if="row.deceased.city || row.deceasedRetail.city"
|
||||
>{{ row.deceased.city || row.deceasedRetail.city }}/</span
|
||||
>
|
||||
<span
|
||||
v-if="row.deceased.address || row.deceasedRetail.address"
|
||||
>{{
|
||||
row.deceased.address || row.deceasedRetail.address
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="deceased.name"
|
||||
label="购买人姓名"
|
||||
align="center"
|
||||
width="120">
|
||||
<template #default="{ row }">
|
||||
<span>{{
|
||||
row.deceased.familyName || row.deceasedRetail.deceasedName
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="deceased.familyPhone"
|
||||
label="购买人电话"
|
||||
align="center"
|
||||
width="150" />
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="项目名称"
|
||||
align="center"
|
||||
width="150" />
|
||||
<el-table-column
|
||||
prop="price"
|
||||
label="单价"
|
||||
align="center"
|
||||
width="150" />
|
||||
<el-table-column
|
||||
prop="quantity"
|
||||
label="数量"
|
||||
align="center"
|
||||
width="150" />
|
||||
<el-table-column prop="sum" label="金额" align="center" width="150">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
width="280"
|
||||
align="center" />
|
||||
</template>
|
||||
</base-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import request from "@/lib/request";
|
||||
import { tableOptionType } from "@/types/table";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import ExcelJS from "exceljs";
|
||||
|
||||
const categories = ref<Array<{ id: number; name: string }>>([]);
|
||||
let searchForm = ref({
|
||||
dateRange: [],
|
||||
serviceName: "",
|
||||
categoryName: "",
|
||||
categories: "",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
deceasedName: "",
|
||||
guide: "",
|
||||
familyName: "",
|
||||
});
|
||||
|
||||
let tableOption = ref<tableOptionType>({
|
||||
url: "/stats/salesDetails",
|
||||
searchUrl: "/stats/salesDetails",
|
||||
searchParams: searchForm,
|
||||
executeType: "list",
|
||||
});
|
||||
let table = ref();
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const res = await request().get("/public/service-categories");
|
||||
categories.value = res.data.map((item: any) => ({
|
||||
id: item.value,
|
||||
name: item.label,
|
||||
}));
|
||||
} catch (error) {
|
||||
ElMessage.error("分类加载失败");
|
||||
}
|
||||
};
|
||||
const getSummaries = (param: { columns: any[]; data: any[] }) => {
|
||||
const { columns, data } = param;
|
||||
const sums: any[] = [];
|
||||
// 需要统计的字段列表
|
||||
const sumKeys = ["price", "quantity", "sum"];
|
||||
columns.forEach((column, index) => {
|
||||
if (index === 0) {
|
||||
sums[index] = "合计";
|
||||
return;
|
||||
}
|
||||
if (sumKeys.includes(column.property)) {
|
||||
const values = data.map((item) => {
|
||||
const keys = column.property.split(".");
|
||||
let value = item;
|
||||
for (const key of keys) {
|
||||
value = value?.[key] || 0;
|
||||
}
|
||||
return Number(value) || 0;
|
||||
});
|
||||
|
||||
if (!values.every((value) => isNaN(value))) {
|
||||
const sum = values.reduce((prev, curr) => {
|
||||
return prev + curr;
|
||||
}, 0);
|
||||
sums[index] = `${sum.toFixed(2)}`;
|
||||
} else {
|
||||
sums[index] = "0.00 元";
|
||||
}
|
||||
} else {
|
||||
sums[index] = "";
|
||||
}
|
||||
});
|
||||
|
||||
return sums;
|
||||
};
|
||||
|
||||
const dateChange = (val: string[]) => {
|
||||
if (val?.length === 2) {
|
||||
searchForm.value.dateRange = val;
|
||||
searchForm.value.startDate = val[0];
|
||||
searchForm.value.endDate = val[1];
|
||||
}
|
||||
};
|
||||
|
||||
function reset() {
|
||||
searchForm.value = {
|
||||
dateRange: [],
|
||||
serviceName: "",
|
||||
categoryName: "",
|
||||
categories: "",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
deceasedName: "",
|
||||
familyName: "",
|
||||
};
|
||||
table.value.methods.setDataType("reset");
|
||||
}
|
||||
|
||||
function query() {
|
||||
table.value.methods.setDataType("search");
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchCategories();
|
||||
});
|
||||
|
||||
async function exportSalesDetailsExcel(type: "当前" | "所有") {
|
||||
try {
|
||||
let list = [];
|
||||
|
||||
if (type === "当前") {
|
||||
list = table.value.tableData.data;
|
||||
} else {
|
||||
// 1. 获取所有数据
|
||||
const res = await request().post("/stats/salesDetails", {
|
||||
pageSize: 999999999,
|
||||
pageNumber: 1,
|
||||
dateRange: [],
|
||||
serviceName: "",
|
||||
categoryName: "",
|
||||
categories: "",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
deceasedName: "",
|
||||
guide: "",
|
||||
familyName: "",
|
||||
});
|
||||
list = res.data?.list;
|
||||
}
|
||||
|
||||
// 2. 获取模板文件
|
||||
const templateRes = await fetch("/excelTemple/销售明细.xlsx");
|
||||
const buffer = await templateRes.arrayBuffer();
|
||||
|
||||
// 3. 加载 Excel 模板
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.load(buffer);
|
||||
const sheet = workbook.getWorksheet(1); // 默认取第一个 sheet
|
||||
|
||||
// 4. 从第 3 行开始填充数据
|
||||
list.forEach((item, index) => {
|
||||
const row = sheet.getRow(index + 2); // 第3行开始
|
||||
const deceased = item.deceased || {};
|
||||
const retail = item.deceasedRetail || {};
|
||||
|
||||
row.getCell(1).value = index + 1;
|
||||
row.getCell(2).value = retail.checkoutDate || "";
|
||||
row.getCell(3).value = deceased.name || "";
|
||||
row.getCell(4).value = retail.guide || "";
|
||||
row.getCell(5).value = deceased.gender || "";
|
||||
row.getCell(6).value = deceased.age || "";
|
||||
row.getCell(7).value = deceased.idNumber || "";
|
||||
row.getCell(8).value = deceased.address || "";
|
||||
row.getCell(9).value = deceased.familyName || "";
|
||||
row.getCell(10).value = deceased.familyPhone || "";
|
||||
row.getCell(11).value = item.name || "";
|
||||
row.getCell(12).value = item.price || "";
|
||||
row.getCell(13).value = item.quantity || "";
|
||||
row.getCell(14).value = item.sum || "";
|
||||
row.getCell(15).value = item.remark || "";
|
||||
row.eachCell((cell) => {
|
||||
cell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
});
|
||||
row.commit(); // 应用修改
|
||||
});
|
||||
|
||||
// 5. 导出为 blob
|
||||
const blob = await workbook.xlsx.writeBuffer();
|
||||
const file = new Blob([blob], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
|
||||
// 6. 触发下载
|
||||
const url = URL.createObjectURL(file);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "销售明细.xlsx";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("导出销售明细失败:", error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.is-loading {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotating {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #000;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
text-align: center !important;
|
||||
}
|
||||
@media print {
|
||||
/* 移除全局布局影响 */
|
||||
body,
|
||||
html {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
margin: 2mm !important; /* 添加安全边距 */
|
||||
}
|
||||
|
||||
#print-container {
|
||||
width: auto !important; /* 改为自动宽度 */
|
||||
margin: 0 auto !important; /* 居中显示 */
|
||||
font-size: 12pt !important; /* 调整字号 */
|
||||
min-height: 297mm;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 优化单元格内边距 */
|
||||
td,
|
||||
th {
|
||||
padding: 4px 2mm !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
tr,
|
||||
td {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
#print-container {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 5mm !important;
|
||||
font-size: 14pt;
|
||||
color: #000 !important;
|
||||
font-family: Microsoft YaHei, "SimSun", serif !important;
|
||||
}
|
||||
/* 添加表格布局优化 */
|
||||
table {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse !important;
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 10px 2mm !important;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
498
frontEnd/src/pages/statistics/sales/sales.vue
Normal file
498
frontEnd/src/pages/statistics/sales/sales.vue
Normal file
@@ -0,0 +1,498 @@
|
||||
<template>
|
||||
<div style="background-color: #fff; padding: 15px">
|
||||
<baseTableHeader title="销售统计报表" @resetSearch="reset" @search="query">
|
||||
<template #content>
|
||||
<el-form v-model="searchForm">
|
||||
<el-row :gutter="25">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="统计日期">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="选择开始日期"
|
||||
end-placeholder="选择结束日期"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
@change="dateChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="服务名称">
|
||||
<el-input
|
||||
v-model="searchForm.serviceName"
|
||||
placeholder="输入服务名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="项目分类">
|
||||
<el-select
|
||||
v-model="searchForm.categoryName"
|
||||
placeholder="选择分类"
|
||||
clearable>
|
||||
<el-option
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:label="category.name"
|
||||
:value="category.name" />
|
||||
<el-option label="其他服务" value="其他服务" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- <div>
|
||||
<el-button
|
||||
type="primary"
|
||||
style="margin-left: 15px"
|
||||
@click="query"
|
||||
:loading="loading">
|
||||
查询
|
||||
</el-button>
|
||||
<el-button type="warning" @click="reset">重置</el-button>
|
||||
</div> -->
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<template #operateBtns>
|
||||
<el-button size="small" v-print="print"
|
||||
><img
|
||||
src="/assets/icon/打印机.svg"
|
||||
style="margin-right: 5px"
|
||||
width="20"
|
||||
height="20" />打印单据</el-button
|
||||
>
|
||||
<el-button @click="exportTableToExcel"
|
||||
><img
|
||||
src="/assets/images/Excel.svg"
|
||||
alt=""
|
||||
width="20"
|
||||
height="20" />导出</el-button
|
||||
>
|
||||
</template>
|
||||
</baseTableHeader>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-mask">
|
||||
<el-icon class="is-loading" color="#409EFC" :size="30">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<span style="margin-left: 10px">数据加载中...</span>
|
||||
</div>
|
||||
<div id="print-container">
|
||||
<h2
|
||||
style="
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-size: 20pt;
|
||||
padding: 15px;
|
||||
font-weight: bold;
|
||||
">
|
||||
销售统计报表
|
||||
</h2>
|
||||
<table
|
||||
v-show="!loading"
|
||||
style="
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: 宋体;
|
||||
">
|
||||
<!-- 表头 -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="border: 1px solid #000; padding: 8px">序号</th>
|
||||
<th style="border: 1px solid #000; padding: 8px">服务项目</th>
|
||||
<th style="border: 1px solid #000; padding: 8px">单价(元)</th>
|
||||
<th style="border: 1px solid #000; padding: 8px">数量</th>
|
||||
<th style="border: 1px solid #000; padding: 8px">小计(元)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 新增空状态 -->
|
||||
<tr v-if="showEmpty">
|
||||
<td
|
||||
colspan="4"
|
||||
style="text-align: center; color: #999; padding: 20px">
|
||||
未查询到相关数据
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 分类数据 -->
|
||||
<template
|
||||
v-for="category in statsData.categories"
|
||||
:key="category.categoryName">
|
||||
<tr>
|
||||
<td
|
||||
colspan="5"
|
||||
style="font-weight: bold; padding: 8px; text-align: center">
|
||||
{{ category.categoryName }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(service, index) in category.services"
|
||||
:key="service.serviceName">
|
||||
<td style="padding: 8px">{{ index + 1 }}</td>
|
||||
<td style="border: 1px solid #000; padding: 8px">
|
||||
{{ service.serviceName }}
|
||||
</td>
|
||||
<td
|
||||
style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ service.price.toFixed(2) }}
|
||||
</td>
|
||||
<td
|
||||
style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ service.quantity }}
|
||||
</td>
|
||||
<td
|
||||
style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ service.subtotal.toFixed(2) }}
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr style="font-weight: bold">
|
||||
<td colspan="2" style="border: 1px solid #000; padding: 8px">
|
||||
分类合计
|
||||
</td>
|
||||
<td
|
||||
style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ category.totalQuantity }}
|
||||
</td>
|
||||
<td
|
||||
style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ category.totalAmount.toFixed(2) }}
|
||||
</td>
|
||||
</tr> -->
|
||||
</template>
|
||||
|
||||
<!-- 其他服务 -->
|
||||
<tr v-if="statsData.otherCategory.services.length">
|
||||
<td colspan="5" style="font-weight: bold; padding: 8px">
|
||||
{{ statsData.otherCategory.categoryName }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="statsData.otherCategory.services.length"
|
||||
v-for="(service, index) in statsData.otherCategory.services"
|
||||
:key="'other-' + service.serviceName">
|
||||
<td style="padding: 8px">{{ index + 1 }}</td>
|
||||
<td style="border: 1px solid #000; padding: 8px">
|
||||
{{ service.serviceName }}
|
||||
</td>
|
||||
<td style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ service.price.toFixed(2) }}
|
||||
</td>
|
||||
<td style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ service.quantity }}
|
||||
</td>
|
||||
<td style="border: 1px solid #000; text-align: right; padding: 8px">
|
||||
{{ service.subtotal.toFixed(2) }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 总计行 -->
|
||||
<tr>
|
||||
<td
|
||||
colspan="3"
|
||||
style="
|
||||
border: 1px solid #000;
|
||||
padding: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
">
|
||||
数量和金额总计
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
border: 1px solid #000;
|
||||
text-align: right;
|
||||
padding: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
">
|
||||
{{ statsData.grandTotalQuantity }}
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
border: 1px solid #000;
|
||||
text-align: right;
|
||||
padding: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
">
|
||||
{{ statsData.grandTotalAmount.toFixed(2) }} 元
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Loading } from "@element-plus/icons-vue";
|
||||
import request from "@/lib/request";
|
||||
import dayjs from "dayjs";
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
interface ServiceStatsItem {
|
||||
serviceName: string;
|
||||
price: number;
|
||||
unit: string;
|
||||
quantity: number;
|
||||
subtotal: number;
|
||||
}
|
||||
|
||||
interface CategoryStats {
|
||||
categoryName: string;
|
||||
services: ServiceStatsItem[];
|
||||
totalQuantity: number;
|
||||
totalAmount: number;
|
||||
}
|
||||
|
||||
interface StatsResponse {
|
||||
categories: CategoryStats[];
|
||||
otherCategory: CategoryStats;
|
||||
grandTotalQuantity: number;
|
||||
grandTotalAmount: number;
|
||||
}
|
||||
const print = {
|
||||
id: "print-container",
|
||||
};
|
||||
// 保持原有响应式数据结构
|
||||
const loading = ref(false);
|
||||
const searchForm = ref({
|
||||
dateRange: [
|
||||
dayjs().startOf("day").format("YYYY-MM-DD 00:00:00"),
|
||||
dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"),
|
||||
] as string[],
|
||||
serviceName: "",
|
||||
categoryName: "",
|
||||
});
|
||||
|
||||
const statsData = ref<StatsResponse>({
|
||||
categories: [],
|
||||
otherCategory: {
|
||||
categoryName: "其他服务",
|
||||
services: [],
|
||||
totalQuantity: 0,
|
||||
totalAmount: 0,
|
||||
},
|
||||
grandTotalQuantity: 0,
|
||||
grandTotalAmount: 0,
|
||||
});
|
||||
|
||||
const categories = ref<Array<{ id: number; name: string }>>([]);
|
||||
const showEmpty = computed(
|
||||
() =>
|
||||
statsData.value?.categories?.length === 0 &&
|
||||
statsData.value.otherCategory.services.length === 0
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await fetchCategories();
|
||||
await query();
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const res = await request().get("/public/service-categories");
|
||||
categories.value = res.data.map((item: any) => ({
|
||||
id: item.value,
|
||||
name: item.label,
|
||||
}));
|
||||
} catch (error) {
|
||||
ElMessage.error("分类加载失败");
|
||||
}
|
||||
};
|
||||
|
||||
const query = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const params = {
|
||||
startDate: searchForm.value.dateRange[0],
|
||||
endDate: searchForm.value.dateRange[1],
|
||||
serviceName: searchForm.value.serviceName,
|
||||
categoryName: searchForm.value.categoryName,
|
||||
};
|
||||
|
||||
const res = await request().get("/stats/servicesStats", { params });
|
||||
statsData.value = res.data || res;
|
||||
} catch (error) {
|
||||
ElMessage.error(`查询失败: ${(error as Error).message}`);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
searchForm.value = {
|
||||
dateRange: [
|
||||
dayjs().startOf("day").format("YYYY-MM-DD 00:00:00"),
|
||||
dayjs().format("YYYY-MM-DD 23:59:59"),
|
||||
],
|
||||
serviceName: "",
|
||||
categoryName: "",
|
||||
};
|
||||
query();
|
||||
};
|
||||
|
||||
const dateChange = (val: string[]) => {
|
||||
if (val?.length === 2) {
|
||||
searchForm.value.dateRange = val;
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
ElMessage({
|
||||
type: "error",
|
||||
message: "系统异常,请稍后重试",
|
||||
duration: 3000,
|
||||
});
|
||||
};
|
||||
|
||||
function exportTableToExcel() {
|
||||
const table = document.querySelector(
|
||||
"#print-container table"
|
||||
) as HTMLTableElement;
|
||||
if (!table) {
|
||||
console.error("找不到表格");
|
||||
return;
|
||||
}
|
||||
|
||||
// 将 HTML 表格转换为 worksheet
|
||||
const worksheet = XLSX.utils.table_to_sheet(table, {
|
||||
raw: true,
|
||||
});
|
||||
|
||||
// 获取范围
|
||||
const range = XLSX.utils.decode_range(worksheet["!ref"]!);
|
||||
|
||||
// 遍历每个单元格,设置样式
|
||||
for (let R = range.s.r; R <= range.e.r; ++R) {
|
||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C });
|
||||
const cell = worksheet[cellAddress];
|
||||
if (!cell || !cell.v) continue;
|
||||
|
||||
// 是否是最后一行(数量和金额总计行)
|
||||
const isLastRow = R === range.e.r;
|
||||
|
||||
cell.s = {
|
||||
alignment: {
|
||||
horizontal: "center",
|
||||
vertical: "center",
|
||||
wrapText: true,
|
||||
},
|
||||
font: {
|
||||
bold: isLastRow, // 最后一行加粗
|
||||
sz: 12,
|
||||
},
|
||||
border: {
|
||||
top: { style: "thin", color: { rgb: "000000" } },
|
||||
bottom: { style: "thin", color: { rgb: "000000" } },
|
||||
left: { style: "thin", color: { rgb: "000000" } },
|
||||
right: { style: "thin", color: { rgb: "000000" } },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 创建工作簿并导出
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "销售统计");
|
||||
XLSX.writeFile(workbook, "销售统计报表.xlsx");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.is-loading {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotating {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #000;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
text-align: center !important;
|
||||
}
|
||||
@media print {
|
||||
/* 移除全局布局影响 */
|
||||
body,
|
||||
html {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
margin: 2mm !important; /* 添加安全边距 */
|
||||
}
|
||||
|
||||
#print-container {
|
||||
width: auto !important; /* 改为自动宽度 */
|
||||
margin: 0 auto !important; /* 居中显示 */
|
||||
font-size: 12pt !important; /* 调整字号 */
|
||||
min-height: 297mm;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 优化单元格内边距 */
|
||||
td,
|
||||
th {
|
||||
padding: 4px 2mm !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
tr,
|
||||
td {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
#print-container {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 5mm !important;
|
||||
font-size: 14pt;
|
||||
color: #000 !important;
|
||||
font-family: Microsoft YaHei, "SimSun", serif !important;
|
||||
}
|
||||
/* 添加表格布局优化 */
|
||||
table {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse !important;
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 10px 2mm !important;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user