vue源码学习笔记

Vue的本质

Vue的本质就是用一个Function实现的Class,然后在它的原型prototype本身上面扩展一些属性和方法。

它的定义是在src/core/instance/index.js里面定义

使用ES5的方式,即用函数来实现一个class,不用ES6来实现class的原因:在ES5中,是可以往Vue的原型上挂很多方法,并且可以将不同的原型方法拆分到不同的文件下,这样方便代码的管理,不用再单个文件上把Vue的原型方法都定义一遍

Vue中的全局方法定义在src/core/global-api里面:定义Vue的全局配置

 

一:数据驱动

Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护

可以采用简洁的模板语法来声明式的将数据渲染为 DOM

数据驱动的两个核心思想:模板和数据是如何渲染成最终的DOM;数据更新驱动视图变化

问题:vue中模板和数据如何渲染成最终的DOM????

 

2、new Vue()发生了什么?

1)new关键字实例化一个对象,Vue()是一个类,在js中类用Function定义

2)在Vue()函数中调用初始化函数:Vue 初始化主要就干了几件事情,合并配置初始化生命周期初始化事件中心初始化渲染初始化datapropscomputedwatcher 等等。Vue 的初始化逻辑写的非常清楚,把不同的功能逻辑拆成一些单独的函数执行,让主线逻辑一目了然

3)初始化的最后,检测到如果有 el 属性,则调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM

 

分析 Vue 的挂载过程

 

 

在vue的项目中去调试vue源码,如下图所示:

import Vue from 'vue'

var vue = new Vue({

el: "#app",data() { return { message: 'hello' } }})


vue的定义是在node_modules下面定义的,在vue文件夹下面的package.json下面定义了如下内容:

"main": "dist/vue.runtime.esm.js","module": "dist/vue.runtime.esm.js"

如果项目用vue-cli构建的话,那么vue的引进其实是在build/webpack.base.conf.js中进行配置的

  resolve: {
    extensions: ['.js','.vue','.json'],alias: {
      'vue$': 'vue/distue.esm.js',//完整的写法:node_modules/vue/dist/vue.esm.js
      '@': resolve('src'),'common':resolve('src/common'),'components':resolve('src/components')
    }
  }

即:

import Vue from 'vue' 就相当于import Vue from 'node_modules/vue/dist/vue.esm.js'

那调用initMixin()方法时,就是调用这个路径下的文件里面定义的方法,可以在里面添加debugger,然后就可以调试vue中的源码了

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    debugger
    var vm = this;
    // a uid
    vm._uid = uid$3++;

    var startTag,endTag;
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = "vue-perf-start:" + (vm._uid);
      endTag = "vue-perf-end:" + (vm._uid);
      mark(startTag);
    }

    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow,and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm,options);
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),options || {},vm
      );
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm);
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    vm._self = vm;
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm,'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm,'created');

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm,false);
      mark(endTag);
      measure(("vue " + (vm._name) + " init"),startTag,endTag);
    }
    //如果有设置Vue()实例,那么就挂载到该el对象上
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };
}

 

在data()属性里面定义了变量之后,就可以通过this.变量名访问到在data()里面定义的变量,这是为什么呢?

 

通过在初始化函数中调用initState(vm),然后调用初始化data的函数initData(),在里面将数据赋给vm_.data,然后通过proxy()将vm._data.key替换成vm.key,在proxy()中对vm._data.key设置setter  getter,然后通过Object.defineProperty(target,key,sharedPropertyDefinition)来实现代理的

import Vue from 'vue' 

var vue = new Vue({

   el: "#app",mounted: {
      console.log(this.message) //相当于this._data_message,通过proxy做一层代理,主要应用Object.defineProperty()实现一个代理
   },data() {
     return {
        message: 'hello'
     }
   }
})

在init.js文件中初始化函数中有一个initState(vm),而这个函数是定义在state.js中

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm,opts.props)
  if (opts.methods) initMethods(vm,opts.methods)
  //如果定义了data,就初始化data
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {},true /* asRootData */)
  }
  if (opts.computed) initComputed(vm,opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm,opts.watch)
  }
}

initData()函数定义如下:

function initData (vm: Component) {
  let data = vm.$options.data  //在Vue()中的data()中定义的对象
  data = vm._data = typeof data === 'function'  // data = vm._data
    ? getData(data,vm)      //从vm中拿到data  getData(data,vm)
    : data || {}
  //如果data不是一个函数,那么就在浏览器报一个警告  
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      //methods和data中不能有同名的属性变量,有的话就报警告
      if (methods && hasOwn(methods,key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,vm
        )
      }
    }
    //props和data中不能有同名的属性变量,有的话就报警告
    if (props && hasOwn(props,key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,vm
      )
    } else if (!isReserved(key)) {
      //如果没有同名的话,那就通过proxy()函数进行一个代理
      //vm实例对象,在_data对象上的key添加getter  setter
      proxy(vm,`_data`,key)
    }
  }
  // observe data  初始化的时候对data做了一个响应式的处理
  observe(data,true /* asRootData */)
}


3、Vue实例挂载的实现,也就是执行vm.$mount()做了哪些事情

用的是runtime+compiler的版本,所以入口在entre-runtime-with-compiler.js

 

Vue中是通过$mount实例方法挂载vm

$mount方法定义在Vue的原型上:Vue.prototype.$mount

 

1)首先缓存了原型上的 $mount 方法

2)重新定义该方法

a)  它对 el 做了限制,Vue 不能挂载在 body、html 这样的根节点上

b)  很关键的逻辑 —— 如果没有定义 render 方法,则会把 el 或者 template 字符串转换成 render 方法。这里我们要牢记,在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法,无论我们是用单文件 .vue 方式开发组件,还是写了 el 或者 template 属性,最终都会转换成 render 方法,那么这个过程是 Vue 的一个“在线编译”的过程,它是调用 compileToFunctions 方法实现的

3)  最后,调用原先原型上的 $mount 方法挂载

原先原型上的 $mount 方法在 src/platform/web/runtime/index.js 中定义,之所以这么设计完全是为了复用,因为它是可以被 runtime only 版本的 Vue 直接使用的

原先原型上的方法$mount会调用mountComponent(),定义在src/core/instance/lifecycle.js

mountComponent ()核心

a)  先调用 vm._render 方法生成VNode

b)  再实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,最终调用 vm._update 更新 DOM

c)  最后判断为根节点时,设置 vm._isMounted true(表示这个实例已经挂载了,同时执行 mounted 钩子函数。 这里注意 vm.$vnode 表示 Vue 实例的父虚拟 Node,所以它为 Null 则表示当前是根 Vue 的实例

Watcher的作用

a)  初始化的时候执行回调函数

b)  当 vm 实例中,监测的数据发生变化的时候执行回调函数

 

mountComponent()方法中的vm._reneder()---创建VNode和vm._update()---将VNode挂载到DOM上

(1)vm._render():定义在Vue.prototype._render上---把实例渲染成一个虚拟 Node

vm._render() 最终是通过执行 createElement 方法,然后返回 vnode

Virtual  DOM:用VNode类描述

用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中,Virtual DOM 是用 VNode 这么一个 Class 去描述,它是定义在 src/core/vdom/vnode.js 中的

 VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的

 

Virtual DOM映射到真实的DOM上要经历过VNodecreate    diff   patch等过程,Vnode的create是通过createElement()方法创建的

 

createElement()创建Vnode

定义在'src/core/vdom/create-elemenet.js'

createElement 方法实际上是对 _createElement 方法的封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode 的函数 _createElement

 

createElement 函数的流程略微有点多—— 这里主要介绍children 的规范化以及 VNode 的创建

children 的规范化:children 变成了一个类型为 VNode 的 Array

由于 Virtual DOM 实际上是一个树状结构,每一个 VNode 可能会有若干个子节点,这些子节点应该也是 VNode 的类型。_createElement 接收的第 4 个参数 children 任意类型的,因此我们需要把它们规范成 VNode 类型

根据normalizationType 的不同,调用了 normalizeChildren(children) 和 simpleNormalizeChildren(children) 方法,

这两个方法在src/core/vdom/helpers/normalzie-children.js定义

1)simpleNormalizeChildren :调用场景render 函数是编译生成的。理论上编译生成的 children 都已经是 VNode 类型的,但这里有一个例外,就是 functional component 函数式组件返回的是一个数组而不是一个根节点,所以会通过 Array.prototype.concat 方法把整个 children 数组打平,让它的深度只有一层

2)normalizeChildren :调用场景2 种

一个场景是 render 函数是用户手写的,当 children 只有一个节点的时候,Vue.js 从接口层面允许用户把 children 写成基础类型用来创建单个简单的文本节点,这种情况会调用 createTextVNode 创建一个文本节点的 VNode

另一个场景是当编译 slot、v-for 的时候会产生嵌套数组的情况,会调用 normalizeArrayChildren 方法

 

 

normalizeArrayChildren 接收 2 个参数,children 表示要规范的子节点nestedIndex 表示嵌套的索引,因为单个 child 可能是一个数组类型

 normalizeArrayChildren 主要的逻辑就是遍历 children,获得单个节点 c,然后对 c 的类型判断:

1)如果是一个数组类型,则递归调用 normalizeArrayChildren

2)如果是基础类型,则通过 createTextVNode 方法转换成 VNode 类型否则就已经是 VNode 类型

3)如果 children 是一个列表并且列表还存在嵌套的情况,则根据 nestedIndex 去更新它的 key。这里需要注意一点,在遍历的过程中,对这 3 种情况都做了如下处理:如果存在两个连续的 text节点,会把它们合并成一个 text 节点。

 

createElement函数中,规范化children之后,就会创建一个VNode的实例

 

这里先对 tag 做判断

1)如果是 string 类型,则接着判断

a)如果是内置的一些节点,则直接创建一个普通 VNode

b)如果是为已注册的组件名,则通过 createComponent 创建一个组件类型的 VNode,否则创建一个未知的标签的 VNode2)如果是 tag 一个 Component 类型,则直接调用 createComponent 创建一个组件类型的 VNode 节点

 

(2)vm._update():调用的时机有两个----首次渲染和数据更新(响应式原理)

作用把VNode渲染成真实的DOM,定义在---src/core/instance/lifecycle.js

_update 的核心:调用 vm.__patch__ 方法,这个方法实际上在不同的平台,比如 web 和 weex 上的定义是不一样的,因此在 web 平台中它的定义在 src/platforms/web/runtime/index.js

 

Vue.prototype.__patch__ = inBrowser ? patch : noop

可以看到,甚至在 web 平台上,是否是服务端渲染也会对这个方法产生影响。因为在服务端渲染中,没有真实的浏览器 DOM 环境,所以不需要把 VNode 最终转换成 DOM,因此是一个空函数,而在浏览器端渲染中,它指向了 patch 方法,它的定义在 src/platforms/web/runtime/patch.js中

 

createPatchFunction 内部定义了一系列的辅助方法,最终返回了一个 patch 方法,这个方法就赋值给了 vm._update 函数里调用的 vm.__patch__

 

patch 是平台相关的,在 Web 和 Weex 环境,它们把虚拟 DOM 映射到 “平台 DOM” 的方法是不同的,并且对 “DOM” 包括的属性模块创建和更新也不尽相同。因此每个平台都有各自的 nodeOps modules,它们的代码需要托管在 src/platforms 这个大目录下

 

不同平台的 patch 主要逻辑部分是相同的,所以这部分公共的部分托管在 core 这个大目录下,差异化部分只需要通过参数来区别,这里用到了一个函数柯里化的技巧,通过 createPatchFunction 差异化参数提前固化,这样不用每次调用 patch 的时候都传递 nodeOps 和 modules 了,这种编程技巧也非常值得学习。

 

nodeOps 表示对 “平台 DOM” 的一些操作方法modules 表示平台的一些模块,它们会在整个 patch 过程的不同阶段执行相应的钩子函数

 

 patch() 方法本身,它接收 4个参数

oldVnode 表示旧的 VNode 节点,它也可以不存在或者是一个 DOM 对象

vnode 表示执行 _render 后返回的 VNode 的节点

hydrating 表示是否是服务端渲染

removeOnly 是给 transition-group 用的

 

vm._update 的方法里是这么调用 patch 方法

vm.$el = vm.__patch__(vm.$el,vnode,hydrating,false /* removeOnly */)

hydrating 非服务端渲染情况下为 falseremoveOnly false

 

由于我们传入的 oldVnode 实际上是一个 DOM container,所以 isRealElement true,接下来又通过 emptyNodeAt 方法把 oldVnode 转换成 VNode 对象,然后再调用 createElm 方法

 

createElm 的作用:通过虚拟节点创建真实的 DOM插入到它的父节点中

createElm 关键逻辑:createComponent 方法目的是尝试创建子组件,在当前这个 case 下它的返回值为 false

接下来判断 vnode 是否包含 tag,如果包含,先简单对 tag 的合法性在非生产环境下做校验,看是否是一个合法标签

然后再去调用平台 DOM 的操作去创建一个占位符元素

 

调用 createChildren 方法去创建子元素,逻辑如下:

遍历子虚拟节点递归调用 createElm,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入

 

接着再调用 invokeCreateHooks 方法执行所有的 create 的钩子,并把 vnode pushinsertedVnodeQueue

 

最后调用 insert 方法把 DOM 插入到父节点中,因为是递归调用子元素会优先调用 insert,所以整个 vnode 树节点的插入顺序是先子后父。它的定义在 src/core/vdom/patch.js 上

insert 逻辑:调用一些 nodeOps 子节点插入到父节点中,这些辅助方法定义在 src/platforms/web/runtime/node-ops.js 中

其实就是调用原生 DOM 的 API 进行 DOM 操作

 

 

createElm 过程中,如果 vnode 节点不包含 tag,则它有可能是一个注释或者纯文本节点,可以直接插入到父元素

再回到 patch 方法,首次渲染我们调用了 createElm 方法,这里传入的 parentElm 是 oldVnode.elm 的父元素, 实际上整个过程就是递归创建了一个完整的 DOM 树并插入到 Body 上
最后
,我们根据之前递归 createElm 生成的 vnode 插入顺序队列,执行相关的 insert 钩子函数

 

 

二:组件

组件化:把页面拆分成多个组件 (component),每个组件依赖的 CSS、JavaScript、模板、图片等资源放在一起开发和维护。组件是资源独立的,组件在系统内部可复用组件和组件之间可以嵌套

 

createElement ()最终会调用 _createElement(),其中有一段逻辑是对参数 tag 的判断,如果是一个普通的 html 标签,像上一章的例子那样是一个普通的 div,则会实例化一个普通 VNode 节点,否则通过 createComponent 方法创建一个组件 VNode

 

createComponent :src/core/vdom/create-component.js

 

在createComponent()中针对组件渲染这个 case 主要就 3 个关键步骤
构造子类构造函数、安装组件钩子函数、实例化 vnode

1、构造子类构造函数

在.vue文件中,export {},即输出是一个对象,所以在createComponent()函数中会执行

baseCtor.extend(Ctor)

 baseCtor 实际上就是 Vue, src/core/global-api/index.js 中的 initGlobalAPI 函数

 

Vue.options._base = Vue

这里是 Vue.option,而我们的 createComponent 取的是 context.$options,实际上在 src/core/instance/init.js 里 Vue 原型上的 _init 函数中有这么一段逻辑

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),vm
)

Vue 上的一些 option 扩展到了 vm.$option 

mergeOptions()的作用:把 Vue 构造函数options 用户传入的 options 做一层合并vm.$options

 

Vue.extend():src/core/global-api/extend.js

作用:就是构造一个 Vue 的子类,使用原型继承的方式把一个纯对象转换一个继承于 Vue 的构造器 Sub 返回

然后对 Sub 这个对象本身扩展一些属性,如扩展 options添加全局 API

配置中的 props computed 做了初始化工作

最后对这个 Sub 构造函数做了缓存,避免多次执行 Vue.extend 的时候对同一个子组件重复构造

 

实例化 Sub 的时候,就会执行 this._init 逻辑再次走到了 Vue 实例的初始化逻辑

 

2、安装组件钩子函数

installComponentHooks(data)

整个installComponentHooks 的过程就是把 componentVNodeHooks 钩子函数合并data.hook

VNode 执行 patch 的过程中执行相关的钩子函数

在合并过程中,如果某个时机的钩子已经存在 data.hook 中,那么通过执行 mergeHook 函数做合并,这个逻辑很简单,就是在最终执行的时候,依次执行这两个钩子函数即可

 

3、实例化VNode

通过 new VNode 实例化一个 vnode 并返回

需要注意的是和普通元素节点的 vnode 不同组件的 vnode 是没有 children

组件patch的过程:createComponent  -> 子组件初始化  ->  子组件render -> 子组件patch

activeInstance为当前激活的vm实例;vm.$vnode为组件的占位vnode;vm._vnode为组件的渲染vnode

嵌套组件的插入顺序是先子后父

通过 createComponent 创建了组件 VNode,接下来会走到 vm._update,执行 vm.__patch__ 去把 VNode 转换成真正的 DOM 节点。组件的 VNode 如何patch

patch 的过程会调用 createElm 创建元素节点,定义在src/core/vdom/patch.js 

 

createComponent() 

首先对 vnode.data 做了一些判断,如果vnode 是一个组件 VNode,那么条件会满足,并且得到 i 就是 init 钩子函数

init() 钩子函数,它是通过 createComponentInstanceForVnode 创建一个 Vue 的实例,然后调用 $mount 方法挂载子组件

 

createComponentInstanceForVnode()造的一个内部组件的参数,然后执行 new vnode.componentOptions.Ctor(options)------是子组件的构造函数,它实际上是继承于 Vue 的一个构造器 Sub,相当于 new Sub(options) 这里有几个关键参数要注意几个点,_isComponenttrue 表示它是一个组件,parent 表示当前激活的组件实例


所以子组件的实例化实际上就是在这个时机执行的,并且它会执行实例的 _init 方法,代码在 src/core/instance/init.js 中

这里首先是合并 options 的过程有变化,_isComponent true,所以走到了 initInternalComponent ()

 

initInternalComponent()这个过程我们重点记住以下几个点即可:opts.parent = options.parentopts._parentVnode = parentVnode,把之前我们通过 createComponentInstanceForVnode 函数传入的几个参数合并到内部的选项 $options 里了

 

三、响应式原理

什么是响应式对象?响应式对象的创建过程?

Vue.js实现响应式的核心是利用ES5的Object.defineProperty(obj,prop,descriptor),给对象的属性添加getter  setter,返回这个对象

响应式对象的定义:如果对象的属性拥有了setter  getter,就可以将该对象成为响应式的对象

在Vue中响应式对象有:props  data   computed  watcher  methods(如果子属性为对象,则递归的把该对象变成响应式的)

生命周期钩子函数中定义的对象是不具有响应式的

 

什么是依赖收集?依赖收集的流程和目的

在访问对象的属性的时候,在get中会完成依赖的收集--收集当前正在计算的watcher作为订阅者

 

 

 

 

原文地址:https://blog.csdn.net/tangxiujiang

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


https://segmentfault.com/a/1190000022018995 https://www.jianshu.com/p/8c3599dda094 vuex教程中,有这样一句话和这样一段代码: 实践中,我们会经常用到 ES2015 的参数解构来简化代码(特别是我们需要调用commi
ES6 (ECMAScript 6)中的模块是一个包含 JavaScript 代码的文件,在这个模块中所有的变量都对其他模块是不可见的,除非我们导出它。 ES6的模块系统大致分为导出(export)和导入(import)两个模块。 1、模块导出(export) 可以 导出 所有的最外层 函数 、 类
from https://mp.weixin.qq.com/s/-rc1lYYlsfx-wR4mQmIIQQ Vue知识点汇总(含Vue3) 一、Vue 基础 1. Vue的基本原理 当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使
D:\Temp>npm init vite@latest vue3study --template vuenpm ERR! code ETIMEDOUTnpm ERR! errno ETIMEDOUTnpm ERR! network request to https://registry.np
文章浏览阅读1.2k次。最近自己从零撸起的甘特图组件需要子组件的滚动条同步滚动这就涉及到子组件之间的互相通信,通过 消息总线可以达到我们的需求 ,首先建立一个标志位,拖动左边滚动条的时候,右边的滚动条事件不处理,反之拖动右边滚动条时,左边的滚动条事件不做处理,建立一个公共的变量用于两者的互斥store.jsimport Vue from 'vue'export let store = Vue.observable({ scrollFlag: true})export let mutations =.._vue 能不能同时有两个滚动事件
文章浏览阅读3.3k次,点赞3次,收藏16次。静默打印是什么?简单来说就是不需要用户点击"打印",自动去打印,但是使用浏览器web打印不可避免的要弹出以下画面面对这种问题也只能用"富客户端"技术来解决,在浏览器的沙盒安全模型中无法做到,那么只能使用插件的技术,这个我们就不自己花力气去做了,我找来了 lodop 这个免费的打印组件,功能还是挺强大的,下载下图的发行包解压后安装下图两个exe如果你的系统是64位的,可以安装install_lodop64.exe上图的LodopFuncs.js 是客户端要使用的核心库文件..._this.$getlodop().then((lodop) =>{
文章浏览阅读1.7k次。个人觉得大屏展示其实很简单,噱头多过技术含量,下面使用了 DataV (不是阿里的那个DataV哈,具体链接在这里)开发了一个大屏展示,使用了css flex弹性布局,使用了DataV的一些比较酷炫的边框(SVG写的),基本上功能没有全部完成,但是模子已经刻出来了,只是后端推送的内容没有全部写出来前端<template> <dv-full-screen-container class="screen-container"> <div class="ti_用signalr做一个简单的实时大屏显示
文章浏览阅读3.4k次,点赞3次,收藏10次。【说明】导入的Excel 字体颜色和背景色只能识别【标准色】,别的如"主题颜色",exceljs 解析出来不是颜色值。导入的样式包括字体,字号,列宽,合并单元格,【部分能识别】的背景色,文字颜色。导入到 x-data-spreadsheet 如下图。原Excel样式如下。_x-data-spreadsheet
文章浏览阅读1.7k次。之前参考某文章把 router-view 放在 el-tab-pane 外面都不起作用,问题根本不是出在 el-tab-pane,而是v-for 里面有多个route-view , keep-alive 时 tab 并未销毁掉,而是缓存隐藏了起来。需要把 router-view 的 name 与路由的 index.js 名称对应起来。之前参照很多文章修改试图修正这个问题,结果都徒劳,终于让我找到。我做了如下修改,主页面 main.vue。_el-tab-pane 后面接router-view
文章浏览阅读533次。今天在一台虚拟机上面运行老项目,报各种类型上图的错误提示,一开始还以为是less的问题,结果一个个装完还是报错,后面又说webpack, webpack cli有问题,头有点大了,google 一下,发现一个命令。讨论这个命令的文章,可以了解一下。运行以后终于出现了期待已久的。_npm install 忽略依赖
文章浏览阅读8k次,点赞3次,收藏12次。从这篇文章得到启发先定义一个组件从外部接收Template,然后在组件里调用<template > <div ref="markedContent"></div></template><script>import Vue from 'vue/dist/vue.esm.js'export default { name: 'wf-marked-content', props: ['content'], mounte.._vue components 动态传入模板
文章浏览阅读5.4k次。参考上一篇知识开发的一个功能,制作一个打印模板的管理模块,如下(就是保存froala编辑后的html文本,其中包括Vue的Template,这样我们可以利用Vue的模板的优势来动态绑定一些数据源进行HTML的打印,基本上跟过去水晶报表做一个模板再绑定数据源的方法异曲同工)在 main.js 里引用 froala 组件// Import and use Vue Froala lib.import VueFroala from 'vue-froala-wysiwyg'// 引入 Fr.._vue设计网页打印模板
文章浏览阅读992次。计划是这样,公司的项目一直在持续改动,安装包总是需要频繁生成新的,由此我想到了"持续集成"!有自动化工具不用,岂不可惜?这周的主要时间就用来学习CruiseControl.Net全面实现持续集成_怎么在vue的 script部分使用 eldigloa
文章浏览阅读1.2k次。其实Element UI 只用了文字提示的 el-tooltip 组件,不喜欢可以去掉,不记得是从哪拿到的原始代码,我给加了高亮渐变显示,图标,和拖拽时只能拖拽图标的位置,效果如上图,可以水平方向拖动,也可以垂直方向拖动。样式是less写的,css写嵌套样式太繁琐了。拿来主义,改造有理!下面贴代码<template> <div ref="splitPane" class="split-pane" :class="direction" :"{ fl..._element ui拉条样式
文章浏览阅读953次,点赞2次,收藏2次。接上一篇,这次加入的是从x-speadsheet导出Excel,并且带有x-speadsheet中的样式,重点关注 exportExcel 这个方法,我加入了 tinycolor 这个库用来翻译颜色值,值得注意的是, exceljs的颜色值是 argb 不是 rgba,一定不要弄混了a 是代表的透明度放在最前面_x-data-spreadsheet 导出
文章浏览阅读5.5k次,点赞2次,收藏21次。尝试了两个连线库 jsplumb 和 leadline ,其实两个库都很强大,但是基于个人使用的习惯,决定还是用 leadline ,在Vue 下我使用它的一个包装库 leader-line-vue 下面是上图的连接线示例代码,连接线很轻松的就实现了一个渐变效果..._vue 连线
文章浏览阅读4.2k次,点赞2次,收藏5次。首先官网推荐的安装方法没有生成dist文件,导致样式表等这些文件并没有生成npm install element-plus --save以上方法是有问题的,如果不幸执行了上面的命令,那么先执行卸载npm uninstall element-plus删除 main.js文件对element ui的引用,输入以下命令vue add element-plus..._elementui3.0
文章浏览阅读3.1k次。如上图,下面贴代码<template> <div> <el-date-picker size="large" style ="width:120px" v-model="selectYear" format="yyyy 年" value-format="yyyy" type="year" :clearable = "false" placeholder="选择年">.._vue多选周
文章浏览阅读1.8k次,点赞6次,收藏6次。经过 2021年的一个春节,从年前到现在,大致撸出一个 甘特图,进度条是用SVG画的,使用了几个工具库 (interactjs 用来处理拖拽和修改尺寸,snap.svg 用来处理 svg 的dom 操作,moment.js用来处理时间的操作),其他没有依赖任何的UI组件,目前初见雏形,还比较粗糙,后面会不断更新源码地址点击期间也摸索了怎么把vs code的项目上传到 GitHub 上面进行源代码的管理,基本上是参考的这篇文章做的..._vue gantt demo
文章浏览阅读2.1k次。接上两篇vue 下使用 exceljs + x-spreadsheet 带样式导入Excelvue 下使用 exceljs + x-spreadsheet 带样式导出Excel下面封装好一个组件调用组件的页面效果如图,目前“导出Json”还没有做_x-spreadsheet导入导出