编写种子搜索结果页面:基于 EJS + Vue 的混合渲染实践

2026-02-18 62 浏览 0 评论

在做资源类、搜索类网站时,「 搜索结果页 」几乎是用户停留时间最长、交互最密集的页面之一。它不仅承载着数据展示的职责,还需要支持排序、筛选、分页以及批量操作等复杂交互。

本文将结合一个实际的种子搜索结果页面,讲解如何使用 EJS 服务端模板 + Vue 客户端交互 的方式,构建一个功能完整、逻辑清晰、体验友好的搜索结果页。


一、整体页面结构设计

这个页面采用的是典型的 服务端渲染 + 前端增强 架构:

  • 服务端(Node.js)

  • 使用 EJS 渲染基础 HTML

  • 负责数据查询、分页、首屏渲染

  • 客户端(Vue + jQuery)

  • 负责排序、筛选、批量操作等交互

  • 通过修改 URL 参数刷新页面,实现“伪 SPA”体验

这种模式在 SEO 友好性、开发成本和可维护性之间取得了很好的平衡。

页面整体结构可以拆分为:

  1. 公共头部( head.ejsheader.ejs
  2. 筛选与排序区域
  3. 搜索结果列表
  4. 分页组件
  5. 批量编辑工具栏
  6. 公共底部( footer.ejs

二、搜索筛选与排序区域

页面顶部是一个固定的筛选栏,支持 类型筛选多维度排序 (大小、文件数、热度)。

<div class="filterWrap">
  <div class="container">
    <div class="row">
      <div class="col-md-8 col-md-offset-2">
        <div class="filterItem">
          <div class="dropdown">
            <a class="filterItemALink" href="javascript:" data-toggle="dropdown">
              {{ typeText }}
              <span class="caret"></span>
            </a>
            <ul class="dropdown-menu">
              <li v-for="(item, index) in type" :key="index">
                <a href="javascript:" @click="typeClick(item)">{{ item.label }}</a>
              </li>
            </ul>
          </div>
        </div>
        ...
      </div>
    </div>
  </div>
</div>

设计要点说明

  • 类型筛选

  • 类型数据由服务端注入( JSON.stringify(type)

  • Vue 只负责渲染和交互,不直接请求接口

  • 排序逻辑

  • 每个排序字段有三种状态: autodescasc

  • 任意一个排序生效时,其它排序自动重置,避免 SQL 组合过复杂

  • URL 驱动状态

  • 排序和筛选都会反映到 URL 参数中

  • 刷新页面或复制链接,状态可完整复现

这种“ URL 即状态 ”的设计,在搜索类页面中非常重要。


三、搜索结果列表渲染

搜索结果列表完全由服务端渲染,Vue 不参与 DOM 创建,只做事件增强。

<% if(torrents.length){ %>
<div class="torList">
  <% torrents.forEach(function(item){ %>
  <%-include('loop.ejs', { torrent: item })%>
  <% }) %>
</div>
<%-include('pageNavi.ejs')%>
<% }else{ %>
<div class="empty">
  <img src="/public/img/empty.png" />
  <p>暂无数据</p>
</div>
<% } %>

为什么这么做?

  • 首屏渲染速度快
  • 对搜索引擎友好
  • 列表结构复杂时,EJS 比纯前端模板更直观
  • 避免 Vue 接管整个列表带来的性能与状态同步问题

loop.ejs 中通常包含:

  • 标题
  • 种子大小
  • 文件数量
  • 热度 / 下载量
  • 复选框(用于批量操作)

四、Vue 只负责“交互逻辑”

Vue 实例挂载在 #search 上,但它并不控制整个 DOM,而是:

  • 管理排序状态
  • 处理 URL 参数
  • 控制批量操作逻辑
new Vue({
  el: '#search',

  data() {
    return {
      typeText: '',
      typeVal: 0,
      type: JSON.parse('<%-JSON.stringify(type)%>'),
      size: 'auto',
      file_count: 'auto',
      hot: 'auto',
      checkShow: false,
      checkIds: [],
    };
  },
});

URL 参数驱动页面刷新

sizeClick() {
  if (this.size == 'auto') this.size = 'desc';
  else if (this.size == 'desc') this.size = 'asc';
  else if (this.size == 'asc') this.size = 'auto';

  this.file_count = 'auto';
  this.hot = 'auto';

  let url = handleUrlParams(
    location.href,
    { size: this.size },
    ['file_count', 'hot', 'page']
  );
  location.href = url;
}

这里的关键点是:

  • 不通过 Ajax 更新列表
  • 而是通过修改 URL 触发服务端重新渲染
  • 同时清理分页参数,避免排序切换后仍停留在旧页码

五、批量编辑与删除功能

页面右下角提供了一个简洁的“编辑模式”入口:

<div class="editBox">
  <a href="javascript:" @click="checkShow=true">编辑</a>
  <template v-if="checkShow">
    <a href="javascript:" @click="checkAll">全选</a>
    <a href="javascript:" @click="checkNone">取消</a>
    <a href="javascript:" @click="deleteBatch">删除</a>
  </template>
</div>

批量删除实现思路

async deleteBatch() {
  let that = this;
  if (confirm('确认删除吗?')) {
    loading('删除中');
    let jom = $('.torItemCheck');
    for (let i = 0; i < jom.length; i++) {
      let obj = jom.eq(i);
      if (obj.prop('checked')) {
        await that.delete($(obj).val(), obj);
      }
    }
    hideLoading();
  }
}

这里使用了 async + await 串行删除,优点是:

  • 服务端压力可控
  • 删除失败时容易定位
  • 前端 DOM 可以逐条移除,反馈更直观

六、混合使用 Vue + jQuery 的取舍

你可能注意到,这个页面中 Vue 和 jQuery 是同时存在的 。这是一个非常现实的工程选择:

  • Vue 负责状态与业务逻辑
  • jQuery 负责 DOM 查询、事件代理和旧组件兼容

老项目渐进式改造 、或 非 SPA 页面 中,这种方式依然非常高效。


七、总结

这个搜索结果页的核心设计思路可以总结为:

  • 服务端负责数据和结构
  • 前端负责交互和状态
  • URL 是唯一可信状态源
  • Vue 不强行接管一切

如果你正在做的是:

  • 种子搜索
  • 资源聚合
  • 日志查询
  • 后台列表页

这种 EJS + Vue 的混合渲染模式 ,依然是一个成熟、稳定、易维护的选择。

后续你还可以进一步优化的方向包括:

  • 排序状态视觉强化
  • 批量操作权限控制
  • 搜索条件缓存
  • 前后端参数校验统一

希望这篇文章能对你构建复杂列表页面有所帮助。


发布评论

发布评论前请先 登录
取消
0 评论
点赞
收藏

评论列表 0

暂无评论