在开发 uni-app + Vue3 的 H5 项目时,我们经常会遇几个需求:

  • 打包后想在本地预览,保证生产环境下资源加载正常;
  • API 请求需要转发到后端,防止跨域;
  • 希望能 一键启动本地 HTTPS 服务器,支持电脑和手机访问。

本文结合 Windows + iOS 手机 场景,介绍完整流程。


一、问题背景

1. 打包后的 H5 项目预览

通常我们有两种方式:

  • 开发模式
npm run dev:h5

支持 HMR、热更新,但不是生产环境。

  • 打包 + 预览
npm run build:h5
npm run preview

模拟真实生产环境,查看打包后的 dist/build/h5 文件。

痛点

  1. vite preview 下的 API 代理失效;
  2. HTTP 页面访问 HTTPS 后端接口会出现跨域或安全警告;
  3. 局域网手机访问不支持 HTTPS,浏览器报 NET::ERR_CERT_AUTHORITY_INVALID

2. 跨域问题

浏览器同源策略会阻止从 HTTP 页面请求 HTTPS 接口:

Access to fetch at 'https://api.xxx.com'
from origin 'http://localhost:8080' has been blocked by CORS policy

解决方案:

  • 后端配置 CORS(成本高,需协调后端)
  • 本地代理转发(推荐,透明代理绕过浏览器限制)

二、解决方案核心思路

  1. 打包项目 → 生成 dist/build/h5
  2. Express 本地服务器托管静态资源
  3. http-proxy-middleware 转发 API
  4. HTTPS 支持 + mkcert 生成自签证书
  5. 支持电脑和手机局域网访问
  6. 一键启动 + 自动打开浏览器

三、准备工作

1. 安装依赖

npm install express http-proxy-middleware serve-static open --save-dev

依赖说明

  • express:本地服务器
  • http-proxy-middleware:接口代理
  • serve-static:托管静态文件
  • open:自动打开浏览器
  • mkcert:生成受信任自签证书

2. Windows 下生成 HTTPS 证书

参考:https://cloud.tencent.com/developer/article/2191830
下载 mkcert-v1.4.4-windows-amd64.exe

  • 打开 CMD 或 PowerShell
  • 安装 mkcert 根证书(第一次使用)
mkcert-v1.4.4-windows-amd64.exe -install

在这里插入图片描述

  • 查看证书所处位置
mkcert-v1.4.4-windows-amd64.exe -CAROOT
//C:\Users\Administrator\AppData\Local\mkcert

在这里插入图片描述

  • 生成包含局域网 IP 的证书(假设本机局域网 IP 为 192.168.31.161):
mkcert-v1.4.4-windows-amd64.exe localhost 127.0.0.1 192.168.31.161

在这里插入图片描述

localhost+2.pem         // 证书
localhost+2-key.pem     // 私钥
rootCA.pem              // 根证书

⚠️ 注意:

  • iOS 手机只需要安装 rootCA.pem,不要安装具体域名证书。
  • 手机访问局域网 HTTPS 时,浏览器会自动信任由 rootCA 签发的证书。

3. 安装根证书到 iOS 手机

  1. rootCA.pem 发送到手机(AirDrop / 邮件 / 下载)
  2. Safari 打开,iOS 会提示“已下载配置描述文件”
  3. 打开 设置 → 通用 → VPN 与设备管理 → 已下载的描述文件 → 安装证书
  4. 安装完成后,打开 设置 → 通用 → 关于本机 → 证书信任设置 → 打开“完全信任此根证书”

✅ 这样手机就能访问 https://192.168.31.161:8080 而不会报错。


四、一键 HTTPS 本地预览脚本

在项目根目录创建 scripts/preview.js

import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
import serveStatic from "serve-static";
import open from "open";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
import { execSync } from "child_process";
import os from "os";
import fs from "fs";
import https from "https";

// 修复 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// -------------------------
// 1. 打包项目
// -------------------------
console.log("📦 开始打包 H5...");
try {
  execSync("npm run build:h5", { stdio: "inherit" });
  console.log("✅ 打包完成!");
} catch (error) {
  console.error("❌ 打包失败:", error);
  process.exit(1);
}

// -------------------------
// 2. 获取本机局域网 IP
// -------------------------
function getLocalIP() {
  const interfaces = os.networkInterfaces();
  for (const dev of Object.values(interfaces)) {
    if (!dev) continue;
    for (const detail of dev) {
      if (detail.family === "IPv4" && !detail.internal) {
        return detail.address;
      }
    }
  }
  return "localhost";
}
const localIP = getLocalIP();

// -------------------------
// 3. 启动 HTTPS 本地服务器
// -------------------------
const DIST_DIR = resolve(__dirname, "../dist/build/h5");
const PORT = 8080;

// API 代理配置
const PROXY_CONFIG = {
  "/api": {
    target: "https://xxx目标服务器地址",
    changeOrigin: true,
    pathRewrite: { "^/api": "" },
  },
};

const app = express();

// 挂载代理
for (const [path, options] of Object.entries(PROXY_CONFIG)) {
  app.use(path, createProxyMiddleware(options));
}

// 托管静态资源
app.use(
  serveStatic(DIST_DIR, {
    index: 'index.html',
    fallthrough: true, // 重要:让静态文件服务在找不到文件时继续
  })
);

// SPA 路由回退处理
// 问题:History 模式直接访问 /pages/user-detail 会 404
// 原理:服务器对包含 /pages/ 的路径返回 index.html,让前端路由接管
// 参数处理:前端通过 window.location 获取完整 URL 路径和参数
app.use((req, res, next) => {
  if (req.path.startsWith('/api')) return next(); // API 请求跳过

  if (req.path.includes('/pages/')) {
    console.log(`🔄 SPA 路由回退: ${req.originalUrl} -> index.html`);

    // 此时浏览器地址栏保持原始 URL 不变:
    // https://localhost:8080/pages/user-detail?userId=123&status=Online

    // 前端应用启动后可以通过以下方式获取完整路径和参数:
    // const fullPath = window.location.pathname; // /pages/user-detail
    // const searchParams = window.location.search; // ?userId=123&status=Online
    // const fullUrl = window.location.href; // 完整 URL

    // 然后使用 router.replace(fullPath + searchParams) 跳转到对应页面

    return res.sendFile(resolve(DIST_DIR, 'index.html'));
  }
  next(); // 其他请求继续(静态资源等)
});

// HTTPS 证书路径
const options = {
  key: fs.readFileSync("./cert/localhost+2-key.pem"),
  cert: fs.readFileSync("./cert/localhost+2.pem"),
};

// 启动 HTTPS 服务
https.createServer(options, app).listen(PORT, "0.0.0.0", () => {
  const url = `https://${localIP}:${PORT}`;
  console.log(`🚀 HTTPS 本地服务器已启动: ${url}`);
  open(url); // 自动在电脑打开浏览器
  console.log(`📱 局域网手机可访问: ${url}`);
});

5. package.json 脚本配置

"scripts": {
  "preview": "node scripts/preview.js"
}

6. 启动一键 HTTPS 预览

npm run preview

输出示例:

📦 开始打包 H5...
✅ 打包完成!
🚀 HTTPS 本地服务器已启动: https://192.168.31.161:8080
📱 局域网手机可访问: https://192.168.31.161:8080

五、总结

通过上述方案,你可以实现:

  • 打包 H5 + 本地 HTTPS 预览
  • 支持接口代理,解决跨域
  • 局域网手机访问 HTTPS
  • 一键启动 + 自动打开浏览器

核心原理:

  • mkcert 生成自签名证书
  • iOS 信任 rootCA.pem
  • Express 托管静态资源 + http-proxy-middleware 转发 API

这样,你就能在开发阶段 完整模拟生产环境,同时解决跨域和 HTTPS 访问问题。

Logo

惟楚有才,于斯为盛。欢迎来到长沙!!! 茶颜悦色、臭豆腐、CSDN和你一个都不能少~

更多推荐