google drive使用教程
api官方文档
developers.google.cn/workspace/d…
需要配置SCOPES, CLIENT_ID, API_KEY, Project_Number
SCOPES
drive官方权限文档
developers.google.cn/workspace/d…
调用picker的所需的权限配置
console.cloud.google.com/auth/scopes
增加权限下面两个权限
www.googleapis.com/auth/drive.…
www.googleapis.com/auth/drive.…
apikey
console.cloud.google.com/apis/creden…
配置apikey需要权限
Google Photos Picker API Google Picker API Google Drive API (这个是选择文件后使用api)
在API和服务-->库 中搜索选择对应api启用
注:由于这个api key放在前端使用尽量粒度最小 CLIENT_ID console.cloud.google.com/apis/creden…
oauth2的clientid
需要设置授权域名如:www.example.com
本地域名只能使用localhost,记得加进去
AppId
使用 Cloud 项目编号设置云端硬盘应用 ID
console.cloud.google.com/home/dashbo…里的Project number (项目编号)
纯数字来的,不是project ID(项目 ID)
注:此文档仅限web自身浏览器交互不涉及后端调用drive接口
示例代码:
<template>
<div class="pannel-wrap">
<div class="upload-box w-full" @click="handleUpload">
<drag-fullscreen :limit="limit" :before="false" @uploadFile="onFileInputChange" />
<input
id="hide-input"
ref="uploadInput"
type="file"
:multiple="limit > 1"
:accept="accept"
@change="onFileInputChange"
/>
<div class="upload-content h-full column-center">
<!-- <div class="button" /> -->
<i-icon class="upload-icon" icon="upload" />
<p class="desc">{{ $t(`components.pannel.${$store.state.isH5 ? 'descH5' : 'descPc'}`) }}</p>
<div class="or row-center">{{ $t('components.pannel.or') }}</div>
<div class="google-drive" @click.stop="handleGoogleDriveUpload">
<i-icon class="icon" icon="google-drive" />
<p class="text">{{ $t('components.pannel.desc2') }}</p>
</div>
<p class="tip">{{ $t('components.pannel.tip') }}</p>
</div>
</div>
<div class="sketch row-center">
<div v-for="(item, index) in sketch" :key="index" class="sketch-item row">
<i-icon :icon="item.icon" />
<span>{{ $t(item.text) }}</span>
</div>
</div>
<h5-upload @handleFileUpload="handleFileUpload" @handleGoogleDriveUpload="handleGoogleDriveUpload" />
</div>
</template>
<script>
import CoreMixins from '@/mixins/core'
import ImageMixins from '@/mixins/image'
const { SCOPES, CLIENT_ID, API_KEY, APP_ID } = process.env.GOOGLE
let tokenClient
let accessToken = null
let pickerInited = false
let gisInited = false
export default {
mixins: [CoreMixins, ImageMixins],
props: {
accept: {
type: String,
default: '*',
},
limit: {
type: Number,
default: 3,
},
},
data() {
return {
sketch: [
{ icon: 'sketch_1', text: 'components.pannel.sketch1' },
{ icon: 'sketch_2', text: 'components.pannel.sketch2' },
{ icon: 'sketch_3', text: 'components.pannel.sketch3' },
],
}
},
mounted() {
if (!pickerInited) {
this.loadJS('https://apis.google.com/js/api.js').then(() => {
this.gapiLoaded()
})
} else {
pickerInited = true
}
if (!gisInited) {
this.loadJS('https://accounts.google.com/gsi/client').then(() => {
this.gisLoaded()
})
} else {
gisInited = true
}
},
methods: {
handleUpload() {
this.$refs.uploadInput.click()
},
onFileInputChange(e) {
this.$emit('change', e)
},
handleFileUpload() {
this.handleUpload()
window.scrollTo({
top: 0,
behavior: 'smooth',
})
},
async handleGoogleDriveUpload() {
// JS 资源不存在,再次 fetch
if (!pickerInited) {
await this.loadJS('https://apis.google.com/js/api.js')
this.gapiLoaded()
}
if (!gisInited) {
await this.loadJS('https://accounts.google.com/gsi/client')
this.gisLoaded()
}
if (accessToken !== null) {
await this.createPicker()
return
}
this.handleAuthClick()
if (this.$store.state.isH5) {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
},
/**
* Callback after api.js is loaded.
*/
gapiLoaded() {
gapi.load('client:picker', this.initializePicker)
},
/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
async initializePicker() {
await gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest')
pickerInited = true
},
/**
* Callback after Google Identity Services are loaded.
*/
gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '',
})
gisInited = true
},
/**
* Sign in the user upon button click.
*/
handleAuthClick() {
tokenClient.callback = async response => {
if (response.error !== undefined) {
throw response
}
accessToken = response.access_token
await this.createPicker()
}
if (accessToken === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({ prompt: 'consent' })
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({ prompt: '' })
}
},
/**
* Sign out the user upon button click.
*/
handleSignoutClick() {
if (accessToken) {
accessToken = null
google.accounts.oauth2.revoke(accessToken)
}
},
/**
* Create and render a Picker object for searching images.
*/
createPicker() {
const view = new google.picker.DocsView(google.picker.ViewId.DOCS)
// 只读取 docx、xlsx、pptx、png、jpeg、jpg 类型文件
view.setMimeTypes(
'application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.google-apps.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.google-apps.spreadsheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.google-apps.presentation,application/pdf,image/png,image/jpeg,image/jpg'
)
view.setIncludeFolders(true)
const shareview = new google.picker.DocsView(google.picker.ViewId.DOCS)
// 只读取 docx、xlsx、pptx、png、jpeg、jpg 类型文件
shareview.setMimeTypes(
'application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.google-apps.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.google-apps.spreadsheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.google-apps.presentation,application/pdf,image/png,image/jpeg,image/jpg'
)
shareview.setEnableDrives(true)
shareview.setIncludeFolders(true)
const picker = new google.picker.PickerBuilder()
// .enableFeature(google.picker.Feature.NAV_HIDDEN)
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
// .setDeveloperKey(API_KEY)
.setAppId(APP_ID)
.setOAuthToken(accessToken)
.addView(view)
.addView(shareview)
// .addView(new google.picker.DocsUploadView())
.setCallback(this.pickerCallback)
.build()
picker.setVisible(true)
},
/**
* Displays the file details of the user's selection.
* @param {object} data - Containers the user selection from the picker
*/
async pickerCallback(data) {
if (data.action === google.picker.Action.PICKED) {
const document = data[google.picker.Response.DOCUMENTS][0]
const fileId = document[google.picker.Document.ID]
const res = await gapi.client.drive.files.get({
fileId,
fields: '*',
})
const { name, mimeType, exportLinks, webContentLink } = res.result
const res2 = await gapi.client.drive.files.download({
fileId,
})
const downloadUri = res2.result.response.downloadUri
console.log(fileId)
console.log(res.result)
console.log(name, mimeType, exportLinks)
console.log(downloadUri)
// let fetchUrl = webContentLink
// let fetchUrl = `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`
let fetchUrl = downloadUri
let fileName = name
let fileType = mimeType
// 如果 mimeType 是如下类型,说明是 google drive 内建类型,需要转换格式
if (mimeType === 'application/vnd.google-apps.document') {
fetchUrl = exportLinks['application/vnd.openxmlformats-officedocument.wordprocessingml.document']
fileName = `${name}.docx`
fileType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
} else if (mimeType === 'application/vnd.google-apps.spreadsheet') {
fetchUrl = exportLinks['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
fileName = `${name}.xlsx`
fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
} else if (mimeType === 'application/vnd.google-apps.presentation') {
fetchUrl = exportLinks['application/vnd.openxmlformats-officedocument.presentationml.presentation']
fileName = `${name}.pptx`
fileType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
}
this.startLoading()
fetch(fetchUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then(response => response.blob()) // 将响应转为 Blob
.then(blob => {
// 创建 File 对象
const file = new File([blob], fileName, { type: fileType })
console.log(file)
this.stopLoading()
this.$emit('change', [file], 'google_drive')
})
.catch(error => {
this.stopLoading()
console.error('Error downloading file:', error)
})
}
},
},
}
</script>
<style lang="scss" scoped>
#hide-input {
display: none;
}
@media (min-width: 980px) {
.pannel-wrap {
width: 680px;
margin: 0 auto;
}
.upload-box {
padding: 20px;
border-radius: 20px;
padding: 16px;
background: var(--primary);
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
.upload-content {
height: 320px;
border-radius: 10px;
border: 2px dashed rgba(255, 255, 255, 0.5);
.upload-icon {
width: 72px;
height: 72px;
}
.desc {
min-width: 246px;
padding: 14px 16px;
background: #fff;
margin-top: 18px;
font-family: Manrope-SemiBold, Manrope;
font-weight: 600;
font-size: 16px;
color: #000;
line-height: 20px;
text-align: center;
border-radius: 4px;
}
.or {
font-family: Manrope-Regular, Manrope;
font-weight: 400;
font-size: 13px;
color: #ffffff;
line-height: 22px;
margin: 4px 0;
&::before,
&::after {
content: '';
width: 59px;
height: 1px;
background: #fff;
opacity: 0.24;
}
&::before {
margin-right: 10px;
}
&::after {
margin-left: 10px;
}
}
.google-drive {
margin-bottom: 42px;
display: flex;
align-items: center;
.icon {
width: 22px;
height: 22px;
}
.text {
margin-left: 6px;
font-family: Manrope-Bold;
font-weight: bold;
font-size: 14px;
color: #ffffff;
line-height: 22px;
text-align: left;
font-style: normal;
text-decoration-line: underline;
&:hover {
opacity: 0.7;
}
}
}
.tip {
font-family: Manrope-Regular, Manrope;
font-weight: 400;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
line-height: 22px;
text-align: center;
}
}
.sketch {
padding: 48px 12px 0;
gap: 28px;
&-item {
max-width: 200px;
font-family: Manrope-Regular, Manrope;
font-weight: 400;
font-size: 18px;
color: #020322;
line-height: 18px;
}
.i-icon {
width: 48px;
height: 48px;
margin-right: 12px;
flex-shrink: 0;
}
}
}
@media (max-width: 980px) {
.pannel-wrap {
width: 6.8rem;
margin: 0 auto;
}
.upload-box {
padding: 0.2rem;
background: var(--primary);
border-radius: 0.24rem;
}
.upload-content {
padding: 0.6rem 0 0.24rem;
// height: 3.96rem;
border-radius: 0.12rem;
border: 0.04rem dashed rgba(255, 255, 255, 0.5);
.upload-icon {
width: 1rem;
height: 1rem;
}
.desc {
min-width: 4.92rem;
padding: 0.28rem 0.32rem;
margin-top: 0.3rem;
background: #fff;
font-family: Manrope-SemiBold, Manrope;
font-weight: 600;
font-size: 0.32rem;
color: #000000;
line-height: 0.4rem;
text-align: center;
border-radius: 0.08rem;
}
.or {
font-family: Manrope-Regular, Manrope;
font-weight: 400;
font-size: 0.26rem;
color: #ffffff;
line-height: 0.44rem;
margin: 0.08rem 0;
&::before,
&::after {
content: '';
width: 1.18rem;
height: 0.02rem;
background: #fff;
opacity: 0.24;
}
&::before {
margin-right: 0.2rem;
}
&::after {
margin-left: 0.2rem;
}
}
.google-drive {
margin-bottom: 0.64rem;
display: flex;
align-items: center;
.icon {
width: 0.4rem;
height: 0.4rem;
}
.text {
margin-left: 0.1rem;
font-family: Manrope-Bold;
font-weight: bold;
font-size: 0.28rem;
color: #ffffff;
line-height: 0.44rem;
text-align: left;
font-style: normal;
text-decoration-line: underline;
}
}
.tip {
font-family: Manrope-Regular, Manrope;
font-weight: 400;
font-size: 0.26rem;
color: rgba(255, 255, 255, 0.8);
line-height: 0.3rem;
}
}
.sketch {
margin-top: 0.48rem;
gap: 0.28rem;
&-item {
max-width: 2.08rem;
font-family: Manrope-Regular, Manrope;
font-weight: 400;
font-size: 0.26rem;
color: #020322;
line-height: 0.26rem;
}
.i-icon {
width: 0.72rem;
height: 0.72rem;
margin-right: 0.12rem;
flex-shrink: 0;
}
}
}
</style>
下课!