瀏覽代碼

refactor: use dedicated nginx port (5198) for Vite preview

Instead of path-rewriting proxy middleware (which was whack-a-mole with
every URL pattern), give Vite its own nginx server block on port 5198
serving at /. This naturally handles:
- All JS/CSS/asset paths (no prefix needed)
- WebSocket for HMR
- @fs paths for symlinked user assets

Removed:
- previewProxy middleware (no longer needed)
- base config from vite.config.js (no longer needed)
- PREVIEW_BASE_PATH env var (no longer needed)

Updated PREVIEW_PUBLIC_URL to https://color2.jccytech.cn:5198/
guoziyun 3 周之前
父節點
當前提交
7e2839cb01

+ 1 - 2
ecosystem.config.js

@@ -7,8 +7,7 @@ module.exports = {
       env: {
         NODE_ENV: "production",
         PORT: 3001,
-        PREVIEW_PUBLIC_URL: "https://color2.jccytech.cn/ads/preview/",
-        PREVIEW_BASE_PATH: "/ads-preview/",
+        PREVIEW_PUBLIC_URL: "https://color2.jccytech.cn:5198/",
       },
       // 构建服务可能需要较多内存
       max_memory_restart: "512M",

+ 0 - 3
platform/server/dist/index.js

@@ -15,7 +15,6 @@ const builds_1 = require("./routes/builds");
 const preview_1 = require("./routes/preview");
 const preview_2 = require("./routes/preview");
 const errorHandler_1 = require("./middleware/errorHandler");
-const previewProxy_1 = require("./middleware/previewProxy");
 const PORT = process.env.PORT || 3001;
 const STORAGE_DIR = path_1.default.resolve(__dirname, "../../../storage");
 const CLIENT_DIST = path_1.default.resolve(__dirname, "../../client/dist");
@@ -33,8 +32,6 @@ async function main() {
     app.use("/api/v1", (0, assets_1.assetsRouter)(db, STORAGE_DIR));
     app.use("/api/v1", (0, builds_1.buildsRouter)(db, STORAGE_DIR, preview_2.onThemeSaved));
     app.use("/api/v1", (0, preview_1.previewRouter)(db, STORAGE_DIR));
-    // Vite 预览代理:将 /preview/* 请求转发到 Vite dev server 并重写 HTML 路径
-    app.use("/preview", previewProxy_1.previewProxy);
     // 生产环境:serve React 静态文件
     app.use(express_1.default.static(CLIENT_DIST));
     app.get("*", (_req, res) => {

+ 1 - 1
platform/server/dist/index.js.map

@@ -1 +1 @@
-{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,sDAA8B;AAC9B,gDAAwB;AACxB,gDAAwB;AACxB,4CAA6C;AAC7C,oCAA0C;AAC1C,kDAAqD;AACrD,kDAAqD;AACrD,4CAA+C;AAC/C,4CAA+C;AAC/C,8CAAiD;AACjD,8CAAgD;AAChD,4DAAyD;AACzD,4DAAyD;AAEzD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AACtC,MAAM,WAAW,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;AAChE,MAAM,WAAW,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;AAEjE,KAAK,UAAU,IAAI;IACjB,SAAS;IACT,MAAM,EAAE,GAAG,IAAA,uBAAY,EAAC,WAAW,CAAC,CAAC;IACrC,IAAA,oBAAa,EAAC,EAAE,CAAC,CAAC;IAElB,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IAEtB,MAAM;IACN,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,GAAE,CAAC,CAAC;IAChB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,SAAS;IACT,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAA,2BAAe,EAAC,EAAE,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAA,2BAAe,EAAC,EAAE,EAAE,WAAW,EAAE,sBAAY,CAAC,CAAC,CAAC;IAC7E,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAA,qBAAY,EAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAA,qBAAY,EAAC,EAAE,EAAE,WAAW,EAAE,sBAAY,CAAC,CAAC,CAAC;IAChE,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAA,uBAAa,EAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;IAEnD,2DAA2D;IAC3D,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,2BAAY,CAAC,CAAC;IAElC,wBAAwB;IACxB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IACrC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACzB,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,OAAO;IACP,GAAG,CAAC,GAAG,CAAC,2BAAY,CAAC,CAAC;IAEtB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpB,OAAO,CAAC,GAAG,CAAC,iDAAiD,IAAI,EAAE,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,uBAAuB,WAAW,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,sDAA8B;AAC9B,gDAAwB;AACxB,gDAAwB;AACxB,4CAA6C;AAC7C,oCAA0C;AAC1C,kDAAqD;AACrD,kDAAqD;AACrD,4CAA+C;AAC/C,4CAA+C;AAC/C,8CAAiD;AACjD,8CAAgD;AAChD,4DAAyD;AAEzD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AACtC,MAAM,WAAW,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;AAChE,MAAM,WAAW,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;AAEjE,KAAK,UAAU,IAAI;IACjB,SAAS;IACT,MAAM,EAAE,GAAG,IAAA,uBAAY,EAAC,WAAW,CAAC,CAAC;IACrC,IAAA,oBAAa,EAAC,EAAE,CAAC,CAAC;IAElB,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IAEtB,MAAM;IACN,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,GAAE,CAAC,CAAC;IAChB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,SAAS;IACT,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAA,2BAAe,EAAC,EAAE,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAA,2BAAe,EAAC,EAAE,EAAE,WAAW,EAAE,sBAAY,CAAC,CAAC,CAAC;IAC7E,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAA,qBAAY,EAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAA,qBAAY,EAAC,EAAE,EAAE,WAAW,EAAE,sBAAY,CAAC,CAAC,CAAC;IAChE,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAA,uBAAa,EAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;IAEnD,wBAAwB;IACxB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IACrC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACzB,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,OAAO;IACP,GAAG,CAAC,GAAG,CAAC,2BAAY,CAAC,CAAC;IAEtB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpB,OAAO,CAAC,GAAG,CAAC,iDAAiD,IAAI,EAAE,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,uBAAuB,WAAW,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}

+ 0 - 8
platform/server/dist/middleware/previewProxy.d.ts

@@ -1,8 +0,0 @@
-import { Request, Response } from "express";
-/**
- * 将 /preview/* 请求代理到 Vite dev server。
- * 对 HTML 响应自动重写路径,给所有根绝对路径加上 /ads/preview/ 前缀,
- * 确保浏览器通过 nginx 正确加载资源。
- */
-export declare function previewProxy(req: Request, res: Response): void;
-//# sourceMappingURL=previewProxy.d.ts.map

+ 0 - 1
platform/server/dist/middleware/previewProxy.d.ts.map

@@ -1 +0,0 @@
-{"version":3,"file":"previewProxy.d.ts","sourceRoot":"","sources":["../../src/middleware/previewProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAmB5C;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CA2F9D"}

+ 0 - 110
platform/server/dist/middleware/previewProxy.js

@@ -1,110 +0,0 @@
-"use strict";
-var __importDefault = (this && this.__importDefault) || function (mod) {
-    return (mod && mod.__esModule) ? mod : { "default": mod };
-};
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.previewProxy = previewProxy;
-const http_1 = __importDefault(require("http"));
-const VITE_HOST = "127.0.0.1";
-const VITE_PORT = 5199;
-// 这些 header 需要从代理响应中剔除(hop-by-hop)
-const HOP_BY_HOP = new Set([
-    "transfer-encoding",
-    "connection",
-    "keep-alive",
-    "proxy-connection",
-    "proxy-authenticate",
-    "proxy-authorization",
-    "te",
-    "trailers",
-    "upgrade",
-]);
-/**
- * 将 /preview/* 请求代理到 Vite dev server。
- * 对 HTML 响应自动重写路径,给所有根绝对路径加上 /ads/preview/ 前缀,
- * 确保浏览器通过 nginx 正确加载资源。
- */
-function previewProxy(req, res) {
-    // Express mount 会剥离 /preview 前缀,req.url 即 Vite 需要的路径
-    const targetPath = req.url || "/";
-    console.log(`[preview:proxy] ${req.method} ${targetPath}`);
-    // 过滤请求 headers(去掉 hop-by-hop、条件缓存头,设置正确的 host)
-    const reqHeaders = {};
-    for (const [key, value] of Object.entries(req.headers)) {
-        if (value === undefined)
-            continue;
-        const lk = key.toLowerCase();
-        if (lk === "host")
-            continue;
-        if (lk === "connection" || lk === "keep-alive" || lk === "upgrade")
-            continue;
-        // 强制完整响应,避免 304 缓存导致 HTML 无法被重写
-        if (lk === "if-none-match" || lk === "if-modified-since" || lk === "cache-control")
-            continue;
-        reqHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
-    }
-    reqHeaders["host"] = `${VITE_HOST}:${VITE_PORT}`;
-    reqHeaders["cache-control"] = "no-cache";
-    const proxyReq = http_1.default.request({
-        hostname: VITE_HOST,
-        port: VITE_PORT,
-        path: targetPath,
-        method: req.method,
-        headers: reqHeaders,
-    }, (proxyRes) => {
-        const contentType = (proxyRes.headers["content-type"] || "");
-        const isText = contentType.includes("text/html") ||
-            contentType.includes("text/javascript") ||
-            contentType.includes("application/javascript");
-        console.log(`[preview:proxy] response ${proxyRes.statusCode} content-type=${contentType} isText=${isText}`);
-        // 复制响应 headers(过滤 hop-by-hop)
-        const resHeaders = {};
-        for (const [key, value] of Object.entries(proxyRes.headers)) {
-            if (HOP_BY_HOP.has(key.toLowerCase()))
-                continue;
-            if (value !== undefined && value !== null) {
-                resHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
-            }
-        }
-        res.status(proxyRes.statusCode || 200);
-        if (isText) {
-            const chunks = [];
-            proxyRes.on("data", (chunk) => chunks.push(chunk));
-            proxyRes.on("end", () => {
-                let body = Buffer.concat(chunks).toString("utf-8");
-                // 重写 HTML (src="/...", href="/...") 和 JS (from "/...", import "/...")
-                // 为 /ads/preview/...(跳过已有前缀的,支持单双引号)
-                body = body.replace(/((?:src|href)=["']|(?:from|import)\s*["'])\/(?!ads\/preview\/)([^"']+)(["'])/g, '$1/ads/preview/$2$3');
-                // 日志
-                if (contentType.includes("text/html")) {
-                    const viteClientMatch = body.match(/src="[^"]*@vite\/client[^"]*"/);
-                    console.log(`[preview:proxy] HTML vite/client: ${viteClientMatch?.[0] || "NOT FOUND"}`);
-                }
-                resHeaders["content-type"] = contentType.includes("text/html")
-                    ? "text/html; charset=utf-8"
-                    : contentType;
-                resHeaders["content-length"] = String(Buffer.byteLength(body, "utf-8"));
-                res.set(resHeaders);
-                res.send(body);
-            });
-        }
-        else {
-            res.set(resHeaders);
-            proxyRes.pipe(res);
-        }
-    });
-    proxyReq.on("error", (err) => {
-        console.error(`[preview:proxy] ${err.message}`);
-        if (!res.headersSent) {
-            res.status(502).json({ error: "Preview server not available" });
-        }
-    });
-    // POST/PUT/PATCH 请求需要转发 body
-    if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
-        req.pipe(proxyReq);
-    }
-    else {
-        proxyReq.end();
-    }
-}
-//# sourceMappingURL=previewProxy.js.map

文件差異過大導致無法顯示
+ 0 - 0
platform/server/dist/middleware/previewProxy.js.map


+ 1 - 1
platform/server/dist/services/previewService.js

@@ -53,7 +53,7 @@ async function startPreview(creativeId, theme, storageDir) {
     const configContent = (0, configGenerator_1.generateAdConfig)({ creativeId, theme, storageDir });
     const configPath = path_1.default.join(TEMPLATE_DIR, "src", "filler", "_ad_config_.ts");
     fs_1.default.writeFileSync(configPath, configContent, "utf-8");
-    // 4. 启动 Vite dev server(无 base,Express 代理会负责路径重写
+    // 4. 启动 Vite dev server(无 base,由 nginx 独立端口代理,所有路径走 /
     console.log(`[preview] Starting Vite dev server on port ${PREVIEW_PORT}...`);
     viteProcess = (0, child_process_1.spawn)(path_1.default.join(TEMPLATE_DIR, "node_modules", ".bin", "vite"), [
         "--port", String(PREVIEW_PORT),

文件差異過大導致無法顯示
+ 0 - 0
platform/server/dist/services/previewService.js.map


+ 0 - 4
platform/server/src/index.ts

@@ -10,7 +10,6 @@ import { buildsRouter } from "./routes/builds";
 import { previewRouter } from "./routes/preview";
 import { onThemeSaved } from "./routes/preview";
 import { errorHandler } from "./middleware/errorHandler";
-import { previewProxy } from "./middleware/previewProxy";
 
 const PORT = process.env.PORT || 3001;
 const STORAGE_DIR = path.resolve(__dirname, "../../../storage");
@@ -34,9 +33,6 @@ async function main() {
   app.use("/api/v1", buildsRouter(db, STORAGE_DIR, onThemeSaved));
   app.use("/api/v1", previewRouter(db, STORAGE_DIR));
 
-  // Vite 预览代理:将 /preview/* 请求转发到 Vite dev server 并重写 HTML 路径
-  app.use("/preview", previewProxy);
-
   // 生产环境:serve React 静态文件
   app.use(express.static(CLIENT_DIST));
   app.get("*", (_req, res) => {

+ 0 - 116
platform/server/src/middleware/previewProxy.ts

@@ -1,116 +0,0 @@
-import { Request, Response } from "express";
-import http from "http";
-
-const VITE_HOST = "127.0.0.1";
-const VITE_PORT = 5199;
-
-// 这些 header 需要从代理响应中剔除(hop-by-hop)
-const HOP_BY_HOP = new Set([
-  "transfer-encoding",
-  "connection",
-  "keep-alive",
-  "proxy-connection",
-  "proxy-authenticate",
-  "proxy-authorization",
-  "te",
-  "trailers",
-  "upgrade",
-]);
-
-/**
- * 将 /preview/* 请求代理到 Vite dev server。
- * 对 HTML 响应自动重写路径,给所有根绝对路径加上 /ads/preview/ 前缀,
- * 确保浏览器通过 nginx 正确加载资源。
- */
-export function previewProxy(req: Request, res: Response): void {
-  // Express mount 会剥离 /preview 前缀,req.url 即 Vite 需要的路径
-  const targetPath = req.url || "/";
-  console.log(`[preview:proxy] ${req.method} ${targetPath}`);
-
-  // 过滤请求 headers(去掉 hop-by-hop、条件缓存头,设置正确的 host)
-  const reqHeaders: Record<string, string> = {};
-  for (const [key, value] of Object.entries(req.headers)) {
-    if (value === undefined) continue;
-    const lk = key.toLowerCase();
-    if (lk === "host") continue;
-    if (lk === "connection" || lk === "keep-alive" || lk === "upgrade") continue;
-    // 强制完整响应,避免 304 缓存导致 HTML 无法被重写
-    if (lk === "if-none-match" || lk === "if-modified-since" || lk === "cache-control") continue;
-    reqHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
-  }
-  reqHeaders["host"] = `${VITE_HOST}:${VITE_PORT}`;
-  reqHeaders["cache-control"] = "no-cache";
-
-  const proxyReq = http.request(
-    {
-      hostname: VITE_HOST,
-      port: VITE_PORT,
-      path: targetPath,
-      method: req.method,
-      headers: reqHeaders,
-    },
-    (proxyRes) => {
-      const contentType = (proxyRes.headers["content-type"] || "") as string;
-      const isText = contentType.includes("text/html") ||
-                     contentType.includes("text/javascript") ||
-                     contentType.includes("application/javascript");
-      console.log(`[preview:proxy] response ${proxyRes.statusCode} content-type=${contentType} isText=${isText}`);
-
-      // 复制响应 headers(过滤 hop-by-hop)
-      const resHeaders: Record<string, string> = {};
-      for (const [key, value] of Object.entries(proxyRes.headers)) {
-        if (HOP_BY_HOP.has(key.toLowerCase())) continue;
-        if (value !== undefined && value !== null) {
-          resHeaders[key] = Array.isArray(value) ? value.join(", ") : value;
-        }
-      }
-
-      res.status(proxyRes.statusCode || 200);
-
-      if (isText) {
-        const chunks: Buffer[] = [];
-        proxyRes.on("data", (chunk: Buffer) => chunks.push(chunk));
-        proxyRes.on("end", () => {
-          let body = Buffer.concat(chunks).toString("utf-8");
-
-          // 重写 HTML (src="/...", href="/...") 和 JS (from "/...", import "/...")
-          // 为 /ads/preview/...(跳过已有前缀的,支持单双引号)
-          body = body.replace(
-            /((?:src|href)=["']|(?:from|import)\s*["'])\/(?!ads\/preview\/)([^"']+)(["'])/g,
-            '$1/ads/preview/$2$3',
-          );
-
-          // 日志
-          if (contentType.includes("text/html")) {
-            const viteClientMatch = body.match(/src="[^"]*@vite\/client[^"]*"/);
-            console.log(`[preview:proxy] HTML vite/client: ${viteClientMatch?.[0] || "NOT FOUND"}`);
-          }
-
-          resHeaders["content-type"] = contentType.includes("text/html")
-            ? "text/html; charset=utf-8"
-            : contentType;
-          resHeaders["content-length"] = String(Buffer.byteLength(body, "utf-8"));
-          res.set(resHeaders);
-          res.send(body);
-        });
-      } else {
-        res.set(resHeaders);
-        proxyRes.pipe(res);
-      }
-    },
-  );
-
-  proxyReq.on("error", (err) => {
-    console.error(`[preview:proxy] ${err.message}`);
-    if (!res.headersSent) {
-      res.status(502).json({ error: "Preview server not available" });
-    }
-  });
-
-  // POST/PUT/PATCH 请求需要转发 body
-  if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
-    req.pipe(proxyReq);
-  } else {
-    proxyReq.end();
-  }
-}

+ 1 - 1
platform/server/src/services/previewService.ts

@@ -53,7 +53,7 @@ export async function startPreview(
   const configPath = path.join(TEMPLATE_DIR, "src", "filler", "_ad_config_.ts");
   fs.writeFileSync(configPath, configContent, "utf-8");
 
-  // 4. 启动 Vite dev server(无 base,Express 代理会负责路径重写
+  // 4. 启动 Vite dev server(无 base,由 nginx 独立端口代理,所有路径走 /
   console.log(`[preview] Starting Vite dev server on port ${PREVIEW_PORT}...`);
   viteProcess = spawn(path.join(TEMPLATE_DIR, "node_modules", ".bin", "vite"), [
     "--port", String(PREVIEW_PORT),

+ 0 - 5
templates/coloring/vite.config.js

@@ -42,12 +42,7 @@ module.exports = defineConfig(({ mode }) => {
   const output = platformBuild?.output;
   const outDir = output ? `dist/${output}` : "dist";
 
-  // PREVIEW_BASE_PATH 仅用于 Vite dev server,控制 JS module import 的路径前缀
-  // (HTML 中的 @vite/client 注入不生效,由 Express 代理处理)
-  const base = process.env.PREVIEW_BASE_PATH || undefined;
-
   return {
-    base,
     plugins: [viteSingleFile(), finalizeHtmlPlugin(outDir, adapter)],
     server: {
       allowedHosts: ["color2.jccytech.cn", "localhost", ".jccytech.cn"],

部分文件因文件數量過多而無法顯示