如下图,你在【工序过帐】这个界面录入了很多数据后,切换到【字段自定义】界面,再切换回来后,当你发现【工序过帐】界面又回到了解放前,你是不是想骂人

缓存原理,使用keep-alive保存当前界面状态,代码如下
html
<template>
<router-view v-if="!isRefreshing" v-slot="{ Component }">
<transition name="fade" mode="out-in">
<keep-alive :include="aliveViews">
<component :is="Component" :key="activeRouteFullPath" />
</keep-alive>
</transition>
</router-view>
<frame-page />
</template>
ts代码如下,这样可以实现切换tab标签后保存界面状态
TypeScript
<script setup lang="ts">
import isBoolean from 'lodash/isBoolean';
import isUndefined from 'lodash/isUndefined';
import type { ComputedRef } from 'vue';
import { computed } from 'vue';
import FramePage from '@/layouts/frame/index.vue';
import { useTabsRouterStore } from '@/store';
import { useRouter,useRoute } from 'vue-router';
const activeRouteFullPath = computed(() => {
const route = useRoute();
console.log('route.fullPath', route.fullPath);
return route.fullPath;
});
const aliveViews = computed(() => {
const tabsRouterStore = useTabsRouterStore();
const { tabRouters } = tabsRouterStore;
return tabRouters
.filter((route) => {
const keepAliveConfig = route.meta?.keepAlive;
const isRouteKeepAlive = isUndefined(keepAliveConfig) || (isBoolean(keepAliveConfig) && keepAliveConfig); // 默认开启keepalive
return route.isAlive && isRouteKeepAlive;
})
.map((route) => route.name);
}) as ComputedRef<string[]>;
const isRefreshing = computed(() => {
const tabsRouterStore = useTabsRouterStore();
const { refreshing } = tabsRouterStore;
return refreshing;
});
</script>
但是你的系统功能很强大,例如ERP或MES等企业管理系统,很多界面功能是共用,例如下面的2个报表,界面相似,但功能不一样

大量图片下载时,会显示下载进度条(下一张图片显示进度)



源码如下:
html
<template>
<div ref="fullscreenElementRef">
<t-card>
<t-table size="small" :data="data" :columns="columns" :max-height="tableHeight" :pagination="paginationConfig"
:hover="hover" :loading="isLoading" bordered resizable @page-change="onPageChange"
@column-resize-change="handleColumnResize">
<template #topContent>
<t-row style="padding-bottom:10px" justify="space-between">
<t-col flex="auto">
<advanced-search ref="advancedSearchRef" v-if="filterShow" :filterCode="filterCode" visible-search="true"
fullscreen="true" Logic="And" @dynamicFilter="dynamicFilter" @exportHandler="exportFile"
@downloadHandler="downloadFile" @formLoaded="formLoaded" @fullscreenChange="fullscreenChange" />
</t-col>
</t-row>
</template>
<template #LotNumber="{ row }">
<t-link theme="primary" @click="queryLotNumberReport(row)">{{ row && row.LotNumber ? row.LotNumber : '' }}
</t-link>
</template>
<template #OriginalLotNumber="{ row }">
<t-link theme="primary" @click="queryLotNumberReport(row)">{{ row && row.OriginalLotNumber
? row.OriginalLotNumber : '' }}
</t-link>
</template>
<template #Image="{ row }">
<t-link v-if="row && row.Image" theme="primary" @click="viewImage(row)">{{ row && row.ImageText ?
row.ImageText : t('pages.common.Image') }}
</t-link>
</template>
<template #Image1="{ row }">
<t-link v-if="row && row.Image1" theme="primary" @click="viewImage1(row)">{{ row && row.Image1Text ?
row.Image1Text : t('pages.common.Image') }}
</t-link>
</template>
<template #Image2="{ row }">
<t-link v-if="row && row.Image2" theme="primary" @click="viewImage2(row)">{{ row && row.Image2Text ?
row.Image2Text : t('pages.common.Image') }}
</t-link>
</template>
<template v-if="summaryShow" #footerSummary>
<div v-html="summaryData"></div>
</template>
</t-table>
<add-form ref="addFormRef" :form-data="formData" @init="init" />
</t-card>
</div>
</template>
ts代码如下:关键代码通过 query.Id区分加载let parma = { Id: query.Id };
TypeScript
<script lang="tsx">
export default {
name: 'CommonReportUI',
};
</script>
<script lang="tsx" setup>
import { computed, ref, watch, onMounted, nextTick } from 'vue';
import { globalPageSize, globalColumnControllerConfig, globalClientHeight, globalForbidStatusOptions, globalStatusTheme, globalPageSizeOptions, globalIconSize } from '@/config/global';
import AdvancedSearch from '@/components/AdvancedSearch/src/AdvancedSearch.vue';
import { CONTRACT_PAYMENT_TYPES, FROM_STATUS, CONTRACT_TYPES } from '@/constants';
import { filterReport, getReports, getReportColumn, getExcel, PostReportData, downLoadCommonExcel, downLoadFile, deleteReport, getSetExcel, getReportCode, getDetail, modifyReport, modifyDetailReportSort, forbidReportBatch, enableReportBatch } from '@/api/report/report';
import { t } from '@/locales';
import { getPermissionStore, useUserStore } from '@/store';
import '@/globals';
import '@boldreports/javascript-reporting-controls/Content/v2.0/tailwind-light/bold.report-viewer.min.css';
import '@boldreports/javascript-reporting-controls/Scripts/v2.0/common/bold.reports.common.min';
import '@boldreports/javascript-reporting-controls/Scripts/v2.0/common/bold.reports.widgets.min';
import '@boldreports/javascript-reporting-controls/Scripts/v2.0/bold.report-viewer.min';
import '@/reportglobal/reportviewer/ej.localetexts.zh-CN.min';
import { useLocale } from '@/locales/useLocale';
import { useRoute } from 'vue-router'
import { getEnumCode } from '@/api/system/enum';
import { filterLot, getLot, createLot, getLotList, exporter, downloadReport } from '@/api/prd/lot';
import addForm from '../lotReport/components/addForm.vue';
import { formatDateFields } from '@/utils/date'
const { locale } = useLocale();
const userStore = useUserStore();
const route = useRoute();
const query = route.query;
const formData = ref({});
const addFormRef = ref();
const advancedSearchRef = ref();
const fullscreenElementRef = ref();
const data = ref([]);
const columns = ref([]);
const TOTAL = ref(0);
const currentPage = ref(1);
const paginationConfig = computed(() => {
return {
current: currentPage.value,
pageSize: userFilter.value?.PageSize || globalPageSize,
defaultCurrent: 1,
defaultPageSize: globalPageSize,
pageSizeOptions: globalPageSizeOptions,
total: TOTAL.value,
showJumper: true,
popupProps: { attach: 'body' }
};
});
const hover = ref(true);
const init = () => {
isLoading.value = true;
let parma = { Id: query.Id };
getReports(parma).then((res) => {
if (res.AutoLoad === 'Y') {
autoLoadQuery.value = true;
}
filterCode.value = res.FiltersCode;
reportCode.value = res.Code;
//viewerFlag.value = false;
filterShow.value = true;
}).then(() => {
getReportColumn(query.Id).then((resColumns) => {
console.log('resColumns', resColumns);
var data = localStorage.getItem(`tableColumnsConfig${reportCode.value}`);
var localColumnsConfig = null;
if (data != null) {
localColumnsConfig = JSON.parse(data);
}
columns.value = [{ colKey: 'serial-number', title: t('pages.common.sequence'), width: 65, align: 'center' }];
if (Array.isArray(resColumns)) {
resColumns.forEach((item) => {
if (localColumnsConfig != null && localColumnsConfig.hasOwnProperty(item)) {
var newcloumns = { colKey: item, title: item, width: localColumnsConfig[item] };
columns.value.push(newcloumns);
} else {
var cloumns = { colKey: item, title: item };
columns.value.push(cloumns);
}
})
} else {
for (const key in resColumns) {
if (resColumns.hasOwnProperty(key)) {
if (localColumnsConfig != null && localColumnsConfig.hasOwnProperty(resColumns[key])) {
var newcloumns = { colKey: resColumns[key], title: key, width: localColumnsConfig[resColumns[key]] };
columns.value.push(newcloumns);
} else {
var cloumns = { colKey: resColumns[key], title: key };
columns.value.push(cloumns);
}
}
}
}
})
}).finally(() => {
isLoading.value = false;
});
}
const onPageChange = (pageInfo: any) => {
userFilter.value.PageSize = pageInfo.pageSize;
userFilter.value.CurrentPage = pageInfo.current;
dynamicFilter(userFilter.value);
};
onMounted(() => {
init();
});
</script>
从上面的代码可以看出报表的搜索条件是通过advanced-search组件配置的
报表配置界面

报表支持类型ECharts图片,Excel图表、RDLC(微软的ReportBuild或PowerBI)等格式

配置带link功能

界面查询条件与数据表格可以通过存储过程自动解析

搜索条件配置界面

界面缓存的关键,是通过组件的name,只要将共用界面的name追加到keep-alive :include="aliveViews"即可,把动态菜单路由改为route.fullPath,不要写错了route与router是有区别的


通过上面操作带参数的共用界面也可以缓存了。