# -*- coding: utf-8 -*- # 静态文件服务:对 Trae 预览注入的 /@vite/* 热更新脚本返回 200 空JS, # 避免 404 导致预览面板中断主文档加载(net::ERR_ABORTED) import http.server import socketserver import os os.chdir(os.path.dirname(os.path.abspath(__file__))) class H(http.server.SimpleHTTPRequestHandler): # HTTP/1.1 keep-alive:避免 HTTP/1.0 每请求关连接,预览面板复用连接时遇到 RST 而 ERR_ABORTED protocol_version = "HTTP/1.1" def _vite(self): self.send_response(200) self.send_header("Content-Type", "text/javascript") self.send_header("Cache-Control", "no-store") self.send_header("Content-Length", "2") self.end_headers() self.wfile.write(b"//") def _strip_cond(self): # 剥离条件请求头 → 永远 200 全量正文: # 预览面板需改写 HTML 正文注入热更新脚本,304 无正文会使其放弃本次加载并重发(net::ERR_ABORTED) for h in ("If-Modified-Since", "If-None-Match"): if h in self.headers: del self.headers[h] def send_header(self, keyword, value): # 注意:Python 3.14 的 send_head 用的是 "Content-type"(小写t),比较必须忽略大小写 if keyword.lower() == "content-type" and "text/html" in value: super().send_header("Cache-Control", "no-store") super().send_header(keyword, value) PAGE = "/AI%E5%BF%AB%E9%80%9F%E6%8A%A5%E4%BB%B7_Demo2.optimizing.html" def do_GET(self): # 根路径/短别名 → 同一页面:预览面板用最短的纯ASCII URL,避免长百分号编码URL q = self.path.split("?", 1)[0] if q in ("/", "/index.html", "/index"): self.path = self.PAGE + ("?" + self.path.split("?", 1)[1] if "?" in self.path else "") if self.path.startswith("/@vite/"): self._vite() elif self.path.startswith("/ref_parts"): # 读取参考报价表的 styles.xml / theme.xml(base64),供前端导出时原样套用 import json, base64, zipfile try: rp = os.path.join(os.path.join(os.path.expanduser("~"), "Desktop"), "6.18号欧阳路预估报价.xlsx") z = zipfile.ZipFile(rp) theme = z.read("xl/theme/theme1.xml") if "xl/theme/theme1.xml" in z.namelist() else b"" resp = json.dumps({"styles": base64.b64encode(z.read("xl/styles.xml")).decode(), "theme": base64.b64encode(theme).decode()}).encode() self.send_response(200) except Exception as e: resp = json.dumps({"err": str(e)}).encode() self.send_response(500) self.send_header("Content-Type", "application/json") self.send_header("Cache-Control", "no-store") self.send_header("Content-Length", str(len(resp))) self.end_headers() self.wfile.write(resp) else: self._strip_cond() super().do_GET() def do_HEAD(self): if self.path.startswith("/@vite/"): self._vite() else: self._strip_cond() super().do_HEAD() def do_POST(self): # 导出Excel:前端生成xlsx字节(base64) → 服务端写入桌面 if self.path == "/save_xlsx": import json, base64 try: ln = int(self.headers.get("Content-Length", 0)) d = json.loads(self.rfile.read(ln).decode("utf-8")) name = os.path.basename(d["name"]) data = base64.b64decode(d["data"]) path = os.path.join(os.path.join(os.path.expanduser("~"), "Desktop"), name) with open(path, "wb") as f: f.write(data) resp = json.dumps({"ok": True, "path": path}, ensure_ascii=False).encode("utf-8") self.send_response(200) except Exception as e: resp = json.dumps({"ok": False, "err": str(e)}, ensure_ascii=False).encode("utf-8") self.send_response(500) self.send_header("Content-Type", "application/json") self.send_header("Cache-Control", "no-store") self.send_header("Content-Length", str(len(resp))) self.end_headers() self.wfile.write(resp) else: self.send_response(404) # HTTP/1.1 keep-alive 下必须给出 Content-Length,否则客户端会挂起等待正文 self.send_header("Content-Length", "0") self.end_headers() socketserver.ThreadingTCPServer.allow_reuse_address = True s = socketserver.ThreadingTCPServer(("0.0.0.0", 8002), H) print("server ready on 8002") s.serve_forever()