@@ -41,9 +41,13 @@ export function validateRepoUrl(input: string): { ok: true; url: string } | { ok
4141 return { ok : true , url }
4242}
4343
44+ // 报告「返回」链接:本地模式用绝对地址(上传报告经 blob: URL 打开,相对 '/' 会失效)
45+ let SERVER_BASE = '/'
46+
4447export function startWebServer ( opts : WebServerOptions ) : void {
4548 const locale = resolveLocale ( DEFAULT_CONFIG )
4649 const host = opts . local ? '127.0.0.1' : '0.0.0.0'
50+ SERVER_BASE = opts . local ? `http://localhost:${ opts . port } /` : '/'
4751 let active = 0
4852
4953 const server = createServer ( async ( req , res ) => {
@@ -113,7 +117,7 @@ async function handleRepo(res: any, repo: string, locale: 'zh' | 'en', inc: () =
113117 try {
114118 await cloneRepo ( v . url , dir )
115119 const { report, scan } = runProjectComplianceAudit ( DEFAULT_CONFIG , dir )
116- send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root : v . url , backLink : '/' } ) )
120+ send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root : v . url , backLink : SERVER_BASE } ) )
117121 } catch ( e : any ) {
118122 const msg = esc ( e ?. message || String ( e ) )
119123 send ( res , 502 , 'text/html' , errorPage (
@@ -133,7 +137,7 @@ async function handleLocal(res: any, path: string, locale: 'zh' | 'en', inc: ()
133137 inc ( )
134138 try {
135139 const { report, scan } = runProjectComplianceAudit ( DEFAULT_CONFIG , root )
136- send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root, backLink : '/' } ) )
140+ send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root, backLink : SERVER_BASE } ) )
137141 } finally {
138142 dec ( )
139143 }
@@ -176,7 +180,7 @@ async function handleUpload(req: any, res: any, locale: 'zh' | 'en', inc: () =>
176180 }
177181 const { report, scan } = runProjectComplianceAudit ( DEFAULT_CONFIG , dir )
178182 const rootName = typeof payload . root === 'string' && payload . root ? payload . root : '(uploaded folder)'
179- send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root : rootName , backLink : '/' } ) )
183+ send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root : rootName , backLink : SERVER_BASE } ) )
180184 } catch ( e : any ) {
181185 send ( res , 500 , 'text/html' , errorPage ( '扫描失败:' + esc ( e ?. message || String ( e ) ) ) )
182186 } finally {
@@ -226,7 +230,7 @@ function handleDemo(res: any, locale: 'zh' | 'en', inc: () => void, dec: () => v
226230 writeFileSync ( join ( dir , '.env' ) ,
227231 'AWS_ACCESS_KEY=AKIARZ9MKP2QWLS7YV3N\nDB_PASSWORD=Sup3rS3cretProdPwd2026\n' )
228232 const { report, scan } = runProjectComplianceAudit ( DEFAULT_CONFIG , dir )
229- send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root : '示例项目(含风险)/ demo-ai-app' , backLink : '/' } ) )
233+ send ( res , 200 , 'text/html' , renderHtmlReport ( report , scan , locale , { root : '示例项目(含风险)/ demo-ai-app' , backLink : SERVER_BASE } ) )
230234 } catch ( e : any ) {
231235 send ( res , 500 , 'text/html' , errorPage ( '演示失败:' + esc ( e ?. message || String ( e ) ) ) )
232236 } finally {
@@ -373,20 +377,26 @@ function page(title: string, body: string): string {
373377*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;
374378background:linear-gradient(135deg,#eef1f6,#e2e8f0);color:#0f172a;
375379font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
376- .hero{background:#fff;max-width:560px ;width:92%;margin:40px;padding:40px;border-radius:18px;
377- box-shadow:0 12px 40px rgba(15,23,42,.12);text-align:center}
378- .logo{font-weight:800;font-size:15px}.logo span{color:#cb0000}
379- h1{font-size:30px ;margin:14px 0 8px;letter-spacing:-.5px}
380- .sub{color:#64748b;margin:0 0 26px }
380+ .hero{background:#fff;max-width:580px ;width:92%;margin:40px;padding:38px 40px 34px ;border-radius:18px;
381+ box-shadow:0 12px 40px rgba(15,23,42,.12);text-align:center;border-top:4px solid #cb0000 }
382+ .logo{font-weight:800;font-size:15px;letter-spacing:.2px }.logo span{color:#cb0000}
383+ h1{font-size:29px ;margin:12px 0 8px;letter-spacing:-.5px}
384+ .sub{color:#64748b;margin:0 0 24px;font-size:15px }
381385form{display:flex;flex-direction:column;gap:10px;text-align:left}
382- label{font-size:13px;font-weight:600 ;color:#475569 }
386+ label{font-size:13px;font-weight:700 ;color:#334155 }
383387input{padding:14px 16px;border:1px solid #cbd5e1;border-radius:10px;font-size:16px;width:100%}
384388input:focus{outline:none;border-color:#cb0000;box-shadow:0 0 0 3px rgba(203,0,0,.12)}
385- .hint{font-size:12.5px;color:#64748b;margin:2px 0 6px}
389+ .hint{font-size:12.5px;color:#64748b;margin:2px 0 6px;line-height:1.55 }
386390.hint code{background:#f1f5f9;padding:1px 6px;border-radius:5px}
387- input[type=file]{padding:12px;background:#f8fafc;cursor:pointer}
391+ /* 文件选择器:美化成虚线投放区 + 红色按钮 */
392+ input[type=file]{width:100%;padding:20px 16px;border:2px dashed #cbd5e1;border-radius:12px;
393+ background:#f8fafc;cursor:pointer;font-size:14px;color:#64748b;transition:.15s}
394+ input[type=file]:hover{border-color:#cb0000;background:#fff}
395+ input[type=file]::file-selector-button{background:#cb0000;color:#fff;border:0;border-radius:8px;
396+ padding:9px 18px;margin-right:14px;font-weight:700;font-size:14px;cursor:pointer}
397+ input[type=file]::file-selector-button:hover{background:#a80000}
388398button{background:#cb0000;color:#fff;border:0;border-radius:10px;padding:14px;font-size:16px;
389- font-weight:700;cursor:pointer;margin-top:4px}button:hover{background:#a80000}
399+ font-weight:700;cursor:pointer;margin-top:4px;transition:.15s }button:hover{background:#a80000}
390400button:disabled{background:#94a3b8;cursor:default}
391401form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
392402.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
0 commit comments