baidu / amis

前端低代码框架,通过 JSON 配置就能生成各种页面。
https://baidu.github.io/amis/
Apache License 2.0
16.85k stars 2.46k forks source link

app组件的pages或者api很大会不会出现性能问题 #10606

Open lawbc opened 1 month ago

lawbc commented 1 month ago

实现场景:

在app组件中,把一个系统的所有页面都使用js编写(schema是js文件)然后设置到app的pages数组中,这样一个系统几百个页面会导致这个数组非常大,可能有5M,会不会出现性能问题,比如第一次进入页面卡顿

存在的问题:

因为无法使用js动态加载页面的schema,可能会导致第一次加载页面卡顿,pages使用schemaApi加载的话,js文件限制很多,我看只能是立即执行函数或者直接返回一个json对象才行,页面如果存在import就会报错

当前方案:

请粘贴你当前方案的完整 amis schema 代码...

index.js:

import 'amis/sdk/sdk.css'
import 'amis/sdk/antd.css'
import 'amis/sdk/helper.css'
import 'amis/sdk/iconfont.css'

import 'amis/sdk/sdk.js'
import { createBrowserHistory } from 'history';

import * as u from './utils'
import {configAxios} from './axiosConfig'
import pages from './site'
import loadMenus from './menu'

;(async () => {
  //这里加入登录token判断,没有登录就跳转到login页面
  if(!u.getToken()){
    window.location = '/login.html'
    return;
  }

  //configAxios();
  //const menuResult = await loadMenus();

  let amis = amisRequire('amis/embed');
  const match = amisRequire('path-to-regexp').match;

  // 如果想用 browserHistory 请切换下这处代码, 其他不用变
  const history = createBrowserHistory();
  //const history = createHashHistory();

  const app = {
    type: 'app',
    brandName: 'XXX管理后台',
    logo: '/public/logo.png',
    header: {
      type: 'tpl',
      inline: false,
      className: 'w-full',
      tpl: '<div class="flex justify-between"><div>顶部区域左侧</div><div>顶部区域右侧</div></div>'
    },
    // footer: '<div class="p-2 text-center bg-light">底部区域</div>',
    // asideBefore: '<div class="p-2 text-center">菜单前面区域</div>',
    // asideAfter: '<div class="p-2 text-center">菜单后面区域</div>',
    pages: pages
  };

  function normalizeLink(to, location = history.location) {
    to = to || '';

    if (to && to[0] === '#') {
      to = location.pathname + location.search + to;
    } else if (to && to[0] === '?') {
      to = location.pathname + to;
    }

    const idx = to.indexOf('?');
    const idx2 = to.indexOf('#');
    let pathname = ~idx
      ? to.substring(0, idx)
      : ~idx2
        ? to.substring(0, idx2)
        : to;
    let search = ~idx ? to.substring(idx, ~idx2 ? idx2 : undefined) : '';
    let hash = ~idx2 ? to.substring(idx2) : location.hash;

    if (!pathname) {
      pathname = location.pathname;
    } else if (pathname[0] != '/' && !/^https?\:\/\//.test(pathname)) {
      let relativeBase = location.pathname;
      const paths = relativeBase.split('/');
      paths.pop();
      let m;
      while ((m = /^\.\.?\//.exec(pathname))) {
        if (m[0] === '../') {
          paths.pop();
        }
        pathname = pathname.substring(m[0].length);
      }
      pathname = paths.concat(pathname).join('/');
    }

    return pathname + search + hash;
  }

  function isCurrentUrl(to, ctx) {
    if (!to) {
      return false;
    }
    const pathname = history.location.pathname;
    const link = normalizeLink(to, {
      ...location,
      pathname,
      hash: ''
    });

    if (!~link.indexOf('http') && ~link.indexOf(':')) {
      let strict = ctx && ctx.strict;
      return match(link, {
        decode: decodeURIComponent,
        strict: typeof strict !== 'undefined' ? strict : true
      })(pathname);
    }

    return decodeURI(pathname) === link;
  }

  let amisInstance = amis.embed(
    '#app',
    app,
    {
      location: history.location,
      data: {
        // 全局数据,是受控的数据
      },
      context: {
        // 全局上下文数据, 非受控的数据,无论哪一层都能获取到,包括弹窗自定义数据映射后都能获取到。
        // 可以用来放一下全局配置等。比如 API_HOST, 这样页面配置里面可以通过 ${API_HOST} 来获取到。
        API_HOST: import.meta.env.VITE_API_URL
      }
    },
    {
      // watchRouteChange: fn => {
      //   return history.listen(fn);
      // },
      updateLocation: (location, replace) => {
        location = normalizeLink(location);
        if (location === 'goBack') {
          return history.goBack();
        } else if (
          (!/^https?\:\/\//.test(location) &&
            location ===
            history.location.pathname + history.location.search) ||
          location === history.location.href
        ) {
          // 目标地址和当前地址一样,不处理,免得重复刷新
          return;
        } else if (/^https?\:\/\//.test(location) || !history) {
          return (window.location.href = location);
        }

        history[replace ? 'replace' : 'push'](location);
      },
      jumpTo: (to, action) => {
        if (to === 'goBack') {
          return history.goBack();
        }

        to = normalizeLink(to);

        if (isCurrentUrl(to)) {
          return;
        }

        if (action && action.actionType === 'url') {
          action.blank === false
            ? (window.location.href = to)
            : window.open(to, '_blank');
          return;
        } else if (action && action.blank) {
          window.open(to, '_blank');
          return;
        }

        if (/^https?:\/\//.test(to)) {
          window.location.href = to;
        } else if (
          (!/^https?\:\/\//.test(to) &&
            to === history.pathname + history.location.search) ||
          to === history.location.href
        ) {
          // do nothing
        } else {
          history.push(to);
        }
      },
      requestAdaptor: async (api, context) => {
        const headers = api.headers || {};
        headers['X-APP'] = 'bqex';
        //这里加入token的逻辑,如果获取到了就放到header中
        if(!u.isIgnoreUrl(api.url)){
          headers['Authorization'] = u.getToken() || '';
        }
        api.headers = headers;
        console.log(api.url)
        //当请求url不包含http且不是js和json后缀的时候(amis本身的请求,非后端接口),自动加上域名
        if(!api.url.startsWith('http') && !api.url.includes('.js') && !api.url.includes('.json')){
          api.url = import.meta.env.VITE_API_URL + api.url;
        }
        return api;
      },
      responseAdaptor: (api, payload, query, request, response) => {
        if(payload.code == 401){
          window.location = '/login.html';
          return;
        }

        if(api.url.includes('.js')){
          let module = {};
          let oldModule = window.module;
          window.module = module;
          try {
              eval('(' + payload + ')');
          } catch (e) {
              console.error('Error loading script: ', e);
          }
          window.module = oldModule;
          payload = module.exports;
        }

        return payload;
      },
      isCurrentUrl: isCurrentUrl,
      theme: 'antd'
    }
  );

  //这里可以拦截路由变化
  history.listen(state => {
    amisInstance.updateProps({
      location: state.location || state
    });
  });
})();

site.js(app的pages返回值)

import rolePage from './pages/role/index'
import roleOrgPage from './pages/role/org-index'
import menuPage from './pages/menu/index'
import userPage from './pages/user/index'
import adminPage from './pages/admin/index'
import onlineUserPage from './pages/user/online'
import systemPage from './pages/system/index'
import regionPage from './pages/region/index'
import areaPage from './pages/area/index'
import dictPage from './pages/dict/index'
import loginLogPage from './pages/log/login-log'
import operationLogPage from './pages/log/operation-log'

export default [
    {
      label: 'Home',
      url: '/',
      redirect: '/user'
    },
    {
      children: [
        {
          label: '系统管理',
          children: [
            {
              label: '用户管理',
              url: 'user',
              schema: userPage
            },
            {
              label: '管理员管理',
              url: 'admin',
              schema: adminPage
            },
            {
              label: '在线用户',
              url: 'user/online',
              schema: onlineUserPage
            },
            {
              label: '菜单管理',
              url: 'menu',
              schema: menuPage
            },
            {
              label: '角色管理',
              url: 'role',
              schema: rolePage
            },
            {
              label: '机构角色管理',
              url: 'role/org',
              schema: roleOrgPage
            },
            {
              label: '子系统管理',
              url: 'system',
              schema: systemPage
            },
            {
              label: '片区管理',
              url: 'region',
              schema: regionPage
            },
            {
              label: '行政区管理',
              url: 'area',
              schema: areaPage
            },
            {
              label: '字典表管理',
              url: 'dict',
              schema: dictPage
            },
            {
              label: '登录日志管理',
              url: 'log/login',
              schema: loginLogPage
            },
            {
              label: '操作日志管理',
              url: 'log/operation',
              schema: operationLogPage
            }
          ]
        }
      ]
    }
  ]

上面这种需要一次性import所有的页面,导致pages对象非常大,有没有办法动态import呢

lawbc commented 1 month ago

schema能否支持动态import,比如schema: await import('./xxx.js')

xiahao90 commented 1 month ago

schema用api动态加载就行了,再api配置上缓存,也不会重复加载

lawbc commented 1 month ago

schema用api动态加载就行了,再api配置上缓存,也不会重复加载

api方式不支持加载模块化的js代码呢,有demo吗,因为一个页面的js文件需要导入其他js函数不是简单的返回json对象的

xiahao90 commented 1 month ago

如你的:{ label: '用户管理', url: 'user', schema: userPage }, 改成 { label: '用户管理', url: 'user', schemaApi: { "method": "get", "url": "/pages/user/index", "cache": 2000 } }, 后端返回这个json格式

lawbc commented 1 month ago

如你的:{ label: '用户管理', url: 'user', schema: userPage }, 改成 { label: '用户管理', url: 'user', schemaApi: { "method": "get", "url": "/pages/user/index", "cache": 2000 } }, 后端返回这个json格式

如果userPage中有一些自定义的逻辑需要import其他库函数就不行了,如果全都是单纯的js那很好办,都可以直接使用js:xxxx.js来加载,问题就是每个js都有可能有自定义逻辑的

xiahao90 commented 1 month ago

杠精不看文档?/pages/user/index接口返回这个不就可以模块化加载了? {"type":"page","onEvent":{"init":{"actions":[{"actionType":"custom","script":"console.log('加载你的动态js')"}]}}}

lawbc commented 1 month ago

杠精不看文档?/pages/user/index接口返回这个不就可以模块化加载了? {"type":"page","onEvent":{"init":{"actions":[{"actionType":"custom","script":"console.log('加载你的动态js')"}]}}}

没有杠精的意思,虚心请教,文档也看了很多遍了,确实不知道schemaApi如何加载模块化的js文件,直接return {}这种是官方有介绍,actionType为custom的文档里面没有看到过

lawbc commented 1 month ago

杠精不看文档?/pages/user/index接口返回这个不就可以模块化加载了? {"type":"page","onEvent":{"init":{"actions":[{"actionType":"custom","script":"console.log('加载你的动态js')"}]}}}

/pages/user/index这个是一个js文件,js里面又import了其他模块的js