feat(reports): 接入真实报表分析及分组明细与异步导出
This commit is contained in:
@@ -1,91 +1,111 @@
|
||||
/**
|
||||
* 报表分析的数据装载与筛选:四个菜单共用同一个数据集与同一份筛选条件。
|
||||
*
|
||||
* 数据集放在模块级而不是每个页面各取一次:四个菜单的数据源完全一样,
|
||||
* 各取一次会生成四份互不相干的演示数据 —— 从「签署总览」切到「签署量分析」,
|
||||
* 累计发起数会变,看起来像统计口径不同,其实只是随机数不同。
|
||||
*/
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
getReportAnalyticsDataset,
|
||||
buildReportAnalyticsQuery,
|
||||
getReportAnalytics,
|
||||
getReportOptions,
|
||||
type ReportAnalyticsDataset,
|
||||
type ReportTaskRow,
|
||||
type ReportOptions,
|
||||
} from '@/api/workbench/report-analytics'
|
||||
import { toUserMessage } from '@/utils/api-response'
|
||||
|
||||
import { filterReportTasks, resolveDateWindow } from './aggregate'
|
||||
import { resolveDateWindow } from './aggregate'
|
||||
import { reportFilter } from './filter-store'
|
||||
|
||||
const dataset = ref<ReportAnalyticsDataset | null>(null)
|
||||
const options = ref<ReportOptions | null>(null)
|
||||
const loading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const optionsError = ref('')
|
||||
|
||||
let pending: Promise<void> | null = null
|
||||
|
||||
/**
|
||||
* 装载数据集。
|
||||
*
|
||||
* 并发去重:页面挂载与筛选条刷新可能同时触发,重复请求没有意义。
|
||||
* 只取一次成功后就不再重取 —— 演示数据是确定性的,重取只会换掉「数据截至」的时刻。
|
||||
*/
|
||||
export function loadReportDataset(): Promise<void> {
|
||||
if (dataset.value) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
pending ??= (async () => {
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
let requestSequence = 0
|
||||
let pendingOptions: Promise<void> | null = null
|
||||
let filterWatcherInstalled = false
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
export function loadReportOptions(): Promise<void> {
|
||||
if (options.value) return Promise.resolve()
|
||||
pendingOptions ??= (async () => {
|
||||
optionsError.value = ''
|
||||
try {
|
||||
dataset.value = await getReportAnalyticsDataset()
|
||||
} catch {
|
||||
errorMessage.value = '报表数据加载失败,请稍后重试'
|
||||
options.value = await getReportOptions()
|
||||
} catch (error) {
|
||||
optionsError.value = toUserMessage(error, '报表筛选项加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
pending = null
|
||||
pendingOptions = null
|
||||
}
|
||||
})()
|
||||
|
||||
return pending
|
||||
return pendingOptions
|
||||
}
|
||||
|
||||
export async function loadReportDataset(): Promise<void> {
|
||||
const sequence = ++requestSequence
|
||||
dataset.value = null
|
||||
const dateWindow = resolveDateWindow(reportFilter)
|
||||
if (!dateWindow) {
|
||||
errorMessage.value = ''
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const query = buildReportAnalyticsQuery(reportFilter, dateWindow)
|
||||
const result = await getReportAnalytics(query)
|
||||
if (sequence === requestSequence) dataset.value = result
|
||||
} catch (error) {
|
||||
if (sequence === requestSequence) errorMessage.value = toUserMessage(error, '报表数据加载失败')
|
||||
} finally {
|
||||
if (sequence === requestSequence) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function installFilterWatcher() {
|
||||
if (filterWatcherInstalled) return
|
||||
filterWatcherInstalled = true
|
||||
watch(
|
||||
() => [
|
||||
reportFilter.dateRange,
|
||||
reportFilter.customFrom,
|
||||
reportFilter.customTo,
|
||||
reportFilter.campus,
|
||||
reportFilter.department,
|
||||
reportFilter.category,
|
||||
reportFilter.method,
|
||||
],
|
||||
() => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => void loadReportDataset(), 250)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** 仅供演示 / 验证脚本:清掉缓存后重新装载 */
|
||||
export function resetReportDataset(): void {
|
||||
dataset.value = null
|
||||
pending = null
|
||||
requestSequence++
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
}
|
||||
|
||||
export function useReportDataset() {
|
||||
/** 当前筛选对应的时间窗口;null 表示自定义区间还没填完 */
|
||||
installFilterWatcher()
|
||||
const window = computed(() => resolveDateWindow(reportFilter))
|
||||
|
||||
const allTasks = computed<ReportTaskRow[]>(() => dataset.value?.tasks ?? [])
|
||||
|
||||
/** 当前筛选下的任务明细:四个页面的所有数字都从这里算 */
|
||||
const tasks = computed<ReportTaskRow[]>(() => filterReportTasks(allTasks.value, reportFilter))
|
||||
|
||||
const available = computed(() => dataset.value?.available ?? false)
|
||||
const available = computed(() => dataset.value !== null)
|
||||
const generatedAt = computed(() => dataset.value?.generatedAt ?? '')
|
||||
|
||||
/**
|
||||
* 自定义区间只填了一头时为 true。
|
||||
* 这种半截条件既不该按半截筛,也不该悄悄套默认窗口,页面据此提示用户补全。
|
||||
*/
|
||||
const customRangeIncomplete = computed(
|
||||
() =>
|
||||
reportFilter.dateRange === 'custom' && (!reportFilter.customFrom || !reportFilter.customTo),
|
||||
)
|
||||
|
||||
return {
|
||||
allTasks,
|
||||
tasks,
|
||||
dataset,
|
||||
options,
|
||||
window,
|
||||
available,
|
||||
generatedAt,
|
||||
loading,
|
||||
errorMessage,
|
||||
optionsError,
|
||||
customRangeIncomplete,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user