封装localstorage

使用localStorage本地存储进行二次封装 对localStorage进行二次封装,优化性能,减少请求。这里使用最新的ts脚本语言进行封装,当然js代码也可以使用,只需将类型限制去除即可! 阅读详情

很多人在用 localStorage 或 sessionStorage 的时候喜欢直接用,明文存储,直接将信息暴露在;浏览器中,虽然一般场景下都能应付得了且简单粗暴,但特殊需求情况下,比如设置定时功能,就不能实现。就需要对其进行二次封装,为了在使用上增加些安全感,那加密也必然是少不了的了。为方便项目使用,特对常规操作进行封装。

设计

封装之前先梳理下所需功能,并要做成什么样,采用什么样的规范,部分主要代码片段是以 localStorage作为示例,最后会贴出完整代码的。可以结合项目自行优化,也可以直接使用。

// 区分存储类型 type
// 自定义名称前缀 prefix
// 支持设置过期时间 expire
// 支持加密可选,开发环境下未方便调试可关闭加密

// 支持数据加密 这里采用 crypto-js 加密 也可使用其他方式

// 判断是否支持 Storage isSupportStorage

// 设置 setStorage

// 获取 getStorage

// 是否存在 hasStorage

// 获取所有key getStorageKeys

// 根据索引获取key getStorageForIndex

// 获取localStorage长度 getStorageLength

// 获取全部 getAllStorage

// 删除 removeStorage

// 清空 clearStorage

//定义参数 类型 window.localStorage,window.sessionStorage,
const config = {
    type: 'localStorage', // 本地存储类型 localStorage/sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}

设置 setStorage

Storage 本身是不支持过期时间设置的,要支持设置过期时间,可以效仿 Cookie 的做法,setStorage(key,value,expire) 方法,接收三个参数,第三个参数就是设置过期时间的,用相对时间,单位秒,要对所传参数进行类型检查。可以设置统一的过期时间,也可以对单个值得过期时间进行单独配置。两种方式按需配置。

代码实现:

// 设置 setStorage
export const setStorage = (key,value,expire=0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }

    if (isNaN(expire) || expire < 1) throw new Error("Expire must be a number");

    expire = (expire?expire:config.expire) * 60000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }

    window[config.type].setItem(key, JSON.stringify(data));
}

获取 getStorage

首先要对 key 是否存在进行判断,防止获取不存在的值而报错。对获取方法进一步扩展,只要在有效期内获取 Storage 值,就对过期时间进行续期,如果过期则直接删除该值。并返回 null

// 获取 getStorage
export const getStorage = (key) => {
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null'){
        return null;
    }

    // 优化 持续使用中续期
    const storage = JSON.parse(window[config.type].getItem(key));
    console.log(storage)
    let nowTime = Date.now();
    console.log(config.expire*6000 ,(nowTime - storage.time))
    // 过期删除
    if (storage.expire && config.expire*6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        // 未过期期间被调用 则自动续期 进行保活
        setStorage(key,storage.value);
        return storage.value;
    }
}

获取所有值

// 获取全部 getAllStorage
export const getAllStorage = () => {
    let len = window[config.type].length // 获取长度
    let arr = new Array() // 定义数据集
    for (let i = 0; i < len; i++) {
        // 获取key 索引从0开始
        let getKey = window[config.type].key(i)
        // 获取key对应的值
        let getVal = window[config.type].getItem(getKey)
        // 放进数组
        arr[i] = { 'key': getKey, 'val': getVal, }
    }
    return arr
}

删除 removeStorage

// 名称前自动添加前缀
const autoAddPrefix = (key) => {
    const prefix = config.prefix ? config.prefix + '_' : '';
    return  prefix + key;
}

// 删除 removeStorage
export const removeStorage = (key) => {
    window[config.type].removeItem(autoAddPrefix(key));
}

清空 clearStorage

// 清空 clearStorage
export const clearStorage = () => {
    window[config.type].clear();
}

加密、解密

加密采用的是 crypto-js

// 安装crypto-js
npm install crypto-js

// 引入 crypto-js 有以下两种方式
import CryptoJS from "crypto-js";
// 或者
const CryptoJS = require("crypto-js");

对 crypto-js 设置密钥和密钥偏移量,可以采用将一个私钥经 MD5 加密生成16位密钥获得。

// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");

对加密方法进行封装

/**
 * 加密方法
 * @param data
 * @returns {string}
 */
export function encrypt(data) {
  if (typeof data === "object") {
    try {
      data = JSON.stringify(data);
    } catch (error) {
      console.log("encrypt error:", error);
    }
  }
  const dataHex = CryptoJS.enc.Utf8.parse(data);
  const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
    iv: SECRET_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.ciphertext.toString();
}

对解密方法进行封装

/**
 * 解密方法
 * @param data
 * @returns {string}
 */
export function decrypt(data) {
  const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
  const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
  const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
    iv: SECRET_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
  return decryptedStr.toString();
}

在存储数据及获取数据中进行使用:

这里我们主要看下进行加密和解密部分,部分方法在下面代码段中并未展示,请注意,不能直接运行。

const config = {
    type: 'localStorage', // 本地存储类型 sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}

// 设置 setStorage
export const setStorage = (key, value, expire = 0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }

    if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");

    expire = (expire ? expire : config.expire) * 1000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }
    // 对存储数据进行加密 加密为可选配置
    const encryptString = config.isEncrypt ? encrypt(JSON.stringify(data)): JSON.stringify(data);
    window[config.type].setItem(autoAddPrefix(key), encryptString);
}

// 获取 getStorage
export const getStorage = (key) => {
    key = autoAddPrefix(key);
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
        return null;
    }

    // 对存储数据进行解密
    const storage = config.isEncrypt ? JSON.parse(decrypt(window[config.type].getItem(key))) : JSON.parse(window[config.type].getItem(key));
    let nowTime = Date.now();
    
    // 过期删除
    if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        //  持续使用时会自动续期
        setStorage(autoRemovePrefix(key), storage.value);
        return storage.value;
    }
}

使用

使用的时候你可以通过 import 按需引入,也可以挂载到全局上使用,一般建议少用全局方式或全局变量,为后来接手项目继续开发维护的人,追查代码留条便捷之路!不要为了封装而封装,尽可能基于项目需求和后续的通用,以及使用上的便捷。比如获取全部存储变量,如果你项目上都未曾用到过,倒不如删减掉,留着过年也不见得有多香,不如为减小体积做点贡献!

import {isSupportStorage, hasStorage, setStorage,getStorage,getStorageKeys,getStorageForIndex,getStorageLength,removeStorage,getStorageAll,clearStorage} from '@/utils/storage'

完整代码

该代码已进一步完善,需要的可以直接进一步优化,也可以将可优化或可扩展的建议,留言说明,我会进一步迭代的。可以根据自己的需要删除一些不用的方法,以减小文件大小。

/***
 * title: storage.js
 * Author: Gaby
 * Email: xxx@126.com
 * Time: 2022/6/1 17:30
 * last: 2022/6/2 17:30
 * Desc: 对存储的简单封装
 */
 
import CryptoJS from 'crypto-js';

// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");

// 类型 window.localStorage,window.sessionStorage,
const config = {
    type: 'localStorage', // 本地存储类型 sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}

// 判断是否支持 Storage
export const isSupportStorage = () => {
    return (typeof (Storage) !== "undefined") ? true : false
}

// 设置 setStorage
export const setStorage = (key, value, expire = 0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }

    if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");

    expire = (expire ? expire : config.expire) * 1000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }
    
    const encryptString = config.isEncrypt 
    ? encrypt(JSON.stringify(data))
    : JSON.stringify(data);
    
    window[config.type].setItem(autoAddPrefix(key), encryptString);
}

// 获取 getStorage
export const getStorage = (key) => {
    key = autoAddPrefix(key);
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
        return null;
    }

    // 优化 持续使用中续期
    const storage = config.isEncrypt 
    ? JSON.parse(decrypt(window[config.type].getItem(key))) 
    : JSON.parse(window[config.type].getItem(key));
    
    let nowTime = Date.now();

    // 过期删除
    if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        // 未过期期间被调用 则自动续期 进行保活
        setStorage(autoRemovePrefix(key), storage.value);
        return storage.value;
    }
}

// 是否存在 hasStorage
export const hasStorage = (key) => {
    key = autoAddPrefix(key);
    let arr = getStorageAll().filter((item)=>{
        return item.key === key;
    })
    return arr.length ? true : false;
}

// 获取所有key
export const getStorageKeys = () => {
    let items = getStorageAll()
    let keys = []
    for (let index = 0; index < items.length; index++) {
        keys.push(items[index].key)
    }
    return keys
}

// 根据索引获取key
export const getStorageForIndex = (index) => {
    return window[config.type].key(index)
}

// 获取localStorage长度
export const getStorageLength = () => {
    return window[config.type].length
}

// 获取全部 getAllStorage
export const getStorageAll = () => {
    let len = window[config.type].length // 获取长度
    let arr = new Array() // 定义数据集
    for (let i = 0; i < len; i++) {
        // 获取key 索引从0开始
        let getKey = window[config.type].key(i)
        // 获取key对应的值
        let getVal = window[config.type].getItem(getKey)
        // 放进数组
        arr[i] = {'key': getKey, 'val': getVal,}
    }
    return arr
}

// 删除 removeStorage
export const removeStorage = (key) => {
    window[config.type].removeItem(autoAddPrefix(key));
}

// 清空 clearStorage
export const clearStorage = () => {
    window[config.type].clear();
}

// 名称前自动添加前缀
const autoAddPrefix = (key) => {
    const prefix = config.prefix ? config.prefix + '_' : '';
    return  prefix + key;
}

// 移除已添加的前缀
const autoRemovePrefix = (key) => {
    const len = config.prefix ? config.prefix.length+1 : '';
    return key.substr(len)
    // const prefix = config.prefix ? config.prefix + '_' : '';
    // return  prefix + key;
}

/**
 * 加密方法
 * @param data
 * @returns {string}
 */
const encrypt = (data) => {
    if (typeof data === "object") {
        try {
            data = JSON.stringify(data);
        } catch (error) {
            console.log("encrypt error:", error);
        }
    }
    const dataHex = CryptoJS.enc.Utf8.parse(data);
    const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
        iv: SECRET_IV,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return encrypted.ciphertext.toString();
}

/**
 * 解密方法
 * @param data
 * @returns {string}
 */
const decrypt = (data) => {
    const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
    const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
        iv: SECRET_IV,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
    return decryptedStr.toString();
}

来自:Gaby
https://juejin.cn/post/7104301566857445412

详解vue中localStorage的使用方法 什么是localStorage 对浏览器来说,使用 Web Storage 存储键值对比存储 Cookie 方式更直观,而且容量更大,它包含两种:localStorage 和 sessionStorage 1.sessionStorage(临时存储) :为每一个数据源维持一个存储区域,在浏览器打开期间存在,包括页面重新加载 2.localStorage(长期存储) :与 sessionStorage 一样,但是浏览器关闭后,数据依然会一直存在 所以上次使用cookie的时候就遇到了一个坑,设置后马上访问session会获取不到,蛋疼,还需要刷新一下,原因是: 当我们首次访问设置Cookie的页 立即下载

相关推荐

【Matlab】从零开始掌握 MATLAB 安装激活:从环境搭建到问题解决全指南

打开 MATLAB,点击 “主页 - 设置路径”;点击 “添加文件夹”,选择自定义的工作文件夹,点击 “保存”,重启 MATLAB 生效。MATLAB R2024a 的安装与激活是开启 MATLAB 学习的第一步,也是构建稳定学习环境的基础。本文从安装前准备、分步操作、验证测试、问题解决到环境优化,完整覆盖了新手所需的所有知识点 —— 核心要点在于:确认系统兼容、使用合法授权、选择纯英文安装路径、完成功能验证。

unber的博客 596

Vue 中使用 localStorage 详解

文章目录1、什么是 localStorage ?2、使用方法2.1 保存2.2 获取2.3 删除3、案例展示 1、什么是 localStorage ? 使用 Web Storage 存储键值对比存储 Cookie 方式更直观,而且容量更大,它包含两种:localStorage 和 sessionStorage : sessionStorage 临时存储,为每一个数据源维持一个存储区域,在浏览器打开期间存在,包括页面重新加载。 localStorage 长期存储,与 sessionStorage 一样

方糖世界 2万+

SAP_SD模块-销售批次策略应用记录

SAP_SD模块-销售批次策略完整案例

风沙的博客 2611

LocalStorage,该提升下逼格了

localStorage 么?该提升下逼格了

qq_40063198的博客 402

存储硬盘格式怎么把512还原为520格式??

硬盘厂商工具(如SeaChestsg_format)可以调整扇区大小。备份数据,因为调整扇区会清除硬盘内容。根据硬盘型号和接口类型,选择合适的工具进行调整。如果你需要更详细的帮助,请提供你的硬盘型号和存储设备类型。希望如上措施及解决方案能够帮到有需要的你。PS:如若遇到采纳如下方案还是未解决的同学,希望不要抱怨&&急躁,毕竟影响因素众多,我写出来也是希望能够尽最大努力帮助到同类似问题的小伙伴,即把你未解决或者产生新Bug黏贴在评论区,我们大家一起来努力,一起帮你看看,可以不咯。

**My Coding Family** 1146

Xilinx差分原语-IBUFGDS与OBUFGDS FPGA

它将差分信号转换为单端信号,并将其输入到FPGA的逻辑片上。它将单端信号转换为差分信号,并将其输出到FPGA的引脚。在FPGA设计中,差分信号是一种常见的信号传输方式,用于提高抗噪性和抑制共模噪声。Xilinx是一家知名的FPGA供应商,提供了许多差分原语,其中包括IBUFGDS和OBUFGDS。通过使用Xilinx的差分原语,如IBUFGDS和OBUFGDS,可以实现差分信号的输入和输出。这些差分原语提供了方便且可靠的接口,使得在FPGA设计中处理差分信号变得更加简单和高效。是差分输入信号的端口,

git_database727的博客 2096

封装localStorage

storage会根据传入的参数类型自动进行json转换,不用再自己手动写JSON.stringify()和JSON.parse(),需要的小伙伴直接复制到项目中就能使用。

CodeBoyDong的博客 394

【Agent】AI Agent架构趋势及演进

本文系统介绍了AI Agent架构的发展趋势和技术框架,涵盖单Agent与多Agent系统设计、提示词工程与上下文管理、SpringAI开发框架等关键技术。重点阐述了AI原生应用参考架构,包括Nacos配置管理、Higress AI网关、RocketMQ会话状态管理等核心组件解决方案。同时深入探讨了AI应用可观测性体系,包含OpenTelemetry全链路追踪、关键指标监控以及基于LoongSuite开源工具的数据采集评估方法,为构建高效可靠的AI应用提供了全景式技术参考。

一个内容严谨、方向前沿、极具实践价值的AI大模型技术分享站 1106

LocalStorage封装

本文不讲解基础api,主要教你如何优雅的Storage工具类如何封装(支持前缀key、加密存储、过期时间,ts封装等)。真实存储大小/存储统计、如何监听、同源问题与同源窗口通信。

★【World Of Moshow 郑锴】★ 1223

LVGL hal indev(porting evdev)

LVGL中对输入设备的管理是单例实现的,而在Linux的输入设备是多个的,所以需要对其进行改造成多例实现。

Bigan(安)的博客 3346

扫码登录原理

扫码登录功能通过PC端与移动端的交互实现安全认证,分为三个阶段:待扫描阶段生成唯一二维码ID并轮询状态;已扫描阶段移动端提交token与二维码ID关联,生成临时token并要求用户确认;确认阶段验证临时token后为PC端生成正式登录凭证。整个过程通过二维码唯一性、状态轮询和二次确认机制确保安全性,防止未授权登录,最终完成跨设备认证流程。

阿杰同学的博客 347

在vue中封装localStorage

localStorage localStorage 和 sessionStorage 属性允许在浏览器中存储 key/value 对的数据。 localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。 localStorage 属性是只读的。 提示: 如果你只想将数据保存在当前会话中,可以使用 sessionStorage 属性,改数据对象临时保...

前端李小白的博客 1337

6-Halcon-- var_threshold 函数功能(用于基于局部统计特性的动态阈值分割的算子)

var_threshold 是 HALCON 中用于基于局部统计特性的动态阈值分割的算子,特别适用于处理低对比度图像或纹理均匀但包含细微差异的场景。与 dyn_threshold 不同,它不依赖预处理生成的参考图像,而是直接在局部区域内计算灰度值的统计特性(均值和标准差),从而自适应调整阈值。

weixin_45590420的博客 1328

对localStorage和sessionStorage的封装

Web 存储 API 提供了 sessionStorage (会话存储) 和 localStorage(本地存储)两个存储对象来对网页的数据进行添加、删除、修改、查询操作。 1.localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除。 2.sessionStorage 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。 但原生的localStorage和sessionStorage不大好用,保存的value只能是字符串类型,所以可以在添加ke

weixin_44385241的博客 843

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

vue中localStorage封装函数

localStorage封装函数: // 存数据 function setLocal (key, value) { window.localStorage.setItem(key, JSON.stringify(value)) } // 取数据 function getLocal (key) { return JSON.parse(window.localStorage.getItem(key)) } // 删数据 function delLocal (key) { window.localSt

qq_43217258的博客 983

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

localStorage封装

【代码】localStorage封装。

qq_46645908的博客 512

封装localStorage本地存储

localStorage封装有什么好处

yeh_shyer的博客 458

localStorage封装代码

JSON.parse(): 将 JSON 字符串转化为 JS 对象,键值都必须使用双引号包裹。JSON.stringfy(): 将 JS 对象转化为 JSON 字符串。

cherry__yu的博客 446

utils封装 —— localStorage && sessionStorage

LocalStorage 本地存储相关操作: 存储:localStorage.setItem(key,JSON.stringify(value)) 获取:JSON.parse( localStorage.getItem(key) ) 移除:localStorage.removeItem(key) 清空:localStorage.clear() 1.简单粗暴式封装 export class Stor...

Zhou的博客 1688

封装 sessionStorage 和 localStorage

封装 sessionStorage 和 localStorage //util.js export const storage = { setLocal: (key, value) => { window.localStorage.setItem(key, JSON.stringify(value)) }, getLocal: key => { let va...

qq_15721845的博客 509

localStorage在vue中的封装

在vue项目中,我们经常会使用到 localStorage 进行数据的前端缓存,为了方便我就简单做了个 localStorage 的封装,我们可以在vue项目的 utils 文件夹下建个 index.js 文件,把下面封装好的函数写在 index.js中就可以。当我们使用时,在需要使用的文件中直接引入就可以了。localStorage 的存储。localStorage 的读取。localStorage 的删除。.........

锦瑟年华L的博客 997

vue3中localStorage的二次封装

通过封装的操作,我们可以在 Vue 应用中更加方便、安全地使用本地存储,同时可以避免直接操作导致的潜在问题(如 JSON 解析错误、过期数据等)。

DYXin__的博客 1372

localStorage的用法与封装

文章目录localStorage的用法在localstorage内定定义两个方法1. fetch(key) => 获取localStorage 中key 的记录 返回对象2. save(key,obj) => 保存 key 为 key对象 => localStorage localStorage的用法 let todos = [ { id:1, text:"哈哈" }, {

m0_45272038的博客 695

localStorage工具类的封装分享

在实际项目开发的过程中,我们难免会用到本地缓存这些东西,像cookie,localStorage,sessionStorage啊,虽然这些很简单,今天分享一个基于localStorage工具类的封装。 /** * localStorage 帮助类 * 小提示: 用static修饰的方法,可以用类型加方法名自己调用 */ class localStorageUtil { /** * lo...

m0_37685031的博客 750
上一篇: macbook 从0开始下载git/nvm
下一篇: 在node服务中使用puppeteer导出pdf
lxsssss
博客等级 码龄7年 76粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值