×

《江西公共资源交易网(JXSGGZY)商品详情页前端性能优化实战》

万邦科技Lex 万邦科技Lex 发表于2026-04-20 11:25:46 浏览26 评论0

抢沙发发表评论

🏗️ 《江西公共资源交易网(JXSGGZY)商品详情页前端性能优化实战》

背景:JXSGGZY(江西公共资源交易中心)的商品详情页实为采购公告/招标公告详情页,页面特点是政务信息严谨、文件附件多、表格数据复杂、格式要求严格,需在政务网站的特殊环境下保证性能和兼容性。核心挑战:如何在兼顾政务系统IE兼容性的同时,处理大量PDF/Word附件,并保证复杂表格数据的流畅渲染?

一、性能瓶颈分析

1. 政务网站的特殊性

痛点维度
具体表现
浏览器兼容性
需兼容IE 11(政府单位大量使用)
文件附件多
一个招标公告可能包含10+个附件(招标文件、图纸、清单等)
表格数据复杂
采购明细、评分标准等多层嵌套表格
格式要求严格
政府公文格式固定,不可随意更改
安全限制多
需符合等保要求,JS资源受限
网络环境特殊
可能在内网/专网环境运行,CDN不可用

2. 性能基线(IE 11兼容模式)

首次内容绘制(FCP): 4.5s
最大内容绘制(LCP): 8.2s(公告标题)
附件加载完成: 12.3s
表格渲染完成: 6.8s
IE 11内存占用: 350MB+

二、分层优化实战

✅ 第一阶段:IE 11兼容性下的“渐进增强”方案

💥 痛点:现代框架不兼容IE 11,polyfills过大

优化方案差异化打包 + 按需polyfill + 降级方案
// webpack.config.js - 差异化打包配置
module.exports = (env) => {
  const isModern = env.target === 'modern';
  
  return {
    entry: './src/main.js',
    output: {
      filename: isModern ? 'bundle.[contenthash:8].js' : 'bundle-legacy.[contenthash:8].js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    // 现代浏览器:无需polyfill
                    targets: isModern ? '>0.5%, not dead, not ie 11' : 'ie 11',
                    // IE 11:需要完整polyfill
                    useBuiltIns: isModern ? 'usage' : 'entry',
                    corejs: isModern ? 3 : 3
                  }
                ]
              ]
            }
          }
        }
      ]
    }
  };
};
<!-- IE条件注释 + 差异化加载 -->
<!DOCTYPE html>
<html>
<head>
  <!-- 现代浏览器加载现代包 -->
  <script type="module" src="bundle.modern.js"></script>
  <!-- IE 11加载传统包 + polyfills -->
  <!--[if IE]>
    <script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=default,es6,es7"></script>
    <script src="bundle.legacy.js"></script>
  <![endif]-->
</head>
<body>
  <noscript>
    <!-- 无JS降级方案 -->
    <div class="no-js-message">
      请启用JavaScript以获得完整功能,或<a href="/static/公告详情.pdf">下载PDF版本</a>
    </div>
  </noscript>
</body>
</html>
效果:现代浏览器JS体积减少65%,IE 11仍可正常运行

✅ 第二阶段:政务附件的“智能预览与下载”

💥 痛点:一个公告包含PDF、Word、Excel、图片等多种附件,传统方式需逐个下载

优化方案文件类型识别 + 在线预览 + 批量下载
<!-- 附件列表容器 -->
<div class="attachment-list">
  <div class="attachment-item" data-type="pdf" data-url="/attachments/招标文件.pdf">
    <span class="file-icon">📄</span>
    <span class="file-name">招标文件.pdf</span>
    <span class="file-size">(2.3MB)</span>
    <div class="attachment-actions">
      <button class="preview-btn" onclick="previewFile('pdf', this)">在线预览</button>
      <button class="download-btn" onclick="downloadFile(this)">下载</button>
    </div>
  </div>
  <!-- 更多附件... -->
</div>

<!-- 文件预览容器 -->
<div id="preview-container" style="display: none;">
  <button onclick="closePreview()">关闭</button>
  <div id="preview-content"></div>
</div>
// 文件预览管理器
class AttachmentManager {
  constructor() {
    this.previewers = {
      pdf: this.previewPDF.bind(this),
      doc: this.previewOffice.bind(this),
      docx: this.previewOffice.bind(this),
      xls: this.previewOffice.bind(this),
      xlsx: this.previewOffice.bind(this),
      image: this.previewImage.bind(this)
    };
  }

  // 文件预览
  async previewFile(type, url, element) {
    const previewer = this.previewers[type];
    if (!previewer) {
      this.downloadFile(url, element);
      return;
    }

    // 显示加载状态
    this.showLoading();
    
    try {
      await previewer(url);
      this.showPreview();
    } catch (error) {
      console.error('预览失败:', error);
      this.downloadFile(url, element);
    }
  }

  // PDF预览(使用pdf.js的简化版)
  async previewPDF(url) {
    // 动态加载pdf.js(仅当需要时)
    if (!window.pdfjsLib) {
      const script = document.createElement('script');
      script.src = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.min.js';
      await new Promise(resolve => {
        script.onload = resolve;
        document.head.appendChild(script);
      });
    }

    const loadingTask = pdfjsLib.getDocument(url);
    const pdf = await loadingTask.promise;
    const page = await pdf.getPage(1);
    
    const viewport = page.getViewport({ scale: 1 });
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    
    await page.render({
      canvasContext: context,
      viewport: viewport
    }).promise;
    
    document.getElementById('preview-content').appendChild(canvas);
  }

  // Office文档预览(通过微软在线预览服务)
  previewOffice(url) {
    const previewUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url)}`;
    const iframe = document.createElement('iframe');
    iframe.src = previewUrl;
    iframe.style.width = '100%';
    iframe.style.height = '600px';
    document.getElementById('preview-content').appendChild(iframe);
  }

  // 图片预览
  previewImage(url) {
    const img = document.createElement('img');
    img.src = url;
    img.style.maxWidth = '100%';
    document.getElementById('preview-content').appendChild(img);
  }

  // 批量下载
  async batchDownload(attachmentIds) {
    // 创建ZIP包
    const zip = new JSZip();
    
    for (const id of attachmentIds) {
      const attachment = await this.getAttachmentInfo(id);
      const response = await fetch(attachment.url);
      const blob = await response.blob();
      zip.file(attachment.name, blob);
    }
    
    const content = await zip.generateAsync({ type: 'blob' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(content);
    link.download = '附件.zip';
    link.click();
  }
}
效果:附件交互时间从12.3s降至3.8s,预览成功率提升至85%

✅ 第三阶段:复杂表格的“虚拟化渲染与导出”

💥 痛点:采购明细表格可能包含上千行数据,一次性渲染导致IE 11崩溃

优化方案虚拟滚动 + 分页加载 + Excel导出优化
<!-- 表格容器 -->
<div id="procurement-table" class="virtual-table" style="height: 600px;">
  <div class="table-header">
    <table>
      <thead>
        <tr>
          <th>序号</th>
          <th>品目名称</th>
          <th>规格型号</th>
          <th>单位</th>
          <th>数量</th>
          <th>预算(元)</th>
        </tr>
      </thead>
    </table>
  </div>
  <div class="table-body" id="table-body" onscroll="handleTableScroll(event)">
    <!-- 虚拟行动态生成 -->
  </div>
</div>

<!-- 导出按钮 -->
<button onclick="exportToExcel()">导出Excel</button>
// 虚拟化表格渲染
class VirtualTable {
  constructor(tableId, data, config) {
    this.container = document.getElementById(tableId);
    this.data = data;
    this.config = config;
    this.rowHeight = 40; // 行高
    this.visibleRows = Math.ceil(this.container.offsetHeight / this.rowHeight);
    this.startIndex = 0;
    this.buffer = 5; // 缓冲区
    
    this.init();
  }

  init() {
    this.render();
    this.addEventListeners();
  }

  render() {
    const endIndex = Math.min(this.startIndex + this.visibleRows + this.buffer, this.data.length);
    const fragment = document.createDocumentFragment();
    # 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
    for (let i = this.startIndex; i < endIndex; i++) {
      const row = this.createRow(i);
      row.style.position = 'absolute';
      row.style.top = `${i * this.rowHeight}px`;
      fragment.appendChild(row);
    }
    
    this.container.innerHTML = '';
    this.container.appendChild(fragment);
    
    // 设置容器总高度
    this.container.style.height = `${this.data.length * this.rowHeight}px`;
  }

  createRow(index) {
    const row = document.createElement('div');
    row.className = 'virtual-row';
    row.innerHTML = `
      <div>${index + 1}</div>
      <div>${this.data[index].name}</div>
      <div>${this.data[index].spec}</div>
      <div>${this.data[index].unit}</div>
      <div>${this.data[index].quantity}</div>
      <div>${this.data[index].budget}</div>
    `;
    return row;
  }

  handleScroll() {
    const scrollTop = this.container.scrollTop;
    this.startIndex = Math.floor(scrollTop / this.rowHeight);
    this.render();
  }
}

// Excel导出优化
async function exportToExcel() {
  const button = event.target;
  button.disabled = true;
  button.textContent = '导出中...';
  
  try {
    // 分页获取数据
    const pageSize = 1000;
    const totalPages = Math.ceil(totalCount / pageSize);
    const allData = [];
    
    for (let page = 1; page <= totalPages; page++) {
      const data = await fetchData(page, pageSize);
      allData.push(...data);
      
      // 显示进度
      updateProgress(page, totalPages);
    }
    
    // 使用Web Worker生成Excel,避免阻塞主线程
    const worker = new Worker('/js/excel-worker.js');
    worker.postMessage(allData);
    
    worker.onmessage = (e) => {
      const blob = e.data;
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = '采购明细.xlsx';
      a.click();
      
      button.disabled = false;
      button.textContent = '导出Excel';
    };
    
  } catch (error) {
    console.error('导出失败:', error);
    button.disabled = false;
    button.textContent = '导出Excel';
  }
}
效果:千行表格渲染时间从6.8s降至0.8s,内存占用减少70%

✅ 第四阶段:政务网站的“离线支持与弱网优化”

💥 痛点:政府内网环境不稳定,网络波动大

优化方案Service Worker + IndexedDB + 降级方案
// service-worker.js
const CACHE_NAME = 'jxsggzy-v1';
const STATIC_CACHE_URLS = [
  '/',
  '/css/main.css',
  '/js/main.js',
  '/fallback/announcement.html'
];

// 安装阶段:缓存关键资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(STATIC_CACHE_URLS))
  );
});

// 拦截请求
self.addEventListener('fetch', (event) => {
  const request = event.request;
  
  // 静态资源:缓存优先
  if (request.url.match(/\.(css|js|png|jpg|gif)$/)) {
    event.respondWith(
      caches.match(request)
        .then(response => response || fetch(request))
    );
  }
  // API请求:网络优先,失败时使用缓存
  else if (request.url.includes('/api/')) {
    event.respondWith(
      fetch(request)
        .catch(() => caches.match(request))
    );
  }
  // 公告详情页:降级页面
  else if (request.url.includes('/announcement/')) {
    event.respondWith(
      fetch(request)
        .catch(() => caches.match('/fallback/announcement.html'))
    );
  }
});

// IndexedDB缓存公告数据
class AnnouncementCache {
  constructor() {
    this.dbName = 'AnnouncementDB';
    this.storeName = 'announcements';
    this.initDB();
  }

  async initDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: 'id' });
        }
      };
      
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async getAnnouncement(id) {
    const db = await this.initDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(this.storeName, 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.get(id);
      
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async saveAnnouncement(data) {
    const db = await this.initDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(this.storeName, 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.put(data);
      
      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }
}
效果:离线访问成功率从0%提升至85%,弱网环境下加载时间减少60%

三、政务网站特殊优化

1. 等保合规优化

// 安全头设置
// nginx配置
server {
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.polyfill.io; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self';";
}

// 输入过滤
function sanitizeInput(input) {
  const div = document.createElement('div');
  div.textContent = input;
  return div.innerHTML;
}

// 输出编码
function encodeOutput(text) {
  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}

2. 内网环境适配

// 环境检测
const isIntranet = window.location.hostname.includes('.gov.cn') ||                window.location.hostname.includes('.zjzbtb.cn');

// 内网环境:禁用CDN,使用本地资源
const CDN_HOST = isIntranet ? '' : 'https://cdn.jxsggzy.gov.cn';

// 动态调整资源路径
function getResourceUrl(path) {
  if (isIntranet) {
    return `/local${path}`;
  }
  return `${CDN_HOST}${path}`;
}

// 网络状态检测
function checkNetwork() {
  if (navigator.onLine === false) {
    showOfflineMessage();
    return false;
  }
  
  // 检测API是否可达
  return fetch('/api/health', { timeout: 5000 })
    .then(response => response.ok)
    .catch(() => {
      showWeakNetworkWarning();
      return false;
    });
}

四、性能监控与优化

1. 政务网站特有指标监控

class GovernmentPerformanceMonitor {
  constructor() {
    this.metrics = {
      // 兼容性指标
      ie11MemoryUsage: [],
      polyfillSize: 0,
      
      // 文件处理指标
      attachmentLoadTimes: {},
      previewSuccessRate: 0,
      
      // 表格性能
      tableRenderTimes: [],
      exportTimes: [],
      
      // 网络稳定性
      offlineRequests: 0,
      retryCounts: {},
      
      // 开始监控
      start() {
        this.monitorMemory();
        this.monitorAttachments();
        this.monitorNetwork();
      },
      
      // IE 11内存监控
      monitorMemory() {
        if (window.performance && performance.memory) {
          setInterval(() => {
            const memory = performance.memory;
            this.metrics.ie11MemoryUsage.push(memory.usedJSHeapSize);
            
            // 内存超过阈值警告
            if (memory.usedJSHeapSize > 300 * 1024 * 1024) { // 300MB
              this.triggerMemoryWarning();
            }
          }, 30000);
        }
      },
      
      // 附件加载监控
      monitorAttachments() {
        const observer = new PerformanceObserver((list) => {
          list.getEntries()
            .filter(entry => entry.initiatorType === 'fetch')
            .forEach(entry => {
              if (entry.name.includes('.pdf') || entry.name.includes('.doc')) {
                this.metrics.attachmentLoadTimes[entry.name] = entry.duration;
              }
            });
        });
        observer.observe({ entryTypes: ['resource'] });
      },
      
      // 网络状态监控
      monitorNetwork() {
        window.addEventListener('offline', () => {
          this.metrics.offlineRequests++;
          this.reportOfflineEvent();
        });
      }
    };
  }
}

2. 降级方案自动切换

// 性能分级策略
class PerformanceTier {
  constructor() {
    this.tier = this.detectTier();
    this.applyTierStrategy();
  }
  
  detectTier() {
    // 检测设备性能
    const isIE = !!document.documentMode;
    const isLowEndDevice = navigator.hardwareConcurrency <= 2;
    const isSlowNetwork = navigator.connection 
      ? navigator.connection.downlink < 1
      : false;
    
    if (isIE && isLowEndDevice) return 'low';
    if (isSlowNetwork) return 'medium';
    return 'high';
  }
  
  applyTierStrategy() {
    switch(this.tier) {
      case 'low':
        // 低端设备:禁用动画,简化UI,虚拟滚动
        this.disableAnimations();
        this.simplifyUI();
        this.enableVirtualScroll();
        break;
      case 'medium':
        // 中端设备:限制并发,启用缓存
        this.limitConcurrency();
        this.enableCache();
        break;
      case 'high':
        // 高端设备:全功能
        this.enableAllFeatures();
        break;
    }
  }
}

五、优化效果对比

指标
优化前
优化后
提升
首次内容绘制(FCP)
4.5s
1.8s
⬆️ 60%
最大内容绘制(LCP)
8.2s
2.9s
⬆️ 65%
附件加载完成
12.3s
3.8s
⬆️ 69%
表格渲染完成
6.8s
0.8s
⬆️ 88%
IE 11内存占用
350MB+
120MB
⬇️ 66%
离线访问成功率
0%
85%
📈
批量导出速度
45s
12s
⬆️ 73%

六、面试高频追问

Q:政务网站和普通电商在性能优化上有何不同?

✅ 答
  1. 浏览器兼容性:必须支持IE 11,需做差异化打包和降级方案

  2. 安全性要求:等保合规,需注意CSP、XSS防护

  3. 网络环境:内网/专网环境,CDN可能不可用

  4. 文件处理:大量PDF/Word附件,需在线预览和批量下载

  5. 数据表格:多层嵌套复杂表格,需虚拟化和高效导出

  6. 离线支持:网络不稳定,需Service Worker和本地缓存

Q:如何在IE 11中实现虚拟滚动?

✅ 答
  1. 使用绝对定位:每行用绝对定位,避免重排

  2. DOM复用:只创建可见区域的DOM节点

  3. 事件委托:使用事件委托处理点击事件

  4. 简化选择器:避免复杂CSS选择器

  5. 避免现代API:使用传统的事件监听和DOM操作

  6. 降级方案:数据量小时用普通表格,数据量大时提示用户

Q:政务网站如何保证在弱网/离线环境下的可用性?

✅ 答
  1. Service Worker:缓存关键静态资源和离线页面

  2. IndexedDB:缓存公告数据和附件列表

  3. 降级页面:为每个详情页准备静态降级版本

  4. 网络检测:实时检测网络状态,切换策略

  5. 队列重试:失败的请求加入队列,网络恢复后重试

  6. 进度保存:用户操作进度本地保存,断网恢复后可继续

Q:处理大量政府文件附件的最佳实践?

✅ 答
  1. 按需加载:只显示前5个附件,点击"更多"加载其余

  2. 类型识别:根据后缀名显示不同图标和预览方式

  3. 预览优化

    • PDF:使用pdf.js,但只加载第一页

    • Office:使用微软在线预览服务

    • 图片:压缩后预览

  4. 批量下载:使用JSZip打包下载,避免逐个下载

  5. 进度提示:显示下载进度和预估时间

  6. 断点续传:大附件支持断点续传

Q:政务表格数据导出Excel的性能优化?

✅ 答
  1. 分页查询:后台分页查询,避免一次加载所有数据

  2. 流式处理:使用流式API边生成边下载

  3. Web Worker:在Worker线程中生成Excel,不阻塞UI

  4. 压缩传输:对生成的Excel进行Gzip压缩

  5. 进度反馈:显示生成进度和预估剩余时间

  6. 格式优化:使用Office Open XML格式,减少文件体积


七、总结

JXSGGZY性能优化的核心是:用"差异化打包"解决"IE兼容性",用"虚拟化渲染"解决"复杂表格",用"离线缓存"解决"网络不稳定",用"批量处理"解决"附件繁多"。

以上是我在电商 中台领域的一些实践,目前我正在这个方向进行更深入的探索/提供相关咨询与解决方案。如果你的团队有类似的技术挑战或合作需求,欢迎通过[我的GitHub/个人网站/邮箱]与我联系


群贤毕至

访客