Skip to content
本页目录

PC界面渲染包 mas-explorer

PC界面渲染包是专门为PC浏览器访问系统而设计的界面渲染功能,引入组件库 Element UI、路由管理器 Vue Router,注册全局UI组件、全局路由,定义主题机制,汇总菜单、主题等信息,启动界面渲染,并提供默认组件封装、默认主题、默认路由、默认菜单和服务接口错误提示功能。

为平台扩展的能力

  • 渲染PC Web界面
  • PC浏览器前端组件库(Element UI)
  • 路由管理(Vue Router)与定义
  • 菜单定义
  • 全局UI组件注册能力
  • 主题机制与主题切换能力
  • 主题开发与定义能力
  • Ajax调用平台级错误显示

引入的框架

Element UI 组件库

Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库,当前版本为 2.15.2,具体组件信息,详见官网:https://element.eleme.cn/#/zh-CN/component/installation

Vue Router 路由管理器

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。当前版本为 3.6.5,详见官网:https://v3.router.vuejs.org/zh/

封装的对象

主题

解析主题配置信息,提供显示主题区域和激活主题的功能方法

js
class Theme {
  constructor(ctx, app, options) {},
  // 编号:<app_name>.<theme_name>
  id,
  // 所属应用对象
  app,
  // 上下文
  ctx,
  // 是否激活
  actived,
  // options 中的属性
  name, 
  title, 
  author, 
  image, 
  description,
  main, 
  login, 
  lockscreen, 
  css,
  // 显示主题区域(main/login/lockscreen)
  show(section) {},
  // 激活主题(成为当前主题)
  active() {}
}

其中,options 定义如下:

js
{
  // 名称
  name: 'default',
  // 标题
  title: '政务蓝'
  // 作者,
  author: '中科天翔',
  // 预览图
  image: "/images/themeImg1.png",
  // 描述信息
  description: '蓝色稳重,简洁大气,作为默认主题,通用性高',
  // 主界面
  main: () => import(/* webpackChunkName: "main" */ './theme/Main.vue'),
  // 纯工作区
    page: () => import(/* webpackChunkName: "main" */ './theme/Page.vue'),
  // 登录界面
  login: () => import(/* webpackChunkName: "main" */ './theme/Login.vue'),
  // 锁屏页面
  lockscreen: () => import(/* webpackChunkName: "main" */ './theme/LockScreen.vue'),
  //引入的css文件列表
  css: ["/theme/index.css"]
}

其中,imagecss 中的地址如果不以 http://https://开头的,必须相对于应用的静态文件夹(默认为:public)目录地址,如:

/theme/index.css => public/theme/index.css
theme/index.css => public/theme/index.css
/index.css => public/index.css

说明

默认主题为 mas-explorer 应用提供的 default,其定义如前文所述。其余主题定义过程中,如其中部分属性没有定义,则使用默认主题中相应属性替代

封装的UI 组件

页头

  • Logo,
  • LogoImg,
  • LogoTitle,
  • Header,

菜单

  • Menu,
  • SysMenu,
  • LeftSubMenu,
  • MenuItem,
  • SubNavBar,
  • SubNavBarLogo,
  • SubNavbarIsHide,
  • SidebarFirst,
  • HeaderNoMenu,

主题

  • Theme,
  • LoginMask,

表格

  • Title,
  • TableNoData
  • Pagination,

窗口大小拖拽

  • Container,
  • Splitter,

其他

  • SysPanel,
  • GuideMask,
  • Label,
  • Badge,

对微应用能力的扩展

全局 UI 组件

按Vue框架制作的全局UI组件,可以通过components属性,在微应用定义中声明,平台会自动注册为全局组建

js
{
  ...
  components: [
    // 数组定义方式:[tagName, 组件]
    ["uiblock", UIBlock],
    // 仅组件,使用组件定义中的name属性值作为tagName
    Container
  ]
}

开发主题

各应用可以自行开发并定义主题,主题包括:基本信息、页面和样式信息等,页面又包含登录页、锁屏页、主页面和纯工作区,分别以 loginlockscreenmainpage定义。应用对外定义主题时,在应用定义对象中,通过 themes 属性设置,具体定义如下:

js
themes: [
  {
    // 名称
    name: 'default',
    // 标题
    title: '政务蓝'
    // 作者,
    author: '中科天翔',
    // 预览图
    image: "/images/themeImg1.png",
    // 描述信息
    description: '蓝色稳重,简洁大气,作为默认主题,通用性高',
    // 主界面
    main: () => import(/* webpackChunkName: "main" */ './theme/Main.vue'),
    // 登录界面
    login: () => import(/* webpackChunkName: "main" */ './theme/Login.vue'),
    // 锁屏页面
    lockscreen: () => import(/* webpackChunkName: "main" */ './theme/LockScreen.vue'),
    // 纯工作区
    page: () => import(/* webpackChunkName: "main" */ './theme/Page.vue'),
    //引入的css文件列表
    css: ["/theme/index.css"]
  }
]

主界面

作为登录后的框架页面,一般包含菜单和工作区,至少包含一个 router-view 节点作为工作区,举例如下:

vue
<template>
  <el-container ref="container" class="main">
    <el-header style="height:64px;">
      <NextHeader ref="header" />
    </el-header>
    <el-main ref="mainContent">
      <transition name="component-fade" mode="out-in">
        <router-view class="main-content" />
      </transition>
    </el-main>
      <GuideMask/>
  </el-container>
</template>
<style scoped>
.el-container {
  height: calc(100vh);
   font-family: 'Source Han Sans CN Regular';
}
.el-header {
  background-color: #1A81FF;
  color: white;
  padding: 0;
}

.el-main {
  height: calc(100vh - 64px);
  background: #f0f2f5;
  padding: 24px;
  position: relative;
  width: 100%;
  overflow: auto;
  overflow-x: hidden;

}
.main-content{
  height: 100%;
  width: 100%;
  box-sizing: border-box;
  /* background: #fff; */
  border-radius: 12px
}
<style>

纯工作区

当页面进入编辑、预览模式,即 this.$app.ctx.modepagemobild-page 时,主页面的菜单等附加部件需要隐藏,便于页面编辑。

为确保页面编辑、预览和实际使用过程中样式一致,纯工作区模式的主界面除隐藏菜单等部件外,工作区的显示样式要保持一致

登录页

用于系统登录,需要包含用户登录区,实现用户登录逻辑,举例如下:

查看代码
vue
<template>
  <div>
    <img class="login-image" :src="loginImage" :onerror="defaultImage"/>
    <div class="login-card">
      <div class="left"></div>
      <div class="right">
        <span @click="handleQrCodeClick" v-show="showQrCode" class="iconfont1 qrCode">&#xe6a3;</span>
        <span @click="handleQrCodeClick" v-show="!showQrCode" class="iconfont1">&#xe887;</span>
        <div slot="header" class="header">
          <NextLogo />
        </div>
        <div class="form-div" v-if="!showQrCode">
          <el-form  ref="form" :model="form" :rules="rules" labelPosition="top">
            <el-form-item label="登录名" prop="username">
              <el-input
                placeholder="请输入登录名"
                v-model="form.username"
                @keyup.native.enter.stop.prevent="$refs.password.select"
                ref="username"
              ></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input
                placeholder="请输入密码"
                v-model="form.password"
                @keyup.native.enter.stop.prevent="onSubmit"
                :type="secretFlag == false ? 'password' : 'text'"
                ref="password"
              >
              <i slot="suffix" :class="secretFlag == false ? 'iconfont icon-in_biyan' : 'el-icon-view'" @click="changeIconState('secretFlag')"></i>
              </el-input>
            </el-form-item>
            <el-alert
              :title="error"
              v-if="error && !showQrCode"
              :closable="false"
              type="error"
              show-icon
            ></el-alert>
            <el-form-item>
              <div class="group-btn-block">
                <el-button type="info" plain @click="resetForm" >重置</el-button>
                <el-button type="primary" @click="onSubmit">登录</el-button>
              </div>
            </el-form-item>
          </el-form>
        </div>
        <div class="hide-mask" v-if="showQrCode"><div class='div-qrcode-desc'>浙政钉扫码登录</div></div>
        <iframe v-if="showQrCode" :src="iframeSrc" scrolling="auto" frameborder="0" id="iframe" width="100%" height="100%"></iframe>
        <div :style="{display: showQrCode && error && error!='' ? '' : 'none'}" class="qrMsg">{{error && error !='' ? error : '登录失败'}}</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Login",
  data() {
    return {
      secretFlag:false,
      showQrCode: false,
      iframeSrc: this.$store.getters.setting.qrCodeUrl ? this.$store.getters.setting.qrCodeUrl: 'https://login.dg-work.cn/oauth2/auth.htm?response_type=code&client_id=scanTestSn_dingoa&scope=get_user_info&authType=QRCODE&embedMode=true&redirect_uri=http://localhost:8081/#/mas-panel/index',
      form: {
        username: this.$store.getters.user.username,
        password: "",
      },
      rules: {
        username: [
          {
            validator: (rule, value, callback) => {
              if (value === "") {
                callback(new Error("请输入用户名"));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
          {
            pattern: /^[a-zA-Z]+[0-9a-zA-Z-_]*$/,
            message: "登录名必须字母开头,仅包含字母、数字和下划线",
          },
        ],
        password: [
          {
            validator: (rule, value, callback) => {
              if (value === "") {
                callback(new Error("请输入密码"));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
        ],
      },
    };
  },
  mounted() {
    this.$refs.username.select();
    let that = this
    window.addEventListener('message', function(event) {
      if (event.data && event.data.code) {
        that.$store.commit("error");
        that.$store.dispatch("qrLogin", {qrCode: event.data.code}).then(() => {
          that.$router.push(that.$store.getters.home);
        });
      }
    });
  },
  computed: {
    error() {
      return this.$store.getters.error;
    },
    loginImage() {
      return this.$store.getters.setting.loginImage;
    },
    defaultImage() {
      return 'this.src="' + require('../assets/login.png') + '"';
    }
  },
  methods: {
    changeIconState(){
      this.secretFlag = !this.secretFlag
    },
    onSubmit() {
      this.$refs.form.validate((valid) => {
        if (valid) {
          this.$store.commit("error");
          this.$store.dispatch("login", this.form).then(() => {
            this.$router.push(this.$store.getters.home);
          });
        } else {
          return false;
        }
      });
    },
    resetForm() {
      this.$store.commit("error");
      this.$refs.form.resetFields();
    },
    handleQrCodeClick() {
      this.showQrCode = !this.showQrCode
    }
  },
};
</script>

锁屏页

用于锁屏后界面的显示,并且可以解锁,需要实现解锁逻辑,举例如下:

查看代码
vue
<template>
  <div>
    <img class="login-image" :src="loginImage"  :onerror="defaultImage"/>
    <div class="login-card">
      <div class="left"></div>
      <div class="right">
        <div slot="header" class="header">
          <NextLogo />
        </div>
        <div class="form-div">
          <el-alert
          :title="`已被${$store.getters.user.name}(${$store.getters.user.username})锁定,请解锁`"
          :closable="false"
          type="info"
          show-icon
        ></el-alert>
          <el-form ref="form" :model="form" :rules="rules"   labelPosition="top" @submit.native.prevent>
            <el-form-item label="密码:" prop="password">
              <el-input
                placeholder="请输入密码"
                v-model="form.password"
                @keyup.native.enter.stop.prevent="onSubmit"
                :type="secretFlag == false ? 'password' : 'text'"
                ref="password"
              >
                <i slot="suffix" :class="secretFlag == false ? 'iconfont icon-in_biyan' : 'el-icon-view'" @click="changeIconState('secretFlag')"></i>
              </el-input>
            </el-form-item>
            <el-alert
              :title="error"
              v-if="error"
              :closable="false"
              type="error"
              show-icon
            ></el-alert>
            <el-form-item>
              <div class="group-btn-block">
                <el-button type="info" plain @click="resetForm">重置</el-button>
                <el-button type="primary" @click="onSubmit">解锁</el-button>
              </div>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "LockScreen",
  data() {
    return {
      secretFlag:false,
      form: {
        username: this.$store.getters.user.username,
        password: "",
      },
      rules: {
        password: [
          {
            validator: (rule, value, callback) => {
              if (value === "") {
                callback(new Error("请输入密码"));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
        ],
      },
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.$refs["password"].select();
    });
  },
  computed: {
    error() {
      return this.$store.getters.error;
    },
    loginImage() {
      return this.$store.getters.setting.loginImage;
    },
    defaultImage() {
      return 'this.src="' + require('../assets/login.png') + '"';
    }
  },
  methods: {
    changeIconState(){
      this.secretFlag = !this.secretFlag
    },
    onSubmit() {
      this.$refs.form.validate((valid) => {
        if (valid) {
          this.$store.dispatch("unlockscreen", this.form);
        } else {
          return false;
        }
      });
    },
    resetForm() {
      this.$store.commit("error");
      this.$refs.form.resetFields();
    },
  },
};
</script>

样式表

前端主要以Element UI组件库为主,因此样式表文件中必须包含其样式文件

默认主题

此包提供默认主题,确保每个站点都有主题可以正常显示,效果如下:

默认主题

定义路由

全局路由定义主页面、默认工作区页面、切换主题和登录页4个,并提供 rootmain 插槽,各应用可以定义全屏页面路由到 root 插槽下,实现全屏页面(无主题菜单区域);也可以定义工作区页面路由到 main 插槽下,实现菜单切换工作区页面的功能。

此包对 Vue Router 进行扩展,增加了应用相对路径和插槽机制,所有相对路径路由均以 /<app_name> 为前缀改为绝对路径,其中 <app_name> 为当前应用的 name

js
{
  routes: [{
    path: 'users',
    slot: "main", // 默认,可不写
    component: () => import(/* webpackChunkName: "main" */ './pages/Users.vue'),
    children: [{
      path: 'report',
      component: () => import(/* webpackChunkName: "main" */ './pages/UsersReport.vue'),
    }]
  }]
}

路径规则

path 定义路径匹配规则,支持绝对路径、相对路径和动态路由等

  • 绝对路径

    / 开头的固定路径,如 /user-profile,匹配路径 /user-profile

  • 相对路径

    不以 / 开头的固定路径,如 users/info,匹配路径 /<app_name>/users/info

  • 动态路由

    动态路由是在绝对路径或相对路径之上,把路径的一部分区域改成变量,如 users/:id,匹配路径 /<app_name>/users/123,此时页面中可通过 this.$route.params.id 获取到路径中的 123;也可以用 users-*,匹配以 /<app_name>/users- 开头的任意路径,此时页面中可通过this.$route.params.pathMatch 获取到路径中的 users- 之后的所有字符串。

    动态匹配规则可参见 Vue Router 官网说明:https://v3.router.vuejs.org/zh/guide/essentials/dynamic-matching.html

路由区域

slot 定义路由组件显示区域,有效值为 rootmainmain为默认值

  • root 全屏页面

    表示路由组件显示成全屏页面,也就是主题不现实菜单等区域。

  • main 工作区页面

    表示路由组件显示在主题的工作区,也就是主题显示菜单区域等,工作区显示路由组件。

路由组件

component 定义路由组件,一般以() => import(/* webpackChunkName: "main" */ './pages/Users.vue') 定义异步加载组件,也支持同步组件。

子路由

children 定义子路由,实现路由层级嵌套。

应用内路由跳转

应用内定义的路由,在注册到平台路由时,会按规则增加应用 name 属性作为前缀,所以使用默认的路由跳转,path 参数上需要添加 name 属性,不方便,也不利于后期 name 属性的变更,因此在应用原型对象上扩展 router 属性,提供 push 方法,用于应用内路由跳转,可通过 this.$app.router.push(url)调用。

菜单

默认定义了主菜单和系统菜单两个,并提供 main 插槽作为主菜单区,system 插槽作为系统菜单区,profile 插槽作为系统菜单/用户菜单区,console 插槽作为系统菜单/用户菜单/系统管理区

系统菜单

菜单定义格式如下:

json
{
  navs: [{
    title: "业务菜单组A",
    slot: "main", // 放到主菜单区
    children: [{
      title: '业务A1',
      auth: 'r.user',
      path: 'users',
    }, {
      title: '业务A2',
      icon: 'el-icon-lock',
      path: 'roles',
    }]
  },{
    title: '系统管理',
    icon: 'el-icon-setting',
    auth: 'r.admin || r.platform',
    slot: "console", // 放到系统管理区
    path: '/system',
    children: [{
      title: '系统管理1',
      icon: 'el-icon-lock',
      path: 'roles',
    },{
      title: '系统管理2',
      icon: 'el-icon-news',
      handler: function (vm) {
        ...
        // 弹出窗口等,不切换页面
      }
    }]
  }]
}

菜单项名称

title 定义菜单项名称

菜单项图标

icon 定义菜单项图标,值必须是已加载的图标库的图标样式

路径

path 定义菜单路径,支持相对路径和绝对路径,如同路由定义中的 path,相对路径也会自动增加 /<app_name>前缀

非页面切换菜单项

系统菜单中,还有些如“退出系统”等特殊操作,不是切换工作区页面,而取消执行脚本的菜单项。此时可以使用 handler 定义一个方法执行特殊逻辑,而不定义 path 属性。

菜单访问权限

auth 定义菜单访问权限,值为权限表达式。权限表达式可参考:权限验证

跨应用打开窗口

应用定义 action 提供给其他应用调用,而此 action 逻辑中如果涉及到弹窗的话,使用 this.$dialog 方法,会使弹窗页面内的逻辑调用 this.$app 时,出现混乱。因此在应用上定义 $dialog 方法,避免此情况的产生。

对上下文对象的扩展

业务菜单数据

在上下文对象中定义 navs 数组,其值是通过对所有微应用定义的菜单汇总,并通过权限校验剔除没有权限的菜单项后获得。数据格式如下:

json
[
  {
    "title": "首页",
    "icon": "el-icon-s-home",
    "auth": "r.default",
    "path": "/mas-panel/index"
  },
  {
    "path": "/lc-system/project-manager",
    "isDyn": "1",
    "auth": "",
    "icon": "el-icon-suitcase",
    "title": "项目管理"
  },
  {
    "path": "/lc-system/project-workspace",
    "isDyn": "1",
    "auth": "",
    "icon": "el-icon-suitcase",
    "title": "项目空间"
  },
  ...
]

系统菜单数据

在上下文对象中定义 sysNavs 数组,其值是通过对所有微应用定义的菜单汇总,并通过权限校验剔除没有权限的菜单项后获得。数据格式如下:

json
[
  {
    "title": "登录",
    "auth": "!r.default"
  },
  {
    "title": "锁定屏幕",
    "icon": "el-icon-lock"
  },
  {
    "type": "user",
    "path": "/user",
    "auth": "r.default",
    "children": [
      {
        "title": "切换主题",
        "icon": "el-icon-star-off",
        "auth": "r.default",
        "path": "/mas-explorer/themes"
      },
      ...
      {
        "title": "退出系统",
        "icon": "el-icon-news"
      }
    ]
  }
]

主题列表

通过合并所有微应用定义的主题并实例化成 Theme 对象后,以 <app_name>.<theme_name> 为属性名,保存在上下文的 themes 对象上。

js
{
  "mas-explorer.default": Theme{...}
}

路由对象

上下文中以 router 保存了对全局路由对象的引用

对Vue原型的扩展

弹出窗口

主页面打开弹出窗口显示其他页面,用法如下:

js
$dialog({
  width: "60%", // 窗口宽度
  top: "47px",  // 窗口顶部位置
  title: "Json编辑器",  // 窗口标题
  appendToBody: true, // 是否添加在body上,不加在body上可能会被其他元素遮挡
  closeOnClickModal: false, // 是否可以在弹窗外单击鼠标关闭弹窗
  ... // 此处可以定义el-dialog的其他属性,参见Element UI官网
  noBindOptions: true // 必填参数,解决兼容性问题
}, "UIBlock", {
  code: "page-code",  // 需要在弹窗中显示的页面代码
  ...   // 此处可以定义code指定的页面所需要的参数
}).then(data=>{
  ... // 此处代码为弹出窗口被关闭时执行,其中data为弹出窗口放回的数据
})

弹出窗口中的页面需要主动关闭弹出窗口时,用法如下:

js
$emit("close", data) // 其中data为需要返回给主页面的数据
$emit("cancel") // 关闭窗口,不返回任何数据

弹出抽屉面板

主页面弹出抽屉面板显示其他页面,用法如下:

js
$drawer({
  size: "60%", // 抽屉大小
  title: "Json编辑器",  // 窗口标题
  appendToBody: true, // 是否添加在body上,不加在body上可能会被其他元素遮挡
  ... // 此处可以定义el-dialog的其他属性,参见Element UI官网
}, "UIBlock", {
  code: "page-code",  // 需要在弹窗中显示的页面代码
  ...   // 此处可以定义code指定的页面所需要的参数
}).then(data=>{
  ... // 此处代码为弹出窗口被关闭时执行,其中data为弹出窗口放回的数据
})

弹出窗口中的页面需要主动关闭弹出窗口,用法如下:

js
$emit("close", data) // 其中data为需要返回给主页面的数据
$emit("cancel") // 关闭抽屉,不返回任何数据

内部资料,请勿外传