辣评 移动端全面优化:可折叠筛选、卡片视图与邮箱自助修改(三十)

 

用 Playwright 对移动端逐页截图分析后,发现了一系列严重的可用性问题——表格截断、筛选栏占满首屏、暗黑模式下导航栏刺眼白色。一天时间,13 个文件,+2400 行代码,完成了 4 个页面的移动端重构和邮箱自助修改功能。本文记录整个分析和修复过程。


一、Playwright 驱动的问题发现

这次优化的起点不是用户反馈,而是自动化截图分析。我们编写了一套 Playwright 脚本,在 iPhone 14 (390px) 和 375px 两种视口下对所有前台页面截图,然后逐帧对比分析问题。

核心发现:

页面 问题 严重度
资格统计 10 列表格只能看到 3 列,核心数据全部丢失 致命
管理员评论 el-table 严重截断,评论内容/评分不可见 致命
前台评论 筛选栏占首屏 60%,内容不可见
排行榜 筛选栏占首屏 70%
底部导航栏 暗黑模式下仍为白色背景
移动端 header 没有暗黑模式切换入口

这个分析方法本身就是一个值得分享的经验:不要靠感觉判断移动端适配质量,用真实设备视口的截图说话。


二、可折叠筛选栏:一行摘要 + 展开面板

2.1 问题:筛选栏吞掉了整个首屏

移动端纵向排列 4-6 个筛选字段 + 按钮,高度轻松超过 400px。在 844px 高度的 iPhone 14 上,减去 header (44px) 和 tabbar (62px),可用高度只有 738px——筛选栏直接占了一半以上。

2.2 方案:双模式切换

我们实现了「收起态」和「展开态」两种模式:

<!-- 收起态:一行摘要 -->
<div v-if="!filterExpanded" class="filter-summary" @click="filterExpanded = true">
  <div class="filter-tags">
    <el-tag v-if="queryParams.competitionId" size="small" closable
      @close.stop="clearSingleFilter('competitionId')">
      
    </el-tag>
    <!-- 其他激活的筛选条件 Tag... -->
    <span v-if="!hasActiveFilters" class="no-filter">全部评论</span>
  </div>
  <el-button text type="primary" size="small">
    <el-icon><Filter /></el-icon>筛选
  </el-button>
</div>

<!-- 展开态:完整表单 -->
<FilterBar v-if="filterExpanded" class="filter-container-mobile">
  <!-- 筛选字段 + 重置/完成按钮 -->
</FilterBar>

关键设计决策:

  1. Tag 可单独关闭:点击 Tag 上的 × 可直接清除某个筛选条件,无需展开面板
  2. 桌面端不受影响:通过 .desktop-filter / .mobile-filterdisplay: none 在 768px 断点切换
  3. 小说类型用 Chip 按钮组:从 el-select 改为 el-check-tag,减少一次点击

效果:首屏从只能看到筛选栏 → 直接看到 3 张评论卡片 + FAB 按钮。


三、移动端卡片视图:替代不可用的表格

3.1 资格统计页:从零到完整

修复前,10 列表格在移动端只能看到 ID、用户名和操作按钮——笔名、届次、等效评论、实际评论、总字数、资格状态、更新时间全部丢失。

我们参照前台评论页已有的 desktop-table / mobile-card-list 双视图模式,为资格统计页设计了卡片布局:

<div class="stat-card">
  <div class="card-header">
    <div class="card-user">
      <span class="user-name"></span>
      <span class="user-id"></span>
    </div>
    <el-tag :type="row.isQualified ? 'success' : 'danger'" size="small">
      
    </el-tag>
  </div>
  <div class="card-body">
    <!-- 2×2 网格:届次、等效评论、实际评论、总字数 -->
  </div>
  <div class="card-footer">
    <span class="update-time"></span>
    <el-button link type="primary" size="small" @click="handleRecalculateUser(row)">重算</el-button>
  </div>
</div>

3.2 管理员评论页:同样的方案

管理员评论页的 el-table 也严重截断,用相同思路添加了卡片视图,每张卡片展示标题+类型、笔名/届次/评分、评论预览、编辑/删除操作。


四、排行榜资格详情:从 80% 抽屉到全屏重构

4.1 问题

点击排行榜中的评论者名字,会打开一个 80% 宽度的右侧抽屉显示资格详情。这个抽屉在移动端有多个致命问题:

  • 5 列统计卡片网格崩塌
  • 7 列明细表格完全截断
  • 没有关闭按钮
  • 左侧露出排行榜内容,视觉干扰

4.2 方案:条件渲染两套抽屉

<!-- 桌面端:保持原有右侧抽屉 -->
<el-drawer v-if="!isMobile" v-model="showDetail" size="80%">
  <!-- 桌面版内容不变 -->
</el-drawer>

<!-- 移动端:全屏底部抽屉 -->
<el-drawer v-if="isMobile" v-model="showDetail" direction="btt" size="100%">
  <div class="mobile-detail-header">
    <el-button text @click="showDetail = false">
      <el-icon><ArrowLeft /></el-icon>
    </el-button>
    <span>资格详情</span>
  </div>
  <div class="mobile-detail-body">
    <!-- 状态卡片:用户名 + 届次 + 资格Tag -->
    <!-- 统计 2列网格:等效/字数/总评/深评 -->
    <!-- 综合检查:列表 + 通过/未过 Tag -->
    <!-- 明细:卡片列表替代 7 列表格 -->
  </div>
</el-drawer>

关键改动:

  • 统计从 5 列 → 2 列网格
  • 明细表格 → 每条一张卡片(标题+类型/字数+权重+深评+累计)
  • 综合检查从嵌套括号文字 → 结构化列表 + Tag
  • 标题从「爱丽丝-第5届-未参赛」纯文字 → 用户名大字 + 届次小字 + 状态 Tag

五、暗黑模式补全

5.1 底部导航栏

暗黑模式下 tabbar 仍为 rgba(255, 255, 255, 0.96)——在深色页面上非常刺眼。

由于 FrontLayout 使用了 <style scoped>html.dark 选择器不生效。解决方案是新增一个非 scoped 的 <style> 块:

<style>
html.dark .front-layout .mobile-tabbar {
  background: rgba(30, 30, 46, 0.96);
  border-top-color: rgba(140, 150, 200, 0.15);
}
</style>

5.2 移动端暗黑切换入口

移动端 header 只有标题和用户头像,没有 ThemeSwitch。直接在 .mobile-actions 中添加已有的 <ThemeSwitch /> 组件即可。


六、邮箱自助修改功能

6.1 需求

原来修改邮箱需要联系管理员。我们实现了验证码自助修改流程。

6.2 后端:两个新 API

// POST /api/users/profile/email/send-code
func SendEmailChangeCode(c *gin.Context) {
    // 1. 校验新邮箱 ≠ 当前邮箱
    // 2. 校验新邮箱未被其他用户注册(唯一性)
    // 3. 复用 EmailService.SendVerificationCode(newEmail, "change_email")
}

// PUT /api/users/profile/email
func ConfirmEmailChange(c *gin.Context) {
    // 1. EmailService.VerifyCode 验证验证码
    // 2. 再次校验唯一性(防并发)
    // 3. 更新邮箱
}

复用了现有的 EmailService,无需新建任何服务。唯一性校验做了两次——发送时一次(用户体验),确认时再一次(安全防并发)。

6.3 前端交互

邮箱区域从 disabled input 改为纯文本 + 「修改」链接,点击后展开验证码流程:

admin@laping.test  修改    ← 默认态
     ↓ 点击修改
[请输入新邮箱      ] [发送验证码]  ← 编辑态
[请输入验证码      ]              ← 发送后出现
        [取消]  [确认修改]

60 秒倒计时防止频繁发送,cancelEmailEdit() 可随时退出。


七、个人信息页 UI 优化

7.1 表单 label 上下布局

移动端 640px 以下,表单 label 从左侧 100px 固定宽度改为位于输入框上方,输入框全宽。用 :deep() 覆盖 Element Plus 默认布局。

7.2 密码卡片可折叠

修改密码三个输入框默认展示占据大量空间。改为移动端默认收起,点击标题展开:

<div class="clickable-header" @click="passwordExpanded = !passwordExpanded">
  <span>修改密码</span>
  <el-icon :class="{ 'is-rotated': passwordExpanded }"><ArrowDown /></el-icon>
</div>
<div v-show="passwordExpanded || !isMobileView">
  <!-- 密码表单 -->
</div>

桌面端通过 || !isMobileView 始终展示,箭头图标用 CSS 隐藏。


八、样式一致性统一

跨 4 个页面的可折叠筛选栏最初存在不一致:

  • label 最小宽度:50px / 60px / 70px / 80px
  • label 文字:「届次」vs「比赛届数」,「类型」vs「小说类型」
  • 完成按钮:有的带 Search 图标,有的不带

统一标准后:

  • min-width: 70px
  • 同名字段用相同文字(比赛届数、小说类型、笔名、小说标题)
  • 完成按钮统一 type="primary" 蓝色,不带图标
  • 小说类型统一使用 el-check-tag Chip 按钮组

总结

  1. Playwright 截图分析法:不要靠感觉判断移动端适配,用真实视口截图发现问题比手动测试更高效
  2. 双视图模式desktop-table + mobile-card-list 是表格页面移动端适配的通用方案
  3. 可折叠筛选:移动端首屏寸土寸金,筛选栏必须可以收起
  4. scoped 与 html.dark:暗黑模式样式需要放在非 scoped <style> 块中
  5. 条件渲染两套布局v-if="isMobile" 比 CSS 媒体查询更灵活,适合结构差异大的场景
  6. 复用现有服务:邮箱修改功能复用了已有的 EmailService,零新依赖
  7. 统一设计规范:跨页面组件样式必须从第一天就定标准,否则越做越散

本文遵守 Attribution-NonCommercial 4.0 International 许可协议。 Attribution-NonCommercial 4.0 International