NProgress 是轻量级、高颜值的进度条库(仅 2KB),在 CRM 系统中主要用于全局接口请求加载路由跳转加载大文件上传 / 下载进度等场景,能直观反馈操作进度,提升用户体验。下面从「核心集成→Vue3 实战配置→CRM 高频场景→定制化」全维度讲解。

一、核心定位(CRM 为什么需要 NProgress?)

CRM 系统的核心痛点:

  1. 无感加载:接口请求 / 路由跳转耗时较长时,用户不知道是否在加载,易重复操作;
  2. 进度反馈:大文件上传(如合同、物料图片)、批量数据导出需展示实时进度;
  3. 全局统一:所有加载场景用统一的进度条样式,保持视觉一致性;
  4. 轻量无侵入:体积小,接入成本低,不影响核心业务逻辑。

NProgress 核心优势:

  • 纯 JS 实现,无依赖,支持 Vue/React/ 原生 JS;
  • 可自定义颜色、速度、位置、动画;
  • 支持手动控制进度(0-100%),适配异步操作;
  • 样式简洁,可快速融入 CRM 品牌风格。

二、快速集成(Vue3 + Vite 项目)

1. 安装依赖

bash

运行

# 核心库
npm install nprogress -S
# TS 类型(可选)
npm install @types/nprogress -D
# 引入样式(必须!)
import 'nprogress/nprogress.css';

2. 封装通用进度条工具(CRM 首选)

封装统一的 NProgress 工具类,避免重复调用,同时统一配置全局样式:

typescript

运行

// src/utils/nprogress.ts
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';

// NProgress 全局配置
NProgress.configure({
  easing: 'ease', // 动画方式
  speed: 500, // 进度条速度(ms)
  showSpinner: false, // 隐藏加载转圈图标(CRM 更简洁)
  trickleSpeed: 80, // 自动递增速度(ms)
  minimum: 0.1, // 最小进度值(避免进度条瞬间消失)
});

/**
 * 启动进度条
 * @param percentage 初始进度(可选,默认自动递增)
 */
export const startProgress = (percentage?: number) => {
  NProgress.start();
  if (percentage !== undefined && percentage >= 0 && percentage <= 1) {
    NProgress.set(percentage);
  }
};

/**
 * 设置进度条进度
 * @param percentage 进度值(0-1)
 */
export const setProgress = (percentage: number) => {
  if (percentage < 0) percentage = 0;
  if (percentage > 1) percentage = 1;
  NProgress.set(percentage);
};

/**
 * 结束进度条(自动完成到100%)
 */
export const doneProgress = () => {
  NProgress.done();
};

/**
 * 中断进度条(立即隐藏)
 */
export const stopProgress = () => {
  NProgress.done(true);
};

// 导出原始 NProgress 实例(自定义扩展用)
export default NProgress;

3. 全局样式定制(适配 CRM 品牌)

修改 NProgress 默认样式,贴合 CRM 主题色(如蓝色):

css

/* src/styles/nprogress.css */
/* 进度条背景 */
#nprogress .bar {
  background: #1890ff !important; /* CRM 主色 */
  height: 3px !important; /* 进度条高度 */
}

/* 进度条阴影(可选) */
#nprogress .peg {
  box-shadow: 0 0 10px #1890ff, 0 0 5px #1890ff !important;
}

/* 隐藏默认转圈图标(已在配置中关闭,此处兜底) */
#nprogress .spinner {
  display: none !important;
}

main.ts 引入自定义样式(覆盖默认样式):

typescript

运行

import { createApp } from 'vue';
import App from './App.vue';
// 先引入默认样式,再引入自定义样式
import 'nprogress/nprogress.css';
import '@/styles/nprogress.css';

const app = createApp(App);
app.mount('#app');

三、CRM 实战场景(核心用法)

场景 1:路由跳转进度条(全局)

CRM 路由跳转(如从客户列表到订单详情)时展示进度条,适配异步路由加载:

typescript

运行

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { startProgress, doneProgress } from '@/utils/nprogress';
import routes from './routes';

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
});

// 路由开始跳转时启动进度条
router.beforeEach((to, from, next) => {
  startProgress();
  next();
});

// 路由跳转完成后结束进度条
router.afterEach(() => {
  // 延迟结束(避免进度条闪一下,提升体验)
  setTimeout(() => {
    doneProgress();
  }, 300);
});

// 路由跳转失败时中断进度条
router.onError(() => {
  stopProgress();
});

export default router;

场景 2:全局接口请求进度条(Axios 拦截器)

CRM 所有接口请求统一展示进度条,支持批量请求合并进度:

typescript

运行

// src/utils/axios.ts
import axios from 'axios';
import { startProgress, setProgress, doneProgress, stopProgress } from '@/utils/nprogress';
import { ElMessage } from 'element-plus';

// 记录当前请求数(避免多个请求重复启动/结束进度条)
let requestCount = 0;

// 创建 Axios 实例
const service = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL,
  timeout: 10000,
});

// 请求拦截器:启动进度条
service.interceptors.request.use(
  (config) => {
    requestCount++;
    // 第一个请求启动进度条
    if (requestCount === 1) {
      startProgress();
    }
    // 自定义配置:是否显示进度条(默认显示)
    if (config.headers?.hideProgress) {
      return config;
    }
    return config;
  },
  (error) => {
    requestCount--;
    if (requestCount === 0) {
      stopProgress();
    }
    ElMessage.error('请求异常:' + error.message);
    return Promise.reject(error);
  }
);

// 响应拦截器:结束进度条
service.interceptors.response.use(
  (response) => {
    requestCount--;
    // 最后一个请求结束进度条
    if (requestCount === 0) {
      doneProgress();
    }
    return response.data;
  },
  (error) => {
    requestCount--;
    if (requestCount === 0) {
      stopProgress();
    }
    ElMessage.error('请求失败:' + (error.response?.data?.msg || error.message));
    return Promise.reject(error);
  }
);

// 扩展:支持手动设置请求进度(如大文件上传)
service.interceptors.request.use((config) => {
  // 如果配置了 onUploadProgress,则手动更新进度条
  if (config.onUploadProgress) {
    const originalOnUploadProgress = config.onUploadProgress;
    config.onUploadProgress = (progressEvent) => {
      const percent = progressEvent.loaded / progressEvent.total;
      setProgress(percent);
      originalOnUploadProgress(progressEvent);
    };
  }
  return config;
});

export default service;

场景 3:大文件上传 / 下载进度(手动控制)

CRM 上传合同 / 物料图片、下载批量数据时,展示实时进度:

vue

<template>
  <div class="file-upload">
    <input
      type="file"
      accept=".pdf,.jpg,.png"
      @change="handleFileUpload"
    />
    <!-- 进度条展示(可选:复用 NProgress 或单独展示) -->
    <div class="upload-progress" v-if="uploading">
      <span>上传进度:{{ uploadPercent }}%</span>
      <el-progress :percentage="uploadPercent" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { startProgress, setProgress, doneProgress, stopProgress } from '@/utils/nprogress';
import { uploadToOss } from '@/utils/oss';
import { ElMessage } from 'element-plus';

const uploading = ref(false);
const uploadPercent = ref(0);

// 大文件上传(带进度)
const handleFileUpload = async (e) => {
  const file = e.target.files[0];
  if (!file) return;
  
  uploading.value = true;
  uploadPercent.value = 0;
  // 启动进度条
  startProgress(0);

  try {
    // 调用 OSS 上传,传入进度回调
    await uploadToOss(
      file,
      'crm/contract/',
      (percent) => {
        uploadPercent.value = percent;
        // 手动更新 NProgress 进度
        setProgress(percent / 100);
      }
    );
    // 完成进度条
    doneProgress();
    ElMessage.success('文件上传成功');
  } catch (err) {
    // 中断进度条
    stopProgress();
    ElMessage.error('文件上传失败:' + (err as Error).message);
  } finally {
    uploading.value = false;
    uploadPercent.value = 0;
    e.target.value = '';
  }
};
</script>

<style scoped>
.file-upload {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.upload-progress {
  width: 100%;
  gap: 8px;
  display: flex;
  flex-direction: column;
}
</style>

场景 4:批量数据处理进度(手动控制)

CRM 批量导入客户、批量更新订单时,展示处理进度:

vue

<template>
  <button 
    class="btn-crm" 
    @click="handleBatchImport"
    :disabled="processing"
  >
    批量导入客户
  </button>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { startProgress, setProgress, doneProgress } from '@/utils/nprogress';
import { ElMessage } from 'element-plus';

const processing = ref(false);

// 批量导入客户(模拟分步处理)
const handleBatchImport = async () => {
  processing.value = true;
  startProgress(0);

  try {
    // 步骤1:读取文件(20%)
    await new Promise(resolve => setTimeout(resolve, 500));
    setProgress(0.2);

    // 步骤2:解析数据(50%)
    await new Promise(resolve => setTimeout(resolve, 800));
    setProgress(0.5);

    // 步骤3:验证数据(80%)
    await new Promise(resolve => setTimeout(resolve, 600));
    setProgress(0.8);

    // 步骤4:提交数据(100%)
    await new Promise(resolve => setTimeout(resolve, 700));
    doneProgress();

    ElMessage.success('批量导入客户成功');
  } catch (err) {
    stopProgress();
    ElMessage.error('批量导入失败:' + (err as Error).message);
  } finally {
    processing.value = false;
  }
};
</script>

四、核心避坑点

1. 进度条重复启动 / 结束

  • 问题:多个接口同时请求时,进度条刚结束又立即启动,闪跳严重;
  • 解决方案:
    1. requestCount 计数,仅第一个请求启动、最后一个请求结束;
    2. 路由跳转进度条延迟结束(如 300ms),避免闪跳。

2. 进度条无法隐藏

  • 问题:请求失败 / 中断后,进度条卡在中间不消失;
  • 解决方案:
    1. 异常场景调用 stopProgress()NProgress.done(true)),立即隐藏;
    2. 给所有异步操作加 finally,兜底重置进度条。

3. 样式覆盖不生效

  • 问题:自定义的进度条颜色 / 高度不生效;
  • 解决方案:
    1. 自定义样式文件放在默认样式之后引入;
    2. 样式加 !important 强制覆盖;
    3. 检查 CSS 选择器优先级(#nprogress .bar 优先级高于 .bar)。

4. 移动端进度条适配问题

  • 问题:移动端进度条显示不全、位置偏移;
  • 解决方案:
    1. 进度条高度设为 2-3px(适配移动端);
    2. 禁用 showSpinner,避免移动端布局错乱;
    3. 配置 minimum: 0.05,移动端更灵敏。

5. 进度条速度过快 / 过慢

  • 问题:短时间请求进度条刚显示就消失,体验差;
  • 解决方案:
    1. 路由跳转进度条延迟结束(300-500ms);
    2. 调整 speed(500ms)和 trickleSpeed(80ms);
    3. 手动设置初始进度(如 startProgress(0.1))。

五、定制化扩展(CRM 进阶)

1. 多主题进度条(适配深色模式)

根据 CRM 主题切换进度条颜色:

typescript

运行

// src/utils/nprogress.ts
/**
 * 切换进度条主题
 * @param theme 主题(light/dark)
 */
export const switchProgressTheme = (theme: 'light' | 'dark') => {
  const bar = document.getElementById('nprogress')?.querySelector('.bar');
  if (!bar) return;
  if (theme === 'dark') {
    bar.style.background = '#40a9ff'; // 深色模式主色
    bar.style.boxShadow = '0 0 10px #40a9ff, 0 0 5px #40a9ff';
  } else {
    bar.style.background = '#1890ff'; // 浅色模式主色
    bar.style.boxShadow = '0 0 10px #1890ff, 0 0 5px #1890ff';
  }
};

// 主题切换时调用
// watch(theme, (val) => {
//   switchProgressTheme(val);
// });

2. 进度条回调(自定义操作)

监听进度条状态变化,执行自定义逻辑:

typescript

运行

// 监听进度条开始
NProgress.on('start', () => {
  console.log('进度条启动');
  // 禁用页面滚动(可选)
  document.body.style.overflow = 'hidden';
});

// 监听进度条结束
NProgress.on('done', () => {
  console.log('进度条结束');
  // 恢复页面滚动
  document.body.style.overflow = 'auto';
});

六、总结(CRM 开发最佳实践)

  1. 封装统一工具:将 NProgress 封装为全局工具函数,统一配置和样式,避免重复代码;
  2. 场景适配
    • 路由跳转:全局路由拦截,延迟结束进度条;
    • 接口请求:Axios 拦截器计数,批量请求合并进度;
    • 大文件 / 批量操作:手动控制进度,展示实时百分比;
  1. 体验优化
    • 避免进度条闪跳:计数控制启动 / 结束,延迟结束;
    • 异常兜底:所有异步操作加 finally,中断进度条;
    • 样式定制:贴合 CRM 品牌色,移动端适配高度;
  1. 避坑核心
    • 用请求计数解决重复启动 / 结束问题;
    • 自定义样式加 !important 强制覆盖;
    • 异常场景调用 stopProgress() 立即隐藏。

NProgress 是 Vue3 + CRM 进度反馈的 “最优解”,接入成本低、体验好,只需封装一次即可覆盖所有加载场景;结合路由、Axios 拦截器,能实现全局统一的进度反馈,大幅提升 CRM 系统的用户体验。

更多推荐