在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]