vue3 带tabs的后台管理系统,切换tab标签后,共用界面带参数缓存界面状态

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

缓存原理,使用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是有区别的

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

相关推荐
мо仙堡杠把子ご灬9 小时前
微前端架构实践:避免Vuex模块重复注册的崩溃陷阱
前端
叫我:松哥9 小时前
基于机器学习的地震风险评估与可视化系统,采用Flask后端与Bootstrap前端,系统集成DBSCAN空间聚类算法与随机森林算法
前端·算法·机器学习·flask·bootstrap·echarts·聚类
呆头鸭L9 小时前
用vue3+ts+elementPlus+vite搭建electron桌面端应用
前端·vue.js·electron
aPurpleBerry9 小时前
React Hooks(数据驱动、副作用、状态传递、状态派生)
前端·react.js·前端框架
IT_陈寒9 小时前
2025年React生态最新趋势:我从Redux迁移到Zustand后性能提升40%的心得
前端·人工智能·后端
前端小臻9 小时前
react没有双向数据绑定是怎么实现数据实时变更的
前端·javascript·react.js
困惑阿三9 小时前
CSS 动效交互实验室
前端·css
哟哟耶耶9 小时前
随笔小计-前端经常接触的http响应头(跨域CORS,性能-缓存-安全,token)
前端·网络协议·http
Allen_LVyingbo9 小时前
病历生成与质控编码的工程化范式研究:从模型驱动到系统治理的范式转变
前端·javascript·算法·前端框架·知识图谱·健康医疗·easyui