食堂演示报表,用户管理姓名支持数字

This commit is contained in:
zjf committed 2026-08-17 13:47:15 +08:00
1 parent 232cb6f502
commit dded2ab332
5 files changed
+751 -5

No files matched your search

+43
View File
@@ -1,5 +1,48 @@
import request from "/@/utils/request"
/** 人脸消费导入 */
export function fetchFaceImportLogPage(query?: Object) {
return request({ url: "/canteen/canteenReport/faceImport/log/page", method: "get", params: query })
}
export function fetchFaceDetailPage(query?: Object) {
return request({ url: "/canteen/canteenReport/faceImport/detail/page", method: "get", params: query })
}
export function importFaceExcel(data: FormData, params: Object) {
return request({
url: "/canteen/canteenReport/faceImport/import",
method: "post",
params,
data,
headers: { "Content-Type": "multipart/form-data" },
timeout: 600000,
})
}
export function aggregateFaceToSwipe(params: Object) {
return request({
url: "/canteen/canteenReport/faceImport/aggregate",
method: "post",
params,
timeout: 600000,
})
}
/** 结算台厂区 */
export function fetchSettleParkPage(query?: Object) {
return request({ url: "/canteen/canteenReport/settlePark/page", method: "get", params: query })
}
export function fetchSettleParks() {
return request({ url: "/canteen/canteenReport/settlePark/parks", method: "get" })
}
export function addSettlePark(obj?: Object) {
return request({ url: "/canteen/canteenReport/settlePark", method: "post", data: obj })
}
export function putSettlePark(obj?: Object) {
return request({ url: "/canteen/canteenReport/settlePark", method: "put", data: obj })
}
export function delSettlePark(ids?: Object) {
return request({ url: "/canteen/canteenReport/settlePark", method: "delete", data: ids })
}
/** 刷卡明细 */
export function fetchSwipePage(query?: Object) {
return request({ url: "/canteen/canteenReport/swipe/page", method: "get", params: query })
+7 -4
View File
@@ -47,15 +47,18 @@ export const rule = {
}
},
/**
* 姓名/昵称:中文,或英文(单词间可空格,如 John Smith)
* 姓名/昵称:中文、英文、数字(单词间可空格,如 John Smith、张三1)
*/
validatorPersonName(rule: any, value: any, callback: any) {
const pattern =
/^(?:[\u4e00-\u9fa5]{1,32}|[A-Za-z]+(?:\s[A-Za-z]+){0,8}|[\u4e00-\u9fa5A-Za-z]+(?:\s[\u4e00-\u9fa5A-Za-z]+){0,8})$/;
const pattern = /^[\u4e00-\u9fa5A-Za-z0-9]+(?:\s[\u4e00-\u9fa5A-Za-z0-9]+){0,8}$/;
if (value) {
const normalized = String(value).trim().replace(/\s+/g, ' ');
if (normalized.length > 32) {
callback(new Error('不能超过32个字符'));
return;
}
if (!pattern.test(normalized)) {
callback(new Error('仅支持中文,或英文(单词间可用空格,如 John Smith)'));
callback(new Error('仅支持中文、英文、数字(单词间可用空格)'));
return;
}
}
@@ -0,0 +1,519 @@
<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"
clearable
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="state.queryForm.status" clearable placeholder="全部" style="width: 120px">
<el-option label="成功" value="SUCCESS" />
<el-option label="失败" value="FAILED" />
<el-option label="进行中" value="RUNNING" />
</el-select>
</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="DataAnalysis" type="success" class="ml10" :loading="aggLoading" @click="openAggregate">
统计汇总到刷卡明细
</el-button>
<el-button icon="List" plain type="primary" class="ml10" @click="openDetail()">查看逐笔明细</el-button>
<el-button icon="Setting" plain class="ml10" @click="goSettlePark">结算台厂区</el-button>
<el-button icon="Tickets" plain class="ml10" @click="goSwipe">刷卡明细</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10 mr20" style="float: right" @queryTable="getDataList" />
</div>
</el-row>
<el-alert
type="info"
:closable="false"
show-icon
class="mb-3"
title="导入按 Excel 原列写入「逐笔明细」。点「统计汇总」时才做映射:人员编号→工号、三级组织→消费区域、交易设备→结算台、订单金额合计→消费金额;刷脸→员工刷卡、B扫C→支付宝;厂区按结算台配置。"
/>
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="bizMonth" label="业务月" width="90" />
<el-table-column label="数据日期段" min-width="200">
<template #default="{ row }">
<span v-if="row.dateStart || row.dateEnd">{{ row.dateStart || "-" }} ~ {{ row.dateEnd || "-" }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="fileName" label="Excel 文件" min-width="160" show-overflow-tooltip />
<el-table-column prop="successRows" label="明细入库" width="90" align="right" />
<el-table-column prop="skipRows" label="跳过" width="70" align="right" />
<el-table-column prop="status" label="导入" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === 'SUCCESS'" type="success" size="small">成功</el-tag>
<el-tag v-else-if="row.status === 'FAILED'" type="danger" size="small">失败</el-tag>
<el-tag v-else type="warning" size="small">{{ row.status || "-" }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="aggStatus" label="汇总" width="90" align="center">
<template #default="{ row }">
<el-tag v-if="row.aggStatus === 'DONE'" type="success" size="small">已汇总{{ row.aggRows ? `(${row.aggRows})` : "" }}</el-tag>
<el-tag v-else type="info" size="small">未汇总</el-tag>
</template>
</el-table-column>
<el-table-column prop="costMs" label="耗时" width="80" align="right">
<template #default="{ row }">
{{ row.costMs != null ? (row.costMs / 1000).toFixed(1) + "s" : "-" }}
</template>
</el-table-column>
<el-table-column prop="operator" label="操作人" width="90" show-overflow-tooltip />
<el-table-column prop="createTime" label="导入时间" width="170" />
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button v-if="row.fileUrl" link type="primary" @click="openFile(row.fileUrl)">Excel</el-button>
<el-button link type="primary" @click="openDetail(row)">明细</el-button>
<el-button link type="success" :loading="aggLoading" @click="aggregateOne(row)">汇总</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 导入 -->
<el-dialog
v-model="importVisible"
title="导入人脸消费 Excel(逐笔明细)"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="!importLoading"
:show-close="!importLoading"
:before-close="onImportDialogBeforeClose"
destroy-on-close
>
<div v-loading="importLoading" :element-loading-text="importLoadingText" class="import-dialog-body">
<el-alert v-if="importLoading" type="warning" :closable="false" show-icon class="mb-4">
<template #title>
<div style="line-height: 1.7">
<div><b>正在导入,请勿关闭页面或重复点击。</b></div>
<div>数据量较大时(如十几万~几十万行)后台仍在处理,前端可能看起来「没反应」,请耐心等候,通常几分钟内完成。</div>
<div v-if="importElapsedSec > 0">已等待 {{ importElapsedSec }} 秒…</div>
</div>
</template>
</el-alert>
<el-alert v-else type="info" :closable="false" show-icon class="mb-4">
<template #title>
<div style="line-height: 1.6">
<div><b>报表归属月</b>:数据算进哪个月的报表(看板按这个月查)。</div>
<div><b>本文件日期段</b>:这份 Excel 实际覆盖哪几天(如同月拆成 1–15、16–31 两份时必填)。</div>
<div>大文件(几十万行)导入较慢,提交后请耐心等候,不要刷新页面。</div>
</div>
</template>
</el-alert>
<el-form label-width="120px" :disabled="importLoading">
<el-form-item label="报表归属月" required>
<el-date-picker v-model="importForm.bizMonth" type="month" value-format="YYYY-MM" placeholder="yyyy-MM" />
<div class="form-tip">一般与下面日期段同一月,例如 2026-08</div>
</el-form-item>
<el-form-item label="本文件日期段">
<el-date-picker
v-model="importDateRange"
type="daterange"
value-format="YYYY-MM-DD"
start-placeholder="起日,如 08-01"
end-placeholder="止日,如 08-15"
style="width: 100%"
@change="onImportDateRangeChange"
/>
<div class="form-tip">例:上半月填 2026-08-01~2026-08-15;下半月再导一份填 16~31</div>
</el-form-item>
<el-form-item label="覆盖本段明细">
<el-switch v-model="importForm.replaceRange" />
<span class="form-tip ml-2">开启后只替换「本文件日期段」内已有明细,不会清掉同月其它日期段</span>
</el-form-item>
<el-form-item label="Excel 文件" required>
<el-upload
ref="importUploadRef"
:auto-upload="false"
:limit="1"
accept=".xlsx,.xls"
:disabled="importLoading"
:on-change="onImportFileChange"
:on-remove="() => (importFile = null)"
>
<el-button type="primary" :disabled="importLoading">选择文件</el-button>
</el-upload>
<div v-if="importFileHint" class="form-tip">{{ importFileHint }}</div>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button :disabled="importLoading" @click="importVisible = false">取消</el-button>
<el-button type="primary" :loading="importLoading" @click="submitImport">
{{ importLoading ? "导入中,请稍候…" : "开始导入" }}
</el-button>
</template>
</el-dialog>
<!-- 按月汇总 -->
<el-dialog v-model="aggVisible" title="统计汇总到刷卡明细" width="480px" :close-on-click-modal="false">
<el-form label-width="120px">
<el-form-item label="业务月" required>
<el-date-picker v-model="aggForm.bizMonth" type="month" value-format="YYYY-MM" />
</el-form-item>
<el-form-item label="覆盖当月FACE">
<el-switch v-model="aggForm.replaceFace" />
<span class="ml-2 text-xs text-gray-400">先清空该月已汇总的人脸刷卡行再写入</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="aggVisible = false">取消</el-button>
<el-button type="primary" :loading="aggLoading" @click="submitAggregate">开始汇总</el-button>
</template>
</el-dialog>
<!-- 明细 -->
<el-dialog v-model="detailVisible" title="人脸消费明细(与Excel列一致)" width="95%" top="3vh" destroy-on-close>
<el-form :inline="true" class="mb-2">
<el-form-item label="人员编号">
<el-input v-model="detailQuery.empNo" clearable style="width: 120px" />
</el-form-item>
<el-form-item label="支付方式">
<el-input v-model="detailQuery.payMethod" clearable placeholder="如 B扫C / 刷脸" style="width: 140px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadDetails">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="detailList" v-loading="detailLoading" border max-height="68vh" size="small">
<el-table-column prop="orderNo" label="订单号" min-width="160" show-overflow-tooltip fixed />
<el-table-column prop="thirdOrderNo" label="第三方订单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="orderCreateTime" label="创建时间" width="160" show-overflow-tooltip />
<el-table-column prop="payTime" label="支付时间" width="160" show-overflow-tooltip />
<el-table-column prop="deductTime" label="扣款时间" width="160" show-overflow-tooltip />
<el-table-column prop="orgLevel1" label="一级组织" min-width="110" show-overflow-tooltip />
<el-table-column prop="orgLevel2" label="二级组织" min-width="110" show-overflow-tooltip />
<el-table-column prop="orgLevel3" label="三级组织" min-width="110" show-overflow-tooltip />
<el-table-column prop="canteen" label="食堂" min-width="120" show-overflow-tooltip />
<el-table-column prop="canteenArea" label="食堂区域" min-width="100" show-overflow-tooltip />
<el-table-column prop="consumePoint" label="消费点" min-width="110" show-overflow-tooltip />
<el-table-column prop="empName" label="姓名" width="90" show-overflow-tooltip />
<el-table-column prop="phone" label="手机号" width="120" show-overflow-tooltip />
<el-table-column prop="legalEntity" label="法律归属" min-width="140" show-overflow-tooltip />
<el-table-column prop="empNo" label="人员编号" width="110" show-overflow-tooltip />
<el-table-column prop="cardNo" label="卡号" width="110" show-overflow-tooltip />
<el-table-column prop="groupName" label="分组" width="100" show-overflow-tooltip />
<el-table-column prop="deptName" label="部门" min-width="110" show-overflow-tooltip />
<el-table-column prop="payMethod" label="支付方式" width="100" />
<el-table-column prop="payType" label="支付类型" width="100" />
<el-table-column prop="orderAmount" label="订单金额" width="100" align="right" />
<el-table-column prop="changeAmount" label="动账金额" width="100" align="right" />
<el-table-column prop="storedChange" label="储值动账" width="100" align="right" />
<el-table-column prop="subsidyChange" label="补贴动账" width="100" align="right" />
<el-table-column prop="giftChange" label="赠送动账" width="100" align="right" />
<el-table-column prop="storedBalance" label="储值钱包余额" width="120" align="right" />
<el-table-column prop="subsidyBalance" label="补贴钱包余额" width="120" align="right" />
<el-table-column prop="giftBalance" label="赠送钱包余额" width="120" align="right" />
<el-table-column prop="changeOrg" label="动账组织" min-width="120" show-overflow-tooltip />
<el-table-column prop="discount" label="优惠" width="90" show-overflow-tooltip />
<el-table-column prop="discountAmount" label="优惠金额" width="100" align="right" />
<el-table-column prop="discountType" label="优惠类型" width="100" show-overflow-tooltip />
<el-table-column prop="couponType" label="券类型" width="90" show-overflow-tooltip />
<el-table-column prop="deductAmount" label="抵扣金额" width="100" align="right" />
<el-table-column prop="feeAmount" label="手续费" width="90" align="right" />
<el-table-column prop="mealSubsidy" label="餐补" width="90" align="right" />
<el-table-column prop="consumeType" label="消费类型" width="100" />
<el-table-column prop="mealSegment" label="餐段" width="80" />
<el-table-column prop="deviceName" label="交易设备" min-width="130" show-overflow-tooltip />
<el-table-column prop="deviceStatus" label="设备状态" width="90" />
<el-table-column prop="remark" label="备注" min-width="100" show-overflow-tooltip />
<el-table-column prop="operatorName" label="操作员" width="90" show-overflow-tooltip />
<el-table-column prop="visitorPayMethod" label="访客餐支付方式" width="130" show-overflow-tooltip />
</el-table>
<div class="mt-3 flex justify-end">
<el-pagination
v-model:current-page="detailPage.current"
v-model:page-size="detailPage.size"
:total="detailPage.total"
layout="total, prev, pager, next"
@current-change="loadDetails"
/>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemCanteenReportFaceImport">
import type { UploadFile, UploadInstance } from "element-plus"
import { BasicTableProps, useTable } from "/@/hooks/table"
import {
fetchFaceImportLogPage,
fetchFaceDetailPage,
importFaceExcel,
aggregateFaceToSwipe,
} from "/@/api/canteen/canteenReport"
import { useMessage, useMessageBox } from "/@/hooks/message"
import { useRouter } from "vue-router"
const router = useRouter()
const queryRef = ref()
const showSearch = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: { bizMonth: "2026-07", status: "" },
pageList: fetchFaceImportLogPage,
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
const resetQuery = () => {
queryRef.value?.resetFields()
state.queryForm = { bizMonth: "2026-07", status: "" }
getDataList()
}
const goSwipe = () => {
router.push({ path: "/canteen/canteenReportSwipe/index", query: { bizMonth: state.queryForm.bizMonth || "" } })
}
const goSettlePark = () => {
router.push({ path: "/canteen/canteenReportSettlePark/index" })
}
const openFile = (url: string) => {
if (url) window.open(url, "_blank")
}
const importVisible = ref(false)
const importLoading = ref(false)
const importLoadingText = ref("正在导入,请耐心等候…")
const importElapsedSec = ref(0)
const importFile = ref<File | null>(null)
const importFileHint = ref("")
const importUploadRef = ref<UploadInstance>()
const importDateRange = ref<string[] | null>(null)
const importForm = reactive({ bizMonth: "2026-07", replaceRange: true })
let importTimer: ReturnType<typeof setInterval> | null = null
const clearImportTimer = () => {
if (importTimer) {
clearInterval(importTimer)
importTimer = null
}
importElapsedSec.value = 0
}
onBeforeUnmount(() => {
clearImportTimer()
})
const startImportTimer = () => {
clearImportTimer()
importElapsedSec.value = 0
importTimer = setInterval(() => {
importElapsedSec.value += 1
if (importElapsedSec.value >= 60) {
importLoadingText.value = `仍在导入中(已 ${importElapsedSec.value} 秒),大文件请继续等候,勿关闭…`
} else {
importLoadingText.value = `正在导入,请耐心等候…(已 ${importElapsedSec.value} 秒)`
}
}, 1000)
}
const onImportDialogBeforeClose = (done: () => void) => {
if (importLoading.value) {
useMessage().warning("正在导入中,请耐心等候,暂不可关闭")
return
}
done()
}
const openImport = () => {
importForm.bizMonth = state.queryForm.bizMonth || "2026-07"
importForm.replaceRange = true
importDateRange.value = null
importFile.value = null
importFileHint.value = ""
importLoading.value = false
clearImportTimer()
importVisible.value = true
}
const onImportFileChange = (file: UploadFile) => {
importFile.value = (file.raw as File) || null
if (importFile.value) {
const mb = importFile.value.size / (1024 * 1024)
if (mb >= 5) {
importFileHint.value = `当前文件约 ${mb.toFixed(1)} MB,数据量可能较大,导入需数分钟,提交后请耐心等候,勿刷新页面。`
} else {
importFileHint.value = `已选择:${importFile.value.name}(${mb.toFixed(2)} MB)`
}
} else {
importFileHint.value = ""
}
}
/** 选完本文件日期段后,自动带出报表归属月(取起日所在月) */
const onImportDateRangeChange = (val: string[] | null) => {
if (val?.length === 2 && val[0]?.length >= 7) {
importForm.bizMonth = val[0].substring(0, 7)
}
}
const submitImport = async () => {
if (importLoading.value) return
if (!importForm.bizMonth) return useMessage().warning("请选择报表归属月")
if (importDateRange.value?.length === 2) {
const rangeMonth = importDateRange.value[0].substring(0, 7)
if (rangeMonth !== importForm.bizMonth) {
return useMessage().warning(`报表归属月(${importForm.bizMonth})与本文件日期段所在月(${rangeMonth})不一致,请改成同一月`)
}
}
if (!importFile.value) return useMessage().warning("请选择 Excel 文件")
const fd = new FormData()
fd.append("file", importFile.value)
const params: any = { bizMonth: importForm.bizMonth, replaceRange: importForm.replaceRange }
if (importDateRange.value?.length === 2) {
params.dateStart = importDateRange.value[0]
params.dateEnd = importDateRange.value[1]
}
importLoading.value = true
importLoadingText.value = "正在导入,请耐心等候…"
startImportTimer()
useMessage().info("已开始导入。数据量较大时请耐心等候,弹窗会保持加载状态直至完成。")
try {
const res: any = await importFaceExcel(fd, params)
useMessage().success(res.msg || "导入成功")
importVisible.value = false
importFile.value = null
importFileHint.value = ""
importUploadRef.value?.clearFiles()
state.queryForm.bizMonth = importForm.bizMonth
getDataList()
} catch (err: any) {
useMessage().error(err.msg || "导入失败")
} finally {
importLoading.value = false
clearImportTimer()
}
}
const aggVisible = ref(false)
const aggLoading = ref(false)
const aggForm = reactive({ bizMonth: "2026-07", replaceFace: true })
const openAggregate = () => {
aggForm.bizMonth = state.queryForm.bizMonth || "2026-07"
aggForm.replaceFace = true
aggVisible.value = true
}
const submitAggregate = async () => {
if (!aggForm.bizMonth) return useMessage().warning("请选择业务月")
try {
await useMessageBox().confirm(`确认将 ${aggForm.bizMonth} 人脸明细汇总写入刷卡明细?B扫C 将计入支付宝。`)
} catch {
return
}
aggLoading.value = true
try {
const res: any = await aggregateFaceToSwipe({
bizMonth: aggForm.bizMonth,
replaceFace: aggForm.replaceFace,
})
useMessage().success(res.msg || "汇总成功")
aggVisible.value = false
getDataList()
} catch (err: any) {
useMessage().error(err.msg || "汇总失败")
} finally {
aggLoading.value = false
}
}
const aggregateOne = async (row: any) => {
try {
await useMessageBox().confirm(`确认汇总该批导入到刷卡明细?将覆盖该批已汇总的刷卡行。`)
} catch {
return
}
aggLoading.value = true
try {
// 只传批次 ID + 归属月,不要传日期段(日期段与明细 biz_date 不一致时会查空)
const res: any = await aggregateFaceToSwipe({
bizMonth: row.bizMonth,
importLogId: String(row.id),
replaceFace: true,
})
useMessage().success(res.msg || "汇总成功")
getDataList()
} catch (err: any) {
useMessage().error(err.msg || "汇总失败")
} finally {
aggLoading.value = false
}
}
const detailVisible = ref(false)
const detailLoading = ref(false)
const detailList = ref<any[]>([])
const detailQuery = reactive({ bizMonth: "", importLogId: undefined as any, empNo: "", payMethod: "" })
const detailPage = reactive({ current: 1, size: 20, total: 0 })
const openDetail = (row?: any) => {
detailQuery.bizMonth = row?.bizMonth || state.queryForm.bizMonth || ""
detailQuery.importLogId = row?.id
detailQuery.empNo = ""
detailQuery.payMethod = ""
detailPage.current = 1
detailVisible.value = true
loadDetails()
}
const loadDetails = async () => {
detailLoading.value = true
try {
const res: any = await fetchFaceDetailPage({
current: detailPage.current,
size: detailPage.size,
bizMonth: detailQuery.bizMonth,
importLogId: detailQuery.importLogId != null ? String(detailQuery.importLogId) : undefined,
empNo: detailQuery.empNo,
payMethod: detailQuery.payMethod,
})
detailList.value = res.data?.records || []
detailPage.total = res.data?.total || 0
} catch (err: any) {
useMessage().error(err.msg || "加载明细失败")
} finally {
detailLoading.value = false
}
}
</script>
<style scoped>
.form-tip {
margin-top: 4px;
font-size: 12px;
color: #909399;
line-height: 1.4;
}
.ml-2 {
margin-left: 8px;
}
.import-dialog-body {
min-height: 220px;
}
.mb-4 {
margin-bottom: 16px;
}
</style>
@@ -0,0 +1,174 @@
<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="settleCounter">
<el-input v-model="state.queryForm.settleCounter" clearable placeholder="交易设备名称" style="width: 180px" />
</el-form-item>
<el-form-item label="厂区" prop="park">
<el-select v-model="state.queryForm.park" clearable placeholder="全部" style="width: 140px">
<el-option v-for="p in parks" :key="p" :label="p" :value="p" />
</el-select>
</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>
<right-toolbar v-model:showSearch="showSearch" class="ml10 mr20" style="float: right" @queryTable="getDataList" />
</div>
</el-row>
<el-alert
type="info"
:closable="false"
show-icon
class="mb-3"
title="人脸明细汇总时:交易设备名称匹配「结算台」,写入刷卡明细的厂区。请先配置再汇总。"
/>
<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="settleCounter" label="结算台/交易设备" min-width="200" show-overflow-tooltip />
<el-table-column prop="park" label="厂区" width="120" />
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="170" />
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openForm(row)">编辑</el-button>
<el-button link type="danger" @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="480px" destroy-on-close>
<el-form label-width="110px">
<el-form-item label="结算台" required>
<el-input v-model="form.settleCounter" placeholder="与人脸表「交易设备」一致" />
</el-form-item>
<el-form-item label="厂区" required>
<el-select v-model="form.park" placeholder="选择厂区" style="width: 100%">
<el-option v-for="p in parks" :key="p" :label="p" :value="p" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false">取消</el-button>
<el-button type="primary" :loading="formLoading" @click="submitForm">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemCanteenReportSettlePark">
import { BasicTableProps, useTable } from "/@/hooks/table"
import {
fetchSettleParkPage,
fetchSettleParks,
addSettlePark,
putSettlePark,
delSettlePark,
} from "/@/api/canteen/canteenReport"
import { useMessage, useMessageBox } from "/@/hooks/message"
const parks = ref<string[]>([])
const queryRef = ref()
const showSearch = ref(true)
const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: { settleCounter: "", park: "" },
pageList: fetchSettleParkPage,
})
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state)
onMounted(async () => {
try {
const res: any = await fetchSettleParks()
parks.value = res.data || []
} catch {
parks.value = ["敏惠园区", "敏实园区", "敏胜园区", "敏华园区", "湖州园区", "华智一期", "华智二期", "安吉园区"]
}
})
const resetQuery = () => {
queryRef.value?.resetFields()
state.queryForm = { settleCounter: "", park: "" }
getDataList()
}
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id)
multiple.value = !objs.length
}
const formVisible = ref(false)
const formLoading = ref(false)
const form = reactive<any>({ id: undefined, settleCounter: "", park: "", remark: "" })
const openForm = (row?: any) => {
form.id = row?.id
form.settleCounter = row?.settleCounter || ""
form.park = row?.park || ""
form.remark = row?.remark || ""
formVisible.value = true
}
const submitForm = async () => {
if (!form.settleCounter || !form.park) {
useMessage().warning("请填写结算台和厂区")
return
}
formLoading.value = true
try {
if (form.id) {
await putSettlePark({ ...form })
} else {
await addSettlePark({ settleCounter: form.settleCounter, park: form.park, remark: form.remark })
}
useMessage().success("保存成功")
formVisible.value = false
getDataList()
} catch (err: any) {
useMessage().error(err.msg || "保存失败")
} finally {
formLoading.value = false
}
}
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm("确认删除选中配置?")
} catch {
return
}
try {
await delSettlePark(ids)
getDataList()
useMessage().success("删除成功")
} catch (err: any) {
useMessage().error(err.msg || "删除失败")
}
}
</script>
@@ -56,6 +56,7 @@
<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="bizDate" label="业务日" width="110" />
<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 />
@@ -66,6 +67,7 @@
<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="source" label="来源" width="80" />
<el-table-column prop="remarkTag" label="备注标识" width="90" show-overflow-tooltip />
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
@@ -130,7 +132,9 @@ 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"
import { useRoute } from "vue-router"
const route = useRoute()
const incomeTypes = ["员工刷卡", "支付宝", "包厢收入", "其他收入", "利润调整", "开票结算", "无法收回"]
const queryRef = ref()
@@ -139,7 +143,10 @@ const selectObjs = ref([]) as any
const multiple = ref(true)
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: { bizMonth: "2026-07" },
queryForm: {
bizMonth: (route.query.bizMonth as string) || "2026-07",
...(route.query.importLogId ? { importLogId: route.query.importLogId } : {}),
},
pageList: fetchSwipePage,
})