本帖最后由 Matthew.Xue 于 2025-4-2 11:38 编辑
背景
在V18版本中,表格(在sheet中的普通表格,后面简称为sheet table)支持了绑定数据源(DataManager)的功能,这是一项非常重要的更新,因为它允许我们在表格中直接加载数据源中的数据,并且支持前端修改后自动同步到后端接口。
熟悉SpreadJS的朋友可能会问,这不就是集算表吗?的确,它的功能和集算表非常相似,但sheet table有一个优势是集算表不可比拟的,就是sheet table兼容Excel,你可以将sheet table导出为xlsx文件,并在本地Excel打开,而集算表的功能导出后会丢失,这是因为集算表属于SpreadJS特有的功能,Excel目前尚未支持。
当然,sheet table在单纯的数据增删改查方面仍然不如集算表那么强大,比如sheet table还不支持对于列的增删改,目前dataManager包含了addColumn、updateColumn、removeColumn的配置,与数据的增删改查相似,你可以直接在集算表中添加一列,并配置这一列的字段名、显示名和数据类型,集算表会自动调用addColumn,通知后端接口去增加一列,而V18版本的sheet table还不支持这样的功能。
但是SpreadJS作为一款前端控件,支持非常灵活的定制化开发,我们完全可以通过二次开发让sheet table做到对于列的增删改查,接下来就带大家看一看如何在sheet table中实现这一点。
前期准备
信息来源
我们想要实现列的增删改查,会用到哪些信息?
- 增删改查的接口肯定要用到,dataManager中可以配置,我们可以从中读取;
- 删和改时,当前被点击的是哪一列?我们可以从设计器的右键菜单中拿到行列信息,进而拿到当前被点击的列字段信息。
操作方式
在添加、删除、更新列的时候,以什么方式实现会比较好,如何与SpreadJS结合?这里有两种方式,其一是在右键菜单添加选项,其二是在设计上上方添加菜单,二者均是可行的。
在本demo中,我选择在右键菜单中添加选项,如下图所示:
点击菜单后,应该有一个弹框让我们操作,我这里采用了前端开发最熟悉的element-ui来实现,以删除为例:
接口准备
尽管这不属于前端开发的范畴,但是为了便于读者理解,还是有必要展示一下后端接口是如何实现的。
接口分为两类,第一类是对数据的增删改查,第二是对列信息的增删改查。
可以看到在删除列时,不仅仅列信息被删除了,我还在数据信息中将每一条数据的对应字段都删除了,新增也是同理。
具体实现
制作模板
由于这篇文章是介绍sheet table绑定数据源的功能,所以我们首先需要一个数据源,我使用设计器制作好之后将模板保存了下来,模板中包含一个名为“学生表”的数据源,并配置好数据和列的增删改查接口:
绑定数据源到sheet table
这里就直接使用了模板对应的sjs文件:
- fetch("./test.sjs").then(r => r.blob()).then(res => {
- spread.open(res, function () {
- init()
- })
- })
- function init() {
- sheet = spread.getActiveSheet()
- let dmTable = spread.dataManager().tables.学生表
- bindSourceToTable(dmTable, sheet)
- }
复制代码 在bindSourceToTable方法中,就可以看到我们V18版本的新特性了,直接将数据源绑定至sheet table中:
- function bindSourceToTable(dmTable, sheet, table) {
- // 可以通过数据源中配置的拉取列信息的接口拿到每一列的信息
- axios.get(dmTable.options.remote.getColumns.url).then(res => {
- app.columnList = res.data.map(v => {
- v.label = v.caption
- v.key = v.field
- return v
- })
- // columnInfo可以根据列信息让表格的列头显示对应的中文名,而非原来的英文字段
- let columnInfo = []
- res.data.forEach((v, i) => {
- columnInfo.push(new GC.Spread.Sheets.Tables.TableColumn(i, v.field, v.caption))
- })
- if (table) {
- sheet.tables.remove(table, GC.Spread.Sheets.Tables.TableRemoveOptions.none)
- }
- table = sheet.tables.add("table1", 0, 0, 1, columnInfo.length)
- table.autoGenerateColumns(false)
- // 这里就是绑定数据源到sheet table的方法,dmTable是一个数据源
- table.bind(columnInfo, undefined, dmTable)
- // 由于目前无法获取sheet table绑定了哪个数据源,所以先记录下来
- table.dataSourceName = dmTable.name
- })
- }
复制代码 此时,我们表格就已经准备好了:
添加右键菜单
这里只展示“插入字段”,删除和更新其实都是同样的方式,读者可直接查看demo中的写法。
- let config = GC.Spread.Sheets.Designer.DefaultConfig
- config.contextMenu.unshift("insertField");
- config.commandMap = {
- "insertField": {
- text: "插入字段",
- commandName: "insertField",
- visibleContext: "ClickViewport && TableClicked", // 这里配置的目的是让“插入字段”只在表格中右键时显示
- }
- }
- // 命令的定义暂时留空,后续补充
- let insertFieldCommand = {}
- let designer = new GC.Spread.Sheets.Designer.Designer("designer-container", config)
- let spread = designer.getWorkbook()
- let commandManager = spread.commandManager()
- commandManager.register("insertField", insertFieldCommand, null, false, false, false, false);
复制代码
点击菜单后的逻辑
前面提到,点击菜单后展示的页面是用element-ui实现的,由于element-ui是用于vue2.0的ui组件,所以不可避免要创建一个vue实例,实例名为app:
- let app = new Vue({
- el: '#vueapp',
- data: {
- showEditDialog: false,
- showDeleteDialog: false,
- requestInfo: null,
- dmTable: null,
- sheetTable: null,
- editType: "",
- form: {},
- columnList: []
- }
- });
复制代码
app实例中会存储数据源、当前的操作对应的接口(比如addColumn的url、请求方式)、sheet table实例等等信息,这些信息会在菜单被点击时被赋值。以点击插入字段为例,点击后实际上执行的是insertFieldCommand,前面的代码中对该命令留空了,这里咱们可以对其进行补全:
- let insertFieldCommand = {
- canUndo: false,
- execute: function (_spread, options) {
- let result = testField(_spread, options)
- if (result.type == "error") {
- app.$message.error(result.msg)
- return
- }
- let { dataSourceTable, _table } = result
- let remote = dataSourceTable.options.remote
- if (!remote.addColumn.url) {
- app.$message.error(`数据源${_table.dataSourceName}未设置添加列的接口,请设置后重试`)
- return
- }
- app.editType = "insert"
- app.form = {}
- app.requestInfo = remote.addColumn
- app.dmTable = dataSourceTable
- app.sheetTable = _table
- app.showEditDialog = true
- }
- };
复制代码 其中testField是一个校验方法,用于检查用户的操作使用合法,以及当前是否存在数据源等信息。
- function testField(_spread, options) {
- let selections = options.selections
- if (selections.length > 1) {
- return {
- type: "error",
- msg: "不支持多区域选择"
- }
- }
- if (selections[0].colCount > 1) {
- return {
- type: "error",
- msg: "不支持多列选择"
- }
- }
- let _sheet = _spread.getSheetFromName(options.sheetName)
- let _table = _sheet.tables.findByName(options.tableName)
- if (!_table.dataSourceName) {
- return {
- type: "error",
- msg: "此表格未绑定数据源,请绑定数据源后再试"
- }
- }
- let dataSourceTable = _spread.dataManager().tables[_table.dataSourceName]
- if (!dataSourceTable) {
- return {
- type: "error",
- msg: `数据源${_table.dataSourceName}不存在,请添加后重试`
- }
- }
- return {
- type: "normmal",
- dataSourceTable,
- _table
- }
- }
复制代码 校验都通过后,就可以展示插入字段的弹框了:
点击确定后的逻辑
当用户填写完成点击确定后,我们就可以调用后端接口了,由于之前已经在vue的实例app中存储了相关的信息,这里就可以直接调用了:
- methods: {
- onEditSubmit() {
- this.showEditDialog = false
- // 调用插入的接口
- axios(this.requestInfo.url, {
- data: this.form, // 这里是用户填写的信息
- method: this.requestInfo.method
- }).then(res => {
- // 接口返回成功后,再重新拉取一次columnInfo,并重新进行一次绑定
- this.dmTable.fetch(true).then(_columnList => {
- this.columnList = _columnList
- bindSourceToTable(this.dmTable, spread.getActiveSheet(), this.sheetTable)
- })
- })
- }
- }
复制代码 到这里,一切就做完了,html部分以及一些小细节没有讲到,读者可以下载demo后自己尝试。
|
|