讲讲项目里的仪表盘编辑器(二)

这篇具有很好参考价值的文章主要介绍了讲讲项目里的仪表盘编辑器(二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

应用场景

        正常来说,编辑器应用场景应该包括:

  •         编辑器-预览
  •         编辑器
  •         最终运行时    

怎么去设计

        讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

        上一篇推文,我们已经大概了解了编辑器场景。接下来,我们来看预览时的设计

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

编辑器-预览

        点击预览按钮,执行以下逻辑:

  /** @name 预览 **/
  async handlePreview() {
    ...
    // 打开抽屉组件,并往里面放置运行时模块
    createDrawer(
      h => h(DashboardRuntime, { props: { dashboard: this.form } }),
      {
        title: '预览仪表盘',
        width: 'calc(100vw - 200px)',
      },
    );
  }

        也就是说:

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

        所以我们直接关注运行时表现

运行时设计

<template>
  <HabitContext :habitKey="habitKey" @init="habitContextInit">
    <!-- loading框 -->
    <a-spin v-if="loading" />
    <div
      v-else
      :style="styleCSSVariable"
    >
      <background :background="themeBackground" :class="$style.background">
          <grid-layout v-bind="layoutProps">
            <dashboard-item
              v-for="field in fields"
              :key="field.pkId"
            />
          </grid-layout>
      </background>
    </div>
  </HabitContext>
</template>

         这里套了一层HabitContext框架,是用来应用和记录用户习惯的(后面讲)。a-spin是加载层。紧接着和设计器差不多,局部变量样式集里面套了个背景框架和grid-layout布局。

        我们再看看dashboard-item的实现:

<template>
  <grid-item
    v-bind="layout"
    static
  >
    ...
  </grid-item>
</template>

         这里通过v-bind动态传入grid-item的属性(也就是拣选出来的x/y/w/h这些)。同时用static固定gird-item,使其无法缩放、拖动、被其他元素影响。

<template>
  <grid-item
    v-bind="layout"
    static
  >
    <div
      v-if="showChart"
    >
     ...
    </div>
    <!-- 没权限显示占位图 -->
    <div
      v-else
      style="height: 100%; width: 100%; display: flex; flex-direction: column"
    >
      <div>
        <span :style="titleCss">
          {{ field.name }}
        </span>
      </div>
    </div>
  </grid-item>
</template>

        这里就是简单的做了一个占位

<template>
  <grid-item
    v-bind="layout"
    static
  >
    <div
      v-if="showChart"
    >
      <div :class="$style.action">
        <template v-for="action in actions">
          <a-tooltip
            :key="action.key"
            placement="bottom"
            :mouseLeaveDelay="0"
            :title="action.name"
          >
            <x-icon
              :type="action.icon"
              @click="execAction(action)"
            />
          </a-tooltip>
        </template>
      </div>
      <component
        :is="component"
        :field="field"
      />
    </div>
    <!-- 没权限显示占位图 -->
    <div
      v-else
      style="height: 100%; width: 100%; display: flex; flex-direction: column"
    >
      <div>
        <span :style="titleCss">
          {{ field.name }}
        </span>
      </div>
    </div>
  </grid-item>
</template>

        浮层按钮还有具体的图表组件讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

数据流设计

        到这里,我们已经看完了编辑器功能的大概设计。接下来该写写这套系统最核心的部分,数据流设计了。

        创建一个仪表盘编辑器

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

        点下新增按钮后,我们传入一些系统参数【应用id,功能类别等等,在这里我们并不需要关注】储存新建仪表盘在系统的位置和属性。

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

        在接口储存完这些系统信息后,跳转到仪表盘页面进行最为关键的仪表盘初始化数据生成。

async handleAddForm(category) {
    // 弹窗让填写名称、图标等基础信息
    const result = await GroupForm.createModal(
      {
        data: { parentId: this.groupId, appId: this.appId, category },
      },
      {
        title: this.getCategoryName(category),
        width: '427px',
      },
    );
    // 调用接口保存
    const formId = await add(result);
    this.$message.success(this.$t('common.tips.add'));
    // 保存完毕后跳转到页面
    switch (category) {
      case FormCategoryType.DASHBOARD:
        return this.$router.push(`/dashboard-design/${formId}`);
        ...
      default:
        return this.$router.push(`/form-design/${formId}/form`);
    }
  }

        这里是通过vue-router进行跳转。这里也简单贴出路由代码

import DashboardDesign from '@/views/dashboard-design';

const DashboardDesignRoutes = [
  {
    path: '/dashboard-design/:id',
    component: DashboardDesign,
  },
  ...
];

export default DashboardDesignRoutes;

        到这里结束,一个仪表盘编辑器已经创建完毕了。它只存储了系统数据,没有仪表盘的初始数据。而当我们进入仪表盘编辑器页面的时候,完成有效编辑之后,才会以正式数据存储下来

        当然这里指的是前端数据,后端还是会根据我们穿进去的系统参数生成一份默认的接口向的仪表盘数据模板(比如默认权限、默认刷新时间上面的)

        进入仪表盘编辑器页面 

        先通过后端接口,拿到当前仪表盘编辑器id的接口数据

@formDesignModule.Action init;
async created() {
  ...
  await this.init(this.formId).then(() => {
      ...
  }
}

        大概长这样,记录一些系统信息或默认属性 。这里的init是vuex的action操作。为了是把数据保存到前端本地。更多关于本项目的vuex方法请看我另外一篇文章的介绍

讲讲项目里的状态存储器vuex_AI3D_WebEngineer的博客-CSDN博客https://blog.csdn.net/weixin_42274805/article/details/133237271?spm=1001.2014.3001.5501讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

         看看这个init的actions做了什么?

actions: {
    async init({ commit }, formId) {
        const form = await getFormData(formId);
        commit('saveForm', form);
    }
}
mutations: {
    saveForm(state, data) {
      state.form = data;
      ...
      state.loading = false;
      state.changed = false;
    }
}

          这里是通过调用接口获取当前仪表盘的数据,并把它存到当前的formDesignModule,也就是formDesign这个命名空间的仓库里。

        

        仪表盘编辑页面的状态管理器

        我们刚刚看到了代码,在编辑页面created里我们执行了init。其实就是非显示地获取数据。吧获取数据的过程从页面隐式地放到了状态管理器里的actions里面。并通过state返回关注的数据。这样子无论我们在仪表盘功能里怎么去跳转页面,都不需要再重新调用接口了,而是直接从仓库里拿。

  @formDesignModule.State form;
  @formDesignModule.Action init;
  @formDesignModule.State loading;
  @formDesignModule.State selectedField;
  @formDesignModule.Getter fields;
  @formDesignModule.Mutation updateSelectedField;
  @formDesignModule.Mutation selectField;
  @formDesignModule.Mutation updateSetting;
  @formDesignModule.Mutation saveForm;
  @formDesignModule.Mutation updateDashboardConfig;
  @formDesignModule.Action save;

        大概有这些属性和方法来完成编辑器的功能实现。看看就行了。紧接着我们来讲其中一些实现

        点击添加组件到仪表盘

        有两种添加方法:

        ① 点击组件按钮添加

        ② 拖拽组件添加

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

        点击组件


handleClickAdd() {
    ...
    // 初始化layout
    const layout = getDashboardLayoutByType(type);
    const layoutList = ensureArray(this.$refs.container.layout);
    layout.x = (layoutList.length * 2) % 60;
    layout.y = layoutList.length + 60;
    field.widget.layout = layout;

    // 初始化风格
    field = this.initFieldStyle(field);
}

        getDashboardLayoutByType是根据你点击的组件生成默认的组件layout数据。比如图片组件定义的默认layout是:

export function getDashboardLayoutByType(type) {
  const layout = getDashboardControlMeta(type, 'layout');
  return { x: 0, y: 0, ...(typeof layout === 'function' ? layout() : layout) };
}

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

         这时返回了一个初始化的layout即{w:30,h:15,minH:7,x:0,y:0}。

     const layoutList = ensureArray(this.$refs.container.layout);这里是直接获取设计器组件里面的layout属性(它的data值)。这个layout目前是个空数组(因为是新建的仪表盘,里面没有组件)。

layout.x = (layoutList.length * 2) % 60;
layout.y = layoutList.length + 60;
field.widget.layout = layout;


// 更新布局
this.$refs.container.syncLayout();

        很好理解啦,我们吧初始化layout的横纵坐标调整到它应该在的位置上,并吧这个调整过的layout信息存储到新增组件的布局属性里(替换掉初始化layout)。讲讲为什么这么计算:        

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

        可以看到实例中这两个组件的x/y值并不像上面这个逻辑计算出来的。 如果按照上面那个逻辑计算出来,则应该是{x:0,y:60...}和{x:2,y:61...}。其实这个计算过程是为了保证第n+1个组件的x和y一定大于第n个。从而避免重叠出错,而至于精准的layout数据,是借助vue-layout-grid插件行自适应生成。具体怎么做,我们看代码:

  /** @name 同步layout **/
  syncLayout() {
    this.layout = ensureArray(this.fields).map(field => ({
      ...field.widget.layout,
      i: field.pkId,
    }));
  }
<grid-layout
     ref="layout"
     :class="$style.layout"
     :layout.sync="layout"
>
    ...
</grid-layout>

        很多人看到这里就要骂了,骗人,你这不是啥都没干?只是把layout重新赋值了一遍。让我们改下代码看看:

  /** @name 同步layout **/
  async syncLayout() {
    this.layout = ensureArray(this.fields).map(field => ({
      ...field.widget.layout,
      i: field.pkId,
    }));
    console.log(this.layout);
    await this.$nextTick();
    console.log(this.layout);
  }

        第一个输出:

[
    {
            h: 10,w: 12,x: 0,y: 0
 
    },
    {
            h: 20,w: 60,x: 2,y: 61
     }   
]

        第二个输出:

[
{
   h: 10,w: 12,x: 0,y: 0,i: "39b19b29-c8ef-4fd3-8604-d7e168196ae6"
},
{
   h: 20,w: 60,x: 2,y: 10,i: "5d684834-26bd-4d35-b7ff-36d8de9d903e"
},
]   

        可以看到此时this.layout已经变了。这是因为<grid-layout>已经自适应了布局。

        由此,我们的保存仪表盘布局方法也呼之欲出了:

save() {
     // 拿到同步后的this.layout
     const layout = this.$refs.container.layout;
    // 生成组件id和layout信息的映射表
    const layoutMap = generateMap(layout, 'i', item =>
      pick(item, 'x', 'y', 'w', 'h'),
    );
    ...
}

        先看到这里,这里要生成一份类似于:'amdous123623': {w:10,h:20,x:0,y:0...}这样的映射表,是整个仪表盘布局的储存并不是直接存储类似于girdLayout的这种数组,而是由一个个组件自身的layout属性(甚至无视组件排序)拣选出来生成this.layout。也就是说仪表盘的存储结构为Array<field>这样的。

save(fields) {
    const layout = this.$refs.container.layout;
    const layoutMap = generateMap(layout, 'i', item =>
      pick(item, 'x', 'y', 'w', 'h'),
    );
    this.privateUpdateFields(
      (fields || this.fields).map(field => {
        if (!layoutMap[field.pkId]) return field;
        return {
          ...field,
          widget: {
            ...field.widget,
            layout:{
                  ...field.widget.layout,
                  ...layoutMap[field.pkId],
                }
          },
        };
      }),
    );
  }

        拖拽添加组件到仪表盘

        前面我们已经讲了拖拽添加组件的思路,和预防错位或重叠的处理。现在来讲讲具体代码实现。

        之前讲过了,在control-list.vue也就是左边的组件列表拖拽出组件,触发@dragstart方法,同时往设计器里传入dragType。设计器里根据dragType找对对应的组件初始化layout

@Watch('dragType')
  handleDragTypeChange(type) {
    this.isInChildCom = false; // 重新拖动需要重置
    if (type) {
      this.dragLayout = {
        i: 'drag',
        ...getDashboardLayoutByType(type),
      };
    } else {
      this.dragLayout = null;
    }
  }

        假设此时拖拽元素已经拖拽到在设计器(也就是gird-layout)上面。触发@dragover.native="handleDrag"

handleDrag(ev) {
    if (this.isInChildCom) return; // 进入子元素范围则无需触发
    ev.preventDefault();
    this._handleDrag(ev);
}
@throttle(100)
_handleDrag(ev) {
    if (!this.dragType || !this.$el) return;
    if (
      this.dragContext.clientX === ev.clientX &&
      this.dragContext.clientY === ev.clientY
    )
      return;
    this.dragContext.clientX = ev.clientX;
    this.dragContext.clientY = ev.clientY;
    this.updateInside(ev);
    this.updateDrag(ev);
}

         _handleDrag每100秒记录一次拖拽元素的位置,当拖拽元素发生变动时,更新设计器视图。

 updateInside(ev) {
    if (!this.dragType || !this.$el) return;
    const rect = this.$el.getBoundingClientRect();
    const errorRate = 10;
    const inside =
      ev.clientX > rect.left + errorRate &&
      ev.clientX < rect.right - errorRate &&
      ev.clientY > rect.top + errorRate &&
      ev.clientY < rect.bottom - errorRate;
    if (inside && this.dragLayoutIndex === -1) {
      this.layout.push(this.dragLayout);
    }
    if (!inside && this.dragLayoutIndex !== -1) {
      this.layout.splice(this.dragLayoutIndex, 1);
    }
  }

        这里是获取设计器边界的位置属性(errorRate为误差范围,你可以理解为设计器有padding),判断拖拽元素是否在设计器边界内,如果是,就往layout里面加入它(重复则不加入),如果已经超出设计器,则移除。

讲讲项目里的仪表盘编辑器(二),三叠云项目,编辑器,typescript,前端,javascript,html,css

         我们往编辑器拖拽移动,可以看到这个虚线框会一直跟随变动,可能你们就要问了,上面的代码里dragLayout一但被添加进layout,那么dragLayoutIndex就不会是-1,也就是说layout里面的dragLayout不会改变(x或y)。那这个虚框是怎么还在移动的?

        其实啊,这个虚框并不由layout里的数据决定。而是由vue-grid-layout这个插件负责渲染的。在拖动的时候,this.layout是不会变的。我们只需要每100毫秒记录一次拖拽元素的当前位置this.dragLayout,直到放置生效之后,用this.dragLayout去覆盖this.layout里面的那个被拖动元素。

         所以updateDrag是为了更新this.dragLayout。通过clientY/X换算成vue-grid-layout的x,y

 const dragRef = this.getDragRef();
    if (!this.dragType || !dragRef) return;
    const rect = this.$el.getBoundingClientRect();
    const dragging = {
      top: this.dragContext.clientY - rect.top,
      left: this.dragContext.clientX - rect.left,
    };
    dragRef.dragging = dragging;
    const newLayout = dragRef.calcXY(dragging.top, dragging.left);
    this.dragLayout.x = newLayout.x;
    this.dragLayout.y = newLayout.y;
  }
getDragRef() {
    // vue-grid-layout默认在$children内存在一个组件实例了, 其实每次拖动直接取最后一个实例应该就可以了
    return this.$refs.layout.$children[this.$refs.layout.$children.length - 1];
}

        当我们放手时,触发 <grid-layout>组件上的drop事件,我们来看看@drop.native="handleDrop"的handleDrop方法

 async handleDrop() {
    if (this.isInChildCom) return; // 进入子元素范围则无需触发
    if (!this.dragType) return;
    ...
}

        重叠和空类型直接当做无效动作处理

 async handleDrop() {
    if (this.isInChildCom) return; // 进入子元素范围则无需触发
    if (!this.dragType) return;
    try {...}
    catch (e) {
      this.layout.splice(this.dragLayoutIndex, 1);
      throw e;
    }
    finally {
        this.$emit('update:dragType', null);
    }
}

         这个try catch我们之前已经讲过了。try里面的逻辑也很简单

try {
     let field = createDashboardField(this.dragType);
     ...
     field.widget.layout = pick(this.dragLayout, 'x', 'y', 'w', 'h');
     ...
     // 更新布局
      this.layout.splice(this.dragLayoutIndex, 1, {
        ...field.widget.layout,
        i: field.pkId,
      });
     // 提交数据存储
      this.$emit('add', field);
}

        拖拽移动组件位置

         由插件处理,会自动更新到this.layout

        放大缩小组件

        由插件处理,会自动更新到this.layout

        删除组件

async handleDelete(pkId) {
    const cloneFields = deepClone(this.fields);
    // 摘除删除的组件数据
    this.updateFields(
      cloneFields.filter(field => {
        return field.pkId !== pkId;
      }),
    );
    await this.$nextTick();
    this.$refs.container.syncLayout();
}
 /** @name 同步layout **/
  async syncLayout() {
    this.layout = ensureArray(this.fields).map(field => ({
      ...field.widget.layout,
      i: field.pkId,
    }));
    await this.$nextTick();
  }

        额外讲一下选中组件对组件进行修改

        当我们选中组件的时候,需要在vuex里登记一下当前的选中状态

<grid-item
    v-for="layoutItem in layout"
    ...
    @mousedown.native="handlePointerDown"
    @mouseup.native="handlePointerUp($event, layoutItem.i)"
>
    ...
</grid-item>

      加了一些位置判断,以防这个组件位置出错或已经不在布局里

  /** @name 鼠标设备按下与抬起事件处理 **/
  _pointerContext = null;
  handlePointerDown(ev) {
    this._pointerContext = {
      x: ev.clientX,
      y: ev.clientY,
    };
  }
  handlePointerUp(ev, pkId) {
    if (!this._pointerContext || !this.fieldMap[pkId]) return;
    const { x, y } = this._pointerContext;
    if (x !== ev.clientX || y !== ev.clientY) return;
    this.selectField(this.fieldMap[pkId]);
  }
 @formDesignModule.Mutation selectField;

        再来看看仓库的代码

    // fromdesign.js
    selectField(state, field) {
      state.selectedField = field;
    },

         如果当前组件的内容或属性发送变更,则执行

 commit('selectField', newField);

        

        文章来源地址https://www.toymoban.com/news/detail-729779.html

到了这里,关于讲讲项目里的仪表盘编辑器(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 新版Grafana仪表盘

    一 Grafana 是什么         Grafana 是一个开源的指标量监测和可视化工具,常用于展示基础设施的时序数据和应用 程序运行分析。         官网指路: https://grafana.com/         与前文相关的两个概念:         1)数据源(Datasource):定义了将用方式来查询数据展

    2024年02月13日
    浏览(41)
  • echarts绘制仪表盘

     代码展示:

    2024年02月13日
    浏览(44)
  • ChatGPT实现仪表盘生成

    Grafana是开源社区最流行的数据可视化软件,一定程度上也和 superset 一起被视为 tableau 等商业 BI 的开源替代品,很多IT 团队、科研团队,都会使用 Grafana 来做数据监控、挖掘分析。Grafana社区也有很多贡献者,在 github 上分享自己针对不同场景制作的数据分析仪表盘效果和配置

    2024年02月02日
    浏览(35)
  • QML 仪表盘小示例

    本次项目已发布在CSDN-GitCode,下载方便,安全,可在我主页进行下载即可,后面的项目和素材都会发布这个平台。 个人主页:https://gitcode.com/user/m0_45463480 怎么下载:在项目中点击克隆,windows:zip linux:tar.gz tar # .pro

    2024年02月05日
    浏览(43)
  • 15. Canvas制作汽车油耗仪表盘

    1. 说明 本篇文章在14. 利用Canvas组件制作时钟的基础上进行一些更改,想查看全面的代码可以点击链接查看即可。 效果展示: 2. 整体代码

    2024年02月11日
    浏览(41)
  • Prometheus + Grafana 搭建监控仪表盘

    目标要求 1、需要展现的仪表盘: SpringBoot或JVM仪表盘 Centos物理机服务器(实际为物理分割的虚拟服务器)仪表盘 2、展现要求: 探索Prometheus + Grafana搭建起来的展示效果,尽可能展示能展示的部分。 监控系统核心:prometheus-2.45.0.linux-amd64.tar 下载地址:https://github.com/prometheus

    2024年04月23日
    浏览(45)
  • 纯JS+Vue实现一个仪表盘

    在使用canvas的时候发现数值变化,每次都要重新渲染,值都从0开始,这和我的需求冲突。 利用 border-radius ,就可将正方形变成圆形 一共100个值,每两个刻度就要有线,到10线的长度会更长一点。其实和画钟表一样,0的位置是坐标轴的225°,到100的位置,总共是180°+45° 静下心

    2024年02月14日
    浏览(49)
  • QPaint绘制自定义仪表盘组件01

    网上抄别人的,只是放这里自己看一下,看完就删掉 ui Dashboard.pro  mainwindow.h  mainwindow.cpp main.cpp 

    2024年02月22日
    浏览(38)
  • 安装istio和部署实例以及仪表盘

    安装Istio 接下来我们将介绍如何在 Kubernetes 集群中安装 Istio,这里我们使用的是最新的 1.10.3 版本。 下面的命令可以下载指定的 1.10.3 版本的 Istio: 如果安装失败,可以用手动方式进行安装,在 GitHub Release 页面获取对应系统的下载地址: 其中 samples/ 目录下面是一些示例应用程

    2024年02月13日
    浏览(42)
  • 【监控仪表系统】Grafana 中文入门教程 | 构建你的第一个仪表盘

    Grafana 读音:/grəˈfɑːnˌɑː/ 在大厂工作久了,时常对一些工具的存在觉得理所当然。 比如说,需要计算资源的时候,一个配置文件就可以要来两百台虚拟化好的机子。需要试下缓存?点下鼠标就可以要到几十个配置好的 Redis 结点。 最省心的是,这些工具都已经根据工作流

    2024年02月02日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包