V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
GrapeCityChina
V2EX  ›  推广

使用 VUE 组件创建 SpreadJS 自定义单元格(一)

  •  
  •   GrapeCityChina · 2022-01-07 11:32:14 +08:00 · 530 次点击
    这是一个创建于 861 天前的主题,其中的信息可能已经有所发展或是发生改变。

    作为近五年都冲在热门框架排行榜首的 Vue ,大家一定会学到的一部分就是组件的使用。前端开发的模块化,可以让代码逻辑更加简单清晰,项目的扩展性大大加强。对于 Vue 而言,模块化的体现集中在组件之上,以组件为单位实现模块化。

    通常我们使用组件的方式是,在实例化 Vue 对象之前,通过 Vue.component 方法来注册全局的组件。

    // 告诉 Vue ,现在需要组件 todo-item ,配置如下,包含 props 和 template
    Vue.component('todo-item', {
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    }) 
    // 实例化一个 Vue 对象,挂载在#app-7 元素下,定它的属性,数组 groceryList 
    var app7 = new Vue({
      el: '#app-7',
      data: {
        groceryList: [
          { text: 'Vegetables' },
          { text: 'Cheese' },
          { text: 'Whatever else humans are supposed to eat' }
        ]
      }
    })
    
    

    在众多组件之中,作为办公必备的电子表格,在前端组件中也占据了重要地位。除了以表格的形式展示数据,电子表格还有一个非常重要的功能,即支持自定义功能拓展和各种定制化的数据展示效果,比如 checkbox ,Radio button 等;还需要实现当单元格进入编辑状态时,使用下拉菜单(或其他输入控件)输入的效果。我们称之为"自定义单元格",一种嵌入组件内的组件。SpreadJS 目前拥有 8 种下拉列表,在打开列表之前,我们只需要在单元格样式中设置选项数据。 你可以参考以下代码使用列表:

    在线体验地址

      // The way of click the dropdown icon to open list. 
       var style = new GC.Spread.Sheets.Style();
       style.cellButtons = [
           {
               imageType: GC.Spread.Sheets.ButtonImageType.dropdown,
               command: "openList",
               useButtonStyle: true,
           }
       ];
       style.dropDowns = [
           {
               type: GC.Spread.Sheets.DropDownType.list,
               option: {
                   items: [
                       {
                           text: 'item1',
                           value: 'item1'
                       },
                       {
                           text: 'item2',
                           value: 'item2'
                       },
                       {
                           text: 'item3',
                           value: 'item3'
                       },
                       {
                           text: 'item4',
                           value: 'item4'
                       }
                   ],
               }
           }
       ];
       sheet.setText(2, 1, "Vertical text list");
       sheet.setStyle(3, 1, style);
    
       // The way open list with command rather then clicking the dropdown button.
       spread.commandManager().execute({cmd:"openList",row:3,col:1,sheetName:"Sheet1"});
    
    
    

    前端电子表格固然好用, 但由于框架生命周期以及自定义单元格渲染逻辑的问题,目前的技术手段无法直接在框架页面下直接通过 template 的方式使用框架下的组件。在之前的内容中,我们提到了可以使用 Svelte 使用 Web Conmponents 封装其他组件可以使用的组件。 除了上面提到的方法之外,我们如果想在 Vue 环境下使用自定义单元格,可以考虑使用持动态渲染的方式来创建和挂载组件,从而将组件注入自定义单元格。

    下面为大家演演示如何在 VUE 项目中,创建一个使用 VUE 组件的自定义单元格。

    实践

    首先,在项目中开启运行时加载,在 vue.config.js 中添加 runtimeCompiler: true 。

        module.exports = {
            devServer: {
                port: 3000
            },
            <font color="#ff0000">runtimeCompiler: true</font>
          }
    
    
    

    引用 ElementUI ,需要注意要把 element 的 css 引用放在 APP import 前,这样修改样式,才能覆盖原有项目内容。

    import Vue from 'vue'
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    import App from './App.vue'
    import router from './router'
    
    Vue.use(ElementUI);
    
    new Vue({
      el: '#app',
      router,
      render: h => h(App)
    })
    
    Vue.config.productionTip = false
    
    
    

    创建 AutoComplateCellType ,具体代码如下,需要注意几点。 1 、自定义的元素,需要添加 gcUIElement 属性,如果元素或者其父元素没有该属性,点击创建的组件便会直接退出编辑状态无法编辑。 对于 ElementUI 的 autocomplete ,默认下拉选项内容是注入到 body 中的,需要给组件模板中设置:popper-append-to-body="false",让弹出的下拉选项在 gcUIElement 的 Div 中渲染。 如果使用其他组件没有类似选项,也可以跟进实际情况在弹出时在添加 gcUIElement 属性。 2 、使用动态挂载组件的 this.vm 设置和获取单元格的值。 3 、在 deactivateEditor 中销毁组件。

    import Vue from 'vue'
    import * as GC from "@grapecity/spread-sheets"
    import DataService from './dataService'
    
    function AutoComplateCellType() {
    }
    AutoComplateCellType.prototype = new GC.Spread.Sheets.CellTypes.Base();
    AutoComplateCellType.prototype.createEditorElement = function (context, cellWrapperElement) {
      cellWrapperElement.style.overflow = 'visible'
      let editorContext = document.createElement("div")
      editorContext.setAttribute("gcUIElement", "gcEditingInput");
      let editor = document.createElement("div");
      // 自定义单元格中 editorContext 作为容器,需要在创建一个 child 用于挂载,不能直接挂载到 editorContext 上
      editorContext.appendChild(editor);
      return editorContext;
    }
    AutoComplateCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
        let width = cellRect.width > 180 ? cellRect.width : 180;
        if (editorContext) {
            
            // 动态创建 VUE 组件并挂载到 editor
            const AutoCompleteComponent = {
                props: ['text','cellStyle'],
                template: `<div>
                            <el-autocomplete
                            :style="cellStyle"
                            popper-class="my-autocomplete"
                            v-model="text"
                            :fetch-suggestions="querySearch"
                            placeholder="请输入内容"
                            :popper-append-to-body="false"
                            value-key="name"
                            @select="handleSelect">
                            <i class="el-icon-edit el-input__icon"
                                slot="suffix"
                                @click="handleIconClick">
                            </i>
                            <template slot-scope="{ item }">
                                <div class="name">{{ item.name }}</div>
                                <span class="addr">{{ item.phone }}</span>
                            </template>
                            </el-autocomplete>
                        </div>`,
                mounted() {
                    this.items = DataService.getEmployeesData();
                },
                methods: {
                    querySearch(queryString, cb) {
                        var items = this.items;
                        var results = queryString ? items.filter(this.createFilter(queryString)) : items;
                        // 无法设置动态内容的位置,可以动态添加 gcUIElement
                        // setTimeout(() => {
                        //   let popDiv = document.getElementsByClassName("my-autocomplete")[0];
                        //   if(popDiv){
                        //     popDiv.setAttribute("gcUIElement", "gcEditingInput");
                        //   }
                        // }, 500);
                        // 调用 callback 返回建议列表的数据
                        cb(results);
                    },
                    createFilter(queryString) {
                        return (restaurant) => {
                        return (restaurant.name.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
                        };
                    },
                    handleSelect(item) {
                        console.log(item);
                    },
                    handleIconClick(ev) {
                        console.log(ev);
                    }
                }
            };
    
          // create component constructor
          const AutoCompleteCtor = Vue.extend(AutoCompleteComponent);
          this.vm = new AutoCompleteCtor({
            propsData: {
              cellStyle: {width: width+"px"}
            }
          }).$mount(editorContext.firstChild);
        }
        return editorContext;
    };
    AutoComplateCellType.prototype.updateEditor = function(editorContext, cellStyle, cellRect) {
        // 给定一个最小编辑区域大小
        let width = cellRect.width > 180 ? cellRect.width : 180;
        let height = cellRect.height > 40 ? cellRect.height : 40;
        return {width: width, height: height};
    };
    AutoComplateCellType.prototype.getEditorValue = function (editorContext) {
        // 设置组件默认值
        if (this.vm) {
            return this.vm.text;
        }
    };
    AutoComplateCellType.prototype.setEditorValue = function (editorContext, value) {
        // 获取组件编辑后的值
        if (editorContext) {
          this.vm.text = value;
        }
    };
    AutoComplateCellType.prototype.deactivateEditor = function (editorContext, context) {
        // 销毁组件
        this.vm.$destroy();
        this.vm = undefined;
    };
    
    export {AutoComplateCellType};
    
    
    

    效果如图:

    一个完美的单元格新鲜出炉~

    这里介绍的方式只是诸多实现方案的一种。如果大家有其他更好的想法方法,欢迎一起讨论 ~

    如果你对其他更多前端电子表格中有趣功能感兴趣,可以查看 SpreadJS 更多实例演示

    我们也会在之后,持续为大家带来更多带来更多严肃和有趣的内容 ~

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   894 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 20:59 · PVG 04:59 · LAX 13:59 · JFK 16:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.