hexo search

记录Hexo博客安装hexo-generator-search遇到的问题|Zam9036博客 问题描述 在用Git bash安装hexo-generator-search和hexo-generator-searchdb来实现本地搜索时遇到以下问题 npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.1.1 (node_modules\fsevents): npm WARN notsup SKIPPING OPTIONAL D... 阅读详情
/* global CONFIG */

window.addEventListener('DOMContentLoaded', () => {
  // Popup Window
  let isfetched = false;
  let datas;
  let isXml = true;
  // Search DB path
  let searchPath = CONFIG.path;
  if (searchPath.length === 0) {
    searchPath = 'search.xml';
  } else if (/json$/i.test(searchPath)) {
    isXml = false;
  }
  const path = CONFIG.root + searchPath;
  const input = document.getElementById('search-input');
  const resultContent = document.getElementById('search-result');

  // Ref: https://github.com/ForbesLindesay/unescape-html
  const unescapeHtml = html => {
    return String(html)
      .replace(/"/g, '"')
      .replace(/'/g, '\'')
      .replace(/:/g, ':')
      // Replace all the other &#x; chars
      .replace(/&#(\d+);/g, (m, p) => {
        return String.fromCharCode(p);
      })
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&amp;/g, '&');
  };

  const getIndexByWord = (word, text, caseSensitive) => {
    let wordLen = word.length;
    if (wordLen === 0) {
      return [];
    }
    let startPosition = 0;
    let position = [];
    let index = [];
    if (!caseSensitive) {
      text = text.toLowerCase();
      word = word.toLowerCase();
    }
    while ((position = text.indexOf(word, startPosition)) > -1) {
      index.push({
        position: position,
        word    : word
      });
      startPosition = position + wordLen;
    }
    return index;
  };

  // Merge hits into slices
  const mergeIntoSlice = (start, end, index, searchText) => {
    let item = index[index.length - 1];
    let position = item.position;
    let word = item.word;
    let hits = [];
    let searchTextCountInSlice = 0;
    while (position + word.length <= end && index.length !== 0) {
      if (word === searchText) {
        searchTextCountInSlice++;
      }
      hits.push({
        position: position,
        length  : word.length
      });
      let wordEnd = position + word.length;

      // Move to next position of hit
      index.pop();
      while (index.length !== 0) {
        item = index[index.length - 1];
        position = item.position;
        word = item.word;
        if (wordEnd > position) {
          index.pop();
        } else {
          break;
        }
      }
    }
    return {
      hits           : hits,
      start          : start,
      end            : end,
      searchTextCount: searchTextCountInSlice
    };
  };

  // Highlight title and content
  const highlightKeyword = (text, slice) => {
    let result = '';
    let prevEnd = slice.start;
    slice.hits.forEach(hit => {
      result += text.substring(prevEnd, hit.position);
      let end = hit.position + hit.length;
      result += `<b class="search-keyword">${text.substring(hit.position, end)}</b>`;
      prevEnd = end;
    });
    result += text.substring(prevEnd, slice.end);
    return result;
  };

  const inputEventFunction = () => {
    let searchText = input.value.trim().toLowerCase();
    let keywords = searchText.split(/[-\s]+/);
    if (keywords.length > 1) {
      keywords.push(searchText);
    }
    let resultItems = [];
    if (searchText.length > 0) {
      // Perform local searching
      console.log(datas);
      datas.forEach(data => {
        // Only match articles with not empty titles
        if (!data.title) {
          return;
        }
        let searchTextCount = 0;
        let title = data.title.trim();
        let titleInLowerCase = title.toLowerCase();
        console.log(data.content);
        let content = data.content ? data.content.trim().replace(/<(\/|\!|\?|[a-zA-Z])([^>]*)>/g, '') : '';
        console.log(content);
        if (CONFIG.localsearch.unescape) {
          content = unescapeHtml(content);
        }
        let contentInLowerCase = content.toLowerCase();
        let articleUrl = decodeURIComponent(data.url).replace(/\/{2,}/g, '/');
        let indexOfTitle = [];
        let indexOfContent = [];
        keywords.forEach(keyword => {
          indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
          indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
        });

        // Show search results
        if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
          let hitCount = indexOfTitle.length + indexOfContent.length;
          // Sort index by position of keyword
          [indexOfTitle, indexOfContent].forEach(index => {
            index.sort((itemLeft, itemRight) => {
              if (itemRight.position !== itemLeft.position) {
                return itemRight.position - itemLeft.position;
              }
              return itemLeft.word.length - itemRight.word.length;
            });
          });

          let slicesOfTitle = [];
          if (indexOfTitle.length !== 0) {
            let tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfTitle.push(tmp);
          }

          let slicesOfContent = [];
          while (indexOfContent.length !== 0) {
            let item = indexOfContent[indexOfContent.length - 1];
            let position = item.position;
            let word = item.word;
            // Cut out 100 characters
            let start = position - 20;
            let end = position + 80;
            if (start < 0) {
              start = 0;
            }
            if (end < position + word.length) {
              end = position + word.length;
            }
            if (end > content.length) {
              end = content.length;
            }
            let tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfContent.push(tmp);
          }

          // Sort slices in content by search text's count and hits' count
          slicesOfContent.sort((sliceLeft, sliceRight) => {
            if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
              return sliceRight.searchTextCount - sliceLeft.searchTextCount;
            } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
              return sliceRight.hits.length - sliceLeft.hits.length;
            }
            return sliceLeft.start - sliceRight.start;
          });

          // Select top N slices in content
          let upperBound = parseInt(CONFIG.localsearch.top_n_per_article, 10);
          if (upperBound >= 0) {
            slicesOfContent = slicesOfContent.slice(0, upperBound);
          }

          let resultItem = '';

          if (slicesOfTitle.length !== 0) {
            resultItem += `<li><a href="${articleUrl}" class="search-result-title">${highlightKeyword(title, slicesOfTitle[0])}</a>`;
          } else {
            resultItem += `<li><a href="${articleUrl}" class="search-result-title">${title}</a>`;
          }

          slicesOfContent.forEach(slice => {
            resultItem += `<a href="${articleUrl}"><p class="search-result">${highlightKeyword(content, slice)}...</p></a>`;
          });

          resultItem += '</li>';
          resultItems.push({
            item           : resultItem,
            searchTextCount: searchTextCount,
            hitCount       : hitCount,
            id             : resultItems.length
          });
        }
      });
    }
    if (keywords.length === 1 && keywords[0] === '') {
      resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x"></i></div>';
    } else if (resultItems.length === 0) {
      resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x"></i></div>';
    } else {
      resultItems.sort((resultLeft, resultRight) => {
        if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
          return resultRight.searchTextCount - resultLeft.searchTextCount;
        } else if (resultLeft.hitCount !== resultRight.hitCount) {
          return resultRight.hitCount - resultLeft.hitCount;
        }
        return resultRight.id - resultLeft.id;
      });
      let searchResultList = '<ul class="search-result-list">';
      resultItems.forEach(result => {
        searchResultList += result.item;
      });
      searchResultList += '</ul>';
      resultContent.innerHTML = searchResultList;
      window.pjax && window.pjax.refresh(resultContent);
    }
  };

  const fetchData = callback => {
    fetch(path)
      .then(response => response.text())
      .then(res => {
        // Get the contents from search data
        isfetched = true;
        datas = isXml ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(item => {
          return {//textContent
            title  : item.querySelector('title').innerHTML,
            content: item.querySelector('content').textContent,
            url    : item.querySelector('url').innerHTML
          };
        }) : JSON.parse(res);

        // Remove loading animation
        document.querySelector('.search-pop-overlay').innerHTML = '';
        document.body.style.overflow = '';

        if (callback) {
          callback();
        }
      });
  };

  if (CONFIG.localsearch.preload) {
    fetchData();
  }

  const proceedSearch = () => {
    document.body.style.overflow = 'hidden';
    document.querySelector('.search-pop-overlay').style.display = 'block';
    document.querySelector('.popup').style.display = 'block';
    document.getElementById('search-input').focus();
  };

  // Search function
  const searchFunc = () => {
    document.querySelector('.search-pop-overlay').style.display = '';
    document.querySelector('.search-pop-overlay').innerHTML = '<div class="search-loading-icon"><i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i></div>';
    fetchData(proceedSearch);
  };

  if (CONFIG.localsearch.trigger === 'auto') {
    input.addEventListener('input', inputEventFunction);
  } else {
    document.querySelector('.search-icon').addEventListener('click', inputEventFunction);
    input.addEventListener('keypress', event => {
      if (event.keyCode === 13) {
        inputEventFunction();
      }
    });
  }

  // Handle and trigger popup window
  document.querySelector('.popup-trigger').addEventListener('click', () => {
    if (isfetched === false) {
      searchFunc();
    } else {
      proceedSearch();
    }
  });

  // Monitor main search box
  const onPopupClose = () => {
    document.body.style.overflow = '';
    document.querySelector('.search-pop-overlay').style.display = 'none';
    document.querySelector('.popup').style.display = 'none';
  };

  document.querySelector('.search-pop-overlay').addEventListener('click', onPopupClose);
  document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
  window.addEventListener('pjax:success', onPopupClose);
  window.addEventListener('keyup', event => {
    if (event.which === 27) {
      onPopupClose();
    }
  });
});

使用Hexo实现前端开发中的搜索功能 通过安装并配置"hexo-generator-search"插件,生成搜索索引,创建搜索页面和搜索布局,并编写相应的JavaScript代码,我们成功地为Hexo静态网站添加了搜索功能。在Hexo根目录下的"source"文件夹中创建一个名为"js"的文件夹,并在该文件夹下创建一个名为"search.js"的JavaScript文件。这段JavaScript代码将监听搜索输入框的输入事件,并根据输入的关键字从搜索索引中筛选匹配的结果,并将结果显示在搜索结果列表中。第七步:美化样式(可选) 阅读详情

相关推荐

hexo-generator-searchdb:each适用于Hexo的Seach数据生成器插件

hexo-generator-searchdb 搜索Hexo的数据生成器插件。 该插件用于生成搜索索引文件,其中包含文章的所有必要数据,可用于为博客编写本地搜索引擎。 支持XML和JSON格式输出。 安装 $ npm install hexo-generator-searchdb 选项 您可以在根_config.yml配置此插件。 所有参数都是可选的。 search : path : search.xml field : post content : true format : html path-文件路径。 默认情况下是search.xml 。 如果文件扩展名为.json ,则输出格式为JSON。 否则,将导出XML格式的文件。 字段-您要搜索的搜索范围,可以选择: 帖子(默认)-仅覆盖您博客的所有帖子。 页面-仅覆盖博客的所有页面。 全部-将覆盖您博客的

Hexo骚操作:主题配置|搜索|评论|统计|图床

前言 相信各位在看过上一篇blog手把手教你使用hexo搭建属于你的个人博客后已经初步搭建了属于自己的博客,不过细心的你可能已经发现这样的博客还是缺点什么,现在就来说说Hexo的骚操作:添加主题、统计、评论、SEO等等等等。 承接上篇最后说过的:根目录下的_config.yml叫做站点配置文件,主题下文件夹的_config.yml叫做主题配置文件。好了,现在进入正题,今天我们要对Hexo进行

catalinaLi的博客 1717

hexo-generator-search:一个为Hexo生成搜索数据的插件

六元发生器搜索 生成Hexo 3.x和4.x的搜索数据。 此插件用于生成搜索索引文件,其中包含您文章的所有必要数据,可用于为博客编写本地搜索引擎。 支持XML和JSON格式输出。 -试用此站点中的搜索引擎。 安装 $ npm install hexo-generator-search --save 选项 您可以在根_config.yml配置此插件。 search : path : search.xml field : post content : true template : ./search.xml path-文件路径。 默认情况下是search.xml 。 如果文件扩展名为.json ,则输出格式为JSON。 否则,将导出XML格式的文件。 字段-您要搜索的搜索范围,可以选择: 帖子(默认)-仅涵盖您博客的所有帖子。 页面-仅覆盖博客的所有页面。 全部

博客搭建之路:hexo增加搜索功能

时,algolia会多条合并进行提交,而免费的algolia会有大小的限制,可以看到超过10000字节就不行了,algolia默认会50条合并提交,可以设置几条进行合并。前期还好,后来随着我的文章越来越多,search.json文件已经达到了十几M,每次加载页面都需要花费几分钟来加载search.json文件,使用体验很不好。作为一个博客,没有搜索功能,如何在大批文章中找到自己想要的,那在hexo中如何增加搜索功能呢?本地搜索走不通,那就只能使用远程搜索了,还好hexo提供了algolia搜索。

Lxn2zh的博客 1318

初识机会网络仿真器ONE中ExternalMovement移动模型(导入外部移动数据)

最近半个月一直在研究ONE中关于ExternalMovement移动模型的源代码,在网上几乎没有找到相关的资料,很是苦恼,如果有共同研究方向的小伙伴欢迎联系我,共同讨论进步。 下面记录一下几点感悟: 1、使用外部数据的前提是在ONE的配置文件中增加及修改相关代码: Group.movementModel = ExternalMovement ExternalMovement.file = move...

qq_36974792的博客 659

数模【Mathematica(安装、入门方法、基本计算、基本图形、创建互动模型、利用数据、幻灯片演示、完整实例)】

Mathematica(安装and激活、入门方法、基本计算、基本图形、创建互动模型、利用数据、幻灯片演示、完整实例) Mathematica_11.3.0 安装教程 Mathematica11.3 激活 P1 笔记本 P2 入门方法 P3 基本计算 P4 基本图形 P5 创建互动模型 P6 利用数据 P7 演示 P8 完整实例

upward 12万+

HEXO博客系统添加搜索功能

Hexo 可以通过接入第三方搜索引擎来为博客添加搜索功能。这里给出一种基于 Algolia 搜索引擎的实现方式。

2037

【Python实战解析】从数据爬取到房价预测:一个完整的数据科学项目流程拆解

本文详细拆解了一个完整的Python数据科学项目流程,从爬取长沙二手房数据到房价预测模型构建。通过实战案例,介绍了数据爬取、清洗、可视化及模型优化的关键技巧,帮助开发者掌握Python在数据分析中的应用,提升程序开发能力。

weixin_30379531的博客 411

hexo 搜索功能

搜索功能真心好用,当文章多起来的时候,标签提供的作用已经很少了,只能简单索引,搜索却能精确查找,这里我用的依旧是最简单的本地站内搜索。 安装插件 npm install hexo-generator-searchdb --save 修改 站点配置 文件 search: path: search.xml field: post format: html limit:

阿甘的博客 9365

RK3588网络调试笔记:当我把默认RTL8211F PHY换成另一款时,踩了哪些坑?

本文详细记录了在RK3588平台上将默认RTL8211F PHY芯片替换为其他型号的全过程,包括硬件设计验证、内核驱动适配、复位时序配置等关键步骤。通过实际案例分享,提供了从驱动调试到硬件优化的系统性解决方案,帮助开发者高效完成PHY芯片替换,确保网络接口稳定性。

weixin_30291791的博客 669

HarmonyOS应用开发者基础认证

B. 自定义组件:可复用的 UI 单元,可组合其它组件。D. 内置组件:ArkTS中默认内置的基本组件和布局组件,开发者可以直接调用,如Column、Text、Divider、Button等。E. 属性方法:用于组件属性的配置,统一通过属性方法进行设置,如fontSize()、width()、height()、color() 等。在Column和Row容器组件中,justifyContent用于设置子组件在主轴方向上的对齐格式,alignItems用于设置子组件在交叉轴方向上的对齐格式。

牛肉胡辣汤 3818

探索 Hexo 的强大插件:Hexo-Generator-Search

探索 Hexo 的强大插件:Hexo-Generator-Search 项目简介 是一个为 Hexo 构建的搜索引擎生成器。如果你是 Hexo 博客的用户,想要在你的站点中添加一个高效、自托管的搜索功能,那么这个项目就是你需要的解决方案。 技术分析 该插件基于lunr.js,一个轻量级的全文搜索引擎库,可以在客户端进行高效的文本检索。它的工作原理是在 Hexo 渲染博客文章时,会将每篇文章的关键信...

gitblog_00088的博客 495

GitLab CI/CD .gitlab-ci.yml 关键词(十一):发布版本 release

本篇文章介绍使用GitLab CI/CD 发布一个版本,对外发布。并填写一些发布的信息。

知无涯 4720

hexo-generator-search插件教程

hexo-generator-search插件教程 项目介绍 hexo-generator-search 是一个专为 Hexo 博客框架设计的开源插件,旨在生成JSON格式的搜索引擎数据文件,从而提高博客站点的搜索体验。它允许博主轻松集成自定义搜索引擎,让站内内容的查找更加便捷高效。这个插件简化了索引创建过程,无需外部依赖即可在Hexo构建流程中自动生成所需的搜索数据。 项目快速启动 安装插件...

gitblog_00078的博客 514

《UDS协议从入门到精通》系列——图解0x2E:通过ID写数据

图解数据包格式+直观的通信示例,迅速认识0x2E服务!!

车载系统攻城狮 3602

QT应用编程: Win10系统下QT5.12搭建Android开发环境(配件齐全)

在win10系统下,搭建QT5.12.6 的Android开发环境。

7689

【亲测免费】 为Hexo博客打造本地搜索功能:hexo-generator-search

为Hexo博客打造本地搜索功能:hexo-generator-search 项目介绍 hexo-generator-search 是一个专为 Hexo 3.x 和 4.x 版本设计的插件,旨在为你的博客生成一个搜索索引文件。这个插件能够提取你博客中的所有必要数据,帮助你构建一个高效的本地搜索引擎。无论是XML还是JSON格式的输出,hexo-generator-search 都能轻松应对,让你的博...

gitblog_01046的博客 726

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Hexo-Generator-Search 项目教程

Hexo-Generator-Search 项目教程 1. 项目的目录结构及介绍 Hexo-Generator-Search 项目的目录结构如下: hexo-generator-search/ ├── demo_output/ ├── lib/ ├── templates/ ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── in...

gitblog_00531的博客 501

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

【亲测免费】 Hexo Generator Search 文档指南

Hexo Generator Search 文档指南 1. 目录结构及介绍 此开源项目 hexo-generator-search 是专为 Hexo 博客框架设计的一个插件,用于生成站内搜索功能所需的索引文件。其基本目录结构如下: hexo-generator-search ├── package.json - 包含项目元数据和依赖关系 ├── README.md ...

gitblog_00948的博客 430

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

hexo-generator-search项目常见问题解决方案

hexo-generator-search项目常见问题解决方案 hexo-generator-search 是一个为 Hexo 博客生成搜索索引文件的开源插件,它允许开发者创建本地的搜索功能。该项目的编程语言主要是 JavaScript。 新手常见问题及解决步骤 问题一:如何安装hexo-generator-search插件? 解决步骤: 确保您的系统中已经安装了Node.js。 在您的Hexo...

gitblog_01128的博客 470

探秘Hexo-Generator-Search:打造高效博客搜索引擎

探秘Hexo-Generator-Search:打造高效博客搜索引擎 是一个针对Hexo框架的插件,它旨在为你的静态博客提供强大的本地搜索功能。如果你正在使用Hexo构建个人博客,并且希望让你的读者能够更方便地找到他们感兴趣的内容,那么这个项目绝对值得你关注。 项目简介 Hexo是一个快速、简洁且高效的静态站点生成器,而Hexo-Generator-Search则是在此基础上增添的一把利剑。这个插...

gitblog_00094的博客 752

Vue Router 命名路由(Named Routes)完全指南:定义、链接与底层实现

命名路由(Named Routes)是 Vue Router 为每条路由赋予一个稳定名称的机制,让你在模板中用 `<router-link>` 声明式跳转、或在组件中调用 `router.push()` 进行编程式导航时,不必再硬编码长长的路径字符串,尤其适合包含动态参数(如 `/user/:userId`)的路由场景。阅读本文后,你将掌握如何在 `routes` 配置中定义命名路由、通过 `{

gitblog_00265的博客 422

Hexo+Butterfly主题引入Local search 本地搜索

引入Local search 本地搜索 效果 安装hexo-generator-search npm install hexo-generator-search --save 添加配置 在全局配置文件_config.yml文件中添加配置: search: path: search.xml field: post # post:文章范围、page:页面范围、all:覆盖所有 conte...

CodeRain的博客 3921

我的Hexo博客搭好了12 - 自动提交URL到搜索引擎(IndexNow + Google Search Console)

快速索引:新文章可能在几分钟到几小时内被索引智能提交:只提交有变化的 URL,节省资源覆盖全面:同时支持 Google、Bing、Yandex 等搜索引擎完全自动:每次部署自动执行,无需手动操作详细日志:清晰显示提交的 URL 和结果再也不用担心新文章迟迟不被搜索引擎收录了!

mediocrep的博客 1015

Hexo个人博客NexT主题添加Local Search本地搜索

添加本地自定义站点内容搜索,可以通过文字标题或文字内容关键字搜索出相应文章 安装hexo-generator-searchdb,在站点的根目录下执行以下命令: $ npm install hexo-generator-searchdb --save 编辑站点配置文件,新增以下内容到任意位置: search: path: search.xml field: p...

野猿新一 3793

hexo+next 增加搜索功能

1、安装本地搜索插件 hexo-generator-search 在博客根目录安装搜索插件 # 安装插件,用于生成博客索引数据(在博客根目录下执行下列命令): npm install hexo-generator-search --save 安装之后,会在站点目录的 public 文件夹下创建一个 search.xml 文件。(如果没有 search.xml 文件,请继续往下看) 2、修改站点配置文件(如果上一步没有找到 search.xml文件 则可以跳过 ) 在主题配置文件中的 _config

fly_wugui的博客 925
上一篇: JAVA泛型
code609
博客等级 码龄11年 0粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值