找回密码
 立即注册

QQ登录

只需一步,快速开始

dexteryao 讲师达人认证 悬赏达人认证 SpreadJS 开发认证
超级版主   /  发表于:2024-5-14 09:14  /   查看:1391  /  回复:0
本帖最后由 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中所有的属性和方法都可以随便调用。
  1. <script setup lang="ts">
  2. import { ref } from 'vue';
  3. const datePicker = ref<any>(null);
  4. const text = defineModel('text')
  5. const cellStyle = ref(null);

  6. const setFocus = function(){
  7.   if(datePicker.value){
  8.     datePicker.value.focus();
  9.   }
  10. }

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

  14. defineExpose({
  15.   text,
  16.   updateStyle,
  17.   setFocus
  18. })

  19. </script>

  20. <template>
  21.       <el-date-picker
  22.         ref="datePicker"
  23.         v-model="text"
  24.         type="date"
  25.         placeholder="选择日期"
  26.         popper-class="spread-date-picker-popper"
  27.         :teleported="false"
  28.         :style="cellStyle"
  29.       />
  30. </template>

  31. <style scoped>

  32. </style>
复制代码


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

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

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

  6.   private _app: any = null;
  7.   private _vm: any = null;

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

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

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

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

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

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

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

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

  52. export { datePickerEditorNew }
复制代码


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


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

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

  3. import { ElDatePicker } from 'element-plus'

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

  5.   private _text: any = "";

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

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

  20.       let cellType = this;

  21.       let width = cellRect.width > 180 ? cellRect.width : 180;
  22.       const pickerCom = defineComponent({
  23.         data() {
  24.           return { text: value}
  25.         },
  26.         mounted() {
  27.           nextTick(() => { this.setFocus() });
  28.         },
  29.         methods: {
  30.           setFocus() {
  31.             (this as any).datePicker.value.focus();
  32.           }
  33.         },
  34.         render() {
  35.           (this as any).datePicker = ref<any>(null);
  36.           const vnode = h(ElDatePicker, {
  37.             modelValue: this.text,
  38.             "onUpdate:modelValue": (val: any) => {
  39.               this.text = val;
  40.               cellType._text = val
  41.             },
  42.             teleported: false,
  43.             style: { width: width + "px" },
  44.             ref: (this as any).datePicker
  45.           });
  46.           return vnode;
  47.         },
  48.         expose: ["text", "setFocus"]
  49.       });

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

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

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

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

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

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

  73. }

  74. export { datePickerEditorRender }
复制代码

使用CellType代码相同

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

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




0 个回复

您需要登录后才可以回帖 登录 | 立即注册
返回顶部