零成本!无需服务器,搭建你的图床!

先给大家看看成品:
image.png

访问地址:http://cp64mba5g6h48vl4is50.app.memfiredb.cn/

这是我花十分钟做出来的零成本,不需要服务器的图床,不需要登录,任何人都可以在上面上传图片和拿到图片链接去使用,当然这只是一个简单的图床功能,后续我会慢慢优化更强大。接下来我将教大家如何做一个零成本,无需服务器的免费图床。

1.创建免费应用

先去前往MemFire Cloud登录后,在首页创建一个免费的应用

image.png

这个应用创建完成后,你将拥有

  • 免费云数据库
  • 自动生成api
  • 对象存储
  • 二十几种第三方认证服务
  • 实时数据库
  • 静态托管

然后我们这个图床主要用到的是自动生成api和对象存储

2.创建存储桶

应用创建完成后,我们先去存储的界面创建一个存储桶用来存放图片

image.png

image.png

3.初始化vue项目

大家可以用npx或者vue创建都可以,我是用vue创建的

vue create vue-tuchuang

创建完成后,在根目录下创建一个.env.local,来存放环境变量

4.安装依赖包

安装用于访问应用资源的依赖包

npm i @supabase/supabase-js

5.配置环境变量

MemFire Cloud应用控制台的“应用设置”->“API”找到应用的URL和anon 公钥,分别填到.env.local里面

image.png

VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=

6.初始化客户端

在src下面创建一个supabase.js,将下面的代码填入

import { createClient } from '@supabase/supabase-js'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

7.编写页面

src\App.vue

<template>
  <div id="app">
    <header>
      <h1>图床</h1>
      <input type="file" @change="handleFileUpload" accept="image/*" />
    </header>
    <main>
      <section v-if="fileList.length > 0">
        <h2>已上传的图片</h2>
        <ul class="image-list">
          <li v-for="(image, index) in fileList" :key="index" class="image-item">
            <img :src="image.publicUrl" alt="uploaded image" @click="showImage(index)" />
            <div class="image-overlay">
              <span class="image-name">{{ truncateFileName(image.name) }}</span>
              <button @click="copyImageUrl(index)">复制URL</button>
            </div>
          </li>
        </ul>
      </section>
      <section v-else>
        <p style="color: black;">暂无上传的图片</p>
      </section>
    </main>
    <!-- 放大查看图片的弹窗 -->
    <div v-if="showModal" class="modal" @click="closeModal">
      <div class="modal-content">
        <img :src="selectedImageUrl" alt="enlarged image" />
      </div>
    </div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue';
import { supabase } from './supabase';

export default {
  setup() {
    const fileList = ref([]);
    const files = ref();
    const showModal = ref(false);
    const selectedImageUrl = ref('');

    onMounted(() => {
      getImageUrl();
    });

    const handleFileUpload = async (evt) => {
      files.value = evt.target.files;
      try {
        if (!files.value || files.value.length === 0) {
          throw new Error('请选择要上传的图片.');
        }
        const file = files.value[0];
        const fileExt = file.name.split('.').pop();
        const filePath = `${Math.random()}.${fileExt}`;
        let { error: uploadError } = await supabase.storage.from('drawingBoard').upload(filePath, file);
        if (uploadError) throw uploadError;
        getImageUrl();
      } catch (error) {
        alert(error.message);
      }
    };

    const copyImageUrl = (index) => {
      const publicUrl = fileList.value[index].publicUrl;
      navigator.clipboard
        .writeText(publicUrl)
        .then(() => {
          alert(`已复制图片URL:${publicUrl}`);
        })
        .catch((err) => {
          console.error('复制失败:', err);
        });
    };

    const getImageUrl = async () => {
      const { data, error } = await supabase
        .storage
        .from('drawingBoard')
        .list()
        const filteredData = data.filter(item => item.name !== ".emptyFolderPlaceholder");
        filteredData.forEach((item) => {
        const { data } = supabase
          .storage
          .from('drawingBoard')
          .getPublicUrl(item.name)
        if (data) {
          item.publicUrl = data.publicUrl
        }
      })
      if(filteredData.length >0){
        fileList.value = filteredData
      }
     

    }

    const showImage = (index) => {
      selectedImageUrl.value = fileList.value[index].publicUrl;
      showModal.value = true;
    };

    const closeModal = () => {
      showModal.value = false;
    };

    const truncateFileName = (fileName) => {
      const maxLength = 20; // 设置名称最大长度
      if (fileName.length > maxLength) {
        return fileName.substring(0, maxLength - 3) + '...'; // 超过长度则省略
      }
      return fileName;
    };

    return {
      fileList,
      handleFileUpload,
      copyImageUrl,
      showImage,
      showModal,
      selectedImageUrl,
      closeModal,
      truncateFileName
    };
  },
};
</script>

<style>
#app {
  font-family: Arial, sans-serif;
  margin: 0 auto;
  max-width: 800px;
  padding: 20px;
}

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}
h2{
  color: #000;
}
header h1{
  font-size: 24px;
  margin: 0;
}

input[type='file'] {
  display: inline-block;
  padding: 10px;
}

main {
  background-color: #f5f5f5;
  padding: 20px;
}
ul.image-list li img:hover {
 transform: scale(1.1); /* Enlarge on hover */
}
ul.image-list {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

.image-item {
  width: calc(33.33% - 20px);
  text-align: center;
  margin-bottom: 20px;
  position: relative;
}

.image-item img {
  /* max-width: 100%;
  border-radius: 5px;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
  cursor: pointer; */

  max-width: 100%;
 height: auto; /* Maintain aspect ratio */
 border-radius: 5px;
 box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
 cursor: pointer; /* Show pointer on hover for clicking */
 transition: transform 0.2s ease; /* Add smooth transition for scaling */
}

.image-overlay {
  position: absolute;
  bottom: 10px;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.5);
  padding: 5px;
  border-radius: 5px;
}

.image-overlay .image-name {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: #000; /* 设置名称颜色为黑色 */
}

button {
  padding: 5px 10px;
  margin-left: 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}

/* 弹窗样式 */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 999;
}

.modal-content {
  max-width: 80%;
  max-height: 80%;
  overflow: auto;
}

.modal-content img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
</style>

8.运行程序

npm run dev

效果:

image.png

9.静态托管

项目开发完了,大家肯定想把这个作品展示给自己朋友或者网友使用,刚好我们使用MemFire Cloud的静态托管来托管我们的项目,就相当于部署我们的项目,我们不需要在服务器上部署。
在根目录下执行命令

npm run build

打包完成后,我们进入文件资源管理器,将里面的所有文件选中打成一个zip压缩包

image.png

image.png

我们再次打开MemFire Cloud应用的控制台,我们来到静态托管的选项下,然后将我们刚刚打包好的压缩包上传到这里,上传完成后会发现上面出现一个访问地址,这个地址就可以给大家安全的访问啦,如果你觉得这个地址有点丑,你还可以去配置域名,前提是你已经有了一个域名并且已经备案了。

image.png

好啦,咱们这个简单的图床就做好了,全程没有需要服务器,零成本完成的!后续我会慢慢优化这个图床!大家也可以试试用MemFire Cloud来做一些应用。

具体的大家可以参考或者阅读一下文档

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值