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是有区别的

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

相关推荐
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅14 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼15 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte15 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc