| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- # -*- 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()
|