本次要跟姊姊一起學習Vue 3 + Firebase Storage 上傳檔案功能
▶ 如果您尚未建立專案/安裝 Firebase JS SDK 並初始化 Firebase,請先到
開箱23:Vue 3 + 建立web應用程式+Firebase JS SDK 初始化
官方文件:
簡介:Cloud Storage for Firebase
如何開始?Get started with Cloud Storage on Web
Firebase Storage 是 Google Cloud Platform 的一部分,專為 Firebase 開發者設計的雲端存儲解決方案。它允許開發者存儲和分享使用者生成的內容,例如圖片、音訊和影片。
主要特點:
強大的文件上傳/下載:不論網絡狀況如何,Firebase Storage 都可以確保文件的上傳和下載。即使在不穩定或差的網路環境下也能保持穩定。
安全性:Firebase Storage 配合 Firebase 的安全規則,讓你能夠精確地控制誰有存取、讀取、寫入的權限。
整合性:與 Firebase 的其他服務(如 Firebase Realtime Database、Firebase Authentication)有很好的整合,這使得開發流程更加順暢。
基於 Google Cloud Storage:Firebase Storage 在幕後實際上是由 Google Cloud Storage 支持的,這意味著它非常可靠,並且可以在必要時擴展到 Google Cloud 的其他功能和服務。
SDK 支援:Firebase Storage 提供 Android、iOS 和 JavaScript 的 SDK,讓開發者能夠輕鬆整合存儲功能到他們的應用程式或網站上。
性能和效率:自動適應網絡品質,如果應用程式在上傳或下載過程中發生斷網,Firebase Storage 可以在網絡重新連接時從斷點繼續。
使用 Firebase Storage 可以減少許多開發和維護雲端存儲解決方案的複雜性,並允許開發者專注於應用程式的核心功能。
免費使用的話:
存儲空間: 5GB 的總存儲容量。
下載流量: 每天 1GB 的下載數據。
上傳/下載操作: 上傳操作20萬次/日,下載操作5萬次/日。
開始實作本次功能吧!
▲ 成果
Demo網址:https://hahasister-ironman-project.netlify.app/#/uploadToFirebase
版本 "firebase": "^10.4.0"
☆★☆★ 詳細程式碼 前往 >> 本次程式 commit 紀錄
功能大綱
-實作可上傳圖片/取得上傳圖片清單/刪除圖片
結構
project-root/
├─ src/
│ ├─ views/
│ │ ├─ UploadToFirebase.vue // 新增操作頁面
│ │
│ ├─ services/
│ │ ├─ firebase.js // 引入
│ │
│ │
│ ├─ App.vue
│ ├─ main.js
└─ ...
回到Firebase console 控制台,開啟
可先以測試模式啟動(開通30天內-11/10,大家都可以讀寫資料庫),可之後再修改權限
Firebase 雲端儲存安全規則中的使用條件
安全規則保護 Cloud Storage 資料的訊息
選擇預設 Cloud Storage 儲存分區的位置。
修改 src/services/firebase.js
import { initializeApp } from "firebase/app";
import { getStorage } from 'firebase/storage'; // 新增
const firebaseConfig = {
// ...
storageBucket: ''
};
// Initialize Firebase
export const setupFirebase = initializeApp(firebaseConfig);
// Initialize Cloud Storage and get a reference to the service
export const storage = getStorage(setupFirebase); // 新增
創建參考:
詳細可看官方文件:Create a Reference
import { getStorage, ref } from "firebase/storage";
const storage = getStorage();
// Create a child reference
const imagesRef = ref(storage, 'images');
// imagesRef now points to 'images'
// Child references can also take paths delimited by '/'
const spaceRef = ref(storage, 'images/space.jpg');
// spaceRef now points to "images/space.jpg"
// imagesRef still points to "images"
以上是兩種不同創建方式的差異
上傳文件:
多種寫法,本範例
使用從Blob或File上傳呼叫uploadBytes()
方法
詳細可看官方文件:Upload Files
整合起來,來完成一個基本範例如下:
功能大綱
上傳圖片 > 按下上傳 > 上傳至firebase
重點
因為有用到兩個ref
,是會error的,所以將from 'firebase/storage'的ref
重新命名為storageRef
<template>
<div>
<input type="file" accept="image/*" @change="handleFileSelect" />
<button @click="upload">upload</button>
</div>
</template>
<script setup>
import { storage } from '@/services/firebase.js';
import { ref as storageRef, uploadBytes } from 'firebase/storage';
import { ref } from 'vue';
const selectedFile = ref();
const handleFileSelect = event => {
selectedFile.value = event.target.files[0];
};
const upload = () => {
if (!selectedFile.value) {
return;
}
const storageName = storageRef(storage, `images/${selectedFile.value.name}`);
uploadBytes(storageName, selectedFile.value).then(snapshot => {
console.log('Uploaded a blob or file!'); //上傳成功
});
};
</script>
這樣就完成了基本成果拉!(下圖)
並且到後台看一下是否新增成功
** 前期提要 **
模板 /
上傳區塊:
圖片預覽區塊:
圖片清單區塊:
資料宣告/
selectedFile:用戶選擇的圖片檔
fileInput:檔案輸入框的參考
downloadURL:上傳圖片後的下載連結
uploadProgress:上傳的進度
loading:是否正在載入圖片
downloadURLs:所有已上傳圖片的下載連結清單
方法宣告/
handleFileSelect:當用戶選擇圖片時檢查圖片大小,若超過2MB則警告
upload:上傳 selectedFile 到 Firebase Storage,並顯示上傳進度
getImgListAll:取得所有已上傳圖片的清單
removeImg:刪除指定的圖片。
onMounted:在組件掛載後,調用 getImgListAll 方法,取得所有已上傳的圖片清單
接著我們修改以上範例,上傳照片時要監控他的上傳進度...等等,可以改用以下方法
<template>
<div>
<p>上傳圖片</p>
<div>
<input type="file" ref="fileInput" accept="image/*" @change="handleFileSelect"/>
<button type="button" @click="upload">上傳</button>
</div>
<div>
<div v-if="uploadProgress !== null">
<progress :value="uploadProgress" max="100">{{ uploadProgress }}%
</progress>{{ uploadProgress }}%
</div>
<p v-if="downloadURL">
<img :src="downloadURL" />
</p>
<p v-else >圖片顯示於此</p>
</div>
</div>
</template>
<script setup>
import { storage } from '@/services/firebase.js';
import {
ref as storageRef,
uploadBytesResumable,
getDownloadURL,
} from 'firebase/storage';
import { onMounted, ref, nextTick } from 'vue';
const selectedFile = ref();
const fileInput = ref(null);
const downloadURL = ref(); // 圖片產生的url
const uploadProgress = ref(null); // 進度條
const handleFileSelect = event => {
const file = event.target.files[0]; // 抓取file
if (file && file.size <= 2 * 1024 * 1024) { // 怕大家傳太大檔案,所以我自己限制
selectedFile.value = file;
downloadURL.value = '';
} else {
alert('文件大小超過2MB,請重新上傳');
fileInput.value.value = ''; // 清空已上傳的檔案值
selectedFile.value = ''; // 清空選擇的檔案
}
};
const upload = () => {
if (!selectedFile.value) {
return;
}
const storageName = storageRef(storage, `images/${selectedFile.value.name}`);
const uploadTask = uploadBytesResumable(storageName, selectedFile.value);
uploadTask.on(
'state_changed',
snapshot => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
console.log('Upload state ', snapshot.state);
uploadProgress.value = parseInt(progress); // 將進度以整數寫入
},
error => {
console.log('Upload error', error);
},
async () => {
downloadURL.value = await getDownloadURL(uploadTask.snapshot.ref); //取得圖片url
console.log('File available at', downloadURL.value);
uploadProgress.value = null; // 進度條清空
fileInput.value.value = ''; // 清空input
selectedFile.value = ''; // 清空input
}
);
};
</script>
<template>
...
...
<div>
<p>照片清單</p>
<p v-if="loading">Loadig...</p>
<div v-else v-for="item in downloadURLs">
<img :src="item.url" alt="" :key="item.url"/>
<button type="button" @click="removeImg(item.fullPath)">
<img src="/src/assets/icon/remove.png" width="32" height="32" />
</button>
</div>
</div>
</template>
希望組出的清單陣列為
downloadURLs = [{url:...,fullPath:...},{url:...,fullPath:...}]
const getImgListAll = async () => {
loading.value = true;
await nextTick();
downloadURLs.value = []; // 清單陣列
const listRef = storageRef(storage, 'images'); // 取得資料夾為images的
try {
const res = await listAll(listRef); // 取得清單
const urls = await Promise.all(
res.items.map(async itemRef => ({
url: await getDownloadURL(itemRef), // 將連結放入陣列
fullPath: itemRef.fullPath
}))
);
downloadURLs.value = urls;
} catch (error) {
console.error('Error getting download URLs', error);
} finally {
loading.value = false;
}
};
onMounted(() => {
getImgListAll(); //畫面載入取得清單
});
const removeImg = async fullPath => {
try {
await deleteObject(storageRef(storage, fullPath));
await getImgListAll(); // 重新取得清單
} catch (error) {
console.error('Error deleting image', error);
}
};
程式碼有點長,好難拆解步驟,詳細請前往 >> 本次程式 commit 紀錄
以上程式碼僅供簡單參考,尚有優化地方,例如:處理錯誤處理,即時取得清單,程式碼優化...等
那我們明天再見了~