vue-grid-layout数据可视化图表面板优化过程所遇问题汇总

对于drag事件不熟悉的,请先阅读:《drag事件详解:html5鼠标拖动排序及resize实现方案分析及实践》

之前老项目grafana面板,如下图所示(GEM添加图表是直接到图表编辑,编辑完成后自动插入到面板最后):

产品希望做成从左侧拖曳进入,所见即所得,如图所示:

这个vue-grid-layout 本身就是支持:

https://jbaysolutions.github.io/vue-grid-layout/guide/10-drag-from-outside.html

为了性能,项目本身升级到vue3,因为整个项目采用TSX,本人改造的版本:https://github.com/zhoulujun/vue3-grid-layout

看了下案例代码:https://github.com/jbaysolutions/vue-grid-layout/blob/master/website/docs/.vuepress/components/Example10DragFromOutside.vue

整个代码如果用在工程里,肯定会卡死,因为:

drag: function (e) {
    let parentRect = document.getElementById('content').getBoundingClientRect();
 }

这个代码为什么不行?首先这个里面拖动计算直接在drag事件里面做的,其次这个案例drogover 是绑定在body上面,如果组件里面也需要接收左侧的拖曳组件,实现很麻烦:

首先,我们解决卡顿问题,其中比较隐蔽的是回流问题,造成掉帧严重

回流问题:

其实很多初级的前端同学只知道JS改变CSS会让浏览器回流,其实JS读取某些属性也会让浏览器回流,比如

js请求以下style信息时,触发回流(浏览器会立刻清空队列:)

  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • width、height
  • getComputedStyle()
  • getBoundingClientRect()

具体查看:《chrome对页面重绘和回流以及优化进行优化》:https://www.zhoulujun.cn/html/webfront/browser/webkit/2016_0506_7820.html

这个在drag里面即使加了防抖,组件多了照样会卡死页面的。

还有有些实现还使用了Bus 透传 drag/dragend 事件,其实这里可能没有理解 :

针对对象事件名称说明被拖动的元素dragstart在元素开始被拖动时候触发 drag在元素被拖动时反复触发 dragend在拖动操作完成时触发 目的地对象dragenter当被拖动元素进入目的地元素所占据的屏幕空间时触发 dragover当被拖动元素在目的地元素内时触发 dragleave当被拖动元素没有放下就离开目的地元素时触发整个拖拽事件触发的顺序如下:dragstart-> drag -> dragenter -> dragover -> dragleave -> drop ->dragend https://www.zhoulujun.cn/html/webfront/SGML/html5/2016_0124_434.html

理解了这个, 其实直接在dragover 做就可以了,这个案例给很多开源项目做了些误导哈*_*

既然

整个拖拽事件触发的顺序如下:dragstart-> drag -> dragenter -> dragover -> dragleave -> drop ->dragend,

那么在dragstart时在dataTransfer.setData,在后续的过程中直dataTransfer.getData读取就行行了吗?

dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题

dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题

dataTransfer.setData()中所设置的数据是存储在drag data store中,而根据W3C标准,drag data store有三种模式,Read/write mode, Read-only mode跟Protected mode。

W3C Working Draft中5.7.2.关于三种drag data store mode的定义

A drag data store mode, which is one of the following:

  • Read/write mode(读/写模式)
    • For the dragstart event. New data can be added to the drag data store.
    • 读/写模式,在dragstart事件中使用,可以添加新数据到drag data store中。
  • Read-only mode(只读模式)
    • For the drop event. The list of items representing dragged data can be read, including the data. No new data can be added.
    • 在drop事件中使用,可以读取被拖拽数据,不可添加新数据。
  • Protected mode(保护模式)
    • For all other events. The formats and kinds in the drag data store list of items representing dragged data can be enumerated, but the data itself is unavailable and no new data can be added.
    • 在所有其他的事件中使用,数据的列表可以被枚举,但是数据本身不可用且不能添加新数据。

    具体查看官方文档:https://html.spec.whatwg.org/multipage/dnd.html#drag-data-store

这样就可以解释为什么dragover中dataTransfer.getData()返回的数据为空,以及在dragover时dataTransfer中的types不为0了,因为在除了dragstart,drop以外的事件,包括dragover,dragenter,dragleave中,drag data store出于安全原因处于保护模式,因此不可访问

如果要实现dragover中访问dragstart中设置的数据,可以采用定义一个全局变量的方法,在dragstart中赋值,之后在dragend中清空。

另外,我在ondragover时,尝试给被拖拽元素添加class以改变其样式发现,虽然拖拽时class已经改变,但在拖拽过程中样式并没有改变,而是等到拖拽动作完成后,也就是drop之后样式才被应用上去,所以在dragover,dragenter,dragleave中做得更多的应该是对数据的处理,而不是应用效果。

drop事件不触发:

在发现页面拖动过程中,drop事件不触发,重新了看了下《drag事件详解:html5鼠标拖动排序及resize实现方案分析及实践》

drop:源对象拖放到目标对象中,目标对象完全接受被拖拽对象时触发,可理解为在目标对象内松手时触发。 dragenter和dragover事件的默认行为是拒绝接受任何被拖放的元素。因此,我们必须阻止浏览器这种默认行为。e.preventDefault(); 如果drop接收盒子要想接收到元素,那么接收的拖动元素 dragenter和dragover必须阻止默认行为。

发行也阻止默认事件了,但是我使用了节流事件,发现不行:

把 e.preventDefault()提取出来就可以,代码如下:

clientX、offsetX、screenX、pageX、x、y、clientLeft、clientTop区别

整体部分可以参看:《再谈BOM和DOM(6):dom对象及event对象位值计算—如offsetX/Top,clentX》

  • clientX、clientY:点击位置距离当前body可视区域的x,y坐标
  • pageX、pageY:对于整个页面来说,包括了被卷去的body部分的长度
  • screenX、screenY:点击位置距离当前电脑屏幕的x,y坐标
  • offsetX、offsetY:相对于带有定位的父盒子的x,y坐标

所以在drogover 中,直接获取offsetY、offsetX 即可:

const { offsetY: top, offsetX: left } = e;
el.dragging.data = { top, left };
const new_pos = el.calcXY(top, left);

这样其实是很方便的

整体实现:

代码23年中应该会全部提交github,这里把拖动的钩子函数贴出来供参考下

import { onUnmounted } from 'vue';
import { BaseHooksData } from '@dashboard/grid-panel/hooks/useHooks';
import { IGridPos, PanelModel } from '@/typings';
import useDashboardModuleStore, { getNewPanel } from '@store/dashboard';
import { initPanel, VIRTUAL_ROOT } from '@/constants';
import { throttle } from 'lodash';
import { deepClone } from '@/utils';
import usePanelEditorStore from '@store/panelEditor';
import createUID from '@/utils/createUID';

export default function useDragMove(
  data: Partial<BaseHooksData>,
  getLayout: () => void,
  editChart: (panel: PanelModel) => void,
) {
  const {
    layout,
    gridLayoutRef,
    gridItemRefs,
  } = data;
  const PanelEditorModule = usePanelEditorStore();
  const DashboardModule = useDashboardModuleStore();
  // 移动的临时组件
  let panel: PanelModel = null;
  let dragPos: IGridPos;
  onUnmounted(() => {
    dragPos = null;
    panel = null;
  });

  /**
   * 图表拖到仪表盘,穿件图表
   * @param e
   */
  function dragenter(e: DragEvent) {
    e.preventDefault();
    console.log('————————鼠标进入编辑器区域');
    if (layout.value.findIndex(item => item.i === 'drop') === -1) {
      const { addPanelData = deepClone(initPanel), addPanelType: type } = PanelEditorModule;
      const { space_uid } = DashboardModule.dashboard;
      panel = getNewPanel(type, new PanelModel({
        ...addPanelData,
        uid: 'drop',
        type,
        space_uid,
      }));
      dragPos = panel.gridPos;
      layout.value.push(dragPos);
    }
  }
  const dragoverThrottle = throttle((e: DragEvent) => {
    const index = layout.value.findIndex(item => item.i === 'drop');
    if (index === -1) {
      return;
    }
    const el = gridItemRefs.value[index];
    if (!el) {
      return;
    }
    const { offsetY: top, offsetX: left } = e;
    el.dragging.data = { top, left };
    const new_pos = el.calcXY(top, left);
    const { h, w } = panel.gridPos;
    gridLayoutRef.value.dragEvent('dragstart', 'drop', new_pos.x, new_pos.y, h, w);
    dragPos.x = layout.value[index].x;
    dragPos.y = layout.value[index].y;
  }, 300);
  function dragover(e: DragEvent) {
    e.preventDefault();
    dragoverThrottle(e);
  }

  function leaveDragArea(refresh = true) {
    const { x, y, h, w } = dragPos;
    gridLayoutRef.value.dragEvent('dragend', 'drop', x, y, h, w);
    // 强制隐藏placeholder
    let t =    setTimeout(() => {
      if (gridLayoutRef.value.isDragging) {
        gridLayoutRef.value.isDragging = false;
      }
      clearTimeout(t);
      t = null;
    }, 100);
    if (refresh) {
      panel = null;
      layout.value = layout.value.filter(item => item.i !== 'drop');
    }
  }


  function dragleave(e: DragEvent) {
    console.log('dragleave');
    const { offsetX, offsetY, clientX, clientY } = e;
    if (!((clientX === 0 && clientY === 0) && (offsetX < 0 && offsetY < 0))) {
      console.log('鼠标离开编辑器区域————————');
      leaveDragArea();
    }
  }

  function drop(e: DragEvent) {
    console.log('drop');
    e.preventDefault();
    const { type } = panel;
    leaveDragArea(false);
    if (['row', 'tab', 'column'].includes(type)) {
      const uid = createUID();
      panel.uid = uid;
      panel.gridPos.i = uid;
      DashboardModule.addCharts([panel]);
    } else {
      panel.uid = VIRTUAL_ROOT;
      panel.gridPos.i = VIRTUAL_ROOT;
      DashboardModule.addCharts([panel]);
      editChart(panel);
    }
    getLayout();
  }

  return {
    dragenter,
    dragover,
    dragleave,
    drop,
  };
}

这是其中拖曳的部分,其中的drop 钩子,可以在tab、swiper、column组件中使用。

代码优化

工程上,当然还得对代码进行拆解,整个仪表盘差不多5000多行代码,vue3可以拆解成多个钩子,方便代码的复用与维护

先写到这吧,后面有时间再理顺一下

本文转载于网络 如有侵权请联系删除

相关文章

  • Vim简明教程【CoolShell】

    大家好,又见面了,我是全栈君vim的学习曲线相当的大(參看各种文本编辑器的学习曲线)。所以。假设你一開始看到的是一大堆VIM的命令分类,你一定会对这个编辑器失去兴趣的。以下的文章翻译自《LearnVimProgressively》,我认为这是给新手最好的VIM的升级教程了,没有列举全部的命令,仅仅是列举了那些最实用的命令。很不错。——————————正文開始——————————你想以最快的速度学习人类史上最好的文本编辑器VIM吗?你先得懂得怎样在VIM幸存下来。然后一点一点地学习各种戏法。VimtheSixBillionDollareditorBetter,Stronger,Faster. 学习vim而且其会成为你最后一个使用的文本编辑器。没有比这个更好的文本编辑器了,很地难学,可是却不可思议地好用。我建议以下这四个步骤:存活感觉良好认为更好,更强。更快使用VIM的超能力当你走完这篇文章,你会成为一个vim的superstar。在開始学习曾经。我须要给你一些警告:学习vim在開始时是痛苦的。 须要时间须要不断地练习。就像你学习一个乐器一样。 不要期望你能在3天内把vim练得比别的编辑器

  • SLAM算法调研「建议收藏」

    大家好,又见面了,我是你们的朋友全栈君。这里写目录标题1SLAM算法1.1各类SLAM算法的发展1.2各类SLAM的优缺点2SLAM算法框架2.1基于激光雷达的SLAM算法2.2基于视觉的SLAM2.3视觉SLAM-间接法2.3.1ORB-SLAM2.4视觉SLAM-直接法1SLAM算法  SLAM,即:同步定位与地图创建(SimultaneousLocalizationandMapping,SLAM)。它试图解决这样的问题:一个机器人在未知环境中运动,如何通过环境的观测确定自身的运动轨迹,同时构建出环境的地图。SLAM根据硬件设备的不同主要有两种:基于激光雷达的SLAM和基于视觉的SLAM(VSLAM)。 可以明显看出,对于同一个场景,视觉SLAM在后半程中出现了偏差,这是因为累积误差所引起的,所以视觉SLAM要进行回环检验。1.1各类SLAM算法的发展1.2各类SLAM的优缺点 总体来说,激光SLAM是目前比较成熟的机器人定位导航技术,而视觉SLAM是未来研究的主流方向。未来,多传感器的融合是一种必然的趋势。取长补短,优势结合,为市场打造出真正好用的、易用的SLAM方案。2SLA

  • RestTemplate推送数据无响应的解决办法

    导读A平台在给其他平台推送数据时,A平台调用其他平台接口成功,但无返回结果,导致消费机数据不消费。逻辑分析应该有推送多长时间无返回结果就为推送失败,这样的逻辑。代码分析推送源码RestTemplaterestTemplate=newRestTemplate(); ResponseResultresponseResult=restTemplate.postForObject(syncUrl,formEntity,ResponseResult.class);复制感觉restTemplate有配置,但是查看了RestTemplate类,并没有相关配置。经过百度发现:SimpleClientHttpRequestFactory类。SimpleClientHttpRequestFactory部分源码publicclassSimpleClientHttpRequestFactoryimplementsClientHttpRequestFactory,AsyncClientHttpRequestFactory{ privatestaticfinalintDEFAULT_CHUNK_SIZE=4096

  • 一个不完全成熟的小想法--密度散点图

    这种密度散点图可谓是高大上了,其实做法也不难,甚至可以做的更好看,这个图的配色一看就知道是R做的,我摒弃R,用python来一发!!!缺乏数据的我自然就只会用np.random咯,废话不多,直接上干货。。。importnumpyasnpimportmatplotlib.pyplotaspltfromscipy.statsimportgaussian_kdeimportpylabfromsklearnimportlinear_modelx=np.random.normal(size=1000) y=x*3+np.random.normal(size=1000)xy=np.vstack([x,y]) z=gaussian_kde(xy)(xy)c=np.column_stack((x,y))fig,ax=plt.subplots()f1=np.polyfit(x,y,1)p1=np.poly1d(f1)yvals=p1(x)plot2=plt.plot(x,yvals,'r',label='polyfitvalues')plt.text(-3,5,&

  • SAP CRM,C4C和Hybris的页面技术明细信息查看

    CRM按F2就能看到页面的technicaldata,就能找到当前页面是哪一个BSPcomponent实现的:C4C也能看见technicaldataHybris比如这个productdetailpage,我想知道是哪个JSP文件实现的?问了成都Hybris开发同事,答案是没有,得自己找。囧找到productdetailpage:双击进去:找到这个productdetailpage的明细:用类似ABAP的思路:SE16查表:select*from{PageTemplate}where{name}='ProductDetailsPageTemplate'查出来6条记录,根据lastmodify判断出是最后一条:路径如下:完整路径如下:C:\Hybris\6.5.0.0.23546\hybris\bin\ext-template\yacceleratorstorefront\web\webroot\WEB-INF\views\responsive\pages\product另一种方法是打开这个文件:里面一样有jsp文件的名称。两种方式都非常不直观,没有WebUI的F2方

  • MIT 深度学习导论来啦!附视频下载

    点击我爱计算机视觉标星,更快获取CVML新技术相信很多同学读过MIT的《算法导论》(IntroductiontoAlgorithms)这本书,虽称“导论”,但其内容还是很丰富的。 上个月,同样出自于MIT的《深度学习导论》(IntroductiontoDeepLearning)课程上线,同样获赞无数。虽视频更新还未结束,且上线不足一个月,YouTube上却已经有43万观看量!既然称导论,课程设计者希望尽量降低课程学习门槛,只要有基本的微积分和线性代数知识即可学习,如果有Python基础那就更好了,没有的话也没关系。从内容上看,这门课程特点是: 1.覆盖深度学习的基础知识;2.涉及深度学习多个应用领域,包括计算机视觉、自然语言处理、语音识别、游戏、艺术等。以期读者既能学到知识,又能对技术的美妙应用感兴趣,激发大家进行更深入的学习和研究。课程大纲: 1.深度学习介绍;2.深度序列数据建模; 3.深度学习与计算机视觉; 4.深度学习生成模型;5.深度增强学习; 6.目前技术的局限和新趋势; 7.神经网络与符号AI结合的混合AI; 8.可泛化自主学习机器人系统;9.神经渲染;10.用于分子气味

  • H3C RIP

    下放默认路由: [R3]iproute-static0.0.0.00.0.0.0NULL0[R3]rip1[R3-rip-1]default-routeoriginatecost5 修改下放缺省路由的度量值为5[R3]disiprouting-tableprotocolrip路由引入:[R3]iproute-static123.1.1.024NULL0 [R3]rip1[R3-rip-1]import-routestaticcost8 引入前修改度量值为8路由过滤: R1入接口配置:[R1]aclbasic2000[R1-acl-ipv4-basic-2000]ruledenysource3.3.3.10[R1-acl-ipv4-basic-2000]ruledenysource3.3.3.20[R1-acl-ipv4-basic-2000]rulepermitsourceany[R1-rip-1]filter-policy2000importVlan-interface12 指定过滤接口R2出接口配置:[R2]aclbasic2000[R2-acl-ipv4-basic-2000]r

  • 【技术分享】Spark DataFrame入门手册

    本文原作者:赖博先,经授权后发布。一、简介SparkSQL是spark主要组成模块之一,其主要作用与结构化数据,与hadoop生态中的hive是对标的。而DataFrame是sparkSQL的一种编程抽象,提供更加便捷同时类同与SQL查询语句的API,让熟悉hive的数据分析工程师能够非常快速上手。   DataFrame是一种以命名列的方式组织的分布式数据集,可以类比于hive中的表。但是比hive表更加灵活的是,你可以使用各种数据源来构建一个DataFrame,如:结构化数据文件(例如json数据)、hive表格、外部数据库,还可以直接从已有的RDD变换得来。后面会把相关方法、接口跟大家一一道来。二、初步使用大家学习一门语言可能都是从“helloword!”开始的,这主要目的是让学习者熟悉程序运行的环境,同时亲身感受程序运行过程。这里我们也会从环境到运行的步骤进行讲解。导入spark运行环境相关的类所有spark相关的操作都是以sparkContext类作为入口,而SparkSQL相关的所有功能都是以SQLContext类作为入口。下面的语句是新建入口类的对象。最下面的语句是引入隐

  • Android 解锁 Gradle 依赖新姿势

    本篇文章已授权为微信公众号code小生发布前言今天在看大牛Github项目源码的时候,发现他们build.gradle文件的写法很有意思,仔细研究了一下,发现自己以前使用Gradle来依赖管理的姿势实在是太Low了,所以整理了一下今天解锁的Gradle依赖新姿势,分享给大家 相信现在大部分人都在使用Gradle来进行依赖管理,不得不说,Gradle真的相当简洁、好用,举个例子,如果我们想依赖okhttp,直接compile'com.squareup.okhttp3:okhttp:3.8.1'复制然后就能在我们的项目中使用okhttp进行网络请求了。不过,不知道你们有没有想过一个问题,如果okhttp版本升级了,那该怎么办?你可能会说直接在build.gradle进行修改就行啊,这当然是个解决方法。如果只有这个地方要进行修改的话,问题倒是不大。但是,如果你的项目里面有好几个module呢,现在APP的规模越来越大,项目里面有多个module是非常正常的,每一次的版本更新,你都要修改所有的地方,麻烦不说,还可能会遗漏掉某些地方。所以分享一下我今天解锁的Gradle依赖新

  • 最性感职业养成记 | 想做数据科学家/工程师?从零开始系统规划大数据学习之路

    大数据文摘作品,转载要求见文末作者|SAURABH编译|张伯楠,万如苑,刘云南引言大数据的领域非常广泛,往往使想要开始学习大数据及相关技术的人望而生畏。大数据技术的种类众多,这同样使得初学者难以选择从何处下手。 这正是我想要撰写本文的原因。本文将为你开始学习大数据的征程以及在大数据产业领域找到工作指明道路,提供帮助。目前我们面临的最大挑战就是根据我们的兴趣和技能选定正确的角色。 为了解决这个问题,我在本文详细阐述了每个与大数据有关的角色,同时考量了工程师以及计算机科学毕业生的不同职位角色。 我尽量详细地回答了每一项人们在学习大数据过程中遇到或可能会遇到的问题。为帮助你根据兴趣选择发展途径,我添加了一组树图,相信会对你找到正确的途径有所帮助。 注释:学习之路树状图在这个树状图的帮助下,你可以根据你的兴趣和目标选择路径。然后,你可以开始学习大数据的旅程了。后台回复“职业路径”3个字,下载高清版本。目录表1.如何开始?2.在大数据领域有哪些职位需求?3.你的领域是什么,适合什么方向?4.勾勒你在大数据领域的角色5.如何成为一名大数据工程师?o什么是大数据行业术语?o你需要了解的系统和结构o学

  • 数据结构树(二叉树的使用)

    #include<stdio.h> #include<stdlib.h> #definemaxSize127 typedefcharTElemType; typedefstructnode{ TElemTypedata;//结点数据 structnode*lchild,*rchild;//左右子女指针 intlength; }BinTNode,*BinTree;//二叉树定义 //创建二叉树 voidcreateBinTree_Pre(BinTNode*T,TElemTypepre[],int&n){ TElemTypech=pre[n++]; if(ch==';')return; if(ch!='#'){ T=(BinTNode*)malloc(sizeof(BinTNode));//递归根节点 T->data=ch; createBinTree_Pre(T->lchild,pre,n);//递归建立左子树 createBinTree_Pre(T->rchild,pre,n);//递归建立右子树 }else T=NULL;//否则建

  • JavaScript引用类型和值类型

    .container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px} .container:before,.container:after{content:"";display:table} .container:after{clear:both} .container:before,.container:after{content:"";display:table} .container:after{clear:both} @media(min-width:768px){.container{width:750px}} @media(min-width:992px){.container{width:970px}} @media(min-width:1200px){.container{width:1170px}} .row{margin-left:-15px;margin-right:-15px} .row:before,.row:after{content:"";display:ta

  • 我在富士康挨踢了七年(十三.悉尼工作篇 )

        [PS:距离上一篇随笔发表日期已经是一年多,在这一年里,是我人生的又一个转折,带领团队经常加班,却不觉得很辛苦,干劲十足,也感谢领导给力,年终绩效给了我的团队三个最优。也感谢中国的股市,第一年入市就小挣了一笔。]         我所在的悉尼office负责人是一个华裔中年女Ling,下面有两位办公室人同事和几个产线工。两位办公室同事是华裔,另外产线工也有好几位都是华裔或者马来西亚人,大家都会讲普通话,我之前还担心沟通的问题,现在毫无压力。      第一次出差,没人安排和指派工作,不太清楚要做什么事情,因为上一位要归国的同事刘工说系统已经上线,所以我就坐在办公室里玩电脑,偶尔帮另外一位出差的台湾同事Frank打帮手。Frank是公司副总的同学,所以他交代的事情我自然不敢怠慢,另外,Frank人也特别好,给我的感觉特别亲切。      就在我无所事事的时候,of

  • TurboLinux系统管理习题一

    TurboLinux系统管理习题一 1.使用vi编辑文本只读时,强制存盘并退出的命令是?(单选题)A:w!        B:q!       C   :wq!        D   :e!答案:C2.使用什么命令把两个文件的合并成一个文件?(单选题)Acat          Bgrep      C   awk        D   cut答案:A3以下哪一个命令只查找源代码、二进制文件和帮助文件,

  • Jenkins 中运行PowerShell 正常命令报错解决方法

    1、在命令后面加2>$null 2、设置JenkinsPowershell两个复选框不勾选。(StoponError,Profile) 如果您觉得本文对你有用,不妨帮忙点个赞,或者在评论里给我一句赞美,小小成就都是今后继续为大家编写优质文章的动力,百小僧拜谢! 欢迎您持续关注我的博客:) 作者:百小僧 版权所有,欢迎保留原文链接进行转载:)

  • 报表-表格-单元格自动合并

    在使用WynEnterprise 表格类报表中,经常会有自动合并单元格的需求,就是说,对于某些/个列的相同数据,不要每行显示相同的数据,而是跨行合并相同内容的单元格,如下图:其中的红框部分,就是自动合并后的效果,如果不合并,原始效果如下图:多数情况下,实现这种单元格合并的最佳方式是使用矩表元素,而不是普通表格。但是如果合并需求不是从左至右的各列都合并,而是靠后的某列(如上图中的【类别名称】)需要合并,前面的某列(上图中的【产品名称】)不要合并,那么普通表格可能是更好的选择。(一)设置某列单元格合并的方法设置某列单元格合并的方法是:(1)选中表格内明细数据行的单元格例如下图中的第二行的那些单元格:(2)设置【自动合并】选项(二)自动合并选项及其含义三种选项值的含义如下:(1)永不合并:同一列的不同行,无论数据内容是否相同,都不合并。这是默认的选项。(2)跨分组合并:同一列的不同行,只要数据内容相同,都要合并。就是【总是合并】的意思。(3)分组内合并:同一列的不同行,数据相同时,是否合并要根据前一列是否合并。其中,第三个【分组内合并】的具体效果,取决于前面的列是否合并。如果前面的

  • Android.mk(零)

    Android.mk可以生产的基本文件 LOCAL_PATH:P=$(callmy-dir)  //返回该Android.mk所在目录的路径,必须放在第一行定义了当前模块的相对路径 include$(CLEAR_VARS)//清除变量 清空当前环境变量 LOCAL_MODULE:=test //生产目标文件编译生成的目标名称 LOCAL_SRC_FILES:=test.c //源文件编译该模块需要的源文件 LOCAL_MODULE_PATH:=$(LOCAL_PATH)//把目标文件生成在当前目录下 include$(BUILD_EXECUTABLE)//生成目标格式 编译所生成的目标文件格式 使用连接符来编译多个文件 LOCAL_SRC_FILES:=test.c test2.c my-dir的定义 build/core/definitions.mk definemy-dir $(strip\ $(evalLOCAL_MODULE_MAKEFILE:=$$(lastword$$(MAKEFILE_LIST)))\ $

  • 后缀数组模板题总结

    出大问题,我原来学了几天,然后刷了七八题吧,之后将近半年一次没用到,今天回头一想后缀数组?????????? 所以来总结一下,省的模板题都不会了。 rk[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP 1 POJ-3261:找出出现k次的可重叠的最长子串的长度。https://vjudge.net/contest/283743#problem/D 思路:直接二分次数就行了,在判断里面看是连续>=k的最多有几个。 #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<cstdlib> #definelllonglong #definerep(i,a,b)for(inti=a;i<=b;i++) #definepbpush_back #definempmake_pair

  • 信号的概念

    信号的概念 信号(signal)--     进程之间通讯的方式,是一种软件中断。一个进程一旦接收到信号就会打断原来的程序执行流程来处理信号。 几个常用信号: SIGINT     终止进程  中断进程  (control+c) SIGTERM   终止进程     软件终止信号 SIGKILL   终止进程     杀死进程 SIGALRM 闹钟信号   进程结束信号 SIGTERM和SIGKILL的区别 SIGTERM比较友好,进程能捕捉这个信号,根据您的需要来关闭程序。在关闭程序之前,您可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。 对于SIGKILL信号,进程是不能忽略的

  • 添加网页内容

    博客园不知道在哪里可以添加自己写好的网站,哇有点心酸

  • redis 基础、持久化、主从、哨兵

    mkdir-p/data/redis_cluster/redis_6379 mkdir-p/opt/redis_cluster/redis_6379/{conf,pid,logs} cd/data/soft/ wgethttp://download.redis.io/releases/redis_3.2.9.tar.gz tarzxfredis-3.2.9.tar.gz-C/opt/redis_cluster/ ln-s/opt/redis_cluster/redis-3.2.9/opt/redis_cluster/redis cd/opt/redis_cluster/redis make&&makeinstall   ################ daemonizeyes  #以守护模式启动 bind10.0.0.51 port6379 pidfile/opt/redis_cluster/redis_6379/pid/redis_6379.pid logfile/opt/redis_cluster/redis_6379/logs/re

相关推荐

推荐阅读