食堂演示报表,用户管理姓名支持数字
This commit is contained in:
1 parent
232cb6f502
commit
dded2ab332
5 files changed
+751
-5
No files matched your search
@@ -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 })
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user