表单相关:序列化、状态持久化、复杂数据处理(附:Object.fromEntries()方法 函数式对象 解释)

表单序列化


在 JavaScript 中,表单数据的序列化(Form Serialization)将 HTML 表单中的数据转换为可传输的格式(如 URL 查询字符串、JSON 等),是 Web 开发中非常常见且重要的操作。

下面以清晰结构说明:


✅ 一、什么是“表单序列化”?

表单序列化(Form Serialization):将 HTML 表单中所有成功控件(successful controls)的 name=value 对,转换为一种便于发送到服务器的格式(如查询字符串或 JSON 对象)。

成功控件包括

  • 有 name 属性的 <input><select><textarea>
  • 未被禁用(disabled)的控件
  • 被选中的复选框/单选按钮
  • 文件上传控件(需特殊处理)

✅ 二、JavaScript 中常见的表单序列化方式(表格对比)

方法代码示例输出格式特点兼容性
1. FormData 对象(推荐)
const form = document.querySelector('form');

const fd = new FormData(form);

FormData 实例(键值对,支持文件)- 自动处理所有表单控件
- 支持文件上传
- 可直接用于 fetch 或 XMLHttpRequest
- 值为 File 或 string
✅ 现代浏览器(IE10+)
2. 序列化为 URL 查询字符串
const params = new URLSearchParams(new FormData(form));
console.log(params.toString()); //"name=Alice&age=30"

application/x-www-form-urlencoded 字符串- 适合 AJAX 发送或拼接到 URL
- 不支持文件
✅ 现代浏览器
3. 手动转为普通对象(Object)
const obj = Object.fromEntries(new FormData(form));

{ name: "Alice", age: "30" }- 方便转 JSON
- 多个同名字段会被覆盖(如复选框)
✅ 需 Object.fromEntries()(ES2019,现代浏览器)
4. 使用 jQuery .serialize()(传统)
$('form').serialize(); //"name=Alice&age=30"

查询字符串- 老项目常用
- 不支持文件
⚠️ 依赖 jQuery
5. 手动遍历 elements
const data = {};
for (let el of form.elements) {
  if (el.name && !el.disabled) data[el.name] = el.value;
}

自定义对象- 灵活但易出错
- 需处理复选框、多选等特殊情况
✅ 全浏览器

✅ 三、详细说明与示例

1. 使用 FormData(最强大)

<form id="myForm">
  <input name="username" value="alice">
  <input name="hobby" type="checkbox" value="reading" checked>
  <input name="hobby" type="checkbox" value="coding" checked>
  <input name="avatar" type="file">
</form>
const form = document.getElementById('myForm');
const fd = new FormData(form);

// 获取所有值(注意:同名字段会保留多个)
for (let [key, value] of fd.entries()) {
  console.log(key, value); 
  // username alice
  // hobby reading
  // hobby coding
  // avatar [object File](如果选择了文件)
}

// 直接用于 fetch
fetch('/api', { method: 'POST', body: fd });

FormData 自动处理:

  • 复选框/多选下拉(保留多个值)
  • 文件上传
  • 编码(UTF-8)

2. 转为查询字符串(用于 GET 请求或日志)

const queryString = new URLSearchParams(new FormData(form)).toString();
// 结果: "username=alice&hobby=reading&hobby=coding"

⚠️ 注意:文件无法转为字符串,会被忽略或报错。


3. 转为普通 JavaScript 对象(适合 JSON API)

// ⚠️ 警告:同名字段会被覆盖!
const obj = Object.fromEntries(new FormData(form));
// { username: "alice", hobby: "coding" } ← "reading" 丢失!

// 安全方式:手动聚合数组
const safeObj = {};
for (let [key, value] of new FormData(form)) {
  if (safeObj[key]) {
    safeObj[key] = Array.isArray(safeObj[key]) ? [...safeObj[key], value] : [safeObj[key], value];
  } else {
    safeObj[key] = value;
  }
}

✅ 四、常见误区

误区正确理解
FormData 可以直接 JSON.stringify()❌ FormData 不是普通对象,需先转为 entries
“表单序列化会包含 disabled 字段”❌ disabled 控件不会被包含
<input type="button"> 会被序列化”❌ 只有序列化“成功控件”(submit/reset/button 不包含)

✅ 五、总结:如何选择?

场景推荐方式
提交完整表单(含文件)new FormData(form) + fetch
发送纯数据到 REST API(JSON)Object.fromEntries(FormData) + 处理多值
构造 URL 查询参数(GET)URLSearchParams(new FormData(form))
老项目兼容 jQuery$(form).serialize()

表单状态持久化到 localStorage


Qwen3-Max

表单状态持久化到 localStorage 是一种常见的前端优化技术,用于在用户刷新页面、意外关闭或返回时自动恢复已填写的内容,提升用户体验(例如注册/问卷中途离开后回来仍保留输入)。

下面以清晰结构说明实现方法、注意事项和最佳实践。


✅ 一、核心思路

  1. 监听表单变化(如 inputchange 事件)
  2. 将当前表单数据序列化(转为 JSON 字符串)
  3. 存入 localStorage
  4. 页面加载时读取并回填表单

✅ 二、完整实现示例(原生 JavaScript)

<form id="myForm">
  <input name="username" placeholder="用户名">
  <input name="email" type="email" placeholder="邮箱">
  <textarea name="bio" placeholder="个人简介"></textarea>
  <label><input type="checkbox" name="subscribe" value="1"> 订阅 newsletter</label>
  <button type="reset">清空</button>
</form>

<script>
  const form = document.getElementById('myForm');
  const STORAGE_KEY = 'form-state-myForm';

  // 1. 页面加载时恢复表单
  function restoreForm() {
    const saved = localStorage.getItem(STORAGE_KEY);
    if (saved) {
      const data = JSON.parse(saved);
      Object.entries(data).forEach(([name, value]) => {
        const field = form.elements[name];
        if (field) {
          if (field.type === 'checkbox') {
            field.checked = value === 'true'; // 注意:localStorage 存的是字符串
          } else if (field.type === 'radio') {
            if (field.value === value) field.checked = true;
          } else {
            field.value = value;
          }
        }
      });
    }
  }

  // 2. 保存表单状态
  function saveForm() {
    const formData = new FormData(form);
    const data = {};
    for (let [name, value] of formData.entries()) {
      // 复选框/单选按钮:存储 checked 状态而非值
      const field = form.elements[name];
      if (field.type === 'checkbox' || field.type === 'radio') {
        data[name] = field.checked.toString();
      } else {
        data[name] = value;
      }
    }
    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
  }

  // 3. 绑定事件
  form.addEventListener('input', saveForm);
  form.addEventListener('change', saveForm); // 覆盖 select、checkbox 等
  window.addEventListener('pageshow', restoreForm); // 包括从缓存返回(如后退按钮)
</script>

优点:支持文本、多行文本、复选框、单选按钮、下拉框等常见控件。


✅ 三、关键细节处理

问题解决方案
复选框/单选按钮状态丢失不要直接用 FormData 的值,而应存储 .checked 布尔值(转为字符串)
多个同名字段(如 hobby[])需特殊处理:存储为数组 ["reading", "coding"]
敏感信息(密码、银行卡)❌ 不要保存!可通过 data-persist="false" 标记跳过
localStorage 容量限制≤5MB(不同浏览器),避免存大文件或 Base64 图片
隐私模式/无痕浏览localStorage 可能不可用,需 try-catch

🔒 跳过敏感字段示例:

// 保存时过滤
for (let el of form.elements) {
  if (el.name && !el.dataset.persist === 'false') {
    // 保存逻辑
  }
}
<input name="password" type="password" data-persist="false">

✅ 四、高级优化建议

优化点说明
防抖保存避免频繁写入(如每 500ms 保存一次)
设置过期时间存储时加时间戳,超过 7 天自动清除
按用户隔离若有登录态,用 userId + formId 作为 key
使用 sessionStorage仅需会话级持久化(关闭标签页即清除)
结合 beforeunload提醒用户“离开将丢失未保存内容”

示例:带过期机制

const TTL = 7 * 24 * 60 * 60 * 1000; // 7天

function saveForm() {
  const payload = {
    data: {/* 表单数据 */},
    timestamp: Date.now()
  };
  localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
}

function restoreForm() {
  const saved = localStorage.getItem(STORAGE_KEY);
  if (saved) {
    const { data, timestamp } = JSON.parse(saved);
    if (Date.now() - timestamp < TTL) {
      // 回填逻辑
    } else {
      localStorage.removeItem(STORAGE_KEY); // 过期清理
    }
  }
}

✅ 五、为什么不直接用 FormData + JSON.stringify

  • FormData 无法区分未选中的复选框(根本不会出现在 entries 中)
  • 多个同名字段会被覆盖(如多个 hobby 复选框)
  • 无法存储布尔状态(checked/unchecked)

✅ 正确做法:遍历 form.elements,按控件类型分别处理


✅ 六、替代方案对比

方案优点缺点
localStorage持久化、简单容量小、同步阻塞、不安全(明文)
sessionStorage自动清理关闭标签页即丢失
IndexedDB容量大、异步API 复杂
Cookie可随请求发送容量极小(4KB)、影响性能
服务端保存草稿安全、跨设备需后端支持

💡 对于简单表单,localStorage 是最轻量高效的选择。


七、总结:最佳实践清单

  • ✅ 只保存非敏感字段
  • ✅ 正确处理 checkbox/radio/select
  • ✅ 使用 pageshow 而非 load 事件恢复(兼容后退缓存)
  • ✅ 添加过期机制避免陈旧数据
  • ✅ 提供“清空草稿”按钮(调用 localStorage.removeItem
  • ❌ 不要保存密码、身份证、支付信息等

如何处理表单中的复杂数据结构?


Qwen3-Max

在 Web 开发中,表单中的“复杂数据结构” 通常指以下情况:

  • 多层级嵌套字段(如 user.profile.name
  • 动态字段(如可增删的地址列表、商品项)
  • 数组型输入(如多个邮箱、标签)
  • 带关联关系的数据(如订单 + 商品明细)
  • 非字符串值(如日期对象、布尔值、数字)

由于 HTML 表单原生只支持 name=value 的扁平键值对(且 value 总是字符串),要处理复杂结构,需通过 命名约定 + 序列化/反序列化逻辑 来实现。


✅ 一、核心策略:使用命名规范编码结构

1. 点号表示法(Dot Notation)

<input name="user.name" value="Alice">
<input name="user.age" value="30">
<input name="user.hobbies[0]" value="reading">
<input name="user.hobbies[1]" value="coding">

2. 方括号表示法(PHP 风格)

<input name="user[name]" value="Alice">
<input name="user[age]" value="30">
<input name="user[hobbies][]" value="reading">
<input name="user[hobbies][]" value="coding">

💡 浏览器本身不解析这些名称——解析逻辑需由 JavaScript 或后端实现


✅ 二、JavaScript 解析复杂表单数据(前端序列化)

方法:遍历 form.elements,按命名规则重建对象

function parseFormData(form) {
  const data = {};

  // 辅助函数:将扁平 key(如 "user.name")转为嵌套路径
  function setNestedValue(obj, key, value) {
    const keys = key.replace(/\[(\d+)\]/g, '.$1').split('.'); // 将 [0] 转为 .0
    let current = obj;
    for (let i = 0; i < keys.length - 1; i++) {
      const k = keys[i];
      if (!current[k]) {
        // 判断下一个是否为数字 → 应该是数组
        const next = keys[i + 1];
        current[k] = /^\d+$/.test(next) ? [] : {};
      }
      current = current[k];
    }
    const lastKey = keys[keys.length - 1];
    if (Array.isArray(current)) {
      current.push(value);
    } else {
      current[lastKey] = value;
    }
  }

  // 收集所有成功控件
  for (const field of form.elements) {
    if (!field.name || field.disabled) continue;

    let value;
    if (field.type === 'checkbox' || field.type === 'radio') {
      if (!field.checked) continue;
      value = field.value === 'on' ? true : field.value; // 可自定义
    } else if (field.type === 'select-multiple') {
      value = Array.from(field.selectedOptions).map(opt => opt.value);
    } else {
      value = field.value;
    }

    // 类型转换(可选)
    if (typeof value === 'string') {
      if (/^\d+$/.test(value)) value = Number(value);
      else if (value === 'true') value = true;
      else if (value === 'false') value = false;
    }

    setNestedValue(data, field.name, value);
  }

  return data;
}

使用示例:

<form id="myForm">
  <input name="user.name" value="Alice">
  <input name="user.age" value="30">
  <input name="user.isActive" value="true">
  <input name="user.tags[]" value="dev">
  <input name="user.tags[]" value="js">
  <input name="addresses[0].city" value="Beijing">
  <input name="addresses[1].city" value="Shanghai">
</form>
const form = document.getElementById('myForm');
const result = parseFormData(form);
console.log(result);
/*
{
  user: {
    name: "Alice",
    age: 30,
    isActive: true,
    tags: ["dev", "js"]
  },
  addresses: [
    { city: "Beijing" },
    { city: "Shanghai" }
  ]
}
*/

✅ 三、反向操作:从复杂对象回填表单(反序列化)

function populateForm(form, data, prefix = '') {
  for (const [key, value] of Object.entries(data)) {
    const fullKey = prefix ? `${prefix}.${key}` : key;

    if (value && typeof value === 'object' && !Array.isArray(value)) {
      // 递归处理嵌套对象
      populateForm(form, value, fullKey);
    } else if (Array.isArray(value)) {
      // 处理数组:动态生成字段或匹配现有字段
      value.forEach((item, index) => {
        const arrayKey = `${fullKey}[${index}]`;
        if (typeof item === 'object') {
          populateForm(form, item, arrayKey);
        } else {
          setFieldValue(form, arrayKey, item);
        }
      });
    } else {
      setFieldValue(form, fullKey, value);
    }
  }
}

function setFieldValue(form, name, value) {
  const field = form.elements.namedItem(name);
  if (!field) return;

  if (field.type === 'checkbox' || field.type === 'radio') {
    field.checked = value === true || value === field.value;
  } else if (field.tagName === 'SELECT' && field.multiple) {
    // 多选下拉
    for (const option of field.options) {
      option.selected = value.includes(option.value);
    }
  } else {
    field.value = String(value);
  }
}

⚠️ 注意:动态表单(如可增删行)需先确保 DOM 结构存在,或结合模板引擎生成。


✅ 四、常见复杂场景处理方案

场景推荐方案
动态增删字段(如“添加更多地址”)- 用模板(<template>)克隆
- 为每个新字段分配唯一 name(如 addresses[2].city
文件上传 + 元数据- 文件单独用 FormData
- 元数据用 JSON 字段存储(如 files[0].description
富文本编辑器- 监听编辑器 change 事件
- 将内容同步到隐藏 <input type="hidden">
日期/时间选择器- 存储 ISO 字符串(如 "2025-12-01"
- 回填时转为 Date 对象或格式化字符串
关联选择(级联下拉)- 用 data-* 属性存储层级关系
- 不依赖 name 嵌套,而是用独立字段

✅ 五、与后端协同建议

  1. 统一命名规范
    前后端约定使用 user[name]user.name,避免解析不一致。

  2. 后端自动解析

    • Express.js:可用 body-parser + qs 库解析 user[name]=Alice
    • PHP:原生支持 user[name] 格式
    • Python(Django/Flask):需手动解析或使用库(如 webargs
  3. API 优先方案
    对于 SPA,可直接提交 JSON:

    fetch('/api/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(parseFormData(form))
    });

✅ 六、现成工具推荐

工具用途
FormData原生支持文件 + 键值对,但仍是扁平结构
qs强大的查询字符串解析/序列化(支持嵌套)
form-serialize轻量级表单序列化库
React Hook Form / Vue FormKit框架级表单管理,内置复杂数据支持

✅ 总结

HTML 表单是扁平的,但你的数据可以是复杂的——关键在于:

  1. 设计合理的 name 命名规则
  2. 编写健壮的序列化/反序列化逻辑
  3. 前后端保持解析一致性

Object.fromEntries()方法详解


Qwen3-Max

Object.fromEntries() 是 ECMAScript 2019(ES10)引入的一个静态方法,用于将可迭代的键值对集合(如 MapArray 等)转换为普通对象(Plain Object)。它是 Object.entries()逆操作


✅ 一、基本语法

Object.fromEntries(iterable)
  • 参数iterable
    一个可迭代对象,其每个元素必须是一个包含两个元素的数组(即 [key, value] 形式)。
  • 返回值:一个新的普通对象,其属性由 iterable 中的键值对定义。

✅ 二、核心用途与示例

1. 将 Map 转换为普通对象

const map = new Map([
  ['name', 'Alice'],
  ['age', 30],
  ['city', 'Beijing']
]);

const obj = Object.fromEntries(map);
console.log(obj); 
// { name: "Alice", age: 30, city: "Beijing" }

💡 这是最常见用法:当你从 fetchFormData 或其他 API 得到 Map 时,快速转为对象以便访问属性(如 obj.name)。


2. 将键值对数组转为对象

const entries = [
  ['a', 1],
  ['b', 2],
  ['c', 3]
];

const obj = Object.fromEntries(entries);
console.log(obj); // { a: 1, b: 2, c: 3 }

3. 与 Object.entries() 配合:过滤/转换对象属性

const original = { a: 1, b: 2, c: 3, d: 4 };

// 过滤出值大于 2 的属性
const filtered = Object.fromEntries(
  Object.entries(original).filter(([key, val]) => val > 2)
);
console.log(filtered); // { c: 3, d: 4 }

// 将所有值转为字符串
const stringified = Object.fromEntries(
  Object.entries(original).map(([k, v]) => [k, String(v)])
);
console.log(stringified); // { a: "1", b: "2", c: "3", d: "4" }

✅ 这是函数式编程中修改对象的常用模式(因为对象本身不可变)。


4. 从 FormData 创建对象(常用于表单处理)

const form = document.querySelector('form');
const formData = new FormData(form);

// 转为普通对象(注意:同名字段会被覆盖!)
const obj = Object.fromEntries(formData);
console.log(obj); // { username: "alice", email: "a@example.com" }

⚠️ 警告:如果表单有多个同名字段(如复选框),只有最后一个值会被保留。需手动处理多值:

const obj = {};
for (let [key, value] of formData) {
  obj[key] = obj[key] ? [...(Array.isArray(obj[key]) ? obj[key] : [obj[key]]), value] : value;
}

✅ 三、支持的可迭代对象类型

可迭代源示例是否有效
Mapnew Map([['k','v']])
Array[['k','v']]
Setnew Set([['k','v']])✅(但 Set 元素必须是 [key, value] 数组)
String'ab' → [['a','b']]❌(每个字符是单个元素,不是 [k,v] 对)
自定义迭代器实现 Symbol.iterator 返回 [k,v]

✅ 四、注意事项与限制

1. 键必须是字符串或 Symbol

  • 如果键是其他类型(如数字、对象),会被强制转换为字符串
    Object.fromEntries([[1, 'one'], [true, 'yes']]);
    // { "1": "one", "true": "yes" }

2. 重复键会被覆盖

Object.fromEntries([['a', 1], ['a', 2]]);
// { a: 2 } ← 后出现的值覆盖前面的

3. 不能处理非 [key, value] 结构

Object.fromEntries([['a'], ['b', 1, 2]]); 
// { a: undefined, b: 1 } ← 多余元素被忽略,缺失值为 undefined

4. 不适用于深层嵌套转换

  • 它只做一层扁平转换,不会递归处理嵌套结构。

✅ 五、浏览器兼容性

浏览器支持版本
Chrome73+
Firefox63+
Safari12.1+
Edge79+
Node.js12.0+

💡 对于旧环境,可使用 polyfill(基于 Array.reduce):

if (!Object.fromEntries) {
  Object.fromEntries = function(iterable) {
    return Array.from(iterable).reduce((obj, [key, val]) => {
      obj[key] = val;
      return obj;
    }, {});
  };
}

✅ 六、对比其他转换方式

方法适用场景优点缺点
Object.fromEntries(map)Map → 对象简洁、标准仅 ES2019+
{...map}Map → 对象语法糖无效!展开运算符对 Map 不生成对象属性
Array.reduce通用兼容性好代码冗长
手动循环精细控制灵活易出错

❌ 常见错误:

const obj = { ...myMap }; // 得到 { "size": 2 } ❌(不是你想要的!)

✅ 七、典型应用场景总结

场景代码片段
表单数据转对象Object.fromEntries(new FormData(form))
过滤对象属性Object.fromEntries(Object.entries(obj).filter(...))
Map 转 JSONJSON.stringify(Object.fromEntries(map))
URLSearchParams 转对象Object.fromEntries(new URLSearchParams(location.search))
转换计算属性Object.fromEntries(keys.map(k => [k, compute(k)]))

✅ 总结

Object.fromEntries() 是现代 JavaScript 中连接“可迭代键值对”与“普通对象”的桥梁。
它简洁、语义清晰,特别适合与 MapFormDataURLSearchParams 等 API 协同工作,是函数式对象操作的重要工具。

合理使用它,可以大幅减少手动循环和临时变量,写出更声明式、更易读的代码。


函数式对象是什么意思


Qwen3-Max

函数式对象”并不是 JavaScript 中一个严格定义的术语,但它通常出现在讨论 函数式编程(Functional Programming)风格操作对象 的上下文中。它的含义可以从两个角度理解:


一、主流理解:以函数式风格操作/转换的对象

这是最常见的用法——指通过纯函数、不可变性、高阶函数等方式来创建或处理的对象,而不是通过命令式修改。

🌰 例子对比:

命令式(非函数式)函数式风格
const obj = { a: 1, b: 2 };
obj.c = 3; // 直接修改原对象

const obj = { a: 1, b: 2 };
const newObj = { ...obj, c: 3 }; // 创建新对象

delete obj.b;

const { b, ...newObj } = obj; // 解构排除

“函数式对象操作”强调

  • 不可变性(不修改原对象)
  • 使用纯函数(如 mapfilterreduce
  • 链式/声明式表达

🔧 典型函数式对象操作工具:

// 1. 过滤属性
const filtered = Object.fromEntries(
  Object.entries(obj).filter(([k, v]) => v > 0)
);

// 2. 映射值
const doubled = Object.fromEntries(
  Object.entries(obj).map(([k, v]) => [k, v * 2])
);

// 3. 合并对象(无副作用)
const merged = { ...obj1, ...obj2 };

💡 这里的 “函数式对象” 指的是 用函数式方法生成或变换的对象,而非对象本身是“函数”。


❌ 二、可能的误解:对象本身就是函数?

在 JavaScript 中,函数也是对象typeof func === 'function',但 func instanceof Objecttrue),所以 technically 所有函数都是“可调用的对象”。

例如:

function greet() { return "Hi"; }
greet.name = "Alice"; // 函数可以有属性!
console.log(greet());     // "Hi"
console.log(greet.name);  // "Alice"

但这通常不叫“函数式对象”,而是称为:

  • 可调用对象(Callable Object)
  • 带属性的函数
  • 函数与对象的混合体

⚠️ 注意:这不是“函数式编程”中的概念,而是 JavaScript 语言特性。


✅ 三、函数式编程中“对象”的正确使用原则

在函数式编程范式中,对对象的处理遵循以下规则:

原则说明
不可变性(Immutability)永远不修改原对象,总是返回新对象
纯函数(Pure Functions)对象转换函数无副作用、相同输入必得相同输出
避免状态共享对象不包含隐藏状态或可变引用
使用代数数据类型(ADT)思想用普通对象表示结构化数据(类似 record)

✅ 函数式风格的对象工厂:

// 纯函数创建对象
const createUser = (name, age) => ({ name, age, active: true });

// 纯函数更新对象
const activateUser = (user) => ({ ...user, active: true });

✅ 四、常见误区澄清

说法正误说明
“函数式对象 = 对象里存了函数”这只是普通对象的用法(如 { fn: () => {} }
“函数式对象 = 用函数式方法处理的对象”正确理解
“React 的 state 对象必须是函数式更新”✅(部分)指使用 setState(prev => ({...})) 避免闭包问题,属于函数式思想应用

✅ 五、总结

“函数式对象” ≈ “以函数式编程原则创建或操作的对象”
它的核心是:

  • 不可变
  • 由纯函数生成或变换
  • 避免直接赋值/删除属性

它不是一种特殊对象类型,而是一种编程风格和数据处理方式


📌 记忆口诀:

“对象不变,函数来变;旧的不去,新的不来。”

如果你看到文档或教程中提到“函数式对象”,大概率是指 Object.fromEntries、解构、展开运算符等无副作用方式处理的对象,而不是某种神秘的新数据类型 😊

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端开发_穆金秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值