google 工作表 api:将工作表从一个电子表格复制到另一个电子表格时出现 CORS 问题

google sheets api : CORS Issue when copying sheets from one to another spreadsheet

我遇到了一个我无法理解也无法解决的令人恼火的问题。 我正在使用 google sheet api 来检索(按顺序) 1. 从价差中检索日期sheet 2. 更新此点差sheet 3. 使用此数据创建新的点差sheet 4. 使用 batchupdate 修改这个新创建的 spreadsheet 5. 将另一个 spreadsheet 中的 sheet 复制到这个新创建的 spreadsheet 中。

一切正常,但我需要在 (4) 之前进行 (5),以便我可以同时修改添加的 sheet。

但是,当我这样做时,我收到 "No 'Access-Control-Allow-Origin' header is present on the requested resource" 错误。

第一:这是为什么呢?如果 4 在 5 之前

则不会造成问题

第二:如何使用此 'gapi' 库提供的 google api 调用来利用 CORS? 我应该切换到一些常规 fetching/XMLHttp 吗? (我做不到 :s。)

这是我的代码,请有人查看并给我一些建议。


    const createSpreadsheet = (type) => {

        type.toLowerCase()

        setDialogLoader({ open: true })

        gapi.client.load('drive', 'v3')
            .then(() => {

                // 1.   getting current doc number

                let req = gapi.client.sheets.spreadsheets.values.get({
                    spreadsheetId: config.configSpreadsheet.id,
                    range: config.configSpreadsheet.range[type],
                    valueRenderOption: 'FORMATTED_VALUE'
                })
                return req
            })
            .then(resp => { 

                // 2.    updating doc number

                console.log(resp)
                let number = parseInt(resp.result.values[0])
                let req = gapi.client.sheets.spreadsheets.values.update({
                    spreadsheetId: config.configSpreadsheet.id,
                    range: config.configSpreadsheet.range[type],
                    valueInputOption: 'RAW',
                    resource: {
                        range: config.configSpreadsheet.range[type],
                        values: [[number + 1]],
                    },
                    includeValuesInResponse: true
                })
                return req
            })
            .then(resp => { 

                // 3.    creating the spreadsheet

                console.log(resp)
                let number = parseInt(resp.result.updatedData.values[0])
                let req = gapi.client.drive.files.create({
                    'mimeType': 'application/vnd.google-apps.spreadsheet',
                    'parents': [`${config.folderId[type]}`],
                    "name": type + '-' + number + '/' + selectedClient.nom,
                    "properties": {
                        type: type,
                        description: 'This is a resume of the tasks to do...',
                        date: Date.now(),
                        clientId: selectedClient.id,
                        number: number,
                    },
                    "fields": 'name, properties, id, webViewLink'
                })
                return req
            })
            .then(resp => { 

                // 4.   batchUpdate :modifying the sheet

                console.log(resp)
                if (type === 'devis') {
                    let newDevis = resp.result
                    setDevis([newDevis, ...devis])
                } else if (type === 'facture') {
                    let newFacture = resp.result
                    setFactures([newFacture, ...factures])
                }
                let params = {
                    spreadsheetId: resp.result.id
                }
                let batchUpdateValuesRequestBody = {
                    requests: requestBody
                };
                let req = gapi.client.sheets.spreadsheets.batchUpdate(params, batchUpdateValuesRequestBody)
                return req
            })
            .then(resp => { 

                // 5.   copying sheet from another SS ( CORS ISSUE IF before step 4 ??? )

                console.log(resp)
                var params = {
                    // The ID of the spreadsheet containing the sheet to copy.
                    spreadsheetId: '1_2Atry0sZ9MJ4VRMDRPC8cVIDWfOnC_k66HYKXfdfS0',
                    // The ID of the sheet to copy.
                    sheetId: 0,
                }
                var copySheetToAnotherSpreadsheetRequestBody = {
                    // The ID of the spreadsheet to copy the sheet to.
                    destinationSpreadsheetId: resp.result.spreadsheetId,
                    // TODO: Add desired properties to the request body.
                };
                var request = gapi.client.sheets.spreadsheets.sheets.copyTo(params, copySheetToAnotherSpreadsheetRequestBody);
                return request
            })
            .then((resp) => setDialogLoader({ open: false }))
            .then((resp) => setSnackbarObject({
                open: true,
                message: type === 'devis' ?
                    `Un devis vient d'être créé dans votre dossier 'Devis` :
                    `Une facture vient d'être créée dans votre dossier 'Factures`
            }))
            .then((resp) => setDocChooserObj({ open: false }))
            .catch(reason => {
                console.log(reason)
            })
    }

现在是 5 在 4 之前的代码。


    const createSpreadsheet = (type) => {

        type.toLowerCase()

        setDialogLoader({ open: true })

        gapi.client.load('drive', 'v3')
            .then(() => {

                // 1.   getting current doc number

                let req = gapi.client.sheets.spreadsheets.values.get({
                    spreadsheetId: config.configSpreadsheet.id,
                    range: config.configSpreadsheet.range[type],
                    valueRenderOption: 'FORMATTED_VALUE'
                })
                return req
            })
            .then(resp => {

                // 2.    updating doc number

                console.log(resp)
                let number = parseInt(resp.result.values[0])
                let req = gapi.client.sheets.spreadsheets.values.update({
                    spreadsheetId: config.configSpreadsheet.id,
                    range: config.configSpreadsheet.range[type],
                    valueInputOption: 'RAW',
                    resource: {
                        range: config.configSpreadsheet.range[type],
                        values: [[number + 1]],
                    },
                    includeValuesInResponse: true
                })
                return req
            })
            .then(resp => {

                // 3.    creating the spreadsheet

                console.log(resp)
                let number = parseInt(resp.result.updatedData.values[0])
                let req = gapi.client.drive.files.create({
                    'mimeType': 'application/vnd.google-apps.spreadsheet',
                    'parents': [`${config.folderId[type]}`],
                    "name": type + '-' + number + '/' + selectedClient.nom,
                    "properties": {
                        type: type,
                        description: 'This is a resume of the tasks to do...',
                        date: Date.now(),
                        clientId: selectedClient.id,
                        number: number,
                    },
                    "fields": 'name, properties, id, webViewLink'
                })
                return req
            })
            .then(resp => {

                // 5.   copying sheet from another SS ( CORS ISSUE IF before step 4 ??? )

                console.log(resp)
                var params = {
                    // The ID of the spreadsheet containing the sheet to copy.
                    spreadsheetId: '1_2Atry0sZ9MJ4VRMDRPC8cVIDWfOnC_k66HYKXfdfS0',
                    // The ID of the sheet to copy.
                    sheetId: 0,
                }
                var copySheetToAnotherSpreadsheetRequestBody = {
                    // The ID of the spreadsheet to copy the sheet to.
                    destinationSpreadsheetId: resp.result.id,
                    // TODO: Add desired properties to the request body.
                };
                var request = gapi.client.sheets.spreadsheets.sheets.copyTo(params, copySheetToAnotherSpreadsheetRequestBody);
                return request
            })
            .then(resp => {

                // 4.   batchUpdate :modifying the sheet

                console.log(resp)
                if (type === 'devis') {
                    let newDevis = resp.result
                    setDevis([newDevis, ...devis])
                } else if (type === 'facture') {
                    let newFacture = resp.result
                    setFactures([newFacture, ...factures])
                }
                let params = {
                    spreadsheetId: resp.result.spreadsheetId
                }
                let batchUpdateValuesRequestBody = {
                    requests: requestBody
                };
                let req = gapi.client.sheets.spreadsheets.batchUpdate(params, batchUpdateValuesRequestBody)
                return req
            })
            .then((resp) => { console.log(resp); setDialogLoader({ open: false })})
            .then((resp) => setSnackbarObject({
                open: true,
                message: type === 'devis' ?
                    `Un devis vient d'être créé dans votre dossier 'Devis` :
                    `Une facture vient d'être créée dans votre dossier 'Factures`
            }))
            .then((resp) => setDocChooserObj({ open: false }))
            .catch(reason => {
                console.log(reason)
            })
    }

以及我收到的确切错误消息

Access to fetch at 'https://apis.google.com/_/scs/apps-static/_/js/k=oz.gapi.fr.HrYtnuOsJ9o.O/m=client/rt=j/sv=1/d=1/ed=1/am=wQE/rs=AGLTcCOM4asNhhVgOFJKHWvKD0xkG7mu1Q/cb=gapi.loaded_0' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

然后这个...

> index.js:1 获取 https://apis.google.com//scs/apps-static//js/k=oz.gapi.fr.HrYtnuOsJ9o.O/m=client/rt=j/sv=1/d=1/ed=1/am=wQE/rs=AGLTcCOM4asNhhVgOFJKHWvKD0xkG7mu1Q/cb=gapi.loaded_0 net::ERR_FAILED

这里是requestBody (batchUpdate)


let setWidthRequest = {
    "updateDimensionProperties": {
        "range": {
            "dimension": "COLUMNS",
            "startIndex": 1,
            "endIndex": 2
        },
        "properties": {
            "pixelSize": 500 // 575 before new column has been added
        },
        "fields": "pixelSize"
    }
}

let setWidthRequest2 = {
    "updateDimensionProperties": {
        "range": {
            "dimension": "COLUMNS",
            "startIndex": 1,
            "endIndex": 3
        },
        "properties": {
            "pixelSize": 75
        },
        "fields": "pixelSize"
    }
}

//Keep only 3 columns
let deleteColumnsRequest = {
    "deleteDimension": {
        "range": {
            "dimension": "COLUMNS",
            "endIndex": 30,
            "startIndex": 5
        }
    }
}

// add cool formula to each cell
let prodFormulaRequest = {
    "repeatCell": {
        "range": {
            "startColumnIndex": 4,
            "startRowIndex": 0,
            "endColumnIndex": 4,
            "endRowIndex": 1000
        },
        "cell": {
            "userEnteredValue": {
                "formulaValue": "=IF(ISBLANK(C1);;C1*D1)"
            }
        },
        "fields": "*"
    }
}

let freezeRequest = {
    "updateSheetProperties": {
        "properties": {
            "gridProperties": {
                "frozenRowCount": 1
            }
        },
        "fields": "gridProperties.frozenRowCount"
    }
}

let addTitleRequest = {
    "updateCells": {
        "fields": "*",
        "range": {
            "startColumnIndex": 0,
            "startRowIndex": 0,
            "endColumnIndex": 5,
            "endRowIndex": 1
        },
        "rows": [
            {
                "values": [
                    {
                        "userEnteredValue": {
                            "stringValue": "Ref"
                        },
                        "textFormatRuns": [
                            {
                                "format": {
                                    "bold": true
                                }
                            }
                        ],
                        "effectiveFormat": {
                            "backgroundColor": {
                                "blue": 1
                            },
                            "verticalAlignment": "TOP"
                        }
                    },
                    {
                        "userEnteredValue": {
                            "stringValue": "Description"
                        },
                        "textFormatRuns": [
                            {
                                "format": {
                                    "bold": true
                                }
                            }
                        ],
                        "effectiveFormat": {
                            "backgroundColor": {
                                "blue": 1
                            },
                            "verticalAlignment": "TOP"
                        }
                    },
                    {
                        "userEnteredValue": {
                            "stringValue": "Quantité",
                        },
                        "textFormatRuns": [
                            {
                                "format": {
                                    "bold": true
                                }
                            }
                        ],
                        "effectiveFormat": {
                            "horizontalAlignment": "CENTER",
                            "backgroundColor": {
                                "blue": 1
                            },
                            "verticalAlignment": "TOP"
                        }
                    },
                    {
                        "userEnteredValue": {
                            "stringValue": "Prix Unitaire"
                        },
                        "textFormatRuns": [
                            {
                                "format": {
                                    "bold": true
                                }
                            }
                        ],
                        "effectiveFormat": {
                            "horizontalAlignment": "CENTER",
                            "backgroundColor": {
                                "blue": 1
                            },
                            "verticalAlignment": "TOP"
                        }
                    },
                    {
                        "userEnteredValue": {
                            "stringValue": "Montant"
                        },
                        "textFormatRuns": [
                            {
                                "format": {
                                    "bold": true
                                }
                            }
                        ],
                        "effectiveFormat": {
                            "horizontalAlignment": "CENTER",
                            "backgroundColor": {
                                "blue": 1
                            },
                            "verticalAlignment": "TOP"
                        }
                    },

                ]
            }
        ]
    }
}

let protectionRangeRequest1 = {
    "addProtectedRange": {
        "protectedRange": {
            "range": {
                "startRowIndex": 0,
                "endRowIndex": 1,
                "startColumnIndex": 0,
                "endColumnIndex": 10
            },
            "description": "no touch",
            "warningOnly": true
        },

    }
}

let protectionRangeRequest2 = {
    "addProtectedRange": {
        "protectedRange": {
            "range": {
                "startRowIndex": 0,
                "endRowIndex": 1000,
                "startColumnIndex": 3,
                "endColumnIndex": 4
            },
            "description": "no touch",
            "warningOnly": true
        },
    }
}

let numberFormatRequest = {
    "repeatCell": {
        "range": {
            "startRowIndex": 1,
            "endRowIndex": 1000,
            "startColumnIndex": 1,
            "endColumnIndex": 4
        },
        "cell": {
            "userEnteredFormat": {
                "numberFormat": {
                    "type": "NUMBER",
                    "pattern": "####.00"
                }
            }
        },
        "fields": "userEnteredFormat.numberFormat"
    }
}

let dataValidation = {

    "setDataValidation": {
        "range": {
            "sheetId": 0,
            "startRowIndex": 1,
            "endRowIndex": 1000,
            "startColumnIndex": 0,
            "endColumnIndex": 1
        },
        "rule": {
            "condition": {
                "type": "ONE_OF_RANGE",
                "values": [
                    {
                        "userEnteredValue": "=\'Copie de liste\'!A1:A17"
                    }
                ]
            },
            "inputMessage": "Choose some, son of a bitch !",
            "strict": true,
            "showCustomUi": true
        }
    }
}

export let requestBody = [
    deleteColumnsRequest,
    prodFormulaRequest,
    setWidthRequest,
    addTitleRequest,
    freezeRequest,
    protectionRangeRequest1,
    protectionRangeRequest2,
    numberFormatRequest,
    // metaRequest
]
  • 你想知道1、2、3、5、4顺序出错,而1、2、3、4、5顺序没有出错的原因。
    • 1、2、3、4、5的请求如下。
      1. gapi.client.sheets.spreadsheets.values.get
      2. gapi.client.sheets.spreadsheets.values.update
      3. gapi.client.drive.files.create
      4. gapi.client.sheets.spreadsheets.batchUpdate
      5. gapi.client.sheets.spreadsheets.sheets.copyTo
  • 您想通过 JavaScript.
  • 使用 gapi 来实现您的目标
  • 您已经能够使用表格 API 为 Google 电子表格获取和放置值,并且您还可以使用驱动器 API.
  • 创建文件

如果我的理解是正确的,这个答案怎么样?

问题和解决方法:

我认为您出现问题的原因是 gapi.client.sheets.spreadsheets.sheets.copyTo return 没有电子表格 ID。我认为这可能是 Google 方面的规范。在您的脚本中,您使用从每个请求中检索到的电子表格 ID。在这种情况下,当电子表格 ID 不是 returned 时,会发生错误。当顺序为1、2、3、4、5时,“sheets.copyTo”的方法是最后一个请求。这样,就不会发生错误。另一方面,当顺序为1、2、3、5和4时,“sheets.copyTo”的方法是运行之后,“batchUpdate”的方法是运行。在这种情况下,“sheets.copyTo”方法 returns 没有电子表格 ID。由此,发生错误。我认为这会带来你的错误。

在1、2、3、5、4的顺序下,为了避免错误,我想向return建议gapi.client.sheets.spreadsheets.sheets.copyTo的回复和电子表格ID .

从你补充的requestBody,我了解到requestBody的结构是正确的。

修改后的脚本:

当你的脚本修改后,变成如下。

发件人:

.then(resp => {

    // 5.   copying sheet from another SS ( CORS ISSUE IF before step 4 ??? )

    console.log(resp)
    var params = {
        // The ID of the spreadsheet containing the sheet to copy.
        spreadsheetId: '1_2Atry0sZ9MJ4VRMDRPC8cVIDWfOnC_k66HYKXfdfS0',
        // The ID of the sheet to copy.
        sheetId: 0,
    }
    var copySheetToAnotherSpreadsheetRequestBody = {
        // The ID of the spreadsheet to copy the sheet to.
        destinationSpreadsheetId: resp.result.id,
        // TODO: Add desired properties to the request body.
    };
    var request = gapi.client.sheets.spreadsheets.sheets.copyTo(params, copySheetToAnotherSpreadsheetRequestBody);
    return request
})
.then(resp => {

    // 4.   batchUpdate :modifying the sheet

    console.log(resp)
    if (type === 'devis') {
        let newDevis = resp.result
        setDevis([newDevis, ...devis])
    } else if (type === 'facture') {
        let newFacture = resp.result
        setFactures([newFacture, ...factures])
    }
    let params = {
        spreadsheetId: resp.result.spreadsheetId
    }
    let batchUpdateValuesRequestBody = {
        requests: requestBody
    };
    let req = gapi.client.sheets.spreadsheets.batchUpdate(params, batchUpdateValuesRequestBody)
    return req
})

收件人:

.then(resp => {

    // 5.   copying sheet from another SS ( CORS ISSUE IF before step 4 ??? )

    console.log(resp)
    var params = {
        // The ID of the spreadsheet containing the sheet to copy.
        spreadsheetId: '1_2Atry0sZ9MJ4VRMDRPC8cVIDWfOnC_k66HYKXfdfS0',
        // The ID of the sheet to copy.
        sheetId: 0,
    }
    var copySheetToAnotherSpreadsheetRequestBody = {
        // The ID of the spreadsheet to copy the sheet to.
        destinationSpreadsheetId: resp.result.id,
        // TODO: Add desired properties to the request body.
    };
    var request = gapi.client.sheets.spreadsheets.sheets.copyTo(params, copySheetToAnotherSpreadsheetRequestBody);
    return [request, resp.result.id];  // <--- Modified
})
.then(([resp, spreadsheetId]) => {  // <--- Modified

    // 4.   batchUpdate :modifying the sheet

    console.log(resp)
    if (type === 'devis') {
        let newDevis = resp.result
        setDevis([newDevis, ...devis])
    } else if (type === 'facture') {
        let newFacture = resp.result
        setFactures([newFacture, ...factures])
    }
    let params = {
        spreadsheetId: spreadsheetId  // <--- Modified
    }
    let batchUpdateValuesRequestBody = {
        requests: requestBody
    };
    let req = gapi.client.sheets.spreadsheets.batchUpdate(params, batchUpdateValuesRequestBody)
    return req
})

注:

  • 在这个修改中,假设requestBody已经给定了。

参考:

首先, 感谢 Tanaike,他花了一些时间思考我的问题并提出了一个解决方案,我将其选为可接受的解决方案。

当然,问题是电子表格的ID是假的,所以请求指向了一个不存在的电子表格。这就是 CORS 警报出现的原因。

这是我的版本。不确定它是否更好,只是有点不同。

const createSpreadsheet = (type) => {

        type.toLowerCase()

        setDialogLoader({ open: true })

        let load = gapi.client.load('drive', 'v3')

        let getNumber = () => {
            let req = gapi.client.sheets.spreadsheets.values.get({
                spreadsheetId: config.configSpreadsheet.id,
                range: config.configSpreadsheet.range[type],
                valueRenderOption: 'FORMATTED_VALUE'
            })
            return req
        }

        let updateNumber = (resp) => {
            let number = parseInt(resp.result.values[0])
            let req = gapi.client.sheets.spreadsheets.values.update({
                spreadsheetId: config.configSpreadsheet.id,
                range: config.configSpreadsheet.range[type],
                valueInputOption: 'RAW',
                resource: {
                    range: config.configSpreadsheet.range[type],
                    values: [[number + 1]],
                },
                includeValuesInResponse: true
            })
            return req
        }

        let createSpreadsheet = (resp) => {
            let number = parseInt(resp.result.updatedData.values[0])
            let req = gapi.client.drive.files.create({
                'mimeType': 'application/vnd.google-apps.spreadsheet',
                'parents': [`${config.folderId[type]}`],
                "name": type + '-' + number + '/' + selectedClient.nom,
                "properties": {
                    type: type,
                    description: 'This is a resume of the tasks to do...',
                    date: Date.now(),
                    clientId: selectedClient.id,
                    number: number,
                },
                "fields": 'name, properties, id, webViewLink'
            })
            return req
        }

        let copyTo = (resp) => {
            console.log(resp)
            var params = {
                // The ID of the spreadsheet containing the sheet to copy.
                spreadsheetId: '1_2Atry0sZ9MJ4VRMDRPC8cVIDWfOnC_k66HYKXfdfS0',
                // The ID of the sheet to copy.
                sheetId: 0,
            }
            var copySheetToAnotherSpreadsheetRequestBody = {
                // The ID of the spreadsheet to copy the sheet to.
                destinationSpreadsheetId: resp.result.id,
                // TODO: Add desired properties to the request body.
            };
            var request = gapi.client.sheets.spreadsheets.sheets.copyTo(params, copySheetToAnotherSpreadsheetRequestBody);
            return request
        }

        let batchUpdate = (resp) => {
            console.log(resp)

            let spreadsheetId = resp[0]
            let sheetId = resp[1]

            console.log(spreadsheetId)
            console.log(sheetId)

            let params = {
                spreadsheetId: spreadsheetId
            }
            let batchUpdateValuesRequestBody = {
                requests: requestBody(sheetId)
            };
            let req = gapi.client.sheets.spreadsheets.batchUpdate(params, batchUpdateValuesRequestBody)
            return req
        }

        let a = load
            .then(() => getNumber())
            .then(resp => updateNumber(resp))
            .then(resp => createSpreadsheet(resp))
            .then(resp => {
                let type = resp.result.properties.type
                if (type === 'devis') {
                    let newDevis = resp.result
                    setDevis([newDevis, ...devis])
                } else if (type === 'facture') {
                    let newFacture = resp.result
                    setFactures([newFacture, ...factures])
                }
                return resp
            })

        let b = a.then(resp => copyTo(resp))

        return Promise.all([a, b])
            .then(resp => {
                console.log(resp)
                let spreadsheetId = resp[0].result.id
                let sheetId = resp[1].result.sheetId
                return batchUpdate([spreadsheetId, sheetId])
            })
            .then(resp => {
                setDialogLoader({ open: false })
                setSnackbarObject({
                    open: true,
                    message: type === 'devis' ?
                        `Un devis vient d'être créé dans votre dossier 'Devis` :
                        `Une facture vient d'être créée dans votre dossier 'Factures`
                })
                setDocChooserObj({ open: false })
            })
            .then(resp => console.log(resp))
            .catch(err => console.log(err))
    }