{"id":67812,"date":"2026-01-29T07:26:31","date_gmt":"2026-01-28T23:26:31","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/67812.html"},"modified":"2026-01-29T07:26:31","modified_gmt":"2026-01-28T23:26:31","slug":"fastapi-%e5%92%8c-htmlcssjs-%e5%bc%80%e5%8f%91%e7%9a%84-pdf%e6%89%93%e5%8d%b0%e6%9c%8d%e5%8a%a1%e5%99%a8-%e8%bf%9e%e6%8e%a5%e5%88%b0%e6%9c%8d%e5%8a%a1%e5%99%a8%e7%9a%84%e7%89%a9%e7%90%86%e6%89%93","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/67812.html","title":{"rendered":"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370"},"content":{"rendered":"<h2><img loading=\"lazy\" decoding=\"async\" alt=\"\" height=\"849\" src=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/01\/20260128232629-697a9b2582169.png\" width=\"1191\" \/><\/h2>\n<h2>PDF\u6253\u5370\u670d\u52a1\u5668<\/h2>\n<p>\u4e00\u4e2a\u57fa\u4e8eFastAPI\u7684PDF\u6253\u5370\u670d\u52a1\u5668&#xff0c;\u652f\u6301\u901a\u8fc7\u7f51\u9875\u754c\u9762\u4e0a\u4f20PDF\u6587\u4ef6\u5e76\u6253\u5370\u5230\u5c40\u57df\u7f51\u5185\u7684\u7269\u7406\u6253\u5370\u673a\u3002<\/p>\n<h3>\u529f\u80fd\u7279\u6027<\/h3>\n<ul>\n<li>\n<p>&#x1f4c4; \u652f\u6301PDF\u6587\u4ef6\u4e0a\u4f20\u548c\u6253\u5370<\/p>\n<\/li>\n<li>\n<p>&#x1f5a8;\ufe0f \u81ea\u52a8\u68c0\u6d4b\u7cfb\u7edf\u6253\u5370\u673a\u5217\u8868<\/p>\n<\/li>\n<li>\n<p>&#x1f4f1; \u54cd\u5e94\u5f0f\u8bbe\u8ba1&#xff0c;\u652f\u6301\u79fb\u52a8\u7aef\u548c\u684c\u9762\u7aef<\/p>\n<\/li>\n<li>\n<p>&#x1f504; \u5b9e\u65f6\u6253\u5370\u4efb\u52a1\u72b6\u6001\u76d1\u63a7<\/p>\n<\/li>\n<li>\n<p>&#x1f4be; \u81ea\u52a8\u4fdd\u5b58\u7528\u6237\u8bbe\u7f6e&#xff08;\u6253\u5370\u673a\u3001\u6253\u5370\u4efd\u6570&#xff09;<\/p>\n<\/li>\n<li>\n<p>&#x1f512; \u5b89\u5168\u7684\u6587\u4ef6\u5904\u7406&#xff0c;\u4e34\u65f6\u6587\u4ef6\u81ea\u52a8\u6e05\u7406<\/p>\n<\/li>\n<li>\n<p>&#x1f310; \u652f\u6301\u5c40\u57df\u7f51\u591a\u8bbe\u5907\u8bbf\u95ee<\/p>\n<\/li>\n<li>\n<p>&#x1f3af; \u53ef\u9760\u7684\u6253\u5370\u4efd\u6570\u63a7\u5236&#xff08;\u652f\u63011-100\u4efd&#xff09;<\/p>\n<\/li>\n<\/ul>\n<h3>\u7cfb\u7edf\u8981\u6c42<\/h3>\n<h4>\u64cd\u4f5c\u7cfb\u7edf<\/h4>\n<ul>\n<li>\n<p>Windows 10\/11 (\u63a8\u8350&#xff0c;\u529f\u80fd\u6700\u5b8c\u6574)<\/p>\n<\/li>\n<\/ul>\n<h4>\u8f6f\u4ef6\u4f9d\u8d56<\/h4>\n<ul>\n<li>\n<p>Python 3.8&#043;<\/p>\n<\/li>\n<li>\n<p>SumatraPDF (Windows\u7528\u6237\u5fc5\u9700)<\/p>\n<\/li>\n<li>\n<p>\u7cfb\u7edf\u6253\u5370\u673a\u9a71\u52a8<\/p>\n<\/li>\n<\/ul>\n<h3>\u5b89\u88c5\u6b65\u9aa4<\/h3>\n<h4>1. \u5b89\u88c5Python\u4f9d\u8d56<\/h4>\n<p>pip install fastapi uvicorn psutil <\/p>\n<h5>Windows\u7528\u6237\u989d\u5916\u5b89\u88c5&#xff1a;<\/h5>\n<p>pip install pywin32 <\/p>\n<h4>2. \u5b89\u88c5SumatraPDF (\u4ec5Windows)<\/h4>\n<li>\n<p>\u4e0b\u8f7dSumatraPDF&#xff1a;https:\/\/www.sumatrapdfreader.org\/download-free-pdf-viewer.html<\/p>\n<\/li>\n<li>\n<p>\u5b89\u88c5\u5230\u9ed8\u8ba4\u8def\u5f84&#xff1a;C:\\\\Program Files\\\\SumatraPDF\\\\<\/p>\n<\/li>\n<h4>3. \u514b\u9686\u6216\u4e0b\u8f7d\u4ee3\u7801<\/h4>\n<p>git clone https:\/\/github.com\/yourusername\/pdf-print-server.git<br \/>\ncd pdf-print-server <\/p>\n<h4>4. \u521b\u5efa\u5fc5\u8981\u7684\u76ee\u5f55\u7ed3\u6784<\/h4>\n<p>pdf-print-server\/<br \/>\n\u251c\u2500\u2500 uploads\/ \u00a0 \u00a0 \u00a0 \u00a0  # \u81ea\u52a8\u521b\u5efa&#xff0c;\u7528\u4e8e\u4e34\u65f6\u5b58\u50a8\u4e0a\u4f20\u7684\u6587\u4ef6<br \/>\n\u251c\u2500\u2500 static\/ \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # \u524d\u7aef\u9759\u6001\u6587\u4ef6<br \/>\n\u2502 \u00a0 \u2514\u2500\u2500 index.html \u00a0 # \u524d\u7aef\u9875\u9762<br \/>\n\u2514\u2500\u2500 server.py \u00a0 \u00a0 \u00a0  # \u540e\u7aef\u670d\u52a1\u5668\u4ee3\u7801 <\/p>\n<h3>\u4f7f\u7528\u65b9\u6cd5<\/h3>\n<h4>1. \u542f\u52a8\u670d\u52a1\u5668<\/h4>\n<p>python server.py <\/p>\n<p>\u670d\u52a1\u5668\u542f\u52a8\u540e\u5c06\u663e\u793a&#xff1a;<\/p>\n<p>&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<br \/>\nPDF\u6253\u5370\u670d\u52a1\u5668\u542f\u52a8 &#8211; SumatraPDF\u4fee\u590d\u7248<br \/>\n&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;<br \/>\n\u672c\u5730\u8bbf\u95ee: http:\/\/localhost:8083<br \/>\n\u5c40\u57df\u7f51\u8bbf\u95ee: http:\/\/192.168.1.100:8083<br \/>\n\u670d\u52a1\u5668\u5730\u5740: 192.168.1.100<br \/>\n\u7cfb\u7edf: Windows<br \/>\n&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061;&#061; <\/p>\n<h4>2. \u8bbf\u95ee\u7f51\u9875\u754c\u9762<\/h4>\n<ul>\n<li>\n<p>\u5728\u670d\u52a1\u5668\u672c\u673a&#xff1a;\u6253\u5f00\u6d4f\u89c8\u5668\u8bbf\u95ee http:\/\/localhost:8083<\/p>\n<\/li>\n<li>\n<p>\u5728\u5c40\u57df\u7f51\u5176\u4ed6\u8bbe\u5907&#xff1a;\u6253\u5f00\u6d4f\u89c8\u5668\u8bbf\u95ee http:\/\/\u670d\u52a1\u5668IP:8083<\/p>\n<\/li>\n<\/ul>\n<h4>3. \u4f7f\u7528\u6d41\u7a0b<\/h4>\n<li>\n<p>\u9009\u62e9PDF\u6587\u4ef6&#xff1a;\u70b9\u51fb&#034;\u9009\u62e9PDF\u6587\u4ef6&#034;\u6309\u94ae\u6216\u62d6\u653e\u6587\u4ef6\u5230\u533a\u57df<\/p>\n<\/li>\n<li>\n<p>\u9009\u62e9\u6253\u5370\u673a&#xff1a;\u4ece\u4e0b\u62c9\u5217\u8868\u4e2d\u9009\u62e9\u8981\u4f7f\u7528\u7684\u6253\u5370\u673a<\/p>\n<\/li>\n<li>\n<p>\u8bbe\u7f6e\u6253\u5370\u4efd\u6570&#xff1a;\u8f93\u5165\u9700\u8981\u6253\u5370\u7684\u4efd\u6570&#xff08;1-100&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u5f00\u59cb\u6253\u5370&#xff1a;\u70b9\u51fb&#034;\u4e0a\u4f20\u5e76\u6253\u5370&#034;\u6309\u94ae<\/p>\n<\/li>\n<li>\n<p>\u67e5\u770b\u72b6\u6001&#xff1a;\u5728\u53f3\u4fa7\u4efb\u52a1\u72b6\u6001\u533a\u57df\u67e5\u770b\u6253\u5370\u8fdb\u5ea6<\/p>\n<\/li>\n<h3>API\u63a5\u53e3<\/h3>\n<h4>\u4e3b\u8981\u63a5\u53e3<\/h4>\n<table>\n<tr>\u7aef\u70b9\u65b9\u6cd5\u63cf\u8ff0<\/tr>\n<tbody>\n<tr>\n<td>\/<\/td>\n<td>GET<\/td>\n<td>\u524d\u7aef\u9875\u9762<\/td>\n<\/tr>\n<tr>\n<td>\/api\/printers<\/td>\n<td>GET<\/td>\n<td>\u83b7\u53d6\u6253\u5370\u673a\u5217\u8868<\/td>\n<\/tr>\n<tr>\n<td>\/api\/upload<\/td>\n<td>POST<\/td>\n<td>\u4e0a\u4f20\u5e76\u6253\u5370PDF\u6587\u4ef6<\/td>\n<\/tr>\n<tr>\n<td>\/api\/tasks<\/td>\n<td>GET<\/td>\n<td>\u83b7\u53d6\u6240\u6709\u4efb\u52a1\u72b6\u6001<\/td>\n<\/tr>\n<tr>\n<td>\/api\/tasks\/{task_id}<\/td>\n<td>GET<\/td>\n<td>\u83b7\u53d6\u7279\u5b9a\u4efb\u52a1\u72b6\u6001<\/td>\n<\/tr>\n<tr>\n<td>\/api\/health<\/td>\n<td>GET<\/td>\n<td>\u670d\u52a1\u5668\u5065\u5eb7\u68c0\u67e5<\/td>\n<\/tr>\n<tr>\n<td>\/api\/sumatra-test<\/td>\n<td>GET<\/td>\n<td>\u6d4b\u8bd5SumatraPDF\u5b89\u88c5<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h4>\u4e0a\u4f20\u6587\u4ef6\u53c2\u6570<\/h4>\n<p>curl -X POST http:\/\/localhost:8083\/api\/upload \\\\<br \/>\n \u00a0-F &#034;file&#061;&#064;document.pdf&#034; \\\\<br \/>\n \u00a0-F &#034;printer&#061;HP LaserJet&#034; \\\\<br \/>\n \u00a0-F &#034;copies&#061;2&#034; <\/p>\n<h3>\u914d\u7f6e\u6587\u4ef6\u8bf4\u660e<\/h3>\n<h4>\u670d\u52a1\u5668\u914d\u7f6e<\/h4>\n<p>\u5728 server.py \u4e2d\u53ef\u4ee5\u4fee\u6539\u4ee5\u4e0b\u914d\u7f6e&#xff1a;<\/p>\n<p># \u7aef\u53e3\u914d\u7f6e<br \/>\nPORT &#061; 8083 \u00a0# \u9ed8\u8ba4\u7aef\u53e3<br \/>\n\u200b<br \/>\n# \u6587\u4ef6\u4e0a\u4f20\u9650\u5236<br \/>\nMAX_FILE_SIZE &#061; 10 * 1024 * 1024 \u00a0# 10MB<br \/>\n\u200b<br \/>\n# \u4e34\u65f6\u6587\u4ef6\u6e05\u7406<br \/>\nFILE_CLEANUP_HOURS &#061; 1 \u00a0# 1\u5c0f\u65f6\u540e\u6e05\u7406\u4e34\u65f6\u6587\u4ef6 <\/p>\n<h4>\u524d\u7aef\u914d\u7f6e<\/h4>\n<p>\u524d\u7aef\u8bbe\u7f6e\u81ea\u52a8\u4fdd\u5b58\u5728\u6d4f\u89c8\u5668\u7684LocalStorage\u4e2d&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u9009\u62e9\u7684\u6253\u5370\u673a<\/p>\n<\/li>\n<li>\n<p>\u6253\u5370\u4efd\u6570<\/p>\n<\/li>\n<li>\n<p>\u5237\u65b0\u9875\u9762\u540e\u8bbe\u7f6e\u4fdd\u6301\u4e0d\u53d8<\/p>\n<\/li>\n<\/ul>\n<h3>\u6545\u969c\u6392\u9664<\/h3>\n<h4>\u5e38\u89c1\u95ee\u9898<\/h4>\n<h5>1. \u6253\u5370\u673a\u5217\u8868\u4e3a\u7a7a<\/h5>\n<ul>\n<li>\n<p>\u68c0\u67e5\u6253\u5370\u673a\u662f\u5426\u5df2\u8fde\u63a5\u5e76\u5b89\u88c5\u9a71\u52a8<\/p>\n<\/li>\n<li>\n<p>Windows&#xff1a;\u786e\u4fdd\u6253\u5370\u673a\u5df2\u8bbe\u7f6e\u4e3a&#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;<\/p>\n<\/li>\n<li>\n<p>Linux&#xff1a;\u786e\u4fddCUPS\u670d\u52a1\u6b63\u5728\u8fd0\u884c<\/p>\n<\/li>\n<\/ul>\n<h5>2. \u6253\u5370\u5931\u8d25<\/h5>\n<ul>\n<li>\n<p>Windows&#xff1a;\u68c0\u67e5SumatraPDF\u662f\u5426\u6b63\u786e\u5b89\u88c5<\/p>\n<\/li>\n<li>\n<p>\u67e5\u770b\u670d\u52a1\u5668\u63a7\u5236\u53f0\u65e5\u5fd7\u83b7\u53d6\u8be6\u7ec6\u9519\u8bef\u4fe1\u606f<\/p>\n<\/li>\n<li>\n<p>\u786e\u4fddPDF\u6587\u4ef6\u6ca1\u6709\u635f\u574f<\/p>\n<\/li>\n<\/ul>\n<h5>3. \u6253\u5370\u4efd\u6570\u4e0d\u8d77\u4f5c\u7528<\/h5>\n<ul>\n<li>\n<p>Windows&#xff1a;\u786e\u8ba4SumatraPDF\u7248\u672c\u652f\u6301-print-settings &#034;Nx&#034;\u53c2\u6570<\/p>\n<\/li>\n<li>\n<p>\u5c1d\u8bd5\u4f7f\u7528\u6253\u5370\u5bf9\u8bdd\u6846\u65b9\u6848<\/p>\n<\/li>\n<\/ul>\n<h5>4. \u65e0\u6cd5\u8bbf\u95ee\u7f51\u9875<\/h5>\n<ul>\n<li>\n<p>\u68c0\u67e5\u9632\u706b\u5899\u8bbe\u7f6e&#xff0c;\u786e\u4fdd8083\u7aef\u53e3\u5f00\u653e<\/p>\n<\/li>\n<li>\n<p>\u786e\u4fdd\u670d\u52a1\u5668\u548c\u5ba2\u6237\u7aef\u5728\u540c\u4e00\u5c40\u57df\u7f51<\/p>\n<\/li>\n<\/ul>\n<h4>\u65e5\u5fd7\u67e5\u770b<\/h4>\n<p>\u670d\u52a1\u5668\u65e5\u5fd7\u5305\u542b\u8be6\u7ec6\u7684\u64cd\u4f5c\u4fe1\u606f&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u6587\u4ef6\u4e0a\u4f20\u72b6\u6001<\/p>\n<\/li>\n<li>\n<p>\u6253\u5370\u547d\u4ee4\u6267\u884c\u60c5\u51b5<\/p>\n<\/li>\n<li>\n<p>\u9519\u8bef\u4fe1\u606f<\/p>\n<\/li>\n<\/ul>\n<h3>\u5b89\u5168\u6ce8\u610f\u4e8b\u9879<\/h3>\n<li>\n<p>\u6587\u4ef6\u5b89\u5168&#xff1a;\u4e0a\u4f20\u7684\u6587\u4ef6\u4f1a\u5b58\u50a8\u5728\u4e34\u65f6\u76ee\u5f55&#xff0c;\u6253\u5370\u5b8c\u6210\u540e\u81ea\u52a8\u5220\u9664<\/p>\n<\/li>\n<li>\n<p>\u8bbf\u95ee\u63a7\u5236&#xff1a;\u5f53\u524d\u7248\u672c\u65e0\u8ba4\u8bc1&#xff0c;\u5efa\u8bae\u5728\u5185\u7f51\u5b89\u5168\u73af\u5883\u4f7f\u7528<\/p>\n<\/li>\n<li>\n<p>\u6587\u4ef6\u5927\u5c0f\u9650\u5236&#xff1a;\u9ed8\u8ba4\u9650\u5236\u4e3a10MB&#xff0c;\u9632\u6b62\u5927\u6587\u4ef6\u653b\u51fb<\/p>\n<\/li>\n<li>\n<p>\u7aef\u53e3\u5b89\u5168&#xff1a;\u5efa\u8bae\u5728\u8def\u7531\u5668\u4e2d\u9650\u52368083\u7aef\u53e3\u7684\u8bbf\u95ee<\/p>\n<\/li>\n<h3>\u5f00\u53d1\u8bf4\u660e<\/h3>\n<h4>\u9879\u76ee\u7ed3\u6784<\/h4>\n<p>pdf-print-server\/<br \/>\n\u251c\u2500\u2500 server.py \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0  # FastAPI\u540e\u7aef\u670d\u52a1\u5668<br \/>\n\u251c\u2500\u2500 static\/<br \/>\n\u2502 \u00a0 \u251c\u2500\u2500 index.html \u00a0 \u00a0 \u00a0  # \u524d\u7aefHTML\u9875\u9762<br \/>\n\u2502 \u00a0 \u2514\u2500\u2500 (CSS\/JS\u5185\u8054) \u00a0 \u00a0  # \u6837\u5f0f\u548c\u811a\u672c<br \/>\n\u251c\u2500\u2500 uploads\/ \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0  # \u4e34\u65f6\u6587\u4ef6\u76ee\u5f55<br \/>\n\u251c\u2500\u2500 requirements.txt \u00a0 \u00a0  # Python\u4f9d\u8d56<br \/>\n\u2514\u2500\u2500 README.md \u00a0 \u00a0 \u00a0 \u00a0 \u00a0  # \u9879\u76ee\u8bf4\u660e <\/p>\n<h3>\u66f4\u65b0\u65e5\u5fd7<\/h3>\n<h4>v1.3.0 (\u6700\u65b0)<\/h4>\n<ul>\n<li>\n<p>\u6839\u636eSumatraPDF\u5b98\u65b9\u6587\u6863\u4fee\u6b63\u6253\u5370\u4efd\u6570\u8bbe\u7f6e<\/p>\n<\/li>\n<li>\n<p>\u589e\u52a0\u6253\u5370\u5bf9\u8bdd\u6846\u5907\u7528\u65b9\u6848<\/p>\n<\/li>\n<li>\n<p>\u589e\u5f3a\u9519\u8bef\u5904\u7406\u548c\u65e5\u5fd7\u8bb0\u5f55<\/p>\n<\/li>\n<li>\n<p>\u65b0\u589eSumatraPDF\u6d4b\u8bd5\u63a5\u53e3<\/p>\n<\/li>\n<\/ul>\n<h4>v1.2.0<\/h4>\n<ul>\n<li>\n<p>\u4fee\u590d\u79fb\u52a8\u7aef\u663e\u793a\u95ee\u9898<\/p>\n<\/li>\n<li>\n<p>\u6dfb\u52a0\u5f02\u6b65\u4efb\u52a1\u5904\u7406<\/p>\n<\/li>\n<li>\n<p>\u6539\u8fdb\u6253\u5370\u4efd\u6570\u5907\u7528\u65b9\u6848<\/p>\n<\/li>\n<li>\n<p>\u589e\u52a0\u7cfb\u7edf\u5065\u5eb7\u68c0\u67e5<\/p>\n<\/li>\n<\/ul>\n<h4>v1.1.0<\/h4>\n<ul>\n<li>\n<p>\u6dfb\u52a0\u8bbe\u7f6e\u81ea\u52a8\u4fdd\u5b58\u529f\u80fd<\/p>\n<\/li>\n<li>\n<p>\u4f18\u5316\u79fb\u52a8\u7aef\u7528\u6237\u4f53\u9a8c<\/p>\n<\/li>\n<li>\n<p>\u6539\u8fdb\u6253\u5370\u673a\u5217\u8868\u83b7\u53d6<\/p>\n<\/li>\n<\/ul>\n<h4>v1.0.0<\/h4>\n<ul>\n<li>\n<p>\u521d\u59cb\u7248\u672c\u53d1\u5e03<\/p>\n<\/li>\n<li>\n<p>\u57fa\u672cPDF\u4e0a\u4f20\u548c\u6253\u5370\u529f\u80fd<\/p>\n<\/li>\n<li>\n<p>\u6253\u5370\u673a\u5217\u8868\u81ea\u52a8\u68c0\u6d4b<\/p>\n<\/li>\n<li>\n<p>\u4efb\u52a1\u72b6\u6001\u76d1\u63a7<\/p>\n<\/li>\n<\/ul>\n<p><img loading=\"lazy\" decoding=\"async\" alt=\"\" height=\"367\" src=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/01\/20260128232630-697a9b2607fb1.png\" width=\"620\" \/><\/p>\n<\/p>\n<h2>v1.3.0 (\u6700\u65b0) \u6700\u65b0\u4ee3\u7801 index.html<\/h2>\n<p>&lt;!DOCTYPE html&gt;<br \/>\n&lt;html lang&#061;&#034;zh-CN&#034;&gt;<\/p>\n<p>&lt;head&gt;<br \/>\n    &lt;meta charset&#061;&#034;UTF-8&#034;&gt;<br \/>\n    &lt;meta name&#061;&#034;viewport&#034; content&#061;&#034;width&#061;device-width, initial-scale&#061;1.0, maximum-scale&#061;1.0, user-scalable&#061;no&#034;&gt;<br \/>\n    &lt;title&gt;PDF\u6253\u5370\u670d\u52a1\u5668&lt;\/title&gt;<br \/>\n    &lt;style&gt;<br \/>\n        :root {<br \/>\n            &#8211;primary-color: #4361ee;<br \/>\n            &#8211;secondary-color: #3a0ca3;<br \/>\n            &#8211;success-color: #2ecc71;<br \/>\n            &#8211;error-color: #e74c3c;<br \/>\n            &#8211;warning-color: #f39c12;<br \/>\n            &#8211;info-color: #3498db;<br \/>\n            &#8211;light-bg: #f8f9fa;<br \/>\n            &#8211;dark-text: #333;<br \/>\n            &#8211;light-text: #666;<br \/>\n            &#8211;border-color: #ddd;<br \/>\n            &#8211;shadow: 0 4px 6px rgba(0, 0, 0, 0.1);<br \/>\n            &#8211;shadow-hover: 0 8px 15px rgba(0, 0, 0, 0.15);<br \/>\n        }<\/p>\n<p>        * {<br \/>\n            box-sizing: border-box;<br \/>\n            margin: 0;<br \/>\n            padding: 0;<br \/>\n            -webkit-tap-highlight-color: transparent;<br \/>\n        }<\/p>\n<p>        body {<br \/>\n            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;<br \/>\n            line-height: 1.6;<br \/>\n            color: var(&#8211;dark-text);<br \/>\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);<br \/>\n            min-height: 100vh;<br \/>\n            padding: 10px;<br \/>\n            overflow-x: hidden;<br \/>\n        }<\/p>\n<p>        .container {<br \/>\n            max-width: 1200px;<br \/>\n            margin: 0 auto;<br \/>\n            background: rgba(255, 255, 255, 0.98);<br \/>\n            border-radius: 12px;<br \/>\n            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        \/* \u79fb\u52a8\u7aef\u4f18\u5316\u5934\u90e8 *\/<br \/>\n        header {<br \/>\n            background: linear-gradient(135deg, var(&#8211;primary-color), var(&#8211;secondary-color));<br \/>\n            color: white;<br \/>\n            padding: 15px;<br \/>\n            text-align: center;<br \/>\n            border-bottom: 3px solid rgba(255, 255, 255, 0.2);<br \/>\n        }<\/p>\n<p>        .header-content h1 {<br \/>\n            font-size: 1.6rem;<br \/>\n            margin-bottom: 8px;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            justify-content: center;<br \/>\n            gap: 8px;<br \/>\n            flex-wrap: wrap;<br \/>\n        }<\/p>\n<p>        .header-content h1 i {<br \/>\n            font-size: 1.4rem;<br \/>\n            animation: pulse 2s infinite;<br \/>\n        }<\/p>\n<p>        &#064;keyframes pulse {<br \/>\n            0% {<br \/>\n                transform: scale(1);<br \/>\n            }<\/p>\n<p>            50% {<br \/>\n                transform: scale(1.1);<br \/>\n            }<\/p>\n<p>            100% {<br \/>\n                transform: scale(1);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .header-content p {<br \/>\n            font-size: 0.9rem;<br \/>\n            opacity: 0.9;<br \/>\n            line-height: 1.3;<br \/>\n        }<\/p>\n<p>        \/* \u79fb\u52a8\u7aef\u4e3b\u5185\u5bb9\u5e03\u5c40 &#8211; \u4fee\u590d\u4e0a\u4f20\u533a\u57df *\/<br \/>\n        .main-content {<br \/>\n            display: flex;<br \/>\n            flex-direction: column;<br \/>\n            gap: 15px;<br \/>\n            padding: 15px;<br \/>\n            background: var(&#8211;light-bg);<br \/>\n            width: 100%;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .main-content {<br \/>\n                display: grid;<br \/>\n                grid-template-columns: 1fr 1fr;<br \/>\n                gap: 20px;<br \/>\n                padding: 25px;<br \/>\n            }<\/p>\n<p>            .header-content h1 {<br \/>\n                font-size: 2.2rem;<br \/>\n            }<\/p>\n<p>            .header-content p {<br \/>\n                font-size: 1.1rem;<br \/>\n            }<\/p>\n<p>            header {<br \/>\n                padding: 25px 30px;<br \/>\n            }<\/p>\n<p>            body {<br \/>\n                padding: 15px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/* \u5361\u7247\u4f18\u5316 &#8211; \u786e\u4fdd\u4e0d\u8d85\u51fa\u5bb9\u5668 *\/<br \/>\n        .card {<br \/>\n            background: white;<br \/>\n            border-radius: 10px;<br \/>\n            padding: 18px;<br \/>\n            box-shadow: var(&#8211;shadow);<br \/>\n            transition: all 0.3s ease;<br \/>\n            border: 1px solid rgba(0, 0, 0, 0.05);<br \/>\n            width: 100%;<br \/>\n            max-width: 100%;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .card {<br \/>\n                padding: 25px;<br \/>\n                border-radius: 12px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .card h2 {<br \/>\n            color: var(&#8211;primary-color);<br \/>\n            margin-bottom: 15px;<br \/>\n            padding-bottom: 10px;<br \/>\n            border-bottom: 2px solid var(&#8211;light-bg);<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 8px;<br \/>\n            font-size: 1.2rem;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .card h2 {<br \/>\n                font-size: 1.4rem;<br \/>\n                margin-bottom: 20px;<br \/>\n                padding-bottom: 12px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/* \u8868\u5355\u7ec4\u4f18\u5316 &#8211; \u786e\u4fdd\u4e0d\u8d85\u51fa\u8fb9\u754c *\/<br \/>\n        .form-group {<br \/>\n            margin-bottom: 15px;<br \/>\n            width: 100%;<br \/>\n            max-width: 100%;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .form-group {<br \/>\n                margin-bottom: 20px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .form-group label {<br \/>\n            display: block;<br \/>\n            margin-bottom: 6px;<br \/>\n            font-weight: 600;<br \/>\n            color: var(&#8211;dark-text);<br \/>\n            font-size: 0.9rem;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 5px;<br \/>\n            width: 100%;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .form-group label {<br \/>\n                font-size: 1rem;<br \/>\n                margin-bottom: 8px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .form-group label i {<br \/>\n            color: var(&#8211;primary-color);<br \/>\n            width: 16px;<br \/>\n            text-align: center;<br \/>\n            flex-shrink: 0;<br \/>\n        }<\/p>\n<p>        \/* \u6587\u4ef6\u8f93\u5165\u4f18\u5316 &#8211; \u4fee\u590d\u8d85\u51fa\u95ee\u9898 *\/<br \/>\n        .file-input-wrapper {<br \/>\n            position: relative;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 6px;<br \/>\n            width: 100%;<br \/>\n            max-width: 100%;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        #pdfFile {<br \/>\n            flex: 1;<br \/>\n            min-width: 0;<br \/>\n            padding: 10px;<br \/>\n            border: 2px dashed var(&#8211;border-color);<br \/>\n            border-radius: 6px;<br \/>\n            background: white;<br \/>\n            cursor: pointer;<br \/>\n            transition: all 0.3s;<br \/>\n            font-size: 0.85rem;<br \/>\n            width: 100%;<br \/>\n            max-width: 100%;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        .file-name {<br \/>\n            font-size: 0.8rem;<br \/>\n            color: var(&#8211;light-text);<br \/>\n            white-space: nowrap;<br \/>\n            overflow: hidden;<br \/>\n            text-overflow: ellipsis;<br \/>\n            max-width: 120px;<br \/>\n            flex-shrink: 0;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .file-name {<br \/>\n                max-width: 180px;<br \/>\n                font-size: 0.85rem;<br \/>\n            }<\/p>\n<p>            #pdfFile {<br \/>\n                font-size: 0.9rem;<br \/>\n                padding: 12px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .file-hint {<br \/>\n            font-size: 0.75rem;<br \/>\n            color: var(&#8211;light-text);<br \/>\n            margin-top: 4px;<br \/>\n            font-style: italic;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        \/* \u6253\u5370\u673a\u9009\u62e9\u4f18\u5316 &#8211; \u786e\u4fdd\u4e0d\u8d85\u51fa *\/<br \/>\n        .printer-select-wrapper {<br \/>\n            display: flex;<br \/>\n            gap: 6px;<br \/>\n            align-items: center;<br \/>\n            width: 100%;<br \/>\n            max-width: 100%;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        #printerSelect {<br \/>\n            flex: 1;<br \/>\n            min-width: 0;<br \/>\n            padding: 10px;<br \/>\n            border: 2px solid var(&#8211;border-color);<br \/>\n            border-radius: 6px;<br \/>\n            font-size: 0.9rem;<br \/>\n            background: white;<br \/>\n            cursor: pointer;<br \/>\n            transition: all 0.3s;<br \/>\n            -webkit-appearance: none;<br \/>\n            appearance: none;<br \/>\n            width: 100%;<br \/>\n            max-width: 100%;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        \/* \u89e6\u6478\u53cb\u597d\u7684\u6309\u94ae &#8211; \u786e\u4fdd\u4e0d\u8d85\u51fa *\/<br \/>\n        .icon-btn, .primary-btn, .secondary-btn {<br \/>\n            padding: 10px 12px;<br \/>\n            background: var(&#8211;primary-color);<br \/>\n            color: white;<br \/>\n            border: none;<br \/>\n            border-radius: 6px;<br \/>\n            cursor: pointer;<br \/>\n            transition: all 0.3s;<br \/>\n            font-size: 0.9rem;<br \/>\n            touch-action: manipulation;<br \/>\n            min-height: 40px;<br \/>\n            white-space: nowrap;<br \/>\n            flex-shrink: 0;<br \/>\n        }<\/p>\n<p>        .icon-btn {<br \/>\n            padding: 10px;<br \/>\n            min-width: 40px;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            justify-content: center;<br \/>\n        }<\/p>\n<p>        .primary-btn {<br \/>\n            padding: 14px 16px;<br \/>\n            font-size: 1rem;<br \/>\n            font-weight: 600;<br \/>\n            width: 100%;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            justify-content: center;<br \/>\n            gap: 8px;<br \/>\n            box-shadow: 0 3px 10px rgba(67, 97, 238, 0.3);<br \/>\n        }<\/p>\n<p>        .secondary-btn {<br \/>\n            padding: 8px 12px;<br \/>\n            font-size: 0.85rem;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 5px;<br \/>\n            background: white;<br \/>\n            color: var(&#8211;primary-color);<br \/>\n            border: 2px solid var(&#8211;primary-color);<br \/>\n        }<\/p>\n<p>        \/* \u89e6\u6478\u53cd\u9988 *\/<br \/>\n        .icon-btn:active, .primary-btn:active, .secondary-btn:active {<br \/>\n            transform: scale(0.98);<br \/>\n        }<\/p>\n<p>        \/* \u4efd\u6570\u8f93\u5165\u4f18\u5316 *\/<br \/>\n        #copies {<br \/>\n            width: 80px;<br \/>\n            padding: 10px;<br \/>\n            border: 2px solid var(&#8211;border-color);<br \/>\n            border-radius: 6px;<br \/>\n            font-size: 0.9rem;<br \/>\n            text-align: center;<br \/>\n            min-height: 40px;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            #copies {<br \/>\n                width: 100px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/* \u4efb\u52a1\u5217\u8868\u4f18\u5316 *\/<br \/>\n        .task-controls {<br \/>\n            display: flex;<br \/>\n            gap: 6px;<br \/>\n            margin-bottom: 12px;<br \/>\n            flex-wrap: wrap;<br \/>\n            width: 100%;<br \/>\n        }<\/p>\n<p>        .task-list {<br \/>\n            min-height: 160px;<br \/>\n            max-height: 300px;<br \/>\n            overflow-y: auto;<br \/>\n            background: #fafafa;<br \/>\n            border-radius: 6px;<br \/>\n            padding: 8px;<br \/>\n            border: 1px solid #eee;<br \/>\n            width: 100%;<br \/>\n        }<\/p>\n<p>        .task-item {<br \/>\n            padding: 10px;<br \/>\n            margin-bottom: 6px;<br \/>\n            background: white;<br \/>\n            border-radius: 6px;<br \/>\n            border-left: 3px solid var(&#8211;primary-color);<br \/>\n            display: flex;<br \/>\n            justify-content: space-between;<br \/>\n            align-items: center;<br \/>\n            transition: all 0.2s;<br \/>\n            animation: slideIn 0.3s ease;<br \/>\n            width: 100%;<br \/>\n            max-width: 100%;<br \/>\n            overflow: hidden;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .task-item {<br \/>\n                padding: 12px;<br \/>\n                margin-bottom: 8px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .task-info {<br \/>\n            flex: 1;<br \/>\n            padding-right: 8px;<br \/>\n            min-width: 0;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        .task-filename {<br \/>\n            font-weight: 600;<br \/>\n            color: var(&#8211;dark-text);<br \/>\n            margin-bottom: 4px;<br \/>\n            font-size: 0.85rem;<br \/>\n            word-break: break-word;<br \/>\n            overflow: hidden;<br \/>\n            text-overflow: ellipsis;<br \/>\n            display: -webkit-box;<br \/>\n            -webkit-line-clamp: 2;<br \/>\n            -webkit-box-orient: vertical;<br \/>\n        }<\/p>\n<p>        .task-details {<br \/>\n            font-size: 0.75rem;<br \/>\n            color: var(&#8211;light-text);<br \/>\n            display: flex;<br \/>\n            gap: 8px;<br \/>\n            flex-wrap: wrap;<br \/>\n        }<\/p>\n<p>        \/* \u4fe1\u606f\u6846\u4f18\u5316 *\/<br \/>\n        .info-box {<br \/>\n            background: white;<br \/>\n            border-radius: 10px;<br \/>\n            padding: 18px;<br \/>\n            margin: 0 12px 15px;<br \/>\n            box-shadow: var(&#8211;shadow);<br \/>\n            border: 1px solid rgba(0, 0, 0, 0.05);<br \/>\n            width: calc(100% &#8211; 24px);<br \/>\n            max-width: 100%;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .info-box {<br \/>\n                padding: 25px;<br \/>\n                margin: 0 20px 25px;<br \/>\n                border-radius: 12px;<br \/>\n                width: calc(100% &#8211; 40px);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .info-box h3 {<br \/>\n            color: var(&#8211;primary-color);<br \/>\n            margin-bottom: 12px;<br \/>\n            font-size: 1.1rem;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 6px;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .info-box h3 {<br \/>\n                font-size: 1.25rem;<br \/>\n                margin-bottom: 15px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .info-box ol {<br \/>\n            padding-left: 18px;<br \/>\n            margin-bottom: 15px;<br \/>\n        }<\/p>\n<p>        .info-box li {<br \/>\n            margin-bottom: 6px;<br \/>\n            line-height: 1.5;<br \/>\n            font-size: 0.9rem;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .info-box li {<br \/>\n                margin-bottom: 8px;<br \/>\n                line-height: 1.6;<br \/>\n                font-size: 0.95rem;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/* \u670d\u52a1\u5668\u4fe1\u606f\u4f18\u5316 *\/<br \/>\n        .server-info {<br \/>\n            background: linear-gradient(135deg, #f8f9fa, #e9ecef);<br \/>\n            padding: 12px;<br \/>\n            border-radius: 6px;<br \/>\n            margin-bottom: 12px;<br \/>\n            border: 1px solid #dee2e6;<br \/>\n            width: 100%;<br \/>\n            overflow: hidden;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .server-info {<br \/>\n                padding: 15px;<br \/>\n                margin-bottom: 15px;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .info-row {<br \/>\n            display: flex;<br \/>\n            justify-content: space-between;<br \/>\n            align-items: center;<br \/>\n            padding: 5px 0;<br \/>\n            border-bottom: 1px dashed #dee2e6;<br \/>\n            font-size: 0.85rem;<br \/>\n            width: 100%;<br \/>\n            flex-wrap: wrap;<br \/>\n        }<\/p>\n<p>        .info-row:last-child {<br \/>\n            border-bottom: none;<br \/>\n        }<\/p>\n<p>        \/* \u54cd\u5e94\u5f0f\u5de5\u5177\u63d0\u793a *\/<br \/>\n        .mobile-tip {<br \/>\n            display: block;<br \/>\n            font-size: 0.75rem;<br \/>\n            color: var(&#8211;info-color);<br \/>\n            background: rgba(52, 152, 219, 0.1);<br \/>\n            padding: 5px 8px;<br \/>\n            border-radius: 4px;<br \/>\n            margin-top: 4px;<br \/>\n            border-left: 3px solid var(&#8211;info-color);<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        &#064;media (min-width: 768px) {<br \/>\n            .mobile-tip {<br \/>\n                display: none;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/* \u6eda\u52a8\u6761\u4f18\u5316 *\/<br \/>\n        .task-list::-webkit-scrollbar {<br \/>\n            width: 5px;<br \/>\n        }<\/p>\n<p>        .task-list::-webkit-scrollbar-track {<br \/>\n            background: #f1f1f1;<br \/>\n            border-radius: 4px;<br \/>\n        }<\/p>\n<p>        .task-list::-webkit-scrollbar-thumb {<br \/>\n            background: var(&#8211;primary-color);<br \/>\n            border-radius: 4px;<br \/>\n        }<\/p>\n<p>        \/* \u79fb\u52a8\u7aef\u4e0b\u62c9\u83dc\u5355\u4f18\u5316 *\/<br \/>\n        select {<br \/>\n            font-size: 16px; \/* \u9632\u6b62iOS\u7f29\u653e *\/<br \/>\n        }<\/p>\n<p>        \/* \u89e6\u6478\u8bbe\u5907\u60ac\u505c\u72b6\u6001\u4fee\u590d *\/<br \/>\n        &#064;media (hover: none) {<br \/>\n            .card:hover {<br \/>\n                transform: none;<br \/>\n                box-shadow: var(&#8211;shadow);<br \/>\n            }<\/p>\n<p>            .icon-btn:hover, .primary-btn:hover, .secondary-btn:hover {<br \/>\n                transform: none;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/* \u786e\u4fdd\u957f\u6309\u4e0d\u4f1a\u51fa\u73b0\u6587\u672c\u9009\u62e9 *\/<br \/>\n        .no-select {<br \/>\n            -webkit-user-select: none;<br \/>\n            -moz-user-select: none;<br \/>\n            -ms-user-select: none;<br \/>\n            user-select: none;<br \/>\n        }<\/p>\n<p>        \/* \u8bbe\u7f6e\u4fe1\u606f\u6837\u5f0f *\/<br \/>\n        .settings-info {<br \/>\n            background: linear-gradient(135deg, #e3f2fd, #bbdefb);<br \/>\n            padding: 10px;<br \/>\n            border-radius: 6px;<br \/>\n            margin: 12px 0;<br \/>\n            border-left: 3px solid #2196f3;<br \/>\n            font-size: 0.85rem;<br \/>\n            color: #1565c0;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 6px;<br \/>\n            width: 100%;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        .printer-hint {<br \/>\n            font-size: 0.8rem;<br \/>\n            color: var(&#8211;info-color);<br \/>\n            margin-top: 6px;<br \/>\n            padding: 6px 8px;<br \/>\n            background: rgba(52, 152, 219, 0.1);<br \/>\n            border-radius: 4px;<br \/>\n            border-left: 3px solid var(&#8211;info-color);<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        .copies-hint {<br \/>\n            font-size: 0.75rem;<br \/>\n            color: var(&#8211;light-text);<br \/>\n            margin-top: 4px;<br \/>\n        }<\/p>\n<p>        \/* \u6d88\u606f\u6846\u52a8\u753b *\/<br \/>\n        .message {<br \/>\n            padding: 10px;<br \/>\n            border-radius: 6px;<br \/>\n            margin-top: 10px;<br \/>\n            font-weight: 500;<br \/>\n            display: none;<br \/>\n            border-left: 3px solid;<br \/>\n            animation: slideIn 0.3s ease;<br \/>\n            font-size: 0.9rem;<br \/>\n            word-break: break-word;<br \/>\n            width: 100%;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        &#064;keyframes slideIn {<br \/>\n            from {<br \/>\n                opacity: 0;<br \/>\n                transform: translateY(-8px);<br \/>\n            }<\/p>\n<p>            to {<br \/>\n                opacity: 1;<br \/>\n                transform: translateY(0);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        .message.success {<br \/>\n            background-color: #d4edda;<br \/>\n            color: #155724;<br \/>\n            border-color: var(&#8211;success-color);<br \/>\n            display: block;<br \/>\n        }<\/p>\n<p>        .message.error {<br \/>\n            background-color: #f8d7da;<br \/>\n            color: #721c24;<br \/>\n            border-color: var(&#8211;error-color);<br \/>\n            display: block;<br \/>\n        }<\/p>\n<p>        .message.info {<br \/>\n            background-color: #d1ecf1;<br \/>\n            color: #0c5460;<br \/>\n            border-color: var(&#8211;info-color);<br \/>\n            display: block;<br \/>\n        }<\/p>\n<p>        \/* \u8bbe\u7f6e\u7ba1\u7406\u4f18\u5316 *\/<br \/>\n        .settings-manage {<br \/>\n            background: #f8f9fa;<br \/>\n            padding: 12px;<br \/>\n            border-radius: 6px;<br \/>\n            margin-top: 12px;<br \/>\n            border: 1px solid #dee2e6;<br \/>\n            width: 100%;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        .settings-manage h4 {<br \/>\n            color: var(&#8211;primary-color);<br \/>\n            margin-bottom: 10px;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 5px;<br \/>\n            font-size: 0.95rem;<br \/>\n        }<\/p>\n<p>        .settings-actions {<br \/>\n            display: flex;<br \/>\n            gap: 6px;<br \/>\n            margin-bottom: 10px;<br \/>\n            flex-wrap: wrap;<br \/>\n        }<\/p>\n<p>        .settings-note {<br \/>\n            font-size: 0.8rem;<br \/>\n            color: var(&#8211;light-text);<br \/>\n            margin-top: 8px;<br \/>\n            padding: 6px 8px;<br \/>\n            background: rgba(67, 97, 238, 0.05);<br \/>\n            border-radius: 4px;<br \/>\n            border-left: 3px solid var(&#8211;primary-color);<br \/>\n            line-height: 1.4;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        \/* \u6545\u969c\u6392\u9664\u4f18\u5316 *\/<br \/>\n        .troubleshooting {<br \/>\n            background: #fff3cd;<br \/>\n            padding: 10px;<br \/>\n            border-radius: 6px;<br \/>\n            border: 1px solid #ffeaa7;<br \/>\n            width: 100%;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        .troubleshooting h4 {<br \/>\n            color: #856404;<br \/>\n            margin-bottom: 6px;<br \/>\n            display: flex;<br \/>\n            align-items: center;<br \/>\n            gap: 5px;<br \/>\n            font-size: 0.95rem;<br \/>\n        }<\/p>\n<p>        .troubleshooting ul {<br \/>\n            padding-left: 16px;<br \/>\n        }<\/p>\n<p>        .troubleshooting li {<br \/>\n            margin-bottom: 5px;<br \/>\n            color: #856404;<br \/>\n            font-size: 0.8rem;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        \/* \u9875\u811a\u4f18\u5316 *\/<br \/>\n        .footer {<br \/>\n            text-align: center;<br \/>\n            padding: 12px;<br \/>\n            background: #f8f9fa;<br \/>\n            color: var(&#8211;light-text);<br \/>\n            border-top: 1px solid #e9ecef;<br \/>\n            font-size: 0.8rem;<br \/>\n            width: 100%;<br \/>\n            box-sizing: border-box;<br \/>\n        }<\/p>\n<p>        .footer p {<br \/>\n            margin: 3px 0;<br \/>\n            word-break: break-word;<br \/>\n        }<\/p>\n<p>        \/* \u7a7a\u72b6\u6001 *\/<br \/>\n        .empty-state {<br \/>\n            text-align: center;<br \/>\n            padding: 30px 15px;<br \/>\n            color: var(&#8211;light-text);<br \/>\n        }<\/p>\n<p>        .empty-state i {<br \/>\n            color: #ccc;<br \/>\n            margin-bottom: 10px;<br \/>\n        }<\/p>\n<p>        .empty-state p {<br \/>\n            font-size: 1rem;<br \/>\n            margin-bottom: 4px;<br \/>\n        }<\/p>\n<p>        .empty-state small {<br \/>\n            font-size: 0.85rem;<br \/>\n        }<\/p>\n<p>        \/* \u72b6\u6001\u6807\u7b7e *\/<br \/>\n        .task-status {<br \/>\n            padding: 5px 8px;<br \/>\n            border-radius: 15px;<br \/>\n            font-size: 0.7rem;<br \/>\n            font-weight: 600;<br \/>\n            text-transform: uppercase;<br \/>\n            white-space: nowrap;<br \/>\n            min-width: 70px;<br \/>\n            text-align: center;<br \/>\n            flex-shrink: 0;<br \/>\n        }<\/p>\n<p>        .status-queued {<br \/>\n            background-color: #fff3cd;<br \/>\n            color: #856404;<br \/>\n            border: 1px solid #ffeaa7;<br \/>\n        }<\/p>\n<p>        .status-processing {<br \/>\n            background-color: #d1ecf1;<br \/>\n            color: #0c5460;<br \/>\n            border: 1px solid #bee5eb;<br \/>\n        }<\/p>\n<p>        .status-completed {<br \/>\n            background-color: #d4edda;<br \/>\n            color: #155724;<br \/>\n            border: 1px solid #c3e6cb;<br \/>\n        }<\/p>\n<p>        .status-failed {<br \/>\n            background-color: #f8d7da;<br \/>\n            color: #721c24;<br \/>\n            border: 1px solid #f5c6cb;<br \/>\n        }<\/p>\n<p>        \/* \u8fdb\u5ea6\u6761 *\/<br \/>\n        .progress-bar {<br \/>\n            height: 8px;<br \/>\n            background-color: #e0e0e0;<br \/>\n            border-radius: 4px;<br \/>\n            margin: 15px 0;<br \/>\n            overflow: hidden;<br \/>\n            display: none;<br \/>\n            position: relative;<br \/>\n            width: 100%;<br \/>\n        }<\/p>\n<p>        .progress-fill {<br \/>\n            height: 100%;<br \/>\n            background: linear-gradient(90deg, var(&#8211;primary-color), var(&#8211;success-color));<br \/>\n            width: 0%;<br \/>\n            transition: width 0.3s ease;<br \/>\n            border-radius: 4px;<br \/>\n        }<\/p>\n<p>        \/* \u4fee\u590d\u79fb\u52a8\u7aef\u8f93\u5165\u6846\u805a\u7126\u65f6\u7684\u7f29\u653e\u95ee\u9898 *\/<br \/>\n        &#064;media screen and (max-width: 767px) {<br \/>\n            input, select, textarea {<br \/>\n                font-size: 16px !important;<br \/>\n            }<br \/>\n        }<br \/>\n    &lt;\/style&gt;<br \/>\n    &lt;link rel&#061;&#034;stylesheet&#034; href&#061;&#034;https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.4.0\/css\/all.min.css&#034;&gt;<br \/>\n&lt;\/head&gt;<\/p>\n<p>&lt;body&gt;<br \/>\n    &lt;div class&#061;&#034;container&#034;&gt;<br \/>\n        &lt;header&gt;<br \/>\n            &lt;div class&#061;&#034;header-content&#034;&gt;<br \/>\n                &lt;h1&gt;&lt;i class&#061;&#034;fas fa-print&#034;&gt;&lt;\/i&gt; PDF\u6253\u5370\u670d\u52a1\u5668&lt;\/h1&gt;<br \/>\n                &lt;p&gt;\u4e0a\u4f20PDF\u6587\u4ef6\u5e76\u901a\u8fc7\u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370&lt;\/p&gt;<br \/>\n                &lt;div class&#061;&#034;mobile-tip&#034;&gt;<br \/>\n                    &lt;i class&#061;&#034;fas fa-mobile-alt&#034;&gt;&lt;\/i&gt; \u79fb\u52a8\u7aef\u4f18\u5316\u7248\u672c<br \/>\n                &lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n        &lt;\/header&gt;<\/p>\n<p>        &lt;div class&#061;&#034;main-content&#034;&gt;<br \/>\n            &lt;!&#8211; \u4e0a\u4f20\u533a\u57df &#8211; \u5df2\u4fee\u590d\u663e\u793a\u95ee\u9898 &#8211;&gt;<br \/>\n            &lt;div class&#061;&#034;card&#034; id&#061;&#034;uploadCard&#034;&gt;<br \/>\n                &lt;h2&gt;&lt;i class&#061;&#034;fas fa-upload&#034;&gt;&lt;\/i&gt; \u4e0a\u4f20PDF\u6587\u4ef6&lt;\/h2&gt;<\/p>\n<p>                &lt;div class&#061;&#034;form-group&#034;&gt;<br \/>\n                    &lt;label for&#061;&#034;pdfFile&#034;&gt;\u9009\u62e9PDF\u6587\u4ef6:&lt;\/label&gt;<br \/>\n                    &lt;div class&#061;&#034;file-input-wrapper&#034;&gt;<br \/>\n                        &lt;input type&#061;&#034;file&#034; id&#061;&#034;pdfFile&#034; accept&#061;&#034;.pdf&#034; required&gt;<br \/>\n                        &lt;span class&#061;&#034;file-name&#034; id&#061;&#034;fileName&#034;&gt;\u672a\u9009\u62e9\u6587\u4ef6&lt;\/span&gt;<br \/>\n                    &lt;\/div&gt;<br \/>\n                    &lt;div class&#061;&#034;file-hint&#034;&gt;\u6700\u5927\u652f\u6301 10MB&#xff0c;\u4ec5\u9650PDF\u683c\u5f0f&lt;\/div&gt;<br \/>\n                    &lt;div class&#061;&#034;mobile-tip&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-hand-pointer&#034;&gt;&lt;\/i&gt; \u70b9\u51fb\u9009\u62e9\u6587\u4ef6<br \/>\n                    &lt;\/div&gt;<br \/>\n                &lt;\/div&gt;<\/p>\n<p>                &lt;div class&#061;&#034;form-group&#034;&gt;<br \/>\n                    &lt;label for&#061;&#034;printerSelect&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-print&#034;&gt;&lt;\/i&gt; \u9009\u62e9\u6253\u5370\u673a:<br \/>\n                    &lt;\/label&gt;<br \/>\n                    &lt;div class&#061;&#034;printer-select-wrapper&#034;&gt;<br \/>\n                        &lt;select id&#061;&#034;printerSelect&#034; required&gt;<br \/>\n                            &lt;option value&#061;&#034;&#034;&gt;\u52a0\u8f7d\u4e2d&#8230;&lt;\/option&gt;<br \/>\n                        &lt;\/select&gt;<br \/>\n                        &lt;button type&#061;&#034;button&#034; id&#061;&#034;refreshPrinters&#034; class&#061;&#034;icon-btn&#034; title&#061;&#034;\u5237\u65b0\u6253\u5370\u673a\u5217\u8868&#034;&gt;<br \/>\n                            &lt;i class&#061;&#034;fas fa-sync-alt&#034;&gt;&lt;\/i&gt;<br \/>\n                        &lt;\/button&gt;<br \/>\n                    &lt;\/div&gt;<br \/>\n                    &lt;div class&#061;&#034;printer-hint&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-info-circle&#034;&gt;&lt;\/i&gt; \u6253\u5370\u673a\u5217\u8868\u4f1a\u81ea\u52a8\u4ece\u670d\u52a1\u5668\u83b7\u53d6<br \/>\n                    &lt;\/div&gt;<br \/>\n                    &lt;div class&#061;&#034;mobile-tip&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-arrow-down&#034;&gt;&lt;\/i&gt; \u70b9\u51fb\u4e0b\u62c9\u9009\u62e9\u6253\u5370\u673a<br \/>\n                    &lt;\/div&gt;<br \/>\n                &lt;\/div&gt;<\/p>\n<p>                &lt;div class&#061;&#034;form-group&#034;&gt;<br \/>\n                    &lt;label for&#061;&#034;copies&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-copy&#034;&gt;&lt;\/i&gt; \u6253\u5370\u4efd\u6570:<br \/>\n                    &lt;\/label&gt;<br \/>\n                    &lt;input type&#061;&#034;number&#034; id&#061;&#034;copies&#034; min&#061;&#034;1&#034; max&#061;&#034;100&#034; value&#061;&#034;1&#034; required&gt;<br \/>\n                    &lt;div class&#061;&#034;copies-hint&#034;&gt;\u8303\u56f4: 1 &#8211; 100&lt;\/div&gt;<br \/>\n                &lt;\/div&gt;<\/p>\n<p>                &lt;div class&#061;&#034;settings-info&#034;&gt;<br \/>\n                    &lt;i class&#061;&#034;fas fa-save&#034;&gt;&lt;\/i&gt; \u8bbe\u7f6e\u4f1a\u81ea\u52a8\u4fdd\u5b58&#xff0c;\u5237\u65b0\u9875\u9762\u540e\u4ecd\u7136\u6709\u6548<br \/>\n                &lt;\/div&gt;<\/p>\n<p>                &lt;button id&#061;&#034;uploadBtn&#034; class&#061;&#034;primary-btn&#034;&gt;<br \/>\n                    &lt;i class&#061;&#034;fas fa-cloud-upload-alt&#034;&gt;&lt;\/i&gt; \u4e0a\u4f20\u5e76\u6253\u5370<br \/>\n                &lt;\/button&gt;<\/p>\n<p>                &lt;div id&#061;&#034;uploadProgress&#034; class&#061;&#034;progress-bar&#034;&gt;<br \/>\n                    &lt;div class&#061;&#034;progress-fill&#034; style&#061;&#034;width: 0%&#034;&gt;&lt;\/div&gt;<br \/>\n                &lt;\/div&gt;<\/p>\n<p>                &lt;div id&#061;&#034;message&#034; class&#061;&#034;message&#034;&gt;&lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<\/p>\n<p>            &lt;!&#8211; \u4efb\u52a1\u72b6\u6001\u533a\u57df &#8211;&gt;<br \/>\n            &lt;div class&#061;&#034;card&#034; id&#061;&#034;taskCard&#034;&gt;<br \/>\n                &lt;h2&gt;&lt;i class&#061;&#034;fas fa-tasks&#034;&gt;&lt;\/i&gt; \u6253\u5370\u4efb\u52a1\u72b6\u6001&lt;\/h2&gt;<\/p>\n<p>                &lt;div class&#061;&#034;task-controls&#034;&gt;<br \/>\n                    &lt;button id&#061;&#034;clearCompleted&#034; class&#061;&#034;secondary-btn&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-trash-alt&#034;&gt;&lt;\/i&gt; \u6e05\u9664\u5df2\u5b8c\u6210<br \/>\n                    &lt;\/button&gt;<br \/>\n                    &lt;button id&#061;&#034;refreshTasks&#034; class&#061;&#034;secondary-btn&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-sync-alt&#034;&gt;&lt;\/i&gt; \u5237\u65b0\u72b6\u6001<br \/>\n                    &lt;\/button&gt;<br \/>\n                &lt;\/div&gt;<\/p>\n<p>                &lt;div id&#061;&#034;taskStatus&#034; class&#061;&#034;task-list&#034;&gt;<br \/>\n                    &lt;div class&#061;&#034;empty-state&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-inbox fa-3x&#034;&gt;&lt;\/i&gt;<br \/>\n                        &lt;p&gt;\u6682\u65e0\u4efb\u52a1&lt;\/p&gt;<br \/>\n                        &lt;small&gt;\u4e0a\u4f20\u6587\u4ef6\u540e&#xff0c;\u4efb\u52a1\u5c06\u663e\u793a\u5728\u8fd9\u91cc&lt;\/small&gt;<br \/>\n                    &lt;\/div&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n                &lt;div class&#061;&#034;mobile-tip&#034;&gt;<br \/>\n                    &lt;i class&#061;&#034;fas fa-arrows-alt-v&#034;&gt;&lt;\/i&gt; \u53ef\u4ee5\u4e0a\u4e0b\u6eda\u52a8\u67e5\u770b\u4efb\u52a1\u5217\u8868<br \/>\n                &lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;!&#8211; \u4fe1\u606f\u533a\u57df &#8211;&gt;<br \/>\n        &lt;div class&#061;&#034;info-box&#034;&gt;<br \/>\n            &lt;h3&gt;&lt;i class&#061;&#034;fas fa-info-circle&#034;&gt;&lt;\/i&gt; \u4f7f\u7528\u8bf4\u660e&lt;\/h3&gt;<br \/>\n            &lt;ol&gt;<br \/>\n                &lt;li&gt;\u786e\u4fdd\u60a8\u7684\u8bbe\u5907\u4e0e\u670d\u52a1\u5668\u5728\u540c\u4e00\u5c40\u57df\u7f51\u4e2d&lt;\/li&gt;<br \/>\n                &lt;li&gt;\u9009\u62e9\u8981\u6253\u5370\u7684PDF\u6587\u4ef6&#xff08;\u6700\u592710MB&#xff09;&lt;\/li&gt;<br \/>\n                &lt;li&gt;&lt;strong&gt;\u9009\u62e9\u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u6253\u5370\u673a&lt;\/strong&gt;&lt;\/li&gt;<br \/>\n                &lt;li&gt;\u8bbe\u7f6e\u6253\u5370\u4efd\u6570&#xff08;1-100&#xff09;&lt;\/li&gt;<br \/>\n                &lt;li&gt;\u70b9\u51fb&#034;\u4e0a\u4f20\u5e76\u6253\u5370&#034;\u6309\u94ae&lt;\/li&gt;<br \/>\n                &lt;li&gt;\u6253\u5370\u5b8c\u6210\u540e&#xff0c;\u4e34\u65f6\u6587\u4ef6\u4f1a\u81ea\u52a8\u5220\u9664&lt;\/li&gt;<br \/>\n            &lt;\/ol&gt;<\/p>\n<p>            &lt;div class&#061;&#034;server-info&#034;&gt;<br \/>\n                &lt;div class&#061;&#034;info-row&#034;&gt;<br \/>\n                    &lt;strong&gt;&lt;i class&#061;&#034;fas fa-network-wired&#034;&gt;&lt;\/i&gt; \u670d\u52a1\u5668\u5730\u5740:&lt;\/strong&gt;<br \/>\n                    &lt;span id&#061;&#034;serverAddress&#034;&gt;\u83b7\u53d6\u4e2d&#8230;&lt;\/span&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n                &lt;div class&#061;&#034;info-row&#034;&gt;<br \/>\n                    &lt;strong&gt;&lt;i class&#061;&#034;fas fa-desktop&#034;&gt;&lt;\/i&gt; \u7cfb\u7edf:&lt;\/strong&gt;<br \/>\n                    &lt;span id&#061;&#034;systemInfo&#034;&gt;\u68c0\u6d4b\u4e2d&#8230;&lt;\/span&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n                &lt;div class&#061;&#034;info-row&#034;&gt;<br \/>\n                    &lt;strong&gt;&lt;i class&#061;&#034;fas fa-print&#034;&gt;&lt;\/i&gt; \u6253\u5370\u673a\u72b6\u6001:&lt;\/strong&gt;<br \/>\n                    &lt;span id&#061;&#034;printerStatus&#034;&gt;\u672a\u77e5&lt;\/span&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<\/p>\n<p>            &lt;div class&#061;&#034;settings-manage&#034;&gt;<br \/>\n                &lt;h4&gt;&lt;i class&#061;&#034;fas fa-cog&#034;&gt;&lt;\/i&gt; \u8bbe\u7f6e\u7ba1\u7406&lt;\/h4&gt;<br \/>\n                &lt;div class&#061;&#034;settings-actions&#034;&gt;<br \/>\n                    &lt;button id&#061;&#034;showSettings&#034; class&#061;&#034;secondary-btn&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-eye&#034;&gt;&lt;\/i&gt; \u67e5\u770b\u5f53\u524d\u8bbe\u7f6e<br \/>\n                    &lt;\/button&gt;<br \/>\n                    &lt;button id&#061;&#034;clearSettings&#034; class&#061;&#034;secondary-btn&#034;&gt;<br \/>\n                        &lt;i class&#061;&#034;fas fa-broom&#034;&gt;&lt;\/i&gt; \u6e05\u9664\u4fdd\u5b58\u7684\u8bbe\u7f6e<br \/>\n                    &lt;\/button&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n                &lt;p class&#061;&#034;settings-note&#034;&gt;<br \/>\n                    &lt;i class&#061;&#034;fas fa-info-circle&#034;&gt;&lt;\/i&gt;<br \/>\n                    \u60a8\u7684\u6253\u5370\u8bbe\u7f6e&#xff08;\u6253\u5370\u673a\u548c\u4efd\u6570&#xff09;\u4f1a\u81ea\u52a8\u4fdd\u5b58\u5230\u6d4f\u89c8\u5668\u4e2d&#xff0c;\u5237\u65b0\u9875\u9762\u540e\u4ecd\u7136\u6709\u6548\u3002<br \/>\n                &lt;\/p&gt;<br \/>\n            &lt;\/div&gt;<\/p>\n<p>            &lt;div class&#061;&#034;troubleshooting&#034;&gt;<br \/>\n                &lt;h4&gt;&lt;i class&#061;&#034;fas fa-tools&#034;&gt;&lt;\/i&gt; \u5e38\u89c1\u95ee\u9898\u89e3\u51b3&lt;\/h4&gt;<br \/>\n                &lt;ul&gt;<br \/>\n                    &lt;li&gt;&lt;strong&gt;Windows\u7528\u6237:&lt;\/strong&gt; \u8bf7\u786e\u4fdd\u5df2\u5b89\u88c5SumatraPDF&#xff0c;\u6216\u4fee\u6539\u4ee3\u7801\u4f7f\u7528\u5176\u4ed6PDF\u6253\u5370\u5de5\u5177&lt;\/li&gt;<br \/>\n                    &lt;li&gt;&lt;strong&gt;\u6253\u5370\u673a\u672a\u5217\u51fa:&lt;\/strong&gt; \u786e\u4fdd\u6253\u5370\u673a\u5df2\u8fde\u63a5\u5e76\u5b89\u88c5\u9a71\u52a8&lt;\/li&gt;<br \/>\n                    &lt;li&gt;&lt;strong&gt;\u6253\u5370\u5931\u8d25:&lt;\/strong&gt; \u68c0\u67e5\u63a7\u5236\u53f0\u65e5\u5fd7&#xff0c;\u786e\u8ba4\u6253\u5370\u673a\u72b6\u6001&lt;\/li&gt;<br \/>\n                    &lt;li&gt;&lt;strong&gt;\u8bbe\u7f6e\u4e0d\u4fdd\u5b58:&lt;\/strong&gt; \u68c0\u67e5\u6d4f\u89c8\u5668\u662f\u5426\u7981\u7528\u4e86localStorage&lt;\/li&gt;<br \/>\n                &lt;\/ul&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<\/p>\n<p>        &lt;div class&#061;&#034;footer&#034;&gt;<br \/>\n            &lt;p&gt;PDF\u6253\u5370\u670d\u52a1\u5668 v1.3 | \u79fb\u52a8\u7aef\u4f18\u5316\u7248&lt;\/p&gt;<br \/>\n            &lt;p&gt;Powered by FastAPI &amp; Python&lt;\/p&gt;<br \/>\n            &lt;p&gt;&lt;i class&#061;&#034;fas fa-save&#034;&gt;&lt;\/i&gt; \u8bbe\u7f6e\u81ea\u52a8\u4fdd\u5b58\u529f\u80fd\u5df2\u542f\u7528&lt;\/p&gt;<br \/>\n            &lt;p&gt;&lt;i class&#061;&#034;fas fa-mobile-alt&#034;&gt;&lt;\/i&gt; \u79fb\u52a8\u7aef\u4f18\u5316\u7248\u672c&lt;\/p&gt;<br \/>\n        &lt;\/div&gt;<br \/>\n    &lt;\/div&gt;<\/p>\n<p>    &lt;script&gt;<br \/>\n        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {<br \/>\n            \/\/ \u83b7\u53d6DOM\u5143\u7d20<br \/>\n            const pdfFileInput &#061; document.getElementById(&#039;pdfFile&#039;);<br \/>\n            const fileNameSpan &#061; document.getElementById(&#039;fileName&#039;);<br \/>\n            const printerSelect &#061; document.getElementById(&#039;printerSelect&#039;);<br \/>\n            const refreshPrintersBtn &#061; document.getElementById(&#039;refreshPrinters&#039;);<br \/>\n            const copiesInput &#061; document.getElementById(&#039;copies&#039;);<br \/>\n            const uploadBtn &#061; document.getElementById(&#039;uploadBtn&#039;);<br \/>\n            const uploadProgress &#061; document.getElementById(&#039;uploadProgress&#039;);<br \/>\n            const progressFill &#061; document.querySelector(&#039;.progress-fill&#039;);<br \/>\n            const messageDiv &#061; document.getElementById(&#039;message&#039;);<br \/>\n            const taskStatusDiv &#061; document.getElementById(&#039;taskStatus&#039;);<br \/>\n            const serverAddressSpan &#061; document.getElementById(&#039;serverAddress&#039;);<br \/>\n            const systemInfoSpan &#061; document.getElementById(&#039;systemInfo&#039;);<br \/>\n            const printerStatusSpan &#061; document.getElementById(&#039;printerStatus&#039;);<br \/>\n            const clearCompletedBtn &#061; document.getElementById(&#039;clearCompleted&#039;);<br \/>\n            const refreshTasksBtn &#061; document.getElementById(&#039;refreshTasks&#039;);<br \/>\n            const showSettingsBtn &#061; document.getElementById(&#039;showSettings&#039;);<br \/>\n            const clearSettingsBtn &#061; document.getElementById(&#039;clearSettings&#039;);<\/p>\n<p>            \/\/ \u8bbe\u7f6e\u670d\u52a1\u5668\u4fe1\u606f<br \/>\n            const serverUrl &#061; &#096;${window.location.protocol}\/\/${window.location.hostname}:${window.location.port}&#096;;<br \/>\n            serverAddressSpan.textContent &#061; serverUrl;<\/p>\n<p>            \/\/ \u5b58\u50a8\u4efb\u52a1\u5217\u8868<br \/>\n            let tasks &#061; [];<\/p>\n<p>            \/\/ \u8bbe\u7f6e\u952e\u540d<br \/>\n            const SETTINGS_KEY &#061; &#039;pdfPrintSettings&#039;;<\/p>\n<p>            \/\/ \u4fdd\u5b58\u8bbe\u7f6e\u5230localStorage<br \/>\n            function saveSettings() {<br \/>\n                const settings &#061; {<br \/>\n                    printer: printerSelect.value,<br \/>\n                    copies: copiesInput.value,<br \/>\n                    lastSaved: new Date().toISOString()<br \/>\n                };<br \/>\n                localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));<br \/>\n                console.log(&#039;\u8bbe\u7f6e\u5df2\u4fdd\u5b58:&#039;, settings);<br \/>\n            }<\/p>\n<p>            \/\/ \u4ecelocalStorage\u52a0\u8f7d\u8bbe\u7f6e<br \/>\n            function loadSettings() {<br \/>\n                try {<br \/>\n                    const savedSettings &#061; localStorage.getItem(SETTINGS_KEY);<br \/>\n                    if (savedSettings) {<br \/>\n                        const settings &#061; JSON.parse(savedSettings);<br \/>\n                        console.log(&#039;\u52a0\u8f7d\u4fdd\u5b58\u7684\u8bbe\u7f6e:&#039;, settings);<br \/>\n                        return settings;<br \/>\n                    }<br \/>\n                } catch (error) {<br \/>\n                    console.error(&#039;\u52a0\u8f7d\u8bbe\u7f6e\u5931\u8d25:&#039;, error);<br \/>\n                }<br \/>\n                return null;<br \/>\n            }<\/p>\n<p>            \/\/ \u6e05\u9664\u4fdd\u5b58\u7684\u8bbe\u7f6e<br \/>\n            function clearSettings() {<br \/>\n                localStorage.removeItem(SETTINGS_KEY);<br \/>\n                showMessage(&#039;\u5df2\u6e05\u9664\u4fdd\u5b58\u7684\u8bbe\u7f6e&#039;, &#039;info&#039;);<br \/>\n                console.log(&#039;\u8bbe\u7f6e\u5df2\u6e05\u9664&#039;);<br \/>\n            }<\/p>\n<p>            \/\/ \u5e94\u7528\u4fdd\u5b58\u7684\u8bbe\u7f6e<br \/>\n            function applySavedSettings(settings) {<br \/>\n                if (settings) {<br \/>\n                    \/\/ \u4fdd\u5b58\u7684\u6253\u5370\u673a\u4f1a\u5728\u6253\u5370\u673a\u5217\u8868\u52a0\u8f7d\u540e\u8bbe\u7f6e<br \/>\n                    \/\/ \u4fdd\u5b58\u7684\u4efd\u6570\u7acb\u5373\u8bbe\u7f6e<br \/>\n                    if (settings.copies) {<br \/>\n                        copiesInput.value &#061; settings.copies;<br \/>\n                    }<br \/>\n                }<br \/>\n            }<\/p>\n<p>            \/\/ \u83b7\u53d6\u7cfb\u7edf\u4fe1\u606f<br \/>\n            async function fetchSystemInfo() {<br \/>\n                try {<br \/>\n                    const response &#061; await fetch(&#039;\/api\/health&#039;);<br \/>\n                    const data &#061; await response.json();<br \/>\n                    systemInfoSpan.textContent &#061; data.system;<\/p>\n<p>                    \/\/ \u68c0\u67e5\u4e0a\u4f20\u76ee\u5f55<br \/>\n                    if (data.upload_dir_exists) {<br \/>\n                        printerStatusSpan.textContent &#061; &#039;\u5c31\u7eea&#039;;<br \/>\n                        printerStatusSpan.style.color &#061; &#039;var(&#8211;success-color)&#039;;<br \/>\n                    } else {<br \/>\n                        printerStatusSpan.textContent &#061; &#039;\u4e0a\u4f20\u76ee\u5f55\u7f3a\u5931&#039;;<br \/>\n                        printerStatusSpan.style.color &#061; &#039;var(&#8211;error-color)&#039;;<br \/>\n                    }<br \/>\n                } catch (error) {<br \/>\n                    systemInfoSpan.textContent &#061; &#039;\u672a\u77e5&#039;;<br \/>\n                    printerStatusSpan.textContent &#061; &#039;\u8fde\u63a5\u5931\u8d25&#039;;<br \/>\n                    printerStatusSpan.style.color &#061; &#039;var(&#8211;error-color)&#039;;<br \/>\n                }<br \/>\n            }<\/p>\n<p>            \/\/ \u83b7\u53d6\u53ef\u7528\u6253\u5370\u673a\u5217\u8868<br \/>\n            async function fetchPrinters(showLoading &#061; true) {<br \/>\n                if (showLoading) {<br \/>\n                    printerSelect.innerHTML &#061; &#039;&lt;option value&#061;&#034;&#034;&gt;\u52a0\u8f7d\u4e2d&#8230;&lt;\/option&gt;&#039;;<br \/>\n                    printerSelect.disabled &#061; true;<br \/>\n                    refreshPrintersBtn.disabled &#061; true;<br \/>\n                }<\/p>\n<p>                try {<br \/>\n                    const response &#061; await fetch(&#039;\/api\/printers&#039;);<br \/>\n                    const data &#061; await response.json();<\/p>\n<p>                    printerSelect.innerHTML &#061; &#039;&#039;;<\/p>\n<p>                    if (data.printers &amp;&amp; data.printers.length &gt; 0) {<br \/>\n                        data.printers.forEach(printer &#061;&gt; {<br \/>\n                            const option &#061; document.createElement(&#039;option&#039;);<br \/>\n                            option.value &#061; printer;<br \/>\n                            option.textContent &#061; printer;<br \/>\n                            printerSelect.appendChild(option);<br \/>\n                        });<\/p>\n<p>                        \/\/ \u5c1d\u8bd5\u5e94\u7528\u4fdd\u5b58\u7684\u6253\u5370\u673a\u8bbe\u7f6e<br \/>\n                        const savedSettings &#061; loadSettings();<br \/>\n                        if (savedSettings &amp;&amp; savedSettings.printer) {<br \/>\n                            \/\/ \u68c0\u67e5\u4fdd\u5b58\u7684\u6253\u5370\u673a\u662f\u5426\u5728\u5f53\u524d\u5217\u8868\u4e2d<br \/>\n                            const printerExists &#061; data.printers.some(p &#061;&gt; p &#061;&#061;&#061; savedSettings.printer);<br \/>\n                            if (printerExists) {<br \/>\n                                printerSelect.value &#061; savedSettings.printer;<br \/>\n                                showMessage(&#096;\u5df2\u6062\u590d\u6253\u5370\u673a: ${savedSettings.printer}&#096;, &#039;info&#039;);<br \/>\n                            }<br \/>\n                        }<\/p>\n<p>                        printerStatusSpan.textContent &#061; &#096;\u627e\u5230 ${data.printers.length} \u53f0\u6253\u5370\u673a&#096;;<br \/>\n                        printerStatusSpan.style.color &#061; &#039;var(&#8211;success-color)&#039;;<br \/>\n                    } else {<br \/>\n                        const option &#061; document.createElement(&#039;option&#039;);<br \/>\n                        option.value &#061; &#039;&#039;;<br \/>\n                        option.textContent &#061; &#039;\u672a\u68c0\u6d4b\u5230\u6253\u5370\u673a&#039;;<br \/>\n                        printerSelect.appendChild(option);<\/p>\n<p>                        printerStatusSpan.textContent &#061; &#039;\u672a\u627e\u5230\u6253\u5370\u673a&#039;;<br \/>\n                        printerStatusSpan.style.color &#061; &#039;var(&#8211;warning-color)&#039;;<br \/>\n                    }<\/p>\n<p>                    printerSelect.disabled &#061; false;<br \/>\n                    refreshPrintersBtn.disabled &#061; false;<\/p>\n<p>                } catch (error) {<br \/>\n                    console.error(&#039;\u83b7\u53d6\u6253\u5370\u673a\u5931\u8d25:&#039;, error);<br \/>\n                    printerSelect.innerHTML &#061; &#039;&lt;option value&#061;&#034;&#034;&gt;\u9519\u8bef&lt;\/option&gt;&#039;;<br \/>\n                    printerSelect.disabled &#061; false;<br \/>\n                    refreshPrintersBtn.disabled &#061; false;<\/p>\n<p>                    printerStatusSpan.textContent &#061; &#039;\u83b7\u53d6\u5931\u8d25&#039;;<br \/>\n                    printerStatusSpan.style.color &#061; &#039;var(&#8211;error-color)&#039;;<\/p>\n<p>                    showMessage(&#039;\u65e0\u6cd5\u83b7\u53d6\u6253\u5370\u673a\u5217\u8868: &#039; &#043; error.message, &#039;error&#039;);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            \/\/ \u6587\u4ef6\u9009\u62e9\u5904\u7406<br \/>\n            pdfFileInput.addEventListener(&#039;change&#039;, function () {<br \/>\n                if (this.files.length &gt; 0) {<br \/>\n                    const file &#061; this.files[0];<br \/>\n                    fileNameSpan.textContent &#061; file.name;<\/p>\n<p>                    \/\/ \u9a8c\u8bc1\u6587\u4ef6\u7c7b\u578b\u548c\u5927\u5c0f<br \/>\n                    if (!file.name.toLowerCase().endsWith(&#039;.pdf&#039;)) {<br \/>\n                        showMessage(&#039;\u8bf7\u53ea\u9009\u62e9PDF\u6587\u4ef6&#039;, &#039;error&#039;);<br \/>\n                        this.value &#061; &#039;&#039;;<br \/>\n                        fileNameSpan.textContent &#061; &#039;\u672a\u9009\u62e9\u6587\u4ef6&#039;;<br \/>\n                        return;<br \/>\n                    }<\/p>\n<p>                    if (file.size &gt; 10 * 1024 * 1024) {<br \/>\n                        showMessage(&#039;\u6587\u4ef6\u5927\u5c0f\u4e0d\u80fd\u8d85\u8fc710MB&#039;, &#039;error&#039;);<br \/>\n                        this.value &#061; &#039;&#039;;<br \/>\n                        fileNameSpan.textContent &#061; &#039;\u672a\u9009\u62e9\u6587\u4ef6&#039;;<br \/>\n                        return;<br \/>\n                    }<\/p>\n<p>                    showMessage(&#096;\u5df2\u9009\u62e9: ${file.name} (${formatFileSize(file.size)})&#096;, &#039;info&#039;);<br \/>\n                } else {<br \/>\n                    fileNameSpan.textContent &#061; &#039;\u672a\u9009\u62e9\u6587\u4ef6&#039;;<br \/>\n                }<br \/>\n            });<\/p>\n<p>            \/\/ \u6253\u5370\u673a\u9009\u62e9\u53d8\u5316\u65f6\u4fdd\u5b58\u8bbe\u7f6e<br \/>\n            printerSelect.addEventListener(&#039;change&#039;, function () {<br \/>\n                if (this.value) {<br \/>\n                    saveSettings();<br \/>\n                }<br \/>\n            });<\/p>\n<p>            \/\/ \u4efd\u6570\u53d8\u5316\u65f6\u4fdd\u5b58\u8bbe\u7f6e<br \/>\n            copiesInput.addEventListener(&#039;change&#039;, function () {<br \/>\n                if (this.value) {<br \/>\n                    saveSettings();<br \/>\n                }<br \/>\n            });<\/p>\n<p>            \/\/ \u6587\u4ef6\u5927\u5c0f\u683c\u5f0f\u5316<br \/>\n            function formatFileSize(bytes) {<br \/>\n                if (bytes &#061;&#061;&#061; 0) return &#039;0 Bytes&#039;;<br \/>\n                const k &#061; 1024;<br \/>\n                const sizes &#061; [&#039;Bytes&#039;, &#039;KB&#039;, &#039;MB&#039;, &#039;GB&#039;];<br \/>\n                const i &#061; Math.floor(Math.log(bytes) \/ Math.log(k));<br \/>\n                return parseFloat((bytes \/ Math.pow(k, i)).toFixed(2)) &#043; &#039; &#039; &#043; sizes[i];<br \/>\n            }<\/p>\n<p>            \/\/ \u4e0a\u4f20\u6587\u4ef6<br \/>\n            async function uploadFile() {<br \/>\n                const file &#061; pdfFileInput.files[0];<br \/>\n                if (!file) {<br \/>\n                    showMessage(&#039;\u8bf7\u5148\u9009\u62e9PDF\u6587\u4ef6&#039;, &#039;error&#039;);<br \/>\n                    return;<br \/>\n                }<\/p>\n<p>                const printer &#061; printerSelect.value;<br \/>\n                if (!printer) {<br \/>\n                    showMessage(&#039;\u8bf7\u9009\u62e9\u6253\u5370\u673a&#039;, &#039;error&#039;);<br \/>\n                    return;<br \/>\n                }<\/p>\n<p>                const copies &#061; parseInt(copiesInput.value) || 1;<\/p>\n<p>                \/\/ \u51c6\u5907\u8868\u5355\u6570\u636e<br \/>\n                const formData &#061; new FormData();<br \/>\n                formData.append(&#039;file&#039;, file);<br \/>\n                formData.append(&#039;printer&#039;, printer);<br \/>\n                formData.append(&#039;copies&#039;, copies);<\/p>\n<p>                \/\/ \u663e\u793a\u8fdb\u5ea6\u6761<br \/>\n                uploadProgress.style.display &#061; &#039;block&#039;;<br \/>\n                progressFill.style.width &#061; &#039;0%&#039;;<br \/>\n                uploadBtn.disabled &#061; true;<br \/>\n                uploadBtn.innerHTML &#061; &#039;&lt;i class&#061;&#034;fas fa-spinner fa-spin&#034;&gt;&lt;\/i&gt; \u4e0a\u4f20\u4e2d&#8230;&#039;;<br \/>\n                hideMessage();<\/p>\n<p>                \/\/ \u4fdd\u5b58\u8bbe\u7f6e&#xff08;\u5728\u4e0a\u4f20\u524d&#xff09;<br \/>\n                saveSettings();<\/p>\n<p>                try {<br \/>\n                    \/\/ \u4f7f\u7528XMLHttpRequest\u4ee5\u83b7\u53d6\u4e0a\u4f20\u8fdb\u5ea6<br \/>\n                    const xhr &#061; new XMLHttpRequest();<\/p>\n<p>                    \/\/ \u8fdb\u5ea6\u4e8b\u4ef6<br \/>\n                    xhr.upload.addEventListener(&#039;progress&#039;, (e) &#061;&gt; {<br \/>\n                        if (e.lengthComputable) {<br \/>\n                            const percent &#061; (e.loaded \/ e.total) * 100;<br \/>\n                            progressFill.style.width &#061; &#096;${percent}%&#096;;<br \/>\n                        }<br \/>\n                    });<\/p>\n<p>                    \/\/ \u5b8c\u6210\u4e8b\u4ef6<br \/>\n                    xhr.addEventListener(&#039;load&#039;, () &#061;&gt; {<br \/>\n                        if (xhr.status &#061;&#061;&#061; 200) {<br \/>\n                            const response &#061; JSON.parse(xhr.responseText);<br \/>\n                            showMessage(&#096;\u6587\u4ef6\u5df2\u63d0\u4ea4\u6253\u5370! \u4efb\u52a1ID: ${response.task_id}&#096;, &#039;success&#039;);<\/p>\n<p>                            \/\/ \u6dfb\u52a0\u4efb\u52a1\u5230\u72b6\u6001\u5217\u8868<br \/>\n                            addTaskToList({<br \/>\n                                id: response.task_id,<br \/>\n                                filename: file.name,<br \/>\n                                printer: printer,<br \/>\n                                copies: copies,<br \/>\n                                status: response.status<br \/>\n                            });<\/p>\n<p>                            \/\/ \u6e05\u7a7a\u6587\u4ef6\u8f93\u5165<br \/>\n                            pdfFileInput.value &#061; &#039;&#039;;<br \/>\n                            fileNameSpan.textContent &#061; &#039;\u672a\u9009\u62e9\u6587\u4ef6&#039;;<br \/>\n                            \/\/ \u6ce8\u610f&#xff1a;\u4e0d\u91cd\u7f6e\u6253\u5370\u673a\u548c\u4efd\u6570&#xff0c;\u4fdd\u6301\u7528\u6237\u7684\u8bbe\u7f6e<br \/>\n                        } else {<br \/>\n                            const error &#061; JSON.parse(xhr.responseText);<br \/>\n                            showMessage(&#096;\u4e0a\u4f20\u5931\u8d25: ${error.detail || &#039;\u670d\u52a1\u5668\u9519\u8bef&#039;}&#096;, &#039;error&#039;);<br \/>\n                        }<br \/>\n                        resetUploadButton();<br \/>\n                    });<\/p>\n<p>                    \/\/ \u9519\u8bef\u4e8b\u4ef6<br \/>\n                    xhr.addEventListener(&#039;error&#039;, () &#061;&gt; {<br \/>\n                        showMessage(&#039;\u7f51\u7edc\u9519\u8bef&#xff0c;\u8bf7\u68c0\u67e5\u8fde\u63a5&#039;, &#039;error&#039;);<br \/>\n                        resetUploadButton();<br \/>\n                    });<\/p>\n<p>                    \/\/ \u53d1\u9001\u8bf7\u6c42<br \/>\n                    xhr.open(&#039;POST&#039;, &#039;\/api\/upload&#039;);<br \/>\n                    xhr.send(formData);<\/p>\n<p>                } catch (error) {<br \/>\n                    showMessage(&#039;\u4e0a\u4f20\u51fa\u9519: &#039; &#043; error.message, &#039;error&#039;);<br \/>\n                    resetUploadButton();<br \/>\n                }<br \/>\n            }<\/p>\n<p>            function resetUploadButton() {<br \/>\n                uploadBtn.disabled &#061; false;<br \/>\n                uploadBtn.innerHTML &#061; &#039;&lt;i class&#061;&#034;fas fa-cloud-upload-alt&#034;&gt;&lt;\/i&gt; \u4e0a\u4f20\u5e76\u6253\u5370&#039;;<br \/>\n                uploadProgress.style.display &#061; &#039;none&#039;;<br \/>\n                progressFill.style.width &#061; &#039;0%&#039;;<br \/>\n            }<\/p>\n<p>            function showMessage(text, type) {<br \/>\n                messageDiv.textContent &#061; text;<br \/>\n                messageDiv.className &#061; &#096;message ${type}&#096;;<br \/>\n            }<\/p>\n<p>            function hideMessage() {<br \/>\n                messageDiv.style.display &#061; &#039;none&#039;;<br \/>\n            }<\/p>\n<p>            function addTaskToList(task) {<br \/>\n                \/\/ \u79fb\u9664\u7a7a\u72b6\u6001\u63d0\u793a<br \/>\n                if (taskStatusDiv.querySelector(&#039;.empty-state&#039;)) {<br \/>\n                    taskStatusDiv.innerHTML &#061; &#039;&#039;;<br \/>\n                }<\/p>\n<p>                \/\/ \u6dfb\u52a0\u5230\u4efb\u52a1\u6570\u7ec4<br \/>\n                tasks.push(task);<\/p>\n<p>                \/\/ \u521b\u5efa\u4efb\u52a1\u5143\u7d20<br \/>\n                const taskElement &#061; document.createElement(&#039;div&#039;);<br \/>\n                taskElement.className &#061; &#039;task-item&#039;;<br \/>\n                taskElement.id &#061; &#096;task-${task.id}&#096;;<\/p>\n<p>                const statusClass &#061; &#096;status-${task.status}&#096;;<br \/>\n                let statusText &#061; &#039;&#039;;<br \/>\n                let statusIcon &#061; &#039;&#039;;<\/p>\n<p>                switch (task.status) {<br \/>\n                    case &#039;queued&#039;:<br \/>\n                        statusText &#061; &#039;\u6392\u961f\u4e2d&#039;;<br \/>\n                        statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-clock&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                        break;<br \/>\n                    case &#039;processing&#039;:<br \/>\n                        statusText &#061; &#039;\u5904\u7406\u4e2d&#039;;<br \/>\n                        statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-cog fa-spin&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                        break;<br \/>\n                    case &#039;completed&#039;:<br \/>\n                        statusText &#061; &#039;\u5df2\u5b8c\u6210&#039;;<br \/>\n                        statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-check-circle&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                        break;<br \/>\n                    case &#039;failed&#039;:<br \/>\n                        statusText &#061; &#039;\u5931\u8d25&#039;;<br \/>\n                        statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-exclamation-circle&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                        break;<br \/>\n                    default:<br \/>\n                        statusText &#061; task.status;<br \/>\n                        statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-question-circle&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                }<\/p>\n<p>                taskElement.innerHTML &#061; &#096;<br \/>\n            &lt;div class&#061;&#034;task-info&#034;&gt;<br \/>\n                &lt;div class&#061;&#034;task-filename&#034;&gt;&lt;i class&#061;&#034;fas fa-file-pdf&#034;&gt;&lt;\/i&gt; ${task.filename}&lt;\/div&gt;<br \/>\n                &lt;div class&#061;&#034;task-details&#034;&gt;<br \/>\n                    &lt;span&gt;&lt;i class&#061;&#034;fas fa-print&#034;&gt;&lt;\/i&gt; ${task.printer}&lt;\/span&gt;<br \/>\n                    &lt;span&gt;&lt;i class&#061;&#034;fas fa-copy&#034;&gt;&lt;\/i&gt; ${task.copies}\u4efd&lt;\/span&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n            &lt;div class&#061;&#034;task-status ${statusClass}&#034;&gt;<br \/>\n                ${statusIcon} ${statusText}<br \/>\n            &lt;\/div&gt;<br \/>\n        &#096;;<\/p>\n<p>                \/\/ \u6dfb\u52a0\u5230\u9876\u90e8<br \/>\n                taskStatusDiv.insertBefore(taskElement, taskStatusDiv.firstChild);<\/p>\n<p>                \/\/ \u5982\u679c\u4efb\u52a1\u8fd8\u5728\u8fdb\u884c\u4e2d&#xff0c;\u5f00\u59cb\u72b6\u6001\u68c0\u67e5<br \/>\n                if (task.status &#061;&#061;&#061; &#039;queued&#039; || task.status &#061;&#061;&#061; &#039;processing&#039;) {<br \/>\n                    setTimeout(() &#061;&gt; checkTaskStatus(task.id), 3000);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            async function checkTaskStatus(taskId) {<br \/>\n                try {<br \/>\n                    const response &#061; await fetch(&#096;\/api\/tasks\/${taskId}&#096;);<br \/>\n                    const task &#061; await response.json();<\/p>\n<p>                    \/\/ \u66f4\u65b0\u4efb\u52a1\u6570\u7ec4<br \/>\n                    const taskIndex &#061; tasks.findIndex(t &#061;&gt; t.id &#061;&#061;&#061; taskId);<br \/>\n                    if (taskIndex !&#061;&#061; -1) {<br \/>\n                        tasks[taskIndex] &#061; { &#8230;tasks[taskIndex], &#8230;task };<br \/>\n                    }<\/p>\n<p>                    const taskElement &#061; document.getElementById(&#096;task-${taskId}&#096;);<br \/>\n                    if (taskElement) {<br \/>\n                        const statusElement &#061; taskElement.querySelector(&#039;.task-status&#039;);<br \/>\n                        const statusClass &#061; &#096;status-${task.status}&#096;;<br \/>\n                        let statusText &#061; &#039;&#039;;<br \/>\n                        let statusIcon &#061; &#039;&#039;;<\/p>\n<p>                        switch (task.status) {<br \/>\n                            case &#039;queued&#039;:<br \/>\n                                statusText &#061; &#039;\u6392\u961f\u4e2d&#039;;<br \/>\n                                statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-clock&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                                break;<br \/>\n                            case &#039;processing&#039;:<br \/>\n                                statusText &#061; &#039;\u5904\u7406\u4e2d&#039;;<br \/>\n                                statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-cog fa-spin&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                                break;<br \/>\n                            case &#039;completed&#039;:<br \/>\n                                statusText &#061; &#039;\u5df2\u5b8c\u6210&#039;;<br \/>\n                                statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-check-circle&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                                break;<br \/>\n                            case &#039;failed&#039;:<br \/>\n                                statusText &#061; &#039;\u5931\u8d25&#039;;<br \/>\n                                statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-exclamation-circle&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                                break;<br \/>\n                            default:<br \/>\n                                statusText &#061; task.status;<br \/>\n                                statusIcon &#061; &#039;&lt;i class&#061;&#034;fas fa-question-circle&#034;&gt;&lt;\/i&gt;&#039;;<br \/>\n                        }<\/p>\n<p>                        statusElement.className &#061; &#096;task-status ${statusClass}&#096;;<br \/>\n                        statusElement.innerHTML &#061; &#096;${statusIcon} ${statusText}&#096;;<\/p>\n<p>                        \/\/ \u5982\u679c\u4efb\u52a1\u8fd8\u5728\u8fdb\u884c\u4e2d&#xff0c;\u7ee7\u7eed\u68c0\u67e5<br \/>\n                        if (task.status &#061;&#061;&#061; &#039;queued&#039; || task.status &#061;&#061;&#061; &#039;processing&#039;) {<br \/>\n                            setTimeout(() &#061;&gt; checkTaskStatus(taskId), 3000);<br \/>\n                        }<br \/>\n                    }<br \/>\n                } catch (error) {<br \/>\n                    console.error(&#039;\u83b7\u53d6\u4efb\u52a1\u72b6\u6001\u5931\u8d25:&#039;, error);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            \/\/ \u6e05\u9664\u5df2\u5b8c\u6210\u4efb\u52a1<br \/>\n            function clearCompletedTasks() {<br \/>\n                const completedTasks &#061; tasks.filter(t &#061;&gt; t.status &#061;&#061;&#061; &#039;completed&#039; || t.status &#061;&#061;&#061; &#039;failed&#039;);<\/p>\n<p>                completedTasks.forEach(task &#061;&gt; {<br \/>\n                    const taskElement &#061; document.getElementById(&#096;task-${task.id}&#096;);<br \/>\n                    if (taskElement) {<br \/>\n                        taskElement.style.opacity &#061; &#039;0&#039;;<br \/>\n                        taskElement.style.transform &#061; &#039;translateX(-20px)&#039;;<br \/>\n                        setTimeout(() &#061;&gt; {<br \/>\n                            taskElement.remove();<br \/>\n                        }, 300);<br \/>\n                    }<br \/>\n                });<\/p>\n<p>                \/\/ \u66f4\u65b0\u4efb\u52a1\u6570\u7ec4<br \/>\n                tasks &#061; tasks.filter(t &#061;&gt; t.status !&#061;&#061; &#039;completed&#039; &amp;&amp; t.status !&#061;&#061; &#039;failed&#039;);<\/p>\n<p>                \/\/ \u5982\u679c\u6ca1\u6709\u4efb\u52a1\u4e86&#xff0c;\u663e\u793a\u7a7a\u72b6\u6001<br \/>\n                if (tasks.length &#061;&#061;&#061; 0) {<br \/>\n                    taskStatusDiv.innerHTML &#061; &#096;<br \/>\n                &lt;div class&#061;&#034;empty-state&#034;&gt;<br \/>\n                    &lt;i class&#061;&#034;fas fa-inbox fa-3x&#034;&gt;&lt;\/i&gt;<br \/>\n                    &lt;p&gt;\u6682\u65e0\u4efb\u52a1&lt;\/p&gt;<br \/>\n                    &lt;small&gt;\u4e0a\u4f20\u6587\u4ef6\u540e&#xff0c;\u4efb\u52a1\u5c06\u663e\u793a\u5728\u8fd9\u91cc&lt;\/small&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n            &#096;;<br \/>\n                }<\/p>\n<p>                showMessage(&#096;\u5df2\u6e05\u9664 ${completedTasks.length} \u4e2a\u4efb\u52a1&#096;, &#039;info&#039;);<br \/>\n            }<\/p>\n<p>            \/\/ \u5237\u65b0\u6240\u6709\u4efb\u52a1\u72b6\u6001<br \/>\n            function refreshAllTasks() {<br \/>\n                tasks.forEach(task &#061;&gt; {<br \/>\n                    if (task.status &#061;&#061;&#061; &#039;queued&#039; || task.status &#061;&#061;&#061; &#039;processing&#039;) {<br \/>\n                        checkTaskStatus(task.id);<br \/>\n                    }<br \/>\n                });<br \/>\n                showMessage(&#039;\u4efb\u52a1\u72b6\u6001\u5df2\u5237\u65b0&#039;, &#039;info&#039;);<br \/>\n            }<\/p>\n<p>            \/\/ \u663e\u793a\u5f53\u524d\u4fdd\u5b58\u7684\u8bbe\u7f6e<br \/>\n            function showSavedSettingsInfo() {<br \/>\n                const settings &#061; loadSettings();<br \/>\n                if (settings) {<br \/>\n                    showMessage(&#096;\u5df2\u4fdd\u5b58\u8bbe\u7f6e &#8211; \u6253\u5370\u673a: ${settings.printer || &#039;\u672a\u8bbe\u7f6e&#039;}, \u4efd\u6570: ${settings.copies || &#039;\u672a\u8bbe\u7f6e&#039;}&#096;, &#039;info&#039;);<br \/>\n                } else {<br \/>\n                    showMessage(&#039;\u5f53\u524d\u6ca1\u6709\u4fdd\u5b58\u7684\u8bbe\u7f6e&#039;, &#039;info&#039;);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            \/\/ \u4e8b\u4ef6\u76d1\u542c<br \/>\n            uploadBtn.addEventListener(&#039;click&#039;, uploadFile);<br \/>\n            refreshPrintersBtn.addEventListener(&#039;click&#039;, () &#061;&gt; fetchPrinters(true));<br \/>\n            clearCompletedBtn.addEventListener(&#039;click&#039;, clearCompletedTasks);<br \/>\n            refreshTasksBtn.addEventListener(&#039;click&#039;, refreshAllTasks);<br \/>\n            showSettingsBtn.addEventListener(&#039;click&#039;, showSavedSettingsInfo);<br \/>\n            clearSettingsBtn.addEventListener(&#039;click&#039;, clearSettings);<\/p>\n<p>            \/\/ \u9875\u9762\u52a0\u8f7d\u65f6\u83b7\u53d6\u6253\u5370\u673a\u5217\u8868\u548c\u7cfb\u7edf\u4fe1\u606f&#xff0c;\u5e76\u5e94\u7528\u4fdd\u5b58\u7684\u8bbe\u7f6e<br \/>\n            fetchPrinters(false).then(() &#061;&gt; {<br \/>\n                \/\/ \u5728\u6253\u5370\u673a\u5217\u8868\u52a0\u8f7d\u540e&#xff0c;\u5e94\u7528\u4fdd\u5b58\u7684\u8bbe\u7f6e<br \/>\n                const savedSettings &#061; loadSettings();<br \/>\n                if (savedSettings) {<br \/>\n                    applySavedSettings(savedSettings);<br \/>\n                }<br \/>\n            });<br \/>\n            fetchSystemInfo();<\/p>\n<p>            \/\/ \u6bcf30\u79d2\u5237\u65b0\u4e00\u6b21\u6253\u5370\u673a\u5217\u8868<br \/>\n            setInterval(() &#061;&gt; fetchPrinters(false), 30000);<br \/>\n        });<\/p>\n<p>    &lt;\/script&gt;<br \/>\n&lt;\/body&gt;<\/p>\n<p>&lt;\/html&gt; <\/p>\n<h2>FastApi\u4ee3\u7801:<\/h2>\n<p>import os<br \/>\nimport uuid<br \/>\nimport subprocess<br \/>\nimport platform<br \/>\nimport logging<br \/>\nimport time<br \/>\nfrom fastapi import FastAPI, UploadFile, File, Form, HTTPException<br \/>\nfrom fastapi.responses import HTMLResponse, JSONResponse<br \/>\nfrom fastapi.staticfiles import StaticFiles<br \/>\nfrom pathlib import Path<br \/>\nfrom typing import Optional<br \/>\nimport uvicorn<br \/>\nimport sys<br \/>\nfrom concurrent.futures import ThreadPoolExecutor<br \/>\nimport threading<\/p>\n<p># \u914d\u7f6e\u65e5\u5fd7<br \/>\nlogging.basicConfig(level&#061;logging.INFO, format&#061;&#039;%(asctime)s &#8211; %(levelname)s &#8211; %(message)s&#039;)<br \/>\nlogger &#061; logging.getLogger(__name__)<\/p>\n<p>app &#061; FastAPI(title&#061;&#034;PDF\u6253\u5370\u670d\u52a1\u5668&#034;, version&#061;&#034;1.3.0&#034;)<\/p>\n<p># \u521b\u5efa\u5fc5\u8981\u7684\u76ee\u5f55<br \/>\nUPLOAD_DIR &#061; Path(&#034;uploads&#034;)<br \/>\nUPLOAD_DIR.mkdir(exist_ok&#061;True)<\/p>\n<p># \u6302\u8f7d\u9759\u6001\u6587\u4ef6\u76ee\u5f55<br \/>\napp.mount(&#034;\/static&#034;, StaticFiles(directory&#061;&#034;static&#034;), name&#061;&#034;static&#034;)<\/p>\n<p># \u5168\u5c40\u72b6\u6001\u5b58\u50a8\u4efb\u52a1\u4fe1\u606f<br \/>\ntasks &#061; {}<br \/>\ntask_lock &#061; threading.Lock()<\/p>\n<p># \u7ebf\u7a0b\u6c60\u7528\u4e8e\u5f02\u6b65\u6267\u884c\u6253\u5370\u4efb\u52a1<br \/>\nexecutor &#061; ThreadPoolExecutor(max_workers&#061;3)<\/p>\n<p>def get_system_printers():<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u7cfb\u7edf\u53ef\u7528\u7684\u6253\u5370\u673a\u5217\u8868&#034;&#034;&#034;<br \/>\n    system &#061; platform.system()<br \/>\n    printers &#061; []<\/p>\n<p>    try:<br \/>\n        if system &#061;&#061; &#034;Windows&#034;:<br \/>\n            import win32print<br \/>\n            # \u83b7\u53d6\u672c\u5730\u6253\u5370\u673a<br \/>\n            printers_info &#061; win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL)<br \/>\n            printers &#061; [p[2] for p in printers_info]<\/p>\n<p>            # \u83b7\u53d6\u7f51\u7edc\u6253\u5370\u673a&#xff08;\u5982\u679c\u9700\u8981&#xff09;<br \/>\n            try:<br \/>\n                network_printers &#061; win32print.EnumPrinters(win32print.PRINTER_ENUM_CONNECTIONS)<br \/>\n                network_printer_names &#061; [p[2] for p in network_printers]<br \/>\n                printers.extend(network_printer_names)<br \/>\n            except:<br \/>\n                pass<\/p>\n<p>        elif system &#061;&#061; &#034;Linux&#034;:<br \/>\n            # \u4f7f\u7528lpstat\u547d\u4ee4\u83b7\u53d6\u6253\u5370\u673a<br \/>\n            result &#061; subprocess.run([&#034;lpstat&#034;, &#034;-p&#034;], capture_output&#061;True, text&#061;True, timeout&#061;5)<br \/>\n            for line in result.stdout.splitlines():<br \/>\n                if line.startswith(&#034;printer&#034;):<br \/>\n                    printer_name &#061; line.split()[1]<br \/>\n                    # \u79fb\u9664&#034;\u6253\u5370\u673a\u72b6\u6001&#034;\u7b49\u540e\u7f00<br \/>\n                    if &#034;disabled&#034; in line:<br \/>\n                        printer_name &#061; printer_name &#043; &#034; (\u5df2\u7981\u7528)&#034;<br \/>\n                    printers.append(printer_name)<\/p>\n<p>        elif system &#061;&#061; &#034;Darwin&#034;:  # macOS<br \/>\n            result &#061; subprocess.run([&#034;lpstat&#034;, &#034;-p&#034;], capture_output&#061;True, text&#061;True, timeout&#061;5)<br \/>\n            for line in result.stdout.splitlines():<br \/>\n                if &#034;printer&#034; in line:<br \/>\n                    printer_name &#061; line.split()[1]<br \/>\n                    printers.append(printer_name)<\/p>\n<p>    except Exception as e:<br \/>\n        logger.error(f&#034;\u83b7\u53d6\u6253\u5370\u673a\u5217\u8868\u5931\u8d25: {e}&#034;)<br \/>\n        # \u8fd4\u56de\u9ed8\u8ba4\u9009\u9879<br \/>\n        return [&#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;]<\/p>\n<p>    # \u53bb\u91cd\u5e76\u6392\u5e8f<br \/>\n    printers &#061; sorted(list(set(printers)))<\/p>\n<p>    # \u5982\u679c\u6ca1\u6709\u627e\u5230\u6253\u5370\u673a&#xff0c;\u6dfb\u52a0\u9ed8\u8ba4\u9009\u9879<br \/>\n    if not printers:<br \/>\n        printers &#061; [&#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;]<\/p>\n<p>    return printers<\/p>\n<p>def print_pdf(file_path: str, printer_name: str &#061; None, copies: int &#061; 1):<br \/>\n    &#034;&#034;&#034;\u6253\u5370PDF\u6587\u4ef6 &#8211; \u6839\u636e\u5b98\u65b9\u6587\u6863\u4fee\u590d\u6253\u5370\u4efd\u6570\u8bbe\u7f6e&#034;&#034;&#034;<br \/>\n    system &#061; platform.system()<br \/>\n    file_path &#061; str(file_path)<br \/>\n    logger.info(f&#034;\u5f00\u59cb\u6253\u5370: \u6587\u4ef6&#061;{file_path}, \u6253\u5370\u673a&#061;{printer_name}, \u4efd\u6570&#061;{copies}&#034;)<\/p>\n<p>    try:<br \/>\n        if system &#061;&#061; &#034;Windows&#034;:<br \/>\n            # \u68c0\u67e5SumatraPDF\u662f\u5426\u5b58\u5728<br \/>\n            sumatra_paths &#061; [<br \/>\n                &#034;SumatraPDF.exe&#034;,  # \u5982\u679c\u5728PATH\u4e2d<br \/>\n                r&#034;C:\\\\Program Files\\\\SumatraPDF\\\\SumatraPDF.exe&#034;,<br \/>\n                r&#034;C:\\\\Program Files (x86)\\\\SumatraPDF\\\\SumatraPDF.exe&#034;,<br \/>\n                r&#034;C:\\\\Users\\\\Public\\\\Downloads\\\\SumatraPDF.exe&#034;<br \/>\n            ]<\/p>\n<p>            sumatra_exe &#061; None<br \/>\n            for path in sumatra_paths:<br \/>\n                if os.path.exists(path):<br \/>\n                    sumatra_exe &#061; path<br \/>\n                    break<\/p>\n<p>            if not sumatra_exe:<br \/>\n                raise Exception(&#034;SumatraPDF\u672a\u627e\u5230&#xff0c;\u8bf7\u5148\u5b89\u88c5SumatraPDF&#034;)<\/p>\n<p>            # \u6784\u5efa\u6253\u5370\u547d\u4ee4<br \/>\n            cmd &#061; [sumatra_exe]<\/p>\n<p>            # \u5904\u7406\u6253\u5370\u673a\u540d\u79f0<br \/>\n            if printer_name and printer_name !&#061; &#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;:<br \/>\n                # \u6e05\u7406\u6253\u5370\u673a\u540d\u79f0&#xff0c;\u79fb\u9664\u53ef\u80fd\u7684\u72b6\u6001\u540e\u7f00<br \/>\n                clean_printer_name &#061; printer_name.split(&#034; (&#034;)[0]<br \/>\n                cmd.extend([&#034;-print-to&#034;, clean_printer_name])<br \/>\n                logger.info(f&#034;\u4f7f\u7528\u6307\u5b9a\u6253\u5370\u673a: {clean_printer_name}&#034;)<br \/>\n            else:<br \/>\n                # \u4f7f\u7528\u9ed8\u8ba4\u6253\u5370\u673a<br \/>\n                cmd.extend([&#034;-print-to-default&#034;])<br \/>\n                logger.info(&#034;\u4f7f\u7528\u9ed8\u8ba4\u6253\u5370\u673a&#034;)<\/p>\n<p>            # \u6839\u636e\u5b98\u65b9\u6587\u6863\u4fee\u590d\u6253\u5370\u4efd\u6570\u8bbe\u7f6e<br \/>\n            # -print-settings &#034;3x&#034; \u8868\u793a\u6253\u53703\u4efd<br \/>\n            if copies &gt; 1:<br \/>\n                cmd.extend([&#034;-print-settings&#034;, f&#034;{copies}x&#034;])<br \/>\n                logger.info(f&#034;\u8bbe\u7f6e\u6253\u5370\u4efd\u6570: {copies}x&#034;)<\/p>\n<p>            # \u9759\u9ed8\u6a21\u5f0f&#xff0c;\u4e0d\u663e\u793a\u9519\u8bef\u4fe1\u606f<br \/>\n            cmd.append(&#034;-silent&#034;)<\/p>\n<p>            # \u6dfb\u52a0\u6587\u4ef6\u8def\u5f84<br \/>\n            cmd.append(file_path)<\/p>\n<p>            logger.info(f&#034;\u6267\u884c\u6253\u5370\u547d\u4ee4: {&#039; &#039;.join(cmd)}&#034;)<\/p>\n<p>            # \u6267\u884c\u6253\u5370<br \/>\n            result &#061; subprocess.run(cmd, capture_output&#061;True, text&#061;True, timeout&#061;60)<\/p>\n<p>            if result.returncode &#061;&#061; 0:<br \/>\n                logger.info(f&#034;\u6253\u5370\u547d\u4ee4\u6267\u884c\u6210\u529f&#xff0c;\u4efd\u6570: {copies}&#034;)<br \/>\n                return True<br \/>\n            else:<br \/>\n                error_msg &#061; result.stderr or result.stdout<br \/>\n                logger.error(f&#034;\u6253\u5370\u5931\u8d25: {error_msg}&#034;)<\/p>\n<p>                # \u5c1d\u8bd5\u5907\u7528\u65b9\u6848&#xff1a;\u4f7f\u7528print-dialog\u663e\u793a\u6253\u5370\u5bf9\u8bdd\u6846<br \/>\n                logger.info(f&#034;\u5c1d\u8bd5\u5907\u7528\u65b9\u6848&#xff1a;\u663e\u793a\u6253\u5370\u5bf9\u8bdd\u6846&#034;)<br \/>\n                dialog_cmd &#061; [sumatra_exe, &#034;-print-dialog&#034;, &#034;-exit-when-done&#034;, file_path]<\/p>\n<p>                try:<br \/>\n                    dialog_result &#061; subprocess.run(dialog_cmd, capture_output&#061;True, text&#061;True, timeout&#061;30)<br \/>\n                    if dialog_result.returncode &#061;&#061; 0:<br \/>\n                        logger.info(f&#034;\u6253\u5370\u5bf9\u8bdd\u6846\u65b9\u6848\u6210\u529f&#034;)<br \/>\n                        return True<br \/>\n                    else:<br \/>\n                        raise Exception(f&#034;\u6253\u5370\u5bf9\u8bdd\u6846\u4e5f\u5931\u8d25: {dialog_result.stderr}&#034;)<br \/>\n                except Exception as dialog_error:<br \/>\n                    logger.error(f&#034;\u6253\u5370\u5bf9\u8bdd\u6846\u65b9\u6848\u5931\u8d25: {dialog_error}&#034;)<\/p>\n<p>                    # \u6700\u7ec8\u65b9\u6848&#xff1a;\u591a\u6b21\u8c03\u7528\u6253\u5370\u547d\u4ee4<br \/>\n                    if copies &gt; 1:<br \/>\n                        logger.info(f&#034;\u5c1d\u8bd5\u6700\u7ec8\u65b9\u6848&#xff1a;\u5206{copies}\u6b21\u6253\u5370&#034;)<br \/>\n                        success_count &#061; 0<br \/>\n                        for i in range(copies):<br \/>\n                            try:<br \/>\n                                logger.info(f&#034;\u6253\u5370\u7b2c {i&#043;1}\/{copies} \u4efd&#034;)<br \/>\n                                single_copy_cmd &#061; cmd.copy()<br \/>\n                                # \u79fb\u9664\u4efd\u6570\u8bbe\u7f6e&#xff0c;\u53ea\u6253\u53701\u4efd<br \/>\n                                if f&#034;{copies}x&#034; in single_copy_cmd:<br \/>\n                                    idx &#061; single_copy_cmd.index(f&#034;{copies}x&#034;)<br \/>\n                                    single_copy_cmd.pop(idx-1)  # \u79fb\u9664-print-settings<br \/>\n                                    single_copy_cmd.pop(idx-1)  # \u79fb\u9664\u4efd\u6570\u503c<\/p>\n<p>                                retry_result &#061; subprocess.run(single_copy_cmd, capture_output&#061;True, text&#061;True, timeout&#061;30)<br \/>\n                                if retry_result.returncode &#061;&#061; 0:<br \/>\n                                    success_count &#043;&#061; 1<br \/>\n                                    time.sleep(2)  # \u6bcf\u6b21\u6253\u5370\u95f4\u96942\u79d2&#xff0c;\u907f\u514d\u6253\u5370\u673a\u8fc7\u8f7d<br \/>\n                                else:<br \/>\n                                    logger.warning(f&#034;\u7b2c {i&#043;1} \u4efd\u6253\u5370\u5931\u8d25&#034;)<br \/>\n                            except Exception as e:<br \/>\n                                logger.warning(f&#034;\u7b2c {i&#043;1} \u4efd\u6253\u5370\u5f02\u5e38: {e}&#034;)<\/p>\n<p>                        if success_count &gt; 0:<br \/>\n                            logger.info(f&#034;\u6700\u7ec8\u65b9\u6848\u6210\u529f\u6253\u5370 {success_count}\/{copies} \u4efd&#034;)<br \/>\n                            return True<br \/>\n                        else:<br \/>\n                            raise Exception(f&#034;\u6240\u6709\u6253\u5370\u5c1d\u8bd5\u90fd\u5931\u8d25: {error_msg}&#034;)<br \/>\n                    else:<br \/>\n                        raise Exception(f&#034;\u6253\u5370\u5931\u8d25: {error_msg}&#034;)<\/p>\n<p>        elif system &#061;&#061; &#034;Linux&#034;:<br \/>\n            # Linux\u7cfb\u7edf\u4f7f\u7528lp\u547d\u4ee4<br \/>\n            cmd &#061; [&#034;lp&#034;, &#034;-n&#034;, str(copies)]<\/p>\n<p>            if printer_name and printer_name !&#061; &#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;:<br \/>\n                # \u6e05\u7406\u6253\u5370\u673a\u540d\u79f0<br \/>\n                clean_printer_name &#061; printer_name.split(&#034; (&#034;)[0]<br \/>\n                cmd.extend([&#034;-d&#034;, clean_printer_name])<br \/>\n                logger.info(f&#034;\u4f7f\u7528\u6307\u5b9a\u6253\u5370\u673a: {clean_printer_name}&#034;)<br \/>\n            else:<br \/>\n                logger.info(&#034;\u4f7f\u7528\u9ed8\u8ba4\u6253\u5370\u673a&#034;)<\/p>\n<p>            cmd.append(file_path)<\/p>\n<p>            logger.info(f&#034;\u6267\u884c\u547d\u4ee4: {&#039; &#039;.join(cmd)}&#034;)<\/p>\n<p>            result &#061; subprocess.run(cmd, capture_output&#061;True, text&#061;True, timeout&#061;60)<\/p>\n<p>            if result.returncode &#061;&#061; 0:<br \/>\n                logger.info(f&#034;\u6253\u5370\u4efb\u52a1\u5df2\u63d0\u4ea4&#xff0c;\u4efd\u6570: {copies}&#034;)<br \/>\n                return True<br \/>\n            else:<br \/>\n                error_msg &#061; result.stderr or result.stdout<br \/>\n                logger.error(f&#034;\u6253\u5370\u5931\u8d25: {error_msg}&#034;)<br \/>\n                raise Exception(f&#034;\u6253\u5370\u5931\u8d25: {error_msg}&#034;)<\/p>\n<p>        elif system &#061;&#061; &#034;Darwin&#034;:  # macOS<br \/>\n            # macOS\u4e5f\u4f7f\u7528lp\u547d\u4ee4<br \/>\n            cmd &#061; [&#034;lp&#034;, &#034;-n&#034;, str(copies)]<\/p>\n<p>            if printer_name and printer_name !&#061; &#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;:<br \/>\n                # \u6e05\u7406\u6253\u5370\u673a\u540d\u79f0<br \/>\n                clean_printer_name &#061; printer_name.split(&#034; (&#034;)[0]<br \/>\n                cmd.extend([&#034;-d&#034;, clean_printer_name])<br \/>\n                logger.info(f&#034;\u4f7f\u7528\u6307\u5b9a\u6253\u5370\u673a: {clean_printer_name}&#034;)<br \/>\n            else:<br \/>\n                logger.info(&#034;\u4f7f\u7528\u9ed8\u8ba4\u6253\u5370\u673a&#034;)<\/p>\n<p>            cmd.append(file_path)<\/p>\n<p>            logger.info(f&#034;\u6267\u884c\u547d\u4ee4: {&#039; &#039;.join(cmd)}&#034;)<\/p>\n<p>            result &#061; subprocess.run(cmd, capture_output&#061;True, text&#061;True, timeout&#061;60)<\/p>\n<p>            if result.returncode &#061;&#061; 0:<br \/>\n                logger.info(f&#034;\u6253\u5370\u4efb\u52a1\u5df2\u63d0\u4ea4&#xff0c;\u4efd\u6570: {copies}&#034;)<br \/>\n                return True<br \/>\n            else:<br \/>\n                error_msg &#061; result.stderr or result.stdout<br \/>\n                logger.error(f&#034;\u6253\u5370\u5931\u8d25: {error_msg}&#034;)<br \/>\n                raise Exception(f&#034;\u6253\u5370\u5931\u8d25: {error_msg}&#034;)<\/p>\n<p>    except subprocess.TimeoutExpired:<br \/>\n        logger.error(&#034;\u6253\u5370\u547d\u4ee4\u6267\u884c\u8d85\u65f6&#034;)<br \/>\n        raise Exception(&#034;\u6253\u5370\u547d\u4ee4\u6267\u884c\u8d85\u65f6&#xff0c;\u8bf7\u68c0\u67e5\u6253\u5370\u673a\u72b6\u6001&#034;)<br \/>\n    except Exception as e:<br \/>\n        logger.error(f&#034;\u6253\u5370\u5f02\u5e38: {e}&#034;)<br \/>\n        raise<\/p>\n<p>def process_print_task(task_id: str, file_path: Path, printer: str, copies: int, original_filename: str):<br \/>\n    &#034;&#034;&#034;\u5f02\u6b65\u5904\u7406\u6253\u5370\u4efb\u52a1&#034;&#034;&#034;<br \/>\n    try:<br \/>\n        logger.info(f&#034;\u5f00\u59cb\u5904\u7406\u6253\u5370\u4efb\u52a1 {task_id}, \u4efd\u6570: {copies}&#034;)<\/p>\n<p>        # \u66f4\u65b0\u4efb\u52a1\u72b6\u6001\u4e3a\u5904\u7406\u4e2d<br \/>\n        with task_lock:<br \/>\n            tasks[task_id][&#034;status&#034;] &#061; &#034;processing&#034;<br \/>\n            tasks[task_id][&#034;started_at&#034;] &#061; time.strftime(&#034;%H:%M:%S&#034;)<\/p>\n<p>        # \u6253\u5370\u6587\u4ef6<br \/>\n        success &#061; print_pdf(file_path, printer, copies)<\/p>\n<p>        # \u66f4\u65b0\u4efb\u52a1\u72b6\u6001<br \/>\n        with task_lock:<br \/>\n            if success:<br \/>\n                tasks[task_id][&#034;status&#034;] &#061; &#034;completed&#034;<br \/>\n                tasks[task_id][&#034;copies_printed&#034;] &#061; copies<br \/>\n            else:<br \/>\n                tasks[task_id][&#034;status&#034;] &#061; &#034;failed&#034;<br \/>\n                tasks[task_id][&#034;copies_printed&#034;] &#061; 0<\/p>\n<p>            tasks[task_id][&#034;completed_at&#034;] &#061; time.strftime(&#034;%H:%M:%S&#034;)<br \/>\n            tasks[task_id][&#034;success&#034;] &#061; success<\/p>\n<p>        # \u6253\u5370\u5b8c\u6210\u540e\u5220\u9664\u6587\u4ef6<br \/>\n        if os.path.exists(file_path):<br \/>\n            try:<br \/>\n                os.remove(file_path)<br \/>\n                logger.info(f&#034;\u4e34\u65f6\u6587\u4ef6\u5df2\u5220\u9664: {file_path}&#034;)<br \/>\n            except Exception as e:<br \/>\n                logger.warning(f&#034;\u5220\u9664\u4e34\u65f6\u6587\u4ef6\u5931\u8d25: {e}&#034;)<\/p>\n<p>        logger.info(f&#034;\u6253\u5370\u4efb\u52a1 {task_id} \u5b8c\u6210&#xff0c;\u72b6\u6001: {&#039;\u6210\u529f&#039; if success else &#039;\u5931\u8d25&#039;}&#034;)<\/p>\n<p>    except Exception as e:<br \/>\n        logger.error(f&#034;\u5904\u7406\u6253\u5370\u4efb\u52a1\u5931\u8d25: {e}&#034;)<\/p>\n<p>        with task_lock:<br \/>\n            tasks[task_id][&#034;status&#034;] &#061; &#034;failed&#034;<br \/>\n            tasks[task_id][&#034;error&#034;] &#061; str(e)<br \/>\n            tasks[task_id][&#034;completed_at&#034;] &#061; time.strftime(&#034;%H:%M:%S&#034;)<br \/>\n            tasks[task_id][&#034;copies_printed&#034;] &#061; 0<\/p>\n<p>        # \u786e\u4fdd\u6e05\u7406\u4e34\u65f6\u6587\u4ef6<br \/>\n        if os.path.exists(file_path):<br \/>\n            try:<br \/>\n                os.remove(file_path)<br \/>\n                logger.info(f&#034;\u6e05\u7406\u4e34\u65f6\u6587\u4ef6: {file_path}&#034;)<br \/>\n            except:<br \/>\n                pass<\/p>\n<p>&#064;app.get(&#034;\/&#034;, response_class&#061;HTMLResponse)<br \/>\nasync def root():<br \/>\n    &#034;&#034;&#034;\u8fd4\u56de\u4e3b\u9875\u9762&#034;&#034;&#034;<br \/>\n    try:<br \/>\n        with open(&#034;static\/index.html&#034;, &#034;r&#034;, encoding&#061;&#034;utf-8&#034;) as f:<br \/>\n            return HTMLResponse(content&#061;f.read(), status_code&#061;200)<br \/>\n    except FileNotFoundError:<br \/>\n        # \u5982\u679c\u9759\u6001\u6587\u4ef6\u4e0d\u5b58\u5728&#xff0c;\u8fd4\u56de\u4e00\u4e2a\u7b80\u5355\u7684\u91cd\u5b9a\u5411\u9875\u9762<br \/>\n        return HTMLResponse(content&#061;&#034;&#034;&#034;<br \/>\n        &lt;!DOCTYPE html&gt;<br \/>\n        &lt;html&gt;<br \/>\n        &lt;head&gt;<br \/>\n            &lt;meta charset&#061;&#034;UTF-8&#034;&gt;<br \/>\n            &lt;meta name&#061;&#034;viewport&#034; content&#061;&#034;width&#061;device-width, initial-scale&#061;1.0&#034;&gt;<br \/>\n            &lt;title&gt;PDF\u6253\u5370\u670d\u52a1\u5668&lt;\/title&gt;<br \/>\n            &lt;style&gt;<br \/>\n                body { font-family: Arial, sans-serif; margin: 50px; text-align: center; }<br \/>\n                h1 { color: #333; }<br \/>\n                .info { background: #f0f8ff; padding: 20px; border-radius: 10px; margin: 20px auto; max-width: 600px; }<br \/>\n                .error { color: red; font-weight: bold; }<br \/>\n            &lt;\/style&gt;<br \/>\n        &lt;\/head&gt;<br \/>\n        &lt;body&gt;<br \/>\n            &lt;h1&gt;PDF\u6253\u5370\u670d\u52a1\u5668&lt;\/h1&gt;<br \/>\n            &lt;div class&#061;&#034;info&#034;&gt;<br \/>\n                &lt;p class&#061;&#034;error&#034;&gt;\u9759\u6001\u6587\u4ef6\u76ee\u5f55\u672a\u627e\u5230&lt;\/p&gt;<br \/>\n                &lt;p&gt;\u8bf7\u786e\u4fdd\u5df2\u7ecf\u521b\u5efa\u4e86 static\/index.html \u6587\u4ef6&lt;\/p&gt;<br \/>\n                &lt;p&gt;\u6216\u8005\u76f4\u63a5\u4f7f\u7528API\u63a5\u53e3&#xff1a;&lt;\/p&gt;<br \/>\n                &lt;ul style&#061;&#034;text-align: left; display: inline-block;&#034;&gt;<br \/>\n                    &lt;li&gt;GET \/api\/printers &#8211; \u83b7\u53d6\u6253\u5370\u673a\u5217\u8868&lt;\/li&gt;<br \/>\n                    &lt;li&gt;POST \/api\/upload &#8211; \u4e0a\u4f20\u5e76\u6253\u5370PDF&lt;\/li&gt;<br \/>\n                    &lt;li&gt;GET \/api\/health &#8211; \u670d\u52a1\u5668\u5065\u5eb7\u68c0\u67e5&lt;\/li&gt;<br \/>\n                &lt;\/ul&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n        &lt;\/body&gt;<br \/>\n        &lt;\/html&gt;<br \/>\n        &#034;&#034;&#034;, status_code&#061;404)<\/p>\n<p>&#064;app.get(&#034;\/api\/printers&#034;)<br \/>\nasync def get_printers():<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u53ef\u7528\u6253\u5370\u673a\u5217\u8868&#034;&#034;&#034;<br \/>\n    try:<br \/>\n        printers &#061; get_system_printers()<br \/>\n        logger.info(f&#034;\u83b7\u53d6\u5230\u6253\u5370\u673a\u5217\u8868: {printers}&#034;)<br \/>\n        return {<br \/>\n            &#034;printers&#034;: printers,<br \/>\n            &#034;count&#034;: len(printers),<br \/>\n            &#034;system&#034;: platform.system(),<br \/>\n            &#034;sumatra_supported&#034;: platform.system() &#061;&#061; &#034;Windows&#034;<br \/>\n        }<br \/>\n    except Exception as e:<br \/>\n        logger.error(f&#034;\u83b7\u53d6\u6253\u5370\u673a\u5217\u8868\u5931\u8d25: {e}&#034;)<br \/>\n        return {<br \/>\n            &#034;printers&#034;: [&#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;],<br \/>\n            &#034;error&#034;: str(e),<br \/>\n            &#034;system&#034;: platform.system()<br \/>\n        }<\/p>\n<p>&#064;app.post(&#034;\/api\/upload&#034;)<br \/>\nasync def upload_pdf(<br \/>\n    file: UploadFile &#061; File(&#8230;, description&#061;&#034;PDF\u6587\u4ef6&#034;),<br \/>\n    printer: Optional[str] &#061; Form(None),<br \/>\n    copies: int &#061; Form(1, ge&#061;1, le&#061;100)<br \/>\n):<br \/>\n    &#034;&#034;&#034;\u4e0a\u4f20PDF\u6587\u4ef6\u5e76\u6dfb\u52a0\u5230\u6253\u5370\u961f\u5217&#034;&#034;&#034;<br \/>\n    # \u9a8c\u8bc1\u6587\u4ef6\u7c7b\u578b<br \/>\n    if not file.filename:<br \/>\n        raise HTTPException(status_code&#061;400, detail&#061;&#034;\u672a\u9009\u62e9\u6587\u4ef6&#034;)<\/p>\n<p>    if not file.filename.lower().endswith(&#034;.pdf&#034;):<br \/>\n        raise HTTPException(status_code&#061;400, detail&#061;&#034;\u53ea\u652f\u6301PDF\u6587\u4ef6&#xff0c;\u8bf7\u4e0a\u4f20PDF\u683c\u5f0f\u6587\u4ef6&#034;)<\/p>\n<p>    # \u9a8c\u8bc1\u6587\u4ef6\u5927\u5c0f<br \/>\n    content &#061; await file.read()<br \/>\n    file_size &#061; len(content)<br \/>\n    if file_size &gt; 10 * 1024 * 1024:  # 10MB\u9650\u5236<br \/>\n        raise HTTPException(status_code&#061;400, detail&#061;&#034;\u6587\u4ef6\u5927\u5c0f\u4e0d\u80fd\u8d85\u8fc710MB&#034;)<\/p>\n<p>    # \u91cd\u7f6e\u6587\u4ef6\u6307\u9488\u4ee5\u4fbf\u518d\u6b21\u8bfb\u53d6<br \/>\n    await file.seek(0)<\/p>\n<p>    # \u4fdd\u5b58\u6587\u4ef6<br \/>\n    filename &#061; f&#034;{uuid.uuid4().hex}.pdf&#034;<br \/>\n    file_path &#061; UPLOAD_DIR \/ filename<\/p>\n<p>    try:<br \/>\n        with open(file_path, &#034;wb&#034;) as f:<br \/>\n            content &#061; await file.read()<br \/>\n            f.write(content)<\/p>\n<p>        logger.info(f&#034;\u6587\u4ef6\u5df2\u4fdd\u5b58: {file_path}, \u539f\u59cb\u6587\u4ef6\u540d: {file.filename}, \u5927\u5c0f: {file_size}\u5b57\u8282, \u4efd\u6570: {copies}&#034;)<\/p>\n<p>        # \u521b\u5efa\u4efb\u52a1ID<br \/>\n        task_id &#061; uuid.uuid4().hex<br \/>\n        with task_lock:<br \/>\n            tasks[task_id] &#061; {<br \/>\n                &#034;id&#034;: task_id,<br \/>\n                &#034;status&#034;: &#034;queued&#034;,<br \/>\n                &#034;filename&#034;: file.filename,<br \/>\n                &#034;printer&#034;: printer or &#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;,<br \/>\n                &#034;copies&#034;: copies,<br \/>\n                &#034;file_size&#034;: file_size,<br \/>\n                &#034;created_at&#034;: time.strftime(&#034;%Y-%m-%d %H:%M:%S&#034;),<br \/>\n                &#034;original_filename&#034;: file.filename<br \/>\n            }<\/p>\n<p>        # \u5f02\u6b65\u6267\u884c\u6253\u5370\u4efb\u52a1<br \/>\n        executor.submit(process_print_task, task_id, file_path, printer, copies, file.filename)<\/p>\n<p>        return {<br \/>\n            &#034;task_id&#034;: task_id,<br \/>\n            &#034;status&#034;: &#034;queued&#034;,<br \/>\n            &#034;filename&#034;: file.filename,<br \/>\n            &#034;printer&#034;: printer or &#034;\u9ed8\u8ba4\u6253\u5370\u673a&#034;,<br \/>\n            &#034;copies&#034;: copies,<br \/>\n            &#034;message&#034;: f&#034;\u6587\u4ef6\u5df2\u4e0a\u4f20&#xff0c;\u6b63\u5728\u6392\u961f\u6253\u5370 {copies} \u4efd&#034;<br \/>\n        }<\/p>\n<p>    except Exception as e:<br \/>\n        logger.error(f&#034;\u5904\u7406\u4e0a\u4f20\u6587\u4ef6\u5931\u8d25: {e}&#034;)<br \/>\n        # \u786e\u4fdd\u6e05\u7406\u4e34\u65f6\u6587\u4ef6<br \/>\n        if os.path.exists(file_path):<br \/>\n            try:<br \/>\n                os.remove(file_path)<br \/>\n            except:<br \/>\n                pass<br \/>\n        raise HTTPException(status_code&#061;500, detail&#061;f&#034;\u5904\u7406\u6587\u4ef6\u5931\u8d25: {str(e)}&#034;)<\/p>\n<p>&#064;app.get(&#034;\/api\/tasks\/{task_id}&#034;)<br \/>\nasync def get_task_status(task_id: str):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u4efb\u52a1\u72b6\u6001&#034;&#034;&#034;<br \/>\n    if task_id not in tasks:<br \/>\n        raise HTTPException(status_code&#061;404, detail&#061;&#034;\u4efb\u52a1\u4e0d\u5b58\u5728&#034;)<\/p>\n<p>    with task_lock:<br \/>\n        task_info &#061; tasks[task_id].copy()<\/p>\n<p>    return task_info<\/p>\n<p>&#064;app.get(&#034;\/api\/tasks&#034;)<br \/>\nasync def get_all_tasks():<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u6240\u6709\u4efb\u52a1&#034;&#034;&#034;<br \/>\n    with task_lock:<br \/>\n        # \u8fd4\u56de\u6700\u8fd1\u7684\u4efb\u52a1&#xff0c;\u6309\u521b\u5efa\u65f6\u95f4\u5012\u5e8f<br \/>\n        all_tasks &#061; list(tasks.values())<br \/>\n        sorted_tasks &#061; sorted(all_tasks, key&#061;lambda x: x.get(&#034;created_at&#034;, &#034;&#034;), reverse&#061;True)<br \/>\n        return {<br \/>\n            &#034;tasks&#034;: sorted_tasks,<br \/>\n            &#034;total&#034;: len(sorted_tasks),<br \/>\n            &#034;queued&#034;: len([t for t in sorted_tasks if t.get(&#034;status&#034;) &#061;&#061; &#034;queued&#034;]),<br \/>\n            &#034;processing&#034;: len([t for t in sorted_tasks if t.get(&#034;status&#034;) &#061;&#061; &#034;processing&#034;]),<br \/>\n            &#034;completed&#034;: len([t for t in sorted_tasks if t.get(&#034;status&#034;) &#061;&#061; &#034;completed&#034;]),<br \/>\n            &#034;failed&#034;: len([t for t in sorted_tasks if t.get(&#034;status&#034;) &#061;&#061; &#034;failed&#034;])<br \/>\n        }<\/p>\n<p>&#064;app.delete(&#034;\/api\/tasks\/{task_id}&#034;)<br \/>\nasync def delete_task(task_id: str):<br \/>\n    &#034;&#034;&#034;\u5220\u9664\u4efb\u52a1&#034;&#034;&#034;<br \/>\n    with task_lock:<br \/>\n        if task_id in tasks:<br \/>\n            del tasks[task_id]<br \/>\n            return {&#034;message&#034;: &#034;\u4efb\u52a1\u5df2\u5220\u9664&#034;, &#034;task_id&#034;: task_id}<br \/>\n        else:<br \/>\n            raise HTTPException(status_code&#061;404, detail&#061;&#034;\u4efb\u52a1\u4e0d\u5b58\u5728&#034;)<\/p>\n<p>&#064;app.delete(&#034;\/api\/tasks&#034;)<br \/>\nasync def clear_completed_tasks():<br \/>\n    &#034;&#034;&#034;\u6e05\u9664\u5df2\u5b8c\u6210\u548c\u5931\u8d25\u7684\u4efb\u52a1&#034;&#034;&#034;<br \/>\n    with task_lock:<br \/>\n        tasks_to_delete &#061; []<br \/>\n        for task_id, task_info in list(tasks.items()):<br \/>\n            if task_info.get(&#034;status&#034;) in [&#034;completed&#034;, &#034;failed&#034;]:<br \/>\n                tasks_to_delete.append(task_id)<\/p>\n<p>        for task_id in tasks_to_delete:<br \/>\n            del tasks[task_id]<\/p>\n<p>        return {<br \/>\n            &#034;message&#034;: f&#034;\u5df2\u6e05\u9664 {len(tasks_to_delete)} \u4e2a\u4efb\u52a1&#034;,<br \/>\n            &#034;cleared&#034;: tasks_to_delete,<br \/>\n            &#034;remaining&#034;: len(tasks)<br \/>\n        }<\/p>\n<p>&#064;app.get(&#034;\/api\/health&#034;)<br \/>\nasync def health_check():<br \/>\n    &#034;&#034;&#034;\u5065\u5eb7\u68c0\u67e5\u7aef\u70b9&#034;&#034;&#034;<br \/>\n    try:<br \/>\n        # \u6d4b\u8bd5\u6253\u5370\u673a\u5217\u8868\u83b7\u53d6<br \/>\n        printers &#061; get_system_printers()<\/p>\n<p>        # \u68c0\u67e5\u4e0a\u4f20\u76ee\u5f55<br \/>\n        upload_dir_exists &#061; UPLOAD_DIR.exists()<\/p>\n<p>        # \u68c0\u67e5\u76ee\u5f55\u5199\u5165\u6743\u9650<br \/>\n        test_file &#061; UPLOAD_DIR \/ &#034;.test_write&#034;<br \/>\n        try:<br \/>\n            test_file.touch()<br \/>\n            can_write &#061; True<br \/>\n            test_file.unlink()<br \/>\n        except:<br \/>\n            can_write &#061; False<\/p>\n<p>        # \u68c0\u67e5SumatraPDF\u662f\u5426\u5b58\u5728&#xff08;\u4ec5Windows&#xff09;<br \/>\n        sumatra_exists &#061; False<br \/>\n        if platform.system() &#061;&#061; &#034;Windows&#034;:<br \/>\n            sumatra_paths &#061; [<br \/>\n                &#034;SumatraPDF.exe&#034;,<br \/>\n                r&#034;C:\\\\Program Files\\\\SumatraPDF\\\\SumatraPDF.exe&#034;,<br \/>\n                r&#034;C:\\\\Program Files (x86)\\\\SumatraPDF\\\\SumatraPDF.exe&#034;,<br \/>\n            ]<br \/>\n            for path in sumatra_paths:<br \/>\n                if os.path.exists(path):<br \/>\n                    sumatra_exists &#061; True<br \/>\n                    break<\/p>\n<p>        return {<br \/>\n            &#034;status&#034;: &#034;healthy&#034;,<br \/>\n            &#034;system&#034;: platform.system(),<br \/>\n            &#034;python_version&#034;: sys.version.split()[0],<br \/>\n            &#034;upload_dir_exists&#034;: upload_dir_exists,<br \/>\n            &#034;can_write&#034;: can_write,<br \/>\n            &#034;printers_count&#034;: len(printers),<br \/>\n            &#034;tasks_count&#034;: len(tasks),<br \/>\n            &#034;sumatra_installed&#034;: sumatra_exists,<br \/>\n            &#034;server_time&#034;: time.strftime(&#034;%Y-%m-%d %H:%M:%S&#034;),<br \/>\n            &#034;version&#034;: &#034;1.3.0&#034;,<br \/>\n            &#034;features&#034;: {<br \/>\n                &#034;copies_support&#034;: True,<br \/>\n                &#034;async_printing&#034;: True,<br \/>\n                &#034;multiple_fallback&#034;: True<br \/>\n            }<br \/>\n        }<br \/>\n    except Exception as e:<br \/>\n        return {<br \/>\n            &#034;status&#034;: &#034;error&#034;,<br \/>\n            &#034;error&#034;: str(e),<br \/>\n            &#034;system&#034;: platform.system()<br \/>\n        }<\/p>\n<p>&#064;app.get(&#034;\/api\/sumatra-test&#034;)<br \/>\nasync def test_sumatra():<br \/>\n    &#034;&#034;&#034;\u6d4b\u8bd5SumatraPDF\u5b89\u88c5\u548c\u914d\u7f6e&#034;&#034;&#034;<br \/>\n    if platform.system() !&#061; &#034;Windows&#034;:<br \/>\n        return {&#034;supported&#034;: False, &#034;message&#034;: &#034;\u6b64\u529f\u80fd\u4ec5\u652f\u6301Windows\u7cfb\u7edf&#034;}<\/p>\n<p>    try:<br \/>\n        sumatra_paths &#061; [<br \/>\n            &#034;SumatraPDF.exe&#034;,<br \/>\n            r&#034;C:\\\\Program Files\\\\SumatraPDF\\\\SumatraPDF.exe&#034;,<br \/>\n            r&#034;C:\\\\Program Files (x86)\\\\SumatraPDF\\\\SumatraPDF.exe&#034;,<br \/>\n        ]<\/p>\n<p>        sumatra_exe &#061; None<br \/>\n        for path in sumatra_paths:<br \/>\n            if os.path.exists(path):<br \/>\n                sumatra_exe &#061; path<br \/>\n                break<\/p>\n<p>        if not sumatra_exe:<br \/>\n            return {<br \/>\n                &#034;installed&#034;: False,<br \/>\n                &#034;message&#034;: &#034;SumatraPDF\u672a\u5b89\u88c5&#034;,<br \/>\n                &#034;download_url&#034;: &#034;https:\/\/www.sumatrapdfreader.org\/download-free-pdf-viewer.html&#034;<br \/>\n            }<\/p>\n<p>        # \u6d4b\u8bd5\u7248\u672c<br \/>\n        result &#061; subprocess.run([sumatra_exe, &#034;-version&#034;], capture_output&#061;True, text&#061;True, timeout&#061;5)<\/p>\n<p>        return {<br \/>\n            &#034;installed&#034;: True,<br \/>\n            &#034;path&#034;: sumatra_exe,<br \/>\n            &#034;version&#034;: result.stdout.strip() if result.returncode &#061;&#061; 0 else &#034;\u672a\u77e5\u7248\u672c&#034;,<br \/>\n            &#034;print_options&#034;: {<br \/>\n                &#034;copies&#034;: &#034;\u4f7f\u7528 -print-settings &#039;Nx&#039; \u683c\u5f0f&#xff0c;\u5982 &#039;3x&#039; \u8868\u793a3\u4efd&#034;,<br \/>\n                &#034;printer&#034;: &#034;\u4f7f\u7528 -print-to &#039;\u6253\u5370\u673a\u540d\u79f0&#039; \u6216 -print-to-default&#034;,<br \/>\n                &#034;silent&#034;: &#034;-silent \u53c2\u6570\u7528\u4e8e\u9759\u9ed8\u6253\u5370&#034;,<br \/>\n                &#034;dialog&#034;: &#034;-print-dialog -exit-when-done \u7528\u4e8e\u663e\u793a\u6253\u5370\u5bf9\u8bdd\u6846&#034;<br \/>\n            }<br \/>\n        }<br \/>\n    except Exception as e:<br \/>\n        return {&#034;installed&#034;: False, &#034;error&#034;: str(e)}<\/p>\n<p>if __name__ &#061;&#061; &#034;__main__&#034;:<br \/>\n    # \u83b7\u53d6\u672c\u5730IP\u5730\u5740<br \/>\n    import socket<br \/>\n    try:<br \/>\n        hostname &#061; socket.gethostname()<br \/>\n        local_ip &#061; socket.gethostbyname(hostname)<br \/>\n    except:<br \/>\n        local_ip &#061; &#034;127.0.0.1&#034;<\/p>\n<p>    print(&#034;&#061;&#034; * 60)<br \/>\n    print(&#034;PDF\u6253\u5370\u670d\u52a1\u5668\u542f\u52a8 &#8211; SumatraPDF\u4fee\u590d\u7248&#034;)<br \/>\n    print(&#034;&#061;&#034; * 60)<br \/>\n    print(f&#034;\u672c\u5730\u8bbf\u95ee: http:\/\/localhost:8083&#034;)<br \/>\n    print(f&#034;\u5c40\u57df\u7f51\u8bbf\u95ee: http:\/\/{local_ip}:8083&#034;)<br \/>\n    print(f&#034;\u670d\u52a1\u5668\u5730\u5740: {local_ip}&#034;)<br \/>\n    print(f&#034;\u7cfb\u7edf: {platform.system()}&#034;)<br \/>\n    print(&#034;&#061;&#034; * 60)<br \/>\n    print(&#034;\u91cd\u8981\u66f4\u65b0:&#034;)<br \/>\n    print(&#034;1. \u6839\u636e\u5b98\u65b9\u6587\u6863\u4fee\u590d\u6253\u5370\u4efd\u6570\u8bbe\u7f6e: -print-settings &#039;Nx&#039;&#034;)<br \/>\n    print(&#034;2. \u65b0\u589e\u6253\u5370\u5bf9\u8bdd\u6846\u5907\u7528\u65b9\u6848&#034;)<br \/>\n    print(&#034;3. \u589e\u52a0SumatraPDF\u6d4b\u8bd5\u63a5\u53e3&#034;)<br \/>\n    print(&#034;&#061;&#034; * 60)<br \/>\n    print(&#034;SumatraPDF\u6253\u5370\u9009\u9879:&#034;)<br \/>\n    print(&#034;  -print-to &#039;\u6253\u5370\u673a\u540d&#039;     # \u6307\u5b9a\u6253\u5370\u673a&#034;)<br \/>\n    print(&#034;  -print-to-default        # \u9ed8\u8ba4\u6253\u5370\u673a&#034;)<br \/>\n    print(&#034;  -print-settings &#039;3x&#039;     # \u6253\u53703\u4efd (\u6839\u636e\u6587\u6863)&#034;)<br \/>\n    print(&#034;  -silent                  # \u9759\u9ed8\u6a21\u5f0f&#034;)<br \/>\n    print(&#034;  -print-dialog            # \u663e\u793a\u6253\u5370\u5bf9\u8bdd\u6846&#034;)<br \/>\n    print(&#034;&#061;&#034; * 60)<\/p>\n<p>    # \u786e\u4fdduploads\u76ee\u5f55\u5b58\u5728<br \/>\n    UPLOAD_DIR.mkdir(exist_ok&#061;True)<\/p>\n<p>    # \u6e05\u7406\u65e7\u7684\u4e34\u65f6\u6587\u4ef6&#xff08;\u8d85\u8fc71\u5c0f\u65f6&#xff09;<br \/>\n    try:<br \/>\n        now &#061; time.time()<br \/>\n        for file in UPLOAD_DIR.glob(&#034;*.pdf&#034;):<br \/>\n            if now &#8211; file.stat().st_mtime &gt; 3600:  # 1\u5c0f\u65f6<br \/>\n                file.unlink()<br \/>\n                logger.info(f&#034;\u6e05\u7406\u65e7\u6587\u4ef6: {file}&#034;)<br \/>\n    except Exception as e:<br \/>\n        logger.warning(f&#034;\u6e05\u7406\u65e7\u6587\u4ef6\u5931\u8d25: {e}&#034;)<\/p>\n<p>    uvicorn.run(app, host&#061;&#034;0.0.0.0&#034;, port&#061;8083, log_level&#061;&#034;info&#034;) <\/p>\n","protected":false},"excerpt":{"rendered":"<p>PDF\u6253\u5370\u670d\u52a1\u5668<br \/>\n\u4e00\u4e2a\u57fa\u4e8eFastAPI\u7684PDF\u6253\u5370\u670d\u52a1\u5668&#xff0c;\u652f\u6301\u901a\u8fc7\u7f51\u9875\u754c\u9762\u4e0a\u4f20PDF\u6587\u4ef6\u5e76\u6253\u5370\u5230\u5c40\u57df\u7f51\u5185\u7684\u7269\u7406\u6253\u5370\u673a\u3002<br \/>\n\u529f\u80fd\u7279\u6027 &#x1f4c4; \u652f\u6301PDF\u6587\u4ef6\u4e0a\u4f20\u548c\u6253\u5370  &#x1f5a8;\ufe0f \u81ea\u52a8\u68c0\u6d4b\u7cfb\u7edf\u6253\u5370\u673a\u5217\u8868  &#x1f4f1; \u54cd\u5e94\u5f0f\u8bbe\u8ba1&#xff0c;\u652f\u6301\u79fb\u52a8\u7aef\u548c\u684c\u9762\u7aef  &#x1f504; \u5b9e\u65f6\u6253\u5370\u4efb\u52a1\u72b6\u6001\u76d1\u63a7  &#x1f4be; \u81ea\u52a8\u4fdd\u5b58\u7528\u6237\u8bbe\u7f6e&#xff08;\u6253\u5370\u673a\u3001\u6253\u5370\u4efd\u6570&#xff09;  &#x1f512; \u5b89\u5168\u7684\u6587\u4ef6\u5904\u7406<\/p>\n","protected":false},"author":2,"featured_media":67810,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[573,72,567],"topic":[],"class_list":["post-67812","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-server","tag-css","tag-fastapi","tag-html"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.wsisp.com\/helps\/67812.html\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"PDF\u6253\u5370\u670d\u52a1\u5668 \u4e00\u4e2a\u57fa\u4e8eFastAPI\u7684PDF\u6253\u5370\u670d\u52a1\u5668&#xff0c;\u652f\u6301\u901a\u8fc7\u7f51\u9875\u754c\u9762\u4e0a\u4f20PDF\u6587\u4ef6\u5e76\u6253\u5370\u5230\u5c40\u57df\u7f51\u5185\u7684\u7269\u7406\u6253\u5370\u673a\u3002 \u529f\u80fd\u7279\u6027 &#x1f4c4; \u652f\u6301PDF\u6587\u4ef6\u4e0a\u4f20\u548c\u6253\u5370 &#x1f5a8;\ufe0f \u81ea\u52a8\u68c0\u6d4b\u7cfb\u7edf\u6253\u5370\u673a\u5217\u8868 &#x1f4f1; \u54cd\u5e94\u5f0f\u8bbe\u8ba1&#xff0c;\u652f\u6301\u79fb\u52a8\u7aef\u548c\u684c\u9762\u7aef &#x1f504; \u5b9e\u65f6\u6253\u5370\u4efb\u52a1\u72b6\u6001\u76d1\u63a7 &#x1f4be; \u81ea\u52a8\u4fdd\u5b58\u7528\u6237\u8bbe\u7f6e&#xff08;\u6253\u5370\u673a\u3001\u6253\u5370\u4efd\u6570&#xff09; &#x1f512; \u5b89\u5168\u7684\u6587\u4ef6\u5904\u7406\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/67812.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-01-28T23:26:31+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/01\/20260128232629-697a9b2582169.png\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"29 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/67812.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/67812.html\",\"name\":\"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-01-28T23:26:31+00:00\",\"dateModified\":\"2026-01-28T23:26:31+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/67812.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/67812.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/67812.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/www.wsisp.com\/helps\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\",\"url\":\"https:\/\/www.wsisp.com\/helps\/\",\"name\":\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"description\":\"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"contentUrl\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"caption\":\"admin\"},\"sameAs\":[\"http:\/\/wp.wsisp.com\"],\"url\":\"https:\/\/www.wsisp.com\/helps\/author\/admin\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.wsisp.com\/helps\/67812.html","og_locale":"zh_CN","og_type":"article","og_title":"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"PDF\u6253\u5370\u670d\u52a1\u5668 \u4e00\u4e2a\u57fa\u4e8eFastAPI\u7684PDF\u6253\u5370\u670d\u52a1\u5668&#xff0c;\u652f\u6301\u901a\u8fc7\u7f51\u9875\u754c\u9762\u4e0a\u4f20PDF\u6587\u4ef6\u5e76\u6253\u5370\u5230\u5c40\u57df\u7f51\u5185\u7684\u7269\u7406\u6253\u5370\u673a\u3002 \u529f\u80fd\u7279\u6027 &#x1f4c4; \u652f\u6301PDF\u6587\u4ef6\u4e0a\u4f20\u548c\u6253\u5370 &#x1f5a8;\ufe0f \u81ea\u52a8\u68c0\u6d4b\u7cfb\u7edf\u6253\u5370\u673a\u5217\u8868 &#x1f4f1; \u54cd\u5e94\u5f0f\u8bbe\u8ba1&#xff0c;\u652f\u6301\u79fb\u52a8\u7aef\u548c\u684c\u9762\u7aef &#x1f504; \u5b9e\u65f6\u6253\u5370\u4efb\u52a1\u72b6\u6001\u76d1\u63a7 &#x1f4be; \u81ea\u52a8\u4fdd\u5b58\u7528\u6237\u8bbe\u7f6e&#xff08;\u6253\u5370\u673a\u3001\u6253\u5370\u4efd\u6570&#xff09; &#x1f512; \u5b89\u5168\u7684\u6587\u4ef6\u5904\u7406","og_url":"https:\/\/www.wsisp.com\/helps\/67812.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-01-28T23:26:31+00:00","og_image":[{"url":"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/01\/20260128232629-697a9b2582169.png"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"29 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/67812.html","url":"https:\/\/www.wsisp.com\/helps\/67812.html","name":"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-01-28T23:26:31+00:00","dateModified":"2026-01-28T23:26:31+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/67812.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/67812.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/67812.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/www.wsisp.com\/helps"},{"@type":"ListItem","position":2,"name":"FastAPI \u548c Html+css+js \u5f00\u53d1\u7684 PDF\u6253\u5370\u670d\u52a1\u5668 \u8fde\u63a5\u5230\u670d\u52a1\u5668\u7684\u7269\u7406\u6253\u5370\u673a\u6253\u5370"}]},{"@type":"WebSite","@id":"https:\/\/www.wsisp.com\/helps\/#website","url":"https:\/\/www.wsisp.com\/helps\/","name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","description":"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":"Person","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41","name":"admin","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/","url":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","contentUrl":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","caption":"admin"},"sameAs":["http:\/\/wp.wsisp.com"],"url":"https:\/\/www.wsisp.com\/helps\/author\/admin"}]}},"_links":{"self":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/67812","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/comments?post=67812"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/67812\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media\/67810"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=67812"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=67812"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=67812"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=67812"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}