feat:钱包模块导出都改为分批,前端显示导出进度条的模式。
fix:修改各个统计模块统计的数量不一致的情况。 fix:修改支付宝分账退款失败的情况
This commit is contained in:
1 parent
452f0839fd
commit
b5ad897188
18 files changed
+497
-260
No files matched your search
@@ -8,7 +8,7 @@ export interface CanteenUserReservationQuery {
|
||||
current?: number
|
||||
/** 每页用户数量。 */
|
||||
size?: number
|
||||
/** 用餐日期闭区间,固定为开始、结束两个 YYYY-MM-DD 日期,最大跨度为两个自然月。 */
|
||||
/** 统计日期闭区间;已核销按实际核销日期,其他状态按预约用餐日期,最大跨度为两个自然月。 */
|
||||
apptUseDateList: [string, string]
|
||||
/** 姓名、登录账号或手机号关键字。 */
|
||||
nameOrPhone?: string
|
||||
@@ -42,6 +42,8 @@ export interface CanteenUserReservationOrder {
|
||||
id: string | number
|
||||
/** 用餐时间段字典编码。 */
|
||||
hourType?: string
|
||||
/** 订单原预约用餐日期;已核销按核销日查询时用于展示跨日预约。 */
|
||||
apptUseDate?: string
|
||||
/** 下单时保存的商品描述。 */
|
||||
productDesc?: string
|
||||
/** 预约所属食堂名称。 */
|
||||
@@ -115,13 +117,13 @@ export interface CanteenUserReservationDishStatistics {
|
||||
|
||||
/** 预约统计的通用汇总结构。金额单位为分。 */
|
||||
export interface CanteenUserReservationStatisticsSummary {
|
||||
/** 用户-用餐日期记录数。 */
|
||||
/** 用户-统计日期记录数。 */
|
||||
userCount?: number
|
||||
/** 当前范围内去重用户数。 */
|
||||
uniqueUserCount?: number
|
||||
/** 已订餐用户-用餐日期记录数。 */
|
||||
/** 已订餐用户-统计日期记录数。 */
|
||||
reservedUserCount?: number
|
||||
/** 未订餐用户-用餐日期记录数。 */
|
||||
/** 未订餐用户-统计日期记录数。 */
|
||||
unreservedUserCount?: number
|
||||
/** 有效预约订单数。 */
|
||||
orderCount?: number
|
||||
@@ -137,7 +139,7 @@ export interface CanteenUserReservationStatisticsSummary {
|
||||
|
||||
/** 预约每日统计结构。 */
|
||||
export interface CanteenUserReservationDailyStatistics extends CanteenUserReservationStatisticsSummary {
|
||||
/** 用餐日期,格式为 YYYY-MM-DD。 */
|
||||
/** 统计日期,已核销取核销日期,其他状态取预约用餐日期。 */
|
||||
date?: string
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ export interface CanteenUserReservationStatisticsResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按用户和用餐日期范围分页查询每日食堂预约,后端会保留每天没有预约日志的权限范围用户。
|
||||
* 按用户和统计日期范围分页查询每日食堂预约,后端会保留没有预约日志的权限范围用户日期行。
|
||||
*/
|
||||
export function canteenUserReservationPage(query: CanteenUserReservationQuery) {
|
||||
return request({
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import request from "/@/utils/request"
|
||||
import type { AsyncTaskProgress } from "/@/api/asyncTask"
|
||||
|
||||
/** 充值扣款水单异步导出提交接口的统一响应。 */
|
||||
export interface WalletRechargeImportExportTaskResponse {
|
||||
/** 后端创建的初始异步任务进度。 */
|
||||
data: AsyncTaskProgress
|
||||
/** 后端返回的任务提交提示。 */
|
||||
msg?: string
|
||||
}
|
||||
|
||||
export function pageJoin(query: any) {
|
||||
return request({
|
||||
@@ -58,3 +67,12 @@ export function recover(params?: Object) {
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 提交充值扣款水单异步分片导出任务。 */
|
||||
export function submitExport(query: Record<string, unknown>) {
|
||||
return request<unknown, WalletRechargeImportExportTaskResponse>({
|
||||
url: "/wallet/wallet-recharge-import/export-async",
|
||||
method: "post",
|
||||
data: query,
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
import request from "/@/utils/request"
|
||||
import type { AsyncTaskProgress } from "/@/api/asyncTask"
|
||||
|
||||
/** 钱包流水异步导出提交接口的统一响应。 */
|
||||
export interface WalletTransactionExportTaskResponse {
|
||||
/** 后端创建的初始异步任务进度。 */
|
||||
data: AsyncTaskProgress
|
||||
/** 后端返回的任务提交提示。 */
|
||||
msg?: string
|
||||
}
|
||||
|
||||
/** 流水备注的前后端统一字符上限,与 wallet_transaction.remark 字段长度保持一致。 */
|
||||
export const WALLET_TRANSACTION_REMARK_MAX_LENGTH = 256
|
||||
@@ -138,6 +147,40 @@ export function tkTotal(data: any) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 提交充值记录异步分片导出任务。 */
|
||||
export function submitCzExport(query: Record<string, unknown>) {
|
||||
return request<unknown, WalletTransactionExportTaskResponse>({ url: "/wallet/wallet-transaction/export-cz-async", method: "post", data: query })
|
||||
}
|
||||
|
||||
/** 提交记账扣款异步分片导出任务。 */
|
||||
export function submitJzExport(query: Record<string, unknown>) {
|
||||
return request<unknown, WalletTransactionExportTaskResponse>({ url: "/wallet/wallet-transaction/export-jz-async", method: "post", data: query })
|
||||
}
|
||||
|
||||
/** 提交消费记录异步分片导出任务。 */
|
||||
export function submitXfExport(query: Record<string, unknown>) {
|
||||
return request<unknown, WalletTransactionExportTaskResponse>({ url: "/wallet/wallet-transaction/export-xf-async", method: "post", data: query })
|
||||
}
|
||||
|
||||
/** 提交退款记录异步分片导出任务。 */
|
||||
export function submitTkExport(query: Record<string, unknown>) {
|
||||
return request<unknown, WalletTransactionExportTaskResponse>({ url: "/wallet/wallet-transaction/export-tk-async", method: "post", data: query })
|
||||
}
|
||||
|
||||
/** 提交食堂退还记录异步分片导出任务。 */
|
||||
export function submitThExport(query: Record<string, unknown>) {
|
||||
return request<unknown, WalletTransactionExportTaskResponse>({ url: "/wallet/wallet-transaction/export-th-async", method: "post", data: query })
|
||||
}
|
||||
|
||||
/** 提交个人充值统计异步分片导出任务。 */
|
||||
export function submitCzTotalPersonExport(query: Record<string, unknown>) {
|
||||
return request<unknown, WalletTransactionExportTaskResponse>({
|
||||
url: "/wallet/wallet-transaction/export-cz-total-person-async",
|
||||
method: "post",
|
||||
data: query,
|
||||
})
|
||||
}
|
||||
|
||||
export function updateBalance(obj?: Object) {
|
||||
return request({
|
||||
url: "/wallet/wallet-transaction/update-balance",
|
||||
|
||||
@@ -1,91 +1,74 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<DraggableDepTree
|
||||
ref="draggableDepTreeRef"
|
||||
placeholder="请输入部门名称"
|
||||
:show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode"
|
||||
switch-inactive-text="包含"
|
||||
switch-active-text="不包含"
|
||||
:defaultTreeWidth="20"
|
||||
:nodeClickable="false"
|
||||
>
|
||||
<DraggableDepTree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含"
|
||||
:defaultTreeWidth="20" :nodeClickable="false">
|
||||
<template #default>
|
||||
<SearchPanel
|
||||
v-show="showSearch"
|
||||
ref="queryRef"
|
||||
:model="state.queryForm"
|
||||
:loading="Boolean(state.loading)"
|
||||
:search-text="$t('common.queryBtn')"
|
||||
:reset-text="$t('common.resetBtn')"
|
||||
@search="doSearch"
|
||||
@reset="resetQuery"
|
||||
>
|
||||
<SearchPanel v-show="showSearch" ref="queryRef" :model="state.queryForm"
|
||||
:loading="Boolean(state.loading)" :search-text="$t('common.queryBtn')"
|
||||
:reset-text="$t('common.resetBtn')" @search="doSearch" @reset="resetQuery">
|
||||
<el-form-item style="width: 250px" label="用餐时间段" prop="hourType">
|
||||
<dict-select v-model="state.queryForm.hourType" dict-code="daily_menu" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 250px" label="食堂" prop="merchantId">
|
||||
<select-mch v-model="state.queryForm.merchantId" deptMch :apiParams="{ parentIsNull: false, types: '1' }" />
|
||||
<select-mch v-model="state.queryForm.merchantId" deptMch
|
||||
:apiParams="{ parentIsNull: false, types: '1' }" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 450px" label="统计日期" prop="statisticsDateTimeList">
|
||||
<el-date-picker
|
||||
v-model="state.queryForm.statisticsDateTimeList"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="datetimerange"
|
||||
:default-time="dateRangeDefaultTime"
|
||||
:clearable="false"
|
||||
/>
|
||||
<el-form-item style="width: 500px" prop="statisticsDateTimeList">
|
||||
<template #label>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
{{ statisticsDateColumnLabel }}
|
||||
<el-tooltip :content="statisticsScopeDescription" placement="top">
|
||||
<el-icon class="cursor-help text-[var(--el-text-color-secondary)]">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="state.queryForm.statisticsDateTimeList"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange"
|
||||
:default-time="dateRangeDefaultTime" :clearable="false" />
|
||||
</el-form-item>
|
||||
</SearchPanel>
|
||||
<div class="flex justify-between items-center">
|
||||
<el-tabs class="flex-1" v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane v-for="tab in sourceTabs" :key="tab.name" :label="tab.label" :name="tab.name" />
|
||||
<el-tab-pane v-for="tab in sourceTabs" :key="tab.name" :label="tab.label"
|
||||
:name="tab.name" />
|
||||
</el-tabs>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'hard_canteenDeptTotal_export'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10 mr20"
|
||||
@queryTable="getDataList"
|
||||
/>
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="'hard_canteenDeptTotal_export'"
|
||||
@exportExcel="exportExcel" class="ml10 mr20" @queryTable="getDataList" />
|
||||
</div>
|
||||
<el-table
|
||||
:data="displayRows"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
row-key="_statisticsRowKey"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
:summary-method="getSummaries"
|
||||
:show-summary="true"
|
||||
:row-class-name="statisticsRowClassName"
|
||||
@sort-change="sortChangeHandle"
|
||||
>
|
||||
<el-alert class="mb-3" :title="statisticsScopeDescription" type="info"
|
||||
:closable="false" show-icon />
|
||||
<el-table :data="displayRows" v-loading="state.loading" border row-key="_statisticsRowKey"
|
||||
:cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
|
||||
:summary-method="getSummaries" :show-summary="true" :row-class-name="statisticsRowClassName"
|
||||
@sort-change="sortChangeHandle">
|
||||
<el-table-column type="expand" width="50">
|
||||
<template #default="{ row }">
|
||||
<div class="mx-[50px] my-2">
|
||||
<div class="flex items-center gap-2 mb-2 text-sm text-[var(--el-text-color-secondary)]">
|
||||
<div
|
||||
class="flex items-center gap-2 mb-2 text-sm text-[var(--el-text-color-secondary)]">
|
||||
<span class="font-medium text-[var(--el-text-color-primary)]">部门餐段明细</span>
|
||||
<span>{{ rowContext(row) }}</span>
|
||||
</div>
|
||||
<el-table
|
||||
:data="displayChildRows(row)"
|
||||
border
|
||||
row-key="_statisticsRowKey"
|
||||
<el-table :data="displayChildRows(row)" border row-key="_statisticsRowKey"
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
:summary-method="getSummaries"
|
||||
:show-summary="true"
|
||||
:row-class-name="statisticsRowClassName"
|
||||
>
|
||||
:header-cell-style="tableStyle.headerCellStyle" :summary-method="getSummaries"
|
||||
:show-summary="true" :row-class-name="statisticsRowClassName">
|
||||
<el-table-column type="index" label="#" width="50" align="center" />
|
||||
<el-table-column prop="userDeptName" align="center" label="部门" width="200" />
|
||||
<el-table-column label="统计维度" min-width="140">
|
||||
<template #default="{ row: child }">
|
||||
<div class="flex items-center gap-2" :class="dimensionIndentClass(child)">
|
||||
<el-icon v-if="isSourceLevel(child)" class="text-[var(--el-text-color-secondary)]"><Right /></el-icon>
|
||||
<span :class="isTotalLevel(child) ? 'font-semibold' : ''">{{ statisticsLevelName(child) }}</span>
|
||||
<div class="flex items-center gap-2"
|
||||
:class="dimensionIndentClass(child)">
|
||||
<el-icon v-if="isSourceLevel(child)"
|
||||
class="text-[var(--el-text-color-secondary)]">
|
||||
<Right />
|
||||
</el-icon>
|
||||
<span :class="isTotalLevel(child) ? 'font-semibold' : ''">{{
|
||||
statisticsLevelName(child) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -95,36 +78,42 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源口径" width="150" align="center">
|
||||
<template #default="{ row: child }"
|
||||
><el-tag size="small" effect="plain" :type="child.saleSource ? sourceTagType(child.saleSource) : 'info'">{{
|
||||
child.saleSource ? child.saleSourceName || sourceName(child.saleSource) : aggregateSourceName
|
||||
}}</el-tag></template
|
||||
>
|
||||
<template #default="{ row: child }"><el-tag size="small" effect="plain"
|
||||
:type="child.saleSource ? sourceTagType(child.saleSource) : 'info'">{{
|
||||
child.saleSource ? child.saleSourceName ||
|
||||
sourceName(child.saleSource) : aggregateSourceName
|
||||
}}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易人数" width="120" align="center" prop="payUserCount">
|
||||
<el-table-column :label="peopleColumnLabel" width="120" align="center"
|
||||
prop="payUserCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.payUserCount || 0 }}人 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易次数" width="120" align="center" prop="timeCount">
|
||||
<el-table-column :label="orderColumnLabel" width="120" align="center"
|
||||
prop="timeCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}次 </span>
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}单 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售出商品数量" width="140" align="center" prop="payCount">
|
||||
<el-table-column :label="quantityColumnLabel" width="140" align="center"
|
||||
prop="payCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-success"> {{ row.payCount || 0 }}份 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="130" prop="totalPrice" align="center">
|
||||
<el-table-column :label="amountColumnLabel" width="130" prop="totalPrice"
|
||||
align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ displayPrice(row.totalPrice) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="220">
|
||||
<template #default="{ row }">
|
||||
<el-button icon="Tickets" text type="primary" @click="doOpenDialog(row)"> 购买人员 </el-button>
|
||||
<el-button icon="Tickets" text type="primary" @click="openProductDialog(row)"> 菜品详情 </el-button>
|
||||
<el-button icon="Tickets" text type="primary"
|
||||
@click="doOpenDialog(row)"> 订单明细 </el-button>
|
||||
<el-button icon="Tickets" text type="primary"
|
||||
@click="openProductDialog(row)"> 菜品详情 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -132,22 +121,27 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="统计日期" align="center" width="130" prop="createTime"
|
||||
><template #default="{ row }">{{ parseTime(row.createTime, "{y}-{m}-{d}") }}</template></el-table-column
|
||||
>
|
||||
<el-table-column :label="statisticsDateColumnLabel" align="center" width="130"
|
||||
prop="createTime"><template #default="{ row }">{{ parseTime(row.createTime, "{y}-{m}-{d}")
|
||||
}}</template></el-table-column>
|
||||
<el-table-column label="食堂" align="center" width="200" prop="mchName" />
|
||||
<el-table-column label="统计维度" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-2" :class="dimensionIndentClass(row)">
|
||||
<el-icon v-if="isSourceLevel(row)" class="text-[var(--el-text-color-secondary)]"><Right /></el-icon>
|
||||
<span :class="isTotalLevel(row) ? 'font-semibold' : ''">{{ statisticsLevelName(row) }}</span>
|
||||
<el-icon v-if="isSourceLevel(row)" class="text-[var(--el-text-color-secondary)]">
|
||||
<Right />
|
||||
</el-icon>
|
||||
<span :class="isTotalLevel(row) ? 'font-semibold' : ''">{{ statisticsLevelName(row)
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源口径" align="center" width="150" prop="saleSourceName">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" effect="plain" :type="row.saleSource ? sourceTagType(row.saleSource) : 'info'">
|
||||
{{ row.saleSource ? row.saleSourceName || sourceName(row.saleSource) : aggregateSourceName }}
|
||||
<el-tag size="small" effect="plain"
|
||||
:type="row.saleSource ? sourceTagType(row.saleSource) : 'info'">
|
||||
{{ row.saleSource ? row.saleSourceName || sourceName(row.saleSource) :
|
||||
aggregateSourceName }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -156,30 +150,32 @@
|
||||
<span class="color-danger"> {{ row.deptCount || 0 }}部门 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易人数" width="120" align="center" prop="payUserCount">
|
||||
<el-table-column :label="peopleColumnLabel" width="120" align="center" prop="payUserCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.payUserCount || 0 }}人 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易次数" width="120" align="center" prop="timeCount">
|
||||
<el-table-column :label="orderColumnLabel" width="120" align="center" prop="timeCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}次 </span>
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}单 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售出商品数量" width="140" align="center" prop="payCount">
|
||||
<el-table-column :label="quantityColumnLabel" width="140" align="center" prop="payCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-success"> {{ row.payCount || 0 }}份 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="130" prop="totalPrice" align="center">
|
||||
<el-table-column :label="amountColumnLabel" width="130" prop="totalPrice" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ displayPrice(row.totalPrice) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="220">
|
||||
<template #default="{ row }">
|
||||
<el-button icon="Tickets" text type="primary" @click="doOpenDialog(row)"> 购买人员 </el-button>
|
||||
<el-button icon="Tickets" text type="primary" @click="openProductDialog(row)"> 菜品详情 </el-button>
|
||||
<el-button icon="Tickets" text type="primary" @click="doOpenDialog(row)"> 订单明细
|
||||
</el-button>
|
||||
<el-button icon="Tickets" text type="primary" @click="openProductDialog(row)"> 菜品详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -187,12 +183,8 @@
|
||||
</DraggableDepTree>
|
||||
<dept-person-total-table ref="deptPersonTotalTableRef" />
|
||||
<product-total-table ref="productTotalTableRef" />
|
||||
<async-task-progress-component
|
||||
ref="exportProgressRef"
|
||||
:config="exportTaskConfig"
|
||||
@success="handleExportSuccess"
|
||||
@failure="handleExportFailure"
|
||||
/>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig"
|
||||
@success="handleExportSuccess" @failure="handleExportFailure" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -204,7 +196,7 @@ import { displayPrice } from "/@/utils/formatMoney"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { useMessage } from "/@/hooks/message"
|
||||
import { parseTime } from "/@/utils/formatTime"
|
||||
import { Right } from "@element-plus/icons-vue"
|
||||
import { QuestionFilled, Right } from "@element-plus/icons-vue"
|
||||
import type { AsyncTaskPollingConfig, AsyncTaskProgress } from "/@/api/asyncTask"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { downBlobFile } from "/@/utils/other"
|
||||
@@ -260,6 +252,39 @@ const aggregateSourceName = computed(() =>
|
||||
activeName.value === "all" ? "线下 + 预约" : sourceTabs.find((tab) => tab.name === activeName.value)?.label || "当前来源"
|
||||
)
|
||||
|
||||
/** 当前是否只查看预约核销来源,用于切换日期与四项指标的业务文案。 */
|
||||
const isReservationSource = computed(() => activeName.value === hardTotalApi.SALE_SOURCE.RESERVATION_VERIFIED)
|
||||
|
||||
/** 日期列随销售来源展示真实业务发生时间,全部来源同时包含支付日期和核销日期。 */
|
||||
const statisticsDateColumnLabel = computed(() => {
|
||||
if (isReservationSource.value) return "核销日期"
|
||||
if (activeName.value === hardTotalApi.SALE_SOURCE.OFFLINE_PURCHASE) return "支付日期"
|
||||
return "支付/核销日期"
|
||||
})
|
||||
|
||||
/** 预约核销页的人数标题,其他来源保留通用交易名称。 */
|
||||
const peopleColumnLabel = computed(() => (isReservationSource.value ? "核销人数" : "交易人数"))
|
||||
|
||||
/** 预约核销页的订单标题,其他来源保留通用交易名称。 */
|
||||
const orderColumnLabel = computed(() => (isReservationSource.value ? "核销订单" : "交易订单"))
|
||||
|
||||
/** 预约核销页的份数标题,突出页面展示的是扣除退款后的实际结果。 */
|
||||
const quantityColumnLabel = computed(() => (isReservationSource.value ? "实际核销份数" : "售出份数"))
|
||||
|
||||
/** 预约核销页的金额标题,突出页面展示的是扣除退款后的实际结果。 */
|
||||
const amountColumnLabel = computed(() => (isReservationSource.value ? "实际核销金额" : "交易金额"))
|
||||
|
||||
/** 当前标签页面向业务人员的统计说明,明确日期依据和退款扣减方式。 */
|
||||
const statisticsScopeDescription = computed(() => {
|
||||
if (isReservationSource.value) {
|
||||
return "这里统计已经完成取餐核销的预约,日期按实际核销当天计算;发生退款时,只扣除实际退款的菜品份数和金额。"
|
||||
}
|
||||
if (activeName.value === hardTotalApi.SALE_SOURCE.OFFLINE_PURCHASE) {
|
||||
return "这里统计现场购买并已付款的订单,日期按付款当天计算。"
|
||||
}
|
||||
return "这里合并显示现场购买和预约核销。现场购买按付款日期统计,预约订餐按实际核销日期统计;预约退款只扣除实际退款的菜品份数和金额。"
|
||||
})
|
||||
|
||||
// table hook
|
||||
const { sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
@@ -432,8 +457,8 @@ const getSummaries = (param: any) => {
|
||||
const totalData = data.some((row: any) => row.statisticsLevel === hardTotalApi.STATISTICS_LEVEL.DATE_TOTAL)
|
||||
? data.filter((row: any) => row.statisticsLevel === hardTotalApi.STATISTICS_LEVEL.DATE_TOTAL)
|
||||
: data.some((row: any) => row.statisticsLevel === hardTotalApi.STATISTICS_LEVEL.HOUR_TOTAL)
|
||||
? data.filter((row: any) => row.statisticsLevel === hardTotalApi.STATISTICS_LEVEL.HOUR_TOTAL)
|
||||
: data
|
||||
? data.filter((row: any) => row.statisticsLevel === hardTotalApi.STATISTICS_LEVEL.HOUR_TOTAL)
|
||||
: data
|
||||
columns.forEach((i: any, index: number) => {
|
||||
if (totalColumns.includes(i.property)) {
|
||||
const total = totalData
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
<SearchPanel v-show="showSearch" ref="queryRef" :model="state.queryForm"
|
||||
:loading="Boolean(state.loading)" :search-text="$t('common.queryBtn')"
|
||||
:reset-text="$t('common.resetBtn')" @search="doSearch" @reset="resetQuery">
|
||||
<el-form-item style="width: 450px" label="统计日期" prop="statisticsDateTimeList">
|
||||
<el-form-item style="width: 500px" prop="statisticsDateTimeList">
|
||||
<template #label>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
{{ statisticsDateColumnLabel }}
|
||||
<el-tooltip :content="statisticsScopeDescription" placement="top">
|
||||
<el-icon class="cursor-help text-[var(--el-text-color-secondary)]">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="state.queryForm.statisticsDateTimeList"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange"
|
||||
:default-time="dateRangeDefaultTime" :clearable="false" />
|
||||
@@ -26,9 +36,10 @@
|
||||
<el-tab-pane v-for="tab in sourceTabs" :key="tab.name" :label="tab.label"
|
||||
:name="tab.name" />
|
||||
</el-tabs>
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="canExport"
|
||||
@exportExcel="exportExcel" class="ml10 mr20" @queryTable="getDataList" />
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="canExport" @exportExcel="exportExcel"
|
||||
class="ml10 mr20" @queryTable="getDataList" />
|
||||
</div>
|
||||
<el-alert class="mb-3" :title="statisticsScopeDescription" type="info" :closable="false" show-icon />
|
||||
<el-table :data="displayRows" v-loading="state.loading" border row-key="_statisticsRowKey"
|
||||
:cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
|
||||
:summary-method="getSummaries" :show-summary="true" :row-class-name="statisticsRowClassName"
|
||||
@@ -49,22 +60,22 @@
|
||||
:header-cell-style="tableStyle.headerCellStyle" row-class-name="warning-row">
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="userDeptName" align="center" label="部门" width="200" />
|
||||
<el-table-column label="交易人数" width="150" align="center" prop="payUserCount">
|
||||
<el-table-column :label="peopleColumnLabel" width="150" align="center" prop="payUserCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.payUserCount || 0 }}人 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易次数" width="150" align="center" prop="timeCount">
|
||||
<el-table-column :label="orderColumnLabel" width="150" align="center" prop="timeCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}次 </span>
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}单 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售出商品数量" width="150" align="center" prop="payCount">
|
||||
<el-table-column :label="quantityColumnLabel" width="150" align="center" prop="payCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-success"> {{ row.payCount || 0 }}份 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="150" prop="totalPrice" align="center">
|
||||
<el-table-column :label="amountColumnLabel" width="150" prop="totalPrice" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ displayPrice(row.totalPrice) }}</span>
|
||||
</template>
|
||||
@@ -75,7 +86,7 @@
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="统计日期" align="center" width="130" prop="createTime">
|
||||
<el-table-column :label="statisticsDateColumnLabel" align="center" width="130" prop="createTime">
|
||||
<template #default="{ row }">
|
||||
<span>{{ parseTime(row.createTime, "{y}-{m}-{d}") }}</span>
|
||||
</template>
|
||||
@@ -106,22 +117,22 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易人数" width="150" align="center" prop="payUserCount">
|
||||
<el-table-column :label="peopleColumnLabel" width="150" align="center" prop="payUserCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.payUserCount || 0 }}人 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易次数" width="150" align="center" prop="timeCount">
|
||||
<el-table-column :label="orderColumnLabel" width="150" align="center" prop="timeCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}次 </span>
|
||||
<span class="color-primary"> {{ row.timeCount || 0 }}单 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="售出商品数量" width="150" align="center" prop="payCount">
|
||||
<el-table-column :label="quantityColumnLabel" width="150" align="center" prop="payCount">
|
||||
<template #default="{ row }">
|
||||
<span class="color-success"> {{ row.payCount || 0 }}份 </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="150" prop="totalPrice" align="center">
|
||||
<el-table-column :label="amountColumnLabel" width="150" prop="totalPrice" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ displayPrice(row.totalPrice) }}</span>
|
||||
</template>
|
||||
@@ -138,15 +149,11 @@
|
||||
v-bind="state.pagination" />
|
||||
</template>
|
||||
</DraggableDepTree>
|
||||
</div>
|
||||
<product-total-table ref="productTotalTableRef" />
|
||||
<async-task-progress-component
|
||||
ref="exportProgressRef"
|
||||
:config="exportTaskConfig"
|
||||
@success="handleExportSuccess"
|
||||
@failure="handleExportFailure"
|
||||
/>
|
||||
</div>
|
||||
<product-total-table ref="productTotalTableRef" />
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig" @success="handleExportSuccess"
|
||||
@failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="canteen-product-appt-total">
|
||||
@@ -156,7 +163,7 @@ import { displayPrice } from "/@/utils/formatMoney"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { useMessage } from "/@/hooks/message"
|
||||
import { parseTime } from "/@/utils/formatTime"
|
||||
import { Right } from "@element-plus/icons-vue"
|
||||
import { QuestionFilled, Right } from "@element-plus/icons-vue"
|
||||
import { auths } from "/@/utils/authFunction"
|
||||
import type { AsyncTaskPollingConfig, AsyncTaskProgress } from "/@/api/asyncTask"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
@@ -227,6 +234,39 @@ const displayRows = computed(() =>
|
||||
|
||||
const aggregateSourceName = computed(() => (activeName.value === "all" ? "线下 + 预约" : sourceTabs.find((tab) => tab.name === activeName.value)?.label || "当前来源"))
|
||||
|
||||
/** 当前是否只查看预约核销,用于切换日期和统计指标的业务名称。 */
|
||||
const isReservationSource = computed(() => activeName.value === hardTotalApi.SALE_SOURCE.RESERVATION_VERIFIED)
|
||||
|
||||
/** 日期标题随来源展示付款日期或实际核销日期。 */
|
||||
const statisticsDateColumnLabel = computed(() => {
|
||||
if (isReservationSource.value) return "核销日期"
|
||||
if (activeName.value === hardTotalApi.SALE_SOURCE.OFFLINE_PURCHASE) return "支付日期"
|
||||
return "支付/核销日期"
|
||||
})
|
||||
|
||||
/** 预约核销页的人数标题,其他来源保留通用交易名称。 */
|
||||
const peopleColumnLabel = computed(() => (isReservationSource.value ? "核销人数" : "交易人数"))
|
||||
|
||||
/** 预约核销页的订单标题,其他来源保留通用交易名称。 */
|
||||
const orderColumnLabel = computed(() => (isReservationSource.value ? "核销订单" : "交易订单"))
|
||||
|
||||
/** 预约核销页的份数标题,突出页面展示的是扣除退款后的实际结果。 */
|
||||
const quantityColumnLabel = computed(() => (isReservationSource.value ? "实际核销份数" : "售出份数"))
|
||||
|
||||
/** 预约核销页的金额标题,突出页面展示的是扣除退款后的实际结果。 */
|
||||
const amountColumnLabel = computed(() => (isReservationSource.value ? "实际核销金额" : "交易金额"))
|
||||
|
||||
/** 当前标签页面向业务人员的统计说明,明确日期依据和退款扣减方式。 */
|
||||
const statisticsScopeDescription = computed(() => {
|
||||
if (isReservationSource.value) {
|
||||
return "这里统计已经完成取餐核销的预约,日期按实际核销当天计算;发生退款时,只扣除实际退款的菜品份数和金额。"
|
||||
}
|
||||
if (activeName.value === hardTotalApi.SALE_SOURCE.OFFLINE_PURCHASE) {
|
||||
return "这里统计现场购买并已付款的订单,日期按付款当天计算。"
|
||||
}
|
||||
return "这里合并显示现场购买和预约核销。现场购买按付款日期统计,预约订餐按实际核销日期统计;预约退款只扣除实际退款的菜品份数和金额。"
|
||||
})
|
||||
|
||||
/** 按当前筛选条件提交按日期分批、流式写入的异步导出任务。 */
|
||||
const exportExcel = async () => {
|
||||
if (exportSubmitting.value) {
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
|
||||
## 功能边界
|
||||
|
||||
`index.vue` 展示“用户 × 用餐日期”的食堂预约汇总。查询的用户主数据来自 UPMS 当前操作者的数据权限范围,预约订单来自 Hardware;因此即使用户当天没有订餐,只要处于权限范围内,也可能返回“未订餐”行。
|
||||
`index.vue` 展示“用户 × 统计日期”的食堂预约汇总。查询的用户主数据来自 UPMS 当前操作者的数据权限范围,预约订单来自 Hardware;因此即使用户当天没有订餐,只要处于权限范围内,也可能返回“未订餐”行。
|
||||
|
||||
## 日期、核销与退款口径
|
||||
|
||||
- 查询“已核销”时,只统计 `hard_order.order_status = 20` 的单笔订单,日期取 `verification_time`;不能再按用户整行是否全部核销来排除其中已经核销的订单。
|
||||
- 查询“待核销”时,只统计 `hard_order.order_status = 5` 的单笔订单,日期取预约用餐日期;“部分核销”仍表示同一用户、同一预约日期同时存在已核销和待核销订单。
|
||||
- 菜品份数和金额先按原购物车商品汇总,再扣除 `hard_after_sale.status = 50` 的完成退款。部分退款只扣已退款份数和金额,全额退款后净数量为零的商品、订单和对应订餐人数不再计入已订餐统计。
|
||||
- 人员页选择“已核销”后,其人数、订单数、菜品份数和金额应与商品统计页“预约核销”来源在相同日期、食堂、餐段及用户范围下保持一致。
|
||||
|
||||
本页面的用户身份及人脸筛选复用全局 `UserIdentityQuery` 组件,查询条件放在搜索面板的“更多条件”区域。新增或调整身份字段时,必须同时检查前端组件、预约接口类型、Hardware 查询 DTO、预约服务转交逻辑和 UPMS 用户 SQL。
|
||||
|
||||
@@ -22,7 +29,7 @@
|
||||
|
||||
## 数据链路
|
||||
|
||||
1. `src/views/hard/appt/canteen-user-appt-total/index.vue` 持有查询表单默认值,并通过 `user-identity-query` 写入身份及人脸条件。
|
||||
1. `src/views/hard/appt/canteen-user-appt-total/index.vue` 持有查询表单默认值,并通过 `user-identity-query` 写入身份及人脸条件;已核销筛选下表格日期展示为核销日期,订单明细额外保留原预约日期。
|
||||
2. `src/api/hard/hard-total-appt.ts` 将条件提交到列表、统计和导出接口;三者共用同一组身份字段。
|
||||
3. `HardCanteenUserApptQueryDto` 实现 `BaseIsIncludeTreeInterface` 和 `BaseUserIdentityQueryInterface`,承载部门包含关系及身份条件。
|
||||
4. Hardware 的预约列表服务和导出服务复制查询快照后,把身份条件转为 UPMS `UserDTO`,避免异步导出或分页预筛选丢字段。
|
||||
@@ -33,4 +40,4 @@
|
||||
- 新增身份类型或扩展字段时,先修改 `src/components/UserIdentityQuery` 的配置和类型,再同步 Java 查询接口、DTO、UPMS SQL 以及本页文档。
|
||||
- 修改查询字段名或匹配规则前,检查 PC 页面、统计抽屉、同步/异步导出、Feign `UserDTO` 和所有调用方;保持旧字段向后兼容。
|
||||
- 不要在页面内新建 Axios 实例,也不要把身份查询改为直接访问数据库或新增数据库字段。
|
||||
- 修改日期、部门或身份查询逻辑后,至少静态核对列表、统计、导出三条请求都携带同一查询快照;完整构建和测试按项目集中验证流程执行。
|
||||
- 修改日期、部门或身份查询逻辑后,至少静态核对列表、统计、导出三条请求都携带同一查询快照,并确认已核销使用核销日期、待核销及部分核销使用预约日期;完整构建和测试按项目集中验证流程执行。
|
||||
+13
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" title="预约统计" size="min(920px, 92vw)" destroy-on-close @closed="resetState">
|
||||
<div v-loading="loading" class="flex h-full min-h-0 flex-col">
|
||||
<el-alert class="mb-3 shrink-0" type="info" :closable="false" show-icon
|
||||
:title="statisticsScopeDescription" />
|
||||
<el-empty v-if="!loading && !hasStatistics" description="当前筛选条件暂无预约统计数据" />
|
||||
<el-tabs v-else v-model="activeTab" class="min-h-0 flex-1" stretch>
|
||||
<el-tab-pane label="按日统计" name="daily">
|
||||
@@ -57,6 +59,17 @@ const message = useMessage()
|
||||
/** 统计请求序号,用于忽略关闭抽屉或重新打开后迟到的旧响应。 */
|
||||
let requestSequence = 0
|
||||
|
||||
/** 当前状态筛选对应的日期、核销和完成退款净值口径。 */
|
||||
const statisticsScopeDescription = computed(() => {
|
||||
if (query.value?.verificationStatus === "VERIFIED") {
|
||||
return "已核销按实际核销日期统计,只计状态为 20 的订单;菜品份数和金额已扣除完成退款。"
|
||||
}
|
||||
if (query.value?.verificationStatus === "UNVERIFIED") {
|
||||
return "待核销按预约用餐日期统计,只计状态为 5 的订单;菜品份数和金额已扣除完成退款。"
|
||||
}
|
||||
return "当前按预约用餐日期统计;部分核销保留用户维度判断,菜品份数和金额均为完成退款后的净值。"
|
||||
})
|
||||
|
||||
/** 当前餐段展示选项;选中餐段筛选时只展示该餐段。 */
|
||||
const mealOptions = computed(() => {
|
||||
const options = (daily_menu.value || [])
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<el-descriptions-item label="去重用户">{{ summary.uniqueUserCount ?? 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约订单">{{ summary.orderCount ?? 0 }} 单</el-descriptions-item>
|
||||
<el-descriptions-item label="菜品份数">{{ summary.dishCount ?? 0 }} 份</el-descriptions-item>
|
||||
<el-descriptions-item label="预约总价">{{ displayPrice(summary.totalPrice ?? 0) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约净额">{{ displayPrice(summary.totalPrice ?? 0) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="border-l-2 border-primary pl-2 text-sm font-medium text-[var(--el-text-color-primary)]">餐段统计</div>
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
<SearchPanel ref="queryRef" :model="state.queryForm" title="预约查询" :loading="Boolean(state.loading)"
|
||||
:search-text="$t('common.queryBtn')" :reset-text="$t('common.resetBtn')"
|
||||
:hidden-active-count="advancedFilterCount" @search="doSearch" @reset="resetQuery">
|
||||
<el-form-item style="width: 450px" label="用餐日期" prop="apptUseDateList">
|
||||
<el-form-item style="width: 450px" prop="apptUseDateList">
|
||||
<template #label>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
统计日期
|
||||
<el-tooltip :content="statisticsScopeDescription" placement="top">
|
||||
<el-icon class="cursor-help text-[var(--el-text-color-secondary)]">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="state.queryForm.apptUseDateList" type="daterange"
|
||||
value-format="YYYY-MM-DD" :clearable="false" unlink-panels range-separator="至"
|
||||
start-placeholder="开始日期" end-placeholder="结束日期" @change="handleDateRangeChange" />
|
||||
@@ -56,6 +66,8 @@
|
||||
</right-toolbar>
|
||||
</div>
|
||||
|
||||
<el-alert class="mb-3" :title="statisticsScopeDescription" type="info" :closable="false" show-icon />
|
||||
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border :row-key="getRowKey"
|
||||
:cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle">
|
||||
<el-table-column label="用户信息" min-width="280" fixed="left">
|
||||
@@ -68,7 +80,8 @@
|
||||
<user-identity-info :user="row" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="apptUseDate" label="用餐日期" width="120" align="center" />
|
||||
<el-table-column prop="apptUseDate" :label="statisticsDateColumnLabel" width="120"
|
||||
align="center" />
|
||||
<el-table-column label="是否订餐" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.reservationStatus === 'RESERVED' ? 'success' : 'info'"
|
||||
@@ -77,7 +90,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当日订餐总价" width="130" align="right">
|
||||
<el-table-column :label="statisticsTotalPriceColumnLabel" width="130" align="right">
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
:class="row.totalPrice ? 'font-medium text-[var(--el-text-color-primary)]' : 'text-[var(--el-text-color-placeholder)]'">
|
||||
@@ -103,9 +116,9 @@
|
||||
<el-tag v-else type="info" size="small" effect="plain">未订餐</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<el-tooltip content="查看当天全部餐段预约" placement="top">
|
||||
<el-tooltip :content="reservationDetailTooltip" placement="top">
|
||||
<el-button class="shrink-0" link type="primary" :icon="Tickets"
|
||||
aria-label="查看当天全部餐段预约" @click="openReservationDetail(row)" />
|
||||
:aria-label="reservationDetailTooltip" @click="openReservationDetail(row)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -132,12 +145,16 @@
|
||||
:title="order.merchantName || '食堂信息未记录'">
|
||||
{{ order.merchantName || "食堂信息未记录" }}
|
||||
</div>
|
||||
<div v-if="shouldShowOriginalApptDate(row, order)"
|
||||
class="truncate text-xs text-[var(--el-text-color-secondary)]">
|
||||
预约日期:{{ order.apptUseDate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="min-w-0 flex-1 text-[var(--el-text-color-placeholder)]">-</span>
|
||||
<el-tooltip content="查看当天全部订餐内容" placement="top">
|
||||
<el-tooltip :content="reservationDetailTooltip" placement="top">
|
||||
<el-button class="shrink-0" link type="primary" :icon="Tickets"
|
||||
aria-label="查看当天全部订餐内容" @click="openReservationDetail(row)" />
|
||||
:aria-label="reservationDetailTooltip" @click="openReservationDetail(row)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -182,9 +199,12 @@
|
||||
<el-dialog v-model="detailDialog.visible" :title="detailDialogTitle" width="980px" destroy-on-close
|
||||
@closed="resetReservationDetailDialog">
|
||||
<el-table v-loading="detailDialog.loading" :data="detailDialog.orders" border max-height="520"
|
||||
empty-text="当天没有订餐记录" :cell-style="tableStyle.cellStyle"
|
||||
:empty-text="detailDialogEmptyText" :cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle">
|
||||
<el-table-column type="index" label="#" width="55" align="center" />
|
||||
<el-table-column prop="apptUseDate" label="预约日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.apptUseDate || "-" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="餐段" width="110" align="center">
|
||||
<template #default="{ row }">{{ getMealSegmentLabel(row.hourType) }}</template>
|
||||
</el-table-column>
|
||||
@@ -237,7 +257,7 @@ import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { downBlobFile } from "/@/utils/other"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import * as hardTotalApptApi from "/@/api/hard/hard-total-appt"
|
||||
import { Tickets } from "@element-plus/icons-vue"
|
||||
import { QuestionFilled, Tickets } from "@element-plus/icons-vue"
|
||||
import dayjs from "dayjs"
|
||||
import {
|
||||
createEmptyUserIdentityStudentQuery,
|
||||
@@ -300,7 +320,7 @@ interface UserReservationRow {
|
||||
studentInfo?: StudentIdentityInfo | null
|
||||
/** 老师身份扩展资料;没有老师身份时为空。 */
|
||||
teacherInfo?: TeacherIdentityInfo | null
|
||||
/** 当前查询的用餐日期,格式为 YYYY-MM-DD。 */
|
||||
/** 当前查询的统计日期;已核销筛选取核销日期,其他筛选取预约用餐日期。 */
|
||||
apptUseDate: string
|
||||
/** 当前筛选口径下是否存在有效预约。 */
|
||||
reservationStatus: ReservationStatus
|
||||
@@ -328,7 +348,7 @@ interface ReservationDetailDialogState {
|
||||
loading: boolean
|
||||
/** 当前查看的用户姓名。 */
|
||||
userName: string
|
||||
/** 当前查看的用餐日期。 */
|
||||
/** 当前查看的统计日期。 */
|
||||
apptUseDate: string
|
||||
/** 当前用户当天的完整有效预约明细。 */
|
||||
orders: ReservationOrder[]
|
||||
@@ -410,10 +430,44 @@ const advancedFilterCount = computed(() => {
|
||||
].filter((value) => (Array.isArray(value) ? value.length > 0 : value !== undefined && value !== null && value !== "")).length
|
||||
})
|
||||
|
||||
/** 当前详情弹窗标题,明确展示用户和用餐日期。 */
|
||||
/** 当前状态筛选对应的统计日期口径。 */
|
||||
const statisticsDateColumnLabel = computed(() =>
|
||||
state.queryForm.verificationStatus === "VERIFIED" ? "核销日期" : "用餐日期",
|
||||
)
|
||||
|
||||
/** 当前状态筛选对应的金额列口径,金额始终为完成退款后的净额。 */
|
||||
const statisticsTotalPriceColumnLabel = computed(() =>
|
||||
state.queryForm.verificationStatus === "VERIFIED" ? "实际核销金额" : "实际订餐金额",
|
||||
)
|
||||
|
||||
/** 当前查询状态面向业务人员的统计说明,明确日期依据和退款扣减方式。 */
|
||||
const statisticsScopeDescription = computed(() => {
|
||||
if (state.queryForm.verificationStatus === "VERIFIED") {
|
||||
return "这里显示已经完成取餐核销的预约,日期按实际核销当天计算;发生退款时,只扣除实际退款的菜品份数和金额。"
|
||||
}
|
||||
if (state.queryForm.verificationStatus === "UNVERIFIED") {
|
||||
return "这里显示已经付款但还没有核销的预约,日期按预约用餐当天计算;发生退款时,只扣除实际退款的菜品份数和金额。"
|
||||
}
|
||||
if (state.queryForm.verificationStatus === "PARTIAL") {
|
||||
return "这里显示同一天既有已核销订单、又有待核销订单的人员,日期按预约用餐当天计算;发生退款时,只扣除实际退款的菜品份数和金额。"
|
||||
}
|
||||
return "这里按预约用餐日期显示全部订餐情况;发生退款时,只扣除实际退款的菜品份数和金额。"
|
||||
})
|
||||
|
||||
/** 详情按钮说明;已核销筛选展示当前核销日和当前条件下的订单,避免误查预约日期。 */
|
||||
const reservationDetailTooltip = computed(() =>
|
||||
state.queryForm.verificationStatus === "VERIFIED" ? "查看该核销日当前条件下的预约" : "查看当天全部订餐内容",
|
||||
)
|
||||
|
||||
/** 详情空状态文案随统计日期口径变化。 */
|
||||
const detailDialogEmptyText = computed(() =>
|
||||
state.queryForm.verificationStatus === "VERIFIED" ? "该核销日没有预约核销记录" : "当天没有订餐记录",
|
||||
)
|
||||
|
||||
/** 当前详情弹窗标题,明确展示用户和统计日期口径。 */
|
||||
const detailDialogTitle = computed(() => {
|
||||
const context = [detailDialog.userName, detailDialog.apptUseDate].filter(Boolean).join(" · ")
|
||||
return context ? `${context} 订餐详情` : "订餐详情"
|
||||
return context ? `${context}(${statisticsDateColumnLabel.value})订餐详情` : "订餐详情"
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -494,6 +548,17 @@ function getSegmentTotalPrice(row: UserReservationRow, hourType: string): number
|
||||
return getSegmentOrders(row, hourType).reduce((total, order) => total + Number(order.totalPrice || 0), 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断已核销订单是否跨预约日期展示,帮助操作人员区分核销日和原预约日。
|
||||
*
|
||||
* @param row 当前用户统计日期行
|
||||
* @param order 当前行内的有效预约订单
|
||||
* @returns 已核销筛选且预约日期与核销日期不同时返回 true
|
||||
*/
|
||||
function shouldShowOriginalApptDate(row: UserReservationRow, order: ReservationOrder): boolean {
|
||||
return state.queryForm.verificationStatus === "VERIFIED" && Boolean(order.apptUseDate) && order.apptUseDate !== row.apptUseDate
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取餐段编码对应的展示文案。
|
||||
*
|
||||
@@ -535,7 +600,7 @@ function getVerificationTagType(status: VerificationStatus): "success" | "warnin
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用餐日期闭区间,前端规则与后端的两个自然月限制保持一致。
|
||||
* 校验统计日期闭区间,前端规则与后端的两个自然月限制保持一致。
|
||||
*
|
||||
* @param showMessage 校验失败时是否显示提示
|
||||
* @returns 日期范围完整、顺序正确且未超过两个月时返回 true
|
||||
@@ -544,16 +609,16 @@ function validateDateRange(showMessage = true): boolean {
|
||||
const dateRange = state.queryForm.apptUseDateList as string[] | undefined
|
||||
let errorMessage = ""
|
||||
if (!Array.isArray(dateRange) || dateRange.length !== 2 || !dateRange[0] || !dateRange[1]) {
|
||||
errorMessage = "请选择开始和结束用餐日期"
|
||||
errorMessage = "请选择开始和结束统计日期"
|
||||
} else {
|
||||
const startDate = dayjs(dateRange[0])
|
||||
const endDate = dayjs(dateRange[1])
|
||||
if (!startDate.isValid() || !endDate.isValid()) {
|
||||
errorMessage = "用餐日期格式不正确"
|
||||
errorMessage = "统计日期格式不正确"
|
||||
} else if (endDate.isBefore(startDate, "day")) {
|
||||
errorMessage = "用餐结束日期不能早于开始日期"
|
||||
errorMessage = "统计结束日期不能早于开始日期"
|
||||
} else if (endDate.isAfter(startDate.add(MAX_QUERY_MONTHS, "month"), "day")) {
|
||||
errorMessage = "用餐日期范围最多查询两个月"
|
||||
errorMessage = "统计日期范围最多查询两个月"
|
||||
}
|
||||
}
|
||||
if (errorMessage && showMessage) message.warning(errorMessage)
|
||||
@@ -617,17 +682,23 @@ function handleGetSelectNode(_curClickedNode: any, checkedTreeNodes: any[], isDe
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开用户当天完整预约详情,并忽略列表上的食堂、餐段和状态筛选。
|
||||
* 打开用户统计日期内的预约详情。已核销按当前核销日结果展示,其他状态查询原预约日完整记录。
|
||||
*
|
||||
* @param row 当前用户和用餐日期对应的列表行
|
||||
* @param row 当前用户和统计日期对应的列表行
|
||||
*/
|
||||
async function openReservationDetail(row: UserReservationRow) {
|
||||
const requestSequence = ++detailRequestSequence
|
||||
detailDialog.visible = true
|
||||
detailDialog.loading = true
|
||||
detailDialog.userName = row.name || row.userName || row.username || "未命名用户"
|
||||
detailDialog.apptUseDate = row.apptUseDate
|
||||
detailDialog.orders = []
|
||||
if (state.queryForm.verificationStatus === "VERIFIED") {
|
||||
// 行日期此时是核销日期,直接展示当前查询已返回的核销订单,避免详情接口按预约日期误查跨日订单。
|
||||
detailDialog.loading = false
|
||||
detailDialog.orders = [...row.orders]
|
||||
return
|
||||
}
|
||||
detailDialog.loading = true
|
||||
try {
|
||||
const response = await hardTotalApptApi.getCanteenUserReservationDetail({
|
||||
userId: row.userId,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<el-dialog title="钱包余额明细" v-model="visible" width="60%" :close-on-click-modal="false" draggable>
|
||||
<el-dialog title="钱包余额明细" v-model="visible" width="70%" :close-on-click-modal="false" draggable>
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle" @sort-change="sortChangeHandle">
|
||||
<el-table-column align="center" width="200" label="编号" prop="id" />
|
||||
<el-table-column align="center" width="200" label="钱包编号" prop="walletId" />
|
||||
<el-table-column align="center" width="200" label="交易类型" prop="title" />
|
||||
<el-table-column align="center" label="交易金额" prop="price">
|
||||
<el-table-column align="center" width="200" label="交易金额" prop="price">
|
||||
<template #default="{ row }"> {{ displayPrice(row.price) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="钱包余额" prop="balance">
|
||||
<el-table-column align="center" width="200" label="钱包余额" prop="balance">
|
||||
<template #default="{ row }"> {{ displayPrice(row.balance) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="备注" prop="remark" min-width="180" show-overflow-tooltip />
|
||||
|
||||
@@ -106,6 +106,8 @@
|
||||
v-bind="state.pagination" />
|
||||
</DraggableDepTree>
|
||||
</div>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig"
|
||||
@success="handleExportSuccess" @failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -113,6 +115,8 @@
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import * as walletTransactionApi from "/@/api/wallet/wallet-transaction"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
isPage: false,
|
||||
@@ -133,7 +137,15 @@ const draggableDepTreeRef = ref()
|
||||
const tableChildrenMap = ref(new Map<string, any>())
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, postDownBlobFile, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 个人充值统计导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:user:export:cz-total",
|
||||
taskName: "个人充值统计导出",
|
||||
fallbackFileName: "个人充值统计.xlsx",
|
||||
submitTask: walletTransactionApi.submitCzTotalPersonExport,
|
||||
})
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -173,13 +185,9 @@ const handleGetSelectNode = (curClickedNode: any, checkedTreeNodes: any, isDeepT
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
/** 按当前统计筛选提交个人充值明细异步导出任务,金额继续以元传给后端。 */
|
||||
const exportExcel = () => {
|
||||
postDownBlobFile(
|
||||
"/wallet/wallet-transaction/export-cz-total-person",
|
||||
Object.assign(state.queryForm, { current: state.pagination?.current, size: state.pagination?.size }),
|
||||
"钱包充值统计.xlsx",
|
||||
)
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
// 多选事件
|
||||
|
||||
@@ -129,16 +129,20 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig"
|
||||
@success="handleExportSuccess" @failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemWalletRecharge">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import { pageCz, czTotal, delObjs } from "/@/api/wallet/wallet-transaction"
|
||||
import { pageCz, czTotal, delObjs, submitCzExport } from "/@/api/wallet/wallet-transaction"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
const showDrawer = ref(false)
|
||||
const balanceStatisticsLoading = ref(false)
|
||||
@@ -176,7 +180,15 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
})
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, postDownBlobFile, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 充值记录导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:user:export:cz",
|
||||
taskName: "充值记录导出",
|
||||
fallbackFileName: "充值记录.xlsx",
|
||||
submitTask: submitCzExport,
|
||||
})
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -231,13 +243,9 @@ const getSummaries = (param: any) => {
|
||||
return totalRow
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
/** 按当前筛选条件提交充值记录异步导出任务,不携带列表页码。 */
|
||||
const exportExcel = () => {
|
||||
postDownBlobFile(
|
||||
"/wallet/wallet-transaction/export-cz",
|
||||
Object.assign(state.queryForm, { current: state.pagination?.current, size: state.pagination?.size }),
|
||||
"充值记录.xlsx",
|
||||
)
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
// 多选事件
|
||||
|
||||
@@ -100,16 +100,20 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig"
|
||||
@success="handleExportSuccess" @failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemWalletRecharge">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { pageJz, jzTotal } from "/@/api/wallet/wallet-transaction"
|
||||
import { pageJz, jzTotal, submitJzExport } from "/@/api/wallet/wallet-transaction"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
/** 所有钱包流水列表共用的备注编辑表单。 */
|
||||
const TransactionRemarkForm = defineAsyncComponent(() => import("../components/transaction-remark-form.vue"))
|
||||
@@ -145,7 +149,15 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
})
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, postDownBlobFile, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 记账扣款导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:user:export:jz",
|
||||
taskName: "记账扣款导出",
|
||||
fallbackFileName: "记账扣款记录.xlsx",
|
||||
submitTask: submitJzExport,
|
||||
})
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -185,13 +197,9 @@ const viewBalanceStatistics = async () => {
|
||||
}
|
||||
|
||||
|
||||
// 导出excel
|
||||
/** 按当前筛选条件提交记账扣款异步导出任务,不携带列表页码。 */
|
||||
const exportExcel = () => {
|
||||
postDownBlobFile(
|
||||
"/wallet/wallet-transaction/export-jz",
|
||||
Object.assign(state.queryForm, { current: state.pagination?.current, size: state.pagination?.size }),
|
||||
"消费记录.xlsx",
|
||||
)
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
// 多选事件
|
||||
|
||||
@@ -125,14 +125,13 @@
|
||||
<script setup lang="ts" name="systemWalletManage">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import { fetchListJoinQb, delObjs, qbTotal, submitQbExport } from "/@/api/wallet/wallet-manage"
|
||||
import type { AsyncTaskPollingConfig, AsyncTaskProgress } from "/@/api/asyncTask"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import { formatDate } from "/@/utils/formatTime"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { downBlobFile } from "/@/utils/other"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
const { wallet_type, yes_no_type } = useDict("wallet_type", "yes_no_type")
|
||||
// 定义变量内容
|
||||
@@ -148,18 +147,6 @@ const balanceStatisticsLoading = ref(false)
|
||||
const balanceStatistics = reactive<any>({
|
||||
|
||||
})
|
||||
/** 月度钱包记录异步导出进度弹窗引用,用于提交成功后启动统一轮询。 */
|
||||
const exportProgressRef = ref<InstanceType<typeof AsyncTaskProgressComponent>>()
|
||||
/** 月度钱包记录导出提交中的本地互斥状态,防止接口返回前连续创建重复任务。 */
|
||||
const exportSubmitting = ref(false)
|
||||
/** 月度钱包记录导出进度配置,与后端导出任务的 Redis 命名空间保持一致。 */
|
||||
const exportTaskConfig: AsyncTaskPollingConfig = {
|
||||
progressUrl: "/wallet/async-task/progress",
|
||||
redisKey: "wallet:manage:export:record",
|
||||
showDialogOnFinished: false,
|
||||
waitingMessage: "正在生成钱包记录导出文件,请耐心等待进度达到 100%",
|
||||
downloadButtonText: "下载 Excel",
|
||||
}
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
@@ -175,6 +162,14 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 月度钱包记录导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:manage:export:record",
|
||||
taskName: "钱包记录导出",
|
||||
fallbackFileName: "钱包记录.xlsx",
|
||||
submitTask: submitQbExport,
|
||||
})
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
@@ -213,45 +208,8 @@ const drawerClose = () => {
|
||||
}
|
||||
|
||||
/** 提交月度钱包记录异步导出任务,并将当前筛选条件作为独立快照交给后台处理。 */
|
||||
const exportExcel = async () => {
|
||||
if (exportSubmitting.value) return
|
||||
exportSubmitting.value = true
|
||||
try {
|
||||
const response = await submitQbExport({ ...state.queryForm })
|
||||
if (!response.data?.taskId) {
|
||||
useMessage().error("导出任务提交成功,但响应中缺少任务ID")
|
||||
return
|
||||
}
|
||||
useMessage().success(response.msg || "钱包记录导出任务已提交")
|
||||
exportProgressRef.value?.start(response.data)
|
||||
} catch (error: any) {
|
||||
useMessage().error(error?.msg || "钱包记录导出任务提交失败")
|
||||
} finally {
|
||||
exportSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 钱包记录导出成功后下载文件中心结果。
|
||||
*
|
||||
* @param progress 后端成功终态,必须包含结果文件地址
|
||||
*/
|
||||
const handleExportSuccess = async (progress: AsyncTaskProgress) => {
|
||||
if (!progress.resultFileUrl) {
|
||||
useMessage().error("导出完成,但未返回文件下载地址")
|
||||
return
|
||||
}
|
||||
useMessage().success(progress.message || "钱包记录导出完成")
|
||||
await downBlobFile(progress.resultFileUrl, {}, progress.resultFileName || "钱包记录.xlsx")
|
||||
}
|
||||
|
||||
/**
|
||||
* 钱包记录导出失败时展示后台任务的受控错误信息。
|
||||
*
|
||||
* @param progress 后端失败终态
|
||||
*/
|
||||
const handleExportFailure = (progress: AsyncTaskProgress) => {
|
||||
useMessage().error(progress.message || "钱包记录导出失败")
|
||||
const exportExcel = () => {
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
const getSummaries = (param: any) => {
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
</SearchPanel>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="'wallet_walletRechargeApply_export'"
|
||||
@exportExcel="exportExcel" class="ml10 mr20" style="float: right"
|
||||
@queryTable="getDataList"></right-toolbar>
|
||||
<right-toolbar v-model:showSearch="showSearch"
|
||||
:export="'wallet_walletRechargeImport_export'" @exportExcel="exportExcel"
|
||||
class="ml10 mr20" style="float: right" @queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
|
||||
@@ -105,6 +105,8 @@
|
||||
</template>
|
||||
</DraggableDepTree>
|
||||
</div>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig" @success="handleExportSuccess"
|
||||
@failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="wallet-recharge-water">
|
||||
@@ -113,7 +115,9 @@ import { displayPrice } from "/@/utils/formatMoney"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import * as walletRechargeImportApi from "/@/api/wallet/wallet-recharge-import"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
const { wallet_biz_recharge, wallet_type, check_status, wallet_transaction_tag } = useDict(
|
||||
"wallet_biz_recharge",
|
||||
@@ -144,7 +148,15 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
})
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, postDownBlobFile, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 充值扣款水单导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:user:export:recharge-water",
|
||||
taskName: "充值扣款水单导出",
|
||||
fallbackFileName: "充值扣款水单.xlsx",
|
||||
submitTask: walletRechargeImportApi.submitExport,
|
||||
})
|
||||
|
||||
// 恢复操作
|
||||
const handleRecover = async (id: string) => {
|
||||
@@ -174,9 +186,9 @@ const getSummaries = (param: any) => {
|
||||
return totalRow
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
/** 按当前筛选条件提交充值扣款水单异步导出任务,不传递页面多选 Ref。 */
|
||||
const exportExcel = () => {
|
||||
postDownBlobFile("/wallet/wallet-recharge-import/export", Object.assign(state.queryForm, { ids: selectObjs }), "充值/扣款流水.xlsx")
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
// 多选事件
|
||||
|
||||
@@ -99,17 +99,21 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig"
|
||||
@success="handleExportSuccess" @failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemWalletRecharge">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import { pageTh, thTotal } from "/@/api/wallet/wallet-transaction"
|
||||
import { pageTh, thTotal, submitThExport } from "/@/api/wallet/wallet-transaction"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import * as hardAfterSaleApi from "/@/api/hard/hard-after-sale"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
/** 所有钱包流水列表共用的备注编辑表单。 */
|
||||
const TransactionRemarkForm = defineAsyncComponent(() => import("../components/transaction-remark-form.vue"))
|
||||
@@ -147,7 +151,15 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
})
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, postDownBlobFile, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 食堂退还记录导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:user:export:th",
|
||||
taskName: "食堂退还记录导出",
|
||||
fallbackFileName: "食堂退还记录.xlsx",
|
||||
submitTask: submitThExport,
|
||||
})
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -194,13 +206,9 @@ const doRefund = async (row: any) => {
|
||||
push({ path: "/hard/hard-after-sale/order-refund", query: { id: data.orderId, type: "view" } })
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
/** 按当前筛选条件提交食堂退还记录异步导出任务,不携带列表页码。 */
|
||||
const exportExcel = () => {
|
||||
postDownBlobFile(
|
||||
"/wallet/wallet-transaction/export-th",
|
||||
Object.assign(state.queryForm, { current: state.pagination?.current, size: state.pagination?.size }),
|
||||
"退还各自食堂记录.xlsx",
|
||||
)
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
const getSummaries = (param: any) => {
|
||||
|
||||
@@ -109,17 +109,21 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig"
|
||||
@success="handleExportSuccess" @failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemWalletRecharge">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import { pageTk, tkTotal } from "/@/api/wallet/wallet-transaction"
|
||||
import { pageTk, tkTotal, submitTkExport } from "/@/api/wallet/wallet-transaction"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import * as hardAfterSaleApi from "/@/api/hard/hard-after-sale"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
const showDrawer = ref(false)
|
||||
const balanceStatisticsLoading = ref(false)
|
||||
@@ -158,7 +162,15 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
})
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, postDownBlobFile, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 退款记录导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:user:export:tk",
|
||||
taskName: "退款记录导出",
|
||||
fallbackFileName: "退款记录.xlsx",
|
||||
submitTask: submitTkExport,
|
||||
})
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -205,13 +217,9 @@ const doRefund = async (row: any) => {
|
||||
push({ path: "/hard/hard-after-sale/order-refund", query: { id: data.orderId, type: "view" } })
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
/** 按当前筛选条件提交退款记录异步导出任务,不携带列表页码。 */
|
||||
const exportExcel = () => {
|
||||
postDownBlobFile(
|
||||
"/wallet/wallet-transaction/export-tk",
|
||||
Object.assign(state.queryForm, { current: state.pagination?.current, size: state.pagination?.size }),
|
||||
"退款记录.xlsx",
|
||||
)
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
const getSummaries = (param: any) => {
|
||||
|
||||
@@ -115,17 +115,21 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
<async-task-progress-component ref="exportProgressRef" :config="exportTaskConfig"
|
||||
@success="handleExportSuccess" @failure="handleExportFailure" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemWalletRecharge">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table"
|
||||
import { pageXf, xfTotal } from "/@/api/wallet/wallet-transaction"
|
||||
import { pageXf, xfTotal, submitXfExport } from "/@/api/wallet/wallet-transaction"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||||
import { useDict } from "/@/hooks/dict"
|
||||
import { displayPrice } from "/@/utils/formatMoney"
|
||||
import { deptTree } from "/@/api/admin/dept"
|
||||
import AsyncTaskProgressComponent from "/@/components/AsyncTaskProgress.vue"
|
||||
import { createEmptyUserIdentityStudentQuery, createEmptyUserIdentityTeacherQuery } from "/@/components/UserIdentityQuery/types"
|
||||
import { useWalletUserExport } from "../composables/useWalletUserExport"
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
@@ -165,7 +169,15 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
})
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, postDownBlobFile, tableStyle } = useTable(state)
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state)
|
||||
|
||||
/** 消费记录导出复用统一提交互斥、进度轮询和文件下载流程。 */
|
||||
const { exportProgressRef, exportTaskConfig, submitExport, handleExportSuccess, handleExportFailure } = useWalletUserExport({
|
||||
redisKey: "wallet:user:export:xf",
|
||||
taskName: "消费记录导出",
|
||||
fallbackFileName: "消费记录.xlsx",
|
||||
submitTask: submitXfExport,
|
||||
})
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
@@ -203,13 +215,9 @@ const viewBalanceStatistics = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
/** 按当前筛选条件提交消费记录异步导出任务,不携带列表页码。 */
|
||||
const exportExcel = () => {
|
||||
postDownBlobFile(
|
||||
"/wallet/wallet-transaction/export-xf",
|
||||
Object.assign(state.queryForm, { current: state.pagination?.current, size: state.pagination?.size }),
|
||||
"消费记录.xlsx",
|
||||
)
|
||||
void submitExport({ ...state.queryForm })
|
||||
}
|
||||
|
||||
const doRefund = (row: any) => {
|
||||
|
||||
Reference in new issue
Block a user