一键上传,无限容量!打造高效图床工具,利用Electron和Gitee搭建自己的私人云存储空间

news2024/12/26 22:15:54

说在前面

平时写文章或写代码的时候,都少不了需要将本地图片转成在线图片链接,大家都是使用什么工具进行转换的呢?相信很多人都有自己的图床工具,今天来给大家介绍一下,怎么基于Gitee和Electron来开发一个便捷的图床工具,支持图片的上传、删除、复制和快速生成markdown链接、快捷键唤起和隐藏面板,粘贴剪切板图片上传等……

框架选型

原本只是想写一个Chrome插件来实现简单功能,后面发现Chrome插件的局限性太大了,所以最后还是选择使用Electron来制作一个桌面程序。

存储方面我们可以直接使用gitee来用做图库存储,不需要额外去购买存储服务器。

准备工作

一、Gitee创建图床仓库目录

1、Gitee注册

直接到Gitee官网: Gitee - 基于 Git 的代码托管和研发协作平台 ,点击注册即可。

image.png

2、仓库创建

注册完账号后直接登录,在首页点击右上角的加号可以新建仓库

image.png

仓库信息自行填写即可

image.png

创建完仓库后我们可以新建一个文件夹用来存储图片:

image.png

3、生成授权码

打开设置里的私人令牌页面

image.png

点击生成新令牌,根据提示填写信息即可,注意保存好生成的令牌。

image.png

二、搭建electron项目

我们可以先搭建一个简单的electron项目:

  1. 安装 Node.js:确保你的电脑上已经安装了 Node.js。你可以从 Node.js 官方网站(https://nodejs.org)下载并安装最新版本的 Node.js。

  2. 创建项目目录:在你想要创建项目的位置,创建一个新的文件夹作为项目目录。

  3. 初始化项目:打开命令行终端,进入到项目目录,并执行以下命令初始化一个新的 npm 项目:

npm init -y
  1. 安装 Electron:在命令行终端中执行以下命令来安装 Electron:
npm install electron
  1. 创建主文件:在项目目录中创建一个名为 main.js 的文件,作为 Electron 应用的主文件。

  2. 编写主文件代码:在 main.js 文件中编写 Electron 应用的主要逻辑。例如,下面是一个简单的示例:

const { app, BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit();
});
  1. 创建 HTML 文件:在项目目录中创建一个名为 index.html 的文件,作为 Electron 应用的初始页面。

  2. 编写 HTML 文件代码:在 index.html 文件中编写你的应用界面的 HTML 代码。

  3. package.json 文件中添加启动命令:打开 package.json 文件,在 "scripts" 部分添加以下内容:

"scripts": {
  "start": "electron ."
}
  1. 启动 Electron 应用:在命令行终端中执行以下命令来启动 Electron 应用:
npm start

功能实现

前面准备工作全都完成后,现在我们就有了一个简单electron项目架子和一个gitee仓库,可以开始来实现相关的功能了。

一、git操作

gitee提供了api文档,我们可以通过gitee的api文档来对我们的仓库进行上传图片和获取图片的操作。

gitee API文档地址:https://gitee.com/api/v5/swagger#/getV5ReposOwnerRepoStargazers?ex=no

这里我将需要使用到的功能写成了一个类:

1、初始化,获取配置信息
  • accessToken

用户授权码,也就是我们前面生成的私人令牌。

image.png

  • username

仓库所属空间地址(企业、组织或个人的地址path),如下图:

image.png

  • repo

仓库路径(path),如下图:

image.png

  • dirPath

图片存放目录地址,如下图:

image.png

  • branchName

分支名,默认为master,我们可以修改成指定分支:

image.png

  init(config = {}) {
    // 设置 Gitee 仓库信息和目录路径
    this.username = config.username;
    this.repo = config.repo;
    this.accessToken = config.accessToken;
    this.branchName = config.branchName || "master";
    this.apiUrl = "https://gitee.com/api/v5/repos/";
    this.dirPath = config.dirPath;
  }

将以上配置信息在程序的配置里设置好即可:

image.png

2、上传图片到gitee图床目录下

根据API文档进行请求即可:

  async uploadToGitee(base64Data) {
    try {
      const formData = new FormData();
      formData.append("content", base64Data);
      formData.append("access_token", this.accessToken);
      formData.append("message", "上传图片");

      const timeStamp = new Date().getTime();
      Toast.showLoading("正在上传");
      const response = await fetch(
        `${this.apiUrl}${this.username}/${this.repo}/contents/${this.dirPath}${timeStamp}.jpg`,
        {
          method: "POST",
          body: formData,
        }
      );
      Toast.hide();
      if (!response.ok) {
        throw new Error("上传图片失败");
      }

      const data = await response.json();
      Toast.showToast("图片上传成功!");
      return data.content.download_url;
    } catch (error) {
      console.error(error);
      Toast.showToast("图片上传失败!");
      throw error;
    }
  }
3、获取gitee图床目录下的所有图片

根据API文档进行请求即可:

async getImg() {
    try {
      const response = await fetch(
        `${this.apiUrl}${this.username}/${this.repo}/contents/${this.dirPath}`,
        {
          headers: {
            Authorization: `token ${this.accessToken}`,
          },
        }
      );

      if (!response.ok) {
        throw new Error("获取图片列表失败");
      }

      const data = await response.json();

      // 筛选出图片文件
      const imageFiles = data.filter(
        (file) =>
          file.type === "file" && file.name.match(/\.(jpg|jpeg|png|gif)$/i)
      );

      return imageFiles;
    } catch (error) {
      console.error(error);
      throw error;
    }
  }
4、删除gitee图床目录下指定图片

根据API文档进行请求即可:

  async deleteImg(fileName, sha, cb) {
    try {
      const response = await fetch(
        `${this.apiUrl}${this.username}/${this.repo}/contents/${this.dirPath}/${fileName}?access_token=${this.accessToken}&ref=${this.branchName}`,
        {
          method: "DELETE",
          headers: {
            "Content-Type": "application/json;charset=utf-8",
          },
          body: JSON.stringify({
            message: "删除图片",
            sha,
            prune: true,
          }),
        }
      );

      if (!response.ok) {
        throw new Error("删除图片失败");
      }

      Toast.showToast("删除成功");
      cb && cb();
    } catch (error) {
      console.error(error);
      Toast.showToast("删除失败");
      throw error;
    }
  }

二、拖拽点击、粘贴、选择文件夹上传图片

我们可以通过三种方式来上传我们本地的图片

image.png

image.png

1、拖拽或点击上传图片

前面有写了一篇实现拖拽或点击上传图片的文章,这里就不详细再赘述了,有兴趣的可以去看看:《文件拖拽上传功能已经烂大街了,你还不会吗?》

2、粘贴上传

平时我们经常会使用到截图,所以我们希望可以直接将截图粘贴到工具中进行上传,这里我们可以通过监听页面上的paste事件,直接读取剪切板的图片展示到页面上并进行上传。

document.addEventListener("paste", function (e) {
  const items = e.clipboardData.items;

  for (const item of items) {
    if (item.type.indexOf("image") !== -1) {
      const blob = item.getAsFile();
      showPreview(blob);
    }
  }
});
3、选择文件夹上传文件夹中的所有图片

一张图片一张图片上传太慢了,所以我们也支持直接选择一个文件夹,将文件夹里的所有图片一次性上传到gitee上,

    1. 创建一个 HTML 文件,包含一个用于选择文件夹的 <input> 元素和一个按钮用于触发上传操作。代码如下:
<input
    type="file"
    style="display: none"
    id="folderInput"
    onchange="uploadImages()"
    webkitdirectory
    multiple
  />
  <button class="upload-btn" onclick="selectFolder()" style="background: #FFD04C;">
    选择文件夹上传
  </button>

webkitdirectory:告诉浏览器文件选择器应该允许选择文件夹(目录),而不仅仅是单个文件。

multiple:告诉浏览器文件选择器应该允许选择多个文件或文件夹。

  • 2、获取到选择的文件并做相关处理
function blobToBase64(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => {
      const base64String = reader.result.split(",")[1];
      resolve(base64String);
    };
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}
function selectFolder() {
  const folderInput = document.getElementById("folderInput");
  folderInput.click();
}

async function uploadImages() {
  const folderInput = document.getElementById("folderInput");
  let files = folderInput.files || [];
  files = [...files].filter((file) => file.type.startsWith("image/"));

  for (let i = 0; i < files.length; i++) {
    Toast.showLoading(`上传中,${i}/${files.length}`);
    const file = files[i];
    const base64Data = await blobToBase64(file);
    await gitOperate.uploadToGitee(base64Data,false);
  }
  Toast.hide();
  Toast.showToast(`已全部上传`);
  waterfall.init();
}

三、瀑布流展示图片

image.png

上传完图片后,我们还希望可以看到之前上传的图片,这里我们需要对图片列表做一个瀑布流展示。

image.png

之前我也有写过一个瀑布流组件详细的实现步骤,有兴趣的同学可以看看:《Vue封装一个瀑布流图片容器组件》。

这里我们可以通过原生JavaScrip来快速实现一个,具体代码如下:

class WaterfallContent {
  constructor(config = {}) {
    this.init(config);
  }
  init(config = {}) {
    this.imgList = config.imgList || [];
    this.column = config.column || 8;
    this.imgMargin = config.imgMargin || 0.5;
    this.domId = config.domId || "waterfall-container";
    this.minHeight = [];
    this.arr = [];
    const ul = document.getElementById(this.domId);
    ul.innerHTML = "";
  }
  async create(imgList = this.imgList, cb) {
    this.init();
    this.imgList = imgList;
    const ul = document.getElementById(this.domId);
    ul.innerHTML = "";
    let trueWidth = Math.floor(
      (100 - this.column * this.imgMargin * 2) / this.column
    );
    let trueWidthPx = 0;
    for (let i = 0; i < this.column; i++) {
      let li = document.createElement("li");
      li.style.listStyle = "none";
      li.style.float = "left";
      li.style.width = `${trueWidth}%`;
      li.style.margin = `0 ${this.imgMargin}%`;
      li.classList.add("git-img");

      ul.appendChild(li);
      this.arr.push(li);
      this.minHeight.push(0);
      trueWidthPx = li.offsetWidth;
    }
    this.loadHandler(trueWidthPx, cb);
  }
  getBase64(file) {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    return new Promise((resolve) => {
      reader.onload = () => {
        resolve(reader.result);
      };
    });
  }
  getImgPx(img, maxWidth) {
    const image = new Image();
    image.src = img;
    return new Promise((resolve) => {
      image.onload = () => {
        const width = image.width;
        const height = image.height;
        image.width = maxWidth;
        image.height = image.height * (maxWidth / width);
        resolve({ width, height, image });
      };
    });
  }
  async loadHandler(trueWidth, cb) {
    for (let i = 0; i < this.imgList.length; i++) {
      const imgItem = this.imgList[i];
      const src = imgItem.download_url;
      const res = await this.getImgPx(src, trueWidth);
      const { image } = res;
      const minHeight = this.minHeight;
      const arr = this.arr;
      // 高度数组的最小值
      const min = Math.min.apply(null, minHeight);
      // 高度数组的最小值索引
      const minIndex = minHeight.indexOf(min);
      // 克隆一份图片
      const im = image.cloneNode(true);
      im.setAttribute("data-sha", imgItem.sha);
      im.onclick = this.imgClick;
      // 将图片假如对应最小值索引的容器中
      arr[minIndex].appendChild(im);
      // 更新最小值索引的容器的高度
      minHeight[minIndex] += im.height;
      if (i === 0 && cb) {
        cb();
      }
    }
  }
}

四、自定义鼠标右键菜单栏

image.png

查看图片的时候我们希望可以对图片进行操作,这里的操作我们通过鼠标右键点击弹出,所以我们可以实现一个自定义鼠标右键菜单栏。

具体代码如下:

class MouseMenu {
  constructor(config) {
    this.menuClass = config.menuClass || "j-mouse-menu";
    this.menuId = config.menuId || "JMouseMenu";
    this.contentId = config.contentId || "j-mouse-content";
    this.init();
  }
  init() {
    const dom = document.getElementById(this.contentId);
    dom.oncontextmenu = (e) => {
      const clickItem = e.path[0];
      if (clickItem.localName !== "img") return;
      const menu = document.getElementById(this.menuId);
      this.clickItem = clickItem;
      this.hideAllMenu();
      // 自定义body元素的鼠标事件处理函数
      e = e || window.event;
      e.preventDefault();
      let scrollTop =
        document.documentElement.scrollTop || document.body.scrollTop; // 获取垂直滚动条位置
      let scrollLeft =
        document.documentElement.scrollLeft || document.body.scrollLeft; // 获取水平滚动条位置
      menu.style.display = "block";
      let left = e.clientX + scrollLeft;
      let top = e.clientY + scrollTop;
      if (menu.offsetHeight + top > window.innerHeight) {
        top = window.innerHeight - menu.offsetHeight - 5;
      }
      if (menu.offsetWidth + left > window.innerWidth) {
        left = window.innerWidth - menu.offsetWidth - 5;
      }
      menu.style.left = left + "px";
      menu.style.top = top + "px";
      document.onclick = () => {
        this.hideAllMenu();
      };
    };
  }
  hideAllMenu() {
    const jMenu = document.getElementsByClassName("j-mouse-menu");
    for (const item of jMenu) {
      item.style.display = "none";
    }
  }
}

五、Toast提示功能

image.png

交互少不了Toast弹窗提示,这里我们使用JavaScrip简单实现一个,具体代码如下:

class ToastC {
  constructor(config) {
    this.config = config;
    this.init();
  }

  init() {
    const body = document.body;
    const toastContainer = document.createElement("div");
    toastContainer.id = "toastContainer";
    const styleObj = {
      position: "fixed",
      top: "50%",
      left: "50%",
      transform: "translate(-50%, -50%)",
      background: "rgba(0, 0, 0, 0.8)",
      color: "#ffffff",
      fontSize: "16px",
      opacity: 0.7,
      transition: "opacity 0.3s ease-in-out",
      padding: "10px",
      "border-radius": "5px",
      display: "none",
      textAlign: "center",
    };
    for (const key in styleObj) toastContainer.style[key] = styleObj[key];
    body.appendChild(toastContainer);

    const loader = document.createElement("div");
    loader.id = "toastLoader";
    const loaderStyleObj = {
      border: "4px solid #f3f3f3",
      borderTop: "4px solid #3498db",
      borderRadius: "50%",
      width: "20px",
      height: "20px",
      animation: "spin 1s linear infinite",
      margin: "0 auto 10px",
      display: "none",
    };
    for (const key in loaderStyleObj) loader.style[key] = loaderStyleObj[key];
    toastContainer.appendChild(loader);

    const text = document.createElement("div");
    text.id = "toastText";
    const textStyleObj = {
      marginTop: "5px",
    };
    for (const key in textStyleObj) text.style[key] = textStyleObj[key];
    toastContainer.appendChild(text);

    const keyframes = `
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }
      `;
    const style = document.createElement("style");
    style.innerHTML = keyframes;
    document.head.appendChild(style);
  }

  showToast(text) {
    const textElem = document.getElementById("toastText");

    // 设置Toast提示文本
    textElem.innerText = text;

    // 显示Toast提示
    toastContainer.style.display = "block";

    // 3秒后隐藏Toast提示
    setTimeout(() => this.hide(), 3000);
  }

  showLoading(text = "加载中...") {
    const loader = document.getElementById("toastLoader");
    const textElem = document.getElementById("toastText");

    // 设置Toast提示文本为加载中
    textElem.innerText = text;

    // 显示Toast提示和加载动画
    loader.style.display = "block";
    this.show();
  }

  show() {
    const toastContainer = document.getElementById("toastContainer");

    // 显示Toast提示
    toastContainer.style.display = "block";
  }

  hide() {
    try {
      const toastContainer = document.getElementById("toastContainer");
      const loader = document.getElementById("toastLoader");

      // 隐藏Toast提示和加载动画
      toastContainer.style.display = "none";
      loader.style.display = "none";
    } catch (err) {}
  }
}

六、快捷键打开隐藏窗口

我们可以设置快捷键快速唤起和隐藏窗口,在根目录下的main.js文件中注册快捷键,这里我设置的是alt + x,大家也可以改成自己喜欢的快捷键,具体代码如下:

  // 注册快捷键
  globalShortcut.register("Alt+X", () => {
    if (mainWindow.isVisible()) {
      mainWindow.hide();
    } else {
      mainWindow.show();
    }
  });

工具使用

一、源码下载

直接到 gitee 上下载即可。

image.png

二、依赖安装

下载完源码之后,我们到gitImgBed目录下运行npm i安装依赖,等待依赖安装完成。

image.png

三、程序打包

依赖安装完成之后,我们可以在gitImgBed目录下运行npm run build进行打包

image.png

打包完成后我们可以在当前目录下看到一个叫jyeontuGitImgBed-win32-x64文件夹,打开文件夹,找到里面一个叫jyeontuGitImgBed的应用程序,双击启动即可

image.png

四、配置填写

将之前准备工作时间的gitee仓库相关信息填写到配置中。

image.png

输入正确信息后保存,便可以上传和查看gitee图床中的图片了。

image.png

image.png

image.png

源码

一、gitee

gitee 地址:https://gitee.com/zheng_yongtao/electron_program

二、公众号

关注公众号『前端也能这么有趣』发送 图床即可获取源码。

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1271587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

消除笔怎么用?手把手教你一键智能消除杂物

消除笔怎么用&#xff1f;消除笔是一种非常实用的工具&#xff0c;可以帮助我们快速修复图片中的小问题。无论是想要消除照片中的路人还是进行一些修改&#xff0c;消除笔都可以轻松地帮助我们实现。 以下是使用消除笔的步骤&#xff1a; 1、打开水印云软件&#xff0c;并在工具…

ArkTS-时间滑动选择器弹窗

时间滑动选择器弹窗 以24小时的时间区间创建时间滑动选择器&#xff0c;展示在弹窗上。 示例 useMilitaryTime: 展示时间是否为24小时制&#xff0c;默认为12小时制。默认值&#xff1a;false Entry Component struct TimePickerDialogExample {private selectTime: Date new …

基于SSM乡镇自来水收费系统的设计与实现

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对乡镇自来水收费信息管理混乱&#xff0c;出错率高&#xff0c;信息安…

i己学助力构建幼教智能时代家园共育新模式

近日,为探索智能时代幼儿教育的智慧化解决方案,智能时代赢之道——2023幼教智能时代精英论坛在北京成功举办。在会议现场,来自全国各地的幼儿教育专家、园长、教师汇聚一堂,针对智能时代幼儿园所应该如何变革展开分享和讨论,“i己学智慧课堂”同期发布。 “在1-6年级阶段,平均…

专业级音频处理 Logic Pro X 中文 for Mac

Logic Pro X是一款专业音频制作和音乐创作软件。它是Mac电脑上最受欢迎和广泛使用的音频工作站&#xff08;DAW&#xff09;。Logic Pro X提供了丰富的功能和工具&#xff0c;适用于音乐制作、录音、编辑、混音和音频处理等方面。以下是Logic Pro X软件的一些主要特点和功能&am…

05-React路由(Router 5版本)

React路由背景介绍 背景介绍 多页面应用与SPA单页面应用 多页面应用 先说传统的多页面&#xff0c;需要写多个子页面 点击导航栏&#xff0c;整个页面都会刷新&#xff0c;但是实际上我只想刷新一小块的内容&#xff0c;其他东西变化不大 而且这个单页面&#xff0c;每次切…

【Java】文件I/O-文件内容操作-输入输出流-Reader/Writer/InputStream/OutputStream四种流

导读 在文件I/O这一节的知识里&#xff0c;对文件的操作主要分为两大类&#xff1a; ☑️针对文件系统进行的操作 ☑️针对文件内容进行的操作 上文已经讲了针对文件系统即File类的操作&#xff0c;这篇文章里博主就来带了解针对文件内容的操作&#xff0c;即输入输出流&am…

2023工作中遇到问题一

1、vue中模拟鼠标点击下拉框 <xxx-select ref"aaa" :value"form.serviceCharge" placeholder"请输入" add"(value) > getBasGoods(value)" :configInfo"configInfo" />vue中模拟鼠标点击 this.$refs.aaa.$el.cli…

『C++成长记』构造函数和析构函数

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、类的六个个默认成员函数 &#x1f4d2;1.1认识默认成员函数 二、构造函数 …

【Linux】SELinux 关闭

SELinux 最在安装国产数据库&#xff0c;遇到了无法远程访问的问题&#xff0c;定位了之后&#xff0c;需要关闭SELinux 。 在关闭它之前我们一起了解一下什么是SELinux &#xff1f; 什么是SELinux 安全增强式 Linux&#xff08;SELinux&#xff09;是一种强制访问控制的…

ElementPlusError: [ElPagination] 你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档

使用element table出现这个错误好几回了&#xff0c;今天把它记录一下&#xff0c;并把错误原因复盘一遍。具体如下&#xff1a; 错误截图 原因 其实这个错误挺迷的&#xff0c;我把各种情况都测试了一遍&#xff0c;最后发现是因为给 翻页参数 total 传值错误导致的。 总结…

docker读取字体异常

解决方法 docker容器中执行 apk add ttf-freefont 根据版本不同 apk add ttf-dejavu-fonts apk add ttf-bernoulli

python 制作3d立体隐藏图

生成文件的3d图&#xff0c;例子&#xff1a; 文字&#xff1a; 隐藏图&#xff1a; 使用建议&#xff1a; &#xff11;、建议不用中文&#xff0c;因为中文太复杂&#xff0c;生成立体图效果不好。 &#xff12;、需要指定FONT_PATH&#xff0c;为一个ttf文件&#xff0c;…

Find My戒指|苹果Find My技术与戒指结合,智能防丢,全球定位

戒指是一种戴套在手指上做纪念或装饰用的小环&#xff0c;用金属、玉石等制成。如今智能戒指是炙手可热的可穿戴设备&#xff0c;智能戒指可以进行健康监测&#xff0c;包括实时监测心率、睡眠质量、步数等个人健康指标&#xff0c;并提供有关饮食和锻炼的建议&#xff0c;以帮…

自动化接口测试:Pytest让你轻松搞定!了解一般流程及方法

首先我们要明确&#xff0c;通常所接口测试其实就属于功能测试&#xff0c;主要校验接口是否实现预定的功能&#xff0c;虽然有些情况下可能还需要对接口进行性能测试、安全性测试。 在学习接口自动化测试之前&#xff0c;我们先来了解手工接口测试怎样进行。 URL组成 为了更…

代码随想录算法训练营 ---第五十一天

1.第一题&#xff1a; 简介&#xff1a; 本题相较于前几题状态复杂了起来&#xff0c;因为多了一个冷冻期。本题讲解可去代码随想录看&#xff0c;这里差不多只是加了些自己的理解。 动规五部曲&#xff0c;分析如下&#xff1a; 确定dp数组以及下标的含义 dp[i][j]&#x…

html5各行各业官网模板源码下载(1)

文章目录 1.来源2.源码模板2.1 HTML5白色简洁设计师网站模板 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134682321 html5各行各业官网模板源码下载&#xff0c;这个主题覆盖各行业的html官网模板&#xff0c;效果模…

用本子堆经验,手把手教你怎么写国自然项目基金!

随着社会经济发展和科技进步&#xff0c;基金项目对创新性的要求越来越高。申请人需要提出独特且有前瞻性的研究问题&#xff0c;具备突破性的科学思路和方法。因此&#xff0c;基金项目申请往往需要进行跨学科的技术融合。申请人需要与不同领域结合&#xff0c;形成多学科交叉…

一键分发平台-账号设置

首页-账号管理 ●登录后点击箭头-账号设置 控制台-账号管理 ●进入控制台-个人中心-账号管理 ●账号管理-个人资料介绍 ●账号管理-修改密码 ●账号管理-通知设置 ●账号管理-上传设置 ●账号管理-账号设置 ●账号管理-登录日志

数据结构与算法复习笔记

1.数据结构基本概念 数据结构: 它是研究计算机数据间关系&#xff0c;包括数据的逻辑结构和存储结构及其操作。 数据&#xff08;Data&#xff09;&#xff1a;数据即信息的载体&#xff0c;是能够输入到计算机中并且能被计算机识别、存储和处理的符号总称。 数据元素&#xf…