| | |
| | | <nonFilteredFileExtension>xls</nonFilteredFileExtension> |
| | | <nonFilteredFileExtension>xlsx</nonFilteredFileExtension> |
| | | <nonFilteredFileExtension>zip</nonFilteredFileExtension> |
| | | <nonFilteredFileExtension>jar</nonFilteredFileExtension> |
| | | </nonFilteredFileExtensions> |
| | | </configuration> |
| | | </plugin> |
| | |
| | | "sys_user_role", |
| | | "sys_role_menu", |
| | | "sys_menu", |
| | | "sys_export_task", |
| | | "sys_pda_role_menu", |
| | | "sys_menu_pda", |
| | | "sys_matnr_role_menu", |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.vincent.rsf.framework.common.SpringUtils; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| New file |
| | |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.schedule.system.entity.ExportTask; |
| | | import com.vincent.rsf.schedule.system.service.ExportTaskService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.io.File; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | @Slf4j |
| | | @Component |
| | | public class ExportTaskCleanupSchedules { |
| | | |
| | | private final ExportTaskService exportTaskService; |
| | | |
| | | @Value("${rsf.export-task.cleanup-batch-size:200}") |
| | | private int cleanupBatchSize; |
| | | |
| | | public ExportTaskCleanupSchedules(ExportTaskService exportTaskService) { |
| | | this.exportTaskService = exportTaskService; |
| | | } |
| | | |
| | | @Scheduled( |
| | | initialDelayString = "${rsf.export-task.cleanup-initial-delay-ms:60000}", |
| | | fixedDelayString = "${rsf.export-task.cleanup-fixed-delay-ms:3600000}" |
| | | ) |
| | | public void cleanupExpiredExportTasks() { |
| | | List<ExportTask> expiredTasks = exportTaskService.list( |
| | | new LambdaQueryWrapper<ExportTask>() |
| | | .eq(ExportTask::getDeleted, 0) |
| | | .isNotNull(ExportTask::getExpireTime) |
| | | .lt(ExportTask::getExpireTime, new Date()) |
| | | .last("LIMIT " + Math.max(cleanupBatchSize, 1)) |
| | | ); |
| | | if (Cools.isEmpty(expiredTasks)) { |
| | | return; |
| | | } |
| | | |
| | | expiredTasks.forEach(this::deleteTaskFile); |
| | | exportTaskService.removeByIds(expiredTasks.stream().map(ExportTask::getId).toList()); |
| | | } |
| | | |
| | | private void deleteTaskFile(ExportTask task) { |
| | | if (task == null || Cools.isEmpty(task.getFilePath())) { |
| | | return; |
| | | } |
| | | try { |
| | | File file = new File(task.getFilePath()); |
| | | if (file.exists() && file.isFile() && !file.delete()) { |
| | | log.warn("导出任务文件删除失败, taskId={}, filePath={}", task.getId(), task.getFilePath()); |
| | | } |
| | | } catch (Exception error) { |
| | | log.warn( |
| | | "导出任务文件删除异常, taskId={}, filePath={}", |
| | | task.getId(), |
| | | Objects.toString(task.getFilePath(), ""), |
| | | error |
| | | ); |
| | | } |
| | | } |
| | | } |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | |
| | | package com.vincent.rsf.schedule.manager.schedules; |
| | | package com.vincent.rsf.schedule.schedules; |
| | | |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| New file |
| | |
| | | package com.vincent.rsf.schedule.system.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | @TableName("sys_export_task") |
| | | public class ExportTask implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty(value = "ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty(value = "文件路径") |
| | | private String filePath; |
| | | |
| | | @ApiModelProperty(value = "过期时间") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date expireTime; |
| | | |
| | | @ApiModelProperty(value = "删除标记") |
| | | private Integer deleted; |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.schedule.system.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.vincent.rsf.schedule.system.entity.ExportTask; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | @Mapper |
| | | @Repository |
| | | public interface ExportTaskMapper extends BaseMapper<ExportTask> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.schedule.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.schedule.system.entity.ExportTask; |
| | | |
| | | public interface ExportTaskService extends IService<ExportTask> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.schedule.system.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.schedule.system.entity.ExportTask; |
| | | import com.vincent.rsf.schedule.system.mapper.ExportTaskMapper; |
| | | import com.vincent.rsf.schedule.system.service.ExportTaskService; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | @Service("exportTaskService") |
| | | public class ExportTaskServiceImpl extends ServiceImpl<ExportTaskMapper, ExportTask> implements ExportTaskService { |
| | | } |
| | |
| | | file: |
| | | path: logs/@pom.artifactId@ |
| | | |
| | | rsf: |
| | | export-task: |
| | | cleanup-batch-size: 200 |
| | | cleanup-initial-delay-ms: 60000 |
| | | cleanup-fixed-delay-ms: 3600000 |
| | | |
| | | # 下位机配置 |
| | | wcs-slave: |
| | | agv: false |
| | |
| | | return Number.isFinite(numericValue) ? numericValue : fallback |
| | | } |
| | | |
| | | function normalizeIds(ids) { |
| | | if (Array.isArray(ids)) { |
| | | return ids |
| | | .map((id) => String(id).trim()) |
| | | .filter(Boolean) |
| | | .join(',') |
| | | } |
| | | if (ids === null || ids === undefined) { |
| | | return '' |
| | | } |
| | | return String(ids).trim() |
| | | } |
| | | |
| | | function filterParams(params = {}, ignoredKeys = []) { |
| | | return Object.fromEntries( |
| | | Object.entries(params) |
| | |
| | | url: `/stockStatistic/${id}` |
| | | }) |
| | | } |
| | | |
| | | export function fetchGetInStatisticItemMany(ids) { |
| | | return request.post({ |
| | | url: `/inStatisticItem/many/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchExportInStatisticItemReport(payload = {}, options = {}) { |
| | | return fetch(`${import.meta.env.VITE_API_URL}/inStatisticItem/export`, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | ...(options.headers || {}) |
| | | }, |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | |
| | | return Number.isFinite(numericValue) ? numericValue : fallback |
| | | } |
| | | |
| | | function normalizeIds(ids) { |
| | | if (Array.isArray(ids)) { |
| | | return ids |
| | | .map((id) => String(id).trim()) |
| | | .filter(Boolean) |
| | | .join(',') |
| | | } |
| | | if (ids === null || ids === undefined) { |
| | | return '' |
| | | } |
| | | return String(ids).trim() |
| | | } |
| | | |
| | | function filterParams(params = {}, ignoredKeys = []) { |
| | | return Object.fromEntries( |
| | | Object.entries(params) |
| | |
| | | params: buildInStatisticPageParams(params) |
| | | }) |
| | | } |
| | | |
| | | export function fetchGetInStatisticMany(ids) { |
| | | return request.post({ |
| | | url: `/inStatistic/many/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchExportInStatisticReport(payload = {}, options = {}) { |
| | | return fetch(`${import.meta.env.VITE_API_URL}/inStatistic/export`, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | ...(options.headers || {}) |
| | | }, |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | | |
| | | export function fetchCreateLocExportTask(payload = {}) { |
| | | return request.post({ |
| | | url: '/loc/export/async', |
| | | params: payload |
| | | }) |
| | | } |
| | | |
| | | export function fetchLocExportTask(taskId) { |
| | | return request.get({ |
| | | url: `/loc/export/task/${String(taskId).trim()}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchDownloadLocExportTask(taskId, options = {}) { |
| | | return fetch( |
| | | `${import.meta.env.VITE_API_URL}/loc/export/task/${String(taskId).trim()}/download`, |
| | | { |
| | | method: 'GET', |
| | | headers: { |
| | | ...(options.headers || {}) |
| | | } |
| | | } |
| | | ) |
| | | } |
| | |
| | | return Number.isFinite(numericValue) ? numericValue : fallback |
| | | } |
| | | |
| | | function normalizeIds(ids) { |
| | | if (Array.isArray(ids)) { |
| | | return ids |
| | | .map((id) => String(id).trim()) |
| | | .filter(Boolean) |
| | | .join(',') |
| | | } |
| | | if (ids === null || ids === undefined) { |
| | | return '' |
| | | } |
| | | return String(ids).trim() |
| | | } |
| | | |
| | | function filterParams(params = {}, ignoredKeys = []) { |
| | | return Object.fromEntries( |
| | | Object.entries(params) |
| | |
| | | params: buildOutStatisticItemPageParams(params) |
| | | }) |
| | | } |
| | | |
| | | export function fetchGetOutStatisticItemMany(ids) { |
| | | return request.post({ |
| | | url: `/outStatisticItem/many/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchExportOutStatisticItemReport(payload = {}, options = {}) { |
| | | return fetch(`${import.meta.env.VITE_API_URL}/outStatisticItem/export`, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | ...(options.headers || {}) |
| | | }, |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | |
| | | return Number.isFinite(numericValue) ? numericValue : fallback |
| | | } |
| | | |
| | | function normalizeIds(ids) { |
| | | if (Array.isArray(ids)) { |
| | | return ids |
| | | .map((id) => String(id).trim()) |
| | | .filter(Boolean) |
| | | .join(',') |
| | | } |
| | | if (ids === null || ids === undefined) { |
| | | return '' |
| | | } |
| | | return String(ids).trim() |
| | | } |
| | | |
| | | function filterParams(params = {}, ignoredKeys = []) { |
| | | return Object.fromEntries( |
| | | Object.entries(params) |
| | |
| | | params: buildOutStatisticPageParams(params) |
| | | }) |
| | | } |
| | | |
| | | export function fetchGetOutStatisticMany(ids) { |
| | | return request.post({ |
| | | url: `/outStatistic/many/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchExportOutStatisticReport(payload = {}, options = {}) { |
| | | return fetch(`${import.meta.env.VITE_API_URL}/outStatistic/export`, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | ...(options.headers || {}) |
| | | }, |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | |
| | | return typeof value === 'string' ? value.trim() : value |
| | | } |
| | | |
| | | function normalizeIds(ids) { |
| | | if (Array.isArray(ids)) { |
| | | return ids |
| | | .map((id) => String(id).trim()) |
| | | .filter(Boolean) |
| | | .join(',') |
| | | } |
| | | if (ids === null || ids === undefined) { |
| | | return '' |
| | | } |
| | | return String(ids).trim() |
| | | } |
| | | |
| | | export function buildStatisticCountPageParams(params = {}) { |
| | | const entries = Object.entries(params).filter(([key, value]) => { |
| | | if (['current', 'pageSize', 'size'].includes(key)) { |
| | |
| | | params: buildStatisticCountPageParams(params) |
| | | }) |
| | | } |
| | | |
| | | export function fetchGetStatisticCountMany(ids) { |
| | | return request.post({ |
| | | url: `/statistic/num/many/${normalizeIds(ids)}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchExportStatisticCountReport(payload = {}, options = {}) { |
| | | return fetch(`${import.meta.env.VITE_API_URL}/statistic/num/export`, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | ...(options.headers || {}) |
| | | }, |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | export function buildExportTaskPageParams(params = {}) { |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | ...(params.condition !== undefined ? { condition: params.condition } : {}), |
| | | ...(params.resourceKey !== undefined ? { resourceKey: params.resourceKey } : {}), |
| | | ...(params.status !== undefined ? { status: params.status } : {}), |
| | | ...(params.timeStart !== undefined ? { timeStart: params.timeStart } : {}), |
| | | ...(params.timeEnd !== undefined ? { timeEnd: params.timeEnd } : {}), |
| | | ...(params.orderBy !== undefined ? { orderBy: params.orderBy } : {}) |
| | | } |
| | | } |
| | | |
| | | export function buildDictDataPageParams(params = {}) { |
| | | return { |
| | | current: params.current || 1, |
| | |
| | | return request.post({ url: '/dictType/page', params: buildDictTypePageParams(params) }) |
| | | } |
| | | |
| | | function fetchExportTaskPage(params = {}) { |
| | | return request.post({ url: '/exportTask/page', params: buildExportTaskPageParams(params) }) |
| | | } |
| | | |
| | | function fetchDictDataPage(params = {}) { |
| | | return request.post({ url: '/dictData/page', params: buildDictDataPageParams(params) }) |
| | | } |
| | | |
| | | function fetchGetDictTypeDetail(id) { |
| | | return request.get({ url: `/dictType/${id}` }) |
| | | } |
| | | |
| | | function fetchGetExportTaskDetail(id) { |
| | | return request.get({ url: `/exportTask/${id}` }) |
| | | } |
| | | |
| | | function fetchGetDictDataDetail(id) { |
| | |
| | | ...(options.headers || {}) |
| | | }, |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | | |
| | | async function fetchDownloadExportTask(id, options = {}) { |
| | | return fetch(`${import.meta.env.VITE_API_URL}/exportTask/${id}/download`, { |
| | | method: 'GET', |
| | | headers: { |
| | | ...(options.headers || {}) |
| | | } |
| | | }) |
| | | } |
| | | |
| | |
| | | fetchSaveSerialRule, |
| | | fetchUpdateSerialRule, |
| | | fetchDeleteSerialRule, |
| | | fetchExportTaskPage, |
| | | fetchGetExportTaskDetail, |
| | | fetchDownloadExportTask, |
| | | fetchDictTypePage, |
| | | fetchGetDictTypeDetail, |
| | | fetchGetDictDataDetail, |
| | |
| | | |
| | | function normalizeIds(ids) { |
| | | if (Array.isArray(ids)) { |
| | | return ids.map((id) => String(id).trim()).filter(Boolean).join(',') |
| | | return ids |
| | | .map((id) => String(id).trim()) |
| | | .filter(Boolean) |
| | | .join(',') |
| | | } |
| | | if (ids === null || ids === undefined) { |
| | | return '' |
| | |
| | | body: JSON.stringify(payload) |
| | | }) |
| | | } |
| | | |
| | | export function fetchCreateWarehouseAreasItemExportTask(payload = {}) { |
| | | return request.post({ |
| | | url: '/warehouseAreasItem/export/async', |
| | | params: payload |
| | | }) |
| | | } |
| | | |
| | | export function fetchWarehouseAreasItemExportTask(taskId) { |
| | | return request.get({ |
| | | url: `/warehouseAreasItem/export/task/${String(taskId).trim()}` |
| | | }) |
| | | } |
| | | |
| | | export async function fetchDownloadWarehouseAreasItemExportTask(taskId, options = {}) { |
| | | return fetch( |
| | | `${import.meta.env.VITE_API_URL}/warehouseAreasItem/export/task/${String(taskId).trim()}/download`, |
| | | { |
| | | method: 'GET', |
| | | headers: { |
| | | ...(options.headers || {}) |
| | | } |
| | | } |
| | | ) |
| | | } |
| | |
| | | <template> |
| | | <ElSpace v-bind="attrs" wrap> |
| | | <ElButton :disabled="disabled" @click="handleExport">{{ t('common.actions.export') }}</ElButton> |
| | | <ElButton :disabled="disabled" @click="handlePrint">{{ t('common.actions.print') }}</ElButton> |
| | | <ElButton v-if="canExport" :disabled="disabled" @click="handleExport"> |
| | | {{ t('common.actions.export') }} |
| | | </ElButton> |
| | | <ElButton v-if="canExport" :disabled="disabled" @click="handlePrint"> |
| | | {{ t('common.actions.print') }} |
| | | </ElButton> |
| | | </ElSpace> |
| | | |
| | | <ListPrintPreviewDialog |
| | |
| | | |
| | | <script setup> |
| | | import { computed, useAttrs } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useI18n } from 'vue-i18n' |
| | | import { useAuth } from '@/hooks/core/useAuth' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import ListPrintPreviewDialog from './list-print-preview-dialog.vue' |
| | | import { |
| | | buildListExportPayload, |
| | |
| | | |
| | | const attrs = useAttrs() |
| | | const { t } = useI18n() |
| | | const { hasAuth } = useAuth() |
| | | const userStore = useUserStore() |
| | | |
| | | const props = defineProps({ |
| | | reportTitle: { type: String, default: '' }, |
| | |
| | | previewMaxRows: { type: Number, default: 50 }, |
| | | total: { type: Number, default: 0 }, |
| | | maxResults: { type: Number, default: 1000 }, |
| | | disabled: { type: Boolean, default: false } |
| | | disabled: { type: Boolean, default: false }, |
| | | exportAuth: { type: [String, Array], default: '' }, |
| | | printAuth: { type: [String, Array], default: '' } |
| | | }) |
| | | |
| | | const emit = defineEmits(['export', 'print']) |
| | | const normalizedMeta = computed(() => |
| | | buildReportStyleMeta({ |
| | | reportTitle: props.reportTitle, |
| | | meta: props.previewMeta |
| | | }) |
| | | ) |
| | | const normalizedMeta = computed(() => buildCurrentReportMeta()) |
| | | const normalizedPreviewColumns = computed(() => |
| | | buildPreviewColumns({ |
| | | columns: props.columns, |
| | |
| | | type: Boolean, |
| | | default: false |
| | | }) |
| | | const canExport = computed(() => !props.exportAuth || hasAuth(props.exportAuth)) |
| | | const canPrint = computed(() => !props.printAuth || hasAuth(props.printAuth)) |
| | | const printLimit = computed(() => { |
| | | const limit = Number(props.maxResults) |
| | | return Number.isFinite(limit) && limit > 0 ? limit : 1000 |
| | | }) |
| | | |
| | | function buildCurrentReportMeta() { |
| | | const now = new Date() |
| | | return buildReportStyleMeta({ |
| | | reportTitle: props.reportTitle, |
| | | meta: { |
| | | reportDate: now.toLocaleDateString('zh-CN'), |
| | | printedAt: now.toLocaleString('zh-CN', { hour12: false }), |
| | | operator: |
| | | userStore.getUserInfo?.name || |
| | | userStore.getUserInfo?.nickname || |
| | | userStore.getUserInfo?.username || |
| | | '', |
| | | ...props.previewMeta |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const handleExport = () => { |
| | | const payload = buildListExportPayload({ |
| | |
| | | selectedRows: props.selectedRows, |
| | | queryParams: props.queryParams, |
| | | columns: props.columns, |
| | | meta: normalizedMeta.value |
| | | meta: buildCurrentReportMeta() |
| | | }) |
| | | emit('export', payload) |
| | | } |
| | | |
| | | const getPrintRowCount = () => { |
| | | const selectedCount = Array.isArray(props.selectedRows) ? props.selectedRows.length : 0 |
| | | if (selectedCount > 0) { |
| | | return selectedCount |
| | | } |
| | | return Number(props.total) || 0 |
| | | } |
| | | |
| | | const validatePrintLimit = () => { |
| | | const rowCount = getPrintRowCount() |
| | | if (rowCount <= printLimit.value) { |
| | | return true |
| | | } |
| | | ElMessage.warning(t('message.printExceedMaxRows', { maxRows: printLimit.value })) |
| | | return false |
| | | } |
| | | |
| | | const handlePrint = () => { |
| | | if (!validatePrintLimit()) { |
| | | return |
| | | } |
| | | |
| | | const payload = buildListPrintPayload({ |
| | | selectedRows: props.selectedRows, |
| | | queryParams: props.queryParams, |
| | |
| | | "userLogin": "Login Logs", |
| | | "role": "Role Manage", |
| | | "userCenter": "User Center", |
| | | "menu": "Menu Manage" |
| | | "menu": "Menu Manage", |
| | | "exportTask": "Export Tasks" |
| | | } |
| | | }, |
| | | "menu": { |
| | |
| | | "qlyInspect": "QlyInspect", |
| | | "qlyIsptItem": "qlyIsptItem", |
| | | "dictType": "DictType", |
| | | "exportTask": "Export Tasks", |
| | | "dictData": "DictData", |
| | | "companys": "Companys", |
| | | "serialRuleItem": "SerialRuleItem", |
| | |
| | | "requestTimeoutStopped": "Request timed out and waiting has stopped", |
| | | "exportTimeoutStopped": "Export request timed out and waiting has stopped", |
| | | "printTimeoutStopped": "Print data loading timed out and waiting has stopped", |
| | | "printExceedMaxRows": "Printing more than {maxRows} rows is disabled to avoid browser memory issues. Please narrow the filters or use export instead.", |
| | | "routeRenderFailedTitle": "Page failed to load", |
| | | "routeRenderFailed": "The page failed to render. Please try again later.", |
| | | "systemUpgradeTitle": "System Upgrade Notice", |
| | |
| | | "userLogin": "登录日志", |
| | | "role": "角色管理", |
| | | "userCenter": "个人中心", |
| | | "menu": "菜单管理" |
| | | "menu": "菜单管理", |
| | | "exportTask": "导出任务" |
| | | } |
| | | }, |
| | | "menu": { |
| | |
| | | "qlyInspect": "质检信息", |
| | | "qlyIsptItem": "质检信息明细", |
| | | "dictType": "数据字典", |
| | | "exportTask": "导出任务", |
| | | "dictData": "字典数据集", |
| | | "companys": "往来企业", |
| | | "serialRuleItem": "编码规则子表", |
| | |
| | | "requestTimeoutStopped": "请求超时,已停止等待", |
| | | "exportTimeoutStopped": "导出请求超时,已停止等待", |
| | | "printTimeoutStopped": "打印数据加载超时,已停止等待", |
| | | "printExceedMaxRows": "打印数据超过 {maxRows} 行,为避免浏览器内存不足,已禁止打印,请先缩小筛选范围或改用导出。", |
| | | "routeRenderFailedTitle": "页面加载失败", |
| | | "routeRenderFailed": "页面渲染失败,请稍后重试", |
| | | "systemUpgradeTitle": "系统升级提示", |
| | |
| | | menu: '/system/menu', |
| | | config: '/system/config', |
| | | dictType: '/system/dict-type', |
| | | exportTask: '/system/export-task', |
| | | fields: '/system/fields', |
| | | fieldsItem: '/system/fields-item', |
| | | whMat: '/basic-info/wh-mat', |
| | |
| | | 'menu.deviceSite': '路径管理', |
| | | 'menu.dictData': '字典数据集', |
| | | 'menu.dictType': '数据字典', |
| | | 'menu.exportTask': '导出任务', |
| | | 'menu.fields': '扩展字段', |
| | | 'menu.fieldsItem': '扩展字段明细', |
| | | 'menu.flowInstance': '流程实例', |
| | |
| | | :preview-rows="previewRows" |
| | | :preview-meta="resolvedPreviewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | :disabled="loading || exportTaskLoading" |
| | | export-auth="manager:loc:export" |
| | | print-auth="list" |
| | | @export="handleExportRequest" |
| | | @print="handlePrint" |
| | | /> |
| | | </ElSpace> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { computed, onBeforeUnmount, onMounted, ref } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useAuth } from '@/hooks/core/useAuth' |
| | |
| | | import { fetchWarehouseAreasList, fetchWarehouseList } from '@/api/warehouse-areas' |
| | | import { |
| | | fetchDeleteLoc, |
| | | fetchCreateLocExportTask, |
| | | fetchDownloadLocExportTask, |
| | | fetchExportLocReport, |
| | | fetchGetLocDetail, |
| | | fetchGetLocMany, |
| | | fetchLocExportTask, |
| | | fetchLocPage, |
| | | fetchLocTypeList, |
| | | fetchSaveLoc, |
| | |
| | | |
| | | defineOptions({ name: 'Loc' }) |
| | | |
| | | const EXPORT_SYNC_MAX_ROWS = 5000 |
| | | const EXPORT_TASK_POLL_INTERVAL = 3000 |
| | | |
| | | const { hasAuth } = useAuth() |
| | | const userStore = useUserStore() |
| | | |
| | |
| | | const warehouseOptions = ref([]) |
| | | const areaOptions = ref([]) |
| | | const locTypeOptions = ref([]) |
| | | const exportTaskLoading = ref(false) |
| | | let handleDeleteAction = null |
| | | let exportTaskTimer = null |
| | | |
| | | const reportTitle = LOC_REPORT_TITLE |
| | | const reportQueryParams = computed(() => buildLocSearchParams(searchForm.value)) |
| | |
| | | ]) |
| | | |
| | | async function fetchLocDetailById(id) { |
| | | return guardRequestWithMessage(fetchGetLocDetail(id), {}, { |
| | | timeoutMessage: '库位详情加载超时,已停止等待' |
| | | }) |
| | | return guardRequestWithMessage( |
| | | fetchGetLocDetail(id), |
| | | {}, |
| | | { |
| | | timeoutMessage: '库位详情加载超时,已停止等待' |
| | | } |
| | | ) |
| | | } |
| | | |
| | | async function openDetail(row) { |
| | |
| | | } |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | const response = Array.isArray(payload?.ids) && payload.ids.length > 0 |
| | | ? await fetchGetLocMany(payload.ids) |
| | | : await fetchLocPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20 |
| | | }) |
| | | const response = |
| | | Array.isArray(payload?.ids) && payload.ids.length > 0 |
| | | ? await fetchGetLocMany(payload.ids) |
| | | : await fetchLocPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: |
| | | Number(pagination.total) > 0 |
| | | ? Number(pagination.total) |
| | | : Number(payload?.pageSize) || 20 |
| | | }) |
| | | return defaultResponseAdapter(response).records |
| | | } |
| | | |
| | |
| | | buildPreviewRows: (records) => buildLocPrintRows(records), |
| | | buildPreviewMeta: (rows) => buildPreviewDialogMeta(rows) |
| | | }) |
| | | |
| | | function clearExportTaskTimer() { |
| | | if (exportTaskTimer) { |
| | | clearTimeout(exportTaskTimer) |
| | | exportTaskTimer = null |
| | | } |
| | | } |
| | | |
| | | function getExportRowCount(payload) { |
| | | const selectedIds = Array.isArray(payload?.ids) ? payload.ids.filter(Boolean) : [] |
| | | if (selectedIds.length > 0) { |
| | | return selectedIds.length |
| | | } |
| | | return Number(pagination.total) || 0 |
| | | } |
| | | |
| | | function needsAsyncExport(payload) { |
| | | return getExportRowCount(payload) > EXPORT_SYNC_MAX_ROWS |
| | | } |
| | | |
| | | function scheduleExportTaskPoll(taskId) { |
| | | clearExportTaskTimer() |
| | | exportTaskTimer = setTimeout(() => { |
| | | pollExportTask(taskId) |
| | | }, EXPORT_TASK_POLL_INTERVAL) |
| | | } |
| | | |
| | | function resolveDownloadFileName(task = {}, response) { |
| | | const contentDisposition = response?.headers?.get('Content-Disposition') || '' |
| | | const matchedPart = contentDisposition |
| | | .split(';') |
| | | .map((part) => part.trim()) |
| | | .find((part) => /^filename\*?=/i.test(part)) |
| | | |
| | | if (matchedPart) { |
| | | let fileName = matchedPart.replace(/^filename\*?=/i, '').trim() |
| | | if (fileName.toLowerCase().startsWith("utf-8''")) { |
| | | fileName = fileName.slice(7) |
| | | } |
| | | if (fileName.startsWith('"') && fileName.endsWith('"')) { |
| | | fileName = fileName.slice(1, -1) |
| | | } |
| | | try { |
| | | return decodeURIComponent(fileName) |
| | | } catch { |
| | | return fileName |
| | | } |
| | | } |
| | | return task.fileName || 'loc.xlsx' |
| | | } |
| | | |
| | | async function downloadExportTaskFile(task) { |
| | | const response = await fetchDownloadLocExportTask(task.id, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }) |
| | | if (!response.ok) { |
| | | throw new Error(`导出文件下载失败(${response.status})`) |
| | | } |
| | | |
| | | const blob = await response.blob() |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = resolveDownloadFileName(task, response) |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | link.remove() |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | } |
| | | |
| | | async function pollExportTask(taskId) { |
| | | try { |
| | | const task = await fetchLocExportTask(taskId) |
| | | const status = Number(task?.status) |
| | | if (status === 2) { |
| | | clearExportTaskTimer() |
| | | await downloadExportTaskFile(task) |
| | | exportTaskLoading.value = false |
| | | ElMessage.success( |
| | | `导出任务已完成,已开始下载${task?.rowCount ? `(${task.rowCount}行)` : ''}` |
| | | ) |
| | | return |
| | | } |
| | | if (status === 3) { |
| | | clearExportTaskTimer() |
| | | exportTaskLoading.value = false |
| | | ElMessage.error(task?.errorMsg || '导出任务执行失败') |
| | | return |
| | | } |
| | | scheduleExportTaskPoll(taskId) |
| | | } catch (error) { |
| | | clearExportTaskTimer() |
| | | exportTaskLoading.value = false |
| | | ElMessage.error(error?.message || '查询导出任务状态失败') |
| | | } |
| | | } |
| | | |
| | | async function handleExportRequest(payload) { |
| | | if (!needsAsyncExport(payload)) { |
| | | await handleExport(payload) |
| | | return |
| | | } |
| | | |
| | | const exportRowCount = getExportRowCount(payload) |
| | | exportTaskLoading.value = true |
| | | clearExportTaskTimer() |
| | | try { |
| | | const task = await fetchCreateLocExportTask(payload) |
| | | ElMessage.success( |
| | | `本次导出共 ${exportRowCount} 行,已超过 ${EXPORT_SYNC_MAX_ROWS} 行,系统已自动切换为后台导出任务${task?.taskCode ? `(${task.taskCode})` : ''}` |
| | | ) |
| | | if (!task?.id) { |
| | | throw new Error('导出任务创建成功,但未返回任务ID') |
| | | } |
| | | scheduleExportTaskPoll(task.id) |
| | | } catch (error) { |
| | | exportTaskLoading.value = false |
| | | clearExportTaskTimer() |
| | | ElMessage.error(error?.message || '创建导出任务失败') |
| | | } |
| | | } |
| | | |
| | | const resolvedPreviewMeta = computed(() => |
| | | buildLocReportMeta({ |
| | |
| | | |
| | | onMounted(async () => { |
| | | await Promise.all([ |
| | | loadOptions(fetchWarehouseList, resolveLocWarehouseOptions, warehouseOptions, '仓库选项加载超时,已停止等待'), |
| | | loadOptions(fetchWarehouseAreasList, resolveLocAreaOptions, areaOptions, '库区选项加载超时,已停止等待'), |
| | | loadOptions(fetchLocTypeList, resolveLocTypeOptions, locTypeOptions, '库位类型选项加载超时,已停止等待') |
| | | loadOptions( |
| | | fetchWarehouseList, |
| | | resolveLocWarehouseOptions, |
| | | warehouseOptions, |
| | | '仓库选项加载超时,已停止等待' |
| | | ), |
| | | loadOptions( |
| | | fetchWarehouseAreasList, |
| | | resolveLocAreaOptions, |
| | | areaOptions, |
| | | '库区选项加载超时,已停止等待' |
| | | ), |
| | | loadOptions( |
| | | fetchLocTypeList, |
| | | resolveLocTypeOptions, |
| | | locTypeOptions, |
| | | '库位类型选项加载超时,已停止等待' |
| | | ) |
| | | ]) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearExportTaskTimer() |
| | | }) |
| | | </script> |
| | |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData" /> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData"> |
| | | <template #left> |
| | | <ListExportPrint |
| | | class="inline-flex" |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :query-params="reportQueryParams" |
| | | :columns="reportColumns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="previewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </template> |
| | | </ArtTableHeader> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | |
| | | |
| | | <script setup> |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { fetchStatisticCountPage } from '@/api/statistic-count' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { |
| | | fetchExportStatisticCountReport, |
| | | fetchGetStatisticCountMany, |
| | | fetchStatisticCountPage |
| | | } from '@/api/statistic-count' |
| | | import { |
| | | buildStatisticCountPrintRows, |
| | | buildStatisticCountPageQueryParams, |
| | | createStatisticCountSearchState, |
| | | getStatisticCountReportColumns, |
| | | STATISTIC_COUNT_REPORT_TITLE, |
| | | normalizeStatisticCountRow |
| | | } from './statisticCountPage.helpers' |
| | | import { createStatisticCountTableColumns } from './statisticCountTable.columns' |
| | | |
| | | defineOptions({ name: 'StatisticCount' }) |
| | | |
| | | const userStore = useUserStore() |
| | | const reportTitle = STATISTIC_COUNT_REPORT_TITLE |
| | | const loading = ref(false) |
| | | const tableData = ref([]) |
| | | const searchForm = ref(createStatisticCountSearchState()) |
| | |
| | | size: 20, |
| | | total: 0 |
| | | }) |
| | | const reportColumns = getStatisticCountReportColumns() |
| | | const reportQueryParams = computed(() => buildStatisticCountPageQueryParams(searchForm.value)) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | |
| | | |
| | | const { columns, columnChecks } = useTableColumns(() => createStatisticCountTableColumns()) |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | if (Array.isArray(payload?.ids) && payload.ids.length > 0) { |
| | | return defaultResponseAdapter(await fetchGetStatisticCountMany(payload.ids)).records |
| | | } |
| | | return defaultResponseAdapter( |
| | | await fetchStatisticCountPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 |
| | | }) |
| | | ).records |
| | | } |
| | | |
| | | const { |
| | | previewVisible, |
| | | previewRows, |
| | | previewMeta, |
| | | handlePreviewVisibleChange, |
| | | handleExport, |
| | | handlePrint |
| | | } = usePrintExportPage({ |
| | | downloadFileName: 'statistic-count-report.xlsx', |
| | | requestExport: (payload) => |
| | | fetchExportStatisticCountReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | resolvePrintRecords, |
| | | buildPreviewRows: (records) => buildStatisticCountPrintRows(records), |
| | | buildPreviewMeta: (rows) => ({ |
| | | reportTitle, |
| | | reportDate: new Date().toLocaleDateString('zh-CN'), |
| | | printedAt: new Date().toLocaleString('zh-CN', { hour12: false }), |
| | | operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', |
| | | count: rows.length |
| | | }) |
| | | }) |
| | | |
| | | function updatePaginationState(response) { |
| | | pagination.total = Number(response?.total || 0) |
| | | pagination.current = Number(response?.current || pagination.current || 1) |
| | |
| | | outAnfme: Number(row.outAnfme || 0) |
| | | } |
| | | } |
| | | |
| | | export function getStatisticCountReportColumns() { |
| | | return [ |
| | | { source: 'dayTime', label: '统计日期' }, |
| | | { source: 'count', label: '记录数', align: 'right' }, |
| | | { source: 'inAnfmeCount', label: '入库笔数', align: 'right' }, |
| | | { source: 'outAnfmeCount', label: '出库笔数', align: 'right' }, |
| | | { source: 'anfme', label: '总数量', align: 'right' }, |
| | | { source: 'inAnfme', label: '入库数量', align: 'right' }, |
| | | { source: 'outAnfme', label: '出库数量', align: 'right' } |
| | | ] |
| | | } |
| | | |
| | | export function buildStatisticCountPrintRows(records = []) { |
| | | if (!Array.isArray(records)) { |
| | | return [] |
| | | } |
| | | return records.map((record) => normalizeStatisticCountRow(record)) |
| | | } |
| | |
| | | import { getInStatisticTaskStatusMeta, getInStatisticTaskTypeMeta } from '../in-statistic/inStatisticPage.helpers.js' |
| | | import { |
| | | getInStatisticTaskStatusMeta, |
| | | getInStatisticTaskTypeMeta |
| | | } from '../in-statistic/inStatisticPage.helpers.js' |
| | | |
| | | export const IN_STATISTIC_ITEM_PAGE_TITLE = '入库统计明细' |
| | | export const IN_STATISTIC_ITEM_REPORT_TITLE = '日入库明细查询' |
| | | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | |
| | | } |
| | | |
| | | return Object.fromEntries( |
| | | Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null) |
| | | Object.entries(searchParams).filter( |
| | | ([, value]) => value !== '' && value !== void 0 && value !== null |
| | | ) |
| | | ) |
| | | } |
| | | |
| | |
| | | dayTimeText: normalizeText(record.dayTime || record.day_time || ''), |
| | | taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text), |
| | | taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info', |
| | | taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text), |
| | | taskStatusText: normalizeText( |
| | | record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text |
| | | ), |
| | | taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info', |
| | | locCode: normalizeText(record.locCode || record.loc_code || ''), |
| | | barcode: normalizeText(record.barcode || ''), |
| | |
| | | memo: normalizeText(record.memo || '') |
| | | } |
| | | } |
| | | |
| | | export function getInStatisticItemReportColumns() { |
| | | return [ |
| | | { source: 'dayTimeText', label: '统计日期' }, |
| | | { source: 'locCode', label: '库位' }, |
| | | { source: 'matnrCode', label: '物料编码' }, |
| | | { source: 'maktx', label: '物料名称' }, |
| | | { source: 'anfme', label: '数量', align: 'right' }, |
| | | { source: 'batch', label: '批次' }, |
| | | { source: 'unit', label: '单位' }, |
| | | { source: 'barcode', label: '托盘码' }, |
| | | { source: 'taskTypeText', label: '任务类型' }, |
| | | { source: 'taskStatusText', label: '任务状态' }, |
| | | { source: 'createByText', label: '创建人' }, |
| | | { source: 'createTimeText', label: '创建时间' }, |
| | | { source: 'updateByText', label: '更新人' }, |
| | | { source: 'updateTimeText', label: '更新时间' } |
| | | ] |
| | | } |
| | | |
| | | export function buildInStatisticItemPrintRows(records = []) { |
| | | if (!Array.isArray(records)) { |
| | | return [] |
| | | } |
| | | return records.map((record) => normalizeInStatisticItemRow(record)) |
| | | } |
| | |
| | | <template> |
| | | <div class="in-statistic-item-page art-full-height"> |
| | | <ArtSearchBar v-model="searchForm" :items="searchItems" :showExpand="true" @search="handleSearch" @reset="handleReset" /> |
| | | <ArtSearchBar |
| | | v-model="searchForm" |
| | | :items="searchItems" |
| | | :showExpand="true" |
| | | @search="handleSearch" |
| | | @reset="handleReset" |
| | | /> |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" /> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData"> |
| | | <template #left> |
| | | <ListExportPrint |
| | | class="inline-flex" |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :query-params="reportQueryParams" |
| | | :columns="reportColumns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="previewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </template> |
| | | </ArtTableHeader> |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="data" |
| | |
| | | |
| | | <script setup> |
| | | import { computed, ref } from 'vue' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import { fetchInStatisticItemPage } from '@/api/in-statistic-item' |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { |
| | | fetchExportInStatisticItemReport, |
| | | fetchGetInStatisticItemMany, |
| | | fetchInStatisticItemPage |
| | | } from '@/api/in-statistic-item' |
| | | import { |
| | | buildInStatisticItemPrintRows, |
| | | buildInStatisticItemPageQueryParams, |
| | | buildInStatisticItemSearchParams, |
| | | createInStatisticItemSearchState, |
| | | getInStatisticItemPaginationKey, |
| | | normalizeInStatisticItemRow |
| | | getInStatisticItemReportColumns, |
| | | normalizeInStatisticItemRow, |
| | | IN_STATISTIC_ITEM_REPORT_TITLE |
| | | } from './inStatisticItemPage.helpers' |
| | | import { createInStatisticItemTableColumns } from './inStatisticItemTable.columns' |
| | | import InStatisticItemDetailDrawer from './modules/in-statistic-item-detail-drawer.vue' |
| | | |
| | | defineOptions({ name: 'InStatisticItem' }) |
| | | |
| | | const userStore = useUserStore() |
| | | const reportTitle = IN_STATISTIC_ITEM_REPORT_TITLE |
| | | const searchForm = ref(createInStatisticItemSearchState()) |
| | | const detailDrawerVisible = ref(false) |
| | | const detailData = ref({}) |
| | | const reportColumns = getInStatisticItemReportColumns() |
| | | const reportQueryParams = computed(() => buildInStatisticItemPageQueryParams(searchForm.value)) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { label: '关键字', key: 'condition', type: 'input', props: { clearable: true, placeholder: '请输入物料名称/编码/批次/库位' } }, |
| | | { label: '统计日期', key: 'dayTime', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } }, |
| | | { label: '库位', key: 'locCode', type: 'input', props: { clearable: true, placeholder: '请输入库位' } }, |
| | | { label: '物料编码', key: 'matnrCode', type: 'input', props: { clearable: true, placeholder: '请输入物料编码' } }, |
| | | { label: '物料名称', key: 'maktx', type: 'input', props: { clearable: true, placeholder: '请输入物料名称' } }, |
| | | { label: '批次', key: 'batch', type: 'input', props: { clearable: true, placeholder: '请输入批次' } } |
| | | { |
| | | label: '关键字', |
| | | key: 'condition', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入物料名称/编码/批次/库位' } |
| | | }, |
| | | { |
| | | label: '统计日期', |
| | | key: 'dayTime', |
| | | type: 'date', |
| | | props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } |
| | | }, |
| | | { |
| | | label: '库位', |
| | | key: 'locCode', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入库位' } |
| | | }, |
| | | { |
| | | label: '物料编码', |
| | | key: 'matnrCode', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入物料编码' } |
| | | }, |
| | | { |
| | | label: '物料名称', |
| | | key: 'maktx', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入物料名称' } |
| | | }, |
| | | { |
| | | label: '批次', |
| | | key: 'batch', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入批次' } |
| | | } |
| | | ]) |
| | | |
| | | function openDetail(row) { |
| | |
| | | columnsFactory: () => createInStatisticItemTableColumns({ handleView: openDetail }) |
| | | }, |
| | | transform: { |
| | | dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeInStatisticItemRow(item)) : []) |
| | | dataTransformer: (records) => |
| | | Array.isArray(records) ? records.map((item) => normalizeInStatisticItemRow(item)) : [] |
| | | } |
| | | }) |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | if (Array.isArray(payload?.ids) && payload.ids.length > 0) { |
| | | return defaultResponseAdapter(await fetchGetInStatisticItemMany(payload.ids)).records |
| | | } |
| | | return defaultResponseAdapter( |
| | | await fetchInStatisticItemPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 |
| | | }) |
| | | ).records |
| | | } |
| | | |
| | | const { |
| | | previewVisible, |
| | | previewRows, |
| | | previewMeta, |
| | | handlePreviewVisibleChange, |
| | | handleExport, |
| | | handlePrint |
| | | } = usePrintExportPage({ |
| | | downloadFileName: 'in-statistic-item-report.xlsx', |
| | | requestExport: (payload) => |
| | | fetchExportInStatisticItemReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | resolvePrintRecords, |
| | | buildPreviewRows: (records) => buildInStatisticItemPrintRows(records), |
| | | buildPreviewMeta: (rows) => ({ |
| | | reportTitle, |
| | | reportDate: new Date().toLocaleDateString('zh-CN'), |
| | | printedAt: new Date().toLocaleString('zh-CN', { hour12: false }), |
| | | operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', |
| | | count: rows.length |
| | | }) |
| | | }) |
| | | |
| | | function handleSearch(params) { |
| | | replaceSearchParams(buildInStatisticItemSearchParams(params)) |
| | | getData() |
| | |
| | | } |
| | | |
| | | export const IN_STATISTIC_PAGE_TITLE = '入库统计' |
| | | export const IN_STATISTIC_REPORT_TITLE = '日入库汇总查询' |
| | | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | |
| | | } |
| | | |
| | | return Object.fromEntries( |
| | | Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null) |
| | | Object.entries(searchParams).filter( |
| | | ([, value]) => value !== '' && value !== void 0 && value !== null |
| | | ) |
| | | ) |
| | | } |
| | | |
| | |
| | | dayTimeText: normalizeText(record.dayTime || record.day_time || ''), |
| | | taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text), |
| | | taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info', |
| | | taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text), |
| | | taskStatusText: normalizeText( |
| | | record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text |
| | | ), |
| | | taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info', |
| | | locCode: normalizeText(record.locCode || record.loc_code || ''), |
| | | barcode: normalizeText(record.barcode || ''), |
| | |
| | | memo: normalizeText(record.memo || '') |
| | | } |
| | | } |
| | | |
| | | export function getInStatisticReportColumns() { |
| | | return [ |
| | | { source: 'dayTimeText', label: '统计日期' }, |
| | | { source: 'matnrCode', label: '物料编码' }, |
| | | { source: 'maktx', label: '物料名称' }, |
| | | { source: 'anfme', label: '数量', align: 'right' }, |
| | | { source: 'batch', label: '批次' }, |
| | | { source: 'unit', label: '单位' }, |
| | | { source: 'taskTypeText', label: '任务类型' }, |
| | | { source: 'taskStatusText', label: '任务状态' } |
| | | ] |
| | | } |
| | | |
| | | export function buildInStatisticPrintRows(records = []) { |
| | | if (!Array.isArray(records)) { |
| | | return [] |
| | | } |
| | | return records.map((record) => normalizeInStatisticRow(record)) |
| | | } |
| | |
| | | @reset="handleReset" |
| | | /> |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" /> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData"> |
| | | <template #left> |
| | | <ListExportPrint |
| | | class="inline-flex" |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :query-params="reportQueryParams" |
| | | :columns="reportColumns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="previewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </template> |
| | | </ArtTableHeader> |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="data" |
| | |
| | | |
| | | <script setup> |
| | | import { computed, ref } from 'vue' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import { fetchInStatisticPage } from '@/api/in-statistic' |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { |
| | | fetchExportInStatisticReport, |
| | | fetchGetInStatisticMany, |
| | | fetchInStatisticPage |
| | | } from '@/api/in-statistic' |
| | | import { |
| | | buildInStatisticPrintRows, |
| | | buildInStatisticPageQueryParams, |
| | | buildInStatisticSearchParams, |
| | | createInStatisticSearchState, |
| | | getInStatisticPaginationKey, |
| | | normalizeInStatisticRow |
| | | getInStatisticReportColumns, |
| | | normalizeInStatisticRow, |
| | | IN_STATISTIC_REPORT_TITLE |
| | | } from './inStatisticPage.helpers' |
| | | import { createInStatisticTableColumns } from './inStatisticTable.columns' |
| | | import InStatisticDetailDrawer from './modules/in-statistic-detail-drawer.vue' |
| | | |
| | | defineOptions({ name: 'InStatistic' }) |
| | | |
| | | const userStore = useUserStore() |
| | | const reportTitle = IN_STATISTIC_REPORT_TITLE |
| | | const searchForm = ref(createInStatisticSearchState()) |
| | | const detailDrawerVisible = ref(false) |
| | | const detailData = ref({}) |
| | | const reportColumns = getInStatisticReportColumns() |
| | | const reportQueryParams = computed(() => buildInStatisticPageQueryParams(searchForm.value)) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { label: '关键字', key: 'condition', type: 'input', props: { clearable: true, placeholder: '请输入物料名称/编码/批次' } }, |
| | | { label: '统计日期', key: 'dayTime', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } }, |
| | | { label: '物料名称', key: 'maktx', type: 'input', props: { clearable: true, placeholder: '请输入物料名称' } }, |
| | | { label: '物料编码', key: 'matnrCode', type: 'input', props: { clearable: true, placeholder: '请输入物料编码' } }, |
| | | { label: '批次', key: 'batch', type: 'input', props: { clearable: true, placeholder: '请输入批次' } } |
| | | { |
| | | label: '关键字', |
| | | key: 'condition', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入物料名称/编码/批次' } |
| | | }, |
| | | { |
| | | label: '统计日期', |
| | | key: 'dayTime', |
| | | type: 'date', |
| | | props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } |
| | | }, |
| | | { |
| | | label: '物料名称', |
| | | key: 'maktx', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入物料名称' } |
| | | }, |
| | | { |
| | | label: '物料编码', |
| | | key: 'matnrCode', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入物料编码' } |
| | | }, |
| | | { |
| | | label: '批次', |
| | | key: 'batch', |
| | | type: 'input', |
| | | props: { clearable: true, placeholder: '请输入批次' } |
| | | } |
| | | ]) |
| | | |
| | | function openDetail(row) { |
| | |
| | | columnsFactory: () => createInStatisticTableColumns({ handleView: openDetail }) |
| | | }, |
| | | transform: { |
| | | dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeInStatisticRow(item)) : []) |
| | | dataTransformer: (records) => |
| | | Array.isArray(records) ? records.map((item) => normalizeInStatisticRow(item)) : [] |
| | | } |
| | | }) |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | if (Array.isArray(payload?.ids) && payload.ids.length > 0) { |
| | | return defaultResponseAdapter(await fetchGetInStatisticMany(payload.ids)).records |
| | | } |
| | | return defaultResponseAdapter( |
| | | await fetchInStatisticPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 |
| | | }) |
| | | ).records |
| | | } |
| | | |
| | | const { |
| | | previewVisible, |
| | | previewRows, |
| | | previewMeta, |
| | | handlePreviewVisibleChange, |
| | | handleExport, |
| | | handlePrint |
| | | } = usePrintExportPage({ |
| | | downloadFileName: 'in-statistic-report.xlsx', |
| | | requestExport: (payload) => |
| | | fetchExportInStatisticReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | resolvePrintRecords, |
| | | buildPreviewRows: (records) => buildInStatisticPrintRows(records), |
| | | buildPreviewMeta: (rows) => ({ |
| | | reportTitle, |
| | | reportDate: new Date().toLocaleDateString('zh-CN'), |
| | | printedAt: new Date().toLocaleString('zh-CN', { hour12: false }), |
| | | operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', |
| | | count: rows.length |
| | | }) |
| | | }) |
| | | |
| | | function handleSearch(params) { |
| | | replaceSearchParams(buildInStatisticSearchParams(params)) |
| | | getData() |
| | |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" /> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData"> |
| | | <template #left> |
| | | <ListExportPrint |
| | | class="inline-flex" |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :query-params="reportQueryParams" |
| | | :columns="reportColumns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="previewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </template> |
| | | </ArtTableHeader> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | |
| | | |
| | | <script setup> |
| | | import { computed, ref } from 'vue' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import { fetchOutStatisticItemPage } from '@/api/out-statistic-item' |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { |
| | | fetchExportOutStatisticItemReport, |
| | | fetchGetOutStatisticItemMany, |
| | | fetchOutStatisticItemPage |
| | | } from '@/api/out-statistic-item' |
| | | import { |
| | | buildOutStatisticItemPrintRows, |
| | | buildOutStatisticItemPageQueryParams, |
| | | buildOutStatisticItemSearchParams, |
| | | createOutStatisticItemSearchState, |
| | | getOutStatisticItemPaginationKey, |
| | | normalizeOutStatisticItemRow |
| | | getOutStatisticItemReportColumns, |
| | | normalizeOutStatisticItemRow, |
| | | OUT_STATISTIC_ITEM_REPORT_TITLE |
| | | } from './outStatisticItemPage.helpers' |
| | | import { createOutStatisticItemTableColumns } from './outStatisticItemTable.columns' |
| | | import OutStatisticItemDetailDrawer from './modules/out-statistic-item-detail-drawer.vue' |
| | | |
| | | defineOptions({ name: 'OutStatisticItem' }) |
| | | |
| | | const userStore = useUserStore() |
| | | const reportTitle = OUT_STATISTIC_ITEM_REPORT_TITLE |
| | | const searchForm = ref(createOutStatisticItemSearchState()) |
| | | const detailDrawerVisible = ref(false) |
| | | const detailData = ref({}) |
| | | const reportColumns = getOutStatisticItemReportColumns() |
| | | const reportQueryParams = computed(() => buildOutStatisticItemPageQueryParams(searchForm.value)) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | |
| | | } |
| | | }) |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | if (Array.isArray(payload?.ids) && payload.ids.length > 0) { |
| | | return defaultResponseAdapter(await fetchGetOutStatisticItemMany(payload.ids)).records |
| | | } |
| | | return defaultResponseAdapter( |
| | | await fetchOutStatisticItemPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 |
| | | }) |
| | | ).records |
| | | } |
| | | |
| | | const { |
| | | previewVisible, |
| | | previewRows, |
| | | previewMeta, |
| | | handlePreviewVisibleChange, |
| | | handleExport, |
| | | handlePrint |
| | | } = usePrintExportPage({ |
| | | downloadFileName: 'out-statistic-item-report.xlsx', |
| | | requestExport: (payload) => |
| | | fetchExportOutStatisticItemReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | resolvePrintRecords, |
| | | buildPreviewRows: (records) => buildOutStatisticItemPrintRows(records), |
| | | buildPreviewMeta: (rows) => ({ |
| | | reportTitle, |
| | | reportDate: new Date().toLocaleDateString('zh-CN'), |
| | | printedAt: new Date().toLocaleString('zh-CN', { hour12: false }), |
| | | operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', |
| | | count: rows.length |
| | | }) |
| | | }) |
| | | |
| | | function handleSearch(params) { |
| | | replaceSearchParams(buildOutStatisticItemSearchParams(params)) |
| | | getData() |
| | |
| | | } from '../out-statistic/outStatisticPage.helpers.js' |
| | | |
| | | export const OUT_STATISTIC_ITEM_PAGE_TITLE = '出库统计明细' |
| | | export const OUT_STATISTIC_ITEM_REPORT_TITLE = '日出库明细查询' |
| | | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | |
| | | } |
| | | |
| | | return Object.fromEntries( |
| | | Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null) |
| | | Object.entries(searchParams).filter( |
| | | ([, value]) => value !== '' && value !== void 0 && value !== null |
| | | ) |
| | | ) |
| | | } |
| | | |
| | |
| | | dayTimeText: normalizeText(record.dayTime || record.day_time || ''), |
| | | taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text), |
| | | taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info', |
| | | taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text), |
| | | taskStatusText: normalizeText( |
| | | record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text |
| | | ), |
| | | taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info', |
| | | locCode: normalizeText(record.locCode || record.loc_code || ''), |
| | | barcode: normalizeText(record.barcode || ''), |
| | |
| | | memo: normalizeText(record.memo || '') |
| | | } |
| | | } |
| | | |
| | | export function getOutStatisticItemReportColumns() { |
| | | return [ |
| | | { source: 'dayTimeText', label: '统计日期' }, |
| | | { source: 'locCode', label: '库位' }, |
| | | { source: 'matnrCode', label: '物料编码' }, |
| | | { source: 'maktx', label: '物料名称' }, |
| | | { source: 'anfme', label: '数量', align: 'right' }, |
| | | { source: 'batch', label: '批次' }, |
| | | { source: 'unit', label: '单位' }, |
| | | { source: 'barcode', label: '托盘码' }, |
| | | { source: 'taskTypeText', label: '任务类型' }, |
| | | { source: 'taskStatusText', label: '任务状态' }, |
| | | { source: 'createByText', label: '创建人' }, |
| | | { source: 'createTimeText', label: '创建时间' }, |
| | | { source: 'updateByText', label: '更新人' }, |
| | | { source: 'updateTimeText', label: '更新时间' } |
| | | ] |
| | | } |
| | | |
| | | export function buildOutStatisticItemPrintRows(records = []) { |
| | | if (!Array.isArray(records)) { |
| | | return [] |
| | | } |
| | | return records.map((record) => normalizeOutStatisticItemRow(record)) |
| | | } |
| | |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" /> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData"> |
| | | <template #left> |
| | | <ListExportPrint |
| | | class="inline-flex" |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :query-params="reportQueryParams" |
| | | :columns="reportColumns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="previewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </template> |
| | | </ArtTableHeader> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | |
| | | |
| | | <script setup> |
| | | import { computed, ref } from 'vue' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import { fetchOutStatisticPage } from '@/api/out-statistic' |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { |
| | | fetchExportOutStatisticReport, |
| | | fetchGetOutStatisticMany, |
| | | fetchOutStatisticPage |
| | | } from '@/api/out-statistic' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | | import { |
| | | buildOutStatisticPrintRows, |
| | | buildOutStatisticPageQueryParams, |
| | | buildOutStatisticSearchParams, |
| | | createOutStatisticSearchState, |
| | | getOutStatisticPaginationKey, |
| | | getOutStatisticReportColumns, |
| | | normalizeOutStatisticRow, |
| | | OUT_STATISTIC_PAGE_TITLE |
| | | OUT_STATISTIC_REPORT_TITLE |
| | | } from './outStatisticPage.helpers' |
| | | import { createOutStatisticTableColumns } from './outStatisticTable.columns' |
| | | import OutStatisticDetailDrawer from './modules/out-statistic-detail-drawer.vue' |
| | | |
| | | defineOptions({ name: 'OutStatistic' }) |
| | | |
| | | const userStore = useUserStore() |
| | | const reportTitle = OUT_STATISTIC_REPORT_TITLE |
| | | const searchForm = ref(createOutStatisticSearchState()) |
| | | const detailDrawerVisible = ref(false) |
| | | const detailData = ref({}) |
| | | const reportColumns = getOutStatisticReportColumns() |
| | | const reportQueryParams = computed(() => buildOutStatisticPageQueryParams(searchForm.value)) |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | |
| | | }) |
| | | }, |
| | | transform: { |
| | | dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeOutStatisticRow(item)) : []) |
| | | dataTransformer: (records) => |
| | | Array.isArray(records) ? records.map((item) => normalizeOutStatisticRow(item)) : [] |
| | | } |
| | | }) |
| | | |
| | | const resolvePrintRecords = async (payload) => { |
| | | if (Array.isArray(payload?.ids) && payload.ids.length > 0) { |
| | | return defaultResponseAdapter(await fetchGetOutStatisticMany(payload.ids)).records |
| | | } |
| | | return defaultResponseAdapter( |
| | | await fetchOutStatisticPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 |
| | | }) |
| | | ).records |
| | | } |
| | | |
| | | const { |
| | | previewVisible, |
| | | previewRows, |
| | | previewMeta, |
| | | handlePreviewVisibleChange, |
| | | handleExport, |
| | | handlePrint |
| | | } = usePrintExportPage({ |
| | | downloadFileName: 'out-statistic-report.xlsx', |
| | | requestExport: (payload) => |
| | | fetchExportOutStatisticReport(payload, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }), |
| | | resolvePrintRecords, |
| | | buildPreviewRows: (records) => buildOutStatisticPrintRows(records), |
| | | buildPreviewMeta: (rows) => ({ |
| | | reportTitle, |
| | | reportDate: new Date().toLocaleDateString('zh-CN'), |
| | | printedAt: new Date().toLocaleString('zh-CN', { hour12: false }), |
| | | operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '', |
| | | count: rows.length |
| | | }) |
| | | }) |
| | | |
| | | function handleSearch(params) { |
| | | replaceSearchParams(buildOutStatisticSearchParams(params)) |
| | | getData() |
| | |
| | | } |
| | | |
| | | export const OUT_STATISTIC_PAGE_TITLE = '出库统计' |
| | | export const OUT_STATISTIC_REPORT_TITLE = '出库统计报表' |
| | | export const OUT_STATISTIC_REPORT_TITLE = '日出库汇总查询' |
| | | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | |
| | | } |
| | | |
| | | return Object.fromEntries( |
| | | Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null) |
| | | Object.entries(searchParams).filter( |
| | | ([, value]) => value !== '' && value !== void 0 && value !== null |
| | | ) |
| | | ) |
| | | } |
| | | |
| | |
| | | dayTimeText: normalizeText(record.dayTime || record.day_time || ''), |
| | | taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text), |
| | | taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info', |
| | | taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text), |
| | | taskStatusText: normalizeText( |
| | | record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text |
| | | ), |
| | | taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info', |
| | | locCode: normalizeText(record.locCode || record.loc_code || ''), |
| | | barcode: normalizeText(record.barcode || ''), |
| | |
| | | } |
| | | return records.map((record) => normalizeOutStatisticRow(record)) |
| | | } |
| | | |
| | | export function getOutStatisticReportColumns() { |
| | | return [ |
| | | { source: 'dayTimeText', label: '统计日期' }, |
| | | { source: 'matnrCode', label: '物料编码' }, |
| | | { source: 'maktx', label: '物料名称' }, |
| | | { source: 'anfme', label: '数量', align: 'right' }, |
| | | { source: 'batch', label: '批次' }, |
| | | { source: 'unit', label: '单位' }, |
| | | { source: 'taskTypeText', label: '任务类型' }, |
| | | { source: 'taskStatusText', label: '任务状态' } |
| | | ] |
| | | } |
| | |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData"> |
| | | <template #left> |
| | | <ElSpace wrap> |
| | | <span v-auth="'list'" class="inline-flex"> |
| | | <ListExportPrint |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :selected-rows="selectedRows" |
| | | :query-params="reportQueryParams" |
| | | :columns="columns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="resolvedPreviewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading" |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </span> |
| | | <ListExportPrint |
| | | :preview-visible="previewVisible" |
| | | @update:previewVisible="handlePreviewVisibleChange" |
| | | :report-title="reportTitle" |
| | | :selected-rows="selectedRows" |
| | | :query-params="reportQueryParams" |
| | | :columns="columns" |
| | | :preview-rows="previewRows" |
| | | :preview-meta="resolvedPreviewMeta" |
| | | :total="pagination.total" |
| | | :disabled="loading || exportTaskLoading" |
| | | export-auth="manager:warehouseAreasItem:export" |
| | | print-auth="list" |
| | | @export="handleExportRequest" |
| | | @print="handlePrint" |
| | | /> |
| | | </ElSpace> |
| | | </template> |
| | | </ArtTableHeader> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useTableColumns } from '@/hooks/core/useTableColumns' |
| | | import { defaultResponseAdapter } from '@/utils/table/tableUtils' |
| | |
| | | import { usePrintExportPage } from '@/views/system/common/usePrintExportPage' |
| | | import ListExportPrint from '@/components/biz/list-export-print/index.vue' |
| | | import { |
| | | fetchCreateWarehouseAreasItemExportTask, |
| | | fetchDownloadWarehouseAreasItemExportTask, |
| | | fetchEnabledFields, |
| | | fetchExportWarehouseAreasItemReport, |
| | | fetchGetWarehouseAreasItemMany, |
| | | fetchWarehouseAreasItemExportTask, |
| | | fetchWarehouseAreasItemIsptPage, |
| | | fetchWarehouseAreasItemPage |
| | | } from '@/api/warehouse-areas-item' |
| | |
| | | |
| | | defineOptions({ name: 'WarehouseAreasItem' }) |
| | | |
| | | const EXPORT_SYNC_MAX_ROWS = 5000 |
| | | const EXPORT_TASK_POLL_INTERVAL = 3000 |
| | | const userStore = useUserStore() |
| | | const reportTitle = WAREHOUSE_AREAS_ITEM_REPORT_TITLE |
| | | const loading = ref(false) |
| | |
| | | const enabledFields = ref([]) |
| | | const selectedRows = ref([]) |
| | | const searchForm = ref(createWarehouseAreasItemSearchState()) |
| | | const exportTaskLoading = ref(false) |
| | | |
| | | const isptDrawerVisible = ref(false) |
| | | const isptLoading = ref(false) |
| | | const isptTableData = ref([]) |
| | | const activeRow = ref({}) |
| | | let exportTaskTimer = null |
| | | |
| | | const pagination = reactive({ |
| | | current: 1, |
| | |
| | | } |
| | | ) |
| | | tableData.value = Array.isArray(response?.records) |
| | | ? response.records.map((record) => normalizeWarehouseAreasItemRow(record, enabledFields.value)) |
| | | ? response.records.map((record) => |
| | | normalizeWarehouseAreasItemRow(record, enabledFields.value) |
| | | ) |
| | | : [] |
| | | updatePaginationState(pagination, response, pagination.current, pagination.size) |
| | | } finally { |
| | |
| | | |
| | | function handleSelectionChange(rows) { |
| | | selectedRows.value = Array.isArray(rows) ? rows : [] |
| | | } |
| | | |
| | | function clearExportTaskTimer() { |
| | | if (exportTaskTimer) { |
| | | clearTimeout(exportTaskTimer) |
| | | exportTaskTimer = null |
| | | } |
| | | } |
| | | |
| | | function getExportRowCount(payload) { |
| | | const selectedIds = Array.isArray(payload?.ids) ? payload.ids.filter(Boolean) : [] |
| | | if (selectedIds.length > 0) { |
| | | return selectedIds.length |
| | | } |
| | | return Number(pagination.total) || 0 |
| | | } |
| | | |
| | | function needsAsyncExport(payload) { |
| | | return getExportRowCount(payload) > EXPORT_SYNC_MAX_ROWS |
| | | } |
| | | |
| | | function scheduleExportTaskPoll(taskId) { |
| | | clearExportTaskTimer() |
| | | exportTaskTimer = setTimeout(() => { |
| | | pollExportTask(taskId) |
| | | }, EXPORT_TASK_POLL_INTERVAL) |
| | | } |
| | | |
| | | function resolveDownloadFileName(task = {}, response) { |
| | | const contentDisposition = response?.headers?.get('Content-Disposition') || '' |
| | | const matchedPart = contentDisposition |
| | | .split(';') |
| | | .map((part) => part.trim()) |
| | | .find((part) => /^filename\*?=/i.test(part)) |
| | | |
| | | if (matchedPart) { |
| | | let fileName = matchedPart.replace(/^filename\*?=/i, '').trim() |
| | | if (fileName.toLowerCase().startsWith("utf-8''")) { |
| | | fileName = fileName.slice(7) |
| | | } |
| | | if (fileName.startsWith('"') && fileName.endsWith('"')) { |
| | | fileName = fileName.slice(1, -1) |
| | | } |
| | | try { |
| | | return decodeURIComponent(fileName) |
| | | } catch { |
| | | return fileName |
| | | } |
| | | } |
| | | return task.fileName || 'warehouse-areas-item.xlsx' |
| | | } |
| | | |
| | | async function downloadExportTaskFile(task) { |
| | | const response = await fetchDownloadWarehouseAreasItemExportTask(task.id, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }) |
| | | if (!response.ok) { |
| | | throw new Error(`导出文件下载失败(${response.status})`) |
| | | } |
| | | |
| | | const blob = await response.blob() |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = resolveDownloadFileName(task, response) |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | link.remove() |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | } |
| | | |
| | | async function pollExportTask(taskId) { |
| | | try { |
| | | const task = await fetchWarehouseAreasItemExportTask(taskId) |
| | | const status = Number(task?.status) |
| | | if (status === 2) { |
| | | clearExportTaskTimer() |
| | | await downloadExportTaskFile(task) |
| | | exportTaskLoading.value = false |
| | | ElMessage.success( |
| | | `导出任务已完成,已开始下载${task?.rowCount ? `(${task.rowCount}行)` : ''}` |
| | | ) |
| | | return |
| | | } |
| | | if (status === 3) { |
| | | clearExportTaskTimer() |
| | | exportTaskLoading.value = false |
| | | ElMessage.error(task?.errorMsg || '导出任务执行失败') |
| | | return |
| | | } |
| | | scheduleExportTaskPoll(taskId) |
| | | } catch (error) { |
| | | clearExportTaskTimer() |
| | | exportTaskLoading.value = false |
| | | ElMessage.error(error?.message || '查询导出任务状态失败') |
| | | } |
| | | } |
| | | |
| | | async function handleExportRequest(payload) { |
| | | if (!needsAsyncExport(payload)) { |
| | | await handleExport(payload) |
| | | return |
| | | } |
| | | |
| | | const exportRowCount = getExportRowCount(payload) |
| | | exportTaskLoading.value = true |
| | | clearExportTaskTimer() |
| | | try { |
| | | const task = await fetchCreateWarehouseAreasItemExportTask(payload) |
| | | ElMessage.success( |
| | | `本次导出共 ${exportRowCount} 行,已超过 ${EXPORT_SYNC_MAX_ROWS} 行,系统已自动切换为后台导出任务${task?.taskCode ? `(${task.taskCode})` : ''}` |
| | | ) |
| | | if (!task?.id) { |
| | | throw new Error('导出任务创建成功,但未返回任务ID') |
| | | } |
| | | scheduleExportTaskPoll(task.id) |
| | | } catch (error) { |
| | | exportTaskLoading.value = false |
| | | clearExportTaskTimer() |
| | | ElMessage.error(error?.message || '创建导出任务失败') |
| | | } |
| | | } |
| | | |
| | | function handleSearch(params) { |
| | |
| | | await fetchWarehouseAreasItemPage({ |
| | | ...reportQueryParams.value, |
| | | current: 1, |
| | | pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20 |
| | | pageSize: |
| | | Number(pagination.total) > 0 |
| | | ? Number(pagination.total) |
| | | : Number(payload?.pageSize) || 20 |
| | | }) |
| | | ).records |
| | | }, |
| | |
| | | buildWarehouseAreasItemReportMeta({ |
| | | previewMeta: previewMeta.value, |
| | | count: previewRows.value.length, |
| | | orientation: previewMeta.value?.reportStyle?.orientation || WAREHOUSE_AREAS_ITEM_REPORT_STYLE.orientation |
| | | orientation: |
| | | previewMeta.value?.reportStyle?.orientation || WAREHOUSE_AREAS_ITEM_REPORT_STYLE.orientation |
| | | }) |
| | | ) |
| | | |
| | |
| | | await loadEnabledFieldDefinitions() |
| | | await loadPageData() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearExportTaskTimer() |
| | | }) |
| | | </script> |
| New file |
| | |
| | | const STATUS_META = { |
| | | 0: { text: '待执行', type: 'info' }, |
| | | 1: { text: '处理中', type: 'warning' }, |
| | | 2: { text: '已完成', type: 'success' }, |
| | | 3: { text: '失败', type: 'danger' } |
| | | } |
| | | |
| | | const RESOURCE_LABEL_MAP = { |
| | | loc: '库位', |
| | | warehouseAreasItem: '收货库存' |
| | | } |
| | | |
| | | function normalizeText(value) { |
| | | return String(value ?? '').trim() |
| | | } |
| | | |
| | | function normalizeNumber(value) { |
| | | if (value === '' || value === null || value === undefined) { |
| | | return undefined |
| | | } |
| | | const numericValue = Number(value) |
| | | return Number.isFinite(numericValue) ? numericValue : undefined |
| | | } |
| | | |
| | | function normalizeDateTime(value) { |
| | | return normalizeText(value) || '--' |
| | | } |
| | | |
| | | export function createExportTaskSearchState() { |
| | | return { |
| | | condition: '', |
| | | resourceKey: '', |
| | | status: '', |
| | | timeStart: '', |
| | | timeEnd: '', |
| | | orderBy: 'createTime desc' |
| | | } |
| | | } |
| | | |
| | | export function getExportTaskPaginationKey() { |
| | | return { |
| | | current: 'current', |
| | | size: 'pageSize' |
| | | } |
| | | } |
| | | |
| | | export function getExportTaskStatusOptions() { |
| | | return Object.entries(STATUS_META).map(([value, meta]) => ({ |
| | | label: meta.text, |
| | | value: Number(value) |
| | | })) |
| | | } |
| | | |
| | | export function getExportTaskResourceOptions() { |
| | | return Object.entries(RESOURCE_LABEL_MAP).map(([value, label]) => ({ |
| | | label, |
| | | value |
| | | })) |
| | | } |
| | | |
| | | export function buildExportTaskSearchParams(params = {}) { |
| | | return { |
| | | ...(normalizeText(params.condition) ? { condition: normalizeText(params.condition) } : {}), |
| | | ...(normalizeText(params.resourceKey) |
| | | ? { resourceKey: normalizeText(params.resourceKey) } |
| | | : {}), |
| | | ...(normalizeNumber(params.status) !== undefined |
| | | ? { status: normalizeNumber(params.status) } |
| | | : {}), |
| | | ...(params.timeStart ? { timeStart: params.timeStart } : {}), |
| | | ...(params.timeEnd ? { timeEnd: params.timeEnd } : {}), |
| | | orderBy: 'createTime desc' |
| | | } |
| | | } |
| | | |
| | | export function buildExportTaskPageQueryParams(params = {}) { |
| | | return { |
| | | current: params.current || 1, |
| | | pageSize: params.pageSize || params.size || 20, |
| | | ...buildExportTaskSearchParams(params) |
| | | } |
| | | } |
| | | |
| | | export function resolveExportTaskResourceLabel(resourceKey) { |
| | | return RESOURCE_LABEL_MAP[normalizeText(resourceKey)] || normalizeText(resourceKey) || '--' |
| | | } |
| | | |
| | | export function normalizeExportTaskRow(record = {}) { |
| | | const statusValue = Number(record.status) |
| | | const statusMeta = STATUS_META[statusValue] || STATUS_META[0] |
| | | |
| | | return { |
| | | ...record, |
| | | id: record.id ?? '--', |
| | | taskCode: normalizeText(record.taskCode) || '--', |
| | | resourceKey: normalizeText(record.resourceKey), |
| | | resourceKeyText: resolveExportTaskResourceLabel(record.resourceKey), |
| | | reportTitle: normalizeText(record.reportTitle) || '--', |
| | | fileName: normalizeText(record.fileName) || '--', |
| | | rowCount: record.rowCount ?? '--', |
| | | statusText: record['status$'] || statusMeta.text, |
| | | statusType: statusMeta.type, |
| | | errorMsg: normalizeText(record.errorMsg) || '--', |
| | | createTimeText: normalizeDateTime(record['createTime$'] || record.createTime), |
| | | updateTimeText: normalizeDateTime(record['updateTime$'] || record.updateTime), |
| | | expireTimeText: normalizeDateTime(record['expireTime$'] || record.expireTime), |
| | | canDownload: statusValue === 2 |
| | | } |
| | | } |
| New file |
| | |
| | | import { h } from 'vue' |
| | | import { ElTag } from 'element-plus' |
| | | import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue' |
| | | |
| | | export function createExportTaskTableColumns({ handleDownload } = {}) { |
| | | return [ |
| | | { type: 'globalIndex', label: '序号', width: 72, align: 'center' }, |
| | | { |
| | | prop: 'taskCode', |
| | | label: '任务编号', |
| | | minWidth: 200, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'resourceKeyText', |
| | | label: '导出资源', |
| | | minWidth: 120, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'reportTitle', |
| | | label: '报表标题', |
| | | minWidth: 180, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'rowCount', |
| | | label: '数据量', |
| | | width: 100, |
| | | align: 'right' |
| | | }, |
| | | { |
| | | prop: 'fileName', |
| | | label: '导出文件', |
| | | minWidth: 240, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'statusText', |
| | | label: '状态', |
| | | width: 110, |
| | | align: 'center', |
| | | formatter: (row) => |
| | | h( |
| | | ElTag, |
| | | { |
| | | type: row?.statusType || 'info', |
| | | effect: 'light' |
| | | }, |
| | | () => row?.statusText || '--' |
| | | ) |
| | | }, |
| | | { |
| | | prop: 'errorMsg', |
| | | label: '错误信息', |
| | | minWidth: 220, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'createTimeText', |
| | | label: '创建时间', |
| | | minWidth: 170, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'updateTimeText', |
| | | label: '更新时间', |
| | | minWidth: 170, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'expireTimeText', |
| | | label: '过期时间', |
| | | minWidth: 170, |
| | | showOverflowTooltip: true |
| | | }, |
| | | { |
| | | prop: 'operation', |
| | | label: '操作', |
| | | width: 92, |
| | | align: 'center', |
| | | fixed: 'right', |
| | | formatter: (row) => |
| | | row?.canDownload |
| | | ? h(ArtButtonTable, { |
| | | icon: 'ri:download-2-line', |
| | | iconClass: 'bg-info/12 text-info', |
| | | onClick: () => handleDownload?.(row) |
| | | }) |
| | | : '--' |
| | | } |
| | | ] |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="export-task-page art-full-height"> |
| | | <ArtSearchBar |
| | | v-model="searchForm" |
| | | :items="searchItems" |
| | | :showExpand="false" |
| | | @search="handleSearch" |
| | | @reset="handleReset" |
| | | /> |
| | | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" /> |
| | | |
| | | <ArtTable |
| | | :loading="loading" |
| | | :data="data" |
| | | :columns="columns" |
| | | :pagination="pagination" |
| | | @pagination:size-change="handleSizeChange" |
| | | @pagination:current-change="handleCurrentChange" |
| | | /> |
| | | </ElCard> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, onBeforeUnmount, ref, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import { useTable } from '@/hooks/core/useTable' |
| | | import { fetchDownloadExportTask, fetchExportTaskPage } from '@/api/system-manage' |
| | | import { |
| | | buildExportTaskPageQueryParams, |
| | | buildExportTaskSearchParams, |
| | | createExportTaskSearchState, |
| | | getExportTaskPaginationKey, |
| | | getExportTaskResourceOptions, |
| | | getExportTaskStatusOptions, |
| | | normalizeExportTaskRow |
| | | } from './exportTaskPage.helpers' |
| | | import { createExportTaskTableColumns } from './exportTaskTable.columns' |
| | | |
| | | defineOptions({ name: 'ExportTask' }) |
| | | |
| | | const AUTO_REFRESH_INTERVAL = 5000 |
| | | |
| | | const userStore = useUserStore() |
| | | const searchForm = ref(createExportTaskSearchState()) |
| | | let autoRefreshTimer = null |
| | | |
| | | const searchItems = computed(() => [ |
| | | { |
| | | label: '关键字', |
| | | key: 'condition', |
| | | type: 'input', |
| | | props: { |
| | | clearable: true, |
| | | placeholder: '请输入任务编号/报表标题/文件名' |
| | | } |
| | | }, |
| | | { |
| | | label: '导出资源', |
| | | key: 'resourceKey', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | filterable: true, |
| | | options: getExportTaskResourceOptions() |
| | | } |
| | | }, |
| | | { |
| | | label: '状态', |
| | | key: 'status', |
| | | type: 'select', |
| | | props: { |
| | | clearable: true, |
| | | options: getExportTaskStatusOptions() |
| | | } |
| | | }, |
| | | { |
| | | label: '开始日期', |
| | | key: 'timeStart', |
| | | type: 'date', |
| | | props: { |
| | | clearable: true, |
| | | type: 'date', |
| | | valueFormat: 'YYYY-MM-DD' |
| | | } |
| | | }, |
| | | { |
| | | label: '结束日期', |
| | | key: 'timeEnd', |
| | | type: 'date', |
| | | props: { |
| | | clearable: true, |
| | | type: 'date', |
| | | valueFormat: 'YYYY-MM-DD' |
| | | } |
| | | } |
| | | ]) |
| | | |
| | | const { |
| | | columns, |
| | | columnChecks, |
| | | data, |
| | | loading, |
| | | pagination, |
| | | getData, |
| | | replaceSearchParams, |
| | | resetSearchParams, |
| | | handleSizeChange, |
| | | handleCurrentChange, |
| | | refreshData |
| | | } = useTable({ |
| | | core: { |
| | | apiFn: fetchExportTaskPage, |
| | | apiParams: buildExportTaskPageQueryParams(searchForm.value), |
| | | paginationKey: getExportTaskPaginationKey(), |
| | | columnsFactory: () => |
| | | createExportTaskTableColumns({ |
| | | handleDownload |
| | | }) |
| | | }, |
| | | transform: { |
| | | dataTransformer: (records) => |
| | | Array.isArray(records) ? records.map((item) => normalizeExportTaskRow(item)) : [] |
| | | } |
| | | }) |
| | | |
| | | function clearAutoRefreshTimer() { |
| | | if (autoRefreshTimer) { |
| | | clearTimeout(autoRefreshTimer) |
| | | autoRefreshTimer = null |
| | | } |
| | | } |
| | | |
| | | function scheduleAutoRefresh() { |
| | | clearAutoRefreshTimer() |
| | | autoRefreshTimer = setTimeout(() => { |
| | | refreshData() |
| | | }, AUTO_REFRESH_INTERVAL) |
| | | } |
| | | |
| | | function resolveDownloadFileName(row = {}, response) { |
| | | const contentDisposition = response?.headers?.get('Content-Disposition') || '' |
| | | const matchedPart = contentDisposition |
| | | .split(';') |
| | | .map((part) => part.trim()) |
| | | .find((part) => /^filename\*?=/i.test(part)) |
| | | |
| | | if (matchedPart) { |
| | | let fileName = matchedPart.replace(/^filename\*?=/i, '').trim() |
| | | if (fileName.toLowerCase().startsWith("utf-8''")) { |
| | | fileName = fileName.slice(7) |
| | | } |
| | | if (fileName.startsWith('"') && fileName.endsWith('"')) { |
| | | fileName = fileName.slice(1, -1) |
| | | } |
| | | try { |
| | | return decodeURIComponent(fileName) |
| | | } catch { |
| | | return fileName |
| | | } |
| | | } |
| | | |
| | | return row.fileName || `${row.taskCode || 'export-task'}.xlsx` |
| | | } |
| | | |
| | | async function handleDownload(row) { |
| | | try { |
| | | const response = await fetchDownloadExportTask(row.id, { |
| | | headers: { |
| | | Authorization: userStore.accessToken || '' |
| | | } |
| | | }) |
| | | if (!response.ok) { |
| | | throw new Error(`导出文件下载失败(${response.status})`) |
| | | } |
| | | |
| | | const blob = await response.blob() |
| | | const downloadUrl = window.URL.createObjectURL(blob) |
| | | const link = document.createElement('a') |
| | | link.href = downloadUrl |
| | | link.download = resolveDownloadFileName(row, response) |
| | | document.body.appendChild(link) |
| | | link.click() |
| | | link.remove() |
| | | window.URL.revokeObjectURL(downloadUrl) |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || '下载导出文件失败') |
| | | } |
| | | } |
| | | |
| | | function handleSearch(params) { |
| | | replaceSearchParams(buildExportTaskSearchParams(params)) |
| | | getData() |
| | | } |
| | | |
| | | function handleReset() { |
| | | Object.assign(searchForm.value, createExportTaskSearchState()) |
| | | resetSearchParams() |
| | | } |
| | | |
| | | watch( |
| | | data, |
| | | (rows) => { |
| | | clearAutoRefreshTimer() |
| | | if (Array.isArray(rows) && rows.some((item) => item?.status === 0 || item?.status === 1)) { |
| | | scheduleAutoRefresh() |
| | | } |
| | | }, |
| | | { |
| | | deep: true |
| | | } |
| | | ) |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearAutoRefreshTimer() |
| | | }) |
| | | </script> |
| | |
| | | :data="scopeState[config.scopeType].treeData" |
| | | node-key="id" |
| | | show-checkbox |
| | | check-strictly |
| | | :default-expand-all="scopeState[config.scopeType].expandAll" |
| | | :default-checked-keys="scopeState[config.scopeType].checkedKeys" |
| | | :props="treeProps" |
| | | @check="handleTreeCheck(config.scopeType)" |
| | | @check-change=" |
| | | (data, checked) => handleTreeCheckChange(config.scopeType, data, checked) |
| | | " |
| | | > |
| | | <template #default="{ data }"> |
| | | <div class="flex items-center gap-2"> |
| | |
| | | halfCheckedKeys: [], |
| | | condition: '', |
| | | expandAll: true, |
| | | treeVersion: 0 |
| | | treeVersion: 0, |
| | | syncingSelection: false |
| | | } |
| | | } |
| | | |
| | |
| | | } finally { |
| | | state.loading = false |
| | | nextTick(() => { |
| | | treeRefs[scopeType]?.setCheckedKeys(scopeState[scopeType].checkedKeys) |
| | | const tree = treeRefs[scopeType] |
| | | if (!tree) return |
| | | state.syncingSelection = true |
| | | tree.setCheckedKeys(state.checkedKeys) |
| | | nextTick(() => { |
| | | state.syncingSelection = false |
| | | }) |
| | | }) |
| | | } |
| | | } |
| | |
| | | scopeState[scopeType].halfCheckedKeys = normalizeScopeKeys(tree.getHalfCheckedKeys()) |
| | | } |
| | | |
| | | const handleTreeCheckChange = (scopeType, data, checked) => { |
| | | const state = scopeState[scopeType] |
| | | if (state.syncingSelection || !data?.id) { |
| | | return |
| | | } |
| | | |
| | | const tree = treeRefs[scopeType] |
| | | if (!tree) { |
| | | return |
| | | } |
| | | |
| | | const nextCheckedKeys = new Set(state.checkedKeys.map((key) => String(key))) |
| | | const currentKey = String(data.id) |
| | | |
| | | if (checked) { |
| | | nextCheckedKeys.add(currentKey) |
| | | getDescendantNodeKeys(data).forEach((key) => nextCheckedKeys.add(key)) |
| | | getParentNodeKeys(state.treeData, currentKey).forEach((key) => nextCheckedKeys.add(key)) |
| | | } else { |
| | | nextCheckedKeys.delete(currentKey) |
| | | getDescendantNodeKeys(data).forEach((key) => nextCheckedKeys.delete(key)) |
| | | |
| | | let currentNodeKey = currentKey |
| | | while (true) { |
| | | const parentKey = getParentNodeKey(state.treeData, currentNodeKey) |
| | | if (!parentKey) { |
| | | break |
| | | } |
| | | |
| | | const siblingKeys = getChildNodeKeys(state.treeData, parentKey) |
| | | const hasCheckedSibling = siblingKeys.some((key) => nextCheckedKeys.has(String(key))) |
| | | if (hasCheckedSibling) { |
| | | break |
| | | } |
| | | |
| | | nextCheckedKeys.delete(String(parentKey)) |
| | | currentNodeKey = String(parentKey) |
| | | } |
| | | } |
| | | |
| | | state.syncingSelection = true |
| | | tree.setCheckedKeys(Array.from(nextCheckedKeys)) |
| | | nextTick(() => { |
| | | handleTreeCheck(scopeType) |
| | | state.syncingSelection = false |
| | | }) |
| | | } |
| | | |
| | | const handleSelectAll = (scopeType) => { |
| | | const tree = treeRefs[scopeType] |
| | | if (!tree) return |
| | | const allKeys = getAllNodeKeys(scopeState[scopeType].treeData) |
| | | tree.setCheckedKeys(allKeys) |
| | | handleTreeCheck(scopeType) |
| | | applyCheckedKeys(scopeType, allKeys) |
| | | } |
| | | |
| | | const handleClear = (scopeType) => { |
| | | const tree = treeRefs[scopeType] |
| | | if (!tree) return |
| | | tree.setCheckedKeys([]) |
| | | handleTreeCheck(scopeType) |
| | | applyCheckedKeys(scopeType, []) |
| | | } |
| | | |
| | | const handleToggleExpand = (scopeType) => { |
| | |
| | | state.expandAll = !state.expandAll |
| | | state.treeVersion += 1 |
| | | nextTick(() => { |
| | | treeRefs[scopeType]?.setCheckedKeys(state.checkedKeys) |
| | | handleTreeCheck(scopeType) |
| | | applyCheckedKeys(scopeType, state.checkedKeys) |
| | | }) |
| | | } |
| | | |
| | |
| | | return keys |
| | | } |
| | | |
| | | const getDescendantNodeKeys = (node) => { |
| | | const keys = [] |
| | | const traverse = (children = []) => { |
| | | children.forEach((child) => { |
| | | if (hasNodeKey(child.id)) { |
| | | keys.push(String(child.id)) |
| | | } |
| | | if (child.children?.length) { |
| | | traverse(child.children) |
| | | } |
| | | }) |
| | | } |
| | | traverse(Array.isArray(node?.children) ? node.children : []) |
| | | return keys |
| | | } |
| | | |
| | | const getParentNodeKeys = (treeData, targetId) => { |
| | | let parentKeys = [] |
| | | const traverse = (nodes, path = []) => { |
| | | for (const node of nodes) { |
| | | if (String(node.id) === String(targetId)) { |
| | | parentKeys = path |
| | | return true |
| | | } |
| | | if (node.children?.length) { |
| | | if (traverse(node.children, [...path, String(node.id)])) { |
| | | return true |
| | | } |
| | | } |
| | | } |
| | | return false |
| | | } |
| | | |
| | | traverse(Array.isArray(treeData) ? treeData : []) |
| | | return parentKeys |
| | | } |
| | | |
| | | const getParentNodeKey = (treeData, targetId) => { |
| | | let parentKey = '' |
| | | const traverse = (nodes) => { |
| | | for (const node of nodes) { |
| | | if (node.children?.length) { |
| | | for (const child of node.children) { |
| | | if (String(child.id) === String(targetId)) { |
| | | parentKey = String(node.id) |
| | | return true |
| | | } |
| | | } |
| | | if (traverse(node.children)) { |
| | | return true |
| | | } |
| | | } |
| | | } |
| | | return false |
| | | } |
| | | |
| | | traverse(Array.isArray(treeData) ? treeData : []) |
| | | return parentKey |
| | | } |
| | | |
| | | const getChildNodeKeys = (treeData, parentId) => { |
| | | let childKeys = [] |
| | | const traverse = (nodes) => { |
| | | for (const node of nodes) { |
| | | if (String(node.id) === String(parentId)) { |
| | | childKeys = Array.isArray(node.children) |
| | | ? node.children.map((child) => String(child.id)) |
| | | : [] |
| | | return true |
| | | } |
| | | if (node.children?.length && traverse(node.children)) { |
| | | return true |
| | | } |
| | | } |
| | | return false |
| | | } |
| | | |
| | | traverse(Array.isArray(treeData) ? treeData : []) |
| | | return childKeys |
| | | } |
| | | |
| | | const applyCheckedKeys = (scopeType, checkedKeys = []) => { |
| | | const state = scopeState[scopeType] |
| | | const tree = treeRefs[scopeType] |
| | | state.checkedKeys = normalizeScopeKeys(checkedKeys) |
| | | state.halfCheckedKeys = [] |
| | | if (!tree) { |
| | | return |
| | | } |
| | | |
| | | state.syncingSelection = true |
| | | tree.setCheckedKeys(state.checkedKeys) |
| | | nextTick(() => { |
| | | handleTreeCheck(scopeType) |
| | | state.syncingSelection = false |
| | | }) |
| | | } |
| | | |
| | | watch( |
| | | () => props.visible, |
| | | async (isVisible) => { |
| New file |
| | |
| | | package com.vincent.rsf.server.common.service; |
| | | |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | |
| | | import java.io.File; |
| | | import java.util.Map; |
| | | import java.util.function.Function; |
| | | |
| | | public interface AsyncListExportTaskService { |
| | | ExportTask createTask( |
| | | String resourceKey, |
| | | String defaultReportTitle, |
| | | Map<String, Object> payload, |
| | | Long tenantId, |
| | | Long userId |
| | | ); |
| | | |
| | | ExportTask getTask(Long taskId, String resourceKey, Long tenantId, Long userId); |
| | | |
| | | ExportTask getTask(Long taskId, Long tenantId, Long userId); |
| | | |
| | | File getDownloadFile(Long taskId, String resourceKey, Long tenantId, Long userId); |
| | | |
| | | File getDownloadFile(Long taskId, Long tenantId, Long userId); |
| | | |
| | | <T, P extends BaseParam> void executeAsync( |
| | | Long taskId, |
| | | String resourceKey, |
| | | Map<String, Object> payload, |
| | | Function<Map<String, Object>, P> paramBuilder, |
| | | ListExportHandler<T, P> exportHandler |
| | | ); |
| | | } |
| | |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.utils.ExcelUtil; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | |
| | | ListExportHandler<T, P> exportHandler, |
| | | HttpServletResponse response |
| | | ) throws Exception { |
| | | ExportWorkbook exportWorkbook = prepareExportWorkbook(map, paramBuilder, exportHandler); |
| | | ExcelUtil.build(exportWorkbook.workbook(), response); |
| | | } |
| | | |
| | | public <T, P extends BaseParam> ExportWorkbook prepareExportWorkbook( |
| | | Map<String, Object> map, |
| | | Function<Map<String, Object>, P> paramBuilder, |
| | | ListExportHandler<T, P> exportHandler |
| | | ) { |
| | | Map<String, Object> sanitizedMap = sanitizeExportMap(map); |
| | | P baseParam = paramBuilder.apply(sanitizedMap); |
| | | List<ExcelUtil.ExportColumn> columns = buildExportColumns(map); |
| | |
| | | .toList(); |
| | | |
| | | ExcelUtil.ExportMeta exportMeta = buildExportMeta(map, rows.size(), exportHandler.defaultReportTitle()); |
| | | ExcelUtil.build(ExcelUtil.create(rows, columns, exportMeta), response); |
| | | return new ExportWorkbook( |
| | | ExcelUtil.create(rows, columns, exportMeta), |
| | | rows.size(), |
| | | exportMeta, |
| | | columns |
| | | ); |
| | | } |
| | | |
| | | private Map<String, Object> sanitizeExportMap(Map<String, Object> map) { |
| | |
| | | } |
| | | return reportStyle; |
| | | } |
| | | |
| | | public record ExportWorkbook( |
| | | Workbook workbook, |
| | | int rowCount, |
| | | ExcelUtil.ExportMeta exportMeta, |
| | | List<ExcelUtil.ExportColumn> columns |
| | | ) { |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.common.service.impl; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.service.AsyncListExportTaskService; |
| | | import com.vincent.rsf.server.common.service.ListExportHandler; |
| | | import com.vincent.rsf.server.common.service.ListExportService; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | import com.vincent.rsf.server.system.service.ExportTaskService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.Instant; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.Date; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.UUID; |
| | | import java.util.function.Function; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AsyncListExportTaskServiceImpl implements AsyncListExportTaskService { |
| | | private final ExportTaskService exportTaskService; |
| | | private final ListExportService listExportService; |
| | | |
| | | @Value("${logging.file.path:logs}") |
| | | private String loggingFilePath; |
| | | |
| | | @Value("${rsf.export-task.expire-days:7}") |
| | | private int exportTaskExpireDays; |
| | | |
| | | @Override |
| | | public ExportTask createTask( |
| | | String resourceKey, |
| | | String defaultReportTitle, |
| | | Map<String, Object> payload, |
| | | Long tenantId, |
| | | Long userId |
| | | ) { |
| | | ExportTask task = new ExportTask(); |
| | | task.setTaskCode(generateTaskCode()); |
| | | task.setResourceKey(resourceKey); |
| | | task.setReportTitle(resolveReportTitle(payload, defaultReportTitle)); |
| | | task.setStatus(ExportTask.STATUS_PENDING); |
| | | task.setPayloadJson(JSON.toJSONString(payload)); |
| | | task.setDeleted(0); |
| | | task.setTenantId(tenantId); |
| | | task.setCreateBy(userId); |
| | | task.setUpdateBy(userId); |
| | | task.setCreateTime(new Date()); |
| | | task.setUpdateTime(new Date()); |
| | | task.setExpireTime(resolveExpireTime()); |
| | | exportTaskService.save(task); |
| | | return task; |
| | | } |
| | | |
| | | @Override |
| | | public ExportTask getTask(Long taskId, String resourceKey, Long tenantId, Long userId) { |
| | | return exportTaskService.getOne( |
| | | buildTaskQuery(taskId, tenantId, userId) |
| | | .eq(ExportTask::getResourceKey, resourceKey), |
| | | false |
| | | ); |
| | | } |
| | | |
| | | @Override |
| | | public ExportTask getTask(Long taskId, Long tenantId, Long userId) { |
| | | return exportTaskService.getOne(buildTaskQuery(taskId, tenantId, userId), false); |
| | | } |
| | | |
| | | @Override |
| | | public File getDownloadFile(Long taskId, String resourceKey, Long tenantId, Long userId) { |
| | | ExportTask task = getTask(taskId, resourceKey, tenantId, userId); |
| | | return resolveDownloadFile(task); |
| | | } |
| | | |
| | | @Override |
| | | public File getDownloadFile(Long taskId, Long tenantId, Long userId) { |
| | | ExportTask task = getTask(taskId, tenantId, userId); |
| | | return resolveDownloadFile(task); |
| | | } |
| | | |
| | | private LambdaQueryWrapper<ExportTask> buildTaskQuery(Long taskId, Long tenantId, Long userId) { |
| | | return new LambdaQueryWrapper<ExportTask>() |
| | | .eq(ExportTask::getId, taskId) |
| | | .eq(ExportTask::getDeleted, 0) |
| | | .eq(ExportTask::getTenantId, tenantId) |
| | | .eq(ExportTask::getCreateBy, userId); |
| | | } |
| | | |
| | | private File resolveDownloadFile(ExportTask task) { |
| | | if (task == null) { |
| | | throw new CoolException("导出任务不存在"); |
| | | } |
| | | if (!Objects.equals(task.getStatus(), ExportTask.STATUS_SUCCESS)) { |
| | | throw new CoolException("导出任务尚未完成"); |
| | | } |
| | | if (Cools.isEmpty(task.getFilePath())) { |
| | | throw new CoolException("导出文件不存在"); |
| | | } |
| | | |
| | | File file = new File(task.getFilePath()); |
| | | if (!file.exists()) { |
| | | throw new CoolException("导出文件不存在"); |
| | | } |
| | | return file; |
| | | } |
| | | |
| | | @Override |
| | | @Async("taskExecutor") |
| | | public <T, P extends BaseParam> void executeAsync( |
| | | Long taskId, |
| | | String resourceKey, |
| | | Map<String, Object> payload, |
| | | Function<Map<String, Object>, P> paramBuilder, |
| | | ListExportHandler<T, P> exportHandler |
| | | ) { |
| | | ExportTask task = exportTaskService.getById(taskId); |
| | | if (task == null) { |
| | | return; |
| | | } |
| | | |
| | | task.setStatus(ExportTask.STATUS_PROCESSING); |
| | | task.setUpdateTime(new Date()); |
| | | exportTaskService.updateById(task); |
| | | |
| | | try { |
| | | ListExportService.ExportWorkbook exportWorkbook = |
| | | listExportService.prepareExportWorkbook(payload, paramBuilder, exportHandler); |
| | | File outputFile = createOutputFile(taskId, resourceKey, task.getReportTitle()); |
| | | writeWorkbook(exportWorkbook.workbook(), outputFile); |
| | | |
| | | task.setStatus(ExportTask.STATUS_SUCCESS); |
| | | task.setRowCount(exportWorkbook.rowCount()); |
| | | task.setFileName(outputFile.getName()); |
| | | task.setFilePath(outputFile.getAbsolutePath()); |
| | | task.setErrorMsg(null); |
| | | task.setUpdateTime(new Date()); |
| | | exportTaskService.updateById(task); |
| | | } catch (Exception error) { |
| | | log.error("异步导出失败, resourceKey={}, taskId={}", resourceKey, taskId, error); |
| | | task.setStatus(ExportTask.STATUS_FAILED); |
| | | task.setErrorMsg(truncateErrorMessage(error)); |
| | | task.setUpdateTime(new Date()); |
| | | exportTaskService.updateById(task); |
| | | } |
| | | } |
| | | |
| | | private String resolveReportTitle(Map<String, Object> payload, String defaultReportTitle) { |
| | | Object metaObject = payload.get("meta"); |
| | | if (metaObject instanceof Map<?, ?> metaMap) { |
| | | Object reportTitle = metaMap.get("reportTitle"); |
| | | if (reportTitle != null && !String.valueOf(reportTitle).trim().isEmpty()) { |
| | | return String.valueOf(reportTitle).trim(); |
| | | } |
| | | } |
| | | return defaultReportTitle; |
| | | } |
| | | |
| | | private String generateTaskCode() { |
| | | return "EXP-" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "-" |
| | | + UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase(); |
| | | } |
| | | |
| | | private Date resolveExpireTime() { |
| | | return Date.from(Instant.now().plus(Math.max(exportTaskExpireDays, 1), ChronoUnit.DAYS)); |
| | | } |
| | | |
| | | private File createOutputFile(Long taskId, String resourceKey, String reportTitle) { |
| | | String safeTitle = sanitizeFileName(reportTitle); |
| | | String safeResourceKey = sanitizePathSegment(resourceKey); |
| | | String dayFolder = new SimpleDateFormat("yyyyMMdd").format(new Date()); |
| | | File dir = new File(loggingFilePath, "export-tasks/" + safeResourceKey + "/" + dayFolder); |
| | | if (!dir.exists() && !dir.mkdirs()) { |
| | | throw new CoolException("导出目录创建失败"); |
| | | } |
| | | |
| | | String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); |
| | | return new File(dir, safeTitle + "-" + taskId + "-" + timestamp + ".xlsx"); |
| | | } |
| | | |
| | | private void writeWorkbook(Workbook workbook, File outputFile) throws IOException { |
| | | try (Workbook targetWorkbook = workbook; FileOutputStream outputStream = new FileOutputStream(outputFile)) { |
| | | targetWorkbook.write(outputStream); |
| | | outputStream.flush(); |
| | | } |
| | | } |
| | | |
| | | private String sanitizeFileName(String fileName) { |
| | | String normalized = Objects.toString(fileName, "导出报表").trim(); |
| | | if (normalized.isEmpty()) { |
| | | normalized = "导出报表"; |
| | | } |
| | | return normalized.replaceAll("[\\\\/:*?\"<>|]", "_"); |
| | | } |
| | | |
| | | private String sanitizePathSegment(String segment) { |
| | | String normalized = Objects.toString(segment, "default").trim(); |
| | | if (Cools.isEmpty(normalized)) { |
| | | normalized = "default"; |
| | | } |
| | | return normalized.replaceAll("[\\\\/:*?\"<>|]", "_"); |
| | | } |
| | | |
| | | private String truncateErrorMessage(Exception error) { |
| | | String message = error == null ? "" : Objects.toString(error.getMessage(), error.getClass().getSimpleName()); |
| | | if (message.length() <= 500) { |
| | | return message; |
| | | } |
| | | return message.substring(0, 500); |
| | | } |
| | | } |
| | |
| | | private static final Pattern EXTEND_FIELD_SOURCE_PATTERN = Pattern.compile("^extendFields\\.\\[(.+)]$"); |
| | | private static final String SEQUENCE_SOURCE = "sequence"; |
| | | private static final String SEQUENCE_LABEL = "序号"; |
| | | private static final int EXCEL_WIDTH_UNIT = 256; |
| | | private static final int MIN_COLUMN_WIDTH_CHARS = 8; |
| | | private static final int MAX_COLUMN_WIDTH_CHARS = 60; |
| | | private static final int COLUMN_WIDTH_PADDING_CHARS = 2; |
| | | |
| | | public static void build(Workbook workbook, HttpServletResponse response) { |
| | | response.reset(); |
| | |
| | | } |
| | | } |
| | | } |
| | | for (int i = 0; i <= fields.length; i++) { |
| | | sheet.autoSizeColumn(i); |
| | | } |
| | | autoFitColumns(sheet, headerIdx, 0); |
| | | |
| | | return workbook; |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | for (int columnIndex = 0; columnIndex < effectiveColumns.size(); columnIndex++) { |
| | | sheet.autoSizeColumn(columnIndex); |
| | | } |
| | | autoFitColumns(sheet, effectiveColumns.size(), header.getRowNum()); |
| | | |
| | | return workbook; |
| | | } |
| | | |
| | | private static void autoFitColumns(Sheet sheet, int columnCount, int startRowIndex) { |
| | | if (sheet == null || columnCount <= 0) { |
| | | return; |
| | | } |
| | | |
| | | DataFormatter formatter = new DataFormatter(Locale.CHINA); |
| | | for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { |
| | | int maxDisplayChars = MIN_COLUMN_WIDTH_CHARS; |
| | | for (int rowIndex = Math.max(startRowIndex, 0); rowIndex <= sheet.getLastRowNum(); rowIndex++) { |
| | | Row row = sheet.getRow(rowIndex); |
| | | if (row == null) { |
| | | continue; |
| | | } |
| | | Cell cell = row.getCell(columnIndex); |
| | | if (cell == null || isMergedCell(sheet, rowIndex, columnIndex)) { |
| | | continue; |
| | | } |
| | | maxDisplayChars = Math.max(maxDisplayChars, getDisplayChars(formatter.formatCellValue(cell))); |
| | | } |
| | | int widthChars = Math.min(MAX_COLUMN_WIDTH_CHARS, maxDisplayChars + COLUMN_WIDTH_PADDING_CHARS); |
| | | sheet.setColumnWidth(columnIndex, widthChars * EXCEL_WIDTH_UNIT); |
| | | } |
| | | } |
| | | |
| | | private static boolean isMergedCell(Sheet sheet, int rowIndex, int columnIndex) { |
| | | for (int index = 0; index < sheet.getNumMergedRegions(); index++) { |
| | | CellRangeAddress region = sheet.getMergedRegion(index); |
| | | if (region.isInRange(rowIndex, columnIndex)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private static int getDisplayChars(String value) { |
| | | if (StringUtils.isBlank(value)) { |
| | | return 0; |
| | | } |
| | | int maxLineChars = 0; |
| | | for (String line : value.split("\\R", -1)) { |
| | | maxLineChars = Math.max(maxLineChars, getLineDisplayChars(line)); |
| | | } |
| | | return maxLineChars; |
| | | } |
| | | |
| | | private static int getLineDisplayChars(String value) { |
| | | int width = 0; |
| | | for (int index = 0; index < value.length(); ) { |
| | | int codePoint = value.codePointAt(index); |
| | | width += isWideCodePoint(codePoint) ? 2 : 1; |
| | | index += Character.charCount(codePoint); |
| | | } |
| | | return width; |
| | | } |
| | | |
| | | private static boolean isWideCodePoint(int codePoint) { |
| | | Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); |
| | | return script == Character.UnicodeScript.HAN |
| | | || script == Character.UnicodeScript.HIRAGANA |
| | | || script == Character.UnicodeScript.KATAKANA |
| | | || script == Character.UnicodeScript.HANGUL; |
| | | } |
| | | |
| | | private static List<ExportColumn> buildEffectiveColumns(List<ExportColumn> columns, ExportMeta exportMeta) { |
| | | List<ExportColumn> effectiveColumns = new ArrayList<>(columns); |
| | | if (exportMeta != null && exportMeta.isShowSequence() && !containsSequenceColumn(columns)) { |
| | |
| | | import com.vincent.rsf.framework.common.Cools; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.common.utils.ExcelUtil; |
| | | import com.vincent.rsf.server.common.annotation.OperationLog; |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.domain.KeyValVo; |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.service.AsyncListExportTaskService; |
| | | import com.vincent.rsf.server.common.service.ListExportHandler; |
| | | import com.vincent.rsf.server.common.service.ListExportService; |
| | | import com.vincent.rsf.server.common.utils.ExcelUtil; |
| | | import com.vincent.rsf.server.common.utils.FileServerUtil; |
| | | import com.vincent.rsf.server.common.utils.OptimisticLockUtils; |
| | | import com.vincent.rsf.server.manager.controller.params.LocMastInitParam; |
| | | import com.vincent.rsf.server.manager.controller.params.LocModifyParams; |
| | |
| | | import com.vincent.rsf.server.manager.service.LocService; |
| | | import com.vincent.rsf.server.manager.utils.buildPageRowsUtils; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.BeanWrapper; |
| | | import org.springframework.beans.BeanWrapperImpl; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import jakarta.validation.Valid; |
| | | import java.io.File; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | @Api(tags = "库位信息") |
| | | @RestController |
| | | public class LocController extends BaseController { |
| | | private static final String EXPORT_RESOURCE_KEY = "loc"; |
| | | private static final String EXPORT_DEFAULT_REPORT_TITLE = "库位报表"; |
| | | |
| | | @Autowired |
| | | private LocService locService; |
| | | |
| | | @Autowired |
| | | private ListExportService listExportService; |
| | | |
| | | @Autowired |
| | | private AsyncListExportTaskService asyncListExportTaskService; |
| | | |
| | | private final ListExportHandler<Loc, BaseParam> locExportHandler = new ListExportHandler<>() { |
| | | @Override |
| | | public List<Loc> listByIds(List<Long> ids) { |
| | | return locService.listByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public List<Loc> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) { |
| | | PageParam<Loc, BaseParam> pageParam = new PageParam<>(baseParam, Loc.class); |
| | | return locService.list(pageParam.buildWrapper(true, getLocSortedFields())); |
| | | } |
| | | |
| | | @Override |
| | | public void fillExportFields(List<Loc> records) { |
| | | buildPageRowsUtils.rowsMap(records); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> toExportRow(Loc record, List<ExcelUtil.ExportColumn> columns) { |
| | | return buildLocExportRow(record, columns); |
| | | } |
| | | |
| | | @Override |
| | | public String defaultReportTitle() { |
| | | return EXPORT_DEFAULT_REPORT_TITLE; |
| | | } |
| | | }; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:loc:list')") |
| | | @PostMapping("/loc/page") |
| | | public R page(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<Loc, BaseParam> pageParam = new PageParam<>(baseParam, Loc.class); |
| | | List<String> list = new ArrayList<>(); |
| | | list.add("row"); |
| | | list.add("col"); |
| | | list.add("lev"); |
| | | PageParam<Loc, BaseParam> page = locService.page(pageParam, pageParam.buildWrapper(true,list)); |
| | | PageParam<Loc, BaseParam> page = locService.page(pageParam, pageParam.buildWrapper(true, getLocSortedFields())); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | | |
| | |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(vos)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:loc:list')") |
| | | @PreAuthorize("hasAuthority('manager:loc:export')") |
| | | @ApiOperation("库位导出") |
| | | @PostMapping("/loc/export") |
| | | public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | List<Loc> locs = new ArrayList<>(); |
| | | if (Objects.isNull(map.get("ids"))) { |
| | | locs = locService.list(); |
| | | } else { |
| | | locs = locService.list(new LambdaQueryWrapper<Loc>().eq(Loc::getStatus, 1)); |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | locExportHandler, |
| | | response |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:loc:export')") |
| | | @PostMapping("/loc/export/async") |
| | | public R createAsyncExportTask(@RequestBody Map<String, Object> map) { |
| | | ExportTask task = asyncListExportTaskService.createTask( |
| | | EXPORT_RESOURCE_KEY, |
| | | EXPORT_DEFAULT_REPORT_TITLE, |
| | | map, |
| | | getTenantId(), |
| | | getLoginUserId() |
| | | ); |
| | | asyncListExportTaskService.executeAsync( |
| | | task.getId(), |
| | | EXPORT_RESOURCE_KEY, |
| | | new HashMap<>(map), |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | locExportHandler |
| | | ); |
| | | return R.ok("导出任务已创建").add(buildPageRowsUtils.rowsMap(task)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:loc:export')") |
| | | @GetMapping("/loc/export/task/{taskId}") |
| | | public R getExportTask(@PathVariable("taskId") Long taskId) { |
| | | ExportTask task = asyncListExportTaskService.getTask( |
| | | taskId, |
| | | EXPORT_RESOURCE_KEY, |
| | | getTenantId(), |
| | | getLoginUserId() |
| | | ); |
| | | if (task == null) { |
| | | return R.error("导出任务不存在"); |
| | | } |
| | | ExcelUtil.build(ExcelUtil.create(buildPageRowsUtils.rowsMap(locs), Loc.class), response); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(task)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:loc:export')") |
| | | @GetMapping("/loc/export/task/{taskId}/download") |
| | | public void downloadExportTask( |
| | | @PathVariable("taskId") Long taskId, |
| | | HttpServletResponse response, |
| | | HttpServletRequest request |
| | | ) { |
| | | File file = asyncListExportTaskService.getDownloadFile( |
| | | taskId, |
| | | EXPORT_RESOURCE_KEY, |
| | | getTenantId(), |
| | | getLoginUserId() |
| | | ); |
| | | FileServerUtil.preview(file, true, file.getName(), null, null, response, request); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:loc:update')") |
| | |
| | | return locService.initLocs(param, getLoginUserId()); |
| | | } |
| | | |
| | | private List<String> getLocSortedFields() { |
| | | return Arrays.asList("row", "col", "lev"); |
| | | } |
| | | |
| | | private Map<String, Object> buildLocExportRow(Loc record, List<ExcelUtil.ExportColumn> columns) { |
| | | BeanWrapper beanWrapper = new BeanWrapperImpl(record); |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("warehouseName", record.getWarehouseId$()); |
| | | row.put("areaName", record.getAreaId$()); |
| | | row.put("typeIdsText", record.getTypeIds$()); |
| | | row.put("useStatus", normalizeExportText(record.getUseStatus$())); |
| | | row.put("flagLogicText", toYesNoText(record.getFlagLogic())); |
| | | row.put("flagLabelMangeText", toYesNoText(record.getFlagLabelMange())); |
| | | row.put("status", record.getStatus$()); |
| | | row.put("updateTimeText", record.getUpdateTime$()); |
| | | |
| | | for (ExcelUtil.ExportColumn column : columns) { |
| | | if (row.containsKey(column.getSource())) { |
| | | continue; |
| | | } |
| | | if (beanWrapper.isReadableProperty(column.getSource())) { |
| | | row.put(column.getSource(), beanWrapper.getPropertyValue(column.getSource())); |
| | | } |
| | | } |
| | | |
| | | return row; |
| | | } |
| | | |
| | | private String toYesNoText(Object value) { |
| | | if (value == null) { |
| | | return ""; |
| | | } |
| | | if (Objects.equals(value, 1) || Objects.equals(value, (short) 1) || Objects.equals(value, true)) { |
| | | return "是"; |
| | | } |
| | | if (Objects.equals(value, 0) || Objects.equals(value, (short) 0) || Objects.equals(value, false)) { |
| | | return "否"; |
| | | } |
| | | return String.valueOf(value); |
| | | } |
| | | |
| | | private String normalizeExportText(String value) { |
| | | return value == null ? "" : value.trim(); |
| | | } |
| | | |
| | | } |
| | |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.domain.KeyValVo; |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.service.ListExportHandler; |
| | | import com.vincent.rsf.server.common.service.ListExportService; |
| | | import com.vincent.rsf.server.common.utils.ExcelUtil; |
| | | import com.vincent.rsf.server.common.utils.FieldsUtils; |
| | | import com.vincent.rsf.server.manager.controller.params.LocToTaskParams; |
| | |
| | | import com.vincent.rsf.server.manager.utils.buildPageRowsUtils; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.BeanWrapper; |
| | | import org.springframework.beans.BeanWrapperImpl; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | |
| | | private LocItemService locItemService; |
| | | @Autowired |
| | | private LocService locService; |
| | | @Autowired |
| | | private ListExportService listExportService; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | private final ListExportHandler<LocItem, BaseParam> locDeadReportExportHandler = new ListExportHandler<>() { |
| | | @Override |
| | | public List<LocItem> listByIds(List<Long> ids) { |
| | | return locItemService.listByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public List<LocItem> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) { |
| | | PageParam<LocItem, BaseParam> pageParam = new PageParam<>(baseParam, LocItem.class); |
| | | return locItemService.list(pageParam.buildWrapper(true)); |
| | | } |
| | | |
| | | @Override |
| | | public void fillExportFields(List<LocItem> records) { |
| | | fillLocItemExtendFields(records); |
| | | buildPageRowsUtils.rowsMap(records); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> toExportRow(LocItem record, List<ExcelUtil.ExportColumn> columns) { |
| | | return buildLocDeadReportExportRow(record, columns); |
| | | } |
| | | |
| | | @Override |
| | | public String defaultReportTitle() { |
| | | return "库存停滞报表"; |
| | | } |
| | | }; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/locDeadReport/page") |
| | | public R page(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | |
| | | QueryWrapper<LocItem> wrapper = pageParam.buildWrapper(true); |
| | | /**拼接扩展字段*/ |
| | | PageParam<LocItem, BaseParam> page = locItemService.page(pageParam, wrapper); |
| | | List<LocItem> records = page.getRecords(); |
| | | for (LocItem record : records) { |
| | | if (!Objects.isNull(record.getFieldsIndex())) { |
| | | Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex()); |
| | | record.setExtendFields(fields); |
| | | } |
| | | } |
| | | page.setRecords(records); |
| | | fillLocItemExtendFields(page.getRecords()); |
| | | |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/locDeadReport/useO/page") |
| | | public R locUseOPage(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | |
| | | locItemQueryWrapper.apply(applySql); |
| | | /**拼接扩展字段*/ |
| | | PageParam<LocItem, BaseParam> page = locItemService.page(pageParam, locItemQueryWrapper); |
| | | List<LocItem> records = page.getRecords(); |
| | | for (LocItem record : records) { |
| | | if (!Objects.isNull(record.getFieldsIndex())) { |
| | | Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex()); |
| | | record.setExtendFields(fields); |
| | | } |
| | | } |
| | | page.setRecords(records); |
| | | fillLocItemExtendFields(page.getRecords()); |
| | | |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | |
| | | * @param param |
| | | * @return |
| | | */ |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @ApiOperation("生成库存出库任务") |
| | | @PostMapping("/locDeadReport/generate/task") |
| | | public R generateTask(@RequestBody LocToTaskParams param) { |
| | |
| | | * @param map |
| | | * @return |
| | | */ |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @ApiOperation("生成盘点出库任务") |
| | | @PostMapping("/locDeadReport/check/task") |
| | | public R genStatisticalTask(@RequestBody LocToTaskParams map) { |
| | |
| | | } |
| | | |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/locDeadReport/list") |
| | | public R list(@RequestBody Map<String, Object> map) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(locItemService.list())); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping({"/locDeadReport/many/{ids}", "/locDeadReport/many/{ids}"}) |
| | | public R many(@PathVariable Long[] ids) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(locItemService.listByIds(Arrays.asList(ids)))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @GetMapping("/locDeadReport/{id}") |
| | | public R get(@PathVariable("id") Long id) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(locItemService.getById(id))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:save')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:save')") |
| | | @OperationLog("Create 库位明细") |
| | | @PostMapping("/locDeadReport/save") |
| | | public R save(@RequestBody LocItem locItem) { |
| | |
| | | return R.ok("Save Success").add(buildPageRowsUtils.rowsMap(locItem)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:update')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:update')") |
| | | @OperationLog("Update 库位明细") |
| | | @PostMapping("/locDeadReport/update") |
| | | public R update(@RequestBody LocItem locItem) { |
| | |
| | | return R.ok("Update Success").add(buildPageRowsUtils.rowsMap(locItem)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:remove')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:remove')") |
| | | @OperationLog("Delete 库位明细") |
| | | @PostMapping("/locDeadReport/remove/{ids}") |
| | | public R remove(@PathVariable Long[] ids) { |
| | |
| | | return R.ok("Delete Success").add(buildPageRowsUtils.rowsMap(ids)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/locDeadReport/query") |
| | | public R query(@RequestParam(required = false) String condition) { |
| | | List<KeyValVo> vos = new ArrayList<>(); |
| | |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(vos)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:statisticReport:list')") |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/locDeadReport/export") |
| | | public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | ExcelUtil.build(ExcelUtil.create(buildPageRowsUtils.rowsMap(locItemService.list()), LocItem.class), response); |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | locDeadReportExportHandler, |
| | | response |
| | | ); |
| | | } |
| | | |
| | | private void fillLocItemExtendFields(List<LocItem> records) { |
| | | for (LocItem record : records) { |
| | | if (!Objects.isNull(record.getFieldsIndex())) { |
| | | Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex()); |
| | | record.setExtendFields(fields); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Map<String, Object> buildLocDeadReportExportRow(LocItem record, List<ExcelUtil.ExportColumn> columns) { |
| | | BeanWrapper beanWrapper = new BeanWrapperImpl(record); |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("deadTime", record.getDeadTime()); |
| | | row.put("typeText", record.getType$()); |
| | | row.put("wkTypeText", record.getWkType$()); |
| | | row.put("statusText", record.getStatus$()); |
| | | row.put("createByText", record.getCreateBy$()); |
| | | row.put("createTimeText", record.getCreateTime$()); |
| | | row.put("updateByText", record.getUpdateBy$()); |
| | | row.put("updateTimeText", record.getUpdateTime$()); |
| | | |
| | | for (ExcelUtil.ExportColumn column : columns) { |
| | | if (row.containsKey(column.getSource())) { |
| | | continue; |
| | | } |
| | | if (beanWrapper.isReadableProperty(column.getSource())) { |
| | | row.put(column.getSource(), beanWrapper.getPropertyValue(column.getSource())); |
| | | } |
| | | } |
| | | return row; |
| | | } |
| | | |
| | | } |
| | |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.domain.KeyValVo; |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.service.ListExportHandler; |
| | | import com.vincent.rsf.server.common.service.ListExportService; |
| | | import com.vincent.rsf.server.manager.entity.StockStatistic; |
| | | import com.vincent.rsf.server.manager.enums.TaskStsType; |
| | | import com.vincent.rsf.server.manager.enums.TaskType; |
| | | import com.vincent.rsf.server.manager.service.StockStatisticService; |
| | | import com.vincent.rsf.server.manager.utils.buildPageRowsUtils; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import org.springframework.beans.BeanWrapper; |
| | | import org.springframework.beans.BeanWrapperImpl; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import java.util.*; |
| | | import java.util.function.Function; |
| | | |
| | | @RestController |
| | | public class StockStatisticController extends BaseController { |
| | | |
| | | @Autowired |
| | | private StockStatisticService stockStatisticService; |
| | | @Autowired |
| | | private ListExportService listExportService; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/stockStatistic/page") |
| | |
| | | public R outStatisticPage(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class); |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, day_time, task_type, task_status, " + |
| | | "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, " + |
| | | "SUM(anfme) AS anfme, MAX(unit) AS unit"); |
| | | wrapper.groupBy("day_time, task_type, task_status, matnr_code"); |
| | | QueryWrapper<StockStatistic> wrapper = buildStatisticSummaryWrapper(pageParam); |
| | | PageParam<StockStatistic, BaseParam> page = stockStatisticService.page(pageParam, wrapper); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | |
| | | public R inStatisticPage(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class); |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, day_time, task_type, task_status, " + |
| | | "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, " + |
| | | "SUM(anfme) AS anfme, MAX(unit) AS unit"); |
| | | wrapper.groupBy("day_time, task_type, task_status, matnr_code"); |
| | | QueryWrapper<StockStatistic> wrapper = buildStatisticSummaryWrapper(pageParam); |
| | | PageParam<StockStatistic, BaseParam> page = stockStatisticService.page(pageParam, wrapper); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | |
| | | public R inStockItemPage(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class); |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, loc_code, day_time, task_type, task_status, barcode, " + |
| | | "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, SUM(anfme) AS anfme, " + |
| | | "MAX(unit) AS unit, create_by, update_by, create_time, update_time"); |
| | | wrapper.groupBy("loc_code, day_time, task_type, task_status, barcode, matnr_code, create_by, update_by, create_time, update_time"); |
| | | QueryWrapper<StockStatistic> wrapper = buildStatisticItemWrapper(pageParam); |
| | | PageParam<StockStatistic, BaseParam> page = stockStatisticService.page(pageParam, wrapper); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | |
| | | public R outStockItemPage(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class); |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, loc_code, day_time, task_type, task_status, barcode, " + |
| | | "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, SUM(anfme) AS anfme, " + |
| | | "MAX(unit) AS unit, create_by, update_by, create_time, update_time"); |
| | | wrapper.groupBy("loc_code, day_time, task_type, task_status, barcode, matnr_code, create_by, update_by, create_time, update_time"); |
| | | QueryWrapper<StockStatistic> wrapper = buildStatisticItemWrapper(pageParam); |
| | | PageParam<StockStatistic, BaseParam> page = stockStatisticService.page(pageParam, wrapper); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | |
| | | public R statisticNumPage(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class); |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, day_time, COUNT(barcode) AS `count`, " + |
| | | "SUM( anfme ) anfme," + |
| | | "COUNT(IF (task_type = 1, 0, NULL)) in_anfme_count, " + |
| | | "COUNT(IF ( task_type = 101, 0, NULL)) out_anfme_count, " + |
| | | "SUM( CASE WHEN task_type = 1 THEN anfme ELSE 0 END ) in_anfme," + |
| | | "SUM( CASE WHEN task_type = 101 THEN anfme ELSE 0 END ) out_anfme"); |
| | | wrapper.in("task_type", Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type)).groupBy("day_time"); |
| | | QueryWrapper<StockStatistic> wrapper = buildStatisticNumWrapper(pageParam); |
| | | PageParam<StockStatistic, BaseParam> page = stockStatisticService.page(pageParam, wrapper); |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(page)); |
| | | } |
| | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping({"/stockStatistic/many/{ids}", "/stockStatistics/many/{ids}"}) |
| | | public R many(@PathVariable Long[] ids) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(stockStatisticService.listByIds(Arrays.asList(ids)))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/outStatistic/many/{ids}") |
| | | public R outStatisticMany(@PathVariable Long[] ids) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(stockStatisticService.listByIds(Arrays.asList(ids)))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/inStatistic/many/{ids}") |
| | | public R inStatisticMany(@PathVariable Long[] ids) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(stockStatisticService.listByIds(Arrays.asList(ids)))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/inStatisticItem/many/{ids}") |
| | | public R inStatisticItemMany(@PathVariable Long[] ids) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(stockStatisticService.listByIds(Arrays.asList(ids)))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/outStatisticItem/many/{ids}") |
| | | public R outStatisticItemMany(@PathVariable Long[] ids) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(stockStatisticService.listByIds(Arrays.asList(ids)))); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/statistic/num/many/{ids}") |
| | | public R statisticNumMany(@PathVariable Long[] ids) { |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(stockStatisticService.listByIds(Arrays.asList(ids)))); |
| | | } |
| | | |
| | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/stockStatistic/export") |
| | | public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | ExcelUtil.build(ExcelUtil.create(buildPageRowsUtils.rowsMap(stockStatisticService.list()), StockStatistic.class), response); |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | createStockStatisticExportHandler("日库存统计报表", pageParam -> pageParam.buildWrapper(true)), |
| | | response |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/inStatistic/export") |
| | | public void inStatisticExport(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | createStockStatisticExportHandler("日入库汇总查询", this::buildStatisticSummaryWrapper), |
| | | response |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/outStatistic/export") |
| | | public void outStatisticExport(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | createStockStatisticExportHandler("日出库汇总查询", this::buildStatisticSummaryWrapper), |
| | | response |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/inStatisticItem/export") |
| | | public void inStatisticItemExport(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | createStockStatisticExportHandler("日入库明细查询", this::buildStatisticItemWrapper), |
| | | response |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/outStatisticItem/export") |
| | | public void outStatisticItemExport(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | createStockStatisticExportHandler("日出库明细查询", this::buildStatisticItemWrapper), |
| | | response |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:stockStatistic:list')") |
| | | @PostMapping("/statistic/num/export") |
| | | public void statisticNumExport(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | listExportService.export( |
| | | map, |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | createStockStatisticExportHandler("日出入库汇总统计", this::buildStatisticNumWrapper), |
| | | response |
| | | ); |
| | | } |
| | | |
| | | private QueryWrapper<StockStatistic> buildStatisticSummaryWrapper(PageParam<StockStatistic, BaseParam> pageParam) { |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, day_time, task_type, task_status, " + |
| | | "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, " + |
| | | "SUM(anfme) AS anfme, MAX(unit) AS unit"); |
| | | wrapper.groupBy("day_time, task_type, task_status, matnr_code"); |
| | | return wrapper; |
| | | } |
| | | |
| | | private QueryWrapper<StockStatistic> buildStatisticItemWrapper(PageParam<StockStatistic, BaseParam> pageParam) { |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, loc_code, day_time, task_type, task_status, barcode, " + |
| | | "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, SUM(anfme) AS anfme, " + |
| | | "MAX(unit) AS unit, create_by, update_by, create_time, update_time"); |
| | | wrapper.groupBy("loc_code, day_time, task_type, task_status, barcode, matnr_code, create_by, update_by, create_time, update_time"); |
| | | return wrapper; |
| | | } |
| | | |
| | | private QueryWrapper<StockStatistic> buildStatisticNumWrapper(PageParam<StockStatistic, BaseParam> pageParam) { |
| | | QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true); |
| | | wrapper.select("MIN(id) AS id, day_time, COUNT(barcode) AS `count`, " + |
| | | "SUM( anfme ) anfme," + |
| | | "COUNT(IF (task_type = 1, 0, NULL)) in_anfme_count, " + |
| | | "COUNT(IF ( task_type = 101, 0, NULL)) out_anfme_count, " + |
| | | "SUM( CASE WHEN task_type = 1 THEN anfme ELSE 0 END ) in_anfme," + |
| | | "SUM( CASE WHEN task_type = 101 THEN anfme ELSE 0 END ) out_anfme"); |
| | | wrapper.in("task_type", Arrays.asList(TaskType.TASK_TYPE_IN.type, TaskType.TASK_TYPE_OUT.type)).groupBy("day_time"); |
| | | return wrapper; |
| | | } |
| | | |
| | | private ListExportHandler<StockStatistic, BaseParam> createStockStatisticExportHandler( |
| | | String reportTitle, |
| | | Function<PageParam<StockStatistic, BaseParam>, QueryWrapper<StockStatistic>> wrapperBuilder |
| | | ) { |
| | | return new ListExportHandler<>() { |
| | | @Override |
| | | public List<StockStatistic> listByIds(List<Long> ids) { |
| | | return stockStatisticService.listByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public List<StockStatistic> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) { |
| | | PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class); |
| | | return stockStatisticService.list(wrapperBuilder.apply(pageParam)); |
| | | } |
| | | |
| | | @Override |
| | | public void fillExportFields(List<StockStatistic> records) { |
| | | buildPageRowsUtils.rowsMap(records); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> toExportRow(StockStatistic record, List<ExcelUtil.ExportColumn> columns) { |
| | | return buildStockStatisticExportRow(record, columns); |
| | | } |
| | | |
| | | @Override |
| | | public String defaultReportTitle() { |
| | | return reportTitle; |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private Map<String, Object> buildStockStatisticExportRow(StockStatistic record, List<ExcelUtil.ExportColumn> columns) { |
| | | BeanWrapper beanWrapper = new BeanWrapperImpl(record); |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("dayTimeText", record.getDayTime()); |
| | | row.put("taskTypeText", record.getTaskType$()); |
| | | row.put("taskStatusText", getTaskStatusText(record.getTaskStatus())); |
| | | row.put("locCode", record.getLocCode()); |
| | | row.put("barcode", record.getBarcode()); |
| | | row.put("matnrCode", record.getMatnrCode()); |
| | | row.put("maktx", record.getMaktx()); |
| | | row.put("batch", record.getBatch()); |
| | | row.put("unit", record.getUnit()); |
| | | row.put("anfme", record.getAnfme()); |
| | | row.put("createByText", record.getCreateBy$()); |
| | | row.put("createTimeText", record.getCreateTime$()); |
| | | row.put("updateByText", record.getUpdateBy$()); |
| | | row.put("updateTimeText", record.getUpdateTime$()); |
| | | |
| | | for (ExcelUtil.ExportColumn column : columns) { |
| | | if (row.containsKey(column.getSource())) { |
| | | continue; |
| | | } |
| | | if (beanWrapper.isReadableProperty(column.getSource())) { |
| | | row.put(column.getSource(), beanWrapper.getPropertyValue(column.getSource())); |
| | | } |
| | | } |
| | | return row; |
| | | } |
| | | |
| | | private String getTaskStatusText(Integer taskStatus) { |
| | | if (taskStatus == null) { |
| | | return ""; |
| | | } |
| | | for (TaskStsType type : TaskStsType.values()) { |
| | | if (Objects.equals(type.id, taskStatus)) { |
| | | return type.desc; |
| | | } |
| | | } |
| | | return String.valueOf(taskStatus); |
| | | } |
| | | |
| | | } |
| | |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.domain.KeyValVo; |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.service.AsyncListExportTaskService; |
| | | import com.vincent.rsf.server.common.service.ListExportHandler; |
| | | import com.vincent.rsf.server.common.service.ListExportService; |
| | | import com.vincent.rsf.server.common.utils.ExcelUtil; |
| | | import com.vincent.rsf.server.common.utils.FileServerUtil; |
| | | import com.vincent.rsf.server.common.utils.FieldsUtils; |
| | | import com.vincent.rsf.server.manager.entity.WarehouseAreasItem; |
| | | import com.vincent.rsf.server.manager.service.WarehouseAreasItemService; |
| | | import com.vincent.rsf.server.manager.utils.buildPageRowsUtils; |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | import io.swagger.annotations.Api; |
| | | import org.springframework.beans.BeanWrapper; |
| | | import org.springframework.beans.BeanWrapperImpl; |
| | |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | | import java.io.File; |
| | | import java.util.*; |
| | | |
| | | @Api(tags = "库区库存明细") |
| | | @RestController |
| | | public class WarehouseAreasItemController extends BaseController { |
| | | private static final String EXPORT_RESOURCE_KEY = "warehouseAreasItem"; |
| | | private static final String EXPORT_DEFAULT_REPORT_TITLE = "收货库存报表"; |
| | | |
| | | @Autowired |
| | | private WarehouseAreasItemService warehouseAreasItemService; |
| | | |
| | | @Autowired |
| | | private ListExportService listExportService; |
| | | |
| | | @Autowired |
| | | private AsyncListExportTaskService asyncListExportTaskService; |
| | | |
| | | private final ListExportHandler<WarehouseAreasItem, BaseParam> warehouseAreasItemExportHandler = new ListExportHandler<>() { |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public String defaultReportTitle() { |
| | | return "收货库存报表"; |
| | | return EXPORT_DEFAULT_REPORT_TITLE; |
| | | } |
| | | }; |
| | | |
| | |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(vos)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:warehouseAreasItem:list')") |
| | | @PreAuthorize("hasAuthority('manager:warehouseAreasItem:export')") |
| | | @PostMapping("/warehouseAreasItem/export") |
| | | public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | listExportService.export( |
| | |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:warehouseAreasItem:export')") |
| | | @PostMapping("/warehouseAreasItem/export/async") |
| | | public R createAsyncExportTask(@RequestBody Map<String, Object> map) { |
| | | ExportTask task = asyncListExportTaskService.createTask( |
| | | EXPORT_RESOURCE_KEY, |
| | | EXPORT_DEFAULT_REPORT_TITLE, |
| | | map, |
| | | getTenantId(), |
| | | getLoginUserId() |
| | | ); |
| | | asyncListExportTaskService.executeAsync( |
| | | task.getId(), |
| | | EXPORT_RESOURCE_KEY, |
| | | new HashMap<>(map), |
| | | exportMap -> buildParam(exportMap, BaseParam.class), |
| | | warehouseAreasItemExportHandler |
| | | ); |
| | | return R.ok("导出任务已创建").add(buildPageRowsUtils.rowsMap(task)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:warehouseAreasItem:export')") |
| | | @GetMapping("/warehouseAreasItem/export/task/{taskId}") |
| | | public R getExportTask(@PathVariable("taskId") Long taskId) { |
| | | ExportTask task = asyncListExportTaskService.getTask( |
| | | taskId, |
| | | EXPORT_RESOURCE_KEY, |
| | | getTenantId(), |
| | | getLoginUserId() |
| | | ); |
| | | if (task == null) { |
| | | return R.error("导出任务不存在"); |
| | | } |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(task)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:warehouseAreasItem:export')") |
| | | @GetMapping("/warehouseAreasItem/export/task/{taskId}/download") |
| | | public void downloadExportTask( |
| | | @PathVariable("taskId") Long taskId, |
| | | HttpServletResponse response, |
| | | HttpServletRequest request |
| | | ) { |
| | | File file = asyncListExportTaskService.getDownloadFile( |
| | | taskId, |
| | | EXPORT_RESOURCE_KEY, |
| | | getTenantId(), |
| | | getLoginUserId() |
| | | ); |
| | | FileServerUtil.preview(file, true, file.getName(), null, null, response, request); |
| | | } |
| | | |
| | | private void fillExtendFields(List<WarehouseAreasItem> records) { |
| | | for (WarehouseAreasItem record : records) { |
| | | if (!Objects.isNull(record.getFieldsIndex())) { |
| New file |
| | |
| | | package com.vincent.rsf.server.system.controller; |
| | | |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.service.AsyncListExportTaskService; |
| | | import com.vincent.rsf.server.common.utils.FileServerUtil; |
| | | import com.vincent.rsf.server.manager.utils.buildPageRowsUtils; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | import com.vincent.rsf.server.system.service.ExportTaskService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import java.io.File; |
| | | import java.util.Map; |
| | | |
| | | @RestController |
| | | public class ExportTaskController extends BaseController { |
| | | |
| | | @Autowired |
| | | private ExportTaskService exportTaskService; |
| | | |
| | | @Autowired |
| | | private AsyncListExportTaskService asyncListExportTaskService; |
| | | |
| | | @PreAuthorize("hasAuthority('system:exportTask:list')") |
| | | @PostMapping("/exportTask/page") |
| | | public R page(@RequestBody Map<String, Object> map) { |
| | | BaseParam baseParam = buildParam(map, BaseParam.class); |
| | | if (baseParam.getOrderBy() == null || baseParam.getOrderBy().trim().isEmpty()) { |
| | | baseParam.setOrderBy("create_time desc"); |
| | | } |
| | | |
| | | PageParam<ExportTask, BaseParam> pageParam = new PageParam<>(baseParam, ExportTask.class); |
| | | |
| | | return R.ok().add( |
| | | buildPageRowsUtils.rowsMap( |
| | | exportTaskService.page( |
| | | pageParam, |
| | | pageParam.buildWrapper(true) |
| | | .eq("deleted", 0) |
| | | .eq("tenant_id", getTenantId()) |
| | | .eq("create_by", getLoginUserId()) |
| | | ) |
| | | ) |
| | | ); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('system:exportTask:list')") |
| | | @GetMapping("/exportTask/{taskId}") |
| | | public R get(@PathVariable("taskId") Long taskId) { |
| | | ExportTask task = asyncListExportTaskService.getTask(taskId, getTenantId(), getLoginUserId()); |
| | | if (task == null) { |
| | | return R.error("导出任务不存在"); |
| | | } |
| | | return R.ok().add(buildPageRowsUtils.rowsMap(task)); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('system:exportTask:list')") |
| | | @GetMapping("/exportTask/{taskId}/download") |
| | | public void download( |
| | | @PathVariable("taskId") Long taskId, |
| | | HttpServletResponse response, |
| | | HttpServletRequest request |
| | | ) { |
| | | File file = asyncListExportTaskService.getDownloadFile(taskId, getTenantId(), getLoginUserId()); |
| | | FileServerUtil.preview(file, true, file.getName(), null, null, response, request); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.io.Serializable; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | |
| | | @Data |
| | | @TableName("sys_export_task") |
| | | public class ExportTask implements Serializable { |
| | | |
| | | public static final int STATUS_PENDING = 0; |
| | | public static final int STATUS_PROCESSING = 1; |
| | | public static final int STATUS_SUCCESS = 2; |
| | | public static final int STATUS_FAILED = 3; |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @ApiModelProperty(value = "ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty(value = "任务编码") |
| | | private String taskCode; |
| | | |
| | | @ApiModelProperty(value = "资源标识") |
| | | private String resourceKey; |
| | | |
| | | @ApiModelProperty(value = "报表标题") |
| | | private String reportTitle; |
| | | |
| | | @ApiModelProperty(value = "状态") |
| | | private Integer status; |
| | | |
| | | @ApiModelProperty(value = "导出总行数") |
| | | private Integer rowCount; |
| | | |
| | | @ApiModelProperty(value = "文件名") |
| | | private String fileName; |
| | | |
| | | @ApiModelProperty(value = "文件路径") |
| | | private String filePath; |
| | | |
| | | @ApiModelProperty(value = "查询参数") |
| | | private String payloadJson; |
| | | |
| | | @ApiModelProperty(value = "过期时间") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date expireTime; |
| | | |
| | | @ApiModelProperty(value = "错误信息") |
| | | private String errorMsg; |
| | | |
| | | @ApiModelProperty(value = "删除标记") |
| | | private Integer deleted; |
| | | |
| | | @ApiModelProperty(value = "租户") |
| | | private Long tenantId; |
| | | |
| | | @ApiModelProperty(value = "创建人") |
| | | private Long createBy; |
| | | |
| | | @ApiModelProperty(value = "创建时间") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date createTime; |
| | | |
| | | @ApiModelProperty(value = "更新人") |
| | | private Long updateBy; |
| | | |
| | | @ApiModelProperty(value = "更新时间") |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | private Date updateTime; |
| | | |
| | | @ApiModelProperty(value = "备注") |
| | | private String memo; |
| | | |
| | | @TableField(exist = false) |
| | | private String createBy$; |
| | | |
| | | @TableField(exist = false) |
| | | private String updateBy$; |
| | | |
| | | public String getStatus$() { |
| | | if (status == null) { |
| | | return null; |
| | | } |
| | | return switch (status) { |
| | | case STATUS_PENDING -> "待执行"; |
| | | case STATUS_PROCESSING -> "处理中"; |
| | | case STATUS_SUCCESS -> "已完成"; |
| | | case STATUS_FAILED -> "失败"; |
| | | default -> String.valueOf(status); |
| | | }; |
| | | } |
| | | |
| | | public String getCreateTime$() { |
| | | if (createTime == null) { |
| | | return ""; |
| | | } |
| | | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(createTime); |
| | | } |
| | | |
| | | public String getUpdateTime$() { |
| | | if (updateTime == null) { |
| | | return ""; |
| | | } |
| | | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(updateTime); |
| | | } |
| | | |
| | | public String getExpireTime$() { |
| | | if (expireTime == null) { |
| | | return ""; |
| | | } |
| | | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(expireTime); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface ExportTaskMapper extends BaseMapper<ExportTask> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | |
| | | public interface ExportTaskService extends IService<ExportTask> { |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.server.system.entity.ExportTask; |
| | | import com.vincent.rsf.server.system.mapper.ExportTaskMapper; |
| | | import com.vincent.rsf.server.system.service.ExportTaskService; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | @Service |
| | | public class ExportTaskServiceImpl extends ServiceImpl<ExportTaskMapper, ExportTask> implements ExportTaskService { |
| | | } |
| New file |
| | |
| | | CREATE TABLE IF NOT EXISTS `sys_export_task` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID', |
| | | `task_code` varchar(64) NOT NULL COMMENT '任务编码', |
| | | `resource_key` varchar(64) NOT NULL COMMENT '资源标识', |
| | | `report_title` varchar(128) DEFAULT NULL COMMENT '报表标题', |
| | | `status` int NOT NULL DEFAULT 0 COMMENT '状态 0待执行 1处理中 2成功 3失败', |
| | | `row_count` int DEFAULT 0 COMMENT '导出行数', |
| | | `file_name` varchar(255) DEFAULT NULL COMMENT '文件名', |
| | | `file_path` varchar(1000) DEFAULT NULL COMMENT '文件路径', |
| | | `payload_json` longtext COMMENT '导出参数', |
| | | `expire_time` datetime DEFAULT NULL COMMENT '过期时间', |
| | | `error_msg` varchar(500) DEFAULT NULL COMMENT '错误信息', |
| | | `deleted` int NOT NULL DEFAULT 0 COMMENT '删除标记', |
| | | `tenant_id` bigint DEFAULT NULL COMMENT '租户ID', |
| | | `create_by` bigint DEFAULT NULL COMMENT '创建人', |
| | | `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
| | | `update_by` bigint DEFAULT NULL COMMENT '更新人', |
| | | `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
| | | `memo` varchar(500) DEFAULT NULL COMMENT '备注', |
| | | PRIMARY KEY (`id`), |
| | | KEY `idx_sys_export_task_resource_status` (`resource_key`, `status`), |
| | | KEY `idx_sys_export_task_creator` (`create_by`, `tenant_id`), |
| | | KEY `idx_sys_export_task_expire_time` (`expire_time`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='导出任务表'; |
| New file |
| | |
| | | -- 导出任务过期时间 |
| | | -- 说明: |
| | | -- 1. 新增 expire_time 字段,用于定时清理导出任务和导出文件 |
| | | -- 2. 既有任务默认按创建时间保留 7 天 |
| | | |
| | | ALTER TABLE `sys_export_task` |
| | | ADD COLUMN `expire_time` datetime DEFAULT NULL COMMENT '过期时间' AFTER `payload_json`; |
| | | |
| | | UPDATE `sys_export_task` |
| | | SET `expire_time` = DATE_ADD(COALESCE(`create_time`, NOW()), INTERVAL 7 DAY) |
| | | WHERE `expire_time` IS NULL; |
| | | |
| | | CREATE INDEX `idx_sys_export_task_expire_time` ON `sys_export_task` (`expire_time`); |
| New file |
| | |
| | | -- 异步导出任务菜单 |
| | | -- 说明: |
| | | -- 1. 在“系统设置”下补充“导出任务”页面菜单 |
| | | -- 2. 权限标识为 system:exportTask:list |
| | | -- 3. 页面展示当前登录用户的异步导出任务,并支持下载已完成文件 |
| | | |
| | | SET @tenant_id := 1; |
| | | |
| | | SET @system_menu_id := ( |
| | | SELECT id |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND type = 0 |
| | | AND ( |
| | | route = '/system' |
| | | OR component = 'system' |
| | | OR name = 'menu.system' |
| | | ) |
| | | ORDER BY id |
| | | LIMIT 1 |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'menu.exportTask', |
| | | @system_menu_id, |
| | | 'menu.system', |
| | | '/system/export-task', |
| | | '/system/export-task', |
| | | '/system/export-task', |
| | | 'exportTask', |
| | | '异步导出任务', |
| | | NULL, |
| | | 0, |
| | | 'system:exportTask:list', |
| | | 'History', |
| | | 90, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '当前用户异步导出任务列表' |
| | | FROM dual |
| | | WHERE @system_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority = 'system:exportTask:list' |
| | | ); |
| New file |
| | |
| | | -- 库位导出权限 |
| | | -- 说明: |
| | | -- 1. 在“库位”菜单下补充独立导出按钮权限 |
| | | -- 2. 权限标识为 manager:loc:export |
| | | -- 3. 该权限覆盖同步导出、异步导出任务创建、任务状态查询、导出文件下载 |
| | | |
| | | SET @tenant_id := 1; |
| | | |
| | | SET @loc_menu_id := ( |
| | | SELECT id |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND type = 0 |
| | | AND ( |
| | | route = '/basic-info/loc' |
| | | OR route = '/manager/loc' |
| | | OR component = 'loc' |
| | | OR name = 'menu.loc' |
| | | ) |
| | | ORDER BY id |
| | | LIMIT 1 |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'Export 库位', |
| | | @loc_menu_id, |
| | | 'menu.loc', |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | '库位导出权限', |
| | | NULL, |
| | | 1, |
| | | 'manager:loc:export', |
| | | NULL, |
| | | 20, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '库位同步导出、异步导出任务与下载' |
| | | FROM dual |
| | | WHERE @loc_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority = 'manager:loc:export' |
| | | ); |
| New file |
| | |
| | | -- 收货库存导出权限 |
| | | -- 说明: |
| | | -- 1. 在“收货库存”菜单下补充独立导出按钮权限 |
| | | -- 2. 权限标识为 manager:warehouseAreasItem:export |
| | | -- 3. 该权限覆盖同步导出、异步导出任务创建、任务状态查询、导出文件下载 |
| | | |
| | | SET @tenant_id := 1; |
| | | |
| | | SET @warehouse_areas_item_menu_id := ( |
| | | SELECT id |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND type = 0 |
| | | AND ( |
| | | route = '/manager/warehouseAreasItem' |
| | | OR component = 'warehouseAreasItem' |
| | | OR name = 'menu.warehouseAreasItem' |
| | | ) |
| | | ORDER BY id |
| | | LIMIT 1 |
| | | ); |
| | | |
| | | INSERT INTO sys_menu ( |
| | | name, |
| | | parent_id, |
| | | parent_name, |
| | | path, |
| | | path_name, |
| | | route, |
| | | component, |
| | | brief, |
| | | code, |
| | | type, |
| | | authority, |
| | | icon, |
| | | sort, |
| | | meta, |
| | | tenant_id, |
| | | status, |
| | | deleted, |
| | | create_time, |
| | | create_by, |
| | | update_time, |
| | | update_by, |
| | | memo |
| | | ) |
| | | SELECT |
| | | 'Export 收货库存', |
| | | @warehouse_areas_item_menu_id, |
| | | 'menu.warehouseAreasItem', |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | NULL, |
| | | '收货库存导出权限', |
| | | NULL, |
| | | 1, |
| | | 'manager:warehouseAreasItem:export', |
| | | NULL, |
| | | 20, |
| | | NULL, |
| | | @tenant_id, |
| | | 1, |
| | | 0, |
| | | NOW(), |
| | | 1, |
| | | NOW(), |
| | | 1, |
| | | '收货库存同步导出、异步导出任务与下载' |
| | | FROM dual |
| | | WHERE @warehouse_areas_item_menu_id IS NOT NULL |
| | | AND NOT EXISTS ( |
| | | SELECT 1 |
| | | FROM sys_menu |
| | | WHERE deleted = 0 |
| | | AND tenant_id = @tenant_id |
| | | AND authority = 'manager:warehouseAreasItem:export' |
| | | ); |