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

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

相关推荐
灵感__idea29 分钟前
Hello 算法:众里寻她千“百度”
前端·javascript·算法
yinuo1 小时前
轻松接入大语言模型API -04
前端
袋鼠云数栈UED团队2 小时前
基于 Lexical 实现变量输入编辑器
前端·javascript·架构
cipher2 小时前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
UrbanJazzerati2 小时前
非常友好的Vue 3 生命周期详解
前端·面试
AAA阿giao2 小时前
从零构建一个现代登录页:深入解析 Tailwind CSS + Vite + Lucide React 的完整技术栈
前端·css·react.js
兆子龙3 小时前
像 React Hook 一样「自动触发」:用 Git Hook 拦住忘删的测试代码与其它翻车现场
前端·架构
兆子龙4 小时前
用 Auto.js 实现挂机脚本:从找图点击到循环自动化
前端·架构
SuperEugene4 小时前
表单最佳实践:从 v-model 到自定义表单组件(含校验)
前端·javascript·vue.js
昨晚我输给了一辆AE864 小时前
为什么现在不推荐使用 React.FC 了?
前端·react.js·typescript