# 前端手册

# 快速开始

# 环境准备

nodejs 14.X 及以上版本

# 安装依赖

推荐使用 yarn 进行依赖管理

# 全局安装 yarn
npm install -g yarn
# 依赖安装
yarn install
# 如果很慢,设置为国内源
yarn config set registry http://registry.npm.taobao.org/
# 然后再重新 yarn
yarn

# 项目启动

yarn run serve

# 新建模块

页面文件在 src/views 中新建文件夹, 最好采用小驼峰形式的命名

API 请求在 src/api 中按照模块新建文件或文件夹,存放 api 路径以及请求方法

本地开发,路由设置在 src/config/router.config.js 中添加新页面路由, 等后端对接完成后,可以上平台的应用管理-权限管理-菜单管理中自行添加,实现动态路由

# 打包项目

yarn run build

# 请求拦截

请求统一处理, 采用axios作为请求插件,拦截器在src/utils/request.js中声明 本拦截器中, 针对失败、各项异常做了处理, 如果有个性化需要,可自由调整修改

# 动态路由

动态路由,是指将本地的配置上云, 在权限模块的菜单管理中来管理前端的所有路由 本地路由菜单配置: src/config/router.config.js 路由拦截器: src/utils/routerUtil.js 路由配置: src/router/index.js

# 前置条件

  1. 平台建立应用进行管理
  2. 需要后端对接好对应的菜单接口, 得到接口之后, 可以在utils/routerUtil.js 里, 修改函数getRouterByUser中所请求的地址来获取路由数据
  3. main.js中的函数 initApp 中添加本应用的 APP_ID, 添加方式如下,id的获取,在平台的管理页面中,查看 url 会带有对应的 id
  4. 基座下的应用因为对外提供服务的端口并不一定会有多个,因此所有前端服务将通过统一的 nginx 代理,每一个需要上线的应用,都需要配置本应用自己的 uri 前缀,就是 vue.config.js 中的publicPath, 使用基座所提供的前端框架可以直接在.env文件中修改VUE_APP_PUBLIC_PATH,具体的值可以直接使用项目名称,可以和基座的前端负责人提前约定
  5. 现场环境所使用的APP_ID可能和测试环境不一致,那么在开发的时候可以在main.js中的initApp函数中增加判断,通过获取 url 地址进行判断,判断为测试环境就设置为测试环境的APP_ID,其余情况就设置为生产环境的APP_ID
cookieUtil.set('applicationId', APP_ID)
store.commit('SET_APP_INFO', { applicationId: APP_ID })

# 在线菜单配置要点

  1. 菜单类型分为三种, 目录菜单按钮 其中只有菜单是对应页面级别, 可以直接访问页面的类型
  2. 目录 主要用于归类某一类型功能页面合并在期下面, 也同时支持布局组件NavLayout,详情可以看下面的布局组件文档
  3. 菜单用于展示页面, 组件字段的填写与前端工程结构相挂钩,是在src/views中根据配置的路径获取页面组件,如组件存放在src/views/result/Success.vue 那要将本页面通过菜单配置, 就在组件字段填写result/Success即可

# 动态路由配置

src/permission.jsaddRouterAndNext 函数里面确认是否有以下片段,如果没有就加进去,确保使用动态路由

router.addRoutes(store.getters.addRouters)

# 组件使用文档

本前端工程模版中,内置了部分组件,可通过以下文档进行熟悉和使用

# 布局组件 Layout

  • NavLayout: 将菜单配置为目录类型, 组件中填写「NavLayout」即可实现侧边目录, 侧边目录的数据来源于本菜单下的所有菜单
  • UserLayout: 将菜单配置为目录类型,组件中填写本组件,一般作为登录页面的布局, 已经配置好对应的应用标题, 图标和底部内容, 只需要登录表单即可
  • BlankLayout: 将菜单配置为目录类型,组件中填写本组件,空白布局
  • PageView: 独立页面,会填入菜单标题,无需配置组件, 菜单类型为「菜单」的独立页面会自动使用本布局组件

# 权限指令 action

//具体代码看 src/core/directives/action.js
//按钮权限校验
<a-button v-has="'user:add'" >添加用户</a-button>
<a-button v-has="['user:edit']">删除用户</a-button>
//角色校验
<a-button v-hasRole="'test'" >test用户</a-button>
<a-button v-hasRole="['test']">test用户</a-button>

# 导入导出Excel组件

本组件主要用 Excel 的导入导出,可控制导入导出的字段等信息

# Excel 导出组件 ExportExcel

用于一站式通用的导出组件

// api.js
export function getZds(data) {
  return request({
    url: '/your/api/export/getZds',
    method: 'get',
    params: data,
    loadingLoader: 'loadingLoader'
  })
}
export function getExport(data) {
  return download('/your/api/export', data)
}
// vue
import ExportExcel from '@/components/ExportExcel'
import { getZds, getExport } from '@/api/ExportExcel'
/**
 * zds-api 是一个 axios 请求,接口是获取需要导出的字段,由后端提供,参考getZds的写法
 * export-api 是一个 axios 请求,接口是导出的接口,由后端提供,参考getExport的写法
 * query-params 是页面导出的分页参数和搜索参数
 */
<ExportExcel
  :zds-api="getZds"
  :export-api="getExport"
  :query-params="{ ...pageInfo, ...query }"
/>

# Excel 导入组件 ImportExcel

用于一站式通用的导入组件

// vue
import ImportExcel from '@/components/ImportExcel'
import { downloadTpl, getImportZd, importData } from '@/api/importExport'
/**
 * text
 * currentType 导入的类型,在@/api/importExport中需要先行定义相关类型的接口map
 * zds-api 是一个 axios 请求, 参考@/api/importExport中的写法
 * download-template 是一个 axios 请求, 参考@/api/importExport中的写法
 * import-api 是一个 axios 请求, 参考@/api/importExport中的写法
 * refreshTable 页面列表的刷新方法
 */
<ImportExcel
  text="导入按钮的名称"
  currentType="type"
  :zds-api="getImportZd"
  :download-template="downloadTpl"
  :import-api="importData"
  :refreshTable="() => $refs.table.refresh(true)"
/>

# 全局过滤器 filter

使用 Vue.filter 独立文件存放

请看 src\utils\filter.js

# 请求 loading 管理 loading

api 层,例如

export function listExp(query) {
  return axios({
    url: '/activiti/exp/list',
    method: 'get',
    params: query,
    loadingLoader: 'listExp', //增加这个字段
  })
}

可使用 $wait.is('listExp') 返回值为 truefalse,常用于 Spin 组件

也可以增加loadingTips字段,顶部会自动使用 antdmessageloading 提示

return axios({
  url: '/activiti/exp/list',
  method: 'get',
  params: query,
  loadingTips: '请求列表中', //增加这个字段
})

# 字典调用 dict

this.$store.dispatch('dict/getDict', 'service_hall_type')

service_hall_type 为字典 key, 调用此方法后,字典会存在 dict store

# 工作流 activiti

# 新建流程

产品新建流程。

流程查看在第三方应用认证/流程中心组件/流程设计 页面,点击设计查看。

弹窗流程下一步候选人,没有需要后端加节点

# 关键参数
  • type-name: 流程分类
  • processInstanceId: 流程id, 关联一个完整流程
  • taskId: 审批节点id, 多个审批节点组成一个流程id
  • taskName: 审批名字, 根据这个判断流程节点

关键参数的获取,可以在一站式的第三方应用认证中,流程中心组件-流程设计,查看流程分类 通过/api/activiti/workFlow/procList?typeName=XXX 查询对应的流程 id, 审批节点id,审批名称等 发起流程是processParams中填入 procDefKey(调用/api/activiti/flow/list 传入typeName获取)、action(0保存草稿 9提交)、assigneeList参数 发给你们定义的业务后端接口,后端调用WorkFlowUtils工具类去发起

# 组件介绍

  1. 按钮

按钮一般和弹窗联动,按钮选择action后打开弹窗

  • authority: 后端按钮权限字段

传入ApprovalModal的值

  • processInstanceId: 流程实例id
  • taskId: 一般工作流结合表单使用,此参数一般为表单的值

常用代码及权限表

自己发起-保存时权限按钮一般为编辑,自己发起-保存为审批

  <ApprovalButtons :authority="authority">
    <a-button slot="cancel" @click="handleCancel">取消</a-button>
    <a-button v-if="!disabled" type="primary" slot="edit" :loading="confirmLoading" @click="handleSubmit(0)">保存</a-button>
    <a-button v-if="!disabled" type="primary" slot="edit" :loading="confirmLoading" @click="handleSubmit(9)">提交</a-button>
    <a-button v-if="!disabled" type="primary" slot="che" :loading="confirmLoading" @click="handleSubmit(3)">撤回</a-button>
    <a-button v-if="!disabled" type="primary" slot="tui" :loading="confirmLoading" @click="handleSubmit(4)">退回</a-button>
    <a-button v-if="!disabled" type="primary" slot="approval" :loading="confirmLoading" @click="handleSubmit(2)">拒绝</a-button>
    <a-button v-if="!disabled" type="primary" slot="approval" :loading="confirmLoading" @click="handleSubmit(1)">同意</a-button>
    <a-button v-if="!disabled" type="primary" slot="again" :loading="confirmLoading">重新申请</a-button>
  </ApprovalButtons>
  <script>
    export default {
      data() {
        return {
          disabled: false,
          action: 0,
          validatedForm: {},
          visible: false,
          confirmLoading: $wait.is('save'),// confirm 保存的loading, 自己填写
        }
      },
      methods: {
        handleSubmit(action) {
          this.form.validate((err, value) => {
            if (!err) {
              this.$refs.approvalModal.startApproval(value, action)
                .then(res => {
                  this.confirmed(res)
                })
            }
          })
        }
      }
    }
  </script>
  1. 弹窗
  • confirmed: 点击弹窗确认后回调,会把 validatedForm 合并 processParam 作为第一个参数回调
  • processParam: 流程参数
<ApprovalModal
  :type-name="'学籍异动'"
  :processInstanceId="mdl.processInstanceId"
  :taskId="mdl.taskId"
  ref="approvalModal"
/>

<script>
export default {
  confirmed(data) {
    // 带着工作流参数
    save(data).then()
  }
}
</script>
  1. 流程步骤图
 <ApprovalProcess
  :processInstanceId="mdl.processInstanceId"
></ApprovalProcess>

# 3. 列表筛选

todo 待完善,可能不准

查看审批状态筛选有三个状态, 见src/utils/constant.js中的workflowTodoType, 一般在查询list的接口,传listType参数过滤

能看到审批数据情况

  • 待办事项: 自己发起-保存 或 别人发起-审批人是自己 或 别人-拒绝/退回
  • 已办事项: 自己发起-提交 或 别人发起-自己同意/退回
  • 关于我的: 自己发起-流程结束

# 文件传输组件 transfer

上传:见src/components/SUpload 可以使用v-modelv-decorator双向绑定数据 下载:见src/utils/download.js 内置封装了一些下载函数,包括通用下载,流下载,blob 下载等,可以直接调用 文件预览:有独立的文件预览系统,通过接口获取预览地址,然后直接打开即可,下面是使用范例

// @/api/common
export function preview(parameter) {
  return axios({
    url: '/file/file/viewUrl',
    method: 'get',
    params: parameter
  })
}
// preview.vue
import { preview } from '@/api/common'
// 这里的id 是文件 id
preview({ id }).then((res) => {
  if (res.code == 0) {
    // 获取到预览地址,直接跳转
    window.open('' + res.msg)
  }
})

# 消息通知 Notice

后端会对接相关接口,这里主要说明前端如何接入

// @/api/notice
export function saveNotice(parameter) {
  // return axios.post(parameter.id ? api.editNotice: api.addNotice,parameter);
  return axios({
    url: '/api/apicenter/api/notice/push',
    method: 'post',
    data: parameter,
    loadingLoader: 'upDataNotice'
  })
}
// notice.vue
import { saveNotice } from '@/api/notice'
saveNotice({
  userName: 'admin',
  title:'测试标题',
  content:'内容',
  availableTimeStart:'2022-10-10',
  availableTimeEnd:'2023-10-10',
  userIds:'1',
  channelCodes:'MC00001'
}).then((res) => {
  if (res.code == 0) {
    console.log('新增成功')
  }
})

# 前端微服务接入 micro

当需要将一个已有的项目, 作为微服务接入到一站式平台时, 可以借助本方案完成接入

# 本地代码调整

微应用不需要额外安装任何其他依赖即可接入门户主应用。

# 导出相应的生命周期钩子

微应用需要在自己的入口 js (通常就是你配置的 webpackentry js) 导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用。

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log('react app bootstraped');
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount({ data = {}, state, appInfo, routerBase, actions } = {}) {
  ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount(props) {
  ReactDOM.unmountComponentAtNode(
    props.container ? props.container.querySelector('#root') : document.getElementById('root'),
  );
}

/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

# 配置微应用的打包工具

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:

# webpack

const packageName = require('./package.json').name;

module.exports = {
  output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
  },
};

相关配置介绍可以查看 webpack 相关文档 (opens new window)

# 项目实践

# 微应用

微应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的微应用(主要是指 Vue、React、Angular)需要做的事情有:

  1. 新增 public-path.js 文件,用于修改运行时的 publicPath什么是运行时的 publicPath ? (opens new window);
  2. 微应用建议使用 history 模式的路由,需要设置路由 base,值和它的 activeRule 是一样的;
  3. 在入口文件最顶部引入 public-path.js,修改并导出三个生命周期函数。
  4. 修改 webpack 打包,允许开发环境跨域和 umd 打包。

注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。

主要的修改就是以上四个,可能会根据项目的不同情况而改变。例如,你的项目是 index.html 和其他的所有文件分开部署的,说明你们已经将构建时的 publicPath 设置为了完整路径,则不用修改运行时的 publicPath (第一步操作可省)。

webpack 构建的微应用直接将 lifecycles 挂载到 window 上即可。

# React 微应用

create react app 生成的 react 16 项目为例,搭配 react-router-dom 5.x。

一、 入口文件 index.js 修改,为了避免根 id #root 与其他的 DOM 冲突,需要限制查找范围。

import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

function render(props) {
  const { container } = props;
  ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}

export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}

export async function unmount(props) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

这里需要特别注意的是,通过 ReactDOM.render 挂载子应用时,需要保证每次子应用加载都应使用一个新的路由实例。

二、 修改 webpack 配置

安装插件 @rescripts/cli,当然也可以选择其他的插件,例如 react-app-rewired

npm i -D @rescripts/cli

根目录新增 .rescriptsrc.js

const { name } = require('./package');

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';

    return config;
  },

  devServer: (_) => {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;

    return config;
  },
};

修改 package.json

-   "start": "react-scripts start",
+   "start": "rescripts start",
-   "build": "react-scripts build",
+   "build": "rescripts build",
-   "test": "react-scripts test",
+   "test": "rescripts test",
-   "eject": "react-scripts eject"

# Vue 微应用

vue-cli 3+ 生成的 vue 2.x 项目为例,vue 3 版本等稳定后再补充。

  1. src 目录新增 public-path.js

    if (window.__POWERED_BY_QIANKUN__) {
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    
  2. 入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。

    import './public-path';
    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import App from './App.vue';
    import routes from './router';
    import store from './store';
    
    Vue.config.productionTip = false;
    
    let router = null;
    let instance = null;
    function render(props = {}) {
      const { container } = props;
      router = new VueRouter({
        base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
        mode: 'history',
        routes,
      });
    
      instance = new Vue({
        router,
        store,
        render: (h) => h(App),
      }).$mount(container ? container.querySelector('#app') : '#app');
    }
    
    // 独立运行时
    if (!window.__POWERED_BY_QIANKUN__) {
      render();
    }
    
    export async function bootstrap() {
      console.log('[vue] vue app bootstraped');
    }
    export async function mount(props) {
      console.log('[vue] props from main framework', props);
      render(props);
    }
    export async function unmount() {
      instance.$destroy();
      instance.$el.innerHTML = '';
      instance = null;
      router = null;
    }
    
  3. 打包配置修改(vue.config.js):

    const { name } = require('./package');
    module.exports = {
      devServer: {
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
      },
      configureWebpack: {
        output: {
          library: `${name}-[name]`,
          libraryTarget: 'umd', // 把微应用打包成 umd 库格式
          jsonpFunction: `webpackJsonp_${name}`,
        },
      },
    };
    

# Angular 微应用

Angular-cli 9 生成的 angular 9 项目为例,其他版本的 angular 后续会逐渐补充。

  1. src 目录新增 public-path.js 文件,内容为:

    if (window.__POWERED_BY_QIANKUN__) {
      // eslint-disable-next-line no-undef
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    
  2. 设置 history 模式路由的 basesrc/app/app-routing.module.ts 文件:

    + import { APP_BASE_HREF } from '@angular/common';
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
      // @ts-ignore
    +  providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }]
    })
    
  3. 修改入口文件,src/main.ts 文件。

    import './public-path';
    import { enableProdMode, NgModuleRef } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';
    
    if (environment.production) {
      enableProdMode();
    }
    
    let app: void | NgModuleRef<AppModule>;
    async function render() {
      app = await platformBrowserDynamic()
        .bootstrapModule(AppModule)
        .catch((err) => console.error(err));
    }
    if (!(window as any).__POWERED_BY_QIANKUN__) {
      render();
    }
    
    export async function bootstrap(props: Object) {
      console.log(props);
    }
    
    export async function mount(props: Object) {
      render();
    }
    
    export async function unmount(props: Object) {
      console.log(props);
      // @ts-ignore
      app.destroy();
    }
    
  4. 修改 webpack 打包配置

    先安装 @angular-builders/custom-webpack 插件,注意:angular 9 项目只能安装 9.x 版本,angular 10 项目可以安装最新版

    npm i @angular-builders/custom-webpack@9.2.0 -D
    

    在根目录增加 custom-webpack.config.js ,内容为:

    const appName = require('./package.json').name;
    module.exports = {
      devServer: {
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
      },
      output: {
        library: `${appName}-[name]`,
        libraryTarget: 'umd',
        jsonpFunction: `webpackJsonp_${appName}`,
      },
    };
    

    修改 angular.json,将 [packageName] > architect > build > builder[packageName] > architect > serve > builder 的值改为我们安装的插件,将我们的打包配置文件加入到 [packageName] > architect > build > options

    - "builder": "@angular-devkit/build-angular:browser",
    + "builder": "@angular-builders/custom-webpack:browser",
      "options": {
    +    "customWebpackConfig": {
    +      "path": "./custom-webpack.config.js"
    +    }
      }
    
    - "builder": "@angular-devkit/build-angular:dev-server",
    + "builder": "@angular-builders/custom-webpack:dev-server",
    
  5. 解决 zone.js 的问题

    父应用引入 zone.js,需要在 import qiankun 之前引入。

    将微应用的 src/polyfills.ts 里面的引入 zone.js 代码删掉。

    - import 'zone.js/dist/zone';
    

    在微应用的 src/index.html 里面的 <head> 标签加上下面内容,微应用独立访问时使用。

    <!-- 也可以使用其他的CDN/本地的包 -->
    <script src="https://unpkg.com/zone.js" ignore></script>
    
  6. 修正 ng build 打包报错问题,修改 tsconfig.json 文件,参考issues/431 (opens new window)

    - "target": "es2015",
    + "target": "es5",
    + "typeRoots": [
    +   "node_modules/@types"
    + ],
    
  7. 为了防止主应用或其他微应用也为 angular 时,<app-root></app-root> 会冲突的问题,建议给<app-root> 加上一个唯一的 id,比如说当前应用名称。

    src/index.html :

    - <app-root></app-root>
    + <app-root id="angular9"></app-root>
    

    src/app/app.component.ts :

    - selector: 'app-root',
    + selector: '#angular9 app-root',
    

当然,也可以选择使用 single-spa-angular 插件,参考single-spa-angular 的官网 (opens new window)angular demo (opens new window)

补充)angular7 项目除了第 4 步以外,其他的步骤和 angular9 一模一样。angular7 修改 webpack 打包配置的步骤如下:

除了安装 angular-builders/custom-webpack 插件的 7.x 版本外,还需要安装 angular-builders/dev-server

npm i @angular-builders/custom-webpack@7 -D
npm i @angular-builders/dev-server -D

在根目录增加 custom-webpack.config.js ,内容同上。

修改 angular.json[packageName] > architect > build > builder 的修改和 angular9 一样, [packageName] > architect > serve > builder 的修改和 angular9 不同。

- "builder": "@angular-devkit/build-angular:browser",
+ "builder": "@angular-builders/custom-webpack:browser",
  "options": {
+    "customWebpackConfig": {
+      "path": "./custom-webpack.config.js"
+    }
  }

- "builder": "@angular-devkit/build-angular:dev-server",
+ "builder": "@angular-builders/dev-server:generic",

# 非 webpack 构建的微应用

一些非 webpack 构建的项目,例如 jQuery 项目、jsp 项目,都可以按照这个处理。

接入之前请确保你的项目里的图片、音视频等资源能正常加载,如果这些资源的地址都是完整路径(例如 https://qiankun.umijs.org/logo.png),则没问题。如果都是相对路径,需要先将这些资源上传到服务器,使用完整路径。

接入非常简单,只需要额外声明一个 script,用于 export 相对应的 lifecycles。例如:

  1. 声明 entry 入口

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Purehtml Example</title>
    </head>
    <body>
      <div>
        Purehtml Example
      </div>
    </body>
    
    + <script src="//yourhost/entry.js" entry></script>
    </html>
    
  2. 在 entry js 里声明 lifecycles

    const render = ($) => {
      $('#purehtml-container').html('Hello, render with jQuery');
      return Promise.resolve();
    };
    
    ((global) => {
      global['purehtml'] = {
        bootstrap: () => {
          console.log('purehtml bootstrap');
          return Promise.resolve();
        },
        mount: () => {
          console.log('purehtml mount');
          return render($);
        },
        unmount: () => {
          console.log('purehtml unmount');
          return Promise.resolve();
        },
      };
    })(window);
    

你也可以直接参照 examples 中 purehtml 部分的代码 (opens new window)

同时,你也需要开启相关资源的 CORS,具体请参照此处

# umi-qiankun 项目

umi-qiankun 的教程请移步 umi 官网 (opens new window)umi-qiankun 的官方 demo (opens new window)