一份面向产品与前端的需求评审交互原则清单

31 阅读8分钟

前言

在前端项目开发过程中,我们经常会遇到这样的情况:功能开发完成后,产品经理提出"这里的交互不对"、"这个提示不够友好"、"加载时应该有个 loading"等反馈,导致返工和时间浪费。这些问题的根源在于:需求评审阶段没有明确交互细节

本文整理了前端项目中需要在需求评审阶段与产品确认的通用交互原则,这些原则适用于所有前端项目。在需求评审时,对照这份清单逐一确认,可以有效避免后期返工,提高开发效率。


一、操作反馈类

1.1 重复操作防护

原则:所有可能被重复触发的操作,都需要防止重复执行。

适用场景

  • 提交操作:表单提交、数据保存、审批操作
  • 上传操作:文件上传、图片上传
  • 下载操作:文件下载、导出 Excel
  • 状态切换:开关切换、状态更新

实现方式需确认

1. 过程中的数据保护和防止重复

  • 业务层面:例如审批流程中,已审批的单据不允许再次审批;上传过程中的数据一旦提交不允许重复提交
  • 代码层面:按钮点击后防抖/节流处理,或禁用按钮直到操作完成
// 示例:防止表单重复提交
const submitLoading = ref(false);

const handleSubmit = async () => {
  if (submitLoading.value) return; // 防止重复提交

  submitLoading.value = true;
  try {
    await submitForm();
    ElMessage.success("提交成功");
  } finally {
    submitLoading.value = false;
  }
};

2. 操作过程提示

  • 显示 loading 状态
  • 禁用相关操作区域
  • 提供操作进度反馈(如上传进度条)

需要与产品确认

  • 哪些操作需要防止重复?
  • 重复操作时如何提示用户?
  • 操作过程中是否需要 loading?loading 显示在哪个区域?
  • 是否需要显示操作进度?

1.2 批量操作处理

原则:批量操作需要考虑异常处理、进度反馈、结果展示等多个维度。

以批量导入为例,需确认以下问题

1. 异常处理策略

问题 1:当某个记录的个别属性出现错误时,如何处理?

  • 选项 A:中断当前记录导入,继续导入下一条记录
  • 选项 B:中断整个导入流程,回滚所有数据
  • 选项 C:记录错误信息,但继续导入该记录的其他有效字段

问题 2:如果有数据项无法导入,是否需要导出错误明细?

  • 是否需要导出失败记录清单?
  • 错误提示是否需要具体到字段级别?
  • 如:"第 3 行,姓名字段为空;第 5 行,手机号格式错误"

问题 3:对于重复数据如何处理?

  • 选项 A:跳过重复数据
  • 选项 B:覆盖原有数据
  • 选项 C:合并数据(保留部分原有,部分更新)
  • 选项 D:由用户手动选择处理方式
// 示例:批量导入错误处理
const importData = async file => {
  const result = await batchImport(file);

  if (result.failedCount > 0) {
    // 展示错误摘要
    ElMessage.warning(`导入完成:成功${result.successCount}条,失败${result.failedCount}条`);

    // 提供下载错误明细的入口
    showErrorDetailDialog(result.errors);

    // 或直接下载错误报告
    downloadErrorReport(result.errors);
  } else {
    ElMessage.success(`导入成功:共${result.successCount}条`);
  }
};

2. 进度反馈

  • 是否需要显示导入进度百分比?
  • 是否需要显示当前处理到第几条?
  • 大量数据导入时,是否允许用户取消操作?

3. 用户反馈

  • 操作完成后,如何提示结果?
    • 简单提示:"导入成功"
    • 详细提示:"成功导入 100 条,失败 2 条"
  • 是否需要提供详细的成功/失败明细列表?
  • 对于大批量操作,是否支持后台异步处理?
    • 如果异步处理,如何通知用户结果?(站内消息、邮件、轮询状态等)

需要与产品确认

  • 批量操作的异常处理策略是什么?
  • 是否需要导出错误明细?格式是什么?
  • 重复数据如何处理?
  • 是否需要进度展示?
  • 是否支持取消操作?
  • 操作结果如何反馈?

二、数据展示类

2.1 边界处理

原则:数据展示需要考虑各种边界情况,确保在极端条件下也能正常显示。

1. 文本超长处理

表格中的文本超长

  • 选项 A:截断显示 + 省略号(...)
  • 选项 B:折行显示(可能导致行高不一致)
  • 选项 C:鼠标悬停显示完整内容(Tooltip)
  • 选项 D:设置固定字数,点击"展开"查看全部
<!-- 示例:表格文本超长处理 -->
<el-table-column prop="description" label="描述" min-width="200">
  <template #default="{ row }">
    <el-tooltip :content="row.description" placement="top">
      <div class="text-overflow">{{ row.description }}</div>
    </el-tooltip>
  </template>
</el-table-column>

<style>
.text-overflow {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>

表单中的文本超长

  • 输入框是否限制最大输入字数?
  • 是否显示当前字数/最大字数?如:"50/200"
  • 超出限制时如何提示?

卡片/详情页的文本超长

  • 是否需要"展开/收起"功能?
  • 默认显示几行?

2. 图片加载失败

  • 显示默认占位图
  • 显示"图片加载失败"文字提示
  • 提供重新加载按钮
  • 不显示任何内容(不推荐)
<!-- 示例:图片加载失败处理 -->
<el-image :src="imageUrl" fit="cover" :alt="imageName">
  <template #error>
    <div class="image-error">
      <el-icon><Picture /></el-icon>
      <span>图片加载失败</span>
    </div>
  </template>
</el-image>

3. 空数据状态

  • 列表/表格无数据时显示什么?
    • 空状态插图 + 提示文案
    • "暂无数据"文字
    • 引导用户操作(如"点击添加第一条数据")
  • 搜索无结果时的提示
<!-- 示例:空数据状态 -->
<el-table :data="tableData">
  <template #empty>
    <el-empty description="暂无数据">
      <el-button type="primary" @click="handleAdd">添加数据</el-button>
    </el-empty>
  </template>
</el-table>

需要与产品确认

  • 文本超长如何处理?(表格、表单、详情各场景)
  • 图片加载失败显示什么?
  • 空数据状态如何展示?

2.2 数据回显

原则:数据回显需要保证准确性、实时性和关联性。

1. 异步数据回显

场景:页面初始化时异步加载数据

  • 加载过程中显示骨架屏还是 loading?
  • 加载失败如何提示?是否提供重试按钮?
<!-- 示例:骨架屏 -->
<el-skeleton :loading="loading" :rows="5" animated>
  <template #default>
    <!-- 实际内容 -->
  </template>
</el-skeleton>

2. 级联数据或键值数据回显

场景:省市区级联选择器、部门-人员选择器等

问题:当下拉选项依赖关系变化时,历史已选数据如何展示?或者历史数据为空如何展示?

  • 例如:用户编辑时原选择的是"北京-朝阳区",但朝阳区已被删除,如何显示?
    • 选项 A:显示原始 ID(不友好)
    • 选项 B:显示"数据已失效"
    • 选项 C:保留原始文本,但标记为"已删除"
    • 选项 D:自动清空该字段
// 示例:处理已失效的级联数据
const displayCascaderValue = (value, options) => {
  // 尝试在当前选项中查找
  const found = findValueInOptions(value, options);
  if (found) return found.label;

  // 如果找不到,从历史记录中获取文本
  const historical = getHistoricalLabel(value);
  return historical ? `${historical}(已失效)` : "数据已失效";
};

3. 实时更新的数据回显

场景:其他用户修改数据后,当前用户页面是否需要实时更新?

实现方式

  • WebSocket 推送
  • 轮询刷新
  • 手动刷新按钮

需确认

  • 哪些页面需要实时更新?
  • 更新频率是多少?
  • 数据更新时是否需要提示用户?

4. 大量数据回显

场景:表格有上千条数据、下拉框有上万个选项

优化方案

  • 虚拟滚动
  • 分页加载
  • 懒加载
  • 搜索后加载
<!-- 示例:远程搜索下拉框 -->
<el-select v-model="value" filterable remote reserve-keyword placeholder="请输入关键词" :remote-method="remoteMethod" :loading="loading">
  <el-option
    v-for="item in options"
    :key="item.value"
    :label="item.label"
    :value="item.value"
  />
</el-select>

5. 关联性数据回显

场景:多个字段存在关联关系

问题:数据回显时,关联字段的数据是否一致?

  • 例如:订单金额 = 单价 × 数量,回显时三个字段的值是否匹配?
  • 如果不匹配,以哪个为准?是否需要提示异常?

需要与产品确认

  • 异步加载时的 loading 样式?
  • 级联数据失效时如何显示?
  • 哪些数据需要实时更新?更新方式?
  • 大量数据如何优化加载?
  • 关联字段不一致时如何处理?

2.3 数据格式化

原则:统一的数据展示格式能提升用户体验和数据可读性。

1. 数字格式化

千分位格式化

  • 12345678901,234,567,890
  • 是否所有数字都需要千分位?
  • 小数点保留几位?

大数字简化

  • 1234567123.46万1.23M
  • 适用场景:图表、统计卡片等空间有限的地方

2. 货币格式化

格式规范

  • 12345.678¥12,345.68$12,345.68
  • 是否需要货币符号?
  • 小数点保留几位?(通常金额保留 2 位)
  • 是否需要负数显示?负数如何表示?(-¥100 或 ¥-100)

3. 日期时间格式化

常用格式

  • YYYY-MM-DD:2024-01-15
  • YYYY-MM-DD HH:mm:ss:2024-01-15 14:30:00
  • YYYY年MM月DD日:2024 年 01 月 15 日

需确认

  • 列表展示用什么格式?
  • 详情展示用什么格式?
  • 时区如何处理?

4. 百分比格式化

格式规范

  • 0.76576.5%76.50%
  • 小数点保留几位?
  • 是否需要百分号?
// 示例:百分比格式化
const formatPercent = (value, decimals = 2) => {
  return `${(value * 100).toFixed(decimals)}%`;
};

5. 文本长度限制与提示

场景:表单输入框

需确认

  • 是否显示当前字数/最大字数?如:"50/200"
  • 超出限制是否允许输入?还是直接截断?

6. 特殊字段格式化

  • 手机号13812345678138 1234 5678138****5678(脱敏)
  • 身份证号110101199001011234110101********1234(脱敏)
  • 银行卡号62220212345678901236222 0212 3456 7890 1236222 **** **** 0123(脱敏)

需要与产品确认

  • 数字是否需要千分位?保留几位小数?
  • 货币格式规范?
  • 日期时间格式规范?
  • 百分比保留几位小数?
  • 哪些字段需要脱敏?脱敏规则是什么?

三、交互逻辑类

3.1 状态切换与联动

原则:状态切换需要考虑关联数据和关联组件的联动效果。

1. 数据联动

场景示例

  • 选择省份后,城市下拉框自动更新
  • 选择产品类型后,产品型号下拉框自动更新
  • 切换 Tab 时,下方图表/表格数据自动更新

需确认

  • 联动时是否需要 loading?
  • 联动失败如何处理?
  • 联动是否需要清空已选择的下级数据?

2. 状态切换的数据处理

场景:开关切换、状态更新等

问题

  • 切换状态前是否需要二次确认?
  • 切换失败后如何处理?(回滚状态?提示错误?)
  • 切换状态是否需要记录操作日志?

3. 组件状态联动

场景

  • 点击"编辑"按钮,表单从只读变为可编辑
  • 勾选"同意协议",提交按钮从禁用变为可用
  • 选择"其他"选项,显示文本输入框

需确认

  • 哪些操作会触发状态联动?
  • 状态联动的优先级是什么?(多个条件同时触发时)

需要与产品确认

  • 哪些字段存在联动关系?
  • 联动时是否清空下级数据?
  • 状态切换是否需要二次确认?
  • 切换失败如何处理?

3.2 表单校验

原则:完善的表单校验能有效减少无效提交和后端压力。

1. 必填项校验

  • 哪些字段必填?
  • 必填提示如何显示?(红色星号、文字提示)
  • 未填必填项时,焦点是否自动定位到第一个错误字段?

2. 格式校验

常见格式

  • 邮箱:/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
  • 手机号:/^1[3-9]\d{9}$/
  • 身份证号:18 位身份证号校验
  • URL:/^https?:\/\/.+/
  • IP 地址:/^(\d{1,3}\.){3}\d{1,3}$/

3. 长度和范围校验

  • 最小/最大长度
  • 最小/最大值
  • 日期范围
  • 文件大小限制
// 示例:长度和范围校验
const rules = {
  username: [
    { required: true, message: "请输入用户名", trigger: "blur" },
    { min: 3, max: 20, message: "用户名长度在3-20个字符", trigger: "blur" }
  ],
  age: [
    { required: true, message: "请输入年龄", trigger: "blur" },
    { type: "number", min: 0, max: 150, message: "年龄必须在0-150之间", trigger: "blur" }
  ]
};

4. 文件类型校验

需确认

  • 允许上传哪些文件类型?(图片:jpg/png/gif,文档:pdf/doc/docx,压缩包:zip/rar 等)
  • 文件大小限制?(如:单个文件不超过 10MB)
  • 文件数量限制?(如:最多上传 5 个文件)
  • 不符合要求时如何提示?

5. 关联字段校验

场景:多个字段之间存在逻辑关系

示例

  • 开始时间不能大于结束时间
  • 起始金额不能大于结束金额
  • 两次输入密码必须一致
  • 优惠价不能高于原价
// 示例:关联字段校验
const rules = {
  endDate: [
    { required: true, message: "请选择结束日期", trigger: "change" },
    {
      validator: (rule, value, callback) => {
        if (form.startDate && value < form.startDate) {
          callback(new Error("结束日期不能早于开始日期"));
        } else {
          callback();
        }
      },
      trigger: "change"
    }
  ]
};

6. 业务逻辑校验

场景:需要调用后端接口验证的业务规则

示例

  • 用户名是否已存在
  • 订单金额是否超出信用额度
  • 库存是否充足

需确认

  • 哪些字段需要后端校验?
  • 校验时机?(失焦时?提交时?)
  • 校验失败如何提示?
// 示例:异步校验
const rules = {
  username: [
    { required: true, message: "请输入用户名", trigger: "blur" },
    {
      validator: async (rule, value, callback) => {
        if (!value) {
          callback();
          return;
        }

        try {
          const exists = await checkUsernameExists(value);
          if (exists) {
            callback(new Error("用户名已存在"));
          } else {
            callback();
          }
        } catch (error) {
          callback(new Error("校验失败,请重试"));
        }
      },
      trigger: "blur"
    }
  ]
};

7. 唯一性/重复性校验

场景

  • 表格中不允许添加重复的记录
  • 批量导入时检查是否有重复数据

需确认

  • 根据哪个字段判断重复?
  • 发现重复时如何处理?(提示错误?自动去重?)

需要与产品确认

  • 所有必填字段清单
  • 各字段的格式要求
  • 文件上传的类型、大小、数量限制
  • 关联字段的校验规则
  • 哪些字段需要后端校验?
  • 重复数据如何判断和处理?

四、性能与体验类

4.1 缓存策略

原则:合理的缓存能提升用户体验,减少不必要的请求。

1. 查询条件缓存

场景:用户在列表页设置了筛选条件,跳转到详情页后返回

需确认

  • 是否保留之前的筛选条件?
  • 是否保留滚动位置?
  • 缓存有效期多久?(会话级别?永久?)

2. Tab 页切换缓存

场景:页面有多个 Tab,切换 Tab 时是否保留之前 Tab 的数据

选项

  • 选项 A:每次切换都重新请求数据(实时性好)
  • 选项 B:缓存 Tab 数据,不重新请求(性能好)
  • 选项 C:首次加载缓存,提供"刷新"按钮手动更新

3. 弹窗数据缓存

场景:关闭弹窗后再打开

需确认

  • 弹窗关闭后是否清空数据?
  • 如果是编辑弹窗,关闭时未保存的数据如何处理?
    • 直接丢弃
    • 提示用户是否保存
    • 自动保存草稿
<!-- 示例:弹窗关闭确认 -->
<el-dialog v-model="dialogVisible" title="编辑信息" :before-close="handleClose">
  <!-- 表单内容 -->
</el-dialog>

<script setup>
const handleClose = done => {
  if (formChanged.value) {
    ElMessageBox.confirm("数据未保存,确认关闭吗?", "提示", {
      type: "warning"
    })
      .then(() => {
        done();
      })
      .catch(() => {
        // 取消关闭
      });
  } else {
    done();
  }
};
</script>

4. 页面跳转后的返回缓存

场景:列表页 → 详情页 → 返回列表页

需确认

  • 返回时是否保留之前的筛选条件?
  • 是否保留滚动位置?
  • 是否重新请求数据?

实现方式

  • 使用浏览器的前进/后退按钮
  • 使用keep-alive缓存组件
  • 使用 Vuex/Pinia 存储状态

需要与产品确认

  • 哪些页面需要缓存?
  • Tab 切换是否缓存数据?
  • 弹窗关闭后是否清空数据?
  • 页面跳转后返回是否保留状态?

4.2 整体页面/系统级交互

1. 页面刷新

场景

  • 用户在表单填写到一半时刷新页面
  • 用户在编辑过程中误触刷新

需确认

  • 是否需要提示"数据未保存,确认离开吗?"
  • 是否需要自动保存草稿?
// 示例:页面刷新提示
onBeforeUnmount(() => {
  if (formChanged.value) {
    window.onbeforeunload = () => {
      return "数据未保存,确认离开吗?";
    };
  }
});

onUnmounted(() => {
  window.onbeforeunload = null;
});

2. 浏览器前进/后退

需确认

  • 使用浏览器后退按钮时,页面是否恢复之前的状态?
  • 是否禁用浏览器后退?(不推荐)

3. 多标签页数据同步

场景:用户在多个浏览器标签页打开了同一个系统

需确认

  • 在一个标签页操作后,其他标签页是否需要同步?
  • 如何实现同步?(WebSocket、LocalStorage、BroadcastChannel)
// 示例:使用 BroadcastChannel 实现多标签页同步
const channel = new BroadcastChannel("app-data-sync");

// 发送消息
channel.postMessage({ type: "data-update", data: newData });

// 接收消息
channel.onmessage = event => {
  if (event.data.type === "data-update") {
    updateLocalData(event.data.data);
  }
};

需要与产品确认

  • 页面刷新时是否需要提示?
  • 是否需要自动保存草稿?
  • 是否需要多标签页同步?

4.3 默认值

原则:合理的默认值能减少用户操作,提升效率。

常见默认值场景

1. 时间范围默认值

  • 默认今天
  • 默认最近 7 天
  • 默认本月
  • 默认上个月

2. 下拉框默认值

  • 默认选中第一个
  • 默认选中"全部"
  • 默认选中上次的选择(记忆功能)

3. 单选/多选默认值

  • 默认选中哪些选项?
  • 是否必须选中一个?

4. 表单默认值

  • 新增时,哪些字段有默认值?
  • 默认值的来源?(固定值、从其他数据继承、当前用户信息等)

5. 分页默认值

  • 每页显示多少条?(10/20/50?)
  • 默认第几页?

需要与产品确认

  • 时间范围的默认值?
  • 下拉框的默认值?
  • 表单字段的默认值?
  • 分页的默认值?

4.4 提示语与引导

原则:清晰的提示语和引导能降低用户的学习成本。

1. 占位符提示(Placeholder)

要求

  • 简洁明确,说明应该输入什么
  • 可以包含示例值
  • 不要用占位符代替 label
<!-- 好的示例 -->
<el-input v-model="phone" placeholder="请输入手机号,如:13812345678" />

<!-- 不好的示例 -->
<el-input v-model="phone" placeholder="手机号" />

2. 帮助提示(Tooltip)

适用场景

  • 字段含义不明确,需要额外说明
  • 填写规则较复杂
<!-- 示例:帮助提示 -->
<el-form-item label="优惠券">
  <el-input v-model="coupon" />
  <el-tooltip content="输入优惠券代码,可享受折扣优惠" placement="top">
    <el-icon><QuestionFilled /></el-icon>
  </el-tooltip>
</el-form-item>

3. 操作引导

场景

  • 用户首次使用某功能
  • 功能较复杂,需要引导

实现方式

  • 新手引导蒙层(Tour Guide)
  • 空状态页面的引导文案
  • 弹窗提示
<!-- 示例:空状态引导 -->
<el-empty description="还没有任何订单">
  <el-button type="primary" @click="handleCreate">创建第一个订单</el-button>
</el-empty>

4. 错误提示

要求

  • 明确指出错误原因
  • 提供解决方案
  • 语气友好,不要责怪用户
// 好的错误提示
ElMessage.error("手机号格式不正确,请输入11位数字");

// 不好的错误提示
ElMessage.error("错误");
ElMessage.error("格式不对");

5. 成功提示

要求

  • 简洁明确
  • 必要时说明下一步操作
ElMessage.success("保存成功");
ElMessage.success("提交成功,等待审批");

6. 确认提示

场景:删除、提交、离开等重要操作

要求

  • 明确说明操作的后果
  • 提供取消选项
  • 区分不同操作的提示样式
// 示例:删除确认
ElMessageBox.confirm("删除后数据将无法恢复,确认删除吗?", "删除确认", {
  type: "warning",
  confirmButtonText: "确定删除",
  cancelButtonText: "取消"
})
  .then(() => {
    // 执行删除
  })
  .catch(() => {
    // 取消删除
  });

需要与产品确认

  • 所有输入框的 placeholder 文案
  • 哪些字段需要帮助提示?文案是什么?
  • 是否需要新手引导?
  • 各种操作的提示文案

五、权限与安全类

5.1 按钮/功能权限

原则:无权限的功能应该隐藏或禁用,并给予适当提示。

需确认

1. 无权限时如何展示?

  • 选项 A:完全隐藏(推荐)
  • 选项 B:显示但禁用(按钮置灰)
  • 选项 C:显示但点击时提示无权限

2. 页面级权限

  • 无权限访问页面时,跳转到哪里?(403 页面?首页?)
  • 是否需要提示原因?

3. 字段级权限

  • 无权限查看的字段如何处理?(不显示?显示***?)
  • 无权限编辑的字段如何处理?(只读?置灰?)

需要与产品确认

  • 所有按钮的权限标识
  • 无权限时的展示方式
  • 页面级权限的跳转逻辑
  • 字段级权限的处理方式

5.2 数据脱敏

原则:敏感数据需要脱敏处理,保护用户隐私。

常见脱敏字段

  • 手机号138****5678
  • 身份证110101********1234
  • 银行卡6222 **** **** 0123
  • 邮箱abc***@example.com
  • 姓名张*李**(根据姓名长度)
  • 地址:部分脱敏或完全脱敏
// 示例:脱敏工具函数
const maskPhone = phone => {
  if (!phone) return "";
  return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
};

const maskIdCard = idCard => {
  if (!idCard) return "";
  return idCard.replace(/(\d{6})\d{8}(\d{4})/, "$1********$2");
};

const maskBankCard = bankCard => {
  if (!bankCard) return "";
  return bankCard.replace(/(\d{4})\d+(\d{4})/, "$1 **** **** $2");
};

需要与产品确认

  • 哪些字段需要脱敏?
  • 脱敏规则是什么?
  • 是否有"查看完整信息"的权限?

六、总结与检查清单

在需求评审时,可以使用以下检查清单,逐项与产品确认:

✅ 操作反馈类

  • 重复操作防护方案
  • 批量操作的异常处理和反馈机制
  • 操作过程的 loading 展示

✅ 数据展示类

  • 文本超长、图片失败等边界情况处理
  • 空数据状态展示
  • 数据回显规则(异步、级联、权限、实时等)
  • 数据格式化规范(数字、货币、日期、百分比等)

✅ 交互逻辑类

  • 状态切换与联动规则
  • 表单校验规则(必填、格式、范围、关联、业务逻辑等)

✅ 性能与体验类

  • 缓存策略(查询条件、Tab 切换、弹窗数据等)
  • 页面刷新和路由跳转的数据保留
  • 默认值设置
  • 提示语和引导文案

✅ 权限与安全类

  • 按钮和功能的权限控制
  • 页面和字段的权限控制
  • 敏感数据脱敏规则

七、附录:常用工具函数

// utils/format.ts

/**
 * 数字格式化:添加千分位
 */
export const formatNumber = (num: number, decimals: number = 2): string => {
  if (!num && num !== 0) return "--";
  return num.toLocaleString("zh-CN", {
    minimumFractionDigits: decimals,
    maximumFractionDigits: decimals
  });
};

/**
 * 货币格式化
 */
export const formatCurrency = (amount: number, currency: string = "CNY"): string => {
  if (!amount && amount !== 0) return "--";
  return new Intl.NumberFormat("zh-CN", {
    style: "currency",
    currency: currency
  }).format(amount);
};

/**
 * 百分比格式化
 */
export const formatPercent = (value: number, decimals: number = 2): string => {
  if (!value && value !== 0) return "--";
  return `${(value * 100).toFixed(decimals)}%`;
};

/**
 * 日期格式化
 */
export const formatDate = (date: string | Date, format: string = "YYYY-MM-DD"): string => {
  if (!date) return "--";
  return dayjs(date).format(format);
};

/**
 * 手机号脱敏
 */
export const maskPhone = (phone: string): string => {
  if (!phone) return "--";
  return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
};

/**
 * 身份证脱敏
 */
export const maskIdCard = (idCard: string): string => {
  if (!idCard) return "--";
  return idCard.replace(/(\d{6})\d{8}(\w{4})/, "$1********$2");
};

/**
 * 文本超长截断
 */
export const ellipsis = (text: string, maxLength: number = 20): string => {
  if (!text) return "";
  return text.length > maxLength ? text.slice(0, maxLength) + "..." : text;
};

/**
 * 文件大小格式化
 */
export const formatFileSize = (bytes: number): string => {
  if (!bytes) return "0 B";
  const k = 1024;
  const sizes = ["B", "KB", "MB", "GB", "TB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return (bytes / Math.pow(k, i)).toFixed(2) + " " + sizes[i];
};

/**
 * 防抖
 */
export const debounce = (fn: Function, delay: number = 300) => {
  let timer: NodeJS.Timeout | null = null;
  return function (this: any, ...args: any[]) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};

/**
 * 节流
 */
export const throttle = (fn: Function, delay: number = 300) => {
  let last = 0;
  return function (this: any, ...args: any[]) {
    const now = Date.now();
    if (now - last > delay) {
      fn.apply(this, args);
      last = now;
    }
  };
};

八、结语

前端交互设计不仅仅是写代码,更多的是站在用户的角度思考问题。一个好的交互能让用户感觉流畅自然,而一个糟糕的交互会让用户感到困惑和挫败。

在需求评审阶段,前端工程师应该主动提出这些交互问题,与产品经理充分沟通,明确每一个细节。这样不仅能提高开发效率,减少返工,还能提升产品的用户体验。

记住:细节决定成败,交互设计无小事。


本文档持续更新中,欢迎补充和完善。