dexteryao 发表于 2024-5-14 09:14:18

在VUE3中创建可编辑的SpreadJS自定义单元格

本帖最后由 dexteryao 于 2024-5-14 14:25 编辑

对于VUE 2 的用户可以参考使用VUE组件创建SpreadJS自定义单元格(二)来创建可编辑的自定义单元格,在VUE 3中原有的$mount弃用由app.mount代替,这是个比较大的breaking change,同时也有人在问是否可以使用h函数方便的创建自定义单元格。接下来我们就看一看如何在VUE3中创建可编辑的自定义单元格。

首先,采用传统mount方式,将VUE2的Demo升级到VUE3. 示例代码时不太严格的TypeScript,大家根据实际情况可以调整。
第一步,还是封装组件
这里使用了element-plus的ElDatePicker组件,组件中设置teleported为false,保证弹出的日期选择框是渲染在单元格的dom中而不是body里
如果使用的组件没有类似属性,出现了点击组件就退出编辑的问题,那就需要在组件生成后或者弹出后给组件添加gcUIElement

defineExpose 中暴露了方法给外部调用,这也是和VUE2的一个差异,VUE2中所有的属性和方法都可以随便调用。
<script setup lang="ts">
import { ref } from 'vue';
const datePicker = ref<any>(null);
const text = defineModel('text')
const cellStyle = ref(null);

const setFocus = function(){
if(datePicker.value){
    datePicker.value.focus();
}
}

const updateStyle = function(style: any){
cellStyle.value = style;
}

defineExpose({
text,
updateStyle,
setFocus
})

</script>

<template>
      <el-date-picker
      ref="datePicker"
      v-model="text"
      type="date"
      placeholder="选择日期"
      popper-class="spread-date-picker-popper"
      :teleported="false"
      :style="cellStyle"
      />
</template>

<style scoped>

</style>


第二步,实现cellType,通过createApp 以及 实例的mount方法挂载组件
mount 的返回类型是ComponentPublicInstance,通过这个instance可以调用Expose的属性和方法,通过text设置日期、setFocus设置日期选择器自动获取焦点

import * as GC from "@grapecity/spread-sheets"
import DatePickerNew from './DatePickerNew.vue'
import { createApp } from 'vue'
import { ElDatePicker } from 'element-plus'

class datePickerEditorNew extends GC.Spread.Sheets.CellTypes.Base {

private _app: any = null;
private _vm: any = null;

public typeName: string = 'datePickerEditorNew'
public createEditorElement(_context: any): HTMLElement {
    console.log('编辑器创建')
    let editorContext = document.createElement("div")
    editorContext.setAttribute("gcUIElement", "gcEditingInput");
    let editor = document.createElement("div");
    editorContext.appendChild(editor);
return editorContext
}

public activateEditor(editorContext: HTMLElement, _cellStyle: any, _cellRect: any, _context?: any): void {
this._app = createApp(DatePickerNew, {});
this._app.use(ElDatePicker);
this._vm = this._app.mount(editorContext.firstChild as HTMLElement);
}

public updateEditor(editorContext: HTMLElement, _cellStyle: any, cellRect: any, _context?: any): any {
    console.log('编辑器更新')
    let width = cellRect.width > 180 ? cellRect.width : 180;
    this._vm.updateStyle({ width: width + "px" });
    const rect = new GC.Spread.Sheets.Rect(cellRect.x, cellRect.y, cellRect.width, cellRect.height);
    (editorContext as any).parentElement.parentElement.style.overflow = "visible"
    return rect;
}

public getEditorValue(_editorContext: HTMLElement, _context?: any): any {
    return this._vm.text;
}

public setEditorValue(_editorContext: HTMLElement, value: any, _context?: any): void {
    if (this._app) {
      this._vm.text = value;
    }
}

public deactivateEditor(_editorContext: HTMLElement, _context?: any): void {
    console.log('组件销毁')
    this._app.unmount();
    this._app = null;
    this._vm = null;
}

public isEditingValueChanged(oldValue: any, newValue: any, _context?: any): boolean {
    return oldValue !== newValue;
}

public focus(_editorContext: HTMLElement, _context?: any): void {
    this._vm.setFocus();
    console.log("CellType Focus")
}
}

export { datePickerEditorNew }

使用时和vue2没有区别
sheet.getCell(2, 1).value("2024/8/1").cellType(new datePickerEditorNew())效果如下:



以上是完整流程的创建,在SpreadJS单元格各个生命周期方法中也掉用VUE组件instance的属性和方法,总体上有些繁琐。
如果只是简单的封装可以使用h函数快速创建一个VNode进行挂载显示,更加灵活简单。

和上面的方法主要区别是在activateEditor中通过render和h函数在创建的dom上渲染vnode。
vnode的赋值和取值直接在这里操作_text完成,没有全局的app和vm。
销毁时也是通过render渲染null
import * as GC from "@grapecity/spread-sheets"
import { h, ref, render, nextTick, defineComponent} from 'vue'

import { ElDatePicker } from 'element-plus'

class datePickerEditorRender extends GC.Spread.Sheets.CellTypes.Base {

private _text: any = "";

public typeName: string = 'datePickerEditorRender'
public createEditorElement(_context: any): HTMLElement {
    console.log('编辑器创建')
    let editorContext = document.createElement("div")
    editorContext.setAttribute("gcUIElement", "gcEditingInput");
    let editor = document.createElement("div");
    editorContext.appendChild(editor);
    return editorContext
}

public activateEditor(editorContext: HTMLElement, _cellStyle: any, cellRect: any, context?: any): void {
    if (editorContext) {
      const { row, col, sheet } = context
      const cell = sheet.getCell(row, col)
      const value = cell.value()

      let cellType = this;

      let width = cellRect.width > 180 ? cellRect.width : 180;
      const pickerCom = defineComponent({
      data() {
          return { text: value}
      },
      mounted() {
          nextTick(() => { this.setFocus() });
      },
      methods: {
          setFocus() {
            (this as any).datePicker.value.focus();
          }
      },
      render() {
          (this as any).datePicker = ref<any>(null);
          const vnode = h(ElDatePicker, {
            modelValue: this.text,
            "onUpdate:modelValue": (val: any) => {
            this.text = val;
            cellType._text = val
            },
            teleported: false,
            style: { width: width + "px" },
            ref: (this as any).datePicker
          });
          return vnode;
      },
      expose: ["text", "setFocus"]
      });

      const vNode = h(pickerCom);
      render(vNode, editorContext.firstChild as HTMLElement)
    }
}

public updateEditor(editorContext: HTMLElement, _cellStyle: any, cellRect: any, _context?: any): any {
    console.log('编辑器更新')
    const rect = new GC.Spread.Sheets.Rect(cellRect.x, cellRect.y, cellRect.width, cellRect.height);
    (editorContext as any).parentElement.parentElement.style.overflow = "visible"
    return rect;
}

public getEditorValue(_editorContext: HTMLElement, _context?: any): any {
    return this._text;
}

public setEditorValue(_editorContext: HTMLElement, value: any, _context?: any): void {
    this._text = value
}

public deactivateEditor(editorContext: HTMLElement, _context?: any): void {
    console.log('组件销毁')
    render(null, editorContext.firstChild as HTMLElement)
}

public isEditingValueChanged(oldValue: any, newValue: any, _context?: any): boolean {
    return oldValue !== newValue;
}

}

export { datePickerEditorRender }
使用CellType代码相同

    sheet.getCell(4, 1).value("2024/8/1").cellType(new datePickerEditorRender())使用render和h函数注入组件,不需要再写额外的组件,也不需要create新的vue实例。
但是由于vNode不返回组件实例,无法在celltype其他方法中调用组件内部方法,组件的focus只能自己延时完成。

以上便是在VUE3中创建可编辑自定义单元格的示例,有问题大家可以跟帖讨论。




页: [1]
查看完整版本: 在VUE3中创建可编辑的SpreadJS自定义单元格