diff --git a/src/pages/Editor.vue b/src/pages/Editor.vue
index fae9a74..10605f4 100644
--- a/src/pages/Editor.vue
+++ b/src/pages/Editor.vue
@@ -60,21 +60,15 @@ editorConfig.MENU_CONF.uploadImage = {
   fieldName: "image", // 后端接收字段名
 
   async customUpload(file, insertFn) {
-
-    const index = imagesCache.value.length; // 当前图片索引
-
-    // 读取图片尺寸
+    const index = imagesCache.value.length;
     const imageSize = await getImageSize(file);
-
-    // 存入缓存
+    const objectURL = URL.createObjectURL(file); // 生成 blob URL
     imagesCache.value.push({
       file,
+      url: objectURL, // 保存 blob URL
       originalWidth: imageSize.width,
-      originalHeight: imageSize.height
+      originalHeight: imageSize.height,
     });
-
-    // 生成临时预览 URL
-    const objectURL = URL.createObjectURL(file);
     insertFn(objectURL, `image-${index}`, objectURL); // 插入编辑器
   }
 };
@@ -155,10 +149,30 @@ const submitBlog = async () => {
 
 
   let content = editorRef.value.getHtml(); // 获取 HTML 内容
-  const images = [...imagesCache.value]; // 复制图片数组
+  let images = [...imagesCache.value]; // 复制图片数组
 
-  // 解析 `<img>` 并处理宽高
+  // 解析 `<img>` 并提取所有 blob URL
   const imgTags = content.match(/<img[^>]+>/g) || [];
+  const usedUrls = new Set(); // 记录编辑器中使用的 blob URL
+
+  imgTags.forEach((imgTag) => {
+    const srcMatch = imgTag.match(/src=["']([^"']+)["']/);
+    if (srcMatch) {
+      usedUrls.add(srcMatch[1]); // 将使用的 blob URL 添加到 Set 中
+    }
+  });
+
+  // 清理 imagesCache 中未被使用的图片
+  images = images.filter((img) => {
+    const isUsed = usedUrls.has(img.url);
+    if (!isUsed) {
+      URL.revokeObjectURL(img.url); // 释放未使用的 blob URL
+    }
+    return isUsed; // 只保留编辑器中仍在使用的图片
+  });
+
+  // 更新 imagesCache
+  imagesCache.value = images;
 
   // 创建 blob URL 到索引的映射
   const urlToIndexMap = new Map();
@@ -185,8 +199,8 @@ const submitBlog = async () => {
       const styleMatch = imgTag.match(/style=["']([^"']+)["']/);
       let width = "", height = "";
 
-      // 获取图片的原始尺寸(从 images 中根据 blob URL 匹配)
-      const imageData = images.find((img) => URL.createObjectURL(img.file) === src);
+      // 从 images 中根据保存的 url 匹配图片数据
+      const imageData = images.find((img) => img.url === src); // 使用保存的 url 比较
       const originalWidth = imageData?.originalWidth || 0;
       const originalHeight = imageData?.originalHeight || 0;
 
@@ -217,7 +231,6 @@ const submitBlog = async () => {
     }
   });
 
-
   // 2️⃣ 构造表单数据
   const formData = new FormData();
   formData.append("title", title);
@@ -225,20 +238,21 @@ const submitBlog = async () => {
   formData.append("allow_comments", allowComments);
   formData.append("draft", true);
 
-  images.forEach((imgData, index) => {
-    formData.append(`images[${index}]`, imgData.file); // 按索引存储图片
+  console.log(images);
+  images.forEach((imgData) => {
+    formData.append(`images`, imgData.file); // 按索引存储图片
   });
 
   console.log(Object.fromEntries(formData.entries()));
-  // 3️⃣ 发送请求
 
+  // 3️⃣ 发送请求
   api.post('/blogs', formData, {
     headers: {
       "Content-Type": "multipart/form-data",
     },
   }).then(response => {
     if (response.code === 0) {
-      swal.window('success', `提交成功, 博客id${response.blogId || '未找到blogId字段'}`);
+      swal.window('success', `提交成功, 博客id${response.blogId || '未找到blogId字段'}`, 'ok', '好的');
       return;
     }
     swal.window('error', '提交失败', `code字段为${response.code}; 错误信息: ${response.message}`, 'ok', '好的');
diff --git a/src/pages/Test_page.vue b/src/pages/Test_page.vue
index 9a77c14..77cc9f5 100644
--- a/src/pages/Test_page.vue
+++ b/src/pages/Test_page.vue
@@ -1,7 +1,12 @@
 <script setup>
-import {ref} from "vue";
+import {onMounted, ref} from "vue";
+import {blogImage} from "../utils/imageResource.js";
 
-const blogDisplay = ref('<h2>654654654</h2><ul><li><strong>砍砍价考核表计划表</strong></li></ul><ol><li><strong>1第三方代发</strong></li><li><strong>水电费水电费sd f收到f</strong></li><li><strong>收到f sd </strong></li><li><strong>11234</strong></li></ol>')
+const blogDisplay = ref(null);
+
+onMounted(() => {
+
+})
 </script>
 
 <template>
diff --git a/src/pages/Tools_home.vue b/src/pages/Tools_home.vue
index 686b51b..39994c6 100644
--- a/src/pages/Tools_home.vue
+++ b/src/pages/Tools_home.vue
@@ -13,6 +13,7 @@ const searchQuery = ref('');
 const tools = ref([
   { id: 1, title: 'GPA在线计算器', description: '手动输入在线算', image: null, category: ['计算'] },
   { id: 2, title: 'PDF页面提取器', description: '提取指定页和封面', image: null, category: ['提取', 'pdf'] },
+  { id: 3, title: '请求测试器', description: '自定义请求测试', image: null, category: ['开发', '网络'] },
 ]);
 
 onMounted(() => {
diff --git a/src/pages/blogPages/SingleBlog_page.vue b/src/pages/blogPages/SingleBlog_page.vue
new file mode 100644
index 0000000..6830045
--- /dev/null
+++ b/src/pages/blogPages/SingleBlog_page.vue
@@ -0,0 +1,24 @@
+<script setup>
+import {onMounted, ref} from "vue";
+
+const blogDisplay = ref(null);
+
+onMounted(() => {
+
+})
+</script>
+
+<template>
+  <div class="container">
+    <div v-html="blogDisplay"></div>
+  </div>
+</template>
+
+<style scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  width: 100%;
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/toolPages/RequestTester/requestTester_page.vue b/src/pages/toolPages/RequestTester/requestTester_page.vue
new file mode 100644
index 0000000..520d713
--- /dev/null
+++ b/src/pages/toolPages/RequestTester/requestTester_page.vue
@@ -0,0 +1,490 @@
+<template>
+  <div class="request-tester">
+    <!-- 左侧操作区 -->
+    <div class="operation-area">
+      <!-- URL 输入 -->
+      <div class="form-group">
+        <label>URL</label>
+        <input v-model="url" placeholder="请输入目标 URL" />
+      </div>
+
+      <!-- 请求方法选择 -->
+      <div class="form-group">
+        <label>请求方法</label>
+        <select v-model="method">
+          <option value="GET">GET</option>
+          <option value="POST">POST</option>
+          <option value="PUT">PUT</option>
+          <option value="DELETE">DELETE</option>
+        </select>
+      </div>
+
+      <!-- 请求头设置 -->
+      <div class="form-group">
+        <label>请求头</label>
+        <div v-for="(header, index) in headers" :key="index" class="key-value-pair">
+          <input v-model="header.key" placeholder="键" />
+          <input v-model="header.value" placeholder="值" />
+          <button @click="removeHeader(index)">删除</button>
+        </div>
+        <button @click="addHeader">添加请求头</button>
+      </div>
+
+      <!-- 请求体类型选择 -->
+      <div class="form-group" v-if="method !== 'GET' && method !== 'DELETE'">
+        <label>请求体类型</label>
+        <select v-model="bodyType">
+          <option value="json">JSON</option>
+          <option value="formdata">FormData</option>
+        </select>
+      </div>
+
+      <!-- 请求体设置:JSON -->
+      <div class="form-group" v-if="method !== 'GET' && method !== 'DELETE' && bodyType === 'json'">
+        <label>请求体 (JSON)</label>
+        <textarea v-model="body" placeholder="请输入 JSON 文本"></textarea>
+      </div>
+
+      <!-- 请求体设置:FormData -->
+      <div class="form-group" v-if="method !== 'GET' && method !== 'DELETE' && bodyType === 'formdata'">
+        <label>请求体 (FormData)</label>
+        <div v-for="(item, index) in formDataItems" :key="index" class="formdata-item">
+          <!-- 键名 -->
+          <input v-model="item.key" placeholder="键名" />
+
+          <!-- 类型选择 -->
+          <select v-model="item.type" @change="resetValue(item)">
+            <option value="pair">普通键值对</option>
+            <option value="array">数组</option>
+          </select>
+
+          <!-- 普通键值对输入 -->
+          <div v-if="item.type === 'pair'">
+            <input v-if="!item.isFile" v-model="item.value" placeholder="值" />
+            <input v-else type="file" @change="handleFileChange(item, $event)" />
+            <div style="margin: 10px 0"/>
+            <button @click="item.isFile = !item.isFile">
+              {{ item.isFile ? '切换为文本' : '切换为文件' }}
+            </button>
+          </div>
+
+          <!-- 数组输入 -->
+          <div v-if="item.type === 'array'">
+            <div v-for="(val, valIndex) in item.value" :key="valIndex" class="array-item">
+              <input v-if="!val.isFile" v-model="val.content" placeholder="数组值" />
+              <input v-else type="file" @change="handleArrayFileChange(item, valIndex, $event)" />
+              <button @click="val.isFile = !val.isFile">
+                {{ val.isFile ? '切换为文本' : '切换为文件' }}
+              </button>
+              <button @click="removeArrayItem(item, valIndex)">删除</button>
+            </div>
+            <button @click="addArrayItem(item)">添加数组项</button>
+          </div>
+
+          <!-- 删除键值对 -->
+          <button @click="removeFormDataItem(index)">删除键值对</button>
+        </div>
+        <button @click="addFormDataItem">添加键值对</button>
+      </div>
+
+      <!-- 查询参数设置 -->
+      <div class="form-group">
+        <label>查询参数</label>
+        <div v-for="(param, index) in params" :key="index" class="key-value-pair">
+          <input v-model="param.key" placeholder="键" />
+          <input v-model="param.value" placeholder="值" />
+          <button @click="removeParam(index)">删除</button>
+        </div>
+        <button @click="addParam">添加参数</button>
+      </div>
+
+      <!-- 操作按钮 -->
+      <div class="actions">
+        <button @click="sendRequest">发送请求</button>
+        <button @click="savePreset">保存预设</button>
+      </div>
+
+      <!-- 响应结果 -->
+      <div class="response" v-if="response">
+        <h3>响应结果</h3>
+        <pre>{{ response }}</pre>
+      </div>
+    </div>
+
+    <!-- 右侧储存栏 -->
+    <div class="storage-area">
+      <h3>储存栏</h3>
+      <div v-if="!requestTesterPresets || Object.keys(requestTesterPresets).length === 0">暂无预设</div>
+      <div v-else v-for="(preset, name) in requestTesterPresets" :key="name" class="preset-item">
+        <span @click="loadPreset(name)" class="preset-name">{{ name }}</span>
+        <button @click="deletePreset(name)">删除</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ref, computed } from 'vue';
+import { useStore } from 'vuex';
+import axios from 'axios';
+import Swal from 'sweetalert2';
+import swal from "../../../utils/sweetalert.js";
+
+export default {
+  name: 'RequestTester',
+  setup() {
+    const store = useStore();
+
+    // 数据定义
+    const url = ref('');
+    const method = ref('GET');
+    const headers = ref([{ key: '', value: '' }]);
+    const bodyType = ref('json'); // 请求体类型:json 或 formdata
+    const body = ref(''); // JSON 文本
+    const formDataItems = ref([]); // FormData 键值对
+    const params = ref([{ key: '', value: '' }]);
+    const response = ref(null);
+
+    // 从 Vuex 获取 requestTester 的预设
+    const requestTesterPresets = computed(() => store.state.demosLocal.requestTester || {});
+
+    // 请求头操作
+    const addHeader = () => headers.value.push({ key: '', value: '' });
+    const removeHeader = (index) => headers.value.splice(index, 1);
+
+    // 查询参数操作
+    const addParam = () => params.value.push({ key: '', value: '' });
+    const removeParam = (index) => params.value.splice(index, 1);
+
+    // FormData 键值对操作
+    const addFormDataItem = () => {
+      formDataItems.value.push({ key: '', type: 'pair', value: '', isFile: false });
+    };
+    const removeFormDataItem = (index) => {
+      formDataItems.value.splice(index, 1);
+    };
+    const resetValue = (item) => {
+      if (item.type === 'pair') {
+        item.value = '';
+        item.isFile = false;
+      } else if (item.type === 'array') {
+        item.value = [{ content: '', isFile: false }];
+      }
+    };
+    const addArrayItem = (item) => {
+      if (!item.value) item.value = [];
+      item.value.push({ content: '', isFile: false });
+    };
+    const removeArrayItem = (item, index) => {
+      item.value.splice(index, 1);
+    };
+    const handleFileChange = (item, event) => {
+      item.value = event.target.files[0];
+    };
+    const handleArrayFileChange = (item, index, event) => {
+      item.value[index].content = event.target.files[0];
+    };
+
+    // 发送请求
+    const sendRequest = async () => {
+      response.value = '等待响应...'; // 发送请求前显示“等待响应”
+
+      try {
+        let finalUrl = url.value;
+        const headersObj = headers.value.reduce((acc, h) => {
+          if (h.key && h.value) acc[h.key] = h.value;
+          return acc;
+        }, {});
+
+        // 处理查询参数
+        const queryParams = params.value
+            .filter((p) => p.key && p.value)
+            .reduce((acc, p) => {
+              acc[p.key] = p.value;
+              return acc;
+            }, {});
+
+        let axiosConfig = {
+          method: method.value,
+          url: finalUrl,
+          headers: headersObj,
+          params: queryParams,
+        };
+
+        // 处理请求体
+        if (method.value !== 'GET' && method.value !== 'DELETE') {
+          if (bodyType.value === 'json') {
+            axiosConfig.data = body.value ? JSON.parse(body.value) : {};
+            if (!headersObj['Content-Type']) {
+              axiosConfig.headers['Content-Type'] = 'application/json';
+            }
+          } else if (bodyType.value === 'formdata') {
+            const formData = new FormData();
+            formDataItems.value.forEach((item) => {
+              if (item.key) {
+                if (item.type === 'pair' && item.value) {
+                  formData.append(item.key, item.value);
+                } else if (item.type === 'array' && item.value.length) {
+                  item.value.forEach((val) => {
+                    if (val.content) {
+                      formData.append(item.key + '[]', val.content);
+                    }
+                  });
+                }
+              }
+            });
+            axiosConfig.data = formData;
+          }
+        }
+
+        const res = await axios(axiosConfig);
+        response.value = JSON.stringify(res.data, null, 2);
+      } catch (error) {
+        response.value = `错误: ${error.message}`;
+      }
+    };
+
+    // 保存预设
+    const savePreset = async () => {
+      const result = await Swal.fire({
+        title: '请输入预设名称',
+        input: 'text',
+        inputLabel: '预设名称',
+        inputPlaceholder: '请输入您的预设名称...',
+        showCancelButton: true,
+        cancelButtonText: '取消',
+        confirmButtonText: '确定',
+        inputValidator: (value) => !value && '预设名称不能为空!',
+      });
+
+      if (!result.isConfirmed) return;
+
+      const presetName = result.value;
+      const preset = {
+        url: url.value,
+        method: method.value,
+        headers: [...headers.value],
+        bodyType: bodyType.value,
+        body: body.value,
+        formDataItems: formDataItems.value.map((item) => ({
+          key: item.key,
+          type: item.type,
+          value: item.type === 'pair' ? (item.isFile ? 'File' : item.value) : item.value.map((v) => (v.isFile ? 'File' : v.content)),
+        })), // 仅保存文件名或文本
+        params: [...params.value],
+      };
+
+      store.commit('setLocalDemoValue', {
+        demo: 'requestTester',
+        value: { [presetName]: preset },
+      });
+    };
+
+    // 加载预设
+    const loadPreset = (name) => {
+      const preset = requestTesterPresets.value[name];
+      if (preset) {
+        url.value = preset.url;
+        method.value = preset.method;
+        headers.value = [...preset.headers];
+        bodyType.value = preset.bodyType;
+        body.value = preset.body;
+        formDataItems.value = preset.formDataItems.map((item) => ({
+          key: item.key,
+          type: item.type,
+          value: item.type === 'pair' ? '' : [], // 加载时重置文件和数组内容
+          isFile: item.type === 'pair' && item.value === 'File',
+        }));
+        params.value = [...preset.params];
+        response.value = null;
+      }
+    };
+
+    // 删除预设
+    const deletePreset = (name) => {
+      swal.window('info', '确定删除?', `删除预设"${name}"`, '确定', '取消').then(result => {
+        if (result.isConfirmed) {
+          store.commit('deleteLocalDemoValue', {
+            demo: 'requestTester',
+            value: name,
+          });
+        }
+      })
+    };
+
+    return {
+      url,
+      method,
+      headers,
+      bodyType,
+      body,
+      formDataItems,
+      params,
+      response,
+      requestTesterPresets,
+      addHeader,
+      removeHeader,
+      addParam,
+      removeParam,
+      addFormDataItem,
+      removeFormDataItem,
+      resetValue,
+      addArrayItem,
+      removeArrayItem,
+      handleFileChange,
+      handleArrayFileChange,
+      sendRequest,
+      savePreset,
+      loadPreset,
+      deletePreset,
+    };
+  },
+};
+</script>
+
+<style scoped>
+/* 默认深色模式 */
+:root {
+  --bg-color: #2c2c2c;
+  --text-color: #ffffff;
+  --input-bg: #3c3c3c;
+  --border-color: #555;
+  --button-bg: #4a90e2;
+  --button-hover: #357abd;
+}
+
+/* 浅色模式 */
+.theme-light {
+  --bg-color: #ffffff;
+  --text-color: #333333;
+  --input-bg: #f0f0f0;
+  --border-color: #ccc;
+  --button-bg: #4a90e2;
+  --button-hover: #357abd;
+}
+
+.request-tester {
+  display: flex;
+  gap: 20px;
+  max-width: 1200px;
+  width: 100%;
+  height: calc(100vh - 60px);
+  color: var(--text-color);
+}
+
+.operation-area {
+  flex: 1;
+  padding: 20px;
+  overflow-y: auto;
+}
+
+.storage-area {
+  width: 250px;
+  padding: 20px;
+  border-left: 1px solid var(--border-color);
+  overflow-y: auto;
+}
+
+.form-group {
+  margin-bottom: 15px;
+}
+
+.form-group label {
+  display: block;
+  margin-bottom: 5px;
+}
+
+input,
+select,
+textarea {
+  width: 100%;
+  padding: 8px;
+  background-color: var(--input-bg);
+  border: 1px solid var(--border-color);
+  color: var(--text-color);
+  border-radius: 4px;
+}
+
+textarea {
+  height: 100px;
+  resize: vertical;
+}
+
+.key-value-pair {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 10px;
+}
+
+.key-value-pair input {
+  flex: 1;
+}
+
+.formdata-item {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  margin-bottom: 20px;
+  border: 1px solid var(--border-color);
+  padding: 10px;
+  border-radius: 4px;
+}
+
+.formdata-item input {
+  width: calc(100% - 18px);
+}
+
+.array-item {
+  display: flex;
+  gap: 10px;
+  margin-top: 5px;
+}
+
+button {
+  padding: 6px 12px;
+  background-color: var(--button-bg);
+  color: #fff;
+  border: gray solid 1px;
+  border-radius: 4px;
+  cursor: pointer;
+}
+.theme-light button {
+  color: black;
+}
+
+button:hover {
+  background-color: var(--button-hover);
+}
+
+.actions {
+  margin-top: 20px;
+  display: flex;
+  gap: 10px;
+}
+
+.response {
+  margin-top: 20px;
+}
+
+.response pre {
+  background-color: var(--input-bg);
+  padding: 10px;
+  border-radius: 4px;
+}
+
+.preset-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 5px 0;
+}
+
+.preset-name {
+  cursor: pointer;
+  color: var(--button-bg);
+}
+
+.preset-name:hover {
+  text-decoration: underline;
+}
+</style>
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
index 5dff1d4..cc26c64 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -4,6 +4,7 @@ import AuthService from "../../services/auth.js";
 import Home from '../pages/Home.vue';
 import Login from "../pages/Login.vue";
 import Blog_home from "../pages/Blog_home.vue";
+
 import Account from "../pages/accountPages/Account.vue";
 import Account_selfpage from "../pages/accountPages/Account_selfpage.vue";
 import Account_worksmanage from "../pages/accountPages/Account_worksmanage.vue";
@@ -12,14 +13,19 @@ import Account_draft from "../pages/accountPages/Account_draft.vue";
 import Account_userInfo from "../pages/accountPages/Account_userInfo.vue";
 import Account_admin_uploadLog from "../pages/accountPages/Account_admin_uploadLog.vue";
 import Account_admin_userManage from "../pages/accountPages/Account_admin_userManage.vue";
+
 import Projects from "../pages/Projects_home.vue";
+
 import Demos_home from "../pages/Demos_home.vue";
 import Board_page from "../pages/demoPages/messageBoard/Board_page.vue";
 import Pod_page from "../pages/demoPages/podExercise/Pod_page.vue";
 import Pod_quiz from "../pages/demoPages/podExercise/Quiz.vue";
+
 import Tools_home from "../pages/Tools_home.vue";
 import GpaCalculator_page from "../pages/toolPages/gpaCalculator/gpaCalculator_page.vue";
 import PdfEx_page from "../pages/toolPages/pdfExtractor/pdfEx_page.vue";
+import RequestTester_page from "../pages/toolPages/RequestTester/requestTester_page.vue";
+
 import About from "../pages/About.vue";
 import Editor from "../pages/Editor.vue";
 import NotFound from "../pages/errorPages/notFound.vue";
@@ -81,6 +87,8 @@ const routes = [
             {path: "gpa", component: GpaCalculator_page},
             {path: "2", component: PdfEx_page},
             {path: "pdf-extractor", component: PdfEx_page},
+            {path: "3", component: RequestTester_page},
+            {path: "request-tester", component: RequestTester_page},
         ]
     }, {
         path: '/about',
diff --git a/src/store/index.js b/src/store/index.js
index df81829..862e090 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -38,6 +38,9 @@ const store = createStore({
         setLocalDemoValue(state, obj) {
             state.demosLocal[obj.demo] = {...state.demosLocal[obj.demo], ...obj.value}
         },
+        deleteLocalDemoValue(state, obj) {
+            delete state.demosLocal[obj.demo][obj.value];
+        },
         saveEdit(state, obj) {
             state.editStore = {...state.editStore, ...obj};
         },
diff --git a/src/utils/imageResource.js b/src/utils/imageResource.js
new file mode 100644
index 0000000..36f8184
--- /dev/null
+++ b/src/utils/imageResource.js
@@ -0,0 +1,5 @@
+import {getDomain} from "./getDomain.js";
+
+export function blogImage(id) {
+    return `https://${getDomain()}/data/blog/images/${id}`;
+}
\ No newline at end of file