智+项目总结

338 阅读24分钟

一、项目简介

1.业务需求:

智+是一个数字化园区管理项目,包括后台管理和可视化大屏两个部分,实现对园区内的楼宇、企业、员工、 车辆和一体杆等进行数字化管理,以及通过园区3D模型实时展示园区概况。

2.使用到的技术栈:vue2、vue3、Vue-admin-template、ElementUI、js-cookie、dayjs、Echarts、Spline、 VscaleScreen、vueRouter、vuex、RBAC、Axios、微前端

二、后台项目搭建

1.利用脚手架vue-admin-template快速搭建后台管理系统

    git clone https://gitee.com/panjiachen/vue-admin-template.git
    npm i 
    //可能遇到的问题:
    //err network => 切换网络   
    //can not find module... =》 删除node_modules 和 package-lock.json 重新执行 npm i 
    npm run dev(查阅package.json-scripts)

三、用户登录功能

1.业务需求

用户使用注册过的账号与密码登录,可以勾选'记住我'本地存储账号密码,登录成功跳转后台管理系统 首页,首页显示用户头像与用户名,登录失败提示失败原因。

2.业务实现

2.1>

el-complete

npm i element-ui -S 导入Element-UI组件库构建结构,使用el-complete组件实现带有输入建议提示的输入框,列表数据为localstorage存储的账号密码对象数组的username映射,点击列表username自动键入选择的username同时触发@select回调,设置form.password=本地存储当前username对应的对象的password

2.2>

el-form表单单独校验(绑定4个属性,不包括label):

       <el-form :model="响应式表单对象" :rules="响应式规则对象">
        //校验字段必须在el-form绑定的表单对象中
        <el-form-item prop="校验字段" label="表单域字段">
        <el-input v-model="响应式表单对象中的属性" />    
        </el-form-item>
        </el-form>
        //规则对象{校验字段:[{required:true/partten:正则reg/validator:自定义校验函数,message:'',trigger:'blur/change/input/...'}...]}
        

2.3>

勾选'记住我',如果是方形要用多选框组件el-checkbox来做,单选框组件radio是圆形的,在el-checkbox元素 中定义v-model绑定变量,单一的checkbox组件(没有用name属性约定一组多选框)默认绑定变量的值会是 Boolean,选中为true

2.4>

登录按钮绑定点击事件,回调函数中首先用el-form的validate方法进行表单统一校验,防止用户没有填表直接点击登录,统一校验通过,判断checkbox绑定的响应式数据是否为true,true则执行vuex的actions方法调用登录接口,请求接口成功则将返回的token存储在vuex-state中并且利用js-cookie实现本地持久化,请求接口失败则返回Promise.reject(new Error())(防止登录失败仍旧跳转路由),在登录组件中使用async/await对dispatch的返回值做判断,成功才会把登录表单对象存储在localstorage的登录表单对象数组中,并跳转路由,提示登录成功。

2.5>

在src下创建permission.js,引入router实例对象,定义whiteList,通过router.berforEach(to,from,next)放置前置路由守卫,再通过jwt(JSON Web Token)对用户登录状态进行判断,注意:通过getToken()获取token必须在前置路由守卫中,否则只会在main.js编译时候执行一次获取token的操作,之后不会获取token更新,如果token存在则再判断vuex中用户信息id是否存在,进而判断是否已经获取了用户信息,如果没有获取用户信息则执行vuex的actions方法请求获取用户信息接口,并调用mutations方法设置vuex-state的用户信息数据,再在首页渲染过程中获取vuex-state中存储的用户信息即可。

关于token存储的总结

vuex基于内存,读写速度快,但是刷新就丢失;cookie存储则相反,基于磁盘,读写速度慢,但是刷新不丢失;这里采用vuex+cookie存储token,读取token从vuex中读取,而每次刷新页面都会从cookie中读取token并重新存储在vuex中。

与cookie类似的还有localstorage,localstorage同样可以实现本地持久化,读写速度也同样不如vuex,token存储在localstorage+vuex同样可以实现上述效果。

至于究竟选择localstorage还是cookie,我们要分析二者的区别根据要存储数据的特点选择,

token属于敏感数据,必须考虑安全性,localstorage可以被同源js访问,容易受到xss攻击(全称Cross-Site Scripting(跨站脚本攻击)),如果我们导入的js中有恶意脚本,那么恶意脚本是可以访问到我们存储在localstorage的token的;cookie可以制定httponly,来防止被JavaScript读取,也可以制定secure,来保证token只在HTTPS下传输,但cookie中存储的数据会跟随接口请求发送给服务器,容易受到CSRF攻击(CSRF:跨站请求伪造,攻击者通过一些技术手段欺骗用户的浏览器,虽然不能直接从浏览器获取用户信息,但是可以访问模拟用户访问用户曾经认证过的网站并从服务器获取数据),所以无论是localstorage还是cookie都存在安全问题,那就先抛开安全不谈;

localstorage可以存储5m左右数据,而cookie只能存储4kb左右,并且cookie能存储的数据条数一般也有限制,所以如果存储数据比较大的话可以选择localstorage;

cookie一般由服务器生成,可以设置失效时间,默认失效时间是浏览器关闭,而localstorage是永久存储,如果需要删除localstorage中的token需要手动清除;

四、后台业务功能

1.业务流程梳理--园区管理

先写园区管理,园区管理有两个模块--楼宇管理和企业管理,企业管理有业务--租赁楼宇,依赖于楼宇管理模块,所以先写楼宇管理,楼宇管理模块主要负责数据展示与提供,简单的CRUD,增与改一般共用一个组件,判断打开组件时是否传入id,来判断是执行增加逻辑还是修改逻辑,修改逻辑要先用id请求接口获取当前数据详情,回显当前数据,提交按钮回调中同样样判断id(id可以在打开组件时候存储在组件实例中),选择接口与对应交互;

C与U共用一个表单带来的问题

注意:如果是路由跳转携带id,每次打开路由表单数据都会自动初始化,但如果是弹窗渲染表单,那修改逻辑执行完毕,关闭弹窗的回调中需要手动初始化表单,否则点击添加,表单里会有数据。

查询操作的本质是修改接口参数,将接口参数对应的响应式数据绑定到查询输入框,通过修改输入框内容修改接口参数进而修改接口返回的数据,进而修改接口返回数据渲染的页面。

写完园区管理,写企业管理,企业管理首先依旧是对于企业的CRUD,其次还多了一个业务--添加合同,在楼宇数据与企业数据之间建立租赁合同关系。

el-select

首先企业的增加操作是通过跳转路由来提供渲染表单的页面的,表单里有一个选择所在行业的下拉框组件,需要在路由跳转之前请求接口获取下拉列表数据,如果是点击下拉框再获取接口,用户体验不好,el-select下拉框的数据绑定:el-select通过v-model绑定form中的数据,获取选中的option的value,v-for遍历行业接口返回的数组渲染option,label绑定item.name,value绑定item.id,v-model获取选中的id,同步到form.行业id,进而在点击确定提交后上传给接口。

el-upload

增加操作还用到了一个el-upload组件来上传文件,在before-upload的回调(默认实参为上传的文件)中判断上传文件类型是否合规,回调返回值为true则继续上传,

使用http-request(不是事件,是动态绑定属性,但属性值为函数)代替action(el-upload必选参数,属性值可以为空或者#但是不能没有这个属性)上传的地址,http-request回调函数参数是由上传文件封装而成的对象,我们这里el-form需要通过upload组件获取两个数据作为将来传给接口的参数,一个是营业执照Id,一个是上传文件所在服务器地址,

可以让后端写一个接口传入参数是上传文件,返回数据是营业执照id与上传文件所在服务器地址,也就是让后端执行了将上传文件存储在服务器的操作并且生成一个不与项目服务器已存储的营业执照id相同的id,

腾讯云存储桶

如果由前端自己写的话,这里可以用腾讯云实现,首先确保在腾讯云有一个可用的存储桶充当自己存储上传文件的服务器,然后项目中 npm i cos-js-sdk-v5 导入腾讯云js-sdk,必须默认导入cos,实例化cos,参数为{SecretId:'...',SecretKey:'...'},这两个数据要与自己腾讯云的秘钥匹配,因为存储桶一般都是公读私写,只有匹配上腾讯云账号秘钥才能写入数据。

然后调用cos实例对象的putObject方法,第一个参数为

{
Bucket:'存储桶名称'//查询腾讯云存储桶获取
Region:'地域名称',//查询腾讯云存储桶详情获取
Key:'上传文件名称',//自定义,一般直接通过http-request的回调函数参数获取,与本地上传文件同名
StorageClass: 'STANDARD'//固定值,
Body:上传的文件对象
},

第二个参数为

   (err,data)=>{if (data.statusCode === 200 && data.Location) 
   {
   //data.Location就是腾讯云返回的存储我们上传文件的地址,我们可以在这里使用uuid()生成一个独一无二的id作为营业执照id将来传给接口(先 npm i uuid 导入)
   }}

查看操作,首先渲染table里有一个合同状态,后端返回数据是数字,而table中显示的是汉字,需要做一个接口返回数据格式化,有两种方式,一种是用el-table-column的formatter属性绑定一个格式化函数,函数的返回值会作为当前column的值;另一种方法是在el-table-column下使用插槽,插槽里用插值表达式执行格式化,返回格式化数据。这里因为合同状态显示在页面中是tag样式,所以选择用插槽,插槽里用el-tag+插值表达式。

文件预览与下载

预览:a链接地址:'view.officeapps.live.com/op/view.asp…' + 上传文件服务器地址 ,a设置 target="_blank",点击a打开新的空白页跳转office准备的预览页;

下载:直接用a跳转上传文件服务器地址

处理完企业的增删改查,这个模块还有一个业务逻辑--添加合同,因为是给指定企业添加合同,需要传当前选中企业的id,所以控制弹窗打开的button是写在table行内,通过自定义插槽从组件内回传的row获取当前行id,这里还有一个要注意的是日期选择的是type=daterange的el-date-picker组件,该组件绑定的v-model是一个数组,数组元素是time1与time2的string数据,显示效果是可以选择time1 至 time2 一段时间范围,但是接口要的参数是两个string类型的数据(startTime,endTime),所以需要进行接口参数格式化,将数组第一项作为startTime参数,数组第二项作为endTime参数。

2.业务流程梳理--行车管理

行车管理包括4个小模块:计费规则管理、区域管理、月卡管理、停车缴费管理,业务依赖关系紧密,停车缴费管理依赖月卡管理与区域管理,既需要月卡管理模块数据来判断用户缴费状态,如果是月卡用户,月卡扣费,缴费金额0;又需要通过区域管理来判断计费规则,同时区域管理也要依赖计费规则管理,每一个区域都要关联自己的计费规则。

所以先做月卡管理与计费规则管理,还是基础的CRUD,注意分页组件el-pagination的pageSize要和接口的pageSize一样,否则页面显示会按照接口pageSize来渲染,会出现显示错误,分页与查询一样,本质仍旧是更改接口参数进而更改接口返回数据,从而更改接口返回数据渲染的列表

接下来是区域管理和停车缴费管理,区域管理还是CRUD,停车缴费管理只有查询渲染功能,没有手动增删改,由停车场入口的一体杆检测汽车入场并添加数据。

3.业务流程梳理--一体杆管理

一体杆管理和告警记录,一体杆管理还是CRUD,每一个一体杆都有一个独有的IP,对应一台一体杆计算机,当一体杆出现故障,一体杆会将故障类型与自己的编号上报;告警记录模块会获取到上报了故障的一体杆信息,管理员在这里可以执行派单操作,通过接口获取维修工列表,选择一个维修工,故障一体杆信息会展示在该工人的接单系统中

4.业务流程梳理--物业费管理

对于园区企业缴纳物业费账单的管理模块,还是基础CRUD,添加账单表单里有一个支付金额默认是disabled禁用的,当租赁楼宇与缴费周期全部选择了之后,支付金额会自动计算生成,这类由组件实例的普通属性计算而得到的属性叫做计算属性,计算属性除了由computed得到,还可以通过watch监听、事件监听实现,所以这里按说应该有3种方法实现支付金额自动生成,但是因为我们这里计算金额是通过接口完成的,而计算属性内部是不支持异步逻辑的,所以排除computed这种方法,只剩下两种方法:

//1.wathch监听
  watch: {
'addForm': {
//监听复杂数据类型开启深度监听
  deep: true,
  async handler(newVal) {
    if (this.addForm.arrTime) {
      this.addForm.startTime = this.addForm.arrTime[0]
      this.addForm.endTime = this.addForm.arrTime[1]
    }
    if (this.addForm.buildingId && this.addForm.startTime && this.addForm.endTime) {
      const [err3, res3] = await to(getPayAmountAPI({
        buildingId: this.addForm.buildingId,
        startTime: this.addForm.startTime,
        endTime: this.addForm.endTime
      }))
      if (!err3) {
        this.addForm.paymentAmount = parseInt(res3)
      }
    }
  }
}},

//2.事件监听,因为租赁楼宇id与租赁时间分别绑定在下拉框与data-picker组件上,所以都选择change事件
//思路:两个组件绑定一个回调函数,当两个组件任意一个组件触发了change事件,回调中判断是否两个组件绑定的
//v-model是否都有值,如果都有值,则调用接口进行计算,否则return

5.业务流程梳理--系统管理

系统管理模块包含两个子模块:角色管理与员工管理,这两个模块是实现RBAC权限控制的基础,角色管理模块可以增加删除修改查看角色信息,角色信息包括名称与描述以及最重要的权限信息,上述所有模块包括每一个大模块下的小模块都对应一个权限点,所有的权限点构成了一份树形数据,使用el-tree(自动识别包含children数组的数据为树形数据)组件渲染,但是接口返回的数据是平铺展开的数组(不包含children),所以我们要做接口返回数据格式化,利用递归将平铺数据格式化为树形数据

const changeListToTree = (list, pid) => {
  const arr = []
  list.forEach(item => {
    if (item.pid === pid) {
      const children = changeListToTree(list, item.id)
      item.children = children
      arr.push(item)
    }
  })
  return arr
}

添加/编辑角色跳转路由,使用el-steps组件实现步骤条:

     <el-steps :active="activeStep">
      <el-step title="角色信息" />
      <el-step title="权限信息" />
      <el-step title="检查并完成" />
    </el-steps>

activeStep等于几,第几个el-step就会高亮,同时可以手动利用activeStep控制下一步、上一步按钮、完成提交按钮以及渲染信息组件的显示与否,控制标签或者组件现实与否有两种方式:v-if/v-show ,前者会直接增删DOM节点,后者只是利用display属性控制元素显示与否,这里渲染信息组件显示与否使用v-show,因为不希望填写完毕的表单被清空,而下一步与上一步等按钮使用v-if或者v-show都可以,但是v-show不会引起回流,如果组件至少会切换显示状态一次甚至可能频繁频繁切换,那么只触发一次回流的v-show性能好于至少触发1次,可能触发多次回流的v-if,但是因为v-show不论组件显示与否都会进行初始化加载,如果标签/组件有可能从来不会显示,触发回流1次的v-show性能不如触发回流0次的v-if,因为v-if如果不显示是不会创建dom节点的

员工管理模块可以进行员工信息的CRUD,给每一个员工账号设置登录账号密码并绑定角色,员工会获得该角色下的所有权限,如果员工账号的状态是启用的,登录员工账号,只可以访问该账号绑定的角色下的权限允许访问的路由,访问其他路由会报403

五、权限控制(路由控制/按钮控制(微前端子应用入口控制))

基于角色的路由权限控制流程:

1.创建角色,绑定权限数据

2.创建员工,绑定角色,获取角色权限数据

3.将路由表routes分为静态路由表routes和动态路由表asyncRoutes,把需要做权限判断的路由放到动态路由表,
并给每个动态路由添加对应的权限标识,路由系统初始化时只绑定展开的静态路由表

4.使用员工账号登录,获取账号权限数据,根据动态路由表具体需求,格式化权限数据

5.使用格式化权限数据筛选动态路由表,将筛选得到的动态路由表通过router.addRoute()添加到路由系统中(注意特殊
角色判断,特殊角色返回的权限数据一般比较特殊,所以筛选得到的动态路由表可能不符合实际角色权限,需要单独确
定动态路由表)

6.当登录状态失效,无论是token过期还是主动退出登录,都应该重置路由并且清空用户信息,以便下次登录重新获取
用户信息并筛选动态路由,否则下次登录,路由系统中还是上一次登录的用户所拥有的路由权限

//手动重置路由
create newRouter = createRouter()
router.matcher = newRouter.matcher
//清空用户信息
this.$store.commit('user/delUserProfile')

//上述代码除了要写在退出登录按钮的回调函数中,还要写在axios响应拦截器对于401的错误处理逻辑中

完成这一步,已经基本实现了通过RBAC控制路由权限的目的,每一个账号都匹配了角色的权限路由,只能
访问当前账号所属角色有权限数据的路由

7.一般路由跳转在页面中都有对应路由菜单辅助,角色没有权限访问的路由不需要显示到角色的路由菜单中,实现过程
是:将筛选得到的动态路由表存到vuex中,将通过路由系统初始化的静态路由表router.options.routes控制的路由菜
单,改为由vuex响应式数据控制,实现路由菜单与账号动态路由表+静态路由表同步。

基于角色的按钮权限控制

同样是写在前置路由守卫中,当判断用户信息不存在时候,通过接口获取用户信息,将其中的按钮权限数据存储在vuex中,每一个需要进行权限控制的按钮都要写上v-if,属性值是判断当前按钮的权限标识是否在vuex中存储的当前用户拥有的按钮权限数组中,在则返回true,否则false,微前端整合到主应用上的子应用也是通过按钮权限控制,无法通过路由权限控制

六、前台项目搭建

后台项目基于vue3开发,使用基于vue-cli脚手架的vue-admin-templete搭建项目,前台项目使用create-vue脚手架搭建项目:

npm init vue@latest
//相当于npx create-vue@latest

前台项目与后台项目共用一个登录接口,封装utils/cookie.js 操作同一个token

没有使用微前端整合主应用与子应用之前的cookie共享问题

cookie / ls / session 本身会有跨域问题 不同域下的cookie信息是不共享的

解决方案:在主域名一致的情况下,可以让cookie信息实现共享,将后台项目启动、登录,把token存入本地cookie,然后把子项目的地址手动换成http://localhost:端口 和主项目保持主域一致即可

//npm i js-cookie
import Cookies from 'js-cookie'

const KEY = 'token_key'//与主应用相同

export function getCookie () {
  return Cookies.get(KEY)
}

//当前子应用只读取cookie中的token
//export function setCookie (value) {
  Cookies.set(KEY, value)
}

//export function removeCookie () {
  Cookies.remove(KEY)
}

封装request请求模块

//npm i axios

import axios from 'axios'
import { getCookie } from './cookie'
const service = axios.create({
  baseURL: 'https://api-hmzs.itheima.net/v1',
  timeout: 5000 // request timeout
})

// 请求拦截器
service.interceptors.request.use(
  config => {
    const token = getCookie()
    if (token) {
      config.headers.Authorization = token
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  response => {
    return response.data
  },
  error => {
    return Promise.reject(error)
  }
)

export default service

绑定路由

// createRouter: 创建路由实例对象
// createWebHistory: 创建history模式的路由(hash / history)

import { createRouter, createWebHistory } from 'vue-router'

// 导入路由级别的组件
import HomeView from '../views/HomeView.vue'
import BigScreenView from '../views/BigScreenView.vue'

const router = createRouter({
  // 类似于mode:history 指定路由为history模式
 ** //上线后createWebHistory()参数为空***********!!!!
  history: createWebHistory(import.meta.env.BASE_URL),
  // 配置路由path和component对应关系
  routes: [
    {
      path: '/',
      redirect: '/big-screen',
      name: 'home',
      component: HomeView,
    },
    {
      path: '/big-screen',
      name: 'big-screen',
      component: BigScreenView
    }
  ]
})

export default router

初始化样式文件 styles/common.scss //文件命名必须是scss,sass会报错

//create-vue创建的项目默认不支持scss语法,需要我们手动安装sass
//npm i sass
html,
body,
#app {
  height: 100vh;
  overflow: hidden;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

七、前台业务梳理

基本数据渲染

调用接口返回数据,存储到ref容器中实现响应式,将响应式数据渲染到模板中,这里渲染的是滚动式的数字,选择用vue的插件count-to实现,npm i vue-count-to,使用< count-to :start-val="0" :end-val="123" :duration="1000" />代替< span>123</ span>;还要注意如果是在模板中渲染对象多层次(大于2层)属性,created/setup阶段发送的接口请求,可能在mounted/onMounted阶段渲染模板的时候还没有返回数据,此时对象的ref容器的值是{},那么访问对象下的属性会报错not defind,解决方法是使用可选链语法,在访问属性的点语法之前加?,它会判断对象是否是空对象,如果是空对象则不会执行点语法读取对象属性,等到接口返回数据了,对象不再是空对象,vue的响应式特性会保证模板的正常渲染。(或者使用v-if保证当前dom在数据存在时再渲染)

问题:为什么访问响应式对象的第1层属性不会报错,而第二层开始就会报错?

echarts实现2D数据可视化

npm install echarts
import * as echarts from 'echarts'//将echarts文件下的所有命名导出全部导入(*)并且挂载到echarts的对象下使用,
如果echarts文件本来就有默认导出,并且默认导出的对象上已经挂载了全部的命名导出,那么使用 import echarts 
from 'echarts'的效果是一样的
//1.准备dom元素作为echarts的容器
const refDom = ref({})//在模板中给要充当echarts容器的dom元素添加属性ref="refDom",将该dom元素存放到名为
refDom的响应式数据中
//2.echarts初始化
const myChart = echarts.init(refDom.value)
//3.配置echarts,自定义图表样式并用自己的数据替换图表数据data
const options = {...data...}
myChart.setOption(options)

3D建模师创建3D模型,后端维护3D模型数据,前端实现3D模型数据可视化

//npm i @splinetool/runtime  //导入3D模型解析包,需要注意解析包与3D建模师创建3D模型的工具是配套的,这里使用的是spline工具

<script setup>
// 导入模型解析的构造函数Application
import { Application } from '@splinetool/runtime'
import { onMounted, ref } from 'vue'

// 1. 准备dom容器
const ref3d = ref(null)
// 2.拉取3D模型并解析渲染
const init3dModel = () => {
  // 实例化解析器实例
  let spline = new Application(ref3d.value)
  // 利用实例上的load方法从后端接口拉取3D模型数据并在浏览器canvas节点解析渲染
  spline.load('https://后端接口').then(() => {
    console.log('3D模型加载并渲染完毕')
  })
}
onMounted(() => {
  await getParkInfo()
  // dom节点渲染完毕且后端接口数据已经返回再实例化Application,确保ref3d.value不是空对象,echarts实例化同样如此
  init3dModel()
})

</script>

<template>
  <div class="model-container">
    <!-- 准备3D渲染canvas节点 -->
    <canvas class="canvas-3d" ref="ref3d" />
  </div>
</template>

<style scoped lang="scss">
.model-container {
  height: 100%;
  background-color: black;
  width: 100%;
  flex-shrink: 0;
}
</style>

3D模型一般数据庞大,无论是从后端接口拉取数据,还是在浏览器解析数据并渲染都是十分耗费时间的,可以简单写一个基于CSS的loading组件提升用户体验

loading组件:

<script setup>
//vue3 用defineProps接收父组件传来的数据
defineProps({
  loading: {
    type: Boolean,
    default: false
  }
})
</script>

<template>
  <div v-if="loading" class="loading">
    <p class="text">园区资源加载中…</p>
    <div class="loading-process">
      <div class="process-wrapper"></div>
    </div>
  </div>
</template>

<style lang="scss">
.loading {
  position: absolute;
  left: 66%;
  top: 40%;
  transform: translateX(-50%);
  text-align: center;

  .text {
    font-size: 14px;
    color: #909399;
    margin-bottom: 16px;
  }

  .loading-process {
    width: 280px;
    height: 4px;
    background: rgba(255, 255, 255, 0.16);
    border-radius: 20px;
    overflow: hidden;
  }

  .process-wrapper {
    height: 100%;
    width: 5%;
    background: linear-gradient(90deg, #48ffff 0%, #3656ff 100%);
    animation-duration: 1000s;
    animation-name: loading;
  }

  @keyframes loading {
    0% {
      transform: scaleX(1);
    }

    1% {
      transform: scaleX(38);
    }

    100% {
      transform: scaleX(40);
    }
  }
}
</style>

使用loading组件的路由组件:

<script>
//准备传给loading组件的响应式数据
const showLoading = ref(false)
//在 init3dModel函数内第一行修改showLoading.value,显示loading组件
init3dModel(){
 showLoading.value = true
 ...
}
</script>

<template>
    <!-- 进度条 -->
    <LoadingComponent :loading="showLoading" />
        <!-- 准备3D渲染节点 -->
    <canvas class="canvas-3d" ref="ref3d" />
</template>

大屏适配

//需求: 适配几个固定的设备 要求在一个主屏上完美适配最贴合UI设计稿,其它少量的设备上,保证正常的浏览显示没有问题
//方案:
//npm i v-scale-screen
<v-scale-screen width="1920" height="1080">
   主体内容盒子
</v-scale-screen>

八、编程规范化--代码拆分优化

代码拆分优点:变得易复用与易维护
代码拆分缺点:增加组合成本

代码拆分分类:

1.组件拆分:将.vue拆分(html、css、js)

2.js拆分(函数拆分,一般以use打头命名):工具函数拆分(时间格式处理、深拷贝)、业务逻辑拆分 ---- js拆分统一逻辑:传入要处理的数据--> 数据处理 --> 返回处理后的数据

3.css拆分:

<style src="css文件路径"></style>

九、基于single-spa的路由劫持方案实现微前端(qiankun)

微前端使用背景:

  1. 后台管理部分使用的技术栈是Vue2,前台可视化部分使用的技术栈是Vue3
  2. 前台可视化项目不是独立存在,而是和后台管理项目共享同一个登录页面

基于single-spa的路由劫持方案实现微前端--原理

  • 1.重写路由监听函数history.pushState()劫持路由,在重写函数中执行原函数,保证能实现主应用路由匹配,再判断window.location.pathname是否能和registerMicroApps中的子应用路径匹配

  • 子应用资源打包部署配置'Access-Control-Allow-Origin': '*',允许跨域,当匹配到子应用路由后,封装importHTML函数(qiankun使用import-html-entry插件)使用fetch方法2.加载子应用资源,这个时候就拿到了子应用的 html 文本。直接通过 container.innerHTML = html 将文本放到容器内,能显示html,但是不会执行js逻辑。这是因为浏览器出于安全考虑,放到页面上的 html 如果包含了 js 脚本,它是不会去执行 js 的。需要手动处理 script 脚本。-->封装getExternalScripts使用不会判断传入参数类型的eval方法处理内联脚本(qiankun使用import-html-entry插件默认导出的方法返回的对象下挂载着execScripts方法执行脚本),使用fetch方法加载含有src的脚本,如果src指向的资源内还有内联脚本,继续使用eval处理。

  • 3.css隔离通过vue自身的组件样式隔离scoped实现

  • 4.js隔离,使用沙箱,防止应用之间变量污染,qiankun提供了三种不同场景使用的沙箱,分别是 snapshotSandboxproxySandboxlegacySandbox

  • 5.实现子应用钩子函数,当路由匹配到某个子应用时,执行该子应用下定义并挂载到浏览器顶级对象window的自定义mount函数(函数名包含子应用name用于匹配钩子),当路由不匹配该子应用时,执行该子应用下定义并挂载到浏览器顶级对象window的自定义unMount函数并清空container.html

沙箱:即 sandbox,顾名思义,就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。

qiankun使用流程

十、配合发布上线