diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d362aa3..cc1e280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: sleep 2 done test "$web" = healthy && test "$worker" = healthy - docker compose exec -T siteforge python -c "import json,urllib.request; data=json.load(urllib.request.urlopen('http://127.0.0.1:8787/api/health')); assert data['version']=='8.10.0-dev.1' and data.get('commit')" + docker compose exec -T siteforge python -c "import json,urllib.request; data=json.load(urllib.request.urlopen('http://127.0.0.1:8787/api/health')); assert data['version']=='8.12.0-dev.1' and data.get('commit')" - name: Stop Compose smoke if: always() env: diff --git a/.gitignore b/.gitignore index f40cb3f..5c17769 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ secrets/ demo_storage/ hesupipe_clean2.xml +.s10_* +.sf_* +.s87_* diff --git a/.s10_job.json b/.s10_job.json deleted file mode 100644 index 6b67341..0000000 --- a/.s10_job.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"310b66d47eef48e7","site_id":"483295c29ea0","kind":"build","state":"done","attempts":1,"error":"","result_json":"{\"site\": \"HESU Pipe\", \"version\": \"5552c7a9a9c1\", \"pages\": 203, \"theme\": \"industrial\", \"theme_version\": \"1.1.0\", \"warnings\": [\"尚未配置完整域名:未生成绝对 canonical\", \"7 个尚无真实内容的标准栏目已生成 noindex 占位页,发布前请补充。\", \"384 个外部图片 URL 尚未打包(可开启镜像重建)\", \"16 个指向源站但未能映射的链接,需人工核验\"], \"errors\": [], \"external_assets\": [\"https://hesupipe.com/wp-content/uploads/2024/04/PVC-CORRUGATED-CONDUIT-5.jpg\", \"https://hesucar.com/wp-content/uploads/2024/08/Flat-welding-flange.gif\", \"https://hesucar.com/wp-content/uploads/2024/08/Butt-welding-flange.gif\", \"https://hesupipe.com/wp-content/uploads/2025/11/Irrigation-Pipe-hesu-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/1HDPE-Water-Supply-Pipe-construction-drawings-9.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/1HDPE-Water-Supply-Pipe-Shipping-Picture-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/01/1HDPE-Water-Supply-Pipe-3-scaled.webp\", \"https://hesupipe.com/wp-content/uploads/2025/01/HDPE-Water-Supply-Pipe-3.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/08/PE-trench-pipe-and-PE-drag-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/07/steel-mesh-skeleton-polyethylene-composite-pipes.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/flange-4.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/flange-1-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/flange-13.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/pointer.png\", \"https://hesupipe.com/wp-content/uploads/2024/09/Municipal-pipeline-laying-method-tunnel-laying.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/08/PVC-U-Sewer-Pipe-6.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/Disadvantages-and-precautions-of-HDPE-gas-pipes-in-application.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/2024.11.26-11.27-Dubai-Construction-Machinery-and-Mining-Equipment-Exhibition-MININGSHOW04.webp\", \"https://hesupipe.com/wp-content/uploads/2024/04/Henan-Hesu-Industrial-Co.-Ltd.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/06/MPP-PIPE.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/04/HDPE-Pipeline-Forecast-to-2031.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/02/electrofusion.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/8300ee863b0451c5a9743.webp\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-Corrugated-Pipe-for-Agricultural-Drainage-in-EI-Salvador-Central-America-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-Corrugated-Pipe-for-Agricultural-Drainage-in-EI-Salvador-Central-America-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/02/2024-Philippine-International-Construction-and-Building-Materials-Exhibition.jpg\", \"https://hesupipe.com/wp-content/uploads/2022/12/Revolutionizing-Municipal-Water-Supply-HDPE-Pipeline-Project.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/HDPE-Gas-Pipe-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/HDPE-Segmented-Pipe-Fittings-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/UPVC-Pipes-fpr-Community-Water-Well-Drilling-Project-in-Guatemala-Central-America-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/UPVC-Pipes-fpr-Community-Water-Well-Drilling-Project-in-Guatemala-Central-America-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/Steel-Belt-Reinforced-PE-Corrugated-Pipe-hesupipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/CPVC-power-pipes-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/CPVC-power-pipes.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/CPVC-power-pipes-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/Potable-Water-Distribution-in-Guyana.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/HDPE-Segmented-Pipe-Fittings.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/04/china-import-and-export-fair-complex.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/354e827793e825d10a7b36.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PE-Water-Supply-Pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PE-Water-Supply-Pipe-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/1HDPE-Water-Supply-Pipe-139-scaled.webp\", \"https://hesupipe.com/wp-content/uploads/2024/11/1HDPE-Water-Supply-Pipe-140-scaled.webp\", \"https://hesupipe.com/wp-content/uploads/2024/12/PE-Water-Supply-Pipe-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/468337781_1575522743332885_4709060476433456246_n.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/06/Steel-Framed-Pipe-pk-PE-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/07/hesu-Plastic-steel-doors-and-windows-thermally-insulated-aluminum-doors-and-windows-aluminum-clad-wood-doors-and-windows-color-coated-steel-plates.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/HDPE-Steel-Framed-Pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/01/LDPE-Drip-irrigation-pipe-16.webp\", \"https://hesupipe.com/wp-content/uploads/2025/01/LDPE-Drip-irrigation-pipe-1.webp\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-SlurrySand-Dredging-Pipeline-System-Shipped-to-Tanzania-East-Africa-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-SlurrySand-Dredging-Pipeline-System-Shipped-to-Tanzania-East-Africa-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/CPVC-Electrical-Conduit-Pipes-hesu.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/25b2916b5c49db617f5224.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/25b2916b5c49db617f5249.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/25b2916b5c49db617f5255.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/25b2916b5c49db617f5232.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-Double-Wall-Corrugated-Pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/80d51573b833057f467a56.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/357dc5fb2a3b1276a21875.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/02/Ductile-Iron-Pipe-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/HDPE-Butt-Fusion-Fittings.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-Double-Wall-Corrugated-Pipe-18.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-Double-Wall-Corrugated-Pipe-01.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-Double-Wall-Corrugated-Pipe-40.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-Double-Wall-Corrugated-Pipe-26.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-Double-Wall-Corrugated-Pipe-151.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/HDPE-Micro-Duct-Tube.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/Pipeline-burial-depth-and-soil-covering-depth_0005_Cover-depth.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/3d184500a572361f091e11.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/fcbb152bcf54c82bf09e63.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/d830e6b0830eb590584097.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/d92f3952a5e8063e381b62.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/Singapore-Building-4-scaled.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/Singapore-Building-3-scaled.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/Singapore-Building-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/Singapore-Building-1-scaled.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/02/Singapore-EXPO.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/PVC-Electrical-Conduit.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/Uz-Mining-Expo.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/Trenchless-construction-technology-directional-drilling-method.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/1HDPE-Water-Supply-Pipe-135.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/1HDPE-Water-Supply-Pipe-114.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/Pipe-Market-Analysis.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PVC-Cable-Trunking.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/Pvc-Cable-Trunking-8.webp\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-Pipes-for-Rural-Water-Supply-in-Philippines-Southeast-Asia-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-Pipes-for-Rural-Water-Supply-in-Philippines-Southeast-Asia-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/HDPE-Water-Pipe-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/HDPE-Water-Pipe-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-pipe-for-Urban-water-network-in-Burkina-Faso-West-Africa-3.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-pipe-for-Urban-water-network-in-Burkina-Faso-West-Africa-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/d1afc2d40d1b42dfc65042.webp\", \"https://hesupipe.com/wp-content/uploads/2023/12/about-hesu.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/CERTIFICATE1.png\", \"https://hesupipe.com/wp-content/uploads/2025/01/HDPE-Double-Wall-Corrugated-Pipe-151.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/01/HDPE-Double-Wall-Corrugated-Pipe-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/01/HDPE-Double-Wall-Corrugated-Pipe-5.webp\", \"https://hesupipe.com/wp-content/uploads/2024/11/hdpe-gas-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/HDPE-gas-pipe-16.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/08/BUILDEXPO-AFRICApng.png\", \"https://hesupipe.com/wp-content/uploads/2023/08/Difference-between-KRAH-pipe-and-HDPE-double-wall-corrugated-pipe-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/HDPE-Double-Wall-Corrugated-Pipe-2-scaled.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/02/71094f2fa8e27b739d2085.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/02/5d518828ff221e17893f66.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/02/b2a88c5402ee66d700be53.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/02/1a785dbb9ad69438fa2696.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/PVC.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-3.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-4.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-5.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-6.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-7.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-8.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-9.png\", \"https://hesupipe.com/wp-content/uploads/2024/03/pvc-1.png\", \"https://hesupipe.com/wp-content/uploads/2024/02/1-HDPE-Electro-Fusion-Fittings.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/steel-pipe-specification-size-comparison-table.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/HDPE-Double-Wall-Corrugated-Pipe-hesupipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/8a993752e9658af1019791.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/HDPE-Butt-Fusion-Fittings-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/PVC-porous-grid-electric-square-pipes.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/06/pe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/Hesupipe-participated-in-the-Mongolia-Quarrying-Machinery-and-Mining-Equipment-Expo.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/HDPE-Segmented-Pipe-Fittings.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/HDPE.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/Infrastructure-Development-in-Maldives.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/06/HDPE-Gas-Pipe-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/06/HDPE-Segmented-Pipe-Fittings-5.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/Disadvantages-and-precautions-of-HDPE-gas-pipes-in-application.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-Pipe-for-Underground-Sewerage-System-in-Mongolia-.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-Water-Supply-Pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/Alloy-resin-system-window-5.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/09/HDPE-GAS-PIPE.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/the-HDPE-Corrugated-drainage-pipe-69.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/the-HDPE-Corrugated-drainage-pipe-56.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/the-HDPE-Corrugated-drainage-pipe-16.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/12/gas-pipe-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/12/gas-pipe-3-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/12/gas-pipe-4.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/12/gas-pipe-5.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/2024-Indonesia-Jakarta-International-Building-Materials-Exhibition-MEGA-BUILD.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-7.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-3.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-6.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-8.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-11.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-10.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/hesu-activity-5.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/HDPE-pipes.jpeg\", \"https://hesupipe.com/wp-content/uploads/2023/12/1HDPE-Water-Supply-Pipe-construction-drawings-62.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/HDPE-OR-PVC.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-Water-Pipe-Coils-shipped-to-Chile-South-America-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/HDPE-Water-Pipe-Coils-shipped-to-Chile-South-America-3.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/02/Types-of-plastic-pipes.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/02/pvc-water-supply-and-drainage-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/25b2916b5c49db617f5280-scaled.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-carat-pipe-(1).jpg\", \"https://hesupipe.com/wp-content/uploads/2024/10/HDPE-carat-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/PVC-Pipe-Irrigation-Project-in-Gambia-West-Africa-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/PVC-Pipe-Irrigation-Project-in-Gambia-West-Africa-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/09/irrigation-pipes.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/Welding-Equipment-Products-1.png\", \"https://hesupipe.com/wp-content/uploads/2025/01/Wood-plastic-door-3.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/01/Wood-plastic-door-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/Mining-Show-2024.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/2024.11.26-11.27-Dubai-Construction-Machinery-and-Mining-Equipment-Exhibition-MININGSHOW04-1.webp\", \"https://hesupipe.com/wp-content/uploads/2019/12/dhahran-expo.jpg\", \"https://hesupipe.com/wp-content/uploads/2019/12/Hesus-advanced-PVC-and-HDPE-pipes.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/5d518828ff221e17893f70.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PVC-U-Sewer-Fittings-20.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PVC-U-Sewer-Fittings-26.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PVC-U-Sewer-Fittings-22.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PVC-U-Sewer-Fittings-31.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PVC-U-Sewer-Fittings-39.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/06/hdpe-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/02/hesu-hdpe-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/SAUDI-PROJECTS.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/HDPE-Double-Wall-Corrugated-Pipe-19.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/11/PVC-U-Drainage-Pipes-hesupipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/Alloy-resin-system-window.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/Alloy-resin-system-window-9.png\", \"https://hesupipe.com/wp-content/uploads/2024/10/5d518828ff221e17893f4.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/PVC-Water-Supply-Pipes.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/12/PVC-Water-Supply-Pipe-60.jpg\", \"https://hesupipe.com/wp-content/uploads/2023/12/1HDPE-Water-Supply-Pipe-construction-drawings-1-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2026/04/Water-distribution-and-sewer-line-upgrade-project-in-Cambodia-Southeast-Asia.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/01/Ecological-wooden-door-3.jpg\", \"https://hesupipe.com/wp-content/uploads/2025/01/Ecological-wooden-door-6.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/05/CORRUGATED-HDPE-POLYPRO-PIPE-1.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/03/HESUPIPE-United-Nations-Interim-Security-Force-in-Abyei-Accommodation-Renovation-Project.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/PE-water-supply-pipe-engineering-drawing-of-Wuxi-Wuai-Primary-School-2.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/01/PE-water-supply-pipe-engineering-drawing-of-Wuxi-Wuai-Primary-School-(1).jpg\", \"https://hesupipe.com/wp-content/uploads/2024/04/Taicang-City-drainage-pipe-network-leak-detection-and-filling-project-EPC-general-contracting-PE-pipe.jpg\", \"https://hesupipe.com/wp-content/uploads/2024/04/Shanghai-Haixin-old-factory-renovation-project-PE-drainage-pipempp-power-pipe-1.jpg\"], \"missing_local_images\": [], \"broken_links\": [], \"legacy_links\": [\"https://hesupipe.com/product-category/hdpe-pipe-fittings/\", \"https://hesupipe.com\", \"http://HESUPIPE.COM\", \"https://hesupipe.com/product-category/hdpe-corrugated-pipe/\", \"https://hesupipe.com/product-category/pvc-conduit/\", \"https://hesupipe.com/hdpe-winding-structure-wall-reinforced-pipe-success-stories/\", \"http://hesupipe.com\", \"https://hesupipe.com/product-category/pvc-pipe-fittings/\", \"https://hesupipe.com/showcase/\", \"https://hesupipe.com/papua-new-guinea-water-supply-project-pe-water-supply-pipe/\", \"https://hesupipe.com/wp-content/uploads/2023/12/HESU-PVC-PIPE-FITTINGS.pdf\", \"https://hesupipe.com/wp-content/uploads/2023/12/HDPE-Double-Wall-Corrugated-Pipe.pdf\", \"https://hesupipe.com/wp-content/uploads/2023/12/HESU-HDPE-WATER-PIPES-FITTINGS.pdf\", \"https://hesupipe.com/wp-content/uploads/2023/12/HESU-PPR-PIPES-FITTINGS.pdf\", \"https://hesupipe.com/feedback\", \"https://hesupipe.com/contact-us\"], \"canonical_mismatches\": [], \"slashless_routes\": [], \"redirects\": 0, \"mirrored_images\": 0, \"quality_status\": \"warning\", \"blueprint_blocks\": 5, \"commercial_readiness\": {\"ready_for_review\": false, \"commercially_certified\": false, \"blockers\": 1, \"warnings\": 4}}","created_by":"admin","created_at":"2026-10-22T07:51:01+00:00","started_at":"2026-10-22T07:51:02+00:00","finished_at":"2026-10-22T07:51:05+00:00","payload_json":"{\"mirror_images\":false}","idempotency_key":"hesu-v810-1792655461","max_attempts":3,"timeout_seconds":300,"available_at":"2026-10-22T07:51:05+00:00","cancel_requested":0,"worker_id":null,"lease_until":null,"heartbeat_at":"2026-10-22T07:51:05+00:00"} \ No newline at end of file diff --git a/.s10_stage.json b/.s10_stage.json deleted file mode 100644 index a1149de..0000000 --- a/.s10_stage.json +++ /dev/null @@ -1 +0,0 @@ -{"batch_id":"a17263ad4e9e48258b2be971f63e2a03","state":"staged","mapped":192,"conflicts":[],"sample":[{"type":"page","title":"Product Inquiry","route":"/inquiry/","summary":"","body":"","cover":"","seo_title":"","seo_description":"","canonical":"","meta":{"fields":{}}},{"type":"page","title":"Hot Products","route":"/hot-products/","summary":"","body":"","cover":"","seo_title":"","seo_description":"","canonical":"","meta":{"fields":{}}},{"type":"page","title":"Privacy Policy","route":"/about-us/privacy-policy/","summary":"","body":"

Privacy Policy

\nHESU Technologies Co. Ltd and its affiliates (collectively, “HESU”, “we”, “us”, and “our”) respect your privacy. Please read the following to learn more about our Privacy Policy (“this Policy”). This Policy applies to HESU websites, products, and services that display or provide links to this Policy.\n\nThis Policy describes how HESU processes your personal data, but it may not address all possible data processing scenarios. HESU may inform you of product- or service-specific data collection through supplementary policies or notices provided before collection.\n\nThis Policy describes:\n
    \n \t
  1. How We Collect & Use Your Personal Data
  2. \n \t
  3. Cookies & Similar Technologies
  4. \n \t
  5. How We Disclose Personal Data
  6. \n \t
  7. How to Access & Control Your Personal Data
  8. \n \t
  9. How We Protect Your Personal Data
  10. \n \t
  11. How We Process Children’s Personal Data
  12. \n \t
  13. Third-Party Providers and Their Services
  14. \n \t
  15. International Transfers of Your Personal Data
  16. \n \t
  17. Updates to This Policy
  18. \n \t
  19. How to Contact Us
  20. \n
\n

1. How We Collect & Use Your Personal Data

\nPersonal data means any data that, either on its own or jointly with other data, can be used to identify a natural person. You directly provide us with such data when you use our websites, products, or services, or interact with us by, for example, creating a HESU account or contacting us for support. We may also obtain data by recording how you interact with our websites, products, or services. For example, we may use technologies like cookies or receive use data from software running on your device. As permitted by law, we may also obtain data from public and commercial third-party sources, for example, purchasing statistics from other companies to support our services. The personal data we collect includes name, gender, enterprise name, job position, postal and email addresses, phone number, login information (account and password), photos, and certificate information, etc., depending on how you interact with HESU, for example, the website you visit or the products and services that you use. We also collect the information you provide to us and the content of messages you send us, such as the query information you provide, or the questions or information you provide for customer service support.\n\nBefore using HESU’s products or services, you may need to provide personal data.  In some cases you may be able to opt not to disclose your personal data to HESU. However, not providing HESU with certain data may mean that we cannot provide you with certain products or services or respond to an issue that you have raised.\n\nWe may use your personal data for the following purposes:\n\nHESU may also collect and use non-personally identifiable information (Non-PII). Non-PII is information that cannot be used to identify a particular individual. For example, HESU will collect statistical data, such as the numbers of visits to its website. We collect this data to understand how users use our websites, products, and services so that we can improve our services and better satisfy your needs. HESU may collect, use, process, transfer, or disclose non-PII for other purposes at its own discretion.\n\nWe will endeavor to isolate your personal data from non-PII and ensure that the two types of data are used separately. If personal data is combined with non-PII, it will still be treated as personal data during processing.\n\nHESU will process your personal data following the requirements of applicable laws on an appropriate legal basis, including:\n\n

2. Cookies & Similar Technologies

\n

2.1 Cookies

\nTo ensure our website works correctly, we may at times place a small piece of data known as a cookie on your computer or mobile device. A cookie is a text file stored by a web server on a computer or mobile device. The content of a cookie can be retrieved or read only by the server that creates the cookie. The text in a cookie often consists of identifiers, site names, and some numbers and characters. Cookies are unique to the browsers or mobile applications you use, and enable websites to store data such as your preferences or items in your shopping cart.\n\nLike many other websites or Internet service providers, HESU uses cookies to improve user experience. Session cookies are deleted after each visit, while persistent cookies remain in place across multiple visits. Cookies allow websites to remember your settings such as language, font size on your computer or mobile device, or other browser preferences. This means that a user does not need to reset preferences for every visit. On the contrary, if cookies are not used, websites will treat you as a new visitor every time you load a web page. For example, if you are redirected to another web page from a website you are already logged in to and then return to the original website, it will not recognize you and you must log in again.\n\nHESU will not use cookies for any purposes not stated in this Policy. You can manage or delete cookies based on your own preferences. For details, visit AboutCookies.org. You can clear all the cookies stored on your computer, and most web browsers provide the option of blocking cookies. However, by doing so, you have to change the user settings every time you visit our website. Find out how to manage cookie settings for your browser here:\nInternet Explorer\nGoogle Chrome\nMozilla Firefox\nSafari\nOpera\n

2.2 Web Beacons and Pixel Tags

\nIn addition to cookies, we may also use other similar technologies on our websites such as web beacons and pixel tags. For example, when you receive an email from HESU, it may contain a click-through URL that links to a HESU web page. If you click the link, HESU will track your visit to help us learn about your preferences for products and services and improve our customer service. A web beacon is a transparent graphic image embedded in a website or in an email. We use pixel tags in emails to find out whether an email has been opened. You can unsubscribe from the HESU mailing list at any time if you do not want to be tracked in this manner.\n\nBy using our website, you consent to the use of cookies, web beacons and pixel tags as described above.\n

3. How We Disclose Personal Data

\nHESU shares your personal data with other partners, as described in this Policy, when services are provided by partners authorized by HESU. For example, when you make an online purchase from HESU, we must share your personal data with the logistics provider to arrange shipment or a partner to provide services. In addition, as a global company, we may share personal data with HESU affiliates and subsidiaries.\n\nTo comply with applicable laws or respond to valid legal procedures, HESU may also disclose your personal data to law enforcement or other government agencies. If HESU is involved in a restructuring, merger & acquisition, or a bankruptcy or liquidation lawsuit in a given jurisdiction, your personal data may be disclosed in connection with the transaction. HESU may also disclose your data when appropriate, for example, to execute Terms and Conditions, when we believe disclosure is necessary or appropriate to prevent physical harm or financial loss, or when it is in connection with an investigation of suspected or actual illegal activity.\n

4. How to Access & Control Your Personal Data

\nIt is your responsibility to ensure that all personal data submitted to HESU is correct. HESU is dedicated to maintaining the accuracy and completeness of personal data and keeping the data up-to-date.\n\nTo the extent required by applicable law, you may (i) have the right to access certain personal data we maintain about you, (ii) request that we update or correct inaccuracies in that data, (iii) object or restrict to our use of your personal data, and (iv) ask us to delete your personal data from our database. To exercise these rights, please click here to give your feedback online. Your written request may be required for security. We may decline the request if we have reasonable grounds to believe that the request is a fraudulent, unfeasible or may jeopardize the privacy of others.\n\nIf allowed by applicable laws, you have the right to withdraw your consent at any time when HESU processes your personal data based on your consent. However, withdrawal does not affect the legitimacy and effectiveness of how we process your personal data based on your consent before the withdrawal is made; nor does it affect any data processing based on another justification other than your consent.\n\nIf you think that the way we process your personal information does not comply with applicable data protection laws, you can contact the relevant competent data protection authority. You can obtain the information for contacting EU data protection authorities at http://ec.europa.eu/justice/article-29/structure/data-protection-authorities/index_en.htm.\n

5. How We Protect and Retain Your Personal Data

\nThe security of your personal data is important to us. We use appropriate physical, management, and technical measures to protect your personal data from unauthorized access, disclosure, use, modification, damage, or loss. For example, we use cryptographic technologies for data confidentiality, protection mechanisms to prevent attacks, and access control mechanisms to permit only authorized access to your personal data. We also provide training on security and privacy protection for employees to raise their awareness of personal data protection. HESU is committed to protecting your personal data; however, please note that no security measure is perfect.\n\nWe will retain your personal information for no longer than is necessary for the purposes stated in this Policy, unless otherwise extending the retention period is required or permitted by law. The data storage period may vary with scenario, product, and service. The standards HESU uses to determine the retention period are as follows: the time required to retain personal data to fulfill business purposes, including providing products and services; maintaining corresponding transaction and business records; controlling and improving the performance and quality of products and services; ensuring the security of systems, products, and services; handling possible user queries or complaints and locating problems; whether the user agrees to a longer retention period; and whether the laws, contracts, and other equivalencies have special requirements for data retention; etc. We will maintain your registration information as long as your account is necessary for service provision. You can choose to deregister your account. After you deregister your account, we will stop providing you with products and services through your account and delete your relevant personal data, provided that deletion is not otherwise stipulated by special legal requirements.\n

6. How We Process Children’s Personal Data

\nOur websites, products and services are primarily intended for adults. A child must not create a HESU account without the consent of a parent or guardian. If a child’s personal data is collected with prior parental consent, we will only use or disclose the data as permitted by law, with the explicit consent of the child’s parents or guardians, or when necessary for the protection of the child. If we accidentally collect a child’s personal data without verified prior consent from the child’s parents, we will attempt to delete the data as soon as possible.\n

7. Third-Party Providers and Their Services

\nTo ensure a positive user experience, you may receive content or web links from third parties other than HESU and its partners (“third parties”). HESU does not have the right to control such third parties, but you can choose whether to use the links, view the content and/or access the products or services provided by third parties.\n\nHESU cannot control the privacy practices and data protection policies of third parties that are not subject to this Policy. When you submit personal information to such a third party, please read and refer to the privacy protection policy of the third party.\n

8. International Transfers of Your Personal Data

\nAs a global company, your personal data collected by HESU may be processed or accessed in the country/region where you use our products and services or in other countries/regions where HESU or its affiliates, subsidiaries, service providers or business partners have a presence. These jurisdictions may have different data protection laws. In such circumstances, HESU will take measures to ensure that data is processed as required by this Policy and applicable laws, which includes when transferring the data subject’s personal data from the EU to a country or region which have been acknowledged by the EU commission as having an adequate level of data protection, we may use a variety of legal mechanisms, such as signing standard contractual clauses approved by the EU Commission, obtaining the consent to the cross-border transfer of a data subject in the EU, or implementing security measures like anonymizing personal data before cross-border data transfer. You can click here to obtain a copy of the EU’s standard contractual clauses.\n

9. Updates to This Policy

\nHESU reserves the right to update or change this Policy at any time. We will release the latest Privacy Policy on this page for any changes. If major changes are made to the Privacy Policy, we may notify you through different channels, for example, posting a notice on our website or sending you direct notification.\n

10. How to Contact Us

\nIf you have any questions or suggestions, please contact info@HESU.com. If you have any privacy complaints or issues, and want to contact HESU Data Protection Officer (DPO), please click here.\n\nWhere your personal data is processed by HESU in accordance with this Privacy Policy, the HESU entity that responds to your request, contacts you, provides you with products or services, or has signed or is about to sign a contract with you is the controller of the related personal data. You can click here to obtain the contacts of HESU entities.\n\n ","cover":"","seo_title":"","seo_description":"","canonical":"","meta":{"fields":{}}},{"type":"page","title":"Our Team","route":"/about-us/henan-hesu-group-co-ltd/","summary":"","body":"About Our Team\n\nAs a professional plastic pipe manufacturer, Henan Hesu Group Co., Ltd. boasts a skilled and experienced team. Our core team covers various fields from R&D, production, to quality control, ensuring that every pipe meets the highest standards from design to manufacturing.\n\nThe company currently operates 68 modern production lines, with an annual output exceeding 20,000 tons of high-quality plastic pipes, widely used in infrastructure and construction projects. We strictly adhere to ISO 9001 Quality Management System, ISO 14001 Environmental Management System, and ISO 45001 Occupational Health and Safety Management System standards to ensure the safety, reliability, and environmental sustainability of our products. Additionally, our products are CE certified, meeting international standards. We export to 165 countries globally, with 123 agency outlets worldwide, ensuring quick responses to customer needs and providing timely, efficient services.\n\nThroughout the production process, our engineering team carefully oversees every detail, using advanced processes and high-quality raw materials to produce durable, eco-friendly, and efficient plastic pipe products. Our production operators and quality inspectors strictly control every step of the process, ensuring that all products meet international standards and fulfill customer requirements.\n\nOur sales and customer service teams are equally outstanding. They are not only familiar with the technical details of our products but also provide customized solutions based on the specific needs of customers, helping them succeed in various projects. Whether it's technical support at the construction site or post-project maintenance, our team is always committed to the principle of “quick response, efficient resolution,” providing comprehensive support to our clients.\n\nIn addition, Henan Hesu Group Co., Ltd. participates in 56 global construction material industry exhibitions every year, aiming to communicate directly with customers worldwide, understanding market trends and client needs, and continuously driving technological innovation and product upgrades. Our team is dedicated to innovation and quality, constantly enhancing technology and market insights to deliver high-performance pipe products to the global market.\n\nEvery member of Henan Hesu Group Co., Ltd. is proud of the company’s achievements and remains committed to providing customers with reliable products and excellent service.","cover":"","seo_title":"","seo_description":"","canonical":"","meta":{"fields":{}}},{"type":"page","title":"About Us","route":"/about-us/","summary":"","body":"

23 years of experience in this business.

Located in Zhengzhou,Henan province,China,Henan Hesu Group Co.,Ltd was originally established in 1998 and started its 1st step of export in the year of 2007. Henan Hesu Group Co., Ltd.,a high-tech enterprise dedicated to the the production and sales of the new plastic pipe and providing professional and complete pipe solution. 

Our company persists in the core values of \"integrity' and which adheres to the corporate tenet of \"realistic innowation, serving the society\" . The company insist on positioning the high-end market and provides healthy and green quality life for the majority of users.

True to its core value of “integrity”, Henan Hesu Group Co., Ltd. is committed to becoming a world-class material provider, and which adhere to the corporate tenet of “realistic innovation, serving the society”. We insists on positioning the high-end market and provides healthy and green quality life for the majority of users.

\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"\"\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t

\"\"

Henan Hesu Group Co., Ltd., established in August 1998, has been unwavering in its commitment to manufacturing and providing comprehensive plumbing solutions for a diverse range of industries including construction, mining, chemical, agriculture, farming, and aquaculture.

In August 2003, the company earned the prestigious title of “Advanced Enterprise in Henan,” distinguishing itself as an industry leader by being one of the first to pass the ISO 9001:2008 Quality Management System in its sector. This recognition marked a significant milestone in Henan Hesu’s journey towards excellence.

By August 2012, Henan Hesu's relentless pursuit of quality and innovation led to its inclusion in the “Top 100 Henan Light Industry Enterprises.” This accolade further cemented its position as a frontrunner in the industry.

In August 2017, the company expanded its physical footprint, establishing a vast complex of 150,000 square meters of large warehouses in a building materials market near the airport. This expansion not only increased its operational capacity but also its ability to serve a broader market.

Continuing its legacy of innovation and societal contribution, Henan Hesu, in August 2019, reaffirmed its dedication to the corporate tenet of \"Realistic Innovation, Serving the Society.\" The company has consistently positioned itself in the high-end market, ensuring the delivery of healthy and green quality products and services.

A significant achievement came in March 2021 when Henan Hesu successfully obtained the IOS9001, 14001, and 45001 Quality Management System Certification. This accomplishment on March 24, 2021, not only reinforced the company's commitment to quality and excellence but also demonstrated its dedication to establishing and improving management systems and standardizing management processes.

Through these milestones, Henan Hesu Group Co., Ltd. has continually evolved and positioned itself as a leader in the field, dedicated to providing innovative and high-quality solutions, and contributing positively to society and the environment.

   \t\t\n\t\t\t

OUR CERTIFICATE

\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"\"\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t
\"\"

Andy

\t\t\n\t\t\t
\"\"

Dave

\t\t\n\t\t\t
\"\"

Danyang

\t\t\n\t\t\t
\"\"

Jenny

\t\t\n\t\t

Drawing from the rich legacy and forward-thinking ethos of Henan Hesu Group Co., Ltd., our team is a dynamic and skilled assembly of professionals dedicated to upholding the highest standards in the industry. Comprised of experienced engineers, innovative designers, dedicated manufacturing specialists, and an adept administrative staff, our team is the driving force behind our trailblazing success.

United by a shared commitment to excellence, our team members bring a diverse range of expertise to the table. From pioneering new plumbing solutions to navigating complex industry challenges, each member plays a crucial role in delivering the quality and innovation that Henan Hesu Group is renowned for. Our engineers and designers are at the forefront of technological advancements, ensuring that every product we create meets the evolving needs of the construction, mining, chemical, agriculture, farming, and aquaculture industries.

https://hesupipe.com/wp-content/uploads/2023/12/HESU-PIPE-ABOUNT.mp4","cover":"","seo_title":"","seo_description":"","canonical":"","meta":{"fields":{}}}],"mapping":{"title":"标题","route":"路径","body":"正文","seo_title":"SEO标题","seo_description":"SEO描述"},"requires_conflict_choice":false,"warnings":[{"row":5,"issue":"含复杂 Elementor/WooCommerce 组件,导入后需人工核对"},{"row":6,"issue":"含复杂 Elementor/WooCommerce 组件,导入后需人工核对"},{"row":12,"issue":"含复杂 Elementor/WooCommerce 组件,导入后需人工核对"},{"row":15,"issue":"含复杂 Elementor/WooCommerce 组件,导入后需人工核对"}]} \ No newline at end of file diff --git a/.s10_ver.txt b/.s10_ver.txt deleted file mode 100644 index a9b5d67..0000000 --- a/.s10_ver.txt +++ /dev/null @@ -1 +0,0 @@ -5552c7a9a9c1 \ No newline at end of file diff --git a/README.md b/README.md index af6dc56..a5d7491 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# SiteForge Enterprise V8.10.0-dev.1 — 简洁管理后台体验升级 +# SiteForge Enterprise V8.12.0-dev.1 — 工作台流程与任务状态增强 -> **开发集成版,尚未通过 P0–P4 全部生产验收门禁。** 完整工程基于 V8.8 升级;P5/多语言和 Art Design Pro 官方工程等未在本轮解决。 +> **开发集成版,尚未通过 P0–P4 全部生产验收门禁。** 完整工程在 V8.11 基线上升级;P5/多语言和 Art Design Pro 官方工程等未在本轮解决。 -## V8.10:管理后台重新设计(本轮重点) +## V8.11:管理后台重新设计(本轮重点) 默认 `http://localhost:8787/` 现使用 **简洁工作台**,保留原有业务 API 和数据结构,不迁移数据库。 @@ -12,13 +12,13 @@ - **网站概览**:四步流程(建立网站、准备内容、完善设计、构建预览),动态提示下一步。 - **内容与页面**:页面、产品、新闻统一管理,可快捷修改文字或进入可视化设计器。 - **外观与设计**:行业主题选择与实际页面编辑;草稿/发布隔离仍由原编辑器负责。 -- **导入与迁移**:简单文件快速导入;WordPress/认证 API/MySQL 等复杂任务引导进入专业工作台。快速导入是直接写入,复杂迁移必须使用带预演与复核的入口。 +- **导入与迁移**:默认进入带预演、复核与批次回滚的导入工作台;小文件直写保留为高级操作且必须确认。 - **构建与交付**:使用既有异步 Worker,预览、静态 ZIP、质量报告集中呈现;正式审批和远程发布保留在企业交付中心。 - **网站设置**:站点名称、域名、公司标语、Logo、类型等从设计功能中独立出来。 - **高级工具**:折叠保留原有生产控制台、任务与审批、主题预览以及历史后台 `/classic`,确保不丢功能。 新增页面在 `app/templates/workspace.html`,样式和功能逻辑在 `app/static/workspace.css`、`app/static/workspace.js`。 -其他高级页面属于旧界面,后续可逐一沿用本次信息架构迁移;这次未将 Art Design Pro 官方完整工程视为已整合。 +专业页面已经使用 V8.11 的统一导航与分组交互;Art Design Pro 官方完整工程仍未正式编译整合。 **运行前后检查**:登录、创建站点、内容编辑、主题切换、文件导入、异步构建、预览/ZIP、手机宽度侧边栏。 @@ -281,3 +281,18 @@ python scripts/verify_release.py 接下来的 P2 收尾任务:自动尝试多种 Sitemap 路径、WordPress 媒体库/分类标签迁移、REST 采集的统计/重试监控、媒体 CDN 授权白名单和批量迁移压测。之后再推进 P3 XML/API/只读数据库连接器、P4 整站编辑器、P5 安全发布、多语言与 PostgreSQL/Redis 生产化。 更多细节:[V8.3 验收与剩余功能](docs/V8_3_RELEASE_STATUS.md)。 + +## V8.11:统一专业管理体验 + +本版在 V8.10 基础上统一五个专业工具页面的导航、当前网站选择、业务提示、表单焦点和操作按钮。编辑器将“草稿/预览/正式发布”分组,平板和手机提供结构/预览/属性面板切换;任务中心按迁移、构建、审批发布分为三个阶段。所有原业务 API 与 Docker 服务保持不变。 + +- 默认工作台:`http://localhost:8787/` +- 主题预览:`/theme-gallery` +- 页面编辑器:从工作台的“外观与设计”进入 +- 高级建站:`/factory-studio` +- 数据迁移:`/production-v8` +- 任务与交付:`/operations` + +详细功能对照与人工验收清单见 [`docs/V8_11_UNIFIED_ADMIN.md`](docs/V8_11_UNIFIED_ADMIN.md)。 + +运行测试:`python -m pytest -q --disable-warnings`。本版在当前环境通过 218 项测试。Docker 实机与浏览器跨设备回归仍需部署后完成。 diff --git a/app/static/factory-v2.js b/app/static/factory-v2.js index 6c28fda..c76f162 100644 --- a/app/static/factory-v2.js +++ b/app/static/factory-v2.js @@ -12,7 +12,7 @@ function setTab(tab){state.tab=tab;$$('#navtabs button').forEach(b=>b.classList. function $$(s){return [...document.querySelectorAll(s)]} function setHeading(h,desc){$('#heading').textContent=h;$('#description').textContent=desc;$('#currentTab').textContent=h;} async function init(){try{state.csrf=(await api('/api/auth/me')).csrf;await refresh();document.querySelectorAll('#navtabs button').forEach(btn=>btn.addEventListener('click',()=>setTab(btn.dataset.tab))); - $('#sitePicker').onchange=()=>{state.site=state.sites.find(s=>s.id===$('#sitePicker').value);render().catch(e=>toast(e.message,true))};$('#reload').onclick=refresh;$('#newSite').onclick=openCreate;$('#closeCreate').onclick=()=>$('#createDialog').close();$('#createForm').onsubmit=create; + $('#sitePicker').onchange=()=>{state.site=state.sites.find(s=>s.id===$('#sitePicker').value);window.SiteForgeShell?.selectSite(state.site?.id);render().catch(e=>toast(e.message,true))};$('#reload').onclick=refresh;$('#newSite').onclick=openCreate;$('#closeCreate').onclick=()=>$('#createDialog').close();$('#createForm').onsubmit=create; $('#buildQuick').onclick=()=>buildNow();await render()}catch(e){toast('初始化失败:'+e.message,true)}} async function refresh(){state.sites=await api('/api/sites');state.themes=await api('/api/factory/themes');let old=state.site?.id;state.site=state.sites.find(x=>x.id===old)||state.sites[0]||null; $('#sitePicker').innerHTML=state.sites.map(s=>``).join('');if(state.site)$('#sitePicker').value=state.site.id; diff --git a/app/static/operations.js b/app/static/operations.js index 4099875..2e631b1 100644 --- a/app/static/operations.js +++ b/app/static/operations.js @@ -11,7 +11,7 @@ async function request(url,{method='GET',body=null,upload=false}={}){ } const post=(url,body)=>request(url,{method:'POST',body}); function formatJobs(list){return list.length?list.map(j=>`
${esc(j.kind||'build')} ${esc(j.state)}${esc(j.id)} · 尝试 ${esc(j.attempts||0)} 次
${['queued','running','retrying'].includes(j.state)?``:''}
`).join(''):'当前没有任务';} -async function chooseSite(){siteId=$('#site').value;lastPreview=null;$('#commitImport').disabled=true;await reload()} +async function chooseSite(){siteId=$('#site').value;window.SiteForgeShell?.selectSite(siteId);lastPreview=null;$('#commitImport').disabled=true;await reload()} async function init(){try{csrf=(await request('/api/auth/me')).csrf;sites=await request('/api/sites');$('#site').innerHTML=sites.map(s=>``).join('');siteId=sites[0]?.id||'';$('#site').onchange=chooseSite;$('#reload').onclick=reload;$('#previewImport').onclick=()=>importJSON(false);$('#commitImport').onclick=()=>importJSON(true);$('#build').onclick=buildSite;$('#deploy').onclick=deploySite;$('#refreshRelease').onclick=reload;await reload()}catch(e){notify(e.message,true)}} async function reload(){if(!siteId)return;try{ const [list,jobsData,buildData,approvals,history,reviewData]=await Promise.all([ diff --git a/app/static/professional-ui.css b/app/static/professional-ui.css new file mode 100644 index 0000000..3ce815b --- /dev/null +++ b/app/static/professional-ui.css @@ -0,0 +1,87 @@ +/* SiteForge V8.11 unified professional experience; isolated sf-* selectors */ +:root{--sf-blue:#245fc7;--sf-ink:#203451;--sf-line:#dce5f1;--sf-soft:#f5f8fd;--sf-muted:#60748e} +.sf-globalbar,.sf-globalbar *,.sf-onboarding,.sf-onboarding *{box-sizing:border-box} +.sf-globalbar{position:relative;z-index:100;background:#fff;color:var(--sf-ink);border-bottom:1px solid var(--sf-line);font:13px/1.45 Inter,'Microsoft YaHei',system-ui,sans-serif;box-shadow:0 2px 12px #20345109} +.sf-bar-main{min-height:61px;display:flex;align-items:center;gap:19px;padding:10px clamp(15px,2.3vw,32px);flex-wrap:wrap} +.sf-bar-brand{display:flex;align-items:center;gap:10px;white-space:nowrap;color:#213b66!important;text-decoration:none!important;font-weight:800;font-size:17px} +.sf-bar-logo{display:grid;place-items:center;width:33px;height:33px;background:#265fbd;color:white;border-radius:9px;letter-spacing:-1px} +.sf-bar-loc{font-size:12px;color:#6d7d94;border-left:1px solid #dae3ef;padding-left:16px;min-width:0} +.sf-bar-loc strong{color:#253d63;font-size:13px} +.sf-bar-nav{display:flex;align-items:center;gap:4px;margin-left:auto;min-width:0} +.sf-bar-nav a,.sf-bar-help{font:600 12px/1.5 system-ui,'Microsoft YaHei',sans-serif;text-decoration:none!important;color:#61758f!important;padding:8px 11px;border:1px solid transparent;border-radius:8px;white-space:nowrap;background:transparent;cursor:pointer} +.sf-bar-nav a:hover,.sf-bar-nav a[aria-current='page'],.sf-bar-help:hover{background:#eaf2fe;color:var(--sf-blue)!important} +.sf-bar-site{display:flex;align-items:center;gap:7px;min-width:0;color:#60748e;font-size:12px} +.sf-bar-site select{min-width:120px;max-width:184px;height:36px;padding:7px 28px 7px 10px;border:1px solid #cdd9e9;border-radius:8px;background:#fff;color:#253d63;font-size:12px} +.sf-onboarding{background:#f4f8ff;border-bottom:1px solid #dfe9f7;color:#4b6180;font:12px/1.6 system-ui,'Microsoft YaHei',sans-serif;padding:10px clamp(15px,2.3vw,32px);display:flex;align-items:center;gap:11px;flex-wrap:wrap} +.sf-onboarding strong{color:#1f4d95;white-space:nowrap}.sf-onboarding .sf-onboarding-more{display:none;flex-basis:100%;padding:10px 12px;border:1px solid #d8e5f9;background:white;border-radius:9px} +.sf-onboarding[data-open='true'] .sf-onboarding-more{display:block}.sf-onboarding [data-sf-help]{margin-left:auto;font-size:12px;border:1px solid #bdcfee;background:#fff;color:#2757aa;border-radius:7px;padding:5px 10px;cursor:pointer} +.sf-professional button:not(:disabled),.sf-professional a{touch-action:manipulation} +.sf-professional :is(input:not([type=checkbox]):not([type=radio]),select,textarea):focus-visible,.sf-professional button:focus-visible,.sf-professional a:focus-visible{outline:2px solid #4485eb!important;outline-offset:2px} +.sf-professional :is(input:not([type=checkbox]):not([type=radio]),select,textarea){accent-color:var(--sf-blue)} +.sf-professional button:disabled{opacity:.5;cursor:not-allowed} +.sf-professional .sf-hint{font-size:12px;color:var(--sf-muted);line-height:1.6} +.sf-professional .sf-step{font-size:11px;color:#2859ad;background:#eaf2fe;border-radius:6px;padding:4px 8px;font-weight:700} +/* Avoid duplicate menus: one universal navigation, page-local tabs remain for tasks. */ +.sf-professional.sf-page-theme>header{display:none} +.sf-professional.sf-page-production>header{display:none} +.sf-professional.sf-page-operations .layout{display:block;min-height:auto} +.sf-professional.sf-page-operations .layout>aside{display:none} +.sf-professional.sf-page-operations .layout>main{max-width:1460px;padding:27px clamp(15px,3vw,35px)} +.sf-professional.sf-page-factory .shell{display:block} +.sf-professional.sf-page-factory .sidebar{height:auto;position:static;background:#fff;color:#263e5d;padding:10px 25px;border-bottom:1px solid var(--sf-line);display:block} +.sf-professional.sf-page-factory .sidebar .identity,.sf-professional.sf-page-factory .sidebar .side-title,.sf-professional.sf-page-factory .sidebar .side-bottom{display:none} +.sf-professional.sf-page-factory #navtabs{display:flex;gap:5px;flex-wrap:wrap} +.sf-professional.sf-page-factory #navtabs button{color:#5b708a;padding:10px 14px;border-radius:8px} +.sf-professional.sf-page-factory #navtabs button.active,.sf-professional.sf-page-factory #navtabs button:hover{background:#e9f1ff;color:#245dc4} +.sf-professional.sf-page-factory .top{min-height:59px} +.sf-professional.sf-page-factory .top-crumb{display:none} +.sf-professional.sf-page-factory .top-controls>label{display:none} +.sf-professional.sf-page-operations header .right>label{display:none} +.sf-professional.sf-page-production #site{display:none} +.sf-professional.sf-page-production .panel:has(> .row > div > h2:first-child){scroll-margin-top:24px} +.sf-professional.sf-page-editor .studio{height:calc(100vh - var(--sf-extras-height,116px));min-height:560px} +.sf-professional.sf-page-editor .studio-top{height:auto;min-height:70px;padding:10px 18px;gap:8px;flex-wrap:wrap} +.sf-professional.sf-page-editor .studio-top .brand,.sf-professional.sf-page-editor .studio-top .divide{display:none} +.sf-professional.sf-page-editor .studio-top .top-center{margin-left:auto;margin-right:8px} +.sf-professional.sf-page-editor .studio-top .file-name{max-width:320px} +.sf-professional.sf-page-editor .sf-editor-actiongroup{display:flex;align-items:center;gap:6px;flex-wrap:wrap} +.sf-professional.sf-page-editor .sf-editor-actiongroup[data-group='publish']{padding-left:9px;border-left:1px solid #dae3ef} +.sf-professional.sf-page-editor .sf-editor-actiongroup .ui-btn{padding:9px 11px;font-size:11px} +.sf-professional.sf-page-editor .preview-workspace .stage{padding:13px} +.sf-editor-mobile-tabs{display:none} +.sf-professional.sf-page-editor .sf-editor-help{color:#6b7d95;font-size:11px;margin:0;padding:0 6px} +@media(max-width:1280px){.sf-bar-main{gap:8px}.sf-bar-nav a{padding:7px}.sf-professional.sf-page-editor .studio-top .file-name{display:none}} +@media(max-width:920px){.sf-bar-nav{order:5;width:100%;overflow:auto;margin-left:0;border-top:1px solid #edf1f8;padding-top:6px}.sf-bar-nav a{flex-shrink:0}.sf-bar-site{margin-left:auto}.sf-professional.sf-page-editor .studio{height:calc(100vh - var(--sf-extras-height,156px))}.sf-professional.sf-page-editor .studio-top{position:relative}.sf-professional.sf-page-editor .right-pane{top:0;position:relative;width:auto;box-shadow:none}} +@media(max-width:650px){.sf-bar-main{padding:10px 12px}.sf-bar-loc{display:none}.sf-bar-site{max-width:180px}.sf-bar-site select{max-width:125px}.sf-onboarding{padding:8px 12px;align-items:flex-start}.sf-onboarding>span{flex:1 1 75%;min-width:0}.sf-onboarding [data-sf-help]{margin-left:0}.sf-professional.sf-page-editor .studio{height:auto;min-height:calc(100vh - 160px)}.sf-professional.sf-page-editor .studio-top{padding:9px;gap:5px}.sf-professional.sf-page-editor .studio-top .ui-btn.ghost{display:inline-flex}.sf-professional.sf-page-editor .studio-top .sf-editor-actiongroup{display:flex}.sf-professional.sf-page-editor .studio-top .top-center{margin:0 auto}.sf-professional.sf-page-editor .studio-body{display:block;min-height:610px}.sf-professional.sf-page-editor .studio-body>aside,.sf-professional.sf-page-editor .studio-body>.preview-workspace{display:none}.sf-professional.sf-page-editor[data-mobile-pane='structure'] .studio-body>.left-pane{display:flex;min-height:600px}.sf-professional.sf-page-editor[data-mobile-pane='preview'] .studio-body>.preview-workspace{display:flex;min-height:600px}.sf-professional.sf-page-editor[data-mobile-pane='properties'] .studio-body>.right-pane{display:flex;min-height:600px}.sf-professional.sf-page-editor .left-pane,.sf-professional.sf-page-editor .right-pane{overflow:visible;border:0}.sf-editor-mobile-tabs{display:flex;gap:5px;padding:8px;background:#fff;border-bottom:1px solid var(--sf-line)}.sf-editor-mobile-tabs button{flex:1;border:1px solid #dce5f1;border-radius:7px;background:#f7f9fc;padding:9px 6px;color:#4c617d}.sf-editor-mobile-tabs button[aria-selected='true']{background:#e8f0ff;border-color:#94b8f3;color:#1c56ba;font-weight:750}.sf-professional.sf-page-editor .device-frame{min-height:570px}.sf-professional.sf-page-editor iframe{min-height:600px}.sf-professional.sf-page-factory .sidebar{padding:8px}.sf-professional.sf-page-factory #navtabs{flex-wrap:nowrap;overflow-x:auto}.sf-professional.sf-page-factory #navtabs button{white-space:nowrap}.sf-professional.sf-page-operations .layout>main{padding:14px}.sf-professional.sf-page-production main{padding:0 13px}} +@media(prefers-reduced-motion:reduce){.sf-globalbar *, .sf-onboarding *{transition:none!important}} + +/* Shared actions and form language */ +.sf-professional :is(.primary,.btn-primary,.ui-btn.primary,button.primary){background:#245fc7!important;border-color:#245fc7!important;color:#fff!important} +.sf-professional :is(.primary,.btn-primary,.ui-btn.primary,button.primary):hover:not(:disabled){background:#1c4fa6!important;border-color:#1c4fa6!important} +.sf-professional :is(.outline,.btn-muted,.secondary,.ui-btn.ghost){border-color:#cedceb;color:#3f5674} +.sf-professional .danger,.sf-professional button.danger{color:#ba4249!important} +.sf-professional :is(input,textarea,select):disabled{background:#f2f5fa;color:#8895a7} +.sf-professional :is(input,textarea,select)[aria-invalid='true']{border-color:#c8464e!important;background:#fff8f8!important} +.sf-professional :is(.panel,.section) h2{letter-spacing:-.25px} +.sf-professional .sf-visually-hidden{position:absolute;width:1px;height:1px;clip:rect(0 0 0 0);overflow:hidden;white-space:nowrap} +/* Keep editing controls accessible on tablets, not only desktop. */ +@media(max-width:950px){ +.sf-professional.sf-page-editor .studio-body{display:block;min-height:600px} +.sf-professional.sf-page-editor .studio-body>aside,.sf-professional.sf-page-editor .studio-body>.preview-workspace{display:none} +.sf-professional.sf-page-editor[data-mobile-pane='structure'] .studio-body>.left-pane{display:flex;min-height:600px} +.sf-professional.sf-page-editor[data-mobile-pane='preview'] .studio-body>.preview-workspace{display:flex;min-height:600px} +.sf-professional.sf-page-editor[data-mobile-pane='properties'] .studio-body>.right-pane{display:flex;min-height:600px;position:static;width:100%;box-shadow:none} +.sf-professional.sf-page-editor .sf-editor-mobile-tabs{display:flex;gap:5px;padding:8px;background:white} +.sf-professional.sf-page-editor .sf-editor-mobile-tabs button{flex:1;padding:9px 5px;border:1px solid #d5e0f0;border-radius:7px} +.sf-professional.sf-page-editor .sf-editor-mobile-tabs button[aria-selected='true']{background:#e8f0ff;color:#245fc7;border-color:#99b7e9} +} + +.sf-ops-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:18px 0 20px} +.sf-ops-tabs button{display:grid;gap:4px;text-align:left;border:1px solid #d9e3f1;border-radius:10px;padding:13px 17px;background:#fff;color:#586e8a;cursor:pointer;font:inherit} +.sf-ops-tabs button strong{font-size:13px;color:#334c6d} +.sf-ops-tabs button small{font-size:11px;color:#8a98aa} +.sf-ops-tabs button[aria-selected='true']{border-color:#91b4ec;background:#eaf2ff;box-shadow:inset 3px 0 #326bd6} +.sf-professional.sf-page-operations .layout>main>.two[hidden]{display:none!important} +.sf-professional.sf-page-production .tool-switch{position:sticky;top:0;z-index:6;box-shadow:0 4px 16px #1b355509} +.sf-professional.sf-page-theme .frame{height:calc(100vh - 285px)} +@media(max-width:650px){.sf-ops-tabs{grid-template-columns:1fr;gap:7px}.sf-ops-tabs button{padding:9px 12px}.sf-professional.sf-page-production .tool-switch{position:static}} diff --git a/app/static/professional-ui.js b/app/static/professional-ui.js new file mode 100644 index 0000000..b51b83c --- /dev/null +++ b/app/static/professional-ui.js @@ -0,0 +1,67 @@ +/* Shared non-invasive shell for SiteForge specialized tools. No business API changes. */ +(()=>{'use strict'; +const PAGES={editor:{title:'可视化编辑器',group:'外观与设计',help:'编辑区块和全站布局,先保存草稿,确认后再发布设计。',more:'左侧调整页面结构,中间查看真实主题预览,右侧编辑属性。修改草稿不会改变已构建网站;发布设计后需要重新构建。',next:'完成设计后到「构建与交付」查看最终 HTML。'},theme:{title:'主题预览',group:'外观与设计',help:'比较四套主题及不同设备的页面效果。',more:'这里使用演示资料展示主题结构。选择真正的网站主题请返回工作台 → 外观与设计,切换后重新构建。',next:'返回外观与设计,选择主题并编辑页面。'},factory:{title:'高级建站',group:'专业工具',help:'主题版本、字段映射、SEO 审计及高级构建集中在这里。',more:'先选择网站,再按“总览 → 主题 → 内容 → 迁移检查 → 构建”逐步操作。普通内容管理请使用简洁工作台。',next:'数据准备完成后执行构建与质量检查。'},production:{title:'数据与迁移工具',group:'导入与迁移',help:'一次只处理一类数据:选工具 → 预览 → 审核 → 提交。',more:'WordPress、Excel/CSV、XML、API 与 MySQL 都进入可复核的暂存流程。高级参数可保留默认值,完成后回到工作台构建。',next:'在构建与交付中验证导入后的实际页面。'},operations:{title:'任务与交付',group:'构建与交付',help:'检查迁移问题、Worker 任务、构建质量和发布审批。',more:'按“迁移复核 → 构建任务 → 质量报告 → 审批 → 部署”顺序处理。部署操作会影响线上环境,请先核查版本和目标。',next:'通过质量与审批后,再选择目标环境发布。'}}; +const ctx=document.body?.dataset.sfPage; if(!PAGES[ctx])return; const p=PAGES[ctx],siteKey='siteforge:selected-site'; +const safeRead=()=>{try{return localStorage.getItem(siteKey)||''}catch{return ''}}; +const safeWrite=id=>{try{if(id)localStorage.setItem(siteKey,id);else localStorage.removeItem(siteKey)}catch{}}; +const route=(view)=>`/?view=${encodeURIComponent(view)}${selected()?`&site=${encodeURIComponent(selected())}`:''}`; +const selected=()=>document.getElementById('sfGlobalSite')?.value || safeRead(); +const navbar=[['网站工作台','/','home'],['内容管理','content','content'],['外观设计','design','design'],['导入迁移','import','import'],['构建交付','build','build']]; +const html=document.createElement('div');html.className='sf-globalbar';html.setAttribute('role','navigation');html.setAttribute('aria-label','网站工作区导航'); +const main=document.createElement('div');main.className='sf-bar-main'; +const brand=document.createElement('a');brand.href='/';brand.className='sf-bar-brand';brand.innerHTML='SiteForge'; +const loc=document.createElement('span');loc.className='sf-bar-loc';loc.textContent=`${p.group} / ${p.title}`; +const nav=document.createElement('nav');nav.className='sf-bar-nav';nav.setAttribute('aria-label','主要功能'); +navbar.forEach(([label,view])=>{const a=document.createElement('a');a.textContent=label;a.dataset.sfView=view;a.href=view==='/'?'/':route(view);if((ctx==='editor'||ctx==='theme')&&view==='design'||ctx==='production'&&view==='import'||ctx==='operations'&&view==='build'){a.setAttribute('aria-current','page')}nav.append(a)}); +const sLabel=document.createElement('label');sLabel.className='sf-bar-site';sLabel.textContent='当前网站';const sel=document.createElement('select');sel.id='sfGlobalSite';sel.setAttribute('aria-label','当前管理的网站');sel.append(new Option('正在加载…',''));sLabel.append(sel); +main.append(brand,loc,nav,sLabel);html.append(main); +const help=document.createElement('div');help.className='sf-onboarding';help.dataset.open='false'; +const strong=document.createElement('strong');strong.textContent='当前任务';const short=document.createElement('span');short.textContent=p.help;const toggle=document.createElement('button');toggle.type='button';toggle.dataset.sfHelp='';toggle.textContent='查看操作说明 +';toggle.setAttribute('aria-expanded','false'); +const more=document.createElement('div');more.className='sf-onboarding-more';more.textContent=`操作方法:${p.more} 下一步:${p.next}`;help.append(strong,short,toggle,more); +document.body.classList.add('sf-professional','sf-page-'+ctx);document.body.insertBefore(help,document.body.firstChild);document.body.insertBefore(html,help); +const syncHeight=()=>{document.documentElement.style.setProperty('--sf-extras-height',(html.getBoundingClientRect().height+help.getBoundingClientRect().height)+'px')}; +const localSelect=()=>({factory:'#sitePicker',operations:'#site',production:'#site'})[ctx]||null; +const refreshNav=()=>nav.querySelectorAll('a[data-sf-view]').forEach(a=>{a.href=a.dataset.sfView==='/'?'/':route(a.dataset.sfView)}); +const local=()=>document.querySelector(localSelect()||'#sf__no_local'); +let applying=false; +function mirrorToLocal(id){const node=local();if(!node||!Array.from(node.options).some(o=>o.value===id))return false;if(node.value===id)return true;applying=true;node.value=id;node.dispatchEvent(new Event('change',{bubbles:true}));applying=false;return true} +function selectSite(id){safeWrite(id);refreshNav();} +window.SiteForgeShell={selectSite,selectedSite:selected}; +sel.addEventListener('change',()=>{const id=sel.value;if(!id)return;selectSite(id);if(ctx==='editor'){location.assign(route('design'));return}if(!mirrorToLocal(id)&&ctx!=='theme'){location.assign(route('overview'))}}); +nav.addEventListener('click',()=>selectSite(sel.value)); +toggle.addEventListener('click',()=>{const on=help.dataset.open!=='true';help.dataset.open=String(on);toggle.textContent=on?'收起操作说明 −':'查看操作说明 +';toggle.setAttribute('aria-expanded',String(on));syncHeight()}); +async function load(){try{const res=await fetch('/api/sites',{credentials:'same-origin'});if(!res.ok)throw Error('网站列表加载失败');const items=await res.json();sel.replaceChildren(new Option('选择网站','')); + for(const item of items){sel.append(new Option(item.name,item.id))} + const parts=location.pathname.split('/');const editorId=ctx==='editor'?parts[2]:'';const params=new URLSearchParams(location.search);const wanted=editorId||params.get('site')||safeRead()||items[0]?.id||''; + if(items.some(s=>s.id===wanted)){sel.value=wanted;safeWrite(wanted)}else if(items.length){sel.value=items[0].id;safeWrite(sel.value)} + const native=local();if(native){const apply=()=>{const desired=sel.value;if(Array.from(native.options).some(o=>o.value===desired))mirrorToLocal(desired)};if(native.options.length)apply();const observer=new MutationObserver(()=>{if(!applying)apply()});observer.observe(native,{childList:true});native.addEventListener('change',()=>{if(applying)return;sel.value=native.value;selectSite(native.value)})} + refreshNav();}catch(err){sel.replaceChildren(new Option('在工作台选择网站',''));sel.disabled=true}syncHeight()} + if(ctx==='operations'){ + // Business steps are optional views over existing DOM; hidden panels retain their API listeners. + const content=document.querySelector('.layout > main'); + const groups=Array.from(content?.querySelectorAll(':scope > .two')||[]); + const metrics=content?.querySelector('.metrics'); + if(groups.length===3&&metrics){ + const tabbar=document.createElement('div');tabbar.className='sf-ops-tabs';tabbar.setAttribute('role','tablist');tabbar.setAttribute('aria-label','运行与发布阶段'); + const choices=[['migration','① 导入与复核','确认旧站数据'],['build','② 生成静态网站','查看 Worker 任务'],['release','③ 审批与发布','核对部署目标']]; + const names=['migration','build','release'];groups.forEach((g,i)=>{g.dataset.sfOps=names[i];g.setAttribute('role','tabpanel')}); + function choose(key){groups.forEach(g=>g.hidden=g.dataset.sfOps!==key);tabbar.querySelectorAll('button').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.view===key)));} + choices.forEach(([key,title,desc])=>{const b=document.createElement('button');b.type='button';b.dataset.view=key;b.setAttribute('role','tab');const label=document.createElement('strong');label.textContent=title;const small=document.createElement('small');small.textContent=desc;b.append(label,small);b.addEventListener('click',()=>choose(key));tabbar.append(b)}); + metrics.insertAdjacentElement('afterend',tabbar);choose('migration'); + } + } + // Mark required and invalid fields visibly, leaving native validation intact. + document.addEventListener('invalid',ev=>{const field=ev.target;if(field.matches?.('input,select,textarea'))field.setAttribute('aria-invalid','true')},true); + document.addEventListener('input',ev=>{if(ev.target.matches?.('input,select,textarea')&&ev.target.checkValidity())ev.target.removeAttribute('aria-invalid')}); + if(ctx==='editor'){ + const studio=document.querySelector('.studio');const top=studio?.querySelector('.studio-top'); + if(top){const reorder=(label,group,names)=>{const div=document.createElement('div');div.className='sf-editor-actiongroup';div.dataset.group=group;div.setAttribute('aria-label',label);for(const name of names){const node=Array.from(top.children).find(x=>x.tagName==='BUTTON' && (x.textContent||'').includes(name));if(node)div.append(node)}top.append(div)}; + reorder('页面与全站设计','design',['设计规范','全站页眉']);reorder('历史与草稿','draft',['撤销','重做','放弃草稿','保存草稿']);reorder('预览与构建','preview',['刷新预览','构建静态站']);reorder('发布设计','publish',['发布设计']); + } + const tabs=document.createElement('div');tabs.className='sf-editor-mobile-tabs';tabs.setAttribute('role','tablist');tabs.setAttribute('aria-label','手机端编辑面板');[['structure','页面结构'],['preview','页面预览'],['properties','属性设置']].forEach(([view,title])=>{const b=document.createElement('button');b.type='button';b.setAttribute('role','tab');b.dataset.sfPane=view;b.textContent=title;b.addEventListener('click',()=>setPane(view));tabs.append(b)}); + const body=studio?.querySelector('.studio-body');if(body)studio.insertBefore(tabs,body); + function setPane(pane){document.body.dataset.mobilePane=pane;tabs.querySelectorAll('button').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.sfPane===pane)))}setPane('structure'); + } + if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',load);else load(); + window.addEventListener('resize',syncHeight); +})(); diff --git a/app/static/workspace.css b/app/static/workspace.css index 33b5c31..96a7e52 100644 --- a/app/static/workspace.css +++ b/app/static/workspace.css @@ -1 +1,4 @@ :root{--bg:#f6f8fb;--surface:#fff;--ink:#152538;--muted:#697a8c;--line:#e5eaf1;--blue:#245ac6;--blue-hover:#164da9;--blue-soft:#edf3ff;--green:#178264;--amber:#b67c2a;--shadow:0 4px 22px rgba(25,52,95,.045)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.55 Inter,"Segoe UI","PingFang SC","Microsoft YaHei",system-ui,sans-serif}button,input,textarea,select{font:inherit}button{cursor:pointer}a{text-decoration:none;color:inherit}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible{outline:3px solid #91b9fc;outline-offset:2px}.app-shell{display:grid;grid-template-columns:242px minmax(0,1fr);min-height:100vh}.nav-side{background:#fff;border-right:1px solid var(--line);height:100vh;position:sticky;top:0;display:flex;flex-direction:column;padding:26px 14px 16px;overflow:auto;z-index:15}.brand{display:flex;align-items:center;gap:12px;padding:2px 12px 29px;border-bottom:1px solid var(--line);margin-bottom:11px}.brand-icon{display:grid;place-items:center;width:39px;height:39px;border-radius:12px;background:#2057bc;color:#fff;font-size:24px;font-weight:750;box-shadow:0 7px 18px #306fbc2b}.brand strong,.brand small{display:block;line-height:1.35}.brand strong{font-size:19px;letter-spacing:-.8px}.brand small{font-size:11px;color:#8491a3;letter-spacing:.2px;margin-top:1px}.nav-group{margin:8px 0 12px}.nav-label{font-size:11px;letter-spacing:.3px;color:#94a0ae;font-weight:700;margin:15px 15px 9px}.nav-item{display:flex;align-items:center;gap:13px;width:100%;border:0;background:transparent;color:#556779;border-radius:9px;padding:11px 13px;text-align:left;font-weight:600;transition:background .15s}.nav-item:hover{background:#f4f7fb;color:#183b75}.nav-item.active{background:var(--blue-soft);color:#1e55b1}.nav-ico{width:20px;min-width:20px;text-align:center;font-size:19px;line-height:1.1;font-weight:400;color:#758397}.nav-item.active .nav-ico{color:#245ac6}.site-mini{margin:0 12px 9px;display:block;max-width:191px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#40536c;font-size:12px;font-weight:650}.advanced{border-top:1px solid var(--line);margin-top:8px;padding-top:16px}.advanced summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:12px;padding:10px 14px;color:#66778b;font-weight:600;border-radius:9px}.advanced summary::-webkit-details-marker{display:none}.advanced summary:hover{background:#f5f8fc}.chevron{margin-left:auto}.advanced[open] .chevron{transform:rotate(180deg)}.nav-sub{display:block;padding:9px 10px 9px 48px;color:#64748a;font-size:12px;border-radius:7px}.nav-sub:hover{background:#f4f7fb;color:var(--blue)}.side-foot{display:flex;gap:10px;align-items:center;border-top:1px solid var(--line);padding:19px 13px 3px;margin-top:auto}.side-indicator{width:8px;height:8px;border-radius:100%;background:#25ae80}.side-foot strong,.side-foot small{display:block}.side-foot strong{font-size:12px;color:#4f6073}.side-foot small{font-size:11px;color:#9aa7b4;margin-top:2px}.content-shell{min-width:0;display:flex;flex-direction:column}.topbar{height:76px;background:rgba(255,255,255,.96);border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 36px;gap:16px;position:sticky;top:0;z-index:9}.top-left,.top-right{display:flex;align-items:center;gap:16px}.breadcrumbs{color:#99a4b1;font-size:13px;white-space:nowrap}.breadcrumbs strong{color:#273a50}.crumb-divider{margin:0 9px;color:#cbd4dd}.site-picker-wrap{display:flex;align-items:center;gap:10px;font-size:11px;font-weight:650;color:#8a97a8}.site-picker-wrap select{min-width:180px;max-width:230px;height:38px;padding:0 11px;border:1px solid var(--line);border-radius:8px;color:#3a4c60;background:#fff;font-size:12px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;min-height:39px;border-radius:9px;padding:9px 16px;border:1px solid #dfe6ef;background:#fff;color:#394b5f;font-weight:650;transition:all .15s}.btn:hover{border-color:#b3c8e4;background:#fafcff}.btn-primary{background:var(--blue);border-color:var(--blue);color:#fff;box-shadow:0 4px 12px #225bc328}.btn-primary:hover{background:var(--blue-hover);color:#fff;border-color:var(--blue-hover)}.btn-muted{background:#f5f7fa}.btn-tiny{font-size:12px;min-height:33px;padding:7px 11px}.btn-link{background:none;border:0;color:var(--blue);padding:6px;font-weight:650}.logout,.hamburger{border:0;background:transparent;color:#7c8b9d;font-size:21px;padding:7px;border-radius:7px}.logout:hover,.hamburger:hover{background:#eef3f9}.hamburger{display:none}.main-content{width:min(1190px,100%);padding:37px 37px 70px;margin:0 auto;flex:1}.app-footer{padding:16px 38px;border-top:1px solid var(--line);font-size:11px;color:#a1adba;display:flex;justify-content:space-between;gap:20px}.eyebrow,.overline{font-size:11px;letter-spacing:1.2px;text-transform:uppercase;color:var(--blue);font-weight:750;margin:0 0 10px}.page-heading{display:flex;align-items:start;justify-content:space-between;gap:22px;margin-bottom:26px}.page-heading h1{font-size:28px;letter-spacing:-.8px;line-height:1.2;margin:0 0 10px;font-weight:740}.page-heading p{font-size:13px;color:var(--muted);margin:0;max-width:680px}.section-heading{display:flex;align-items:center;justify-content:space-between;gap:15px;margin:32px 0 16px}.section-heading h2{font-size:17px;letter-spacing:-.3px;margin:0}.section-heading p{color:var(--muted);font-size:12px;margin:5px 0 0}.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin:26px 0}.stat{border:1px solid var(--line);background:white;border-radius:12px;padding:21px 23px;box-shadow:var(--shadow);position:relative}.stat .value{font-size:29px;letter-spacing:-1px;font-weight:740;display:block;margin:6px 0 4px}.stat small{color:var(--muted);font-size:12px}.stat .glyph{position:absolute;right:17px;top:16px;background:#f2f5fa;border-radius:9px;width:30px;height:30px;display:grid;place-items:center;color:#6a809c}.panel{background:var(--surface);border:1px solid var(--line);border-radius:13px;padding:23px;box-shadow:var(--shadow);min-width:0}.panel h2,.panel h3{margin:0 0 6px}.panel h2{font-size:17px}.panel h3{font-size:15px}.panel-desc{color:var(--muted);font-size:12px;margin:0 0 19px}.panel-head{display:flex;align-items:start;justify-content:space-between;gap:15px;margin-bottom:20px}.panel-head p{color:var(--muted);font-size:12px;margin:5px 0 0}.grid-2{display:grid;grid-template-columns:1.6fr 1fr;gap:18px}.grid-equal{grid-template-columns:repeat(2,minmax(0,1fr))}.quick-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:15px}.quick-card{background:#fff;border:1px solid var(--line);border-radius:12px;padding:20px;display:flex;align-items:start;gap:13px;text-align:left;min-height:138px;transition:transform .16s,box-shadow .16s}.quick-card:hover,.site-card:hover,.theme-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px #1f478417}.quick-ico{width:42px;height:42px;flex:0 0 42px;display:grid;place-items:center;background:#edf3fd;color:#285bb4;border-radius:10px;font-size:23px}.quick-card h3{margin:2px 0 6px;font-size:15px}.quick-card p{font-size:12px;line-height:1.6;color:var(--muted);margin:0}.quick-card .quick-more{font-size:12px;color:var(--blue);display:block;font-weight:700;margin-top:12px}.site-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.site-card{padding:21px;border-radius:12px;border:1px solid var(--line);background:#fff;display:flex;gap:17px;align-items:center;text-align:left;transition:all .15s;min-height:127px;position:relative}.site-icon{width:56px;height:56px;display:grid;place-items:center;border-radius:13px;background:#dceafa;color:#204d9a;font-size:24px;font-weight:800;flex:0 0 56px}.site-icon.industrial{background:#e4edeb;color:#2f7561}.site-icon.minimal{background:#f2eee9;color:#8b6348}.site-icon.automotive{background:#e9edf7;color:#333e70}.site-card h3{font-size:15px;margin:0 0 5px}.site-card p{margin:0;color:var(--muted);font-size:12px;line-height:1.6}.site-badges{display:flex;gap:8px;margin-top:9px;font-size:11px;color:#7c8b9d}.arrow{margin-left:auto;font-size:20px;color:#9eb0c8}.pill{display:inline-block;font-size:11px;font-weight:700;color:#3d70bb;background:#eaf2ff;padding:4px 9px;border-radius:6px}.pill.success{color:#187350;background:#e6f6ed}.pill.warn{color:#9a6615;background:#fff3dc}.pill.gray{color:#64748b;background:#f2f4f7}.wizard-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}.wizard-step{border:1px solid var(--line);background:#fff;border-radius:10px;padding:16px;min-height:135px}.wizard-index{display:grid;place-items:center;width:27px;height:27px;border-radius:50%;font-size:12px;font-weight:800;background:#eff3fa;color:#70819a;margin-bottom:11px}.wizard-step.done .wizard-index{background:#e4f7ed;color:#17805b}.wizard-step.next{border-color:#adc9f8;background:#fafcff}.wizard-step.next .wizard-index{background:#e8f0fe;color:#265ac1}.wizard-step h3{font-size:13px;margin:0 0 5px}.wizard-step p{margin:0;color:#8290a0;font-size:11px}.wizard-step button{padding:0;margin-top:9px;font-weight:700;font-size:11px;border:0;background:none;color:var(--blue)}.task-note{background:#edf4ff;border-radius:11px;border:1px solid #dce9fd;padding:16px 19px;display:flex;align-items:center;justify-content:space-between;gap:18px;margin-bottom:24px}.task-note strong{font-size:13px}.task-note p{margin:4px 0 0;font-size:12px;color:#667c99}.empty{background:#fff;border:1px dashed #d6dfeb;border-radius:12px;padding:42px;text-align:center}.empty .empty-symbol{font-size:30px;color:#a0b5d6}.empty h3{font-size:17px;margin:14px 0 6px}.empty p{font-size:12px;color:var(--muted);margin:0 0 18px}.table-wrap{width:100%;overflow-x:auto;border:1px solid var(--line);border-radius:11px;background:#fff}.data-table{width:100%;border-collapse:collapse;font-size:12px}.data-table th{text-align:left;background:#fafbfe;padding:13px 15px;color:#768699;font-weight:650;white-space:nowrap}.data-table td{padding:14px 15px;border-top:1px solid #eef1f6;vertical-align:middle}.data-table td strong{color:#2a3a51;font-size:13px}.data-table .action-cell{white-space:nowrap}.muted{color:var(--muted)}.toolbar{display:flex;gap:12px;align-items:center;margin:0 0 20px}.toolbar input{max-width:300px}.toolbar select{max-width:175px}.input,input,textarea,select{border:1px solid #d9e1ed;border-radius:8px;background:#fff;padding:10px 12px;color:#324357;min-width:0}input:focus,textarea:focus,select:focus{border-color:#4d8ce8;outline:none;box-shadow:0 0 0 3px #4d8ce820}.field-grid,.form-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:17px}.field-grid label,.form-fields>label{display:grid;gap:7px;color:#3d5067;font-size:12px;font-weight:650}.full{grid-column:1 / -1}.field-grid textarea,.form-fields textarea{width:100%;resize:vertical}.form-actions{display:flex;justify-content:flex-start;gap:10px;margin-top:20px}.optional{font-size:10px;color:#9ca7b7;font-weight:400}.choice-cards{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.theme-card{border:1px solid var(--line);background:#fff;border-radius:12px;overflow:hidden;transition:all .15s}.theme-preview{height:126px;position:relative;padding:17px;display:flex;align-items:flex-end;font-size:22px;font-weight:750;letter-spacing:-.9px}.theme-preview.automotive{background:linear-gradient(125deg,#142b4b,#3b527c);color:#e5d8aa}.theme-preview.industrial{background:linear-gradient(125deg,#d4e2df,#899f9c);color:#254f49}.theme-preview.corporate{background:linear-gradient(125deg,#e4eefe,#9ebff1);color:#17437d}.theme-preview.minimal{background:linear-gradient(125deg,#f4efe8,#dcd3c8);color:#6f5944}.theme-desc{padding:16px}.theme-desc h3{margin:0 0 7px;font-size:13px}.theme-desc p{margin:0 0 12px;font-size:11px;color:var(--muted)}.help-hint{background:#f7f9fc;border:1px solid #e6ecf4;border-radius:10px;padding:14px 18px;color:#61748a;font-size:12px}.help-hint strong{color:#304960}.import-box{border:2px dashed #cbdcf6;background:#fafcff;border-radius:12px;padding:24px;margin:18px 0}.import-box input{width:100%;margin:11px 0}.message{font-size:12px;border-radius:8px;padding:14px;background:#f6f8fb;white-space:pre-wrap;overflow:auto;max-height:240px;color:#496075}.build-row{display:flex;gap:15px;align-items:center;border-top:1px solid var(--line);padding:15px 0}.build-row:first-child{border:0}.build-meta{flex:1;min-width:0}.build-meta h3{margin:0 0 4px;font-size:13px}.build-meta p{font-size:11px;color:var(--muted);margin:0}.inline-actions{display:flex;gap:8px;flex-wrap:wrap}.modal{border:1px solid var(--line);border-radius:15px;max-height:90vh;max-width:calc(100vw - 25px);width:570px;box-shadow:0 24px 80px #0e213d30;padding:0;overflow:auto}.modal::backdrop{background:#101e3385}.modal-inner{padding:29px}.modal-top{display:flex;justify-content:space-between;align-items:start;gap:14px;margin-bottom:24px}.modal-top h2{font-size:23px;letter-spacing:-.6px;margin:0 0 5px}.subtle{margin:0;color:var(--muted);font-size:12px}.x-btn{font-size:26px;background:none;border:0;color:#91a0b0}.modal-actions{display:flex;justify-content:flex-end;gap:9px;border-top:1px solid var(--line);margin-top:23px;padding-top:21px}.form-expand{border:1px solid var(--line);border-radius:8px;padding:14px}.form-expand summary{cursor:pointer;font-weight:650;color:#5e748c}.form-expand .form-fields{margin-top:12px}.toast{position:fixed;right:25px;bottom:22px;z-index:90;background:#273f59;color:#fff;padding:13px 18px;border-radius:11px;box-shadow:0 8px 36px #0002;font-size:13px;max-width:450px;visibility:hidden;opacity:0;transform:translateY(13px);transition:all .2s}.toast.show{visibility:visible;opacity:1;transform:translateY(0)}.toast.bad{background:#a83b47}.skeleton{padding:40px;color:#8ea0b2}.mobile-backdrop{display:none}@media(max-width:1180px){.choice-cards{grid-template-columns:repeat(2,minmax(0,1fr))}.wizard-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.main-content{padding:30px 25px 65px}.topbar{padding:0 25px}.site-list{grid-template-columns:1fr}}@media(max-width:880px){.app-shell{display:block}.nav-side{position:fixed;top:0;left:0;transform:translateX(-100%);width:252px;box-shadow:10px 0 40px #192f4830;transition:transform .2s}.nav-side.open{transform:translateX(0)}.hamburger{display:block}.mobile-backdrop.open{display:block;position:fixed;inset:0;background:#10203864;z-index:12}.topbar{padding:0 16px}.site-picker-wrap>span{display:none}.stats{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-2,.grid-equal{grid-template-columns:1fr}.quick-grid{grid-template-columns:1fr}.quick-card{min-height:100px}.site-picker-wrap select{max-width:190px;min-width:110px}}@media(max-width:570px){.topbar{height:66px}.breadcrumbs{display:none}.top-right{gap:7px}.site-picker-wrap select{max-width:150px;width:148px}.top-right .btn{font-size:11px;padding:8px 10px}.main-content{padding:25px 16px 45px}.page-heading{flex-direction:column}.page-heading h1{font-size:23px}.stats{gap:10px}.stat{padding:17px}.stat .value{font-size:25px}.wizard-grid{grid-template-columns:1fr 1fr}.wizard-step{padding:12px;min-height:135px}.site-list,.field-grid,.form-fields,.choice-cards{grid-template-columns:1fr}.full{grid-column:1}.toolbar{flex-wrap:wrap}.toolbar input,.toolbar select{max-width:none;width:100%}.task-note{align-items:start;flex-direction:column}.app-footer{padding:13px 16px}.app-footer span:last-child{display:none}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{transition:none!important;scroll-behavior:auto!important}} + +/* V8.12: task progress and dangerous actions, consistent focus and mobile sizing */ +.btn-danger{border:1px solid #f1c3c7;color:#a92840;background:#fff5f6}.btn-danger:hover{background:#feecee}.btn:disabled{opacity:.55;cursor:not-allowed}.build-row{min-width:0}.build-meta{overflow-wrap:anywhere}.site-card:focus-visible,.quick-card:focus-visible{outline:3px solid #80aaf6;outline-offset:2px}@media(max-width:570px){.build-row{align-items:flex-start;flex-wrap:wrap}.build-row>.inline-actions{width:100%;padding-left:0}.site-picker-wrap select{max-width:130px}} diff --git a/app/static/workspace.js b/app/static/workspace.js index fff967f..d7aaf6f 100644 --- a/app/static/workspace.js +++ b/app/static/workspace.js @@ -5,7 +5,9 @@ const esc=x=>String(x??'').replace(/[&<>"']/g,ch=>({'&':'&','<':'<','>':' const themeName={automotive:'汽车与出行',industrial:'工业与制造',corporate:'企业品牌',minimal:'极简展示'}; const typeName={page:'页面',product:'产品',vehicle:'车型',post:'新闻',brand:'品牌',case:'案例',service:'服务'}; const fmtDate=t=>{if(!t)return '—';const d=new Date(t);return Number.isNaN(d.getTime())?String(t).slice(0,16):d.toLocaleDateString('zh-CN',{year:'numeric',month:'2-digit',day:'2-digit'})}; -const state={sites:[],site:null,entries:[],builds:[],view:'home',csrf:'',search:'',typ:'',loading:false,job:null}; +const sfSiteKey='siteforge:selected-site'; +const sfRemember=id=>{try{if(id)localStorage.setItem(sfSiteKey,id)}catch{}}; +const state={sites:[],site:null,entries:[],builds:[],jobs:[],view:'home',csrf:'',search:'',typ:'',loading:false,job:null,selectionToken:0,building:false}; const headings={home:'工作台',sites:'我的网站',overview:'网站概览',content:'内容与页面',design:'外观与设计',import:'导入与迁移',build:'构建与交付',settings:'网站设置'}; async function api(url,method='GET',data=null,extraHeaders={}){ const headers={...extraHeaders};if(method!=='GET'&&method!=='HEAD')headers['X-SiteForge-CSRF']=state.csrf; @@ -13,7 +15,7 @@ async function api(url,method='GET',data=null,extraHeaders={}){ const res=await fetch(url,{method,headers,body,credentials:'same-origin'}); if(res.status===401){location.href='/login';throw Error('登录已过期,请重新登录')} let value;try{value=await res.json()}catch{throw Error('服务器响应异常(HTTP '+res.status+')')} - if(!res.ok)throw Error(typeof value.detail==='string'?value.detail:JSON.stringify(value.detail||value));return value; + if(!res.ok){const ex=Error(typeof value.detail==='string'?value.detail:JSON.stringify(value.detail||value));ex.status=res.status;throw ex}return value; } function toast(msg,bad=false){const el=$('#toast');el.textContent=msg;el.classList.toggle('bad',bad);el.classList.add('show');clearTimeout(el.timer);el.timer=setTimeout(()=>el.classList.remove('show'),4000)} function notifyError(error){console.error(error);toast(error?.message||String(error),true)} @@ -22,6 +24,13 @@ function actionButton(text,action=''){return ``} function overviewShortcut(view,icon,title,desc){return ``} function home(){const sites=state.sites;const total=sites.reduce((n,s)=>n+(+s.entries_count||0),0);const builds=sites.reduce((n,s)=>n+(+s.build_count||0),0); @@ -40,19 +49,19 @@ function sites(){return htmlHeading('我的网站','统一管理所有网站项 function currentSite(){return !!state.site} function noSite(){return htmlHeading('请先选择一个网站','选择现有网站或创建新网站,才能继续进行内容编辑、迁移和构建。')+emptyState('尚未选择网站','点击右上角选择网站,或直接创建一个新的企业网站。',actionButton('+ 创建网站','new-site'))} function latestDone(){return state.builds.find(b=>['done','complete','success','completed'].includes(b.status))||null} -function overview(){const s=state.site;if(!s)return noSite();const count=state.entries.length;const build=latestDone();const hasContent=count>1;const hasDomain=!!s.domain;const steps=[ +function overview(){const s=state.site;if(!s)return noSite();const count=state.entries.length;const build=latestDone();const hasContent=buildReadyStep();const hasDomain=!!s.domain;const steps=[ {title:'建立网站',desc:'选择行业主题并创建项目',done:true,view:'settings'}, {title:'准备内容',desc:'添加页面、产品或导入旧数据',done:hasContent,view:'content'}, - {title:'完善设计',desc:'选用主题,调整图文和全站风格',done:false,view:'design'}, - {title:'构建并预览',desc:'生成静态网站,检查效果与链接',done:!!build,view:'build'}]; - const next=steps.find(x=>!x.done)||steps[3]; - return htmlHeading(s.name,'这是您的网站工作台。按照下面的顺序操作,完成后就能预览网站。',actionButton('↗ 构建网站','build-now'))+ - `
下一步建议:${next.title}

${next.desc}。${!hasDomain?'网站域名也可以稍后再设置。':''}

`+ + {title:'调整设计',desc:'可反复完善主题、导航和移动端样式',optional:true,view:'design'}, + {title:'构建并检查',desc:'生成静态网站,查看质量报告',done:!!build,view:'build'}]; + const next=!hasContent?steps[1]:!build?steps[3]:null; + return htmlHeading(s.name,'这里显示真实的内容和构建状态。设计可随时调整,正式上线需要单独审核。',actionButton('↗ 构建网站','build-now'))+ + `
${next?'建议下一步:'+next.title:'网站已经生成过构建版本'}

${next?next.desc:'内容或设计有修改时,重新构建并检查新版即可。'}。${!hasDomain?'正式域名还未填写;测试预览不受影响。':''}

`+ `
${stat('网站内容',count,'▧')}${stat('已构建版本',state.builds.length,'↗')}${stat('当前主题',themeName[s.theme]||'企业品牌','◈')}${stat('域名状态',hasDomain?'已填写':'待设置','◎')}
`+ - `

四步完成网站制作

从左到右进行;每一步都可以随时返回修改。

`+ - `
${steps.map((step,i)=>`
${step.done?'✓':i+1}

${step.title}

${step.desc}

`).join('')}
`+ - `

常用操作

日常更新网站通常只需要使用以下三个入口。

`+ - `
${overviewShortcut('content','▧','添加和编辑内容','管理网站页面、产品、新闻、案例。')}${overviewShortcut('design','◈','修改网站样式','更换主题、编辑页面和移动端样式。')}${overviewShortcut('build','↗','查看和下载网站','提交构建,预览并下载静态网站。')}
`; + `

四步完成网站制作

步骤反映已有数据,设计可以随时修改,不会虚报“设计完成”。

`+ + `
${steps.map((step,i)=>`
${step.done?'✓':step.optional?'◈':i+1}

${step.title}${step.optional?'(持续优化)':''}

${step.desc}

`).join('')}
`+ + `

常用操作

通常只需要下面三个入口。

`+ + `
${overviewShortcut('content','▧','添加和编辑内容','管理页面、产品、新闻和案例。')}${overviewShortcut('design','◈','修改网站样式','更换主题、编辑页面和移动端样式。')}${overviewShortcut('build','↗','查看和下载网站','查看任务状态、预览及下载静态网站。')}
`; } function content(){if(!currentSite())return noSite();let arr=state.entries.filter(e=>(!state.typ||e.type===state.typ)&&(!state.search||`${e.title} ${e.route}`.toLowerCase().includes(state.search.toLowerCase()))); return htmlHeading('内容与页面','所有网页、产品、车型和新闻都在这里维护,保存后重新构建即可更新网站。',actionButton('+ 添加内容','new-entry'))+ @@ -66,14 +75,18 @@ function design(){if(!currentSite())return noSite();const s=state.site;const car `

第二步 · 设计网页

挑选要修改的页面;可在设计器中编辑导航、页眉、页脚及各设备布局。

选择要设计的页面

修改先保存为草稿,正式发布设计后才会参与下一次静态构建。

${state.entries.filter(e=>e.type==='page').slice(0,8).map(e=>`▤

${esc(e.title)}

${esc(e.route)}

›
`).join('')||'
尚无页面,请先添加内容。
'}
提示:网站设置用于公司信息和域名;可视化编辑器用于调整页面外观。两者不会混在同一表单里。
`; } function imports(){if(!currentSite())return noSite();return htmlHeading('导入与迁移','根据您已有的资料选择操作方式:Excel 产品、WordPress 网站,或者外部 API / 数据库。')+ - `
${overviewShortcut('import','▤','导入 Excel / CSV','批量增加产品、车型和新闻数据。')}⇄

迁移 WordPress 旧站

迁移会话、文章采集、图片与 SEO 检查。

打开迁移工作台 ↗
⌘

API / XML / MySQL

连接外部业务系统,进行字段映射和分批导入。

进入高级导入 ↗
`+ - `

快速导入文件

适合简单的 Excel、CSV、JSON、WordPress XML 文件。

上传数据文件

已有网站会保留;重复页面路径默认跳过。复杂迁移请使用上方的专业迁移工作台先预检。

支持 CSV、Excel、JSON、WordPress XML;单文件最多 15 MB。

`+ + `
▤

导入 Excel / CSV

推荐先预览字段、检查冲突,再确认提交。

安全导入与批次回滚 →
⇄

迁移 WordPress 旧站

迁移会话、文章采集、图片与 SEO 检查。

打开迁移工作台 ↗
⌘

API / XML / MySQL

连接外部业务系统,进行字段映射和分批导入。

进入高级导入 ↗
`+ + `

推荐:先预演再导入

在数据导入工作台检查字段匹配、页面路径冲突及品牌分类关系,再分批提交。

打开安全导入工作台 →
高级:直接写入网站(不可批次回滚)

仅用于确认内容无误的小文件,建议先备份;复杂迁移请使用上方工作台。

支持 CSV、Excel、JSON、WordPress XML;单文件最多 15 MB。

`+ `
迁移旧网站请注意:如果需要保留旧 URL、原有 SEO、复杂插件字段和全部图片,请优先使用 WordPress 迁移工作台,而不是仅上传一个内容文件。
`; } -function builds(){if(!currentSite())return noSite();const latest=latestDone();return htmlHeading('构建与交付','将当前网站数据生成为独立的 HTML 文件。先预览和检查,再决定是否发布。',actionButton('↗ 开始构建','build-now'))+ - `
${latest?'上次构建已完成,可以继续预览':'您还没有完成网站构建'}

内容或设计有更新时,需要重新构建才能生成新版本。

构建任务与发布审批 ↗
`+ - `

构建版本

下载的 ZIP 是独立静态网站,可交给运维部署。

${esc(state.job||'')}
`+ - (state.builds.length?state.builds.slice(0,20).map(b=>`
${['done','completed','success','complete'].includes(b.status)?'✓':'◷'}

${esc(fmtDate(b.created_at))} · ${esc(b.total_pages||'—')} 个页面

版本 ${esc(b.id.slice(0,12))} · 状态 ${esc(b.status||'未知')}

`).join(''):emptyState('尚无构建版本','网站内容和设计准备好后,点击“开始构建”生成独立网站文件。',actionButton('开始构建','build-now')))+`
`; +function builds(){if(!currentSite())return noSite();const latest=latestDone();const pending=activeJobs();const first=pending[0]; + return htmlHeading('构建与交付','网站构建由独立 Worker 执行。离开页面或刷新后仍可查看任务状态。',``)+ + `
${first?'当前:'+buildStatusLabel(first.state):latest?'已有构建版本,可以预览':'还没有已完成的网站构建'}

${first?'当前任务 '+esc(first.id.slice(0,12))+';Worker 在后台执行,您可以关闭此页面。':'内容或设计有更新时,重新生成网站版本。'}

任务与发布审批 ↗
`+ + `

后台任务

可以查看任务状态。仅取消当前仍在队列或运行中的构建。

${esc(state.job||'')}
`+ + (state.jobs.length?state.jobs.slice(0,8).map(j=>`
${j.state==='done'?'✓':j.state==='failed'?'!':'◷'}

${esc(buildStatusLabel(j.state))}

${esc(fmtDate(j.created_at))} · 任务 ${esc(j.id.slice(0,12))}${j.state==='failed'&&j.error?' · '+esc(j.error):''}

${['queued','running','retrying'].includes(j.state)?``:''}
`).join(''):'

当前网站还没有后台构建任务。

')+`
`+ + `

构建版本

建议先预览和核对质量报告,再下载 ZIP。预览不代表正式发布。

`+ + (state.builds.length?state.builds.slice(0,20).map(b=>`
${['done','completed','success','complete'].includes(b.status)?'✓':'◷'}

${esc(fmtDate(b.created_at))} · ${esc(b.total_pages||'—')} 个页面

版本 ${esc(b.id.slice(0,12))} · ${esc(b.status||'未知')}

${['done','completed','success','complete'].includes(b.status)?`预览 ↗下载 ZIP`:'尚未完成,暂不能下载'}
`).join(''):emptyState('尚无完成版本','准备好网站数据后,在上方提交构建任务。'))+ + `
`; } function settings(){if(!currentSite())return noSite();const s=state.site;return htmlHeading('网站设置','这里只管理公司资料、域名与站点属性。网页样式请到“外观与设计”修改。')+ `

基本资料

没有域名也可以制作与预览网站,正式上线前再填写即可。

`; @@ -83,8 +96,18 @@ function rerender(){const area=$('#mainContent');const selected=state.view;$('#b area.innerHTML=({home,sites,overview,content,design,import:imports,build:builds,settings}[selected]||home)(); } async function loadSites(){state.sites=await api('/api/sites');const picker=$('#sitePicker'),prev=state.site?.id||'';picker.innerHTML=''+state.sites.map(s=>``).join('');picker.value=state.sites.some(s=>s.id===prev)?prev:''} -async function selectSite(id,go='overview'){if(!id){state.site=null;state.entries=[];state.builds=[];state.view='sites';$('#sitePicker').value='';rerender();return}try{const [site,entries,builds]=await Promise.all([api(`/api/sites/${encodeURIComponent(id)}`),api(`/api/sites/${encodeURIComponent(id)}/entries`),api(`/api/sites/${encodeURIComponent(id)}/builds`)]);state.site=site;state.entries=entries;state.builds=builds;state.view=go;state.search='';state.typ='';$('#sitePicker').value=id;rerender();window.scrollTo({top:0,behavior:'instant'})}catch(e){notifyError(e)}} -function navigate(to){if(!state.site&&!['home','sites'].includes(to)){state.view='sites';rerender();toast('请先选择网站项目');return}state.view=to;rerender();closeMobile();window.scrollTo({top:0,behavior:'instant'})} +async function readJobs(siteId){try{return await api(`/api/enterprise/sites/${encodeURIComponent(siteId)}/jobs`)}catch(e){if(![403,404].includes(Number(e.status)))console.warn('加载任务状态失败',e);return []}} +async function selectSite(id,go='overview',historyMode='push'){ + const token=++state.selectionToken; + if(!id){state.site=null;state.entries=[];state.builds=[];state.jobs=[];state.job=null;state.view=go==='home'?'home':'sites';$('#sitePicker').value='';rerender();if(historyMode!=='none')rememberView(historyMode==='replace');return} + if(!state.sites.some(x=>x.id===id)){toast('该网站不在您的可见列表中',true);return} + try{const [site,entries,builds,jobs]=await Promise.all([api(`/api/sites/${encodeURIComponent(id)}`),api(`/api/sites/${encodeURIComponent(id)}/entries`),api(`/api/sites/${encodeURIComponent(id)}/builds`),readJobs(id)]); + if(token!==state.selectionToken)return; + state.site=site;sfRemember(id);state.entries=entries;state.builds=builds;state.jobs=jobs;state.job=null;state.view=go;state.search='';state.typ='';$('#sitePicker').value=id;rerender();if(historyMode!=='none')rememberView(historyMode==='replace');closeMobile();window.scrollTo({top:0,behavior:'instant'}); + }catch(e){if(token===state.selectionToken)notifyError(e)} +} +function navigate(to,historyMode='push'){if(!state.site&&!['home','sites'].includes(to)){state.view='sites';rerender();toast('请先选择网站项目');return}state.view=to;rerender();closeMobile();if(historyMode!=='none')rememberView(historyMode==='replace');if(to==='build')refreshBuilds();window.scrollTo({top:0,behavior:'instant'})} +async function refreshBuilds(){const id=state.site?.id;if(!id)return;try{const previouslyRunning=activeJobs().length>0;const [jobs,builds]=await Promise.all([readJobs(id),api(`/api/sites/${encodeURIComponent(id)}/builds`)]);if(state.site?.id!==id)return;state.jobs=jobs;state.builds=builds;if(previouslyRunning&&!activeJobs().length){const last=jobs[0];state.job=last?.state==='done'?'后台构建已完成,请预览并查看质量报告。':last?.state==='failed'?'本次构建失败,请在任务中心检查原因。':last?.state==='cancelled'?'本次构建已取消。':null}if(state.view==='build')rerender()}catch(e){notifyError(e)}} function openModal(name){const node=$('#'+name);node.showModal();node.querySelector('input:not([type=hidden]),select')?.focus()} function closeModal(name){$('#'+name)?.close()} function openEntry(id){const form=$('#entryForm');form.reset();form.elements.entry_id.value='';$('#entryModalTitle').textContent=id?'修改内容':'添加内容';if(id){const e=state.entries.find(x=>x.id===id);if(!e)return toast('这条内容不存在',true);for(const field of ['title','route','summary','body','seo_title','seo_description','type','status']){if(form.elements[field])form.elements[field].value=e[field]||''}form.elements.entry_id.value=id}openModal('entryModal')} @@ -96,20 +119,42 @@ async function setTheme(theme){if(!state.site||theme===state.site.theme)return;i async function saveSettings(ev){ev.preventDefault();try{await api(`/api/sites/${state.site.id}`,'PATCH',Object.fromEntries(new FormData(ev.currentTarget)));toast('网站设置已保存');await loadSites();await selectSite(state.site.id,'settings')}catch(e){notifyError(e)}} async function importFile(ev){ev.preventDefault();try{if(!state.site)return;const form=new FormData(ev.currentTarget);if(!form.get('file')?.name)throw Error('请选择文件');if(!confirm('将文件内容直接写入当前网站,重复路径默认跳过。需要预演和批次回滚请先使用高级导入。确认继续?'))return;const result=await api(`/api/sites/${state.site.id}/import`,'POST',form);const box=$('#importSummary');box.hidden=false;box.textContent=`导入完成:新增 ${result.imported||0} 条,更新 ${result.updated||0} 条,跳过 ${result.skipped||0} 条。错误 ${result.errors?.length||0} 条。`;toast('导入已完成');await refreshSiteKeepView()}catch(e){notifyError(e)}} async function refreshSiteKeepView(){if(!state.site)return;const [site,entries,builds]=await Promise.all([api('/api/sites/'+state.site.id),api('/api/sites/'+state.site.id+'/entries'),api('/api/sites/'+state.site.id+'/builds')]);state.site=site;state.entries=entries;state.builds=builds} -async function buildNow(){if(!state.site)return toast('请先选择网站',true);const id=state.site.id;try{navigate('build');state.job='正在提交网站构建任务…';rerender();const job=await api(`/api/sites/${id}/builds`,'POST',{mirror_images:false},{'Idempotency-Key':crypto.randomUUID()});state.job=`网站已进入构建队列(任务 ${job.id}),关闭页面也可以继续运行。`;rerender();for(let i=0;i<120;i++){await new Promise(resolve=>setTimeout(resolve,1600));if(state.site?.id!==id||state.view!=='build')break;const status=await api(`/api/enterprise/sites/${id}/jobs/${job.id}`);if(['done','failed','cancelled'].includes(status.state)){state.job=status.state==='done'?'网站构建已完成,可以预览和下载。':`构建${status.state==='cancelled'?'已取消':'失败'}:${status.error||'请在任务中心查看详情'}`;await refreshSiteKeepView();rerender();toast(state.job,status.state!=='done');break}if(i%5===0){state.job=`Worker 正在构建(${status.state||'等待中'})…`;$('#jobNotice')?.replaceChildren(document.createTextNode(state.job))}}}catch(e){notifyError(e)}} +async function cancelJob(jobId){if(!state.site)return;if(!confirm('确定取消这个构建任务?已经完成的静态版本不会被删除。'))return;try{const id=state.site.id;await api(`/api/enterprise/sites/${encodeURIComponent(id)}/jobs/${encodeURIComponent(jobId)}/cancel`,'POST');toast('已发送取消请求');await refreshBuilds()}catch(e){notifyError(e)}} +async function buildNow(){if(!state.site)return toast('请先选择网站',true);if(state.building||activeJobs().length)return toast('已有构建在执行,请先查看任务状态'); + const id=state.site.id;state.building=true; + try{navigate('build');state.job='正在提交构建任务…';rerender();const job=await api(`/api/sites/${encodeURIComponent(id)}/builds`,'POST',{mirror_images:false},{'Idempotency-Key':crypto.randomUUID()}); + if(state.site?.id!==id)return;state.job=`任务 ${job.id?.slice(0,12)||'已提交'} 已进入队列,可以离开页面;重新打开“构建与交付”查看结果。`;toast('已提交构建任务');await refreshBuilds(); + }catch(e){notifyError(e)}finally{state.building=false;if(state.site?.id===id&&state.view==='build')rerender()}} +async function checkReadiness(){if(!state.site)return;const siteId=state.site.id; + try{const result=await api(`/api/factory/sites/${encodeURIComponent(siteId)}/migration/readiness`); + if(state.site?.id!==siteId)return; + const box=$('#reportContent');if(!box)return; + const gates=result.gates||[]; + box.innerHTML=`

正式交付前检查

检查旧站 URL、SEO 和静态文件风险;通过检查不等于已经上线。

${result.can_publish?'没有发现发布阻断项':'存在需要先处理的问题'}

${gates.length?'':'

当前没有需要处理的检查项。仍需人工确认内容、域名与询盘表单。

'}
进入迁移复核与发布审批 →
`; + box.scrollIntoView({behavior:'smooth',block:'nearest'}); + }catch(e){notifyError(e)} +} async function showReport(buildId){try{const r=await api(`/api/sites/${state.site.id}/builds/${buildId}/report`);const target=$('#reportContent');if(!target)return;target.innerHTML=`

质量检查结果

本报告检查静态内容与资源,不保证搜索引擎排名。

${stat('HTML 页面',r.pages||0,'▤')}${stat('重定向',r.redirects||0,'⇄')}${stat('已本地化图片',r.mirrored_images||0,'▧')}${stat('提示数量',(r.warnings||[]).length,'!')}
${r.warnings?.length?`
${r.warnings.slice(0,20).map(x=>esc(x)).join('\n')}
`:'当前检查没有警告'}
`;target.scrollIntoView({behavior:'smooth',block:'nearest'})}catch(e){notifyError(e)}} function closeMobile(){$('#sideMenu').classList.remove('open');$('#mobileBackdrop').classList.remove('open');$('#menuToggle').setAttribute('aria-expanded','false')} async function init(){try{const me=await api('/api/auth/me');state.csrf=me.csrf;await loadSites();const health=await api('/api/health');$('#appVersion').textContent='版本 '+health.version; - // Opening the home screen intentionally does not auto-select a site. - navigate('home')}catch(e){notifyError(e);$('#mainContent').innerHTML=emptyState('后台加载失败',e.message,'')}} + // Restore URL first, then a previously selected site *only* after verifying /api/sites. + const params=new URLSearchParams(location.search),requested=params.get('site'); + const target=params.get('view');const allowed=new Set(['home','sites','overview','content','design','import','build','settings']); + let stored='';try{stored=localStorage.getItem(sfSiteKey)||''}catch{} + const chosen=[requested,stored].find(id=>id&&state.sites.some(x=>x.id===id)); + if(chosen){await selectSite(chosen,allowed.has(target)?target:'overview','replace')} + else {state.view=target==='sites'?'sites':'home';rerender();rememberView(true)} + }catch(e){notifyError(e);$('#mainContent').innerHTML=emptyState('后台加载失败',e.message,'')}} // Delegated UI interactions keep navigation and form actions centrally auditable. -document.addEventListener('click',async ev=>{const control=ev.target.closest('[data-action]');if(!control)return;const action=control.dataset.action;try{if(action==='navigate')navigate(control.dataset.view);if(action==='new-site')openModal('siteModal');if(action==='select-site')await selectSite(control.dataset.id);if(action==='new-entry')openEntry();if(action==='edit-entry')openEntry(control.dataset.id);if(action==='set-theme')await setTheme(control.dataset.theme);if(action==='build-now')await buildNow();if(action==='report')await showReport(control.dataset.id)}catch(e){notifyError(e)}}); +document.addEventListener('click',async ev=>{const control=ev.target.closest('[data-action]');if(!control)return;const action=control.dataset.action;try{if(action==='navigate')navigate(control.dataset.view);if(action==='new-site')openModal('siteModal');if(action==='select-site')await selectSite(control.dataset.id);if(action==='new-entry')openEntry();if(action==='edit-entry')openEntry(control.dataset.id);if(action==='set-theme')await setTheme(control.dataset.theme);if(action==='build-now')await buildNow();if(action==='refresh-builds')await refreshBuilds();if(action==='check-readiness')await checkReadiness();if(action==='cancel-job')await cancelJob(control.dataset.id);if(action==='report')await showReport(control.dataset.id)}catch(e){notifyError(e)}}); document.addEventListener('click',ev=>{const btn=ev.target.closest('[data-close]');if(btn)closeModal(btn.dataset.close)}); $('#siteForm').addEventListener('submit',saveSite);$('#entryForm').addEventListener('submit',saveEntry); document.addEventListener('submit',ev=>{if(ev.target.id==='settingsForm')saveSettings(ev);if(ev.target.id==='importForm')importFile(ev)}); document.addEventListener('input',ev=>{if(ev.target.id==='contentSearch'){state.search=ev.target.value;const cursor=ev.target.selectionStart;const type=state.typ;rerender();const el=$('#contentSearch');el.focus();el.setSelectionRange(cursor,cursor);state.typ=type}}); document.addEventListener('change',ev=>{if(ev.target.id==='contentType'){state.typ=ev.target.value;rerender()}}); $('#sitePicker').addEventListener('change',ev=>selectSite(ev.target.value)); +window.addEventListener('popstate',async()=>{const p=new URLSearchParams(location.search);const site=p.get('site'),view=p.get('view')||'home';const allowed=['home','sites','overview','content','design','import','build','settings'];if(site&&state.sites.some(s=>s.id===site))await selectSite(site,allowed.includes(view)?view:'overview','none');else{state.selectionToken++;state.site=null;state.entries=[];state.jobs=[];state.builds=[];state.view=view==='sites'?'sites':'home';rerender()}}); +setInterval(()=>{if(!document.hidden&&state.view==='build'&&activeJobs().length)refreshBuilds()},5000); $('#newSiteTop').addEventListener('click',()=>openModal('siteModal')); $('#logoutBtn').addEventListener('click',async()=>{try{await api('/api/auth/logout','POST');location.href='/login'}catch(e){notifyError(e)}}); $('#menuToggle').addEventListener('click',()=>{const open=$('#sideMenu').classList.toggle('open');$('#mobileBackdrop').classList.toggle('open',open);$('#menuToggle').setAttribute('aria-expanded',String(open))}); diff --git a/app/templates/editor.html b/app/templates/editor.html index 19f0932..d2f84c7 100644 --- a/app/templates/editor.html +++ b/app/templates/editor.html @@ -1,5 +1,5 @@ -SiteForge Studio · 可视化建站 - +SiteForge Studio · 可视化建站 +
◈
SiteForgeVISUAL STUDIO
diff --git a/app/templates/factory-v2.html b/app/templates/factory-v2.html index eebfc7b..58ff9f7 100644 --- a/app/templates/factory-v2.html +++ b/app/templates/factory-v2.html @@ -1,5 +1,5 @@ -SiteForge · 高级建站中心 -
+SiteForge · 高级建站中心 +