项目运行及调试

项目clone后直接使用 npm或yarn下载依赖,如使用npm推荐使用淘宝镜像(npm i -g cnpm —registry=https://registry.npm.taobao.org);运行命令为cnpm install或yarn。
本地默认访问后端地址为.env.development文件中的配置具体如下:

  1. NODE_ENV=development
  2. VUE_APP_API_FILe='https://lvshe.huashijc.com/' // 文件服务
  3. #VUE_APP_API_HOST='https://lvshe.huashijc.com/' // 接口访问地址
  4. #VUE_APP_API_HOST='http://10.31.6.42:8070/'
  5. VUE_APP_API_HOST='http://10.31.6.130:8070/'
  6. #VUE_APP_API_HOST='http://10.31.5.66/'
  7. VUE_APP_API_BASR="https://lvshe.huashijc.com/"
  8. VUE_APP_API_WSS="wss://icps.itence.com:8084/" // 长链接地址

可修改VUE_APP_API_HOST地址来指定本地后端接口访问的地址,如https://lvshe.huashijc.com/为线上地址,http://10.31.6.130:8070/为本地开发地址。此处配置为公共配置地址,即所有后端接口都在此处配置,如需要不同的业务需要访问不同的地址需要去vue.config.js文件中的反向代理,具体规则为:

  1. devServer: {
  2. port: 8080,
  3. open: true,
  4. disableHostCheck: true,
  5. proxy: {
  6. '/dev/base': {
  7. target: 'http://10.31.6.130:8088/',
  8. ws: true,
  9. changeOrigin: true,
  10. pathRewrite: {
  11. '^/dev/base': '/base',
  12. }
  13. },
  14. '/dev/resource/': {
  15. target: 'http://10.31.6.130:9999/',
  16. ws: true,
  17. changeOrigin: true,
  18. pathRewrite: {
  19. '^/dev/resource/': '/resource/',
  20. }
  21. },
  22. }
  23. }

此处将基础系统(dev/base)及文件系统(dev/resource)指向了2个不同的后端服务地址,只需要修改target的地址就可以了。

项目结构

  1. icps_baseWeb
  2. ├── package.json
  3. ├── src
  4. | ├── api(所有后端接口地址)
  5. ├── assets(所有图片地址)
  6. ├── common (部分公用JS方法)
  7. | └── common.js
  8. ├── components (组件)
  9. ├── filters (dom过滤方法)
  10. ├── mixins (ICPS混入及base混入)
  11. ├── router (所有路由)
  12. | └── index.js(其中包含所有路由跳转、过滤逻辑)
  13. ├── styles (所有公用样式)
  14. ├── utils (部分常量及自定义插件)
  15. ├── views (所有业务页面及路由页面)
  16. └── storeVuex所有数据)
  17. ├── config
  18. | ├── plugin.js
  19. | ├── config.default.js
  20. ├── config.prod.js
  21. | ├── config.test.js (可选)
  22. | ├── config.local.js (可选)
  23. | └── config.unittest.js (可选)
  24. └── public
  25. └── .env.development(本地开发配置)
  26. └── .env.production(线上打包配置、因为现在线上配置都是动态的、暂时没使用)
  27. └── package.json(依赖配置)
  28. └── vue.config.jswebpack配置)

路由配置(未分离)

前端文件(minxin.js)中存在一份路由配置,前端登录获取到系统及菜单时和minxin进行对比,符合条件的菜单才会被渲染出来。
minxin.js

  1. if (this.Topmenu.some(res => (res.data == 'basismenu'))) {
  2. this.basismenu = this.fiterbasismenu(this.basismenu) // 构造系统
  3. }
  4. /**ICPS系统 */
  5. if (this.Topmenu.findIndex(res => (res.data == 'ICPS')) !== -1) {
  6. this.fiterICPS(this.ICPS)
  7. /**第一層刪除*/
  8. this.ICPS = this.ICPS.filter(res => (res.arr.length !== 0))
  9. /**第二層刪除*/
  10. for (let a in this.ICPS) {
  11. for (let b = 0; b < this.ICPS[a].arr.length; b++) {
  12. if (this.ICPS[a].arr[b].arr) {
  13. if (this.ICPS[a].arr[b].arr.length == 0) {
  14. this.ICPS[a].arr.splice(b, 1)
  15. b--
  16. } else if (this.ICPS[a].arr[b].arr.length>0) {
  17. for (let i =0; i < this.ICPS[a].arr[b].arr.length; i+=1) {
  18. if (this.ICPS[a].arr[b].arr[i].arr && this.ICPS[a].arr[b].arr[i].arr.length === 0) {
  19. this.ICPS[a].arr[b].arr.splice(i, 1);
  20. i--
  21. } else if (this.ICPS[a].arr[b].arr[i].arr && this.ICPS[a].arr[b].arr[i]?.arr?.length> 0) {
  22. for (let j = 0; j < this.ICPS[a].arr[b].arr[i].arr.length; j+=1) {
  23. if (this.ICPS[a].arr[b].arr[i].arr[j].arr && this.ICPS[a].arr[b].arr[i].arr[j].arr.length === 0) {
  24. this.ICPS[a].arr[b].arr[i].arr.splice(j, 1)
  25. j--
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }
  32. }
  33. }
  34. for (let a in this.ICPS) {
  35. for (let b = 0; b < this.ICPS[a].arr.length; b++) {
  36. if (this.ICPS[a].arr[b].arr) {
  37. if (this.ICPS[a].arr[b].arr.length == 0) {
  38. this.ICPS[a].arr.splice(b, 1)
  39. b--
  40. }
  41. }
  42. }
  43. }
  44. this.ICPS = this.ICPS.filter(res => (res.arr.length !== 0))
  45. this.ICPS = JSON.parse(JSON.stringify(this.ICPS)) // 构造菜单

路由文件中的keepAlive将用于页面会否被缓存
router/index.js

{
        path: 'smartschedulj',
        name: 'smartschedulj',
        component: () => import(
          '@/views/platform/ICPS/smart/schedul/productionScheduling.vue'),
        meta: {
          title: '生产调度',
          system: 'basicSite',
          keepAlive: true, // 是否缓存此页面,如缓存,则在切换页面的时候将不刷新页面
        }
      },

业务权限

前端权限分为功能权限及按钮权限。
功能权限通过登录获取到的功能和本地已存在的菜单进行比对,相同的才渲染出来。
按钮权限通过点击菜单时获取到菜单配置的对应按钮
router/index.js

/**对于有权限的页面进行按钮权限获取*/
  getButtonList({ functionCode: to.name }).then(res => {
    if (res.code == 1001) {
      next();
      setButtonList(res.data)
    } else {
      next({ path: "/404" });
    }
  })

将按钮存储在本地,业务页面通过条件判读来进行渲染

<el-button v-if="ButtonList.findIndex(res=>(res.buttonValue=='TIANJIA'))!==-1" size="small" class="bottom_all" style="margin-left: 0" @click="addFromDate('添加')">
                  <i class="icon iconfont iconicon_button_add" style="font-size: 12px"></i>
                  添加周材
                </el-button>

此处判断用户是否存在添加按钮的权限。

基础系统业务代码

基础系统可以通过拷贝代码的方式直接开发,只有一点需要注意的地方
...vue

computed: {
    ...mapGetters({
      screenWidth: "getscreenWidth",
      screenHeight: "getscreenHeight",
      Collapse: "getsCollapse",
    }),
    style() {
      let num = 0;
      if (this.Collapse) {
        num = this.screenWidth - 124;
      } else {
        num = this.screenWidth - 220;
      }
      return "width:" + num + "px;";
    },
  },

此处的逻辑为通过部分参数去控制页面的宽度,主要逻辑为点击菜单的展开收起来修改页面的宽度。

ICPS系统业务代码

icps业务代码使用了minxin,具体逻辑可以查看ICPSmixin.js主要目的是处理业务的增删改查,所以icps的业务页面需要配置minxin
XXX.vue

import mixin from "@/mixins/ICPSmixin.js";
....
mixins: [mixin],
...
async created() {
    this.UserInfo = await getUserInfo();
    this.companylist = await permissionsCompanyList();
    this.stationIdlist = await getUserAutList(true)
    this.tableParam = { ...this.tableParam, code: "", carTypeId: "", stationName: '', plateNumber: '', enable: '', sysType: 1, };
    this.gettableData();
    this.gettypeList();
    getDicType({ code: "MaintenanceType" }).then((res) => {
      this.MaintenanceType = res.data.items;
    });
  },

icps的宽度控制卸载minxi中,所以需要在业务页面中通过:
/表单页类型,0为无树状,1为有树状,有树状在el-main加上display:flex/
pagesType: 0,
来控制页面宽度。

流程管理

  1. 流程配置:

流程分为自定义流程及开发表单,自定义流程需要配置展示的表单同时将表单作为表单权限来控制,开发表单页面元素都是写死的,可以通过审批要素来控制表单权限。
流程设计的节点信息由前端来控制,即前端判断当前节点的上一个节点及下一个节点、同时判断当条件成立时的下一个节点,此处业务稍微复杂,尽量不要修改流程节点配置。
processConfig.vue

setNodeIds(treeData, parentIndex, parentData, grandIndex, grandParent) {
      treeData.forEach((item, index) => {
        if (item.stepType === 5) { // 条件父节点及条件节点
          const conditionFalse = index < (treeData.length - 1) ? treeData[index + 1].nodeId : null;
          if (item.data) { // 条件节点
            if (grandParent) {
              // 判断上一节点是否为条件节点,如果是则通过递归获取到上一节点的ids,如果不是则就是上一节点的ids

2.流程业务:
自定义流程在保存时将所有业务数据组装成字符串,传给后端formStructureData,审批时通过getNodeList接口获取节点信息并判断当前节点是谁审批
taskDetails.vue

getNodeList(params).then(res => {
        if (res.code === 1001) {
          this.allNode = JSON.parse(JSON.stringify(res.data));
          res.data.pop();
          const newData = [];
          res.data.forEach(item => {
            item.instanceLogsList.forEach(items => {
              const data = {
                nodeName: item.nodeName,
                userName: items.userName,
                multiApprovalType: item.multiApprovalType,
                approvalOptions: items.approvalOptions,
                handlerTime: items.handlerTime,
                approvalStatus: items.approvalStatus || item.approvalStatus,
                processInstanceId: item.processInstanceId,
                id: item.id,
              }
              newData.push(data);
            })
          });
          this.tableData = newData;
          const firstPerson = res.data[0].instanceLogsList[0].userId;
          if (firstPerson === getUserInfo().userId) this.hasBackPermiss = true;
        }
      });

当节点状态approvalStatus为1时即为审批节点。
开发表单的业务处理逻辑都在flowContent.vue文件中,此文件中包含了所有的业务处理逻辑,包含保存、提交、打回、回退…等。
如需修改流程的逻辑请仔细阅读WorkFlow文件中的所有页面后在进行修改。
3.开发表单具体业务解析:
CheckFlows2.vue

submitDataType() {
      makeupdStatus2({
        id: this.formData.id,
        detectionStatus: 3,
      });
    },

主要用于修改业务数据的状态,将待审批改为审批中,当发起人提交流程时触发。


resetFlowState (type) {
      makeupdStatus2({
        id: this.formData.id,
        dataStatus: type===1?4:5,
      });
    },

同样用于修改业务数据状态,将业务数据由修改中改为驳回或已撤销,当审批人不同意和发起人撤销时触发。


handleBusiness() {
      const { query } = this.$route;
      const data = JSON.parse(query.dataContent);
      const newData = this.handleTypeList.find(item => item.itemName === this.form.handling);
      const params = {
        process_success: {
          url: '/icps/system/sign/intelligent/concrete/updateExceptionHandling',
          data: {
            id: data.id,
            exceptionHandling: newData ?.itemName,
            exceptionHandlingId: newData ?.id,
          },
          httpMethod: 'post',
        },
        process_failure: ''
      }
      this.showSendVisible = false;
      this.$refs.flowcontent.handleToFlow(params);
    },

最后一个审批人提交时通过时触发的回调方法,如当前审批流程通过后前端调另一个接口去生成其他流程


handleFinish(type, status) {
      const { query } = this.$route;
      const data = JSON.parse(query.dataContent);
      const copyData = JSON.parse(JSON.stringify(this.tableNav));
      copyData.shift();
      if (type === 2 && status * 1 === 2) {
        const params = {
          id: data.id,
          type: 2,
          typeId: data.dataTypeId,
          claim: copyData,
          remark: data.remark,
          outcome: 'NG',
          list: this.tableSource,
        }
        saveCheckOutResult(params);
      }
      addTab.$emit("remove", this.$route.name);
    },

审批通过或不通过的时触发的回调,type 1为审批中提交、2为最后一个人提交;status 2为同意、3为不同意。


handleSubmit(type) {
      const self = this;
      const { query } = this.$route;
      if (query.type * 1 === 2 && type === 2 && query.taskType * 1 !== 2) { // 发起流程
        this.showSendVisible = true;
        return
      }
      if (query.type * 1 === 10) { // 强制编辑

点击提交按钮时触发的方法,审批中时为同意或不同意、当query.type为10时为强制编辑业务数据、否则为保存业务数据,并提交。


initFlowsData(data) {
      const { query } = this.$route;
      this.formData = data;
      this.tableNav = JSON.parse(query.tableTitle);
      this.tableSource = JSON.parse(query.tableDataSource);
    },
    initFlowsData2() {
      const { query } = this.$route;
      this.formData = JSON.parse(query.dataContent);
      this.tableNav = JSON.parse(this.formData.tableTitle);
      this.tableSource = JSON.parse(this.formData.tableDataSource);
      this.form = JSON.parse(this.formData.form);
      this.title = query.taskName;
      this.billCode = query.receiptNumber;
      this.billDate = query.createTime;
      this.applyPerson = query.createUserName;
      this.taskStatus = query.taskStatus;
    },

initFlowsData为发起流程时回显业务数据、initFlowsData2为审批时回显业务数据。

if (query.type * 1 === 1) { // 发起流程
      this.initFlowsData(query);
      /**
       * @Description: 判断为哪种流程
       * @Param: 
       * @Author: wangwangwang
       */
      let code = 'OtherCheck2';
      if (query.flowsType * 1 === 1) {
        code = 'EnterCheck'
      }
      this.$refs.flowcontent.initBaseData(code);
      this.billDate = new Date().Format('yyyy-MM-dd hh:mm:ss');
      this.applyPerson = getUserInfo().userRealName;
      return
    }
    this.initFlowsData2();
    if (query.isReadOnly*1===1) {
      this.readOnly = true;
    }

当页面链接type参数为1时为发起流程、否则为审批流程。并处理对应业务逻辑。

全局状态管理(vuex)

1.人员选择组件,主要存储数据包括:系统所有用户、系统所有组织、系统所有角色、系统所有岗位。
主要逻辑为需要使用这些数据时先判断状态管理中是否已存在这些数据,如果存在则返回已存在的数据,如果不存在就调接口进行请求,并将数据存到状态管理器中,方便下次使用。
approvePerson.js

getorg({ commit }) {
    return new Promise ((resolve) => {
      const params = {
        page: 1,
        pageSize: 10000,
      };
      orgQueryPage(params).then((res) => {
        if (res.code === 1001) {
          commit('SET_ORG', res.data.data);
          resolve(res.data.data);
        }
      });
    })
  },
......
......
......

personModel.vue

/**
     * @Description: 获取角色数据
     * @Param:
     * @Author: wangwangwang
     */
    initRoleData() {
      const { state } = this.$store;
      if (!state.approvePerson.rolesData) { // 如果存在就从状态管理中获取
        this.$store.dispatch("approvePerson/getroles").then(res => {
          this.rolesData = res;
        });
        return
      }
      this.rolesData = state.approvePerson.rolesData;
    },

2.流程配置的nodeID管理,因为流程配置中流程节点id由前端生成,流程节点ID在多个组件之间反复使用,所以将nodeid保存在状态管理器中。nodeId.js
3.theme.js中包含页面主题、二级页面宽度、激活系统的index、基础系统菜单的index、icps系统菜单的index。

    theme: localStorage.getItem('theme'),
    list:'',
    screenWidth:0,
    screenHeight: 0,
    Collapse:false,
    //顶部导航栏的路由
    activeIndex:sessionStorage.getItem('topactiveIndex'),
    //缓存的三级页面路由
    index:sessionStorage.getItem('index'),
    ICPSindex:sessionStorage.getItem('ICPSindex'),

4.user.js 用于用户登录及登出时缓存用户信息、用户菜单、token等。用户登出时清除这些数据。

web缓存

localStorage: 用户信息(用户token、id、用户权限菜单)userInfo,用户权限(功能权限、头像、系统权限)permission
sessionStorage:
watermark 是否有水印
index 当前激活的菜单
buttonlist 当前菜单的按钮权限
navList 缓存的功能菜单
topactiveIndex 激活的系统

项目打包及部署

项目打包运行命令 cnpm run build,因为项目中的如果接口请求及连接都是动态获取的由运维人员在NGINX中配置反向代理实现接口请求,所以项目打包时不需要修改任何配置。
打包完成后将项目上传到服务器,分别为:
10.31.5.145 目录为 /usr/Nginx/html/
10.31.5.146 目录为 /usr/Nginx/html/
将打包完成的dist中的文件放到html文件中就可以了。
image.png
完成后访问路径由NGINX配置。