功能代码分析

168 阅读5分钟

遥控配置中心功能代码分析

软件概述

遥控配置中心是遥控自动化处理软件平台中的一个软件配置项。该配置项接收人工操作命令,调用遥控处理方法库中的方法加工各类遥控信息,生成遥控数据块,并将生成的数据块存入数据库中,完成各种遥控指令及注入数据的加工。支持遥控信息编辑,具备新建、打开、编辑、保存等操作,实现遥控信息反编和比对等功能。

页面功能点实现

  • 如果忘记某个指令配置怎么办?

遥控配置中心需要懂遥控大纲的人使用对遥控指令进行配置,忘了某个指令配置时,给出提示配置信息。具体实现方法为封装一个组件点击[查看帮助],以 Drawer抽屉的形式,从屏幕边缘滑出浮层面板,结合Anchor 锚点选择指令跳转到页面指定位置 ,根据遥控大纲对指令长度、字节、注入数据等遥控帧格式进行填写参考,方便指令配置。

  • 如何对指令进行排序?

点击上移/下移对指令进行排序,确定排序后更新orderNum字段及其指令排序后位置传参给后端。

  /**
     * 移动模式
     * @param data - 单条数据
     * @param index - 索引
     * @param step - 上移-1 下移1
     */
    handleMove(record, index, step) {
      this.data.splice(index, 1);
      this.data.splice(index + step, 0, record);
      // this.move.isMoveMode = true;
      this.table.selectedList[0] += step;
    },
    //确定排序
    sumbitMove() {
      this.data.forEach((e, index) => {
        e.orderNum = index + 1;
      });
      this.requestPut(this.data);
    },
  • 动态导出Excel指令模板,并添加表头标注

单元格注释

单元格注释是对象,被存储在单元格对象的c数组内。实际上注释的内容根据注释的作者被分成了小块。每一个注释对象的a字段存储注释的作者,t字段是注释的纯文字展示。

注意:XLSB对作者的名字施加54个字符的限制。名字的长度超过54个字符可能造成其他的格式问题。

把注释标记为普通的隐藏,只需设置hidden属性,例如下面的片段在单元格A1内添加了单元格注释:

if(!ws.A1.c) ws.A1.c = [];
ws.A1.c.push({a:"SheetJS", t:"This comment is visible"});

if(!ws.A2.c) ws.A2.c = [];
ws.A2.c.hidden = true;
ws.A2.c.push({a:"SheetJS", t:"This comment will be hidden"});

单元格对象:c表示与单元格关联的注释,其他对象不逐一列举。

软件中实现:

选择导出指令的名称,获取所有指令配置导出为Excel表头。需求为指令代号/名称/类型为列表前三列,指令码为最后一列,中间动态添加表头名称,其中方式字、通过标识、APID等几个特殊的指令帧格式配置条目为表头时,需要添加填表标注提示用户。

async exportModal() {
      await this.getTcConfig();
      this.defaultHreadName.splice(3, 0, this.headNameList);
      var aoa = [this.defaultHreadName.flat(1)];
      let _name = this.wholeData[this.selectConfig].name;
      const filename = _name + ".xlsx";
      // Excel第一个sheet的名称
      const ws_name = "Sheet1";
      const wb = XLSX.utils.book_new();
      const ws = XLSX.utils.aoa_to_sheet(aoa);
      /** 表头添加标注 */
      // if (!ws.A1.c) ws.A1.c = [];
      // ws.D1.c.hidden = true;
      // ws.A1.c.push({ a: "Sheet1", t: "This comment is visible" });
      // console.log(ws);
      const txt = `1、二进制输入需以B结尾,填写样例:010101B
2、十六进制输入需以0x开头或H结尾,填写样例:0x475或475H
3、十进制输入无需前后缀,填写样例:61`;
      if (Object.keys(ws).length > 5) {
        let headComments = Object.keys(ws).slice(3, Object.keys(ws).length - 2);
        headComments.forEach((it) => {
          if (!ws[it].c) ws[it].c = [];
          ws[it].c.hidden = true;
          ws[it].c.autoSize = true;
          ws[it].c.push({
            a: "Sheet1",
            t: txt,
          });
        });
      }
      //这个是修改格式的代码 (目前不生效)
      for (const key in ws) {
        if (key.indexOf("!") === -1 && ws[key].v) {
          ws[key].s = {
            font: {
              name: "宋体", // 字体
              sz: 14, // 字体大小
              bold: true, // 加粗
            },
            alignment: {
              horizontal: "center",
              vertical: "center",
              wrap_text: true,
            },
          };
        }
      }
      //控制单元格宽度
      ws["!cols"] = [];
      Object.keys(ws).forEach((_, index) => {
        ws["!cols"][index] = { wpx: 110 };
      });
      // ws["!cols"] = [
      //   { wpx: 70 },
      //   { wpx: 110 },
      //   { wpx: 70 },
      //   { wpx: 110 },
      //   { wpx: 110 },
      //   { wpx: 110 },
      //   { wpx: 110 },
      //   { wpx: 110 },
      //   { wpx: 110 },
      // ];

      // ws["!merges"] = sheet["!merges"];
      XLSX.utils.book_append_sheet(wb, ws, ws_name); // 将数据添加到工作薄
      XLSX.writeFile(wb, filename); // 导出Excel

    },

备注:表头标注文字以字符串模板的方式,直接换行写入描述信息,有空格或者其他标志符导出表格无法识别及其解析,不能实现换行。

更改表头字体需要配合 xlsx-js-style使用

实现结果:

excel表头批注

1670319916682.jpg

组件封装技术点分析

  1. 列表复用

    各种指令配置列表大同小异,没有太多区别,整体封装一个组件,动态显示各种指令配置列表。 组件见代码展示。

    <div>
            <common-list
                :columns_tran="columns"  //表头
                :url_tran="url"         //接口
                request_key="oid"       //指令类型
                delete_key="oid"
                detail_link="delay-detail"  //指令详情相关操作
                :options_tran="option"    //功能键显示与否
            >
                <common-form ref="form" type_tran="oid" />
            </common-list>
        </div>
    
  2. 组件封装复用

    在封装的组件中点击添加/修改按钮,运用ref打开弹窗,同时使用插槽,获取父组件中内容,动态显示不同指令的配置信息。结合get(),set()方法获取和设置值,达到组件复用

    添加

    openModal() {
          this.$refs.modal.openModal("add");
          return;
    },
    

    修改

    handleEdit(record, index) {
          this.$refs.modal.openModal("edit");
          this.$nextTick(() => {
              if (this.isShow("import")) {
                this.$parent.$refs.form.set(record, this.$refs.sat.get());
              } else {
                this.$parent.$refs.form.set(record);
              }
            });
          
        },
    
    openModal(type) {
          this.visible = true;
          this.type = type;
          this.title = type === "add" ? "添加" : "修改";
        },
    

    get()

    get() {
          const res = Object.assign(
            {
              sat_id: this.getSat(),
            },
            this.form
          );
          let flag = this.$rules(rule, res);
          if (flag) {
            return res;
          } else {
            return false;
          }
        },
    

    set()

    set(source) {
          const {
            base,
            data,
            id,
            length,
            name,
            type,
          } = source;
          Object.assign(this.form, { id, length, name, type ,base,data});
    },
    

    点击确定按钮

    async updateList(type) {
          let data = await this.$parent.$refs.form.get();
          if (!data) return this.stopModalLoading(false);
          if (type === "add") {
            if (this.multi.isMultiMode) {
              this.data.unshift(data);
            } else {
              this.requestPost([data]);
            }
          } else if (type === "edit") {
            if (data.id) {
              this.requestPut([data]);
            } else {
              this.data.splice(this.currentEditIndex, 1, data);
              this.stopModalLoading();
            }
          }
        },
    
  3. 规则校验

    封装一个检验规则的方法,提交表单时检验是否有未填项目

        /**
         * 
         * @param {Array} rule [{name:"",message:""}] 
         * @param {Object} obj {}
         * @returns Boolean
         */
        Vue.prototype.$rules = (rule, obj) => {
            let msg = ''
            let flag = rule.every(i => {
                let k = obj[i.name]
                if (i.second) {
                    msg = i.msg
                    return k || obj[i.second]
                }
                if (!k) msg = i.msg
                return k
            }
            )
            if (!flag) {
                message.warning(msg)
            }
            return flag
        }
    
    let flag = this.$rules(rule, obj);
          if (flag) {
            return res;
          } else {
            return false;
          }
    

代码维护复杂程度

公用组件里面有很多方法及其数据处理都在组件内,由于各种指令配置的类型、数据格式、请求传参等存在差异,使得一个方法需要做很多判断。例如基本的查询列表参数,首先要区分是外层数据、底层详情数据,其次请求的数据源是否是midware表做校验,然后对是否有分页做区分,最后就是一般请求,可以看出逻辑功能比较复杂。

async getList(page) {
      this.multi.isMultiMode = false;
      this.data.splice(0);
      page = page || this.table.page;
      const { pagerow } = this.table;
      let sat_id = this.$refs.sat.get();
      let isPageMode = this.isShow("sat");
      const params = this.$route.params;
      this.table.loading = true;
      try {
        let res;
        // 区分请求详情
        if (this.detailMode) {
          this.table.page = 1;
          this.table.pagination.defaultPageSize = 9999;
          res = await request({
            url: this.url,
            params: {
              page,
              pagerow: 9999,
              ...params,
            },
          });
           if (this.isShow("sort")) {
             res.list.sort((a, b) => a.orderNum - b.orderNum);
           }
          this.data.push(...res.list);
        } else if (this.url === urls.group) {
          // 数据源是否是midware表
          res = await request({
            url: this.url,
            params: {
              sat_id,
              type: this.request_key,
            },
          });
          if (res) {
            res.sort((a, b) => a.orderNum - b.orderNum);
            this.table.pagination.total = res.length;
            this.data.push(...res);
          }
        } else if (isPageMode) {
          // 区分是否分页
          res = await request({
            url: this.url,
            params: {
              isall: true,
              sat_id,
              page,
              pagerow,
            },
          });
          if (res) {
            this.table.pagination.total = res.totalRows;
            this.data.push(...res.list);
          }
        } else {
          res = await request({
            url: this.url,
          });
          if (res) {
            this.table.pagination.total = res.length;
            this.data.push(...res);
          }
        }
      } catch (error) {
        message.error(error);
      } finally {
        this.table.selectedList.splice(0);
        this.multi.isMultiMode = false;
        this.move.isMoveMode = false;
        this.table.loading = false;
      }
    },

其余逻辑见线下代码演示

目前该项目为二期开发,比较不好维护