演示报表

This commit is contained in:
zjf committed 2026-08-13 15:33:45 +08:00
1 parent 28078ed292
commit e02f437955
11 files changed
+931

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
import request from "/@/utils/request"
/** 刷卡明细 */
export function fetchSwipePage(query?: Object) {
return request({ url: "/canteen/canteenReport/swipe/page", method: "get", params: query })
}
export function delSwipe(ids?: Object) {
return request({ url: "/canteen/canteenReport/swipe", method: "delete", data: ids })
}
export function clearSwipeMonth(bizMonth: string) {
return request({ url: `/canteen/canteenReport/swipe/month/${bizMonth}`, method: "delete" })
}
/** 食材能耗 */
export function fetchFoodEnergyPage(query?: Object) {
return request({ url: "/canteen/canteenReport/foodEnergy/page", method: "get", params: query })
}
export function delFoodEnergy(ids?: Object) {
return request({ url: "/canteen/canteenReport/foodEnergy", method: "delete", data: ids })
}
export function clearFoodEnergyMonth(bizMonth: string) {
return request({ url: `/canteen/canteenReport/foodEnergy/month/${bizMonth}`, method: "delete" })
}
/** 手工调整 */
export function fetchAdjustPage(query?: Object) {
return request({ url: "/canteen/canteenReport/adjust/page", method: "get", params: query })
}
export function getAdjust(id: string | number) {
return request({ url: `/canteen/canteenReport/adjust/${id}`, method: "get" })
}
export function addAdjust(obj?: Object) {
return request({ url: "/canteen/canteenReport/adjust", method: "post", data: obj })
}
export function putAdjust(obj?: Object) {
return request({ url: "/canteen/canteenReport/adjust", method: "put", data: obj })
}
export function delAdjust(ids?: Object) {
return request({ url: "/canteen/canteenReport/adjust", method: "delete", data: ids })
}
/** 报表计算 */
export function reportSummary(bizMonth: string) {
return request({ url: "/canteen/canteenReport/summary", method: "get", params: { bizMonth } })
}
export function reportIncome(bizMonth: string) {
return request({ url: "/canteen/canteenReport/income", method: "get", params: { bizMonth } })
}
export function reportSupplierShare(bizMonth: string) {
return request({ url: "/canteen/canteenReport/supplierShare", method: "get", params: { bizMonth } })
}
export function reportBusiness(bizMonth: string) {
return request({ url: "/canteen/canteenReport/business", method: "get", params: { bizMonth } })
}
+3
View File
@@ -3,6 +3,9 @@
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="商户" prop="merchantId">
<select-mch v-model="state.queryForm.merchantId" :apiParams="{ types: '1' }" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="餐桌编号" prop="tableNumber">
<el-input placeholder="请输入餐桌编号" v-model="state.queryForm.tableNumber" />
</el-form-item>
@@ -3,6 +3,9 @@
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="商户" prop="merchantId">
<select-mch v-model="state.queryForm.merchantId" :apiParams="{ types: '1' }" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="分类名称" prop="name" >
<el-input placeholder="请输入分类名称" v-model="state.queryForm.name" />
</el-form-item>
@@ -3,6 +3,9 @@
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="商户" prop="merchantId">
<select-mch v-model="state.queryForm.merchantId" :apiParams="{ types: '1' }" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="单据编号" prop="inventoryCode">
<el-input placeholder="请输入单据编号" v-model="state.queryForm.inventoryCode" />
</el-form-item>
@@ -3,6 +3,9 @@
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="商户" prop="merchantId">
<select-mch v-model="state.queryForm.merchantId" :apiParams="{ types: '1' }" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input placeholder="请输入分类名称" v-model="state.queryForm.name" />
</el-form-item>
@@ -3,6 +3,9 @@
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="商户" prop="merchantId">
<select-mch v-model="state.queryForm.merchantId" :apiParams="{ types: '1' }" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="商品名称" prop="name">
<el-input placeholder="请输入商品名称" v-model="state.queryForm.name" />
</el-form-item>
@@ -0,0 +1,183 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="业务月" prop="bizMonth">
<el-date-picker
v-model="state.queryForm.bizMonth"
type="month"
value-format="YYYY-MM"
placeholder="选择月份"
style="width: 140px"
/>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="folder-add" type="primary" class="ml10" @click="openForm()">新 增</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" @click="handleDelete(selectObjs)">
删除
</el-button>
<el-button icon="download" plain type="primary" class="ml10" @click="exportExcel">导出</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10 mr20" style="float: right" @queryTable="getDataList" />
</div>
</el-row>
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="bizMonth" label="业务月" width="100" />
<el-table-column prop="adjustType" label="调整类型" width="120" />
<el-table-column prop="costCenter" label="成本中心" min-width="160" show-overflow-tooltip />
<el-table-column prop="park" label="厂区" width="120" />
<el-table-column prop="amount" label="调整金额" width="120" align="right" />
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button icon="edit-pen" text type="primary" @click="openForm(row)">编辑</el-button>
<el-button icon="delete" text type="primary" @click="handleDelete([row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<el-dialog v-model="formVisible" :title="form.id ? '编辑调整' : '新增调整'" width="520px" destroy-on-close>
<el-form :model="form" label-width="100px">
<el-form-item label="业务月" required>
<el-date-picker v-model="form.bizMonth" type="month" value-format="YYYY-MM" />
</el-form-item>
<el-form-item label="调整类型">
<el-input v-model="form.adjustType" placeholder="跨厂区/开票/其他" />
</el-form-item>
<el-form-item label="成本中心">
<el-input v-model="form.costCenter" placeholder="可空=全局调整" />
</el-form-item>
<el-form-item label="厂区">
<el-input v-model="form.park" />
</el-form-item>
<el-form-item label="调整金额" required>
<el-input-number v-model="form.amount" :precision="2" :step="100" style="width: 100%" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveForm">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemCanteenReportAdjust">
import { BasicTableProps, useTable } from "/@/hooks/table"
import { fetchAdjustPage, addAdjust, putAdjust, delAdjust } from "/@/api/canteen/canteenReport"
import { useMessage, useMessageBox } from "/@/hooks/message"
const queryRef = ref()
const showSearch = ref(true)
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: { bizMonth: "2026-07" },
pageList: fetchAdjustPage,
})
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state)
const resetQuery = () => {
queryRef.value?.resetFields()
state.queryForm = { bizMonth: "2026-07" }
selectObjs.value = []
getDataList()
}
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id)
multiple.value = !objs.length
}
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm("确认删除?")
} catch {
return
}
try {
await delAdjust(ids)
getDataList()
useMessage().success("删除成功")
} catch (err: any) {
useMessage().error(err.msg || "删除失败")
}
}
const exportExcel = () => {
downBlobFile("/canteen/canteenReport/adjust/export", { ...state.queryForm }, `手工调整_${state.queryForm.bizMonth || ""}.xlsx`)
}
const formVisible = ref(false)
const saving = ref(false)
const form = reactive<any>({
id: undefined,
bizMonth: "2026-07",
adjustType: "",
costCenter: "",
park: "",
amount: 0,
remark: "",
})
const openForm = (row?: any) => {
Object.assign(form, {
id: undefined,
bizMonth: state.queryForm.bizMonth || "2026-07",
adjustType: "",
costCenter: "",
park: "",
amount: 0,
remark: "",
})
if (row) {
Object.assign(form, row)
}
formVisible.value = true
}
const saveForm = async () => {
if (!form.bizMonth) {
useMessage().warning("请填写业务月")
return
}
saving.value = true
try {
if (form.id) {
await putAdjust(form)
} else {
await addAdjust(form)
}
useMessage().success("保存成功")
formVisible.value = false
getDataList()
} catch (err: any) {
useMessage().error(err.msg || "保存失败")
} finally {
saving.value = false
}
}
</script>
@@ -0,0 +1,158 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view" v-loading="loading">
<el-row>
<el-form :inline="true" @keyup.enter="loadAll">
<el-form-item label="业务月">
<el-date-picker v-model="bizMonth" type="month" value-format="YYYY-MM" style="width: 140px" />
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" @click="loadAll">计算查询</el-button>
<el-button icon="download" plain type="primary" @click="exportCurrent">导出当前页签</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row :gutter="12" class="mb12" v-if="summary">
<el-col :span="8">
<el-card shadow="hover"><div class="stat">刷卡笔数<br /><b>{{ summary.swipeCount ?? 0 }}</b></div></el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover"><div class="stat">刷卡合计(元)<br /><b>{{ fmt(summary.swipeAmount) }}</b></div></el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover"><div class="stat">食材合计(元)<br /><b>{{ fmt(summary.foodAmount) }}</b></div></el-card>
</el-col>
</el-row>
<el-tabs v-model="activeTab">
<el-tab-pane label="经营报表" name="business">
<el-table :data="businessRows" border size="small" max-height="560" :header-cell-style="headerStyle">
<el-table-column prop="type" label="类型" width="110" fixed />
<el-table-column prop="category" label="类别" width="110" fixed />
<el-table-column v-for="p in parks" :key="p" :label="p" min-width="100" align="right">
<template #default="{ row }">{{ fmt(row.parkAmounts?.[p]) }}</template>
</el-table-column>
<el-table-column prop="subtotal" label="小计" width="100" align="right" fixed="right">
<template #default="{ row }">{{ fmt(row.subtotal) }}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="收入合计" name="income">
<el-table :data="incomeRows" border size="small" max-height="560" :header-cell-style="headerStyle">
<el-table-column prop="groupName" label="收入类别" width="100" fixed />
<el-table-column prop="costCenter" label="成本中心" min-width="160" fixed show-overflow-tooltip />
<el-table-column v-for="p in parks" :key="p" :label="p" min-width="100" align="right">
<template #default="{ row }">{{ fmt(row.parkAmounts?.[p]) }}</template>
</el-table-column>
<el-table-column prop="subtotal" label="小计" width="110" align="right" fixed="right">
<template #default="{ row }">{{ fmt(row.subtotal) }}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="供应商分摊" name="share">
<el-table :data="shareRows" border size="small" max-height="560" :header-cell-style="headerStyle">
<el-table-column prop="costCenter" label="成本中心" min-width="160" fixed show-overflow-tooltip />
<el-table-column v-for="p in shareParks" :key="p" :label="p" min-width="100" align="right">
<template #default="{ row }">{{ fmt(row.parkAmounts?.[p]) }}</template>
</el-table-column>
<el-table-column prop="subtotal" label="小计" width="100" align="right">
<template #default="{ row }">{{ fmt(row.subtotal) }}</template>
</el-table-column>
<el-table-column prop="boxAndOther" label="包厢及其他" width="110" align="right">
<template #default="{ row }">{{ fmt(row.boxAndOther) }}</template>
</el-table-column>
<el-table-column prop="otherAdjust" label="其他调整" width="100" align="right">
<template #default="{ row }">{{ fmt(row.otherAdjust) }}</template>
</el-table-column>
<el-table-column prop="invoiceAmount" label="开票金额" width="110" align="right" fixed="right">
<template #default="{ row }">{{ fmt(row.invoiceAmount) }}</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script setup lang="ts" name="systemCanteenReportBoard">
import { reportSummary, reportIncome, reportSupplierShare, reportBusiness } from "/@/api/canteen/canteenReport"
import { useMessage } from "/@/hooks/message"
import other from "/@/utils/other"
const parks = ["敏惠园区", "敏实园区", "敏胜园区", "敏华园区", "湖州园区", "华智一期", "华智二期", "安吉园区"]
const shareParks = ["敏惠园区", "敏实园区", "敏胜园区", "敏华园区", "湖州园区", "安吉园区", "华智一期"]
const bizMonth = ref("2026-07")
const activeTab = ref("business")
const loading = ref(false)
const summary = ref<any>(null)
const incomeRows = ref<any[]>([])
const shareRows = ref<any[]>([])
const businessRows = ref<any[]>([])
const headerStyle = { background: "var(--el-fill-color-light)", color: "var(--el-text-color-primary)" }
const fmt = (v: any) => {
if (v === null || v === undefined || v === "") return "0"
const n = Number(v)
if (Number.isNaN(n)) return v
return n.toLocaleString("zh-CN", { maximumFractionDigits: 4 })
}
const loadAll = async () => {
if (!bizMonth.value) {
useMessage().warning("请选择业务月")
return
}
loading.value = true
try {
const [s, i, sh, b] = await Promise.all([
reportSummary(bizMonth.value),
reportIncome(bizMonth.value),
reportSupplierShare(bizMonth.value),
reportBusiness(bizMonth.value),
])
summary.value = s.data
incomeRows.value = i.data || []
shareRows.value = sh.data || []
businessRows.value = b.data || []
} catch (err: any) {
useMessage().error(err?.msg || "加载失败")
} finally {
loading.value = false
}
}
const exportCurrent = () => {
const map: Record<string, string> = {
business: "/canteen/canteenReport/export/business",
income: "/canteen/canteenReport/export/income",
share: "/canteen/canteenReport/export/supplierShare",
}
const nameMap: Record<string, string> = {
business: "经营报表",
income: "收入合计",
share: "供应商分摊",
}
other.downBlobFile(map[activeTab.value], { bizMonth: bizMonth.value }, `${nameMap[activeTab.value]}_${bizMonth.value}.xlsx`)
}
onMounted(loadAll)
</script>
<style scoped>
.stat {
text-align: center;
line-height: 1.6;
font-size: 13px;
color: var(--el-text-color-regular);
}
.stat b {
font-size: 18px;
color: var(--el-text-color-primary);
}
.mb12 {
margin-bottom: 12px;
}
</style>
@@ -0,0 +1,260 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="业务月" prop="bizMonth">
<el-date-picker
v-model="state.queryForm.bizMonth"
type="month"
value-format="YYYY-MM"
placeholder="选择月份"
style="width: 140px"
/>
</el-form-item>
<el-form-item label="厂区" prop="park">
<el-input v-model="state.queryForm.park" clearable placeholder="厂区" style="width: 140px" />
</el-form-item>
<el-form-item label="供应商" prop="supplier">
<el-input v-model="state.queryForm.supplier" clearable placeholder="供应商" style="width: 140px" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="upload-filled" type="primary" class="ml10" @click="openImport">Excel 导入</el-button>
<el-button icon="download" plain type="primary" class="ml10" @click="exportExcel">导出</el-button>
<el-button icon="Delete" plain type="danger" class="ml10" :disabled="!state.queryForm.bizMonth" @click="clearMonth">
清空当月
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" @click="handleDelete(selectObjs)">
删除
</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10 mr20" style="float: right" @queryTable="getDataList" />
</div>
</el-row>
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
>
<el-table-column type="selection" width="40" align="center" fixed />
<el-table-column type="index" label="#" width="50" fixed />
<el-table-column prop="bizMonth" label="业务月" width="90" fixed />
<el-table-column prop="bizDate" label="时间" width="100" show-overflow-tooltip />
<el-table-column prop="supplier" label="供应商" width="100" show-overflow-tooltip />
<el-table-column prop="receiptNo" label="收货单号" width="120" show-overflow-tooltip />
<el-table-column prop="category" label="类别" width="90" show-overflow-tooltip />
<el-table-column prop="productCode" label="商品编码" width="110" show-overflow-tooltip />
<el-table-column prop="productName" label="品名" min-width="120" show-overflow-tooltip />
<el-table-column prop="spec" label="规格" width="120" show-overflow-tooltip />
<el-table-column prop="unit" label="单位" width="70" />
<el-table-column prop="qty" label="数量" width="90" align="right" />
<el-table-column prop="unitPrice" label="单价" width="90" align="right" />
<el-table-column prop="listPrice" label="定价" width="90" align="right" />
<el-table-column prop="priceDiff" label="单价差异" width="90" align="right" />
<el-table-column prop="settleUnitPrice" label="结算单价" width="90" align="right" />
<el-table-column prop="settleAmount" label="结算总金额" width="110" align="right" />
<el-table-column prop="amount" label="金额" width="100" align="right" />
<el-table-column prop="amountDiff" label="金额差异" width="100" align="right" />
<el-table-column prop="warehouse" label="食堂仓库" width="100" show-overflow-tooltip />
<el-table-column prop="park" label="厂区" width="100" />
<el-table-column prop="classify" label="分类" width="80" />
<el-table-column prop="energyAmount" label="能耗" width="90" align="right" />
<el-table-column prop="laborAmount" label="人工" width="90" align="right" />
<el-table-column prop="consumableAmount" label="耗材" width="90" align="right" />
<el-table-column prop="outsourceAmount" label="外协" width="90" align="right" />
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<el-dialog v-model="importVisible" title="导入食材能耗" width="520px" :close-on-click-modal="false" destroy-on-close>
<el-alert
type="info"
:closable="false"
show-icon
class="mb-4"
title="可直接上传整本报表 Excel,系统自动读取「食材能耗」sheet。数量/单价/定价按表填写;结算单价、结算总金额为空时自动计算。默认覆盖所选业务月。"
/>
<el-form label-width="100px">
<el-form-item label="业务月" required>
<el-date-picker v-model="importBizMonth" type="month" value-format="YYYY-MM" />
</el-form-item>
<el-form-item label="覆盖当月">
<el-switch v-model="replaceMonth" />
</el-form-item>
<el-form-item label="Excel 文件" required>
<el-upload
ref="importUploadRef"
:auto-upload="false"
:limit="1"
accept=".xlsx,.xls"
:on-change="onImportFileChange"
:on-remove="() => (importFile = null)"
>
<el-button type="primary">选择文件</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="importVisible = false">取消</el-button>
<el-button type="primary" :loading="importLoading" @click="submitImport">开始导入</el-button>
</template>
</el-dialog>
<el-dialog v-model="errorVisible" title="导入校验失败" width="640px" :close-on-click-modal="false">
<el-alert type="error" :closable="false" show-icon class="mb-3" :title="errorTitle" />
<el-table :data="errorData" max-height="420" border size="small">
<el-table-column prop="lineNum" label="行号" width="90" align="center" />
<el-table-column label="错误说明" min-width="360">
<template #default="{ row }">
<el-tag v-for="(e, i) in row.errors || row.reasons || []" :key="i" type="danger" class="mr-1 mb-1">
{{ e }}
</el-tag>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button type="primary" @click="errorVisible = false">知道了</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemCanteenReportFoodEnergy">
import type { UploadFile, UploadInstance } from "element-plus"
import { BasicTableProps, useTable } from "/@/hooks/table"
import { fetchFoodEnergyPage, delFoodEnergy, clearFoodEnergyMonth } from "/@/api/canteen/canteenReport"
import { useMessage, useMessageBox } from "/@/hooks/message"
import request from "/@/utils/request"
const queryRef = ref()
const showSearch = ref(true)
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: { bizMonth: "2026-07" },
pageList: fetchFoodEnergyPage,
})
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state)
const resetQuery = () => {
queryRef.value?.resetFields()
state.queryForm = { bizMonth: "2026-07" }
selectObjs.value = []
getDataList()
}
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id)
multiple.value = !objs.length
}
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm("确认删除选中数据?")
} catch {
return
}
try {
await delFoodEnergy(ids)
getDataList()
useMessage().success("删除成功")
} catch (err: any) {
useMessage().error(err.msg || "删除失败")
}
}
const clearMonth = async () => {
const m = state.queryForm.bizMonth
if (!m) return
try {
await useMessageBox().confirm(`确认清空 ${m} 全部食材能耗?`)
} catch {
return
}
try {
await clearFoodEnergyMonth(m)
getDataList()
useMessage().success("已清空")
} catch (err: any) {
useMessage().error(err.msg || "清空失败")
}
}
const exportExcel = () => {
downBlobFile("/canteen/canteenReport/foodEnergy/export", { ...state.queryForm }, `食材能耗_${state.queryForm.bizMonth || ""}.xlsx`)
}
const importVisible = ref(false)
const importLoading = ref(false)
const importBizMonth = ref("2026-07")
const replaceMonth = ref(true)
const importFile = ref<File | null>(null)
const importUploadRef = ref<UploadInstance>()
const errorVisible = ref(false)
const errorTitle = ref("导入失败")
const errorData = ref<any[]>([])
const openImport = () => {
importBizMonth.value = state.queryForm.bizMonth || "2026-07"
importVisible.value = true
}
const onImportFileChange = (file: UploadFile) => {
importFile.value = (file.raw as File) || null
}
const showImportErrors = (err: any) => {
const list = Array.isArray(err?.data) ? err.data : []
errorTitle.value = err?.msg || `导入校验失败,共 ${list.length || 0} 行有问题`
if (list.length) {
errorData.value = list
errorVisible.value = true
}
useMessage().error(errorTitle.value)
}
const submitImport = async () => {
if (!importBizMonth.value) {
useMessage().warning("请选择业务月")
return
}
if (!importFile.value) {
useMessage().warning("请选择 Excel 文件")
return
}
const fd = new FormData()
fd.append("file", importFile.value)
importLoading.value = true
try {
const res: any = await request({
url: "/canteen/canteenReport/foodEnergy/import",
method: "post",
params: { bizMonth: importBizMonth.value, replaceMonth: replaceMonth.value },
data: fd,
headers: { "Content-Type": "multipart/form-data" },
timeout: 600000,
})
useMessage().success(res.msg || "导入成功")
importVisible.value = false
importFile.value = null
importUploadRef.value?.clearFiles()
state.queryForm.bizMonth = importBizMonth.value
getDataList()
} catch (err: any) {
showImportErrors(err)
} finally {
importLoading.value = false
}
}
</script>
@@ -0,0 +1,258 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="业务月" prop="bizMonth">
<el-date-picker
v-model="state.queryForm.bizMonth"
type="month"
value-format="YYYY-MM"
placeholder="选择月份"
style="width: 140px"
/>
</el-form-item>
<el-form-item label="厂区" prop="park">
<el-input v-model="state.queryForm.park" clearable placeholder="厂区" style="width: 140px" />
</el-form-item>
<el-form-item label="收入类别" prop="incomeType">
<el-select v-model="state.queryForm.incomeType" clearable placeholder="全部" style="width: 140px">
<el-option v-for="t in incomeTypes" :key="t" :label="t" :value="t" />
</el-select>
</el-form-item>
<el-form-item label="成本中心" prop="costCenter">
<el-input v-model="state.queryForm.costCenter" clearable placeholder="核对成本" style="width: 160px" />
</el-form-item>
<el-form-item label="工号" prop="empNo">
<el-input v-model="state.queryForm.empNo" clearable placeholder="工号" style="width: 120px" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="upload-filled" type="primary" class="ml10" @click="openImport">Excel 导入</el-button>
<el-button icon="download" plain type="primary" class="ml10" @click="exportExcel">导出</el-button>
<el-button icon="Delete" plain type="danger" class="ml10" :disabled="!state.queryForm.bizMonth" @click="clearMonth">
清空当月
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" @click="handleDelete(selectObjs)">
删除
</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10 mr20" style="float: right" @queryTable="getDataList" />
</div>
</el-row>
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="bizMonth" label="业务月" width="90" />
<el-table-column prop="empNo" label="工号" width="100" show-overflow-tooltip />
<el-table-column prop="empName" label="姓名" width="90" show-overflow-tooltip />
<el-table-column prop="legalEntity" label="法律归属" min-width="140" show-overflow-tooltip />
<el-table-column prop="deptName" label="部门" min-width="100" show-overflow-tooltip />
<el-table-column prop="consumeArea" label="消费区域" min-width="110" show-overflow-tooltip />
<el-table-column prop="settleCounter" label="结算台" min-width="140" show-overflow-tooltip />
<el-table-column prop="consumeAmount" label="消费金额" width="100" align="right" />
<el-table-column prop="incomeType" label="收入类别" width="100" />
<el-table-column prop="costCenter" label="核对成本" min-width="140" show-overflow-tooltip />
<el-table-column prop="park" label="厂区" width="100" />
<el-table-column prop="remarkTag" label="备注标识" width="90" show-overflow-tooltip />
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<el-dialog v-model="importVisible" title="导入刷卡明细" width="520px" :close-on-click-modal="false" destroy-on-close>
<el-alert
type="info"
:closable="false"
show-icon
class="mb-4"
title="可直接上传整本「202607食堂报表」Excel,系统会自动读取「刷卡明细」sheet;也可只上传该 sheet。导入默认覆盖所选业务月。"
/>
<el-form label-width="100px">
<el-form-item label="业务月" required>
<el-date-picker v-model="importBizMonth" type="month" value-format="YYYY-MM" placeholder="yyyy-MM" />
</el-form-item>
<el-form-item label="覆盖当月">
<el-switch v-model="replaceMonth" />
</el-form-item>
<el-form-item label="Excel 文件" required>
<el-upload
ref="importUploadRef"
:auto-upload="false"
:limit="1"
accept=".xlsx,.xls"
:on-change="onImportFileChange"
:on-remove="() => (importFile = null)"
>
<el-button type="primary">选择文件</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="importVisible = false">取消</el-button>
<el-button type="primary" :loading="importLoading" @click="submitImport">开始导入</el-button>
</template>
</el-dialog>
<el-dialog v-model="errorVisible" title="导入校验失败" width="640px" :close-on-click-modal="false">
<el-alert type="error" :closable="false" show-icon class="mb-3" :title="errorTitle" />
<el-table :data="errorData" max-height="420" border size="small">
<el-table-column prop="lineNum" label="行号" width="90" align="center" />
<el-table-column label="错误说明" min-width="360">
<template #default="{ row }">
<el-tag v-for="(e, i) in row.errors || row.reasons || []" :key="i" type="danger" class="mr-1 mb-1">
{{ e }}
</el-tag>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button type="primary" @click="errorVisible = false">知道了</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemCanteenReportSwipe">
import type { UploadFile, UploadInstance } from "element-plus"
import { BasicTableProps, useTable } from "/@/hooks/table"
import { fetchSwipePage, delSwipe, clearSwipeMonth } from "/@/api/canteen/canteenReport"
import { useMessage, useMessageBox } from "/@/hooks/message"
import request from "/@/utils/request"
const incomeTypes = ["员工刷卡", "支付宝", "包厢收入", "其他收入", "利润调整", "开票结算", "无法收回"]
const queryRef = ref()
const showSearch = ref(true)
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: { bizMonth: "2026-07" },
pageList: fetchSwipePage,
})
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state)
const resetQuery = () => {
queryRef.value?.resetFields()
state.queryForm = { bizMonth: "2026-07" }
selectObjs.value = []
getDataList()
}
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id)
multiple.value = !objs.length
}
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm("确认删除选中数据?")
} catch {
return
}
try {
await delSwipe(ids)
getDataList()
useMessage().success("删除成功")
} catch (err: any) {
useMessage().error(err.msg || "删除失败")
}
}
const clearMonth = async () => {
const m = state.queryForm.bizMonth
if (!m) return
try {
await useMessageBox().confirm(`确认清空 ${m} 全部刷卡明细?`)
} catch {
return
}
try {
await clearSwipeMonth(m)
getDataList()
useMessage().success("已清空")
} catch (err: any) {
useMessage().error(err.msg || "清空失败")
}
}
const exportExcel = () => {
downBlobFile("/canteen/canteenReport/swipe/export", { ...state.queryForm }, `刷卡明细_${state.queryForm.bizMonth || ""}.xlsx`)
}
const importVisible = ref(false)
const importLoading = ref(false)
const importBizMonth = ref("2026-07")
const replaceMonth = ref(true)
const importFile = ref<File | null>(null)
const importUploadRef = ref<UploadInstance>()
const errorVisible = ref(false)
const errorTitle = ref("导入失败")
const errorData = ref<any[]>([])
const openImport = () => {
importBizMonth.value = state.queryForm.bizMonth || "2026-07"
importVisible.value = true
}
const onImportFileChange = (file: UploadFile) => {
importFile.value = (file.raw as File) || null
}
const showImportErrors = (err: any) => {
const list = Array.isArray(err?.data) ? err.data : []
errorTitle.value = err?.msg || `导入校验失败,共 ${list.length || 0} 行有问题`
if (list.length) {
errorData.value = list
errorVisible.value = true
}
useMessage().error(errorTitle.value)
}
const submitImport = async () => {
if (!importBizMonth.value) {
useMessage().warning("请选择业务月")
return
}
if (!importFile.value) {
useMessage().warning("请选择 Excel 文件")
return
}
const fd = new FormData()
fd.append("file", importFile.value)
importLoading.value = true
try {
const res: any = await request({
url: "/canteen/canteenReport/swipe/import",
method: "post",
params: { bizMonth: importBizMonth.value, replaceMonth: replaceMonth.value },
data: fd,
headers: { "Content-Type": "multipart/form-data" },
timeout: 600000,
})
useMessage().success(res.msg || "导入成功")
importVisible.value = false
importFile.value = null
importUploadRef.value?.clearFiles()
state.queryForm.bizMonth = importBizMonth.value
getDataList()
} catch (err: any) {
showImportErrors(err)
} finally {
importLoading.value = false
}
}
</script>
@@ -3,6 +3,9 @@
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="商户" prop="merchantId">
<select-mch v-model="state.queryForm.merchantId" :apiParams="{ types: '5' }" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="类目ID" prop="categoryId">
<el-input placeholder="请输入类目ID" v-model="state.queryForm.categoryId" />
</el-form-item>