upgrade: adopt V8.31.1 (theme 1.3/1.4 redesigns, editor deep-canvas, guided launch, component library)
Overlay 190 upstream items (V8.22→8.31) preserving trade-pro theme, registry tests, and design docs. Re-applied full patch set on identical anchors (discovery/env_for/blocks.css/type-mapping/locale-override/ theme_color/frontend entries/test_theme_pro_v3 mods) plus compose pins. Verified: theme suites 35/35; dual-baseline full run 415 passed vs pristine 386+29 ours, FAILED-set diff EMPTY (same 10 Windows env fails). Live: container 8.31.1-dev.1 healthy, hesupipe 192 entries intact, trade-pro build 203 pages 0 errors. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -28,14 +28,14 @@ jobs:
|
||||
SITEFORGE_SESSION_SECRET: ci-only-session-key-not-for-production-with-entropy
|
||||
SITEFORGE_PG_PASSWORD: ci-only-test-postgresql-password
|
||||
run: |
|
||||
SITEFORGE_REGRESSION_REPEAT=2 bash scripts/check.sh
|
||||
bash scripts/check.sh
|
||||
python scripts/http_end_to_end.py
|
||||
- name: Upload V8.21 regression evidence
|
||||
- name: Upload V8.23 regression evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: v821-regression-${{ matrix.python }}
|
||||
path: reports/v821-regression*
|
||||
name: v823-regression-${{ matrix.python }}
|
||||
path: reports/v823-regression*
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
browser-smoke:
|
||||
@@ -58,6 +58,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p reports/browser
|
||||
python scripts/browser_smoke.py reports/browser
|
||||
python scripts/studio_nested_browser_v823.py
|
||||
- name: Upload browser screenshots
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
Files ./.github/workflows/ci.yml and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/.github/workflows/ci.yml differ
|
||||
Files ./.gitignore and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/.gitignore differ
|
||||
Files ./README.md and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/README.md differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1: README_V8_26.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1: README_V8_28.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1: README_V8_29.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1: README_V8_30.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1: README_V8_31.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1: README_V8_31_1.md
|
||||
Only in .: SHA256SUMS.txt
|
||||
Files ./app/blueprints.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/blueprints.py differ
|
||||
Files ./app/builder.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/builder.py differ
|
||||
Files ./app/core.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/core.py differ
|
||||
Files ./app/factory_api.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/factory_api.py differ
|
||||
Files ./app/main.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/main.py differ
|
||||
Files ./app/multilingual.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/multilingual.py differ
|
||||
Files ./app/reusables.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/reusables.py differ
|
||||
Files ./app/site_design_v87.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/site_design_v87.py differ
|
||||
Files ./app/static/admin.js and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/admin.js differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static: auto31.js
|
||||
Files ./app/static/editor-helpers.js and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/editor-helpers.js differ
|
||||
Files ./app/static/editor.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/editor.css differ
|
||||
Files ./app/static/editor.js and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/editor.js differ
|
||||
Files ./app/static/factory-v2.js and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/factory-v2.js differ
|
||||
Files ./app/static/pro.js and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/pro.js differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static: studio-canvas-bridge.js
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static: studio-experience-v829.js
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static: workspace-guide.js
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static: workspace-launch.js
|
||||
Files ./app/static/workspace.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/workspace.css differ
|
||||
Files ./app/static/workspace.js and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/static/workspace.js differ
|
||||
Files ./app/templates/admin.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/templates/admin.html differ
|
||||
Files ./app/templates/editor.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/templates/editor.html differ
|
||||
Files ./app/templates/theme-gallery.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/templates/theme-gallery.html differ
|
||||
Files ./app/templates/workspace.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/templates/workspace.html differ
|
||||
Files ./app/theme_pro.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/theme_pro.py differ
|
||||
Files ./app/theme_store.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/theme_store.py differ
|
||||
Files ./app/v8_workflows.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/v8_workflows.py differ
|
||||
Files ./app/version.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/app/version.py differ
|
||||
Files ./compose.enterprise.yaml and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/compose.enterprise.yaml differ
|
||||
Files ./compose.yaml and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/compose.yaml differ
|
||||
Only in ./docs: COMPLETION_PLAN.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/docs: V8_22_ADVANCED_VISUAL_STUDIO.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/docs: V8_23_ADVANCED_STUDIO.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/docs: V8_24_DEEP_NESTED_STUDIO.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/docs: V8_25_SHARED_DESIGN_SYSTEM.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/docs: V8_26_GLOBAL_DESIGN.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/docs: V8_27_USER_FRIENDLY_WORKSPACE.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/docs: V8_28_GUIDED_LAUNCH_CENTER.md
|
||||
Only in ./docs: superpowers
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: Studio_V8_22_画布级可视化编辑器_交互演示.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: Studio_V8_23_嵌套组件编辑器_可交互预览.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: Studio_V8_24_深层嵌套可视化编辑器_交互演示.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_25_公共组件_版本与同步演示.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_26_全站设计与导航_交互预览.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_27_新手友好建站工作台_交互演示.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_28_简洁上线操作中心_交互演示.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_29_主题与可视化编辑_离线操作演示.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_30_四套主题_真实页面交互预览.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_31_1_汽车主题性能与语义化图标_离线演示.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: V8_31_汽车主题_完整页面交互预览.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/examples: v830-built-sites
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_22_验收报告.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_22_验收报告.json
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_22_验收报告.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_23_集成验收.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_25_summary.json
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_25_验收报告.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_25_验收报告.md
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_30_mobile_browser_audit.json
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_30_主题设计与兼容验收.html
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: V8_31_汽车主题验收报告.html
|
||||
Only in ./reports: mobile-v8213-browser-console.log
|
||||
Only in ./reports: v8213-final-regression_logs
|
||||
Only in ./reports: v8213-regression_logs
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v823-regression
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v824-regression
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v825-regression
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v826
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v826-regression
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v827
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v828
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v829
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v829-browser
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v829-regression
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v830-regression
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v831
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v8311
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v8311-regression
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v8311-regression-part1.log
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/reports: v8311_regression_run.log
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: browser_guide_v827.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: browser_launch_v828.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: browser_studio_v829.py
|
||||
Files ./scripts/check.ps1 and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts/check.ps1 differ
|
||||
Files ./scripts/check.sh and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts/check.sh differ
|
||||
Files ./scripts/init_config.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts/init_config.py differ
|
||||
Files ./scripts/install_into_art_design_pro.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts/install_into_art_design_pro.py differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: make_theme_demo_v830.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: make_theme_demo_v8311.py
|
||||
Files ./scripts/regression_v821.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts/regression_v821.py differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v823.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v824.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v825.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v826.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v827.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v828.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v829.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v830.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: regression_v8311.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: report_v825.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: report_v828.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: studio_canvas_isolated_browser_v822.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: studio_canvas_smoke_v822.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: studio_nested_browser_v823.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts: studio_nested_browser_v824.py
|
||||
Files ./scripts/verify_art_design_pro_v819.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts/verify_art_design_pro_v819.py differ
|
||||
Files ./scripts/verify_release.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/scripts/verify_release.py differ
|
||||
Files ./tests/test_core_v4.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests/test_core_v4.py differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_deep_nested_v824.py
|
||||
Files ./tests/test_editor_easy_v8212.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests/test_editor_easy_v8212.py differ
|
||||
Files ./tests/test_factory_v2.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests/test_factory_v2.py differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_nested_helpers_v823.cjs
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_nested_v823.py
|
||||
Files ./tests/test_theme_pro_v3.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests/test_theme_pro_v3.py differ
|
||||
Only in ./tests: test_theme_registry.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v822_canvas.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v825_shared_components.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v826_global_design.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v827_guided_workspace.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v828_launch_center.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v829_theme_studio.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v830_real_pages.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v8311_performance_icons.py
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests: test_v831_automotive_ref.py
|
||||
Files ./tests/test_workspace_v810.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests/test_workspace_v810.py differ
|
||||
Files ./tests/test_workspace_v812.py and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests/test_workspace_v812.py differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests_js: deep_nested_v824.test.cjs
|
||||
Files ./tests_js/editor_ux.test.cjs and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests_js/editor_ux.test.cjs differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests_js: studio-experience-v829.test.cjs
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests_js: workspace_guide_v827.test.cjs
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/tests_js: workspace_launch_v828.test.cjs
|
||||
Files ./themes/_professional_components.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/_professional_components.html differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive: auto-icons.svg
|
||||
Files ./themes/automotive/manifest.json and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/manifest.json differ
|
||||
Files ./themes/automotive/pages/about.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/about.html differ
|
||||
Files ./themes/automotive/pages/article.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/article.html differ
|
||||
Files ./themes/automotive/pages/brand.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/brand.html differ
|
||||
Files ./themes/automotive/pages/case.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/case.html differ
|
||||
Files ./themes/automotive/pages/category.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/category.html differ
|
||||
Files ./themes/automotive/pages/contact.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/contact.html differ
|
||||
Files ./themes/automotive/pages/faq.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/faq.html differ
|
||||
Files ./themes/automotive/pages/home.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/home.html differ
|
||||
Files ./themes/automotive/pages/listing.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/listing.html differ
|
||||
Files ./themes/automotive/pages/notfound.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/notfound.html differ
|
||||
Files ./themes/automotive/pages/product.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/product.html differ
|
||||
Files ./themes/automotive/pages/service.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/service.html differ
|
||||
Files ./themes/automotive/pages/standard.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pages/standard.html differ
|
||||
Files ./themes/automotive/pro.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pro.css differ
|
||||
Files ./themes/automotive/pro.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/pro.html differ
|
||||
Files ./themes/automotive/theme.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/theme.css differ
|
||||
Files ./themes/automotive/versions/1.0.0/pro.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/versions/1.0.0/pro.html differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/versions: 1.1.0
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/versions: 1.2.0
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/versions: 1.3.0
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/automotive/versions: 1.4.0
|
||||
Files ./themes/corporate/manifest.json and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/manifest.json differ
|
||||
Files ./themes/corporate/pages/about.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/pages/about.html differ
|
||||
Files ./themes/corporate/pages/article.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/pages/article.html differ
|
||||
Files ./themes/corporate/pages/contact.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/pages/contact.html differ
|
||||
Files ./themes/corporate/pages/home.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/pages/home.html differ
|
||||
Files ./themes/corporate/pages/listing.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/pages/listing.html differ
|
||||
Files ./themes/corporate/pages/product.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/pages/product.html differ
|
||||
Files ./themes/corporate/pro.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/pro.css differ
|
||||
Files ./themes/corporate/theme.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/theme.css differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/versions: 1.1.0
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/corporate/versions: 1.2.0
|
||||
Files ./themes/industrial/manifest.json and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/manifest.json differ
|
||||
Files ./themes/industrial/pages/about.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/pages/about.html differ
|
||||
Files ./themes/industrial/pages/article.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/pages/article.html differ
|
||||
Files ./themes/industrial/pages/contact.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/pages/contact.html differ
|
||||
Files ./themes/industrial/pages/home.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/pages/home.html differ
|
||||
Files ./themes/industrial/pages/listing.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/pages/listing.html differ
|
||||
Files ./themes/industrial/pages/product.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/pages/product.html differ
|
||||
Files ./themes/industrial/pro.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/pro.css differ
|
||||
Files ./themes/industrial/theme.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/theme.css differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/versions: 1.1.0
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/industrial/versions: 1.2.0
|
||||
Files ./themes/minimal/manifest.json and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/manifest.json differ
|
||||
Files ./themes/minimal/pages/about.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/pages/about.html differ
|
||||
Files ./themes/minimal/pages/article.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/pages/article.html differ
|
||||
Files ./themes/minimal/pages/contact.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/pages/contact.html differ
|
||||
Files ./themes/minimal/pages/home.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/pages/home.html differ
|
||||
Files ./themes/minimal/pages/listing.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/pages/listing.html differ
|
||||
Files ./themes/minimal/pages/product.html and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/pages/product.html differ
|
||||
Files ./themes/minimal/pro.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/pro.css differ
|
||||
Files ./themes/minimal/theme.css and C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/theme.css differ
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/versions: 1.1.0
|
||||
Only in C:/Users/30997/Downloads/SiteForge_Factory_V5_Web_Docker/SiteForge_Enterprise_V8_31_1/themes/minimal/versions: 1.2.0
|
||||
Only in ./themes: trade-pro
|
||||
@@ -1,3 +1,139 @@
|
||||
# SiteForge Enterprise V8.31.0-dev.1
|
||||
|
||||
本轮汽车主题按 [Magic Bridge Auto](https://magicbridgeauto.com/) 的导航及选车体验进行行业化重设计,完整说明见 [`README_V8_31.md`](README_V8_31.md)。保留汽车旧主题快照及其他三套主题。
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.30.0-dev.1
|
||||
|
||||
最新交付说明:[`README_V8_30.md`](README_V8_30.md)。
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.29.0-dev.1 — 主题与可视化编辑器体验优化
|
||||
|
||||
新功能及测试见 [README_V8_29.md](README_V8_29.md)。此版本以 V8.28 完整源码为基线,保留其全部业务能力。
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.28.0-dev.1 — 引导式上线中心
|
||||
|
||||
> V8.27 全量源码基础上的集成增量;保留原四套主题、移动端、WordPress、公共组件、编辑器和 Worker。上线向导不跳过任何服务端审批、质量或发布检查。
|
||||
|
||||
# SiteForge Enterprise V8.27.0-dev.1 — 简洁后台与新手建站向导
|
||||
|
||||
> 当前状态:**开发集成版,不能据此认定生产上线验收完成。** 在 V8.26 完整源码上新增三步建站创建、按实际内容/任务记录显示的上线操作指引、精简网站概览和手机布局适配;保留 V8.26 公共组件、全站设计、WordPress 迁移和发布能力。
|
||||
>
|
||||
> 详细说明:`docs/V8_27_USER_FRIENDLY_WORKSPACE.md`。本轮隔离回归按模块 379 项通过,但一次性/组合分批检查有超时与旧断言迁移;真实 Art Design Pro 编译、PostgreSQL/Redis 及生产发布仍待验收。
|
||||
|
||||
## 快速体验
|
||||
|
||||
- 独立交互演示:`examples/V8_27_新手友好建站工作台_交互演示.html`(模拟网络,只展示流程)
|
||||
- 源码后台入口:`/` → 「创建网站」或选中网站后「建站操作指引」
|
||||
- 本轮截图与测试报告:`reports/v827/`
|
||||
|
||||
## 快速验证
|
||||
|
||||
```bash
|
||||
python -m pytest -q tests/test_v827_guided_workspace.py
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/browser_guide_v827.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.26.0-dev.1
|
||||
|
||||
最新版说明请先阅读 [README_V8_26.md](README_V8_26.md)。以下为历史版本说明。
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.25.0-dev.1 — 公共组件与全站设计协作基础
|
||||
|
||||
> **交付状态:开发集成版。** 本版基于 V8.24 完整源码,在现有编辑器加入公共组件版本快照、关联插入、引用页预检、选择性同步到草稿及历史恢复。**不会自动改已发布网站**,需要按原有草稿审核、构建与部署流程完成上线。
|
||||
|
||||
## V8.25 本轮功能
|
||||
|
||||
- 组件库“复制插入” / “关联插入”两种选择;关联插入在区块内记录版本与内容指纹。
|
||||
- 不可变源组件版本快照、更新版本乐观锁、查看历史与恢复为新版本;已有组件自动补“当前版本”快照。
|
||||
- 在一个站点内查看关联引用页面,识别本地修改、已是最新版及发布冲突。
|
||||
- 选择性同步到一个或多个页面**新草稿**:完整预检校验、批次事务、失败整批回滚、独立历史审计,不更改现有在线内容。
|
||||
- 属性栏“解除关联”保留当前区块,避免以后参与公共组件同步。
|
||||
- 四套内置主题、移动端抽屉与底部 Tabs、两层嵌套编辑器、WordPress 迁移、SEO、Worker 和发布接口保持兼容。
|
||||
|
||||
[离线交互演示](examples/V8_25_公共组件_版本与同步演示.html) · [完整功能与安全说明](docs/V8_25_SHARED_DESIGN_SYSTEM.md) · [本轮验收报告](reports/V8_25_验收报告.html)
|
||||
|
||||
```bash
|
||||
python -m pytest -q tests/test_v825_shared_components.py
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/regression_v825.py --timeout 55 --group-size 2
|
||||
```
|
||||
|
||||
**验收限制:** 生产 PostgreSQL/Redis、多 Worker、远程发布与回滚、官方 Art Design Pro 工程编译以及已登录后台的真实浏览器验收未完成;隔离 Chromium 仅验证离线交互演示。
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.24.0-dev.1 — 深层画布编辑与复用组件体验
|
||||
|
||||
> **交付状态:开发集成版**。在 V8.23 完整项目上继续开发;原数据库结构、四套主题、移动端抽屉菜单、底部 Tabs 与静态发布机制保持兼容。本版本没有执行生产环境部署。
|
||||
|
||||
## 本轮新增
|
||||
|
||||
1. **第二层可视化编辑**:在双层布局中点击组件,直接编辑、复制、上下排序、删除,并以层级路径显示所选内容。
|
||||
2. **直接创建内层内容**:可添加内层布局及第二层内容组件,不需要手工填写 JSON。
|
||||
3. **独立响应式布局**:为内层布局设置桌面列宽、列间距、手机 1/2 列、平板 1/2/3 列。
|
||||
4. **图片选取更加准确**:本站素材库、上传图片应用到当前支持图片的组件,而非误写外层布局。
|
||||
5. **组件库更好用**:可保存当前选中的内层组件,插入组件副本;跨页面自动联动仍未实现。
|
||||
6. **验收工具与演示**:新增深层操作 Python/Node 测试、HTTP+Worker 测试、隔离 Chromium 演示、独立分组回归入口。
|
||||
|
||||
- [打开离线交互预览](examples/Studio_V8_24_深层嵌套可视化编辑器_交互演示.html)
|
||||
- [V8.24 技术说明](docs/V8_24_DEEP_NESTED_STUDIO.md)
|
||||
- [回归机器可读结果](reports/v824-regression/result.json)
|
||||
|
||||
```bash
|
||||
bash scripts/check.sh
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/studio_nested_browser_v824.py
|
||||
```
|
||||
|
||||
**限制:** 当前只支持既有两层 layout 深度;组件库为复制副本而非跨站点实时同步。生产 PostgreSQL/Redis、官方 Art Design Pro 编译和登录后的真实浏览器 E2E 均未完成验收。
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.23.0-dev.1 — 嵌套组件与响应式可视化编辑
|
||||
|
||||
> **交付状态:开发集成版。** 基于 **V8.22.0-dev.1 完整源码**继续开发;未覆盖原版本或任何正式网站的数据。此次更新保持原有 HTML 构建、SEO、四套主题、抽屉导航及可选底部 Tabs 的数据兼容性。
|
||||
|
||||
## V8.23 本轮完成
|
||||
|
||||
1. **列内组件画布直接操作**:支持在多列布局中选中直接子组件、复制、删除、同列上下移动;标题/简介(适用组件)可双击直接修改。使用父布局 ID + 子组件 ID 的白名单校验,不接收任意执行命令。
|
||||
2. **直观的嵌套属性编辑**:选中列内组件后,右侧“内容”面板展开对应的标题、文字、图片路径、链接等普通输入表单,不再强制在“响应式”面板翻找组件。
|
||||
3. **桌面布局快捷预设**:等宽两列、左宽右窄、左窄右宽;列间距 0–64px。平板/手机各自的列数、跨列、顺序、显隐继续独立配置。
|
||||
4. **草稿与历史版本兼容**:仍沿用 schema 白名单、原有版本快照、乐观锁、草稿保存和发布审批流程。嵌套组件操作不执行线上构建或发布。
|
||||
5. **回归与离线演示**:新增 Node 安全操作测试、嵌套接口测试和真实 Chromium 沙箱交互测试,另提供无需服务器的交互演示。
|
||||
|
||||
## 快速验证
|
||||
|
||||
```bash
|
||||
bash scripts/check.sh
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/studio_nested_browser_v823.py
|
||||
# 单独测试 Node 嵌套组件操作安全性
|
||||
node tests/test_nested_helpers_v823.cjs
|
||||
```
|
||||
|
||||
## 查看与使用
|
||||
|
||||
- 后台入口:`/editor/{site_id}/{entry_id}` → 在页面中添加“多列自由布局” → 添加列内组件 → 画布上点选或双击文字 → 右侧编辑 → 保存草稿。
|
||||
- [离线交互演示](examples/Studio_V8_23_嵌套组件编辑器_可交互预览.html)(仅演示数据,保存按钮不修改后端)。
|
||||
- [开发与验收报告](docs/V8_23_ADVANCED_STUDIO.md);[分批回归日志](reports/v823-regression/result.json)。
|
||||
|
||||
**仍未完成**:任意深度的子节点直接编辑、公共组件跨页面联动、真正自由定位的布局、Art Design Pro 官方工程编译、生产 PostgreSQL/Redis/Docker 实测以及真实后台 Chromium localhost 端到端验收。已测试的是多列布局直接子组件和受限沙箱交互,不应描述为完整自由设计器或生产可上线。
|
||||
|
||||
---
|
||||
|
||||
# SiteForge Enterprise V8.21.3 — 四套内置主题手机端统一优化
|
||||
|
||||
> **状态:开发集成版**。从 V8.21.2 完整源码升级;原站点与数据未覆盖。四套内置主题统一支持抽屉菜单,可选的 1–5 项手机底栏 Tabs 默认关闭。正式生产的域名部署、Apple/Android 真机与 Art Design Pro 官方构建仍需独立验收。
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# SiteForge Enterprise V8.26.0-dev.1 — 全站设计与公共组件维护
|
||||
|
||||
**交付等级:开发集成版,非生产正式版。** 在 V8.25 全量源码上增量开发,保留已有的 WordPress、四套主题、移动端抽屉导航、可选 Tabs、公共组件版本/同步、草稿审批及静态构建。
|
||||
|
||||
## 这次实际增加了什么
|
||||
|
||||
- **更容易找到**:编辑器左侧增加“编辑全站菜单、页眉与页脚”入口。
|
||||
- **可视化管理全站导航**:编辑名称、站内路径,创建最多三级菜单,添加、上下排序、删除;不必手写 JSON。所有改动仍通过原来的导航结构白名单与路径安全校验。
|
||||
- **发布差异审核**:保存草稿后检查相对于当前发布设置的字段差异,检查页眉、导航、页脚、设计令牌、手机 Tabs 与响应式设置。用户确认后调用带审核令牌的独立发布接口;修改草稿会使旧审核令牌失效。
|
||||
- **全站发布历史与安全恢复**:查看历史快照,将发布前或发布后的版本恢复为新草稿;恢复带当前正式配置哈希/草稿版本双重防护,绝不直接改线上网站。
|
||||
- **继续保留安全边界**:仅站点 Owner 可执行审核发布;Editor 可修改/恢复草稿;Viewer 只读;数据和历史完全按 site_id 隔离。
|
||||
|
||||
## 用户操作(正式系统)
|
||||
|
||||
启动 SiteForge 后打开一个网站的页面编辑器 → 左侧点击“编辑全站菜单、页眉与页脚” → 调整导航与页眉、页脚、手机 Tabs → 保存全站草稿 → 点击“审核差异并发布” → 检查变更清单后确认。正式网页仍需重新构建、发布审批和远端部署。
|
||||
|
||||
## 测试命令
|
||||
|
||||
```bash
|
||||
python -m pytest -q tests/test_v826_global_design.py tests/test_site_design_v87.py
|
||||
node --check app/static/editor.js
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/regression_v826.py --timeout 55 --group-size 2
|
||||
python scripts/verify_release.py
|
||||
```
|
||||
|
||||
详见 `docs/V8_26_GLOBAL_DESIGN.md`、`reports/v826/V8_26_集成验收.html` 与 `examples/V8_26_全站设计与导航_交互预览.html`。离线演示使用模拟数据,不会连接正式管理后台。
|
||||
|
||||
## 尚未完成的正式验收
|
||||
|
||||
官方 Art Design Pro 完整工程编译;正式后台登录后的真实 Chromium E2E;PostgreSQL/Redis 多 Worker、灾备恢复;真实 SFTP/S3/Agent 正式发布、CDN 和生产安全审计,均不应标为已通过。
|
||||
@@ -0,0 +1,40 @@
|
||||
# SiteForge Enterprise V8.28.0-dev.1
|
||||
|
||||
## 本轮内容
|
||||
|
||||
V8.27 基线之上新增“检查并上线”中心,使用者按六步完成网站版本选择、人工预览、提交独立审批、发布前检查、服务器部署和真实网站核对。
|
||||
|
||||
- 修复旧侧边导航按钮无法响应点击的问题。
|
||||
- 规范“生成与预览”与“检查并上线”的导航分工,原高级发布操作仍可通过折叠入口访问。
|
||||
- 严格区分审批待处理/已通过、发布检查通过/部署队列、部署记录/公网实际验收。
|
||||
- 当前版本未批准、未人工核对、目标未配置、检查结果不匹配时,禁止从新手向导提交部署;后端继续执行自身权限与质量校验。
|
||||
- 更换网站、构建版本或目标环境后不复用旧检查状态,避免重复审批及快速重复部署。
|
||||
- 手机端表单与操作步骤适配,支持离线交互演示。
|
||||
|
||||
## 文件入口
|
||||
|
||||
- `examples/V8_28_简洁上线操作中心_交互演示.html`:直接打开;演示是模拟数据,绝不操作真实服务器。
|
||||
- `docs/V8_28_GUIDED_LAUNCH_CENTER.md`:功能说明、使用步骤、后端安全约束及验证边界。
|
||||
- `reports/v828/V8_28_集成验收报告.html`:验收汇总及 24 批详细结果。
|
||||
- `reports/v828-regression/`:初次运行的原始日志;第 22 批旧菜单断言更新后的复测记录单独保留。
|
||||
|
||||
## 验收结果(开发隔离环境)
|
||||
|
||||
- 原项目 Python 48 模块,最终验证 **383 项通过**;24 批中初次 23 批通过,1 批旧断言修复后 13 项复测通过,未关闭失败为 0。
|
||||
- JavaScript 单元测试 **35 项通过**。
|
||||
- HTTP + Worker 实际建站、静态构建、ZIP 与预览检查通过。
|
||||
- 真 Chromium 原工作台 JS + 模拟 API,独立审批状态、预检、部署任务与 390px 手机布局通过。
|
||||
- Art Design Pro 官方工程实际编译,真实 PostgreSQL/Redis 多 Worker,公网发布与回滚、灾备演练仍**未验收**,不能视为生产正式版。
|
||||
|
||||
## 快速执行
|
||||
|
||||
```bash
|
||||
node --test tests_js/*.test.cjs
|
||||
python -m pytest -q tests/test_v828_launch_center.py tests/test_v827_guided_workspace.py
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/browser_launch_v828.py
|
||||
python scripts/verify_release.py
|
||||
python scripts/regression_v828.py --group-size 2 --timeout 25
|
||||
```
|
||||
|
||||
不要将模拟浏览器环境“部署成功”的提示理解为真实服务器已经部署。当前真实发布仍必须由两个不同有权限用户独立审批,通过发布前置条件校验,并由 Worker 触发远程部署。
|
||||
@@ -0,0 +1,45 @@
|
||||
# SiteForge Enterprise V8.29.0-dev.1 — 主题与编辑器体验优化
|
||||
|
||||
**开发基线**:V8.28.0-dev.1 完整源码。此次保留原有后台、静态建站、WordPress 迁移、发布审批、四套移动端抽屉菜单与可选 Tabs、两层组件编辑、公共组件及历史版本。
|
||||
|
||||
## 这次有哪些实质变化?
|
||||
|
||||
1. 四套内置主题(汽车、工业、企业、简约)升级至 `1.2.0`:优化移动端留白、标题缩放、图片卡片比例、焦点反馈、动作幅度和小屏阅读。不会改变旧站 URL、SEO 数据或已发布内容。
|
||||
2. 每套主题的原版 `1.1.0` 已保存到 `themes/<theme>/versions/1.1.0/`。**已固定 1.1.0 的网站仍能用相应旧主题构建**;升级到 1.2.0 需要明确应用新主题。
|
||||
3. 编辑器新增「主题效果」:可比较四套主题 × 六种示例页面 × 三种设备宽度;仅用于比较最新主题示例,不会改动当前页面、主题锁定版本或正式站点。手机端可通过「更多 → 比较四套主题效果」进入。
|
||||
4. 左侧「添加内容」改成按用途分类并搜索(常用内容 / 图片与展示 / 产品与业务 / 复杂布局),减少从一长串区块中查找的成本。
|
||||
5. 右侧「样式」增加清爽白底、柔和浅底、重点深底三项快捷方案;基于现有受控的 `background`、`text_color` 和 `padding` 字段,支持撤销/保存草稿。
|
||||
6. 顶部补充撤销、重做可见快捷入口;手机顶栏两行排布,修复 390px 宽度下「更多」按钮被挤出视口的问题。
|
||||
|
||||
## 用户操作路径
|
||||
|
||||
- 修改当前页面:进入编辑器 → 点击画布区块 → 右侧修改内容或快速外观 → 查看电脑/平板/手机 → 保存草稿 → 发布设计。
|
||||
- 对比主题:点击「主题效果」→ 选择主题、页面类型与设备 → 确认后在网站管理中正式应用(预览不会替你更换主题)。
|
||||
- 发布到正式网站:发布设计并不等于线上部署;仍需静态构建、审批、发布前检查、正式部署与公网核验。
|
||||
|
||||
## 执行检查
|
||||
|
||||
```bash
|
||||
python -m compileall -q app
|
||||
python scripts/verify_release.py
|
||||
python scripts/http_end_to_end.py
|
||||
node --test tests_js/*.test.cjs
|
||||
node tests/test_nested_helpers_v823.cjs
|
||||
python -m pytest -q tests/test_v829_theme_studio.py
|
||||
python scripts/regression_v829.py --timeout 45 --group-size 2
|
||||
# 隔离浏览器:使用真实 HTML/JS/CSS + 测试替身,不会操作真实用户数据
|
||||
python scripts/browser_studio_v829.py
|
||||
```
|
||||
|
||||
**浏览器验收范围**:真实 Chromium 测试使用编辑器本身的 DOM/CSS/JS,模拟 API 数据;能够验证组件搜索、主题弹窗打开/切换、外观预设、撤销、390px 手机布局,但不等于已登录正式服务器后的浏览器端到端验收。受当前环境管理员策略限制,本轮不能完整访问 localhost / 测试域名进行真实登录浏览器测试。另使用真实 FastAPI + Worker 做了 HTTP/ZIP/预览的独立集成验收。
|
||||
|
||||
**生产验收仍未关闭**:Art Design Pro 官方完整项目编译、真实 PostgreSQL/Redis、多 Worker 与远程发布/恢复需在对应实际环境测试;此次不宣称生产级完成。
|
||||
|
||||
- 原项目完整回归:**25 批,387 个 Python 用例通过,0 失败批次**(记录在 `reports/v829-regression/`)。
|
||||
- Node.js 全部已配置测试:**39 项通过**(包含 4 项本轮新测试)。
|
||||
- HTTP+Worker 隔离 E2E:通过。
|
||||
- Chromium 隔离交互:桌面与 390px 手机通过,无脚本异常;本机 HTTP 直接访问由于浏览器策略受限,未通过该方式验收。
|
||||
|
||||
## 版本及兼容说明
|
||||
|
||||
现有 SQLite/PostgreSQL 业务 Schema、草稿结构、接口权限与构建/发布动作未改变。主题 v1.2.0 属于显式版本升级,用户可继续固定 v1.1.0;手动迁移主题后仍应逐页检查自定义内容、图片、移动端样式与 SEO。旧版本目录不得在部署升级时直接删除。
|
||||
@@ -0,0 +1,48 @@
|
||||
# SiteForge Enterprise V8.30.0-dev.1 — 四套主题真实页面设计升级
|
||||
|
||||
> 基于 V8.29 完整源码开发;本轮仅优化内置主题、内页布局、旧版本存档和预览/验收,不改变数据库结构,不重写用户 URL/SEO/媒体数据。
|
||||
|
||||
## 这轮真正改了什么
|
||||
|
||||
四套主题各自升级为 `1.3.0`。这次不只是配色变化:
|
||||
|
||||
| 主题 | 首页 | 列表 | 详情 | 文章 | 关于 | 联系 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 汽车与出行 | 深色宽幅车型视觉、精选产品 | 深色目录开场与车型筛选 | 图片优先、车系标签与参数 | 深蓝编辑式头图 | 企业叙事、真实服务/案例 | 业务咨询、真实邮箱或受控表单 |
|
||||
| 工业建材 | 类别和产品体系 | 浅蓝技术目录 | 产品图、技术参数侧栏 | 技术文档式头部 | 产品与项目能力介绍 | 材料/工程需求咨询 |
|
||||
| 企业商务 | 专业品牌叙事 | 业务目录、留白 | 信息和视觉分层 | 新闻/洞察阅读 | 企业故事与已发布业务 | 简洁商务联系 |
|
||||
| 极简展示 | 大字排版、精选作品 | 编辑式目录 | 极简图片与描述 | 杂志式文章 | 品牌故事与作品 | 极简联系卡 |
|
||||
|
||||
**内容真实性:** 可自动显示的数据,只来自真实已发布内容。没有真实案例、服务、参数或联系方式时,不会冒充存在;示例站点使用明确标注的虚构内容。
|
||||
|
||||
### 历史版本安全
|
||||
|
||||
- V8.29 的 `1.2.0` 四套主题已完整保存到 `themes/<theme>/versions/1.2.0/`,旧 `1.1.0/1.0.0/0.3.0` 保持不变。
|
||||
- 固定在旧主题版本的站点继续构建旧布局,不会因为程序升级而自动换成 v1.3.0。
|
||||
- 新主题必须在网站后台主动选择与确认。
|
||||
- 原有页面 URL、Canonical、SEO 与移动端抽屉导航、可选 Tabs 功能不变。
|
||||
|
||||
### 页面预览
|
||||
|
||||
打开 `examples/V8_30_四套主题_真实页面交互预览.html`,可以切换 **4 套主题 × 6 类真实静态页面 × 电脑/平板/手机**。内置演示数据,不会连接或修改用户站点;其中的视觉图片是演示占位符,正式使用时请添加真实产品与工程照片。
|
||||
|
||||
### 开发与检查
|
||||
|
||||
```bash
|
||||
python scripts/verify_release.py
|
||||
python -m pytest -q tests/test_v830_real_pages.py tests/test_theme_pro_v3.py tests/test_v829_theme_studio.py
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/http_end_to_end.py
|
||||
# 可分批继续,避免历史长流程在某些环境中被中断
|
||||
python scripts/regression_v830.py --start 1 --end 25 --group-size 2 --timeout 40
|
||||
# 在隔离环境执行,切勿使用生产 SITEFORGE_DATA_DIR
|
||||
PYTHONPATH=. SITEFORGE_DATA_DIR=/tmp/v830-demo SITEFORGE_DEMO_OUT=/tmp/v830-sites python scripts/make_theme_demo_v830.py
|
||||
```
|
||||
|
||||
### 本轮实际结果
|
||||
|
||||
- **25 个独立 Python 批次,390 项用例通过;0 个失败批次。** 明细在 `reports/v830-regression/`。
|
||||
- JavaScript 39 项通过;HTTP 登录/CSRF/建站/Worker 构建/ZIP/预览通过。
|
||||
- 每套主题各生成 36 个静态页面,4 套共 144 个;质量检查 **0 错误**。
|
||||
- 真实 Chromium:4 主题 × 6 页面 × 375/390/430px = 72 组合,**0 横向溢出、0 脚本错误**。详情在 `reports/V8_30_mobile_browser_audit.json`。
|
||||
- 未进行真实 iOS/Android 实机验证、Art Design Pro 官方编译、PostgreSQL/Redis 生产环境、正式域名上线或大型真实企业图片素材验收;本版为开发集成版,不可将本地测试宣称为生产验收。
|
||||
@@ -0,0 +1,53 @@
|
||||
# SiteForge Enterprise V8.31.0-dev.1 — 汽车主题行业化重设计
|
||||
|
||||
## 目标与范围
|
||||
|
||||
依据获授权参考网站 https://magicbridgeauto.com/ 的汽车出口内容组织方式,将汽车主题从偏概念视觉,调整为以客户选车、品牌筛选、车型参数和询盘为中心的商用网站。**只参考信息架构、导航交互与视觉层次,不抓取或复制参考站的文案、车型数据、图片及 Logo。**
|
||||
|
||||
本次是 SiteForge **完整源码**中的汽车主题升级,不是另起一个独立 HTML。主题发布版本为 `automotive@1.4.0`,平台版本 `8.31.0-dev.1`。工业、企业及极简三套主题继续保留 `1.3.0`。
|
||||
|
||||
## 用户直接感知到的变化
|
||||
|
||||
- **导航更清晰**:固定醒目的“首页、产品、品牌、资讯、案例、关于、联系”,品牌/分类大菜单使用实际数据;在手机端仍通过右侧抽屉进入。
|
||||
- **首页更聚焦选车**:横幅主信息、车型快速搜索、品牌入口、精选车型、选车/出口服务、公司介绍、资讯案例和询盘 CTA。
|
||||
- **车型列表可快速查找**:`?q=` 搜索参数支持静态页面本地搜索,与已有筛选器协同工作;没有使用必须联网的动态查询服务。
|
||||
- **品牌独立页**:品牌介绍、品牌车型和相关内容分层展示;品牌菜单仅展示网站内真实存在的品牌项。
|
||||
- **车型详情**:车型主图、缩略图图库、型号标签、参数和联系入口分区;真实图片与规格来自站点数据。
|
||||
- **关于、联系等内页**:沿用 V8.30 已优化的业务内容区域,整体视觉匹配新汽车风格。
|
||||
- **移动端**:宽幅内容转单列,车型卡片使用两列,继续保留抽屉菜单、可选底部 Tabs。按钮、点击区域和标题间距优化。
|
||||
|
||||
## 重要兼容性修复
|
||||
|
||||
已有站点可能将汽车主题版本固定为 `1.3.0` 甚至更早版本。在 V8.30 中,此类站点可能仅使用旧版 CSS、却错误包含最新页面片段。本轮修复模板解析:**优先加载固定版本存档里的页面模板**。`themes/automotive/versions/1.0.0`、`1.1.0`、`1.2.0`、`1.3.0` 均保留,旧站不会被强制升级。已固定旧版站点需由用户明确切换至 `1.4.0` 后才会采用新页面。
|
||||
|
||||
## 实际体验
|
||||
|
||||
- 独立预览:`examples/V8_31_汽车主题_完整页面交互预览.html`(支持七种页面类型、电脑/平板/手机切换)
|
||||
- 真正由 SiteForge 静态生成的页面视觉截图:`reports/v831/auto-home-desktop.png`、`auto-home-mobile.png`、`auto-product-desktop.png`
|
||||
- 可视化验收:`reports/V8_31_汽车主题验收报告.html`
|
||||
|
||||
> 演示数据为虚构;未提供有效车辆图片时,保留主题占位区,避免冒用参考网站的车辆照片。实际网站应在内容管理中上传真实车辆图片、品牌资料、商品规格、询盘联系方式,再检查上线效果。
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
python -m pytest -q tests/test_v831_automotive_ref.py tests/test_v830_real_pages.py tests/test_theme_pro_v3.py tests/test_v829_theme_studio.py
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/verify_release.py
|
||||
# 分批历史回归,避免长时间同进程测试相互干扰
|
||||
python scripts/regression_v829.py --group-size 2 --timeout 35
|
||||
```
|
||||
|
||||
可选演示构建:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=. SITEFORGE_DATA_DIR=/tmp/siteforge-v831-demo SITEFORGE_DEMO_OUT=/tmp/siteforge-v831-static \
|
||||
python scripts/make_theme_demo_v830.py
|
||||
```
|
||||
|
||||
已进行四主题样站静态构建、汽车六类页面在 375/390/430/768/1440px 宽度下的 Chromium 检测,并对品牌大菜单、车型过滤、手机抽屉执行交互检查。完整数据请查看 HTML 验收报告与 `reports/v831/browser_results.json`。
|
||||
|
||||
## 仍需由真实业务决定的内容
|
||||
|
||||
此版本不等于对参考网站数据的逐条复制,也不等于取得了全部原站品牌/车型资料。若需要与 magicbridgeauto.com 业务数据 **1:1** 一致,应通过站点数据迁移系统额外导出并核对菜单层级、车型图片、规格及品牌关系。正式 Art Design Pro 完整工程编译、生产 PostgreSQL/Redis、远程上线和真实设备浏览器验收仍需独立完成。
|
||||
@@ -0,0 +1,11 @@
|
||||
# V8.31.1 汽车主题:速度与语义化图标
|
||||
|
||||
- **汽车主题** `1.4.1`,SiteForge `8.31.1-dev.1`。升级前自动存档汽车 `1.4.0`;固定旧主题的网站不会被升级。
|
||||
- 首页、导航、品牌目录、车型列表/详情、新闻和咨询入口采用自托管 SVG 图标集,避免大量同样的箭头。仅分页与方向操作保留箭头。图标配文字,不以图标单独表达含义。
|
||||
- 仅 `automotive@1.4.1` 静态输出:原 7 个 CSS 请求合并为 `assets/auto-compiled.css`,原 3 个 JS 请求合并为 `assets/auto-compiled.js`;顺序与旧版保持一致。额外有可浏览器缓存的 `assets/auto-icons.svg`。
|
||||
- 首页车型大图设置高优先级并异步解码;车型详情主图不再错误地懒加载;卡片和画廊缩略图保留按需加载。
|
||||
- 无外部 SVG 字体/CDN 引用;主题关键内容结构、路由与移动端抽屉和底部 Tabs 保留。
|
||||
- CSS 原文件与 JS 原文件仍存在于 ZIP,方便调试和版本兼容;页面仅引用合并资源。不承诺比 1.4.0 更小的源代码总量,重点改善请求数与首屏资源优先级。
|
||||
- 上线前仍应优化真实车型图片(AVIF/WebP、按宽度提供 `srcset`)、正确缓存/压缩头、CDN 回源;这些依赖真实服务器配置与内容素材,不能由本地模拟测试证明生产提速。
|
||||
|
||||
验证:`python -m pytest -q tests/test_v8311_performance_icons.py`; `python scripts/http_end_to_end.py`; `node --test tests_js/*.test.cjs`。
|
||||
+6
-1
@@ -104,9 +104,14 @@ def validate_blocks(blocks):
|
||||
if not re.fullmatch('[a-zA-Z0-9_-]{4,50}',ident) or ident in found:raise ValueError('区块 ID 缺失或重复')
|
||||
found.add(ident)
|
||||
spec=BLOCKS[block['type']]
|
||||
allowed=set(spec['fields'])|{'id','type','visible','style'}
|
||||
allowed=set(spec['fields'])|{'id','type','visible','style','shared_ref'}
|
||||
normalized={key:val for key,val in block.items() if key in allowed}
|
||||
normalized['id']=ident
|
||||
if 'shared_ref' in normalized:
|
||||
ref=normalized['shared_ref']
|
||||
if not isinstance(ref,dict) or set(ref)!={'id','version','hash'} or not isinstance(ref['id'],str) or not re.fullmatch(r'[a-zA-Z0-9_-]{8,64}',ref['id']) or type(ref['version']) is not int or not 1<=ref['version']<=1000000 or not isinstance(ref['hash'],str) or not re.fullmatch('[a-f0-9]{64}',ref['hash']):
|
||||
raise ValueError('无效的公共组件引用')
|
||||
normalized['shared_ref']={'id':ref['id'],'version':ref['version'],'hash':ref['hash']}
|
||||
if 'style' in normalized:
|
||||
style=normalized['style']
|
||||
if not isinstance(style,dict):raise ValueError('样式必须为对象')
|
||||
|
||||
@@ -204,6 +204,7 @@ def build(site_id,mirror=False):
|
||||
if visuals.is_dir():
|
||||
shutil.copytree(visuals,dist/'assets'/'visual',dirs_exist_ok=True)
|
||||
shutil.copy2(ROOT/'app'/'static'/'pro.js',dist/'assets'/'pro.js')
|
||||
shutil.copy2(ROOT/'app'/'static'/'auto31.js',dist/'assets'/'auto31.js')
|
||||
if (theme_dir/'pro.css').exists():
|
||||
shutil.copy2(theme_dir/'pro.css',dist/'assets'/'pro.css')
|
||||
else:
|
||||
@@ -226,6 +227,7 @@ def build(site_id,mirror=False):
|
||||
f'.btn,.primary,.request,.button,.nav-btn{{background-color:{accent};border-radius:{button}}}'
|
||||
f'body .sf-canvas h1{{font-size:clamp(2.5rem,5vw,calc(4.8rem * var(--sf-heading-scale)))}}')
|
||||
(dist/'assets'/'custom.css').write_text(theme_css+responsive.site_css(records)+site_design_v87.responsive_css(site_design_v87.published(settings)),encoding='utf-8')
|
||||
|
||||
# For installed theme packages, Jinja loads only the trusted included template.
|
||||
professional_template=(theme_dir/'pro.html').exists()
|
||||
if professional_template and site['theme'] not in theme_pro.THEMES:
|
||||
@@ -393,6 +395,26 @@ def build(site_id,mirror=False):
|
||||
(dist/'404.html').write_text(missing_html,encoding='utf-8')
|
||||
# A complete static search page is packaged for every theme, not a live API.
|
||||
static_site.write_assets(dist,local_records)
|
||||
# V8.31.1: only the opt-in automotive theme uses the single-request asset
|
||||
# bundles. Older pinned theme versions retain their exact original HTML and
|
||||
# resource order, so existing sites are not silently changed.
|
||||
if site['theme']=='automotive' and (theme_dir/'manifest.json').is_file() and json.loads((theme_dir/'manifest.json').read_text(encoding='utf-8')).get('version')=='1.4.1':
|
||||
# Preserve CSS cascade by concatenating precisely in template order.
|
||||
css_names=('theme.css','blocks.css','factory.css','pro-base.css','pro.css','commercial.css','custom.css')
|
||||
for resource in css_names:
|
||||
if not (dist/'assets'/resource).is_file():
|
||||
raise ValueError('汽车资源缺失: '+resource)
|
||||
(dist/'assets'/'auto-compiled.css').write_text(
|
||||
'\n'.join((dist/'assets'/name).read_text(encoding='utf-8') for name in css_names),encoding='utf-8')
|
||||
js_names=('site-runtime.js','pro.js','auto31.js')
|
||||
for resource in js_names:
|
||||
if not (dist/'assets'/resource).is_file():
|
||||
raise ValueError('汽车脚本资源缺失: '+resource)
|
||||
(dist/'assets'/'auto-compiled.js').write_text(
|
||||
'\n;\n'.join((dist/'assets'/name).read_text(encoding='utf-8') for name in js_names),encoding='utf-8')
|
||||
shutil.copy2(theme_dir/'auto-icons.svg',dist/'assets'/'auto-icons.svg')
|
||||
|
||||
|
||||
if '/search/' not in route_set:
|
||||
search_file=dist/'search'/'index.html'
|
||||
search_file.parent.mkdir(parents=True,exist_ok=True)
|
||||
|
||||
@@ -277,6 +277,36 @@ def theme_preview_pro_css(theme_id:str,version:str=''):
|
||||
asset=folder/'pro.css'
|
||||
return Response(asset.read_text(encoding='utf-8') if asset.exists() else '',media_type='text/css')
|
||||
|
||||
@router.get('/themes/{theme_id}/preview.bundle.css')
|
||||
def preview_automotive_bundle_css(theme_id:str,version:str=''):
|
||||
"""Read-only editor preview: same cascade as the static automotive build."""
|
||||
from fastapi.responses import Response
|
||||
from . import static_site
|
||||
folder=theme_store.resolve(theme_id,version or None)
|
||||
if theme_id != 'automotive' or not (folder/'auto-icons.svg').is_file():
|
||||
raise HTTPException(404,'此主题没有优化资源包')
|
||||
names=[folder/'theme.css',static_site.ASSETS/'blocks.css',static_site.ASSETS/'factory.css',
|
||||
static_site.ASSETS/'pro-base.css',folder/'pro.css',static_site.ASSETS/'commercial.css']
|
||||
return Response('\n'.join(p.read_text(encoding='utf-8') for p in names), media_type='text/css')
|
||||
|
||||
@router.get('/themes/{theme_id}/preview.bundle.js')
|
||||
def preview_automotive_bundle_js(theme_id:str,version:str=''):
|
||||
from fastapi.responses import Response
|
||||
from . import static_site
|
||||
folder=theme_store.resolve(theme_id,version or None)
|
||||
if theme_id != 'automotive' or not (folder/'auto-icons.svg').is_file():
|
||||
raise HTTPException(404,'此主题没有优化资源包')
|
||||
names=[static_site.ASSETS/name for name in ('site-runtime.js','pro.js','auto31.js')]
|
||||
return Response('\n;\n'.join(p.read_text(encoding='utf-8') for p in names),media_type='application/javascript')
|
||||
|
||||
@router.get('/themes/{theme_id}/preview.icons.svg')
|
||||
def preview_automotive_icons(theme_id:str,version:str=''):
|
||||
from fastapi.responses import Response
|
||||
folder=theme_store.resolve(theme_id,version or None)
|
||||
if theme_id != 'automotive' or not (folder/'auto-icons.svg').is_file():
|
||||
raise HTTPException(404,'此主题没有此图标集')
|
||||
return Response((folder/'auto-icons.svg').read_bytes(),media_type='image/svg+xml',headers={'Cache-Control':'public,max-age=300'})
|
||||
|
||||
@router.get('/themes/{theme_id}/visual/hero.svg')
|
||||
def theme_preview_hero(theme_id:str,version:str=''):
|
||||
from fastapi.responses import Response
|
||||
@@ -319,6 +349,12 @@ def theme_preview(theme_id:str,version:str='',page_type:str='home'):
|
||||
context=theme_pro.make_context(site,page,records,'home' if page_type=='home' else ('notfound' if page_type=='notfound' else 'detail'),[])
|
||||
context.update(custom_blocks=Markup(''),has_custom=False,noindex=page_type=='notfound',theme_overrides=True)
|
||||
html=env.get_template('pro.html').render(**context)
|
||||
if theme_id=='automotive' and (directory/'auto-icons.svg').is_file():
|
||||
ver=version or theme_store.latest(theme_id)
|
||||
root=f'/api/factory/themes/automotive/preview'
|
||||
html=html.replace('assets/auto-compiled.css',f'{root}.bundle.css?version={ver}')
|
||||
html=html.replace('assets/auto-compiled.js',f'{root}.bundle.js?version={ver}')
|
||||
html=html.replace('assets/auto-icons.svg#',f'{root}.icons.svg?version={ver}#')
|
||||
html=html.replace('assets/pro-base.css','/ui/pro-base.css').replace('assets/pro.js','/ui/pro.js').replace('assets/commercial.css','/ui/commercial.css')
|
||||
html=html.replace('assets/pro.css',f'/api/factory/themes/{theme_id}/preview.pro.css?version={version or theme_store.latest(theme_id)}')
|
||||
html=html.replace('assets/theme-overrides.css',f'/api/factory/themes/{theme_id}/preview.css?version={version or theme_store.latest(theme_id)}')
|
||||
|
||||
+4
-28
@@ -544,34 +544,10 @@ def editor_preview(site_id:str,entry_id:str,payload:BlueprintPayload):
|
||||
html=_re.sub(r'<link\s+rel="stylesheet"[^>]*>', '', html)
|
||||
html=_re.sub(r'<script\s+defer\s+src="assets/[^"]+"[^>]*></script>', '', html)
|
||||
html=html.replace('</head>','<style>'+styles+'</style></head>')
|
||||
bridge = r'''<script>(function(){
|
||||
const fields={'title':'h1,h2','description':'.sf-hero-copy p,.sf-image-text p'};
|
||||
const sections=Array.from(document.querySelectorAll('[data-block]'));
|
||||
sections.forEach(block=>{
|
||||
block.setAttribute('data-studio-selectable','true');
|
||||
block.addEventListener('click',event=>{
|
||||
if(event.target.closest('a'))event.preventDefault();
|
||||
sections.forEach(item=>item.classList.remove('sf-editor-selected'));
|
||||
block.classList.add('sf-editor-selected');
|
||||
parent.postMessage({source:'siteforge-select-block',blockId:block.dataset.block},'*');
|
||||
});
|
||||
for(const [field,selector] of Object.entries(fields)){
|
||||
const target=block.querySelector(selector);if(!target)continue;
|
||||
target.title='双击直接修改文字';target.addEventListener('dblclick',()=>{
|
||||
target.contentEditable='plaintext-only';target.focus();
|
||||
target.style.outline='2px dashed #367bdd';
|
||||
});
|
||||
target.addEventListener('blur',()=>{
|
||||
if(!target.isContentEditable)return;
|
||||
target.removeAttribute('contenteditable');target.style.outline='';
|
||||
parent.postMessage({source:'siteforge-inline-editor',blockId:block.dataset.block,field:field,value:target.textContent},'*');
|
||||
});
|
||||
}
|
||||
});
|
||||
const style=document.createElement('style');
|
||||
style.textContent='[data-studio-selectable]:hover{outline:2px dashed #e98b45!important;outline-offset:-2px;cursor:pointer}[data-studio-selectable].sf-editor-selected{outline:2px solid #ea8138!important;outline-offset:-2px}';
|
||||
document.head.appendChild(style);
|
||||
})()</script>'''
|
||||
# External trusted bridge is injected only into the sandboxed editor preview;
|
||||
# production/static builds never include authoring controls.
|
||||
bridge_source=(ROOT/'app'/'static'/'studio-canvas-bridge.js').read_text(encoding='utf-8')
|
||||
bridge='<script>'+bridge_source+'</script>'
|
||||
# The static site's /media/ assets are hosted by the admin at
|
||||
# /local-media/{site}/. Rewrite only the editor response, never builds.
|
||||
html=html.replace('src="/media/',f'src="/local-media/{site_id}/')
|
||||
|
||||
+262
-23
@@ -1,11 +1,39 @@
|
||||
"""Site-scoped reusable editor component library with versioned snapshots."""
|
||||
"""Versioned, site-scoped reusable blocks with opt-in, draft-only propagation.
|
||||
|
||||
Published pages are NEVER rewritten by a shared-component update. A linked
|
||||
instance is a validated, materialized block with shared_ref metadata. Updates
|
||||
require a fresh preview token, exact page revision and an unmodified instance.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from fastapi import APIRouter,Body,HTTPException,Request
|
||||
from . import db,enterprise,blueprints
|
||||
import re
|
||||
import uuid
|
||||
from fastapi import APIRouter, Body, HTTPException, Request
|
||||
from . import db, enterprise, blueprints, studio_v86
|
||||
from .core import uid
|
||||
|
||||
router=APIRouter(prefix='/api/sites',tags=['Reusable Components'])
|
||||
|
||||
|
||||
def canonical(value):
|
||||
return json.dumps(value,ensure_ascii=False,sort_keys=True,separators=(',',':'))
|
||||
|
||||
|
||||
def _content(block):
|
||||
"""Content fingerprint independent of generated instance IDs and provenance."""
|
||||
def strip(item):
|
||||
if isinstance(item,dict):return {k:strip(v) for k,v in item.items() if k not in ('id','shared_ref')}
|
||||
if isinstance(item,list):return [strip(v) for v in item]
|
||||
return item
|
||||
return strip(block)
|
||||
|
||||
|
||||
def fingerprint(block):
|
||||
return hashlib.sha256(canonical(_content(block)).encode()).hexdigest()
|
||||
|
||||
|
||||
def setup():
|
||||
with db.connect() as c:
|
||||
c.executescript('''CREATE TABLE IF NOT EXISTS reusable_components(
|
||||
@@ -13,41 +41,252 @@ def setup():
|
||||
name TEXT NOT NULL,version INTEGER NOT NULL DEFAULT 1,block_json TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL);
|
||||
CREATE INDEX IF NOT EXISTS idx_reuse_site ON reusable_components(site_id);
|
||||
CREATE TABLE IF NOT EXISTS reusable_component_versions(
|
||||
component_id TEXT NOT NULL REFERENCES reusable_components(id) ON DELETE CASCADE,
|
||||
version INTEGER NOT NULL,block_json TEXT NOT NULL,snapshot_hash TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,PRIMARY KEY(component_id,version));
|
||||
''')
|
||||
# Safe additive migration of old version-1 reusable blocks.
|
||||
legacy=c.execute('SELECT id,version,block_json,updated_at FROM reusable_components').fetchall()
|
||||
for row in legacy:
|
||||
block=json.loads(row['block_json'])
|
||||
c.execute('''INSERT INTO reusable_component_versions
|
||||
(component_id,version,block_json,snapshot_hash,created_at)
|
||||
VALUES(?,?,?,?,?) ON CONFLICT(component_id,version) DO NOTHING''',
|
||||
(row['id'],row['version'],row['block_json'],fingerprint(block),row['updated_at']))
|
||||
|
||||
|
||||
def _component(site_id,component_id,c=None):
|
||||
sql='SELECT * FROM reusable_components WHERE site_id=? AND id=?'
|
||||
row=c.execute(sql,(site_id,component_id)).fetchone() if c else db.one(sql,(site_id,component_id))
|
||||
if not row:raise HTTPException(404,'本站组件不存在')
|
||||
return dict(row)
|
||||
|
||||
|
||||
def _validate_block(block):
|
||||
try:
|
||||
validated=blueprints.validate_blocks([block])[0]
|
||||
except (ValueError,TypeError) as exc:raise HTTPException(400,str(exc))
|
||||
# A library source is an independent snapshot. Embedded source links are
|
||||
# removed recursively; only insertion as a linked instance adds provenance.
|
||||
def detach(node):
|
||||
node.pop('shared_ref',None)
|
||||
if node.get('type')=='layout':
|
||||
for col in node.get('columns',[]):
|
||||
for sub in col.get('blocks',[]):detach(sub)
|
||||
detach(validated)
|
||||
return validated
|
||||
|
||||
|
||||
def _name(value):
|
||||
name=str(value or '').strip()
|
||||
if not name or len(name)>100:raise HTTPException(400,'组件名称需要1–100个字符')
|
||||
return name
|
||||
|
||||
|
||||
def versions(site_id,component_id):
|
||||
_component(site_id,component_id)
|
||||
rows=db.rows('''SELECT version,snapshot_hash,created_at FROM reusable_component_versions
|
||||
WHERE component_id=? ORDER BY version DESC''',(component_id,))
|
||||
return {'items':rows}
|
||||
|
||||
|
||||
@router.get('/{site_id}/components')
|
||||
def all_components(request:Request,site_id:str):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
return [{**r,'block':json.loads(r.pop('block_json'))} for r in db.rows(
|
||||
'SELECT * FROM reusable_components WHERE site_id=? ORDER BY updated_at DESC',(site_id,))]
|
||||
return [{**r,'block':json.loads(r['block_json']), 'snapshot_hash':fingerprint(json.loads(r['block_json']))}
|
||||
for r in db.rows('SELECT * FROM reusable_components WHERE site_id=? ORDER BY updated_at DESC',(site_id,))]
|
||||
|
||||
|
||||
@router.post('/{site_id}/components')
|
||||
def save_component(request:Request,site_id:str,payload:dict=Body(...)):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
name=str(payload.get('name','')).strip()[:100]
|
||||
if not name:raise HTTPException(400,'组件名称不能为空')
|
||||
try:block=blueprints.validate_blocks([payload.get('block')])[0]
|
||||
except (ValueError,TypeError) as exc:raise HTTPException(400,str(exc))
|
||||
cid=uid()
|
||||
db.insert('INSERT INTO reusable_components VALUES (?,?,?,?,?,?)',
|
||||
(cid,site_id,name,1,json.dumps(block,ensure_ascii=False),db.now()))
|
||||
return {'id':cid,'name':name,'version':1,'block':block}
|
||||
name=_name(payload.get('name'))
|
||||
block=_validate_block(payload.get('block'))
|
||||
# Saved library snapshots must not retain a reference to a different component.
|
||||
block.pop('shared_ref',None)
|
||||
cid=uid();now=db.now();encoded=canonical(block);sha=fingerprint(block)
|
||||
with db.connect() as c:
|
||||
c.execute('INSERT INTO reusable_components VALUES (?,?,?,?,?,?)',(cid,site_id,name,1,encoded,now))
|
||||
c.execute('INSERT INTO reusable_component_versions VALUES (?,?,?,?,?)',(cid,1,encoded,sha,now))
|
||||
return {'id':cid,'name':name,'version':1,'block':block,'snapshot_hash':sha}
|
||||
|
||||
|
||||
@router.put('/{site_id}/components/{component_id}')
|
||||
def update_component(request:Request,site_id:str,component_id:str,payload:dict=Body(...)):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
current=db.one('SELECT * FROM reusable_components WHERE site_id=? AND id=?',(site_id,component_id))
|
||||
if not current:raise HTTPException(404)
|
||||
if payload.get('expected_version')!=current['version']:raise HTTPException(409,'组件已被修改,请重新加载')
|
||||
try:block=blueprints.validate_blocks([payload.get('block')])[0]
|
||||
except (ValueError,TypeError) as exc:raise HTTPException(400,str(exc))
|
||||
db.execute('UPDATE reusable_components SET name=?,block_json=?,version=?,updated_at=? WHERE id=? AND site_id=?',
|
||||
(str(payload.get('name') or current['name'])[:100],json.dumps(block,ensure_ascii=False),current['version']+1,db.now(),component_id,site_id))
|
||||
return {'id':component_id,'version':current['version']+1}
|
||||
block=_validate_block(payload.get('block'));block.pop('shared_ref',None)
|
||||
with db.connect() as c:
|
||||
row=_component(site_id,component_id,c)
|
||||
rev=payload.get('expected_version')
|
||||
if type(rev) is not int or rev!=row['version']:raise HTTPException(409,'组件版本已改变,请重新加载')
|
||||
version=rev+1;name=_name(payload.get('name') if payload.get('name') is not None else row['name'])
|
||||
encoded=canonical(block);now=db.now()
|
||||
cur=c.execute('''UPDATE reusable_components SET name=?,block_json=?,version=?,updated_at=?
|
||||
WHERE id=? AND site_id=? AND version=?''',(name,encoded,version,now,component_id,site_id,rev))
|
||||
if cur.rowcount!=1:raise HTTPException(409,'组件已由其他用户修改')
|
||||
c.execute('INSERT INTO reusable_component_versions VALUES (?,?,?,?,?)',
|
||||
(component_id,version,encoded,fingerprint(block),now))
|
||||
return {'id':component_id,'version':version,'snapshot_hash':fingerprint(block),'published_unchanged':True}
|
||||
|
||||
|
||||
@router.delete('/{site_id}/components/{component_id}')
|
||||
def delete_component(request:Request,site_id:str,component_id:str):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
if not db.execute('DELETE FROM reusable_components WHERE site_id=? AND id=?',(site_id,component_id)):
|
||||
raise HTTPException(404)
|
||||
_component(site_id,component_id)
|
||||
# Reject deletion while linked instances exist; preserve historical versions.
|
||||
if _references(site_id,component_id):raise HTTPException(409,'组件仍被页面引用,请先解除引用')
|
||||
db.execute('DELETE FROM reusable_components WHERE site_id=? AND id=?',(site_id,component_id))
|
||||
return {'deleted':True}
|
||||
|
||||
|
||||
@router.get('/{site_id}/components/{component_id}/versions')
|
||||
def api_versions(request:Request,site_id:str,component_id:str):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
return versions(site_id,component_id)
|
||||
|
||||
|
||||
@router.get('/{site_id}/components/{component_id}/versions/{version}')
|
||||
def api_version(request:Request,site_id:str,component_id:str,version:int):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
_component(site_id,component_id)
|
||||
row=db.one('SELECT * FROM reusable_component_versions WHERE component_id=? AND version=?',(component_id,version))
|
||||
if not row:raise HTTPException(404,'版本不存在')
|
||||
block=json.loads(row['block_json'])
|
||||
if fingerprint(block)!=row['snapshot_hash']:raise HTTPException(409,'历史快照完整性校验失败')
|
||||
return {'version':version,'block':block,'snapshot_hash':row['snapshot_hash']}
|
||||
|
||||
|
||||
def _walk(rows):
|
||||
for block in rows:
|
||||
yield block
|
||||
if block.get('type')=='layout':
|
||||
for col in block.get('columns',[]):yield from _walk(col.get('blocks',[]))
|
||||
|
||||
|
||||
def _replace_in_tree(rows,block_id,replacement):
|
||||
for i,block in enumerate(rows):
|
||||
if block.get('id')==block_id:
|
||||
rows[i]=replacement;return True
|
||||
if block.get('type')=='layout':
|
||||
for col in block.get('columns',[]):
|
||||
if _replace_in_tree(col['blocks'],block_id,replacement):return True
|
||||
return False
|
||||
|
||||
|
||||
def _references(site_id,component_id):
|
||||
# Bounded scan; site ownership is enforced at query boundary.
|
||||
all_pages=db.rows('SELECT * FROM entries WHERE site_id=?',(site_id,))
|
||||
drafts={r['entry_id']:r for r in db.rows('SELECT * FROM studio_drafts WHERE site_id=?',(site_id,))}
|
||||
found=[]
|
||||
for page in all_pages:
|
||||
draft=drafts.get(page['id'])
|
||||
plan=json.loads(draft['blueprint_json']) if draft else json.loads(page['meta_json'] or '{}')
|
||||
for block in _walk(plan.get('blocks',[])):
|
||||
meta=block.get('shared_ref')
|
||||
if isinstance(meta,dict) and meta.get('id')==component_id:
|
||||
found.append({'entry_id':page['id'],'route':page['route'],'title':page['title'],
|
||||
'block_id':block['id'],'current_version':meta.get('version'),
|
||||
'local_modified':fingerprint(block)!=meta.get('hash'),
|
||||
'draft_revision':draft['revision'] if draft else 0,
|
||||
'base_hash':draft['base_hash'] if draft else studio_v86._hash(page),
|
||||
'has_draft':bool(draft),'conflict':bool(draft and draft['base_hash']!=studio_v86._hash(page))})
|
||||
return found
|
||||
|
||||
|
||||
def _preview(site_id,component_id):
|
||||
component=_component(site_id,component_id)
|
||||
refs=_references(site_id,component_id)
|
||||
for item in refs:
|
||||
item['up_to_date']=item['current_version']==component['version']
|
||||
item['eligible']=not item['local_modified'] and not item['conflict'] and not item['up_to_date']
|
||||
item['reason']=('存在页面发布冲突' if item['conflict'] else '页面有本地修改' if item['local_modified']
|
||||
else '已是最新版' if item['up_to_date'] else '')
|
||||
# Token includes all page revisions and instance fingerprints; replay of stale
|
||||
# preview after any page or component changes is rejected.
|
||||
proof={'component':component_id,'version':component['version'],'references':refs}
|
||||
token=hashlib.sha256(canonical(proof).encode()).hexdigest()
|
||||
return {'component':{'id':component_id,'name':component['name'],'version':component['version']},
|
||||
'references':refs,'plan_token':token,'warning':'同步仅写入新草稿,不自动发布或部署'}
|
||||
|
||||
|
||||
@router.get('/{site_id}/components/{component_id}/usage')
|
||||
def api_usage(request:Request,site_id:str,component_id:str):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
return _preview(site_id,component_id)
|
||||
|
||||
|
||||
def _copy_into(snapshot,original,component_id,version):
|
||||
block=copy.deepcopy(snapshot)
|
||||
def fresh(node,root=False):
|
||||
node['id']=original['id'] if root else uuid.uuid4().hex[:10]
|
||||
node.pop('shared_ref',None)
|
||||
if node.get('type')=='layout':
|
||||
for col in node.get('columns',[]):
|
||||
for sub in col.get('blocks',[]):fresh(sub)
|
||||
fresh(block,True)
|
||||
block['shared_ref']={'id':component_id,'version':version,'hash':fingerprint(block)}
|
||||
return block
|
||||
|
||||
|
||||
@router.post('/{site_id}/components/{component_id}/sync')
|
||||
def api_sync(request:Request,site_id:str,component_id:str,payload:dict=Body(...)):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
selected=payload.get('entry_ids')
|
||||
if not isinstance(selected,list) or not selected or len(selected)>100 or len(set(selected))!=len(selected) or any(not isinstance(s,str) for s in selected):
|
||||
raise HTTPException(400,'选择1–100个不同页面')
|
||||
preview=_preview(site_id,component_id)
|
||||
if payload.get('plan_token')!=preview['plan_token']:raise HTTPException(409,'预检已过期,请重新查看引用页面')
|
||||
chosen=set(selected)
|
||||
available={r['entry_id'] for r in preview['references'] if r['eligible']}
|
||||
if not chosen.issubset(available):raise HTTPException(409,'包含已修改、存在冲突或不可同步的页面')
|
||||
component=_component(site_id,component_id)
|
||||
current=blueprints.validate_blocks([json.loads(component['block_json'])])[0]
|
||||
# Restrict to exactly one linked instance per page per request for now.
|
||||
# When one page has multiple references, sync all eligible instances atomically.
|
||||
updated=[]
|
||||
expected_pages={r['entry_id']:(r['draft_revision'],r['base_hash']) for r in preview['references'] if r['entry_id'] in chosen}
|
||||
with db.connect() as c:
|
||||
# Lock the read/compare/write window. PostgreSQL uses an advisory
|
||||
# transaction lock through the compatibility adapter.
|
||||
c.execute('BEGIN IMMEDIATE')
|
||||
newest=_component(site_id,component_id,c)
|
||||
if newest['version']!=component['version']:
|
||||
raise HTTPException(409,'组件源版本发生变化')
|
||||
for entry_id in selected:
|
||||
page=c.execute('SELECT * FROM entries WHERE site_id=? AND id=?',(site_id,entry_id)).fetchone()
|
||||
if not page:raise HTTPException(409,'页面已删除,请重新预检')
|
||||
draft=c.execute('SELECT * FROM studio_drafts WHERE site_id=? AND entry_id=?',(site_id,entry_id)).fetchone()
|
||||
old_hash=studio_v86._hash(dict(page))
|
||||
expected_page_revision,expected_base=expected_pages[entry_id]
|
||||
if expected_base!=old_hash or (draft['revision'] if draft else 0)!=expected_page_revision:
|
||||
raise HTTPException(409,'页面版本已变化,重新预检后同步')
|
||||
if draft and draft['base_hash']!=old_hash:raise HTTPException(409,'页面内容已变化')
|
||||
plan=json.loads(draft['blueprint_json']) if draft else {
|
||||
'blocks':json.loads(page['meta_json'] or '{}').get('blocks',[]),
|
||||
'page_template':json.loads(page['meta_json'] or '{}').get('page_template','home' if page['route']=='/' else 'standard'),
|
||||
'layout_mode':json.loads(page['meta_json'] or '{}').get('layout_mode','append')}
|
||||
did=0
|
||||
for block in list(_walk(plan['blocks'])):
|
||||
ref=block.get('shared_ref')
|
||||
if isinstance(ref,dict) and ref.get('id')==component_id:
|
||||
if fingerprint(block)!=ref.get('hash') or ref.get('version')==component['version']:
|
||||
raise HTTPException(409,'引用发生变化,请重新预检')
|
||||
if not _replace_in_tree(plan['blocks'],block['id'],_copy_into(current,block,component_id,component['version'])):
|
||||
raise HTTPException(409,'组件位置发生变化')
|
||||
did+=1
|
||||
if not did:raise HTTPException(409,'页面已经没有组件引用')
|
||||
plan=studio_v86._validate(plan)
|
||||
expected=draft['revision'] if draft else 0
|
||||
rev=expected+1
|
||||
if draft:
|
||||
cur=c.execute('''UPDATE studio_drafts SET revision=?,blueprint_json=?,updated_at=?
|
||||
WHERE site_id=? AND entry_id=? AND revision=? AND base_hash=?''',
|
||||
(rev,canonical(plan),db.now(),site_id,entry_id,expected,draft['base_hash']))
|
||||
if cur.rowcount!=1:raise HTTPException(409,'草稿并发冲突')
|
||||
else:
|
||||
c.execute('INSERT INTO studio_drafts VALUES (?,?,?,?,?,?)',
|
||||
(site_id,entry_id,rev,old_hash,canonical(plan),db.now()))
|
||||
studio_v86._journal(c,site_id,entry_id,rev,'draft',plan,enterprise.actor(request))
|
||||
updated.append({'entry_id':entry_id,'revision':rev,'instances':did})
|
||||
return {'updated':updated,'published_unchanged':True,'build_required_after_publish':True}
|
||||
|
||||
+125
-1
@@ -198,9 +198,10 @@ def write(site_id,payload):
|
||||
(site_id,revision+1,base,json.dumps(plan,ensure_ascii=False),db.now()))
|
||||
return {'saved':True,'state':'draft','revision':revision+1,'build_unchanged':True}
|
||||
|
||||
def publish(site_id,expected_revision,actor):
|
||||
def publish(site_id,expected_revision,actor,expected_plan_token=None):
|
||||
site=_site(site_id)
|
||||
with db.connect() as c:
|
||||
c.execute('BEGIN IMMEDIATE')
|
||||
draft=c.execute('SELECT * FROM site_design_drafts WHERE site_id=?',(site_id,)).fetchone()
|
||||
if not draft:raise HTTPException(409,'尚无全站设计草稿')
|
||||
if type(expected_revision) is not int or expected_revision!=draft['revision']:
|
||||
@@ -210,6 +211,10 @@ def publish(site_id,expected_revision,actor):
|
||||
if draft['base_hash']!=_hash(settings):raise HTTPException(409,'网站信息已变更,请重新审核')
|
||||
before=published(settings)
|
||||
config=validate(json.loads(draft['config_json']))
|
||||
if expected_plan_token is not None:
|
||||
proof=_review_token(site_id,draft['revision'],settings,config)
|
||||
if not isinstance(expected_plan_token,str) or not __import__('hmac').compare_digest(expected_plan_token,proof):
|
||||
raise HTTPException(409,'审核预览已过期,请重新查看差异后确认')
|
||||
settings['site_design']=config
|
||||
settings['design_tokens']=config['tokens']
|
||||
# Keep old nav/settings for non-professional legacy templates too.
|
||||
@@ -223,6 +228,87 @@ def publish(site_id,expected_revision,actor):
|
||||
c.execute('DELETE FROM site_design_drafts WHERE site_id=?',(site_id,))
|
||||
return {'published':True,'publication_id':publication_id,'build_required':True}
|
||||
|
||||
# V8.26 - reviewable, immutable full-site design changes. This workflow uses
|
||||
# the existing site_design_drafts/publications tables: no destructive migration.
|
||||
REVIEW_SECTIONS = ('header', 'nav', 'footer', 'mobile_tabs', 'tokens', 'responsive')
|
||||
|
||||
|
||||
def _changes(before, after):
|
||||
"""Return a bounded, deterministic, safe structured diff for human review."""
|
||||
old, new = validate(before), validate(after)
|
||||
rows = []
|
||||
def inspect(path, a, b):
|
||||
if a == b:
|
||||
return
|
||||
if isinstance(a, dict) and isinstance(b, dict):
|
||||
for key in sorted(set(a) | set(b)):
|
||||
inspect(path + '.' + key, a.get(key), b.get(key))
|
||||
elif isinstance(a, list) and isinstance(b, list):
|
||||
# Lists (menu order) are atomic to avoid obscuring a move as deletes.
|
||||
rows.append({'field': path, 'before': a, 'after': b})
|
||||
else:
|
||||
rows.append({'field': path, 'before': a, 'after': b})
|
||||
for section in REVIEW_SECTIONS:
|
||||
inspect(section, old[section], new[section])
|
||||
return rows
|
||||
|
||||
|
||||
def _review_token(site_id, revision, live_settings, draft_config):
|
||||
payload={'site_id':site_id,'revision':revision,
|
||||
'settings_hash':_hash(live_settings), 'draft':validate(draft_config)}
|
||||
return hashlib.sha256(json.dumps(payload,ensure_ascii=False,
|
||||
sort_keys=True,separators=(',',':')).encode()).hexdigest()
|
||||
|
||||
|
||||
def review(site_id):
|
||||
with db.connect() as c:
|
||||
site=c.execute('SELECT settings_json FROM sites WHERE id=?',(site_id,)).fetchone()
|
||||
if not site:raise HTTPException(404,'网站不存在')
|
||||
draft=c.execute('SELECT * FROM site_design_drafts WHERE site_id=?',(site_id,)).fetchone()
|
||||
if not draft:raise HTTPException(409,'没有待审核的全站设计草稿')
|
||||
settings=json.loads(site['settings_json'] or '{}')
|
||||
if draft['base_hash']!=_hash(settings):raise HTTPException(409,'网站设置已经修改,请先处理草稿冲突')
|
||||
proposed=validate(json.loads(draft['config_json']))
|
||||
delta=_changes(published(settings),proposed)
|
||||
sections=[name for name in REVIEW_SECTIONS if any(
|
||||
r['field']==name or r['field'].startswith(name+'.') for r in delta)]
|
||||
return {'revision':draft['revision'], 'changed_fields':delta,
|
||||
'changed_sections':sections, 'change_count':len(delta),
|
||||
'affected_scope':'entire_site_next_build',
|
||||
'published_site_unchanged':True,
|
||||
'plan_token':_review_token(site_id,draft['revision'],settings,proposed)}
|
||||
|
||||
|
||||
def restore_publication(site_id,publication_id,which,expected_revision,expected_live_hash):
|
||||
"""Restore a historical snapshot as a NEW draft, never touch live settings."""
|
||||
if which not in ('before','after'):raise HTTPException(400,'恢复来源仅支持 before 或 after')
|
||||
if type(expected_revision) is not int:raise HTTPException(400,'需要草稿版本号')
|
||||
if not isinstance(expected_live_hash,str) or len(expected_live_hash)!=64:
|
||||
raise HTTPException(400,'需要当前全站发布版本指纹')
|
||||
with db.connect() as c:
|
||||
c.execute('BEGIN IMMEDIATE')
|
||||
site=c.execute('SELECT settings_json FROM sites WHERE id=?',(site_id,)).fetchone()
|
||||
if not site:raise HTTPException(404,'网站不存在')
|
||||
historic=c.execute('SELECT * FROM site_design_publications WHERE site_id=? AND id=?',
|
||||
(site_id,publication_id)).fetchone()
|
||||
if not historic:raise HTTPException(404,'发布记录不存在')
|
||||
settings=json.loads(site['settings_json'] or '{}')
|
||||
live_hash=_hash(settings)
|
||||
if live_hash!=expected_live_hash:raise HTTPException(409,'全站发布版本已变化,请重新载入')
|
||||
raw=historic['old_config_json'] if which=='before' else historic['new_config_json']
|
||||
plan=validate(json.loads(raw))
|
||||
old=c.execute('SELECT revision FROM site_design_drafts WHERE site_id=?',(site_id,)).fetchone()
|
||||
current=old['revision'] if old else 0
|
||||
if expected_revision!=current:raise HTTPException(409,'草稿版本已变化,请重新加载')
|
||||
new_rev=current+1
|
||||
c.execute('INSERT INTO site_design_drafts(site_id,revision,base_hash,config_json,updated_at) '
|
||||
'VALUES(?,?,?,?,?) ON CONFLICT(site_id) DO UPDATE SET '
|
||||
'revision=excluded.revision,base_hash=excluded.base_hash, '
|
||||
'config_json=excluded.config_json,updated_at=excluded.updated_at',
|
||||
(site_id,new_rev,live_hash,json.dumps(plan,ensure_ascii=False),db.now()))
|
||||
return {'restored_as_draft':True,'revision':new_rev,'published_unchanged':True}
|
||||
|
||||
|
||||
def responsive_css(config):
|
||||
r=validate(config)['responsive']
|
||||
tablet=r['tablet_gap'];mobile=r['mobile_gap'];padding=r['mobile_container_padding']
|
||||
@@ -276,3 +362,41 @@ def api_history(request:Request,site_id:str):
|
||||
_site(site_id)
|
||||
return db.rows('''SELECT id,site_id,actor,revision,published_at FROM site_design_publications
|
||||
WHERE site_id=? ORDER BY published_at DESC LIMIT 50''',(site_id,))
|
||||
|
||||
@router.get('/{site_id}/site-design/review')
|
||||
def api_review(request:Request,site_id:str):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
return review(site_id)
|
||||
|
||||
|
||||
@router.post('/{site_id}/site-design/publish-reviewed')
|
||||
def api_publish_reviewed(request:Request,site_id:str,payload:dict=Body(...)):
|
||||
enterprise.require(request,site_id,'owner')
|
||||
return publish(site_id,payload.get('expected_revision'),enterprise.actor(request),
|
||||
payload.get('plan_token',''))
|
||||
|
||||
|
||||
@router.get('/{site_id}/site-design/publications/{publication_id}')
|
||||
def api_publication(request:Request,site_id:str,publication_id:str):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
_site(site_id)
|
||||
row=db.one('SELECT * FROM site_design_publications WHERE site_id=? AND id=?',
|
||||
(site_id,publication_id))
|
||||
if not row:raise HTTPException(404,'发布版本不存在')
|
||||
old=validate(json.loads(row['old_config_json']))
|
||||
new=validate(json.loads(row['new_config_json']))
|
||||
return {'id':row['id'],'published_at':row['published_at'],'actor':row['actor'],
|
||||
'before':old,'after':new,'changes':_changes(old,new)}
|
||||
|
||||
|
||||
@router.get('/{site_id}/site-design/live-hash')
|
||||
def api_live_hash(request:Request,site_id:str):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
return {'live_hash':_hash(_settings(_site(site_id)))}
|
||||
|
||||
|
||||
@router.post('/{site_id}/site-design/publications/{publication_id}/restore')
|
||||
def api_restore_publication(request:Request,site_id:str,publication_id:str,payload:dict=Body(...)):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
return restore_publication(site_id,publication_id,payload.get('snapshot'),
|
||||
payload.get('expected_revision'),payload.get('expected_live_hash'))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
'use strict';(()=>{document.querySelectorAll('.auto31-search').forEach(form=>{form.addEventListener('submit',()=>{const input=form.querySelector('input[name=q]');if(input)input.value=input.value.trim().slice(0,120);});});})();
|
||||
@@ -35,5 +35,119 @@
|
||||
function shouldAcceptPreview(requestedPage,requestedSeq,currentPage,currentSeq){
|
||||
return requestedPage===currentPage&&requestedSeq===currentSeq;
|
||||
}
|
||||
return {reorder,duplicate,filterBlocks,historyState,clampNumber,shouldAcceptPreview};
|
||||
function findTopIndex(rows,id){return Array.isArray(rows)&&typeof id==='string'?rows.findIndex(b=>b.id===id):-1;}
|
||||
function reorderById(rows,sourceId,targetId){
|
||||
const from=findTopIndex(rows,sourceId),to=findTopIndex(rows,targetId);
|
||||
return from<0||to<0||from===to?null:reorder(rows,from,to);
|
||||
}
|
||||
function adjacentWidths(block,widths){
|
||||
if(!block||block.type!=='layout'||!Array.isArray(block.columns)||!Array.isArray(widths)||widths.length!==block.columns.length)return null;
|
||||
const validated=widths.map(v=>Number(v));
|
||||
if(validated.some(v=>!Number.isFinite(v)||v<.5||v>4))return null;
|
||||
// Keep the same type and field shape expected by blueprints.validate_blocks.
|
||||
return validated.map(v=>Math.round(v*100)/100);
|
||||
}
|
||||
function safeCanvasCommand(command,rows,types){
|
||||
if(!command||typeof command!=='object'||typeof command.blockId!=='string'||command.blockId.length>150)return false;
|
||||
const i=findTopIndex(rows,command.blockId);
|
||||
if(i<0)return false;
|
||||
const actions=['select','edit','up','down','add-after','duplicate','delete','reorder','resize-columns','inline-text'];
|
||||
if(!actions.includes(command.action))return false;
|
||||
if(command.action==='reorder')return findTopIndex(rows,command.targetId)>=0;
|
||||
if(command.action==='resize-columns')return !!adjacentWidths(rows[i],command.widths);
|
||||
if(command.action==='inline-text')return (command.field==='title'||command.field==='description')&&
|
||||
typeof command.value==='string'&&command.value.length<=500&&types?.[rows[i].type]?.fields?.includes(command.field);
|
||||
return true;
|
||||
}
|
||||
// V8.24: resolve the actual parent of a nested child from the validated tree.
|
||||
// A client message supplies stable IDs, never trusted indexes or an editable path.
|
||||
// Renderer accepts at most two layout levels; a bounded walk avoids malformed cycles.
|
||||
function findNestedChild(rows,parentId,childId){
|
||||
if(typeof parentId!=='string'||typeof childId!=='string'||!Array.isArray(rows)||
|
||||
!parentId||!childId||parentId===childId)return null;
|
||||
for(let rootIndex=0;rootIndex<rows.length;rootIndex++){
|
||||
const root=rows[rootIndex];
|
||||
function visit(layout,depth,ancestors){
|
||||
if(!layout||layout.type!=='layout'||!Array.isArray(layout.columns)||depth>2)return null;
|
||||
for(let ci=0;ci<layout.columns.length;ci++){
|
||||
const children=layout.columns[ci]?.blocks;
|
||||
if(!Array.isArray(children))continue;
|
||||
for(let ri=0;ri<children.length;ri++){
|
||||
const child=children[ri];
|
||||
if(!child||typeof child!=='object')continue;
|
||||
if(layout.id===parentId&&child.id===childId)
|
||||
return {parentIndex:rootIndex,root,parent:layout,columnIndex:ci,
|
||||
childIndex:ri,child,depth,ancestors:[...ancestors,layout.id]};
|
||||
if(child.type==='layout'&&depth<2){
|
||||
const found=visit(child,depth+1,[...ancestors,layout.id]);
|
||||
if(found)return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const found=visit(root,0,[]);
|
||||
if(found)return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function findChildInRoot(rows,rootId,childId){
|
||||
const rootIndex=findTopIndex(rows,rootId);
|
||||
if(rootIndex<0||typeof childId!=='string')return null;
|
||||
const root=rows[rootIndex];
|
||||
if(root.type!=='layout')return null;
|
||||
function visit(layout,depth){
|
||||
if(depth>2)return null;
|
||||
for(const col of layout.columns||[]){
|
||||
for(const child of col.blocks||[]){
|
||||
if(child?.id===childId)return findNestedChild(rows,layout.id,childId);
|
||||
if(child?.type==='layout'){
|
||||
const match=visit(child,depth+1);
|
||||
if(match)return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return visit(root,0);
|
||||
}
|
||||
function findLayout(rows,layoutId){
|
||||
if(typeof layoutId!=='string'||!Array.isArray(rows))return null;
|
||||
const topIndex=findTopIndex(rows,layoutId);
|
||||
if(topIndex>=0)return rows[topIndex].type==='layout'?{layout:rows[topIndex],rootIndex:topIndex,depth:0}:null;
|
||||
for(let i=0;i<rows.length;i++){
|
||||
const root=rows[i];
|
||||
if(root.type!=='layout')continue;
|
||||
for(const col of root.columns||[])for(const child of col.blocks||[]){
|
||||
if(child?.id===layoutId && child.type==='layout')return {layout:child,rootIndex:i,depth:1};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function safeNestedCanvasCommand(command,rows,types){
|
||||
if(!command||typeof command!=='object')return false;
|
||||
const found=findNestedChild(rows,command.blockId,command.childId);
|
||||
if(!found)return false;
|
||||
const actions=['child-select','child-edit','child-up','child-down','child-duplicate','child-delete','child-inline-text'];
|
||||
if(!actions.includes(command.action))return false;
|
||||
if(command.action==='child-inline-text')return ['title','subtitle','description'].includes(command.field)
|
||||
&& types?.[found.child.type]?.fields?.includes(command.field)
|
||||
&& typeof command.value==='string'&&command.value.length<=500;
|
||||
return true;
|
||||
}
|
||||
function layoutPreset(block,preset){
|
||||
if(!block||block.type!=='layout'||!Array.isArray(block.columns))return null;
|
||||
const count=block.columns.length;if(count<1||count>4)return null;
|
||||
if(preset==='equal')return Array(count).fill(1);
|
||||
if(count!==2)return null;
|
||||
if(preset==='left-wide')return [2,1];
|
||||
if(preset==='right-wide')return [1,2];
|
||||
return null;
|
||||
}
|
||||
function moveNested(block,columnIndex,childIndex,direction){
|
||||
if(!block||block.type!=='layout'||![-1,1].includes(direction))return null;
|
||||
const list=block.columns?.[columnIndex]?.blocks;
|
||||
return Array.isArray(list)?reorder(list,childIndex,childIndex+direction):null;
|
||||
}
|
||||
return {reorder,duplicate,filterBlocks,historyState,clampNumber,shouldAcceptPreview,findTopIndex,reorderById,adjacentWidths,safeCanvasCommand,findNestedChild,findChildInRoot,findLayout,safeNestedCanvasCommand,layoutPreset,moveNested};
|
||||
});
|
||||
|
||||
@@ -88,3 +88,71 @@ iframe{min-height:590px;height:calc(100vh - 224px)}
|
||||
|
||||
/* Accessible repeated-item forms for galleries, carousels, FAQs and features */
|
||||
.sf-array-editor{display:grid;gap:10px}.sf-array-hint{font-size:11px;line-height:1.6;color:#8391a3;margin:0 0 4px}.sf-array-card{border:1px solid #e5eaf0;background:#fafbfd;padding:11px;border-radius:10px}.sf-array-card-head{display:flex;align-items:center;justify-content:space-between;gap:7px;margin-bottom:12px}.sf-array-card-head b{font-size:12px;color:#586a7f}.sf-array-card-head div{display:flex;gap:3px}.sf-array-card-head button{border:1px solid #e2e7ed;border-radius:5px;color:#6c7a8e;background:white;padding:4px 7px;font-size:11px}.sf-array-card-head button:last-child{color:#b95d62}.sf-array-card-head button:disabled{opacity:.3;cursor:not-allowed}.sf-array-field{display:block!important;margin:10px 0!important;font-size:11px!important;font-weight:650!important}.sf-array-field input,.sf-array-field textarea{display:block;width:100%;margin-top:5px;background:white}.sf-array-field textarea{min-height:65px}.sf-array-add{border:1px dashed #e5a77c;border-radius:9px;background:#fff8f2;color:#be672a;padding:13px 9px;font-size:12px;font-weight:800}
|
||||
|
||||
/* V8.22 canvas-level authoring with no change to published templates */
|
||||
.sf-insert-hint{margin:8px 12px;padding:10px 12px;background:#fff3e8;color:#aa5525;font-size:12px;border:1px solid #f0bd98;border-radius:9px;line-height:1.6}
|
||||
.sf-insert-hint[hidden]{display:none!important}
|
||||
.preview-head .preview-note{max-width:350px;font-size:11px;color:#67778b}
|
||||
#sitePreview:focus-visible{outline:2px solid #e77b37;outline-offset:2px}
|
||||
|
||||
/* V8.23 child-first layout inspector */
|
||||
.sf-nested-edit{margin:5px 0 20px;display:grid;gap:9px}.sf-nested-card{border:1px solid #e3e9f0;border-radius:10px;padding:10px;background:#fbfcfd}.sf-nested-card.active{border-color:#42a8b9;background:#f2fbfd}.sf-nested-heading{display:flex;align-items:center;justify-content:space-between;gap:6px}.sf-nested-heading>button{border:0;background:none;font-size:12px;color:#36556e;font-weight:750;text-align:left;cursor:pointer}.sf-nested-heading span{display:flex;gap:4px}.sf-nested-heading span button,.sf-nested-actions button{border:1px solid #d8e4eb;background:white;border-radius:6px;padding:5px 9px;color:#36556e;cursor:pointer}.sf-nested-heading button:disabled{opacity:.3;cursor:not-allowed}.sf-nested-field{display:block;margin:10px 0;color:#536477;font-size:12px}.sf-nested-field input{display:block;margin-top:6px;width:100%;padding:10px;border:1px solid #d7e3e9;border-radius:7px;font-size:12px}.sf-nested-actions{display:flex;gap:6px;margin:10px 0 2px}.sf-nested-actions button:last-child{color:#b44549}.sf-layout-presets{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:9px 0 18px}.sf-layout-presets button{white-space:normal;padding:10px 5px;border:1px solid #e5b995;border-radius:8px;background:#fff8f3;color:#945325;font-size:11px;cursor:pointer}.sf-layout-presets button:disabled{opacity:.4;cursor:not-allowed}
|
||||
|
||||
/* V8.25: friendly shared-component management, scoped to editor only. */
|
||||
.sf-shared-palette-row{border:1px solid #e4e8ef;background:white;border-radius:9px;padding:10px;display:grid;gap:8px;font-size:12px}
|
||||
.sf-shared-palette-row strong{display:flex;justify-content:space-between;gap:7px}
|
||||
.sf-shared-palette-row small{color:#8594a8}
|
||||
.sf-shared-palette-row>div{display:flex;gap:7px}
|
||||
.sf-shared-palette-row button,.sf-shared-actions button{border:1px solid #e0e4e9;border-radius:7px;background:#fff;padding:7px 9px;cursor:pointer;color:#364759}
|
||||
.sf-shared-palette-row button:last-child{background:#fff3e8;color:#b95a23;border-color:#eac7ae}
|
||||
.sf-shared-dialog{width:min(780px,96vw);max-height:88vh;overflow:auto;border:1px solid #e3e7ec;border-radius:16px;padding:24px;box-shadow:0 20px 60px #263a5430;color:#334155}
|
||||
.sf-shared-dialog::backdrop{background:#0e203b70}
|
||||
.sf-shared-dialog-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
||||
.sf-shared-dialog h2{margin:5px 0 12px;font-size:22px}.sf-shared-dialog h3{margin:18px 0 10px}
|
||||
.sf-shared-card{display:flex;justify-content:space-between;gap:14px;border:1px solid #e3e8ee;border-radius:10px;padding:13px;margin:10px 0;align-items:center}
|
||||
.sf-shared-card>div:first-child{display:grid;gap:4px}.sf-shared-card small,.sf-shared-usage small{display:block;color:#64748b;font-size:11px;margin-top:5px}
|
||||
.sf-shared-actions{display:flex;flex-wrap:wrap;gap:6px}
|
||||
.sf-shared-usage{display:flex;align-items:center;gap:12px;padding:12px;border:1px solid #e2e7ed;border-radius:9px;margin:7px 0;cursor:pointer}
|
||||
.sf-shared-usage:has(input:disabled){opacity:.6;cursor:not-allowed}
|
||||
#sharedComponentDetail{margin-top:22px;border-top:1px solid #e2e7ed;padding-top:12px}
|
||||
#sharedComponentDetail>.ui-btn{margin-top:14px}
|
||||
@media(max-width:700px){.sf-shared-card{display:grid}.sf-shared-dialog{padding:15px}}
|
||||
|
||||
.sf-share-info{border:1px solid #f0cfb5;background:#fff8f1;padding:13px;border-radius:10px;margin:12px 0;color:#955023;font-size:12px}.sf-share-info p{line-height:1.5}
|
||||
|
||||
.sf-shared-history{display:flex;justify-content:space-between;align-items:center;gap:10px;border-bottom:1px solid #edf0f4;padding:10px 0;font-size:12px;flex-wrap:wrap}
|
||||
|
||||
/* V8.26 global design navigation, review and restoration */
|
||||
.sf-visual-nav{padding:15px;border:1px solid #e2e8f0;border-radius:12px;background:#fafcff}
|
||||
.sf-visual-nav-header{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px;margin-bottom:12px}
|
||||
.sf-visual-nav-header span,.sf-visual-nav-help{font-size:12px;color:#64748b}
|
||||
.sf-nav-item{padding:8px;margin-bottom:6px;border:1px solid #dde5ed;background:#fff;border-radius:10px;display:flex;align-items:center;flex-wrap:wrap;gap:6px}
|
||||
.sf-nav-item input{min-width:100px;flex:1 1 135px;padding:8px;border:1px solid #cbd5e1;border-radius:7px}
|
||||
.sf-nav-action{font-size:12px!important;padding:6px 9px!important}
|
||||
.sf-global-review{width:min(720px,95vw);max-height:85vh;overflow:auto;border:1px solid #dce5ed;border-radius:16px;padding:24px;box-shadow:0 18px 54px #132c4540}
|
||||
.sf-review-change{padding:12px;border:1px solid #dfe6ee;border-radius:10px;margin:9px 0;background:#f9fbfe;overflow-wrap:anywhere}
|
||||
.sf-review-change p{font-size:12px;color:#64748b;margin:7px 0;white-space:pre-wrap}
|
||||
.sf-review-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:16px}
|
||||
@media(max-width:600px){.sf-nav-item{margin-left:0!important}.sf-global-review{padding:16px}}
|
||||
|
||||
.sf-global-entry{display:flex;flex-direction:column;gap:6px;padding:12px;border:1px solid #f1d7bf;border-radius:10px;background:#fffaf5;margin-bottom:12px}.sf-global-entry button{font-weight:650;text-align:left}.sf-global-entry small{color:#876c56;font-size:11px}
|
||||
|
||||
/* V8.29: discoverable content palette and safe theme comparison */
|
||||
.sf-palette-filter{display:flex;gap:6px;margin:12px 13px 6px}.sf-palette-filter input{min-width:0;flex:1;border:1px solid #d8e2ed;border-radius:9px;padding:10px;font-size:12px}.sf-palette-filter button{border:1px solid #dee6ee;border-radius:8px;background:#f7f9fc;color:#53657a;font-size:11px;padding:0 9px}.sf-palette-summary{font-size:11px;color:#657487;padding:2px 14px 9px}.sf-palette-categories{display:block;padding:0 12px 12px}.sf-palette-group{margin-bottom:12px}.sf-palette-group h4{font-size:12px;color:#4a5870;margin:6px 3px 9px;font-weight:700}.sf-palette-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.sf-palette-grid button{min-height:54px;border:1px solid #e3e9ef;background:white;padding:10px;border-radius:10px;display:flex;gap:8px;align-items:center;text-align:left;color:#35445b;font-size:12px;font-weight:600;line-height:1.4;cursor:pointer}.sf-palette-grid button:hover{background:#fff6ee;border-color:#e8a576}.sf-palette-grid button span{font-size:18px;flex-shrink:0;color:#b9652f}.sf-palette-empty{font-size:12px;color:#697b90;padding:10px}.sf-history-quick{border:1px solid #e0e6ed;background:white;color:#49596a;border-radius:8px;min-width:32px;height:34px;font-size:20px;cursor:pointer}.sf-history-quick:disabled{opacity:.35;cursor:not-allowed}.sf-theme-preview-entry{white-space:nowrap;color:#43576d}.sf-theme-showcase{width:min(1150px,96vw);height:min(94vh,900px);max-height:94vh;border:1px solid #dce3eb;border-radius:17px;overflow:auto;background:white;padding:0;box-shadow:0 25px 80px #10284148}.sf-theme-showcase::backdrop{background:#1424389c}.sf-showcase-head{display:flex;justify-content:space-between;align-items:start;gap:16px;padding:20px 24px 13px;border-bottom:1px solid #e5eaf0}.sf-showcase-head small{color:#b65c2d;font-weight:700}.sf-showcase-head h2{font-size:21px;margin:7px 0}.sf-showcase-head p{font-size:12px;color:#697b90;margin:0}.sf-showcase-tools{display:flex;gap:12px;align-items:end;flex-wrap:wrap;padding:14px 24px}.sf-showcase-tools label{display:grid;gap:5px;font-size:12px;font-weight:600;color:#43566c}.sf-showcase-tools select{border:1px solid #dce3ed;border-radius:8px;padding:9px 14px;min-width:145px;background:white}.sf-showcase-device{display:flex;gap:5px;margin-left:auto;background:#eef2f6;border-radius:8px;padding:4px}.sf-showcase-device button{padding:8px 12px;border:0;background:transparent;color:#596b7e;border-radius:6px;cursor:pointer}.sf-showcase-device button[aria-pressed=true]{background:#fff;color:#ad5c28;box-shadow:0 1px 6px #122c4419;font-weight:700}.sf-showcase-status{margin:0 24px 10px;color:#64748b;font-size:12px}.sf-showcase-stage{height:min(55vh,520px);overflow:auto;background:#eef1f5;padding:15px;display:flex;align-items:stretch;justify-content:center}.sf-showcase-frame{min-width:0;max-width:100%;height:100%;transition:width .18s ease;background:white;box-shadow:0 9px 30px #21334c20}.sf-showcase-frame iframe{display:block;width:100%;height:100%;min-height:0;border:0;background:white}.sf-showcase-footer{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 24px;border-top:1px solid #e5eaf0}.sf-showcase-footer span{color:#718195;font-size:12px}.sf-theme-showcase button:focus-visible,.sf-theme-showcase select:focus-visible,.sf-palette-filter input:focus-visible,.sf-history-quick:focus-visible{outline:3px solid #eeab77;outline-offset:2px}@media(max-width:1100px){.sf-theme-preview-entry{font-size:11px;padding:8px!important}}@media(max-width:700px){.sf-theme-preview-entry{font-size:0;min-width:32px;padding:6px!important}.sf-theme-preview-entry::before{content:'◫';font-size:17px}.sf-showcase-head,.sf-showcase-tools,.sf-showcase-footer{padding:12px}.sf-showcase-stage{height:55vh;padding:8px}.sf-showcase-footer{flex-direction:column;align-items:start}.sf-showcase-device{margin-left:0}}@media(prefers-reduced-motion:reduce){.sf-showcase-frame{transition:none}}
|
||||
|
||||
.sf-style-presets{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:9px 0 17px}.sf-style-presets button{border:1px solid #dae2eb;border-radius:9px;background:#fff;padding:10px 5px;min-height:48px;font-size:11px;color:#364459;cursor:pointer}.sf-style-presets button:nth-child(2){background:#f0f3f8}.sf-style-presets button:nth-child(3){background:#26374d;color:#fff}.sf-style-presets button:focus-visible{outline:3px solid #efa971;outline-offset:2px}
|
||||
/* Prevent translated, closed inspector drawers from widening the mobile viewport. */
|
||||
@media(max-width:1060px){html,body{max-width:100%;overflow-x:clip}}
|
||||
/* V8.29 mobile authoring: two deliberate toolbar rows, never horizontal-scrolling actions. */
|
||||
@media(max-width:720px){
|
||||
.studio-top.sf-easy-top{height:auto;min-height:100px;flex-wrap:wrap;justify-content:space-between;align-content:center;padding:7px 9px;gap:5px}
|
||||
.studio-top.sf-easy-top .brand,.studio-top.sf-easy-top .file-name,.studio-top.sf-easy-top .sf-history-quick,.studio-top.sf-easy-top .sf-theme-preview-entry{display:none!important}
|
||||
.studio-top.sf-easy-top .sf-pane-controls{order:1;flex:0 0 auto}
|
||||
.studio-top.sf-easy-top #saveDraftButton{order:2;flex:0 0 auto}
|
||||
.studio-top.sf-easy-top #publishDesignButton{order:3;flex:0 0 auto}
|
||||
.studio-top.sf-easy-top .studio-actions{order:4;flex:0 0 auto}
|
||||
.studio-top.sf-easy-top .sf-easy-device{order:5;flex:1 1 100%;display:flex;justify-content:center;margin:0}
|
||||
.studio-top.sf-easy-top .sf-easy-device .icon-btn{flex:1;max-width:115px}
|
||||
.studio-body .left-pane,.studio-body .right-pane{top:140px!important}
|
||||
.studio-actions-menu{right:0;max-width:calc(100vw - 18px)}
|
||||
}
|
||||
|
||||
+408
-44
@@ -10,19 +10,296 @@ const toast=(s,err=false)=>{const t=$('#toast');t.textContent=s;t.style.backgrou
|
||||
const activePage=()=>pages.find(e=>e.id===pageId);
|
||||
const editorTools=window.SiteForgeEditorHelpers;
|
||||
let libraryOpen=false;
|
||||
let inspectorTab='content',blockSearch='',previewRequest=0,previewController=null,savePromise=null,lastContinuousEdit=0;
|
||||
let sharedPreview=null;
|
||||
let siteNavDraft=[],siteDesignReviewToken=null;
|
||||
let selectedChildId=null;
|
||||
let inspectorTab='content',paletteSearch='',blockSearch='',previewRequest=0,previewController=null,savePromise=null,lastContinuousEdit=0,insertAfterId=null;
|
||||
|
||||
let snapshots=[],snapshotCursor=-1,draggedChild=null,draftRevision=0,siteDesign=null,siteDesignRevision=0,siteDesignPreview=null;
|
||||
function recordSnapshot(){const current=JSON.stringify({blocks,kind,layoutMode});if(snapshotCursor>=0 && snapshots[snapshotCursor]===current)return;snapshots=snapshots.slice(0,snapshotCursor+1);snapshots.push(current);if(snapshots.length>75)snapshots.shift();snapshotCursor=snapshots.length-1}
|
||||
function restoreSnapshot(index){if(index<0||index>=snapshots.length)return;snapshotCursor=index;const state=JSON.parse(snapshots[index]);blocks=state.blocks;kind=state.kind;layoutMode=state.layoutMode;selected=Math.min(selected,blocks.length-1);$('#pageTemplate').value=kind;$('#layoutMode').value=layoutMode;dirty=true;$('#saveStatus').textContent='未保存更改';$('#saveStatus').classList.add('dirty');Studio.updateHistoryButtons();Studio.draw();Studio.refreshPreview()}
|
||||
function restoreSnapshot(index){if(index<0||index>=snapshots.length)return;snapshotCursor=index;const state=JSON.parse(snapshots[index]);blocks=state.blocks;kind=state.kind;layoutMode=state.layoutMode;selected=Math.min(selected,blocks.length-1);selectedChildId=null;$('#pageTemplate').value=kind;$('#layoutMode').value=layoutMode;dirty=true;$('#saveStatus').textContent='未保存更改';$('#saveStatus').classList.add('dirty');Studio.updateHistoryButtons();Studio.draw();Studio.refreshPreview()}
|
||||
const Studio={
|
||||
highlightCanvas(){const block=blocks[selected];const frame=$('#sitePreview');if(block&&frame?.contentWindow)frame.contentWindow.postMessage({source:'siteforge-studio-host',action:'highlight',blockId:block.id,childId:selectedChildId},'*')},
|
||||
showInsertHint(){const box=$('#sfInsertHint');if(!box)return;const item=blocks.find(x=>x.id===insertAfterId);box.hidden=!item;box.textContent=item?'正在「'+(item.title||schema?.blocks[item.type]?.name||'当前区块')+'」下方插入:选择一种内容。':''},
|
||||
beginAddAfter(blockId){if(editorTools.findTopIndex(blocks,blockId)<0)return;insertAfterId=blockId;Studio.showInsertHint();Studio.toggleLibrary(true);document.body.classList.add('show-structure');document.body.classList.remove('show-inspector');$('#sfAddBlock').scrollIntoView({behavior:'smooth',block:'nearest'})},
|
||||
canvasCommand(command){
|
||||
if(String(command?.action||'').startsWith('child-')){Studio.childCanvasCommand(command);return;}
|
||||
if(command?.action==='resize-columns'){
|
||||
const nestedLayout=editorTools.findLayout(blocks,command.blockId);
|
||||
if(!nestedLayout)return;
|
||||
const widths=editorTools.adjacentWidths(nestedLayout.layout,command.widths);
|
||||
if(!widths)return;
|
||||
if(nestedLayout.layout.columns.every((c,k)=>Math.abs(c.width-widths[k])<.001))return;
|
||||
nestedLayout.layout.columns.forEach((c,k)=>c.width=widths[k]);
|
||||
selected=nestedLayout.rootIndex;
|
||||
selectedChildId=nestedLayout.depth?command.blockId:null;
|
||||
Studio.modified();Studio.draw();return;
|
||||
}
|
||||
if(!editorTools.safeCanvasCommand(command,blocks,schema.blocks))return;
|
||||
const i=editorTools.findTopIndex(blocks,command.blockId),block=blocks[i];
|
||||
if(command.action==='select'||command.action==='edit'){Studio.select(i);if(command.action==='edit'){inspectorTab='content';Studio.applyInspectorTab();}return;}
|
||||
if(command.action==='up'||command.action==='down'){Studio.moveBlock(i,command.action==='up'?-1:1);return;}
|
||||
if(command.action==='add-after'){Studio.beginAddAfter(command.blockId);return;}
|
||||
if(command.action==='duplicate'){Studio.duplicate(i);return;}
|
||||
if(command.action==='delete'){Studio.remove(i);return;}
|
||||
if(command.action==='reorder'){
|
||||
const to=editorTools.findTopIndex(blocks,command.targetId);
|
||||
const next=editorTools.reorderById(blocks,command.blockId,command.targetId);
|
||||
if(!next)return;blocks=next;selected=to;Studio.modified();Studio.draw();return;
|
||||
}
|
||||
if(command.action==='resize-columns'){
|
||||
const widths=editorTools.adjacentWidths(block,command.widths);
|
||||
if(!widths)return;
|
||||
if(block.columns.every((c,k)=>Math.abs(c.width-widths[k])<.001))return;
|
||||
block.columns.forEach((col,j)=>col.width=widths[j]);selected=i;
|
||||
Studio.modified();Studio.inspector();return;
|
||||
}
|
||||
if(command.action==='inline-text'){
|
||||
if(block[command.field]===command.value)return;
|
||||
block[command.field]=command.value;selected=i;Studio.modified();Studio.draw();
|
||||
toast('文字已修改,记得保存草稿');
|
||||
}
|
||||
},
|
||||
childCanvasCommand(command){
|
||||
if(!editorTools.safeNestedCanvasCommand(command,blocks,schema.blocks))return;
|
||||
const found=editorTools.findNestedChild(blocks,command.blockId,command.childId);
|
||||
if(!found)return;
|
||||
const {parentIndex,parent,columnIndex,childIndex,child}=found;
|
||||
if(command.action==='child-select'||command.action==='child-edit'){
|
||||
selected=parentIndex;selectedChildId=child.id;inspectorTab='content';Studio.draw();Studio.highlightCanvas();
|
||||
const card=document.querySelector('#sf-nested-active');card?.scrollIntoView({block:'nearest'});
|
||||
if(window.matchMedia('(max-width:1060px)').matches)document.body.classList.add('show-inspector');
|
||||
return;
|
||||
}
|
||||
if(command.action==='child-inline-text'){
|
||||
if(child[command.field]===command.value)return;
|
||||
child[command.field]=command.value;selected=parentIndex;selectedChildId=child.id;
|
||||
Studio.modified();Studio.draw();return;
|
||||
}
|
||||
if(command.action==='child-delete'){
|
||||
if(!confirm('确定删除这一列中的组件?'))return;
|
||||
parent.columns[columnIndex].blocks.splice(childIndex,1);selectedChildId=null;
|
||||
}else if(command.action==='child-duplicate'){
|
||||
const copy=editorTools.duplicate(child,()=>crypto.randomUUID().replace(/-/g,'').slice(0,10));
|
||||
parent.columns[columnIndex].blocks.splice(childIndex+1,0,copy);selectedChildId=copy.id;
|
||||
}else{
|
||||
const next=editorTools.moveNested(parent,columnIndex,childIndex,command.action==='child-up'?-1:1);
|
||||
if(!next)return;parent.columns[columnIndex].blocks=next;selectedChildId=child.id;
|
||||
}
|
||||
selected=parentIndex;Studio.modified();Studio.draw();
|
||||
},
|
||||
setLayoutGap(value){const block=blocks[selected];if(!block||block.type!=='layout')return;block.gap=Math.max(0,Math.min(64,Math.round(Number(value)||0)));Studio.modified();Studio.inspector()},
|
||||
setLayoutPreset(preset){
|
||||
const block=blocks[selected];const widths=editorTools.layoutPreset(block,preset);
|
||||
if(!widths)return toast('当前列数不支持这种预设',true);
|
||||
block.columns.forEach((col,i)=>col.width=widths[i]);Studio.modified();Studio.inspector();
|
||||
},
|
||||
// One inspector for both levels, with a readable path back to the parent.
|
||||
childQuickEditor(layout){
|
||||
if(layout.type!=='layout')return '';
|
||||
const cards=[];
|
||||
const walk=(container,trail=[],depth=0)=>{
|
||||
if(depth>2)return;
|
||||
container.columns.forEach((column,ci)=>(column.blocks||[]).forEach((child,ri)=>{
|
||||
const active=selectedChildId===child.id;
|
||||
const label=schema.blocks[child.type]?.name||child.type;
|
||||
const breadcrumb=[...trail,`第${ci+1}列`].join(' › ');
|
||||
cards.push(`<div class="sf-nested-card ${active?'active':''}" style="margin-left:${Math.min(depth,1)*12}px" ${active?'id="sf-nested-active"':''}><div class="sf-nested-heading"><button type="button" onclick="Studio.selectChild('${html(child.id)}')">${active?'✓ ':''}${html(breadcrumb)} · ${html(label)}</button><span><button type="button" title="上移" ${ri===0?'disabled':''} onclick="Studio.childCanvasCommand({action:'child-up',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">↑</button><button type="button" title="下移" ${ri===column.blocks.length-1?'disabled':''} onclick="Studio.childCanvasCommand({action:'child-down',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">↓</button></span></div>`);
|
||||
if(active){
|
||||
if(child.type==='layout'){
|
||||
cards.push(`<div class="sf-nested-field">内层布局 · ${child.columns.length} 列</div><div class="sf-nested-actions"><button type="button" onclick="Studio.setNestedLayoutPreset('${html(child.id)}','equal')">均分列宽</button>${child.columns.length===2?`<button type="button" onclick="Studio.setNestedLayoutPreset('${html(child.id)}','left-wide')">左宽右窄</button><button type="button" onclick="Studio.setNestedLayoutPreset('${html(child.id)}','right-wide')">左窄右宽</button>`:''}</div><label class="sf-nested-field">列间距(像素)<input type="number" min="0" max="64" value="${Number(child.gap)||0}" onchange="Studio.setNestedLayoutGap('${html(child.id)}',this.value)"></label><div class="sf-nested-actions"><label>手机列数 <select onchange="Studio.setNestedDeviceColumns('${html(child.id)}','mobile',this.value)"><option value="1" ${child.mobile_columns===1?'selected':''}>1 列</option><option value="2" ${child.mobile_columns===2?'selected':''}>2 列</option></select></label><label>平板列数 <select onchange="Studio.setNestedDeviceColumns('${html(child.id)}','tablet',this.value)">${[1,2,3].map(n=>`<option value="${n}" ${child.tablet_columns===n?'selected':''}>${n} 列</option>`).join('')}</select></label></div>`);
|
||||
}else{
|
||||
const fields=(schema.blocks[child.type]?.fields||[]).filter(k=>['title','subtitle','description','image','button','href'].includes(k));
|
||||
fields.forEach(key=>cards.push(`<label class="sf-nested-field">${html(({title:'标题',subtitle:'副标题',description:'描述',image:'图片地址',button:'按钮文字',href:'跳转链接'})[key])}<input type="text" value="${html(child[key]??'')}" oninput="Studio.childFieldById('${html(child.id)}','${key}',this.value)"></label>`));
|
||||
}
|
||||
if(child.type==='layout'){
|
||||
child.columns.forEach((col,index)=>cards.push(`<div class="sf-nested-actions" role="group" aria-label="内层第${index+1}列添加内容"><span>第 ${index+1} 列</span>${['richtext','image_text','features','cta'].map(t=>`<button type="button" onclick="Studio.addNestedChild('${html(child.id)}',${index},'${t}')">+ ${html(schema.blocks[t]?.name||t)}</button>`).join('')}</div>`));
|
||||
}
|
||||
cards.push(`<button type="button" class="ui-btn ghost" onclick="Studio.saveReusable()">☆ 保存当前组件到组件库</button>`);
|
||||
if(schema.blocks[child.type]?.fields?.includes('image')){
|
||||
cards.push(`<button type="button" class="ui-btn ghost" onclick="Studio.chooseMedia()">🖼 从素材库更换选中图片</button>`);
|
||||
}
|
||||
cards.push(`<div class="sf-nested-actions"><button type="button" onclick="Studio.childCanvasCommand({action:'child-duplicate',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">复制组件</button><button type="button" onclick="Studio.childCanvasCommand({action:'child-delete',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">删除组件</button></div>`);
|
||||
}
|
||||
cards.push('</div>');
|
||||
if(child.type==='layout')walk(child,[...trail,`第${ci+1}列 · 内层布局`],depth+1);
|
||||
}));
|
||||
};
|
||||
walk(layout);
|
||||
return `<div class="sf-nested-edit"><div class="subheading">画布组件 · 选择任意一层内容直接编辑</div>${cards.join('')||'<p class="prop-tip">当前没有子组件,请添加内容。</p>'}</div>`;
|
||||
},
|
||||
setNestedLayoutPreset(layoutId,preset){
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
const widths=editorTools.layoutPreset(found.layout,preset);
|
||||
if(!widths)return toast('当前内层列数不支持此预设',true);
|
||||
found.layout.columns.forEach((c,k)=>c.width=widths[k]);selectedChildId=layoutId;
|
||||
Studio.modified();Studio.draw();
|
||||
},
|
||||
setNestedLayoutGap(layoutId,value){
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
found.layout.gap=Math.round(Math.max(0,Math.min(64,Number(value)||0)));
|
||||
selectedChildId=layoutId;Studio.modified();Studio.draw();
|
||||
},
|
||||
addNestedChild(layoutId,columnIndex,type){
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
// A second-level layout cannot contain another layout. Use only the safe
|
||||
// declarative blocks offered in the UI and validate limits before mutation.
|
||||
if(!['richtext','image_text','features','cta'].includes(type)||
|
||||
!Number.isInteger(columnIndex)||columnIndex<0||columnIndex>=found.layout.columns.length)return;
|
||||
const column=found.layout.columns[columnIndex];
|
||||
if(column.blocks.length>=60)return toast('这一列的组件数量已达到限制',true);
|
||||
const item=structuredClone(schema.blocks[type].defaults);
|
||||
item.id=crypto.randomUUID().replace(/-/g,'').slice(0,10);
|
||||
item.type=type;item.visible=true;
|
||||
column.blocks.push(item);selectedChildId=item.id;
|
||||
Studio.modified();Studio.draw();
|
||||
toast('已添加内层组件,请在右侧修改内容');
|
||||
},
|
||||
setNestedDeviceColumns(layoutId,device,value){
|
||||
if(!['mobile','tablet'].includes(device))return;
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
const max=device==='mobile'?2:3;
|
||||
const count=Math.max(1,Math.min(max,Math.round(Number(value)||1)));
|
||||
found.layout[device+'_columns']=count;
|
||||
found.layout.columns.forEach(c=>c[device+'_span']=Math.min(count,c[device+'_span']||1));
|
||||
selectedChildId=layoutId;Studio.modified();Studio.draw();
|
||||
},
|
||||
selectChild(childId){const root=blocks[selected];const found=editorTools.findChildInRoot(blocks,root?.id,childId);if(!found)return;Studio.childCanvasCommand({action:'child-select',blockId:found.parent.id,childId})},
|
||||
childFieldById(childId,key,value){
|
||||
const parent=blocks[selected];const found=editorTools.findChildInRoot(blocks,parent?.id,childId);
|
||||
if(!found||!schema.blocks[found.child.type]?.fields?.includes(key)||!['title','subtitle','description','image','button','href'].includes(key))return;
|
||||
found.child[key]=String(value).slice(0,1500);selectedChildId=childId;Studio.modified(true);
|
||||
},
|
||||
async init(){try{csrf=(await api('/api/auth/me')).csrf;site=await api(`/api/sites/${siteId}`);pages=await api(`/api/sites/${siteId}/entries`);schema=await api('/api/editor/blocks');$('#siteName').textContent=site.name;const select=$('#pageSelect');select.innerHTML=pages.map(e=>`<option value="${html(e.id)}">${html(e.title)} (${html(e.route)})</option>`).join('');for(let [k,v] of Object.entries(schema.page_templates))$('#pageTemplate').innerHTML+=`<option value="${k}">${html(v.name)}</option>`;Studio.renderPalette();await Studio.loadReusables();await Studio.choosePage(pageId||pages[0]?.id)}catch(e){toast('初始化失败:'+e.message,true);console.error(e)}},
|
||||
async choosePage(id){if(savePromise){toast('保存仍在进行,请稍后切换页面',true);$('#pageSelect').value=pageId;return}if(dirty&&!confirm('当前页面尚未保存,确定切换?')){$('#pageSelect').value=pageId;return}pageId=id;$('#pageSelect').value=id;const data=await api(`/api/sites/${siteId}/pages/${id}/studio-draft`);draftRevision=data.revision;blocks=data.blocks.map(b=>structuredClone(b));kind=data.page_template;layoutMode=data.layout_mode||'append';$('#layoutMode').value=layoutMode;selected=blocks.length?0:-1;$('#pageTemplate').value=kind;$('#pageName').textContent=data.entry.title;$('#routeDisplay').textContent=data.entry.route;dirty=false;snapshots=[];snapshotCursor=-1;lastContinuousEdit=0;blockSearch='';$('#blockSearch').value='';recordSnapshot();Studio.updateHistoryButtons();$('#saveStatus').textContent=draftRevision?'草稿 v'+draftRevision:'未修改';$('#saveStatus').classList.remove('dirty');Studio.draw();Studio.refreshPreview()},
|
||||
async loadReusables(){try{reusables=await api(`/api/sites/${siteId}/components`);$('#reusablePalette').innerHTML=reusables.map(x=>`<button onclick="Studio.insertReusable('${html(x.id)}')">✦ ${html(x.name)} <small>v${x.version}</small></button>`).join('')||'<span style="font-size:12px;opacity:.55;padding:10px">暂无保存的组件</span>'}catch(e){console.warn(e)}},
|
||||
async saveReusable(){const block=blocks[selected];if(!block)return toast('请先选择一个区块',true);const name=prompt('为此组件命名',block.title||schema.blocks[block.type]?.name);if(!name?.trim())return;try{await api(`/api/sites/${siteId}/components`,req('POST',{name,block}));await Studio.loadReusables();toast('组件已保存到本站组件库')}catch(e){toast(e.message,true)}},
|
||||
insertReusable(id){const saved=reusables.find(x=>x.id===id);if(!saved)return;const copy=editorTools.duplicate(saved.block,()=>crypto.randomUUID().replace(/-/g,'').slice(0,10));blocks.push(copy);selected=blocks.length-1;Studio.modified();Studio.draw();},
|
||||
renderPalette(){let p=$('#palette');p.innerHTML=Object.entries(schema.blocks).map(([key,b])=>`<button onclick="Studio.add('${key}')"><span>${html(b.icon)}</span>${html(b.name)}</button>`).join('')},
|
||||
toggleLibrary(force){libraryOpen=typeof force==='boolean'?force:!libraryOpen;const el=$('#sfLibraryPanel'),btn=$('#sfAddBlock');if(el)el.hidden=!libraryOpen;if(btn){btn.setAttribute('aria-expanded',String(libraryOpen));btn.textContent=libraryOpen?'× 收起区块库':'+ 添加内容区块'};Studio.updateGuidance()},
|
||||
async choosePage(id){if(savePromise){toast('保存仍在进行,请稍后切换页面',true);$('#pageSelect').value=pageId;return}if(dirty&&!confirm('当前页面尚未保存,确定切换?')){$('#pageSelect').value=pageId;return}pageId=id;selectedChildId=null;insertAfterId=null;Studio.showInsertHint();$('#pageSelect').value=id;const data=await api(`/api/sites/${siteId}/pages/${id}/studio-draft`);draftRevision=data.revision;blocks=data.blocks.map(b=>structuredClone(b));kind=data.page_template;layoutMode=data.layout_mode||'append';$('#layoutMode').value=layoutMode;selected=blocks.length?0:-1;$('#pageTemplate').value=kind;$('#pageName').textContent=data.entry.title;$('#routeDisplay').textContent=data.entry.route;dirty=false;snapshots=[];snapshotCursor=-1;lastContinuousEdit=0;blockSearch='';$('#blockSearch').value='';recordSnapshot();Studio.updateHistoryButtons();$('#saveStatus').textContent=draftRevision?'草稿 v'+draftRevision:'未修改';$('#saveStatus').classList.remove('dirty');Studio.draw();Studio.refreshPreview()},
|
||||
async loadReusables(){try{reusables=await api(`/api/sites/${siteId}/components`);$('#reusablePalette').innerHTML=reusables.map(x=>`<div class="sf-shared-palette-row"><strong>${html(x.name)} <small>v${x.version}</small></strong><div><button type="button" onclick="Studio.insertReusable('${html(x.id)}')" title="复制后独立编辑">复制</button><button type="button" onclick="Studio.insertReusable('${html(x.id)}',true)" title="可以在检查后选择性同步的公共组件">关联插入</button></div></div>`).join('')||'<span style="font-size:12px;opacity:.55;padding:10px">暂无保存的组件</span>'}catch(e){console.warn(e)}},
|
||||
async saveReusable(){
|
||||
const root=blocks[selected];
|
||||
const found=selectedChildId?editorTools.findChildInRoot(blocks,root?.id,selectedChildId):null;
|
||||
if(selectedChildId&&!found)return toast('当前子组件已失效,请重新选中后保存',true);
|
||||
const block=found?.child||root;
|
||||
if(!block)return toast('请先选择需要保存的组件',true);
|
||||
const name=prompt('为此组件命名',block.title||schema.blocks[block.type]?.name);
|
||||
if(!name?.trim())return;
|
||||
try{await api(`/api/sites/${siteId}/components`,req('POST',{name,block}));
|
||||
await Studio.loadReusables();toast('已保存独立组件副本,其他页面不会自动更新');
|
||||
}catch(e){toast('保存组件失败:'+e.message,true)}
|
||||
},
|
||||
insertReusable(id,linked=false){
|
||||
const saved=reusables.find(x=>x.id===id);if(!saved)return;
|
||||
const copy=editorTools.duplicate(saved.block,()=>crypto.randomUUID().replace(/-/g,'').slice(0,10));
|
||||
if(linked)copy.shared_ref={id:saved.id,version:saved.version,hash:saved.snapshot_hash};
|
||||
const root=blocks[selected];
|
||||
const found=selectedChildId?editorTools.findChildInRoot(blocks,root?.id,selectedChildId):null;
|
||||
if(selectedChildId&&!found)return toast('当前子组件已失效,请重新选中后插入',true);
|
||||
// A nested reusable must never introduce a third layout container level.
|
||||
// Layout templates remain insertable as top-level blocks.
|
||||
if(found && copy.type!=='layout'){
|
||||
const parent=found.child.type==='layout'?found.child:found.parent;
|
||||
const column=found.child.type==='layout'?parent.columns[0]:parent.columns[found.columnIndex];
|
||||
if(!column||column.blocks.length>=60)return toast('此列已达到组件数量限制',true);
|
||||
const after=found.child.type==='layout'?column.blocks.length:found.childIndex+1;
|
||||
column.blocks.splice(after,0,copy);
|
||||
selected=found.parentIndex;selectedChildId=copy.id;
|
||||
}else{
|
||||
const foundIndex=insertAfterId?editorTools.findTopIndex(blocks,insertAfterId):-1;
|
||||
const at=foundIndex>=0?foundIndex+1:blocks.length;
|
||||
if(blocks.length>=60)return toast('页面顶层最多60个组件',true);
|
||||
blocks.splice(at,0,copy);selected=at;selectedChildId=null;
|
||||
}
|
||||
insertAfterId=null;Studio.showInsertHint();Studio.modified();Studio.draw();Studio.toggleLibrary(false);
|
||||
toast(linked?'已关联公共组件:修改源组件后仍需要审核并选择同步':'已插入独立组件副本;不会自动跟随更新');
|
||||
},
|
||||
detachShared(){
|
||||
const item=selectedChildId?editorTools.findChildInRoot(blocks,blocks[selected]?.id,selectedChildId)?.child:blocks[selected];
|
||||
if(!item?.shared_ref)return toast('当前区块未关联公共组件',true);
|
||||
if(!confirm('解除关联后,本区块仍保留当前内容,但以后不会参与公共组件同步。确定吗?'))return;
|
||||
delete item.shared_ref;Studio.modified();Studio.draw();toast('已解除关联。请保存草稿');
|
||||
},
|
||||
async showSharedComponents(){
|
||||
if(!reusables.length)await Studio.loadReusables();
|
||||
const dlg=$('#sharedComponentModal');if(!dlg.open)dlg.showModal();sharedPreview=null;
|
||||
$('#sharedComponentBody').innerHTML=reusables.map(x=>`<article class="sf-shared-card"><div><strong>${html(x.name)}</strong><small>当前版本 v${x.version} · 不会自动更新页面</small></div><div class="sf-shared-actions"><button type="button" onclick="Studio.sharedUsage('${html(x.id)}')">查看引用与同步</button><button type="button" onclick="Studio.sharedVersions('${html(x.id)}')">版本记录</button><button type="button" onclick="Studio.sharedReplace('${html(x.id)}')">用当前选中区块创建新版</button></div></article>`).join('')||'<p>暂无公共组件。先在页面里选择一个区块,再通过左侧「保存当前组件」创建。</p>';
|
||||
},
|
||||
async sharedVersions(id){
|
||||
try{const r=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/versions`);
|
||||
$('#sharedComponentDetail').innerHTML='<h3>组件历史版本</h3><p>恢复历史不会覆盖已有版本,而是创建新的源组件版本。</p>'+r.items.map(x=>`<div class="sf-shared-history"><span>v${x.version} · ${html(x.created_at)} · SHA ${html(x.snapshot_hash.slice(0,12))}</span><button type="button" class="btn ui-btn ghost" onclick="Studio.sharedRestore('${html(id)}',${x.version})">恢复为新版本</button></div>`).join('');
|
||||
}catch(e){toast(e.message,true)}
|
||||
},
|
||||
async sharedRestore(id,version){
|
||||
const current=reusables.find(x=>x.id===id);
|
||||
if(!current)return toast('组件信息已过期,请重新加载',true);
|
||||
if(!confirm(`将历史 v${version} 的内容恢复为全新的组件版本?所有网站页面仍保持不变。`))return;
|
||||
try{const snapshot=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/versions/${version}`);
|
||||
await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}`,req('PUT',{
|
||||
name:current.name,expected_version:current.version,block:snapshot.block}));
|
||||
await Studio.loadReusables();await Studio.showSharedComponents();await Studio.sharedVersions(id);
|
||||
toast('历史组件已恢复为新的源版本,请检查页面后再同步');
|
||||
}catch(e){toast('恢复失败:'+e.message,true)}
|
||||
},
|
||||
async sharedReplace(id){
|
||||
const chosen=selectedChildId?editorTools.findChildInRoot(blocks,blocks[selected]?.id,selectedChildId)?.child:blocks[selected];
|
||||
const current=reusables.find(x=>x.id===id);if(!chosen||!current)return toast('请先选择页面中的区块',true);
|
||||
if(!confirm(`将当前选中的区块保存为「${current.name}」的新版本?已发布页面不会变化。`))return;
|
||||
try{await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}`,req('PUT',{
|
||||
name:current.name,expected_version:current.version,block:chosen}));
|
||||
await Studio.loadReusables();await Studio.showSharedComponents();toast('新版本已保存。请检查引用页面后选择性同步');
|
||||
}catch(e){toast(e.message,true)}
|
||||
},
|
||||
async sharedUsage(id){
|
||||
try{sharedPreview=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/usage`);
|
||||
const preview=sharedPreview;
|
||||
$('#sharedComponentDetail').innerHTML=`<h3>${html(preview.component.name)} · 同步前检查</h3><p>只同步勾选的页面,并新建草稿;已发布网站不会立即变动。</p>`+
|
||||
(preview.references.length?preview.references.map(x=>`<label class="sf-shared-usage"><input type="checkbox" class="sf-shared-select" value="${html(x.entry_id)}" ${x.eligible?'':'disabled'}><span><strong>${html(x.title)}</strong><small>${html(x.route)} · v${html(x.current_version)} → v${html(preview.component.version)} ${x.reason?' · '+html(x.reason):''}</small></span></label>`).join(''):'<p>尚无关联页面。插入时请选择「关联插入」。</p>')+
|
||||
'<button type="button" class="ui-btn primary" onclick="Studio.sharedApply()">确认并同步勾选的页面草稿</button>';
|
||||
}catch(e){toast('读取引用失败:'+e.message,true)}
|
||||
},
|
||||
async sharedApply(){
|
||||
if(!sharedPreview)return;
|
||||
if(dirty)return toast('请先保存当前页面修改,再进行组件同步',true);
|
||||
const ids=[...new Set([...document.querySelectorAll('.sf-shared-select:checked')].map(x=>x.value))];
|
||||
if(!ids.length)return toast('请先选择可以同步的页面',true);
|
||||
if(!confirm(`确认更新 ${ids.length} 个页面的设计草稿?已发布页面不变,稍后需要逐页审核发布。`))return;
|
||||
try{const id=sharedPreview.component.id;
|
||||
const resp=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/sync`,req('POST',{plan_token:sharedPreview.plan_token,entry_ids:ids}));
|
||||
$('#sharedComponentModal').close();sharedPreview=null;
|
||||
if(ids.includes(pageId))await Studio.choosePage(pageId);
|
||||
toast(`已为 ${resp.updated.length} 个页面保存新草稿;请分别检查并发布`);
|
||||
}catch(e){toast('同步取消:'+e.message,true);await Studio.sharedUsage(sharedPreview.component.id)}
|
||||
},
|
||||
renderPalette(){
|
||||
const panel=$('#palette'),summary=$('#paletteSummary');if(!panel||!schema?.blocks)return;
|
||||
const grouped=window.SiteForgeStudioExperience.groupBlocks(schema.blocks,paletteSearch);
|
||||
const order=[['basic','常用内容'],['visual','图片与展示'],['business','产品与业务'],['advanced','复杂布局']];
|
||||
panel.innerHTML=order.map(([type,title])=>{
|
||||
const items=grouped[type]||[];if(!items.length)return '';
|
||||
return `<section class="sf-palette-group" aria-label="${title}"><h4>${title}</h4><div class="sf-palette-grid">${items.map(({key,block})=>`<button type="button" onclick="Studio.add('${html(key)}')"><span aria-hidden="true">${html(block.icon)}</span>${html(block.name)}</button>`).join('')}</div></section>`;
|
||||
}).join('')||'<p class="sf-palette-empty">没有找到对应组件,试试“文字”或“图片”。</p>';
|
||||
summary.textContent='找到 '+Object.values(grouped).reduce((n,x)=>n+x.length,0)+' 个可添加组件';
|
||||
},
|
||||
filterPalette(value){paletteSearch=String(value||'').slice(0,70);Studio.renderPalette()},
|
||||
clearPaletteSearch(){paletteSearch='';const inp=$('#paletteSearch');if(inp){inp.value='';inp.focus()}Studio.renderPalette()},
|
||||
showThemePreview(){const dialog=$('#themePreviewModal');if(!dialog)return;const current=['automotive','industrial','corporate','minimal'].includes(site?.theme)?site.theme:'corporate';$('#themePreviewTheme').value=current;if(!dialog.open)dialog.showModal();Studio.themeDevice('desktop');Studio.updateThemePreview()},
|
||||
closeThemePreview(){const dialog=$('#themePreviewModal');if(dialog?.open)dialog.close();const frame=$('#themePreviewFrame');if(frame)frame.removeAttribute('src')},
|
||||
themeDevice(device){
|
||||
const width={desktop:'100%',tablet:'780px',phone:'390px'}[device];if(!width)return;
|
||||
$('#themePreviewFrameShell').style.width=width;
|
||||
document.querySelectorAll('[data-theme-device]').forEach(btn=>btn.setAttribute('aria-pressed',String(btn.dataset.themeDevice===device)));
|
||||
},
|
||||
updateThemePreview(){
|
||||
const theme=$('#themePreviewTheme')?.value,page=$('#themePreviewPage')?.value;
|
||||
const url=window.SiteForgeStudioExperience.previewUrl(theme,page);
|
||||
if(!url)return;
|
||||
const frame=$('#themePreviewFrame'),status=$('#themePreviewStatus');if(!frame)return;
|
||||
status.textContent='正在加载主题示例…(不影响已保存的页面)';
|
||||
frame.onload=()=>{if($('#themePreviewModal')?.open)status.textContent='已加载示例。可切换主题、页面类型和设备继续比较。'};
|
||||
frame.onerror=()=>{status.textContent='示例加载失败,请稍后重试。'};
|
||||
frame.src=url;
|
||||
}, toggleLibrary(force){libraryOpen=typeof force==='boolean'?force:!libraryOpen;const el=$('#sfLibraryPanel'),btn=$('#sfAddBlock');if(el)el.hidden=!libraryOpen;if(btn){btn.setAttribute('aria-expanded',String(libraryOpen));btn.textContent=libraryOpen?'× 收起区块库':'+ 添加内容区块'};Studio.updateGuidance()},
|
||||
showHelp(){const dlg=$('#sfGuideDialog');if(dlg&&!dlg.open)dlg.showModal()},
|
||||
updateGuidance(){const one=$('#sfStep1'),two=$('#sfStep2'),three=$('#sfStep3');if(!one||!two||!three)return;one.classList.toggle('active',selected<0||libraryOpen);two.classList.toggle('active',selected>=0&&!dirty&&!libraryOpen);three.classList.toggle('active',dirty);},
|
||||
draw(){
|
||||
@@ -39,10 +316,10 @@ const Studio={
|
||||
changeInspectorTab(tab){if(!['content','style','responsive'].includes(tab))return;inspectorTab=tab;Studio.applyInspectorTab()},
|
||||
applyInspectorTab(){for(const key of ['content','style','responsive']){const active=key===inspectorTab;const btn=$('#tab'+key.charAt(0).toUpperCase()+key.slice(1));btn?.setAttribute('aria-selected',String(active));const pane=$('[data-inspector-section="'+key+'"]');if(pane)pane.hidden=!active}},
|
||||
togglePane(which){const type=which==='left'?'show-structure':'show-inspector';document.body.classList.toggle(type);if(type==='show-structure')document.body.classList.remove('show-inspector');else document.body.classList.remove('show-structure');},
|
||||
updateHistoryButtons(){const state=editorTools.historyState(snapshots,snapshotCursor);$('#studioUndo').disabled=!state.canUndo;$('#studioRedo').disabled=!state.canRedo},
|
||||
updateHistoryButtons(){const state=editorTools.historyState(snapshots,snapshotCursor);for(const id of ['#studioUndo','#quickUndo']){const el=$(id);if(el)el.disabled=!state.canUndo}for(const id of ['#studioRedo','#quickRedo']){const el=$(id);if(el)el.disabled=!state.canRedo}},
|
||||
|
||||
select(i){selected=i;Studio.draw();if(window.matchMedia('(max-width:950px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}},
|
||||
add(type){let data=structuredClone(schema.blocks[type].defaults);data.id=crypto.randomUUID().replace(/-/g,'').slice(0,10);data.type=type;data.visible=true;blocks.push(data);selected=blocks.length-1;Studio.modified();Studio.draw();Studio.toggleLibrary(false);if(window.matchMedia('(max-width:950px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}},
|
||||
select(i){selected=i;selectedChildId=null;Studio.draw();if(window.matchMedia('(max-width:950px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}},
|
||||
add(type){let data=structuredClone(schema.blocks[type].defaults);data.id=crypto.randomUUID().replace(/-/g,'').slice(0,10);data.type=type;data.visible=true;const found=insertAfterId?editorTools.findTopIndex(blocks,insertAfterId):-1;const at=found>=0?found+1:blocks.length;blocks.splice(at,0,data);selected=at;insertAfterId=null;Studio.showInsertHint();Studio.modified();Studio.draw();Studio.toggleLibrary(false);if(window.matchMedia('(max-width:950px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}},
|
||||
duplicate(i){const b=editorTools.duplicate(blocks[i],()=>crypto.randomUUID().replace(/-/g,'').slice(0,10));blocks.splice(i+1,0,b);selected=i+1;Studio.modified();Studio.draw()},
|
||||
remove(i){if(!confirm('确定删除这个区块?'))return;blocks.splice(i,1);selected=Math.min(i,blocks.length-1);Studio.modified();Studio.draw()},
|
||||
drag(e,i){e.dataTransfer.setData('text/plain',String(i));e.dataTransfer.effectAllowed='move';e.currentTarget.classList.add('dragging')},
|
||||
@@ -64,9 +341,17 @@ const Studio={
|
||||
removeItem(field,index){const rows=blocks[selected]?.[field];if(!Array.isArray(rows)||!rows[index])return;rows.splice(index,1);Studio.modified();Studio.inspector()},
|
||||
moveItem(field,index,by){const rows=blocks[selected]?.[field],target=index+by;if(!Array.isArray(rows)||target<0||target>=rows.length)return;[rows[index],rows[target]]=[rows[target],rows[index]];Studio.modified();Studio.inspector()},
|
||||
jsonField(key,value){try{let data=JSON.parse(value);if(!Array.isArray(data))throw Error('必须为数组');Studio.fieldQuiet(key,data);$('#jsonError').textContent='✓ JSON 格式有效';$('#jsonError').style.color='#238460'}catch(e){$('#jsonError').textContent='JSON 格式有误:'+e.message;$('#jsonError').style.color='#bf5a58'}},
|
||||
styleField(key,value){const b=blocks[selected];b.style={...(b.style||{}),[key]:value};Studio.modified()},
|
||||
styleField(key,value){const b=blocks[selected];if(!b)return;b.style={...(b.style||{}),[key]:value};Studio.modified()},
|
||||
applyStylePreset(preset){
|
||||
const block=blocks[selected];if(!block)return;
|
||||
const style=window.SiteForgeStudioExperience.stylePreset(site?.theme,preset);
|
||||
if(!style)return;
|
||||
block.style={...(block.style||{}),...style};
|
||||
Studio.modified();Studio.inspector();
|
||||
toast('已应用区块外观,保存草稿前可以撤销');
|
||||
},
|
||||
inspector(){let target=$('#inspector');let b=blocks[selected];$('#blockTypeLabel').textContent=b?schema.blocks[b.type].name:'页面';if(!b){target.innerHTML='<div class="no-selection"><b>还没有选中内容</b><p>先到左侧选择一个区块,或点击“添加内容区块”。</p><button type="button" class="ui-btn primary" onclick="Studio.toggleLibrary(true)">+ 添加内容</button></div>';return}const spec=schema.blocks[b.type];let out=`<div class="inspector-title"><h3>${html(spec.name)}</h3><label class="toggle"><input type="checkbox" ${b.visible!==false?'checked':''} onchange="Studio.field('visible',this.checked)">显示</label></div><div data-inspector-section="content">`;
|
||||
for(const field of spec.fields){let value=b[field]??'',label=({title:'标题',subtitle:'副标题',description:'简介',image:'图片地址',body:'正文 HTML',button:'按钮文字',href:'跳转路径',source:'绑定内容类型',limit:'显示条数',columns:'每行列数',items:'重复条目',images:'画廊图片',image_side:'图片方向',align:'标题对齐',height:'间隔高度'})[field]||field;
|
||||
for(const field of spec.fields.filter(f=>b.type!=='layout'||!['columns','gap','tablet_columns','mobile_columns'].includes(f))){let value=b[field]??'',label=({title:'标题',subtitle:'副标题',description:'简介',image:'图片地址',body:'正文 HTML',button:'按钮文字',href:'跳转路径',source:'绑定内容类型',limit:'显示条数',columns:'每行列数',items:'重复条目',images:'画廊图片',image_side:'图片方向',align:'标题对齐',height:'间隔高度'})[field]||field;
|
||||
out+=`<div class="prop"><label>${html(label)}</label>`;
|
||||
if(field==='items'||field==='images')out+=Studio.arrayEditor(field,value,b.type);
|
||||
else if(field==='source')out+=`<select onchange="Studio.field('${field}',this.value)">${['product','post','page','vehicle','brand','case','service','other','all'].map(t=>`<option value="${t}" ${t===value?'selected':''}>${({product:'产品',post:'文章',page:'普通页面',vehicle:'车型',brand:'品牌',case:'案例',service:'服务',other:'其他',all:'全部类型'})[t]}</option>`).join('')}</select><span class="prop-tip">网站已有内容将自动绑定并生成卡片。</span>`;
|
||||
@@ -77,9 +362,10 @@ const Studio={
|
||||
if(['title','subtitle','description','image','body'].includes(field))out+='<small class="prop-tip">可输入 {{site.name}}、{{page.title}} 等变量。</small>';
|
||||
out+='</div>';
|
||||
}
|
||||
if(b.type==='layout')out+=Studio.childQuickEditor(b);
|
||||
out+='</div><div data-inspector-section="responsive">';
|
||||
if(b.type==='layout'){
|
||||
out+=`<div class="subheading">列内组件 · 按设备排版</div><p class="prop-tip">分别调整平板/手机的列数、顺序、跨度与显隐。保存草稿后在设备预览切换查看效果。</p><div class="prop"><label>平板每行列数 <input type="number" min="1" max="3" value="${html(b.tablet_columns||2)}" onchange="Studio.setDeviceColumns('tablet',this.value)"></label><label>手机每行列数 <input type="number" min="1" max="2" value="${html(b.mobile_columns||1)}" onchange="Studio.setDeviceColumns('mobile',this.value)"></label></div>`;
|
||||
out+=`<div class="subheading">快捷布局</div><p class="prop-tip">常用排版一键设置,也可直接拖动画布中的列宽分隔线。</p><div class="sf-layout-presets"><button type="button" onclick="Studio.setLayoutPreset('equal')">均分列宽</button><button type="button" ${b.columns.length===2?'':'disabled'} onclick="Studio.setLayoutPreset('left-wide')">左宽右窄</button><button type="button" ${b.columns.length===2?'':'disabled'} onclick="Studio.setLayoutPreset('right-wide')">左窄右宽</button></div><div class="prop"><label>列间距(0–64px)</label><input type="number" min="0" max="64" value="${html(b.gap??24)}" onchange="Studio.setLayoutGap(this.value)"></div><div class="subheading">手机与平板</div><p class="prop-tip">选 1 列更适合手机阅读。以下设置只影响对应设备,不更改桌面列宽。</p><div class="prop"><label>平板每行列数 <input type="number" min="1" max="3" value="${html(b.tablet_columns||2)}" onchange="Studio.setDeviceColumns('tablet',this.value)"></label><label>手机每行列数 <input type="number" min="1" max="2" value="${html(b.mobile_columns||1)}" onchange="Studio.setDeviceColumns('mobile',this.value)"></label></div>`;
|
||||
b.columns.forEach((column,ci)=>{
|
||||
out+=`<div class="layout-editor-col" ondragover="event.preventDefault()" ondrop="Studio.dropChild(event,${ci})"><div class="layout-editor-title"><strong>第${ci+1}列</strong><label>宽度 <input type="number" min="0.5" max="4" step="0.5" value="${html(column.width)}" onchange="Studio.setColumnWidth(${ci},this.value)"></label><button onclick="Studio.removeColumn(${ci})">删除列</button></div>`;
|
||||
out+=`<div class="prop"><label>平板排序 <input type="number" min="0" max="20" value="${html(column.tablet_order??(ci+1))}" onchange="Studio.setColumnDevice(${ci},'tablet_order',Number(this.value))"></label><label>手机排序 <input type="number" min="0" max="20" value="${html(column.mobile_order??(ci+1))}" onchange="Studio.setColumnDevice(${ci},'mobile_order',Number(this.value))"></label><label>平板跨列 <input type="number" min="1" max="${html(b.tablet_columns||2)}" value="${html(column.tablet_span||1)}" onchange="Studio.setColumnDevice(${ci},'tablet_span',Number(this.value))"></label><label>手机跨列 <input type="number" min="1" max="${html(b.mobile_columns||1)}" value="${html(column.mobile_span||1)}" onchange="Studio.setColumnDevice(${ci},'mobile_span',Number(this.value))"></label><label><input type="checkbox" ${column.hide_tablet?'checked':''} onchange="Studio.setColumnDevice(${ci},'hide_tablet',this.checked)"> 平板隐藏此列</label><label><input type="checkbox" ${column.hide_mobile?'checked':''} onchange="Studio.setColumnDevice(${ci},'hide_mobile',this.checked)"> 手机隐藏此列</label></div>`;
|
||||
@@ -88,12 +374,14 @@ const Studio={
|
||||
if(b.columns.length>1){out+=`<select onchange="Studio.moveChild(${ci},${j},Number(this.value))"><option value="">移动到其他列</option>${b.columns.map((c,ki)=>ki===ci?'':`<option value="${ki}">第${ki+1}列</option>`).join('')}</select>`}
|
||||
out+='</div>';
|
||||
});
|
||||
out+=`<div class="layout-child-toolbar">${['richtext','image_text','collection','cta','features'].map(t=>`<button onclick="Studio.addChild(${ci},'${t}')">+${html(schema.blocks[t].name)}</button>`).join('')}</div></div>`;
|
||||
out+=`<div class="layout-child-toolbar">${['richtext','image_text','collection','cta','features','layout'].map(t=>`<button onclick="Studio.addChild(${ci},'${t}')">+${t==='layout'?'内层布局':html(schema.blocks[t].name)}</button>`).join('')}</div></div>`;
|
||||
});
|
||||
out+='<button class="ui-btn ghost" onclick="Studio.addColumn()">+ 添加列</button>';
|
||||
}
|
||||
out+=`<div class="subheading">响应式样式</div><div class="prop"><label>平板上下间距(px)</label><input type="number" min="0" max="120" value="${html(b.style?.tablet_padding??50)}" onchange="Studio.styleField('tablet_padding',Number(this.value))"></div><div class="prop"><label>手机上下间距(px)</label><input type="number" min="0" max="120" value="${html(b.style?.mobile_padding??28)}" onchange="Studio.styleField('mobile_padding',Number(this.value))"></div><div class="prop"><label><input type="checkbox" ${b.style?.hide_mobile?'checked':''} onchange="Studio.styleField('hide_mobile',this.checked)"> 手机上隐藏区块</label></div>`;
|
||||
out+=`</div><div data-inspector-section="style"><div class="subheading">外观设置</div><div class="prop"><label>背景颜色</label><input type="color" value="${html(b.style?.background||'#ffffff')}" onchange="Studio.styleField('background',this.value)"></div><div class="prop"><label>上下间距(px)</label><input type="number" min="0" max="120" value="${html(b.style?.padding??82)}" onchange="Studio.styleField('padding',Number(this.value))"></div><div class="prop"><label>从图库选择图片</label><button type="button" class="ui-btn ghost" onclick="Studio.chooseMedia()">打开本站素材库</button></div><div class="prop"><label>上传图片</label><input type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/avif" onchange="Studio.uploadImage(this.files[0])"></div></div>`;
|
||||
out+=`</div><div data-inspector-section="style"><div class="subheading">快速设置外观</div><p class="prop-tip">不必先调色值:选择样式后会立即更新预览,也可以撤销。</p><div class="sf-style-presets"><button type="button" onclick="Studio.applyStylePreset('clean')">清爽白底</button><button type="button" onclick="Studio.applyStylePreset('soft')">柔和浅底</button><button type="button" onclick="Studio.applyStylePreset('feature')">重点深底</button></div><div class="subheading">自定义外观</div><div class="prop"><label>背景颜色</label><input type="color" value="${html(b.style?.background||'#ffffff')}" onchange="Studio.styleField('background',this.value)"></div><div class="prop"><label>上下间距(px)</label><input type="number" min="0" max="120" value="${html(b.style?.padding??82)}" onchange="Studio.styleField('padding',Number(this.value))"></div><div class="prop"><label>从图库选择图片</label><button type="button" class="ui-btn ghost" onclick="Studio.chooseMedia()">打开本站素材库</button></div><div class="prop"><label>上传图片</label><input type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/avif" onchange="Studio.uploadImage(this.files[0])"></div></div>`;
|
||||
const sharing=selectedChildId?editorTools.findChildInRoot(blocks,b.id,selectedChildId)?.child?.shared_ref:b.shared_ref;
|
||||
if(sharing)out+=`<div class="sf-share-info"><strong>已关联公共组件 · v${html(sharing.version)}</strong><p>此区块将来可以按需同步新版。直接修改后会被视为本地定制。</p><button type="button" class="ui-btn ghost" onclick="Studio.detachShared()">解除关联,保留当前内容</button></div>`;
|
||||
out+='<div style="display:flex;gap:8px;margin-bottom:22px"><button class="small-btn" onclick="Studio.duplicate('+selected+')">复制区块</button><button class="small-btn red-btn" onclick="Studio.remove('+selected+')">删除区块</button></div>';
|
||||
target.innerHTML=out;Studio.applyInspectorTab();
|
||||
},
|
||||
@@ -115,7 +403,7 @@ const Studio={
|
||||
try{
|
||||
const doc=await api(`/api/sites/${siteId}/pages/${page}/editor-preview`,{...req('POST',{blocks,page_template:kind,layout_mode:layoutMode,site_design:siteDesignPreview}),signal:previewController.signal});
|
||||
if(!editorTools.shouldAcceptPreview(page,seq,pageId,previewRequest))return;
|
||||
frame.srcdoc=doc;$('#previewState').textContent='实时预览 · 已更新';$('#previewDot').classList.remove('loading','error');
|
||||
frame.onload=()=>Studio.highlightCanvas();frame.srcdoc=doc;$('#previewState').textContent='实时预览 · 已更新';$('#previewDot').classList.remove('loading','error');
|
||||
}catch(e){
|
||||
if(!editorTools.shouldAcceptPreview(page,seq,pageId,previewRequest)||e.name==='AbortError')return;
|
||||
$('#previewState').textContent='预览更新失败';$('#previewDot').classList.remove('loading');$('#previewDot').classList.add('error');toast('预览失败:'+e.message,true);
|
||||
@@ -161,6 +449,23 @@ const Studio={
|
||||
async discardDraft(){if(!draftRevision)return;if(!confirm('确定放弃草稿?'))return;try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft`,{method:'DELETE'});dirty=false;await Studio.choosePage(pageId);toast('草稿已放弃')}catch(e){toast(e.message,true)}},
|
||||
dragChild(event,col,index){draggedChild={container:selected,col,index};event.dataTransfer.setData('application/siteforge-child','1');event.dataTransfer.effectAllowed='move';event.stopPropagation()},
|
||||
dropChild(event,destCol){if(!draggedChild)return;event.preventDefault();event.stopPropagation();const drag=draggedChild;draggedChild=null;const src=blocks[drag.container],dest=blocks[selected];if(!src||!dest||src.type!=='layout'||dest.type!=='layout')return;const moved=src.columns?.[drag.col]?.blocks.splice(drag.index,1)[0];if(!moved)return;dest.columns[destCol].blocks.push(moved);Studio.modified();Studio.draw()},
|
||||
drawSiteNav(){
|
||||
const root=$('#siteNavRows');root.replaceChildren();
|
||||
const draw=(items,depth=0)=>items.forEach((node,index)=>{
|
||||
const line=document.createElement('div');line.className='sf-nav-item';line.style.marginLeft=`${depth*16}px`;
|
||||
const title=document.createElement('input');title.placeholder='菜单名称';title.maxLength=48;title.value=node.title||'';title.setAttribute('aria-label','菜单名称');title.oninput=()=>node.title=title.value;
|
||||
const route=document.createElement('input');route.placeholder='/products/';route.value=node.route||'/';route.setAttribute('aria-label','站内路径');route.oninput=()=>node.route=route.value;
|
||||
line.append(title,route);
|
||||
const op=(label,fn)=>{const button=document.createElement('button');button.type='button';button.className='ui-btn ghost sf-nav-action';button.textContent=label;button.onclick=fn;line.append(button)};
|
||||
if(depth<2)op('+ 子菜单',()=>{node.children=node.children||[];node.children.push({title:'新菜单',route:'/',children:[]});Studio.drawSiteNav()});
|
||||
if(index)op('↑',()=>{[items[index-1],items[index]]=[items[index],items[index-1]];Studio.drawSiteNav()});
|
||||
if(index<items.length-1)op('↓',()=>{[items[index+1],items[index]]=[items[index],items[index+1]];Studio.drawSiteNav()});
|
||||
op('删除',()=>{items.splice(index,1);Studio.drawSiteNav()});
|
||||
root.append(line);if(node.children?.length)draw(node.children,depth+1);
|
||||
});
|
||||
draw(siteNavDraft);
|
||||
},
|
||||
addSiteNav(){siteNavDraft.push({title:'新菜单',route:'/',children:[]});Studio.drawSiteNav()},
|
||||
async showSiteDesign(){try{
|
||||
const result=await api(`/api/sites/${siteId}/site-design`);
|
||||
siteDesign=result.config;siteDesignRevision=result.revision;siteDesignPreview=structuredClone(siteDesign);
|
||||
@@ -168,7 +473,7 @@ const Studio={
|
||||
for(const [name,value] of Object.entries(h)){const el=form.elements.namedItem(name);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
|
||||
for(const [name,value] of Object.entries(f)){const key='footer_'+name,el=form.elements.namedItem(key);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
|
||||
for(const [name,value] of Object.entries(r)){const el=form.elements.namedItem(name);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
|
||||
form.elements.namedItem('nav').value=JSON.stringify(siteDesign.nav,null,2);
|
||||
siteNavDraft=structuredClone(siteDesign.nav||[]);Studio.drawSiteNav();
|
||||
form.elements.namedItem('mobile_tabs_enabled').checked=Boolean(siteDesign.mobile_tabs?.enabled);
|
||||
Studio.drawMobileTabs(siteDesign.mobile_tabs?.items||[]);
|
||||
Studio.toggleMobileTabsForm();
|
||||
@@ -194,8 +499,7 @@ const Studio={
|
||||
collectSiteDesign(){const form=$('#siteDesignForm');if(!siteDesign)throw Error('请先打开全站设计');
|
||||
const v=(n)=>form.elements.namedItem(n).value;
|
||||
const yes=(n)=>form.elements.namedItem(n).checked;
|
||||
let nav;try{nav=JSON.parse(v('nav'))}catch(e){throw Error('导航必须是合法 JSON:'+e.message)}
|
||||
if(!Array.isArray(nav))throw Error('导航必须是数组');
|
||||
const nav=structuredClone(siteNavDraft);
|
||||
const tabs=[...$('#mobileTabsRows').children].map(row=>({title:row.querySelector('.mt-title').value.trim(),icon:row.querySelector('.mt-icon').value,route:row.querySelector('.mt-route').value.trim(),action:row.querySelector('.mt-action').value}));
|
||||
if(yes('mobile_tabs_enabled')&&!tabs.length)throw Error('开启底部 Tabs 后,请至少添加一个按钮');
|
||||
return {tokens:structuredClone(siteDesign.tokens),mobile_tabs:{enabled:yes('mobile_tabs_enabled'),items:tabs},header:{announcement:v('announcement'),show_announcement:yes('show_announcement'),show_search:yes('show_search'),
|
||||
@@ -211,12 +515,73 @@ const Studio={
|
||||
siteDesignRevision=saved.revision;siteDesign=config;siteDesignPreview=structuredClone(config);
|
||||
$('#siteDesignStatus').textContent=`草稿 v${siteDesignRevision} 已保存,正式构建保持不变`;toast('全站设计草稿保存成功');return true;
|
||||
}catch(e){toast('全站草稿保存失败:'+e.message,true);return false}},
|
||||
async publishSiteDesign(){if(!confirm('发布全站设计会影响该站所有页面的下一次静态构建。确定继续?'))return;
|
||||
async publishSiteDesign(){
|
||||
if(!await Studio.saveSiteDesign())return;
|
||||
try{await api(`/api/sites/${siteId}/site-design/publish`,req('POST',{expected_revision:siteDesignRevision}));
|
||||
siteDesignRevision=0;$('#siteDesignStatus').textContent='全站设计已发布,请重新构建网站';
|
||||
$('#siteDesignModal').close();await Studio.refreshPreview();toast('全站设计已发布;现有上线网站仍不改变');
|
||||
}catch(e){toast('发布失败:'+e.message,true)}},
|
||||
try{
|
||||
const review=await api(`/api/sites/${siteId}/site-design/review`);
|
||||
siteDesignReviewToken=review.plan_token;
|
||||
const box=$('#siteDesignReviewBody');box.replaceChildren();
|
||||
const lead=document.createElement('p');lead.textContent=`草稿 v${review.revision} · ${review.change_count} 处字段变更 · 影响本站下一次构建的全部页面`;box.append(lead);
|
||||
if(!review.change_count){const empty=document.createElement('p');empty.textContent='没有任何实际配置变化。可以返回编辑。';box.append(empty);}
|
||||
review.changed_fields.forEach(change=>{
|
||||
const article=document.createElement('article');article.className='sf-review-change';
|
||||
const label=document.createElement('strong');label.textContent=Studio.designFieldLabel(change.field);article.append(label);
|
||||
const old=document.createElement('p');old.textContent='原配置:'+Studio.formatDesignDifference(change.before,change.field);
|
||||
const newer=document.createElement('p');newer.textContent='新配置:'+Studio.formatDesignDifference(change.after,change.field);
|
||||
article.append(old,newer);box.append(article);
|
||||
});
|
||||
$('#siteDesignReviewModal').showModal();
|
||||
}catch(e){toast('审核预览失败:'+e.message,true)}
|
||||
},
|
||||
designFieldLabel(key){const names={header:'页眉',footer:'页脚',nav:'网站导航',mobile_tabs:'手机底部导航',tokens:'全站风格',responsive:'响应式布局',announcement:'提示文字',show_announcement:'显示提示栏',show_search:'显示搜索',show_cta:'显示联系按钮',cta_text:'按钮文字',cta_href:'按钮地址',description:'介绍文字',email:'邮箱',title:'标题',items:'导航项目',enabled:'是否启用'};return key.split('.').map(v=>names[v]||v).join(' · ')},
|
||||
formatDesignDifference(value,field=''){
|
||||
if(typeof value==='boolean')return value?'已开启':'已关闭';
|
||||
if(value==null)return '未设置';
|
||||
if(field==='nav'&&Array.isArray(value)){
|
||||
const parts=[];const walk=(items,depth)=>items.forEach(n=>{parts.push(' '.repeat(depth)+(n.title||'未命名')+' → '+(n.route||'/'));walk(n.children||[],depth+1)});walk(value,0);
|
||||
return parts.length?parts.join('\n'):'(未配置导航)';
|
||||
}
|
||||
if(field==='mobile_tabs'&&value&&typeof value==='object'){
|
||||
return value.enabled?'已启用:'+(value.items||[]).map(v=>v.title+' → '+(v.action==='menu'?'打开菜单':v.route)).join(' · '):'未启用';
|
||||
}
|
||||
if(Array.isArray(value))return value.map(v=>typeof v==='object'?(v.title||JSON.stringify(v)):v).join('、')||'空列表';
|
||||
const text=typeof value==='string'?value:JSON.stringify(value);
|
||||
return text.length>440?text.slice(0,440)+'…':text;
|
||||
},
|
||||
async confirmReviewedSiteDesign(){
|
||||
if(!siteDesignReviewToken)return toast('请先查看发布差异',true);
|
||||
try{
|
||||
await api(`/api/sites/${siteId}/site-design/publish-reviewed`,req('POST',{
|
||||
expected_revision:siteDesignRevision,plan_token:siteDesignReviewToken}));
|
||||
siteDesignReviewToken=null;siteDesignRevision=0;siteDesignPreview=null;
|
||||
$('#siteDesignReviewModal').close();$('#siteDesignModal').close();
|
||||
$('#siteDesignStatus').textContent='全站设计已发布到内容库,下一次构建后生效';
|
||||
await Studio.refreshPreview();toast('全站设计已审核并发布,请构建及部署网站。');
|
||||
}catch(e){toast('发布失败:'+e.message,true)}
|
||||
},
|
||||
async showSiteDesignHistory(){
|
||||
try{
|
||||
const items=await api(`/api/sites/${siteId}/site-design/publications`),box=$('#siteDesignHistoryBody');box.replaceChildren();
|
||||
if(!items.length){box.textContent='尚无发布记录';}
|
||||
for(const item of items){
|
||||
const row=document.createElement('div');row.className='sf-review-change';
|
||||
const title=document.createElement('strong');title.textContent=`${item.published_at} · 版本 ${item.revision}`;row.append(title);
|
||||
const show=document.createElement('button');show.type='button';show.className='ui-btn ghost';show.textContent='查看变更';
|
||||
show.onclick=async()=>{try{const detail=await api(`/api/sites/${siteId}/site-design/publications/${item.id}`);const p=document.createElement('p');p.textContent=detail.changes.map(x=>x.field).join('、')||'无字段变化';row.append(p);show.disabled=true;}catch(e){toast(e.message,true)}};
|
||||
const restore=document.createElement('button');restore.type='button';restore.className='ui-btn ghost';restore.textContent='恢复为新草稿';
|
||||
restore.onclick=async()=>{
|
||||
if(!confirm('确认以这个历史版本生成新的全站设计草稿?不会直接更改线上页面。'))return;
|
||||
try{const live=await api(`/api/sites/${siteId}/site-design/live-hash`);
|
||||
await api(`/api/sites/${siteId}/site-design/publications/${item.id}/restore`,req('POST',{
|
||||
snapshot:'after',expected_revision:siteDesignRevision,expected_live_hash:live.live_hash}));
|
||||
$('#siteDesignHistoryModal').close();await Studio.showSiteDesign();toast('历史版本已恢复为新草稿,请检查后发布');
|
||||
}catch(e){toast(e.message,true)}
|
||||
};
|
||||
row.append(show,restore);box.append(row);
|
||||
}
|
||||
$('#siteDesignHistoryModal').showModal();
|
||||
}catch(e){toast(e.message,true)}
|
||||
},
|
||||
async discardSiteDesign(){if(!confirm('确定删除尚未发布的全站草稿?'))return;
|
||||
try{await api(`/api/sites/${siteId}/site-design`,{method:'DELETE'});siteDesignPreview=null;
|
||||
await Studio.showSiteDesign();await Studio.refreshPreview();toast('全站草稿已放弃')
|
||||
@@ -227,14 +592,25 @@ const Studio={
|
||||
setColumnDevice(i,key,value){const b=blocks[selected];if(!b||b.type!=='layout')return;const col=b.columns[i];if(!col)return;col[key]=value;Studio.modified();Studio.inspector()},
|
||||
addColumn(){const b=blocks[selected];if(!b||b.type!=='layout'||b.columns.length>=4)return;b.columns.push({width:1,blocks:[]});Studio.modified();Studio.inspector()},
|
||||
removeColumn(i){const b=blocks[selected];if(!b||b.type!=='layout'||b.columns.length<=1)return;if(b.columns[i].blocks.length&&!confirm('这一列含有组件,确认删除?'))return;b.columns.splice(i,1);Studio.modified();Studio.inspector()},
|
||||
setColumnWidth(i,value){const b=blocks[selected];if(!b||b.type!=='layout')return;b.columns[i].width=Math.max(.5,Math.min(4,Number(value)||1));Studio.modified()},
|
||||
setColumnWidth(i,value){const b=blocks[selected];if(!b||b.type!=='layout'||!b.columns[i])return;b.columns[i].width=Math.round(Math.max(.5,Math.min(4,Number(value)||1))*100)/100;Studio.modified();Studio.inspector()},
|
||||
addChild(i,type){const b=blocks[selected];if(!b||b.type!=='layout')return;const item=structuredClone(schema.blocks[type].defaults);item.type=type;item.id=crypto.randomUUID().replace(/-/g,'').slice(0,10);item.visible=true;b.columns[i].blocks.push(item);Studio.modified();Studio.inspector()},
|
||||
removeChild(i,j){const b=blocks[selected];if(!b||b.type!=='layout')return;b.columns[i].blocks.splice(j,1);Studio.modified();Studio.inspector()},
|
||||
childField(i,j,key,value){const b=blocks[selected];if(!b||b.type!=='layout')return;b.columns[i].blocks[j][key]=value;Studio.modified()},
|
||||
moveChild(ci,ri,target){const b=blocks[selected];if(!b||b.type!=='layout'||target<0||target>=b.columns.length)return;let [component]=b.columns[ci].blocks.splice(ri,1);b.columns[target].blocks.push(component);Studio.modified();Studio.inspector()},
|
||||
async chooseMedia(){try{const data=await api(`/api/sites/${siteId}/media`);const grid=$('#siteMediaGrid');grid.replaceChildren();if(!data.length){grid.textContent='暂无上传图片。可先在当前组件中上传图片。'}for(const item of data){const button=document.createElement('button');button.type='button';button.style.cssText='border:1px solid #e2e8f0;border-radius:9px;background:white;padding:7px;cursor:pointer';const img=document.createElement('img');img.alt=item.name;img.loading='lazy';img.style.cssText='width:100%;height:105px;object-fit:contain';img.src=`/local-media/${encodeURIComponent(siteId)}/${encodeURIComponent(item.name)}`;const label=document.createElement('small');label.textContent=item.name;label.style.cssText='display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';button.append(img,label);button.addEventListener('click',()=>Studio.applyMedia(item.url));grid.append(button)}$('#siteMediaModal').showModal()}catch(e){toast(e.message,true)}},
|
||||
applyMedia(url){const b=blocks[selected];if(!b)return;if(b.type==='gallery'){b.images=[...(b.images||[]),{url,alt:''}]}else if(b.type==='carousel'){b.items=[...(b.items||[]),{image:url,alt:'',caption:''}]}else{b.image=url}Studio.modified();Studio.draw();$('#siteMediaModal').close();toast('已应用素材到草稿')},
|
||||
async uploadImage(file){if(!file)return;try{const form=new FormData();form.append('file',file);let r=await api(`/api/sites/${siteId}/media`,{method:'POST',body:form});Studio.field('image',r.url);toast('图片已上传,保存页面后参与构建')}catch(e){toast(e.message,true)}},
|
||||
applyMedia(url){
|
||||
const root=blocks[selected];if(!root)return;
|
||||
const found=selectedChildId?editorTools.findChildInRoot(blocks,root.id,selectedChildId):null;
|
||||
if(selectedChildId&&!found)return toast('当前子组件已失效,请重新选中后添加图片',true);
|
||||
const block=found?.child||root;
|
||||
const fields=schema?.blocks?.[block.type]?.fields||[];
|
||||
if(block.type==='gallery')block.images=[...(block.images||[]),{url,alt:''}];
|
||||
else if(block.type==='carousel')block.items=[...(block.items||[]),{image:url,alt:'',caption:''}];
|
||||
else if(fields.includes('image'))block.image=url;
|
||||
else return toast('选中的组件不支持图片,请先选择一个图片组件',true);
|
||||
Studio.modified();Studio.draw();$('#siteMediaModal').close();toast('已应用素材到草稿');
|
||||
},
|
||||
async uploadImage(file){if(!file)return;try{const form=new FormData();form.append('file',file);let r=await api(`/api/sites/${siteId}/media`,{method:'POST',body:form});Studio.applyMedia(r.url);toast('图片已上传,保存页面后参与构建')}catch(e){toast(e.message,true)}},
|
||||
async buildSite(){if(dirty||draftRevision||siteDesignRevision)return toast('有尚未发布的页面或全站草稿,请先发布,再构建网站',true);try{
|
||||
toast('已提交后台构建任务');const task=await api(`/api/sites/${siteId}/builds`,req('POST',{mirror_images:false}));
|
||||
for(let i=0;i<240;i++){
|
||||
@@ -250,22 +626,10 @@ window.addEventListener('DOMContentLoaded',()=>Studio.init());
|
||||
window.addEventListener('beforeunload',e=>{if(dirty){e.preventDefault();e.returnValue='';}});
|
||||
|
||||
window.addEventListener('message',event=>{
|
||||
const frame=document.querySelector('#sitePreview');
|
||||
if(!frame||event.source!==frame.contentWindow)return;
|
||||
const data=event.data||{};
|
||||
if(data.source==='siteforge-select-block'){
|
||||
if(typeof data.blockId!=='string'||data.blockId.length>150)return;
|
||||
const findTop=rows=>{for(let i=0;i<rows.length;i++){const row=rows[i];if(row.id===data.blockId)return i;if(row.type==='layout'&&row.columns?.some(col=>col.blocks?.some(child=>child.id===data.blockId)))return i}return -1};
|
||||
const index=findTop(blocks);if(index>=0){selected=index;Studio.draw();if(window.matchMedia('(max-width:1060px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}}
|
||||
return;
|
||||
}
|
||||
if(data.source!=='siteforge-inline-editor')return;
|
||||
const findChild=rows=>{for(const row of rows){if(row.id===data.blockId)return {block:row,parent:row};if(row.type==='layout'){for(const col of row.columns||[]){const child=findChild(col.blocks||[]);if(child)return {block:child.block,parent:row};}}}return null};
|
||||
const found=findChild(blocks),b=found?.block;
|
||||
if(!b||!['title','description'].includes(data.field)||typeof data.value!=='string')return;
|
||||
b[data.field]=data.value.slice(0,500);
|
||||
selected=blocks.indexOf(found.parent);Studio.modified();Studio.draw();
|
||||
toast('已修改文字,点击保存页面写入数据库');
|
||||
const frame=document.querySelector('#sitePreview');
|
||||
if(!frame||event.source!==frame.contentWindow)return;
|
||||
const data=event.data;
|
||||
if(data?.source==='siteforge-studio-canvas')Studio.canvasCommand(data);
|
||||
});
|
||||
|
||||
window.addEventListener('keydown',event=>{
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
const q=panel.querySelector('[data-pro-filter-search]'),b=panel.querySelector('[data-pro-filter-brand]'),c=panel.querySelector('[data-pro-filter-category]'),count=panel.querySelector('[data-pro-filter-count]');
|
||||
function options(sel,key){const vals=[...new Set(cards.map(x=>x.dataset[key]||'').filter(Boolean))].sort();vals.forEach(x=>sel.add(new Option(x,x)));if(!vals.length)sel.closest('label').hidden=true;}
|
||||
options(b,'brand');options(c,'category');
|
||||
// Support shareable searches from automotive homepage without changing page URLs.
|
||||
const params=new URLSearchParams(window.location.search);
|
||||
if(q && params.has('q')) q.value=(params.get('q')||'').slice(0,120);
|
||||
if(b && params.has('brand') && [...b.options].some(o=>o.value===params.get('brand'))) b.value=params.get('brand');
|
||||
function run(){const str=q.value.trim().toLocaleLowerCase();let visible=0;
|
||||
cards.forEach(el=>{const hit=el.textContent.toLocaleLowerCase().includes(str)&&(!b.value||el.dataset.brand===b.value)&&(!c.value||el.dataset.category===c.value);el.hidden=!hit;visible+=Number(hit);});count.textContent=`共 ${visible} 条`;blank.hidden=visible!==0;}
|
||||
[q,b,c].forEach(x=>x.addEventListener(x===q?'input':'change',run));run();
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/* V8.22 trusted, sandboxed studio canvas bridge. No arbitrary user code. */
|
||||
(function(){
|
||||
'use strict';
|
||||
const source='siteforge-studio-canvas';
|
||||
const list=()=>Array.from(document.querySelectorAll('.sf-section[data-block]')).filter(n=>!n.parentElement.closest('.sf-section[data-block]'));
|
||||
const send=(action,id,extra={})=>parent.postMessage({source,action,blockId:id,...extra},'*');
|
||||
let active='', dragging='',resize=null;
|
||||
const all=()=>Array.from(document.querySelectorAll('.sf-section[data-block]'));
|
||||
const style=document.createElement('style');
|
||||
style.textContent=`
|
||||
.sf-section[data-block]{position:relative}
|
||||
.sf-section[data-block].sf-canvas-selected{outline:2px solid #ec7c35!important;outline-offset:-2px}
|
||||
.sf-section[data-block].sf-canvas-drop{box-shadow:inset 0 4px 0 #ef8741!important}
|
||||
.sf-section[data-block]:hover{outline:1px dashed #eb995a;outline-offset:-1px}
|
||||
.sf-canvas-toolbar{position:absolute;right:7px;top:5px;z-index:2147480000;display:none;align-items:center;gap:3px;padding:5px;background:#263447;color:#fff;border-radius:9px;box-shadow:0 5px 20px #20304450;font:12px/1.4 system-ui,sans-serif;white-space:nowrap;pointer-events:auto}
|
||||
.sf-canvas-selected>.sf-canvas-toolbar,.sf-section[data-block]:hover>.sf-canvas-toolbar,.sf-canvas-toolbar:focus-within{display:flex}
|
||||
.sf-canvas-toolbar button{background:transparent;color:inherit;border:0;cursor:pointer;padding:5px 7px;border-radius:5px;font:inherit}
|
||||
.sf-canvas-toolbar button:hover,.sf-canvas-toolbar button:focus-visible{background:#52687e;outline:1px solid #fff6}
|
||||
.sf-canvas-toolbar .sf-canvas-drag{cursor:grab;background:#496174}
|
||||
.sf-canvas-toolbar .sf-canvas-danger:hover{background:#ab4d46}
|
||||
.sf-section.sf-canvas-child-selected{outline:2px solid #0e97ac!important;outline-offset:-2px}
|
||||
.sf-nested-toolbar{position:absolute;top:6px;left:6px;z-index:2147480001;display:none;gap:3px;background:#126c80;color:#fff;padding:5px;border-radius:8px;font:12px system-ui;pointer-events:auto}
|
||||
.sf-canvas-child-selected>.sf-nested-toolbar,.sf-section[data-block]:hover>.sf-nested-toolbar,.sf-nested-toolbar:focus-within{display:flex}
|
||||
.sf-nested-toolbar button{color:#fff;background:transparent;border:0;border-radius:5px;cursor:pointer;padding:5px 7px}
|
||||
.sf-nested-toolbar button:hover,.sf-nested-toolbar button:focus-visible{background:#248ea4;outline:1px solid #fff6}
|
||||
.sf-canvas-hint{position:fixed;bottom:12px;left:50%;transform:translateX(-50%);background:#1c293ee8;color:white;padding:9px 13px;border-radius:9px;z-index:2147480000;font:12px system-ui;pointer-events:none}
|
||||
.sf-layout-grid{position:relative}
|
||||
.sf-canvas-resize{position:absolute;z-index:2147480000;top:4px;bottom:4px;width:12px;transform:translateX(-50%);cursor:col-resize;touch-action:none;opacity:0;display:flex;align-items:center;justify-content:center}
|
||||
.sf-canvas-resize:after{content:'⋮';background:#e77632;color:white;box-shadow:0 2px 8px #2225;border-radius:8px;font:20px/26px system-ui;text-align:center;width:19px;min-width:19px}
|
||||
.sf-section:hover .sf-canvas-resize,.sf-canvas-resize:focus-visible,.sf-canvas-resize.sf-resizing{opacity:1}
|
||||
[contenteditable].sf-canvas-editing{outline:2px dashed #df7435!important;border-radius:3px;cursor:text!important}
|
||||
@media(max-width:820px){.sf-canvas-resize{display:none!important}}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
const types={title:'h1,h2',description:'.sf-hero-copy p,.sf-image-text p,.sf-cta-wrap p'};
|
||||
function activate(id,notify=true){
|
||||
active=id||'';for(const el of all())el.classList.toggle('sf-canvas-selected',el.dataset.block===id);
|
||||
if(notify&&id)send('select',id);
|
||||
}
|
||||
const topSections=list();
|
||||
for(const block of topSections){
|
||||
const id=block.dataset.block;
|
||||
const bar=document.createElement('div');bar.className='sf-canvas-toolbar';bar.setAttribute('role','toolbar');bar.setAttribute('aria-label','区块快捷操作');
|
||||
const controls=[['↕','拖动区块','drag'],['✎','编辑区块','edit'],['↑','上移区块','up'],['↓','下移区块','down'],['+','在下方添加区块','add-after'],['⧉','复制区块','duplicate'],['×','删除区块','delete']];
|
||||
for(const [txt,label,action] of controls){
|
||||
const b=document.createElement('button');b.type='button';b.textContent=txt;b.title=label;b.setAttribute('aria-label',label);b.dataset.action=action;
|
||||
if(action==='drag'){b.draggable=true;b.className='sf-canvas-drag';b.addEventListener('dragstart',e=>{dragging=id;e.dataTransfer.setData('text/plain',id);e.dataTransfer.effectAllowed='move';e.stopPropagation()});b.addEventListener('dragend',()=>{dragging='';list().forEach(el=>el.classList.remove('sf-canvas-drop'))})}
|
||||
else {if(action==='delete')b.className='sf-canvas-danger';b.addEventListener('click',e=>{e.preventDefault();e.stopPropagation();activate(id,false);send(action,id)})}
|
||||
bar.appendChild(b);
|
||||
}
|
||||
block.prepend(bar);
|
||||
block.addEventListener('click',event=>{
|
||||
if(event.target.closest('.sf-canvas-toolbar,.sf-canvas-resize'))return;
|
||||
if(event.target.closest('a'))event.preventDefault();
|
||||
activate(id);
|
||||
});
|
||||
block.addEventListener('dragover',event=>{if(!dragging)return;event.preventDefault();event.stopPropagation();block.classList.add('sf-canvas-drop')});
|
||||
block.addEventListener('dragleave',event=>{if(!block.contains(event.relatedTarget))block.classList.remove('sf-canvas-drop')});
|
||||
block.addEventListener('drop',event=>{if(!dragging)return;event.preventDefault();event.stopPropagation();block.classList.remove('sf-canvas-drop');if(dragging!==id)send('reorder',dragging,{targetId:id});dragging=''});
|
||||
}
|
||||
// Rich text is not edited as plain text: modifying sanitized HTML would lose formatting.
|
||||
for(const section of topSections){
|
||||
const id=section.dataset.block;
|
||||
for(const [field,selector] of Object.entries(types)){
|
||||
const target=section.querySelector(selector);if(!target||target.closest('.sf-section[data-block]')!==section)continue;
|
||||
target.title='双击直接修改'+(field==='title'?'标题':'描述');
|
||||
target.addEventListener('dblclick',event=>{
|
||||
event.stopPropagation();activate(id);const before=target.textContent;
|
||||
target.contentEditable='true';target.classList.add('sf-canvas-editing');target.focus();
|
||||
let closed=false;
|
||||
const finish=commit=>{
|
||||
if(closed)return;closed=true;target.removeAttribute('contenteditable');target.classList.remove('sf-canvas-editing');
|
||||
const value=target.textContent.replace(/\r/g,'').trim();
|
||||
if(!commit)target.textContent=before;
|
||||
else if(value!==before&&value.length<=500)send('inline-text',id,{field,value});
|
||||
else if(value.length>500)target.textContent=before;
|
||||
};
|
||||
target.addEventListener('blur',()=>finish(true),{once:true});
|
||||
target.addEventListener('keydown',e=>{if(e.key==='Escape'){e.preventDefault();finish(false);target.blur()}if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();target.blur()} });
|
||||
});
|
||||
}
|
||||
}
|
||||
// Children of every nested layout (including second-level content) are selectable. The parent
|
||||
// checks IDs against the current schema and rejects forged/obsolete events.
|
||||
const nested=Array.from(document.querySelectorAll('.sf-layout-column > .sf-section[data-block]'));
|
||||
for(const child of nested){
|
||||
const layout=child.closest('.sf-layout-grid[data-layout]');
|
||||
if(!layout)continue;
|
||||
const parentId=layout.dataset.layout,childId=child.dataset.block;
|
||||
const push=(action,extra={})=>send(action,parentId,{childId,...extra});
|
||||
const toolbar=document.createElement('div');toolbar.className='sf-nested-toolbar';toolbar.setAttribute('role','toolbar');toolbar.setAttribute('aria-label','列内组件操作');
|
||||
for(const [symbol,label,action] of [['✎','编辑子组件','child-edit'],['↑','上移子组件','child-up'],['↓','下移子组件','child-down'],['⧉','复制子组件','child-duplicate'],['×','删除子组件','child-delete']]){
|
||||
const button=document.createElement('button');button.type='button';button.textContent=symbol;button.setAttribute('aria-label',label);
|
||||
button.addEventListener('click',event=>{event.preventDefault();event.stopPropagation();push(action)});
|
||||
toolbar.appendChild(button);
|
||||
}
|
||||
child.prepend(toolbar);
|
||||
child.addEventListener('click',event=>{if(event.target.closest('.sf-nested-toolbar'))return;if(event.target.closest('a'))event.preventDefault();event.stopPropagation();push('child-select')});
|
||||
// Only plain text fields are inline editable; rich HTML remains in the form.
|
||||
for(const [field,selector] of Object.entries(types)){
|
||||
const node=child.querySelector(selector);
|
||||
if(!node||node.closest('.sf-section[data-block]')!==child)continue;
|
||||
node.title='双击编辑列内'+(field==='title'?'标题':'描述');
|
||||
node.addEventListener('dblclick',event=>{
|
||||
event.stopPropagation();push('child-select');const original=node.textContent;
|
||||
node.contentEditable='true';node.classList.add('sf-canvas-editing');node.focus();let complete=false;
|
||||
const finish=save=>{if(complete)return;complete=true;node.removeAttribute('contenteditable');node.classList.remove('sf-canvas-editing');const text=node.textContent.replace(/\r/g,'').trim();if(!save||text.length>500)node.textContent=original;else if(text!==original)push('child-inline-text',{field,value:text})};
|
||||
node.addEventListener('blur',()=>finish(true),{once:true});
|
||||
node.addEventListener('keydown',e=>{if(e.key==='Escape'){e.preventDefault();finish(false);node.blur()}if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();node.blur()}});
|
||||
});
|
||||
}
|
||||
}
|
||||
// Desktop-only handles keep the total adjacent width stable and never free-position blocks.
|
||||
for(const grid of document.querySelectorAll('.sf-layout-grid[data-layout]')){
|
||||
const id=grid.dataset.layout;
|
||||
const cols=Array.from(grid.children).filter(el=>el.classList.contains('sf-layout-column'));
|
||||
if(cols.length<2)continue;
|
||||
for(let i=0;i<cols.length-1;i++){
|
||||
const handle=document.createElement('div');handle.className='sf-canvas-resize';handle.tabIndex=0;handle.setAttribute('role','separator');handle.setAttribute('aria-label',`拖动调整第 ${i+1} 列宽度`);
|
||||
const place=()=>{const a=cols[i].getBoundingClientRect(),g=grid.getBoundingClientRect();handle.style.left=(a.right-g.left+parseFloat(getComputedStyle(grid).columnGap||'0')/2)+'px'};
|
||||
grid.appendChild(handle);place();
|
||||
handle.addEventListener('pointerdown',e=>{
|
||||
if(e.button!==0||matchMedia('(max-width:820px)').matches)return;
|
||||
e.stopPropagation();e.preventDefault();handle.setPointerCapture(e.pointerId);handle.classList.add('sf-resizing');
|
||||
const raw=grid.style.getPropertyValue('--sf-layout-cols').trim().split(/\s+/).map(x=>parseFloat(x)||1);
|
||||
const start=e.clientX,span=Math.max(50,cols[i].getBoundingClientRect().width+cols[i+1].getBoundingClientRect().width);
|
||||
resize={id,i,raw,start,span};activate(id);
|
||||
});
|
||||
handle.addEventListener('pointermove',e=>{
|
||||
if(!resize||resize.i!==i)return;
|
||||
const v=resize.raw.slice(),total=v[i]+v[i+1];const delta=(e.clientX-resize.start)/resize.span*total;
|
||||
v[i]=Math.round(Math.min(4,Math.max(.5,resize.raw[i]+delta))*100)/100;
|
||||
v[i+1]=Math.round(Math.min(4,Math.max(.5,total-v[i]))*100)/100;
|
||||
if(Math.abs(v[i]+v[i+1]-total)>0.011)return;
|
||||
grid.style.setProperty('--sf-layout-cols',v.map(x=>x+'fr').join(' '));place();
|
||||
});
|
||||
const done=e=>{if(!resize||resize.i!==i)return;const raw=grid.style.getPropertyValue('--sf-layout-cols').trim().split(/\s+/).map(x=>parseFloat(x)||1);resize=null;handle.classList.remove('sf-resizing');send('resize-columns',id,{widths:raw});};
|
||||
handle.addEventListener('pointerup',done);handle.addEventListener('pointercancel',()=>{resize=null;handle.classList.remove('sf-resizing')});
|
||||
handle.addEventListener('keydown',e=>{if(e.key!=='ArrowLeft'&&e.key!=='ArrowRight')return;e.preventDefault();const w=grid.style.getPropertyValue('--sf-layout-cols').trim().split(/\s+/).map(x=>parseFloat(x)||1);const delta=e.key==='ArrowRight'?.1:-.1;const total=w[i]+w[i+1];const first=Math.round(Math.min(4,Math.max(.5,w[i]+delta))*100)/100,second=Math.round((total-first)*100)/100;if(second<.5||second>4)return;w[i]=first;w[i+1]=second;grid.style.setProperty('--sf-layout-cols',w.map(x=>x+'fr').join(' '));place();send('resize-columns',id,{widths:w})});
|
||||
new ResizeObserver(place).observe(grid);
|
||||
}
|
||||
}
|
||||
window.addEventListener('message',event=>{
|
||||
if(event.source!==parent||event.data?.source!=='siteforge-studio-host')return;
|
||||
if(event.data.action==='highlight'&&typeof event.data.blockId==='string'){activate(event.data.blockId,false);for(const n of nested)n.classList.toggle('sf-canvas-child-selected',n.dataset.block===event.data.childId);}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,46 @@
|
||||
/* SiteForge V8.29: presentation-only studio helpers. No persisted-schema changes. */
|
||||
'use strict';
|
||||
(function(root){
|
||||
const themes=new Set(['automotive','industrial','corporate','minimal']);
|
||||
const pages=new Set(['home','listing','product','article','about','contact']);
|
||||
const categories={
|
||||
visual:new Set(['hero','banner','carousel','gallery','image','image_text','image-grid','video','slider']),
|
||||
business:new Set(['collection','products','product_list','brand_wall','brands','features','stats','testimonials','faq','contact','form','cta','map']),
|
||||
advanced:new Set(['layout','columns','tabs','accordion','html-embed','embed','spacer','divider'])
|
||||
};
|
||||
function categoryOf(key){for(const [name,ids] of Object.entries(categories))if(ids.has(key))return name;return 'basic'}
|
||||
function groupBlocks(blocks,query=''){
|
||||
const result={basic:[],visual:[],business:[],advanced:[]};
|
||||
const q=String(query).trim().toLocaleLowerCase().slice(0,70);
|
||||
for(const [key,block] of Object.entries(blocks||{})){
|
||||
if(!block||typeof block!=='object')continue;
|
||||
const text=[key,block.name,block.description].join(' ').toLocaleLowerCase();
|
||||
if(q&&!text.includes(q))continue;
|
||||
result[categoryOf(key)].push({key,block});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function previewUrl(theme,page){
|
||||
if(!themes.has(theme)||!pages.has(page))return null;
|
||||
return `/api/factory/themes/${theme}/preview?page_type=${page}`;
|
||||
}
|
||||
/* Only CSS-token values accepted by the existing server-side block validator. */
|
||||
const designPalettes={
|
||||
automotive:['#ffffff','#14283d','#f3f6f9','#102e47'],
|
||||
industrial:['#ffffff','#193f59','#eef5f8','#104a6d'],
|
||||
corporate:['#ffffff','#262d58','#f4f3fb','#34397b'],
|
||||
minimal:['#faf9f5','#292924','#f0efe9','#30302c']
|
||||
};
|
||||
function stylePreset(theme,preset){
|
||||
const [base,ink,soft,dark]=designPalettes[themes.has(theme)?theme:'corporate'];
|
||||
switch(preset){
|
||||
case 'clean':return {background:base,text_color:ink,padding:72};
|
||||
case 'soft':return {background:soft,text_color:ink,padding:58};
|
||||
case 'feature':return {background:dark,text_color:'#ffffff',padding:90};
|
||||
default:return null;
|
||||
}
|
||||
}
|
||||
const api=Object.freeze({categoryOf,groupBlocks,previewUrl,stylePreset});
|
||||
if(typeof module!=='undefined'&&module.exports)module.exports=api;
|
||||
if(root)root.SiteForgeStudioExperience=api;
|
||||
})(typeof window!=='undefined'?window:null);
|
||||
@@ -0,0 +1,35 @@
|
||||
/* V8.27: pure journey model. Display only server-observed milestones as complete. */
|
||||
(function(root,factory){
|
||||
const api=factory();
|
||||
if(typeof module==='object'&&module.exports)module.exports=api;
|
||||
else root.SiteForgeJourney=api;
|
||||
})(typeof window!=='undefined'?window:this,function(){
|
||||
'use strict';
|
||||
const successful=new Set(['complete','completed','done','success']);
|
||||
const active=new Set(['queued','running','retrying']);
|
||||
function parse(t){const d=Date.parse(t||'');return Number.isFinite(d)?d:null}
|
||||
function plan(input){
|
||||
const site=input?.site||null, entries=input?.entries||[], builds=input?.builds||[], jobs=input?.jobs||[], history=input?.delivery?.history||[];
|
||||
if(!site)return {ready:false,steps:[],next:null,completed:0,hasBuild:false,live:false};
|
||||
const userEntries=entries.filter(e=>e.source!=='starter' && e.status!=='draft');
|
||||
const manuallyEditedHome=entries.some(e=>e.route==='/'&&e.status!=='draft'&&e.source==='starter'&&parse(e.updated_at)!==null&&parse(e.created_at)!==null&&parse(e.updated_at)>parse(e.created_at)+1000);
|
||||
const hasContent=userEntries.length>0||manuallyEditedHome;
|
||||
const lastBuild=builds.filter(x=>successful.has(x.status)).sort((a,b)=>(parse(b.created_at)||0)-(parse(a.created_at)||0))[0]||null;
|
||||
const anyRunning=jobs.some(x=>active.has(x.state));
|
||||
const changeTimes=[site.updated_at,...entries.filter(e=>e.status!=='draft').map(e=>e.updated_at)].map(parse).filter(x=>x!==null);
|
||||
const changedAfterBuild=!!lastBuild&&changeTimes.some(x=>{const built=parse(lastBuild.created_at);return built!==null&&x>built+1000});
|
||||
const hasBuild=!!lastBuild;
|
||||
const live=history.some(x=>x.target==='production' && !!x.release_id && !x.is_rollback);
|
||||
const domain=Boolean((site.domain||'').trim());
|
||||
const steps=[
|
||||
{id:'site',label:'创建网站',hint:'网站已经建立,基础资料可以随时修改。',view:'settings',done:true,optional:false},
|
||||
{id:'content',label:'准备网站内容',hint:hasContent?'已有人工添加的正式内容。':'添加页面、产品或导入旧站内容。新建时自带的示例首页不代表内容已准备好。',view:site.mode==='migration'?'import':'content',done:hasContent,optional:false},
|
||||
{id:'design',label:'调整网站样式',hint:'可视化修改页面、页眉、导航和手机布局;设计需人工确认。',view:'design',done:false,optional:true},
|
||||
{id:'build',label:'生成并预览网站',hint:anyRunning?'后台正在生成,请在构建页面查看结果。':!hasBuild?'生成静态页面后,再检查预览与质量报告。':changedAfterBuild?'已有构建版本,但内容可能已更新,请检查是否需要重新构建。':'已有构建版本;仍需人工检查预览、图片和 SEO。',view:'build',done:hasBuild&&!changedAfterBuild,optional:false},
|
||||
{id:'review',label:'审核并正式上线',hint:live?'存在正式发布记录,请核对目标网址是否正确显示。':'正式上线前需要发布条件检查、独立审批与服务器部署。',view:'launch',done:live,optional:false}
|
||||
];
|
||||
const next=steps.find(s=>!s.optional&&!s.done)||steps[4];
|
||||
return {ready:true,steps,next,completed:steps.filter(s=>!s.optional&&s.done).length,hasBuild,changedAfterBuild,live,domain,anyRunning};
|
||||
}
|
||||
return {plan};
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
/* V8.28: untrusted display state only; final authorization always on server. */
|
||||
(function(root,factory){const api=factory();if(typeof module==='object'&&module.exports)module.exports=api;else root.SiteForgeLaunch=api;})(typeof window!=='undefined'?window:this,function(){
|
||||
'use strict';
|
||||
const finished=new Set(['complete','completed','done','success']);
|
||||
function plan({site=null,build=null,requests=[],requestsKnown=false,delivery={},target='staging',previewChecked=false,preflight=null}={}){
|
||||
const version=build&&finished.has(build.status)?build:null;
|
||||
const bids=version?.id;
|
||||
const rows=Array.isArray(requests)?requests.filter(x=>x.build_id===bids):[];
|
||||
const approval=rows.some(x=>x.state==='approved')?'approved':rows.some(x=>x.state==='pending')?'pending':rows.some(x=>x.state==='rejected')?'rejected':requestsKnown&&Array.isArray(requests)?'none':'unknown';
|
||||
const safeTarget=['staging','production'].includes(target)?target:'staging';
|
||||
const preflightReady=Boolean(version&&preflight?.siteId===site?.id&&preflight?.buildId===version.id&&preflight?.target===safeTarget&&preflight?.ready===true);
|
||||
const preflightMessage=preflightReady?'服务器已通过检查,部署时还会重新验证。':preflight?.siteId===site?.id&&preflight?.buildId===bids?preflight.message:'尚未完成此版本的发布前检查';
|
||||
const configured=delivery?.configured?.[safeTarget]===true;
|
||||
const canDeploy=Boolean(version&&previewChecked&&approval==='approved'&&preflightReady&&configured);
|
||||
const history=Array.isArray(delivery?.history)?delivery.history:[];
|
||||
const matches=history.filter(x=>x.target===safeTarget&&x.release_id&&x.build_id===bids);
|
||||
return {approval,canDeploy,preflightReady,preflightMessage,hasRemoteRecord:matches.length>0,
|
||||
remoteDescription:matches.length?'存在当前版本的部署记录,但尚需实际访问确认。':'尚无当前版本在该环境的部署记录。',configured,
|
||||
target:safeTarget,hasBuild:!!version};
|
||||
}
|
||||
return {plan};
|
||||
});
|
||||
@@ -3,4 +3,45 @@
|
||||
/* 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}}
|
||||
|
||||
/* V8.27 guided creation and actionable journey. Keep the original admin styling. */
|
||||
[hidden]{display:none!important}
|
||||
.sf-create-dialog .modal-inner{max-width:650px}.sf-create-dialog .modal-top h2{margin-bottom:4px}
|
||||
.sf-create-progress{list-style:none;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:2px 0 22px;padding:0}
|
||||
.sf-create-progress li{border-bottom:3px solid #dde6f1;padding:9px 2px 11px;color:#8391a3;text-align:center;font-size:12px}
|
||||
.sf-create-progress li[aria-current=step]{border-color:var(--blue);color:var(--blue);font-weight:750}
|
||||
.sf-create-step h3{font-size:19px;margin:10px 0}.sf-guide-desc{font-size:13px;color:var(--muted);line-height:1.7;margin:6px 0 24px}
|
||||
.sf-required{font-size:11px;color:#aa4c3f}.sf-create-summary{border:1px solid #dbe5f0;border-radius:12px;background:#f8fafc;padding:13px 18px;margin:15px 0 22px}
|
||||
.sf-create-summary p{margin:10px 0;overflow-wrap:anywhere}.sf-guide-safety{border-radius:10px;background:#eff5ff;color:#4a6282;padding:13px 15px;line-height:1.7;margin:18px 0}
|
||||
.sf-create-error{color:#ab2537;background:#fff2f2;border:1px solid #f4cbd0;padding:11px;border-radius:8px}
|
||||
.sf-journey-prompt,.sf-journey-intro{display:flex;align-items:center;justify-content:space-between;gap:22px;background:white;border:1px solid var(--line);border-radius:14px;padding:20px 24px;margin:16px 0}
|
||||
.sf-journey-prompt p,.sf-journey-intro p{line-height:1.65;color:var(--muted);margin:7px 0 0}.sf-journey-intro h2{font-size:24px;margin:5px 0}.sf-journey-eyebrow{color:var(--muted);font-size:12px}
|
||||
.sf-journey-progress{display:grid;text-align:center;border-radius:12px;background:#eff5ff;padding:13px 20px;min-width:130px}
|
||||
.sf-journey-progress strong{font-size:28px;color:var(--blue)}.sf-journey-progress span{font-size:11px;color:var(--muted)}
|
||||
.sf-journey-list{margin:16px 0 22px;padding:0;list-style:none;display:grid;gap:10px}
|
||||
.sf-journey-step{display:grid;grid-template-columns:38px minmax(0,1fr) auto;gap:18px;align-items:center;background:white;border:1px solid var(--line);border-radius:13px;padding:17px 19px}
|
||||
.sf-journey-step.is-next{border-color:#aac1f2;box-shadow:0 0 0 2px #eff5ff}
|
||||
.sf-journey-number{width:34px;height:34px;border:1px solid #dde5ef;border-radius:50%;background:#f4f7fa;display:grid;place-items:center;color:#6b788b;font-weight:700}
|
||||
.sf-journey-step.is-done .sf-journey-number{background:#e7f7f0;color:#16835a;border-color:#cbebde}
|
||||
.sf-journey-title{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
|
||||
.sf-journey-title strong{font-size:15px}.sf-journey-title span{color:#6b7b8d;font-size:11px}
|
||||
.sf-journey-step p{color:var(--muted);font-size:12px;line-height:1.65;margin:6px 0 0}
|
||||
.sf-journey-after,.sf-journey-notice{background:white;border:1px solid var(--line);border-radius:13px;padding:20px 22px;margin:16px 0}
|
||||
.sf-journey-after h2{font-size:17px;margin:0 0 14px}.sf-glossary-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
|
||||
.sf-glossary-grid p{margin:0;padding:12px;background:#f7f9fc;border-radius:9px}.sf-glossary-grid strong{display:block;margin-bottom:6px}.sf-glossary-grid span{display:block;color:var(--muted);font-size:12px;line-height:1.7}
|
||||
.sf-journey-notice{background:#fffdf6;color:#705d35;line-height:1.8}
|
||||
@media(max-width:680px){.sf-journey-step{grid-template-columns:34px minmax(0,1fr);gap:11px}.sf-journey-step>.btn{grid-column:2;width:max-content}.sf-journey-intro,.sf-journey-prompt{flex-direction:column;align-items:stretch}.sf-glossary-grid{grid-template-columns:1fr}.sf-journey-progress{max-width:190px}}
|
||||
/* V8.28: calm, readable launch center for nontechnical users */
|
||||
.sf-launch-hero{background:#fff;border:1px solid var(--line);border-radius:14px;padding:23px;display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:19px}
|
||||
.sf-launch-hero h2{margin:5px 0;font-size:22px}.sf-launch-hero p{margin:5px 0;color:var(--muted);line-height:1.65;max-width:700px}
|
||||
.sf-launch-hero>strong{padding:9px 14px;background:#edf3ff;color:#245ac6;border-radius:30px;white-space:nowrap;font-size:12px}
|
||||
.sf-launch-step{display:grid;grid-template-columns:40px minmax(0,1fr);gap:17px;background:#fff;border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin-bottom:12px}
|
||||
.sf-launch-step.is-ready{border-color:#b4e2cf}.sf-launch-index{width:34px;height:34px;border-radius:50%;background:#eef3fb;color:#38577f;display:grid;place-items:center;font-weight:750}
|
||||
.sf-launch-step.is-ready .sf-launch-index{background:#e0f5eb;color:#197a54}.sf-launch-detail{min-width:0}.sf-launch-detail h2{margin:1px 0 6px;font-size:16px}
|
||||
.sf-launch-detail>p{color:var(--muted);font-size:13px;margin:0 0 14px;line-height:1.7}
|
||||
.sf-launch-controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.sf-launch-field{display:grid;gap:6px;font-size:12px;font-weight:650;color:#495d73;min-width:200px;max-width:350px}
|
||||
.sf-launch-field select{width:100%}.sf-launch-check{display:flex;align-items:center;gap:8px;font-size:12px;color:#425572;padding:10px;border-radius:8px;background:#f6f9fd}
|
||||
.sf-launch-check input{width:18px;height:18px;flex-shrink:0}.sf-launch-status{font-size:12px;color:#657589;max-width:600px}
|
||||
.sf-launch-notice{padding:16px 18px;border-radius:10px;background:#edf3ff;color:#244f82;margin:15px 0;line-height:1.75}
|
||||
@media(max-width:620px){.sf-launch-hero{flex-direction:column;align-items:start}.sf-launch-step{padding:16px;gap:11px;grid-template-columns:32px minmax(0,1fr)}.sf-launch-controls{align-items:stretch;flex-direction:column}.sf-launch-field,.sf-launch-controls>.btn{width:100%;max-width:none}.sf-launch-check{align-items:start}}
|
||||
|
||||
.theme-preview.trade-pro{background:linear-gradient(125deg,#0b3d91,#3d6dc1);color:#e8f1fb}.site-icon.trade-pro{background:#e8f1fb;color:#0b3d91}
|
||||
|
||||
+150
-30
@@ -7,8 +7,8 @@ const typeName={page:'页面',product:'产品',vehicle:'车型',post:'新闻',br
|
||||
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 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,delivery:{jobs:[],history:[],configured:{}},language:{group:null,sites:[],units:[]},languageGroups:[],languageEntries:[]};
|
||||
const headings={home:'工作台',sites:'我的网站',overview:'网站概览',content:'内容与页面',design:'外观与设计',import:'导入与迁移',build:'构建与交付',languages:'多语言管理',settings:'网站设置'};
|
||||
const state={launchDeploying:false,releaseRequestSeq:0,releaseRequests:[],releaseRequestsKnown:false,launchBuildId:'',launchTarget:'staging',launchReview:false,launchPreflight:null,sites:[],site:null,entries:[],builds:[],jobs:[],view:'home',csrf:'',search:'',typ:'',loading:false,job:null,selectionToken:0,building:false,delivery:{jobs:[],history:[],configured:{}},language:{group:null,sites:[],units:[]},languageGroups:[],languageEntries:[]};
|
||||
const headings={home:'工作台',sites:'我的网站',guide:'建站操作指引',launch:'上线操作中心',overview:'网站概览',content:'内容与页面',design:'外观与设计',import:'导入与迁移',build:'生成与预览',languages:'多语言管理',settings:'网站设置'};
|
||||
async function api(url,method='GET',data=null,extraHeaders={}){
|
||||
const headers={...extraHeaders};if(method!=='GET'&&method!=='HEAD')headers['X-SiteForge-CSRF']=state.csrf;
|
||||
let body=data;if(data!=null && !(data instanceof FormData)){headers['Content-Type']='application/json';body=JSON.stringify(data)}
|
||||
@@ -49,20 +49,74 @@ 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=buildReadyStep();const hasDomain=!!s.domain;const steps=[
|
||||
{title:'建立网站',desc:'选择行业主题并创建项目',done:true,view:'settings'},
|
||||
{title:'准备内容',desc:'添加页面、产品或导入旧数据',done:hasContent,view:'content'},
|
||||
{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'))+
|
||||
`<div class="task-note"><div><strong>${next?'建议下一步:'+next.title:'网站已经生成过构建版本'}</strong><p>${next?next.desc:'内容或设计有修改时,重新构建并检查新版即可。'}。${!hasDomain?'正式域名还未填写;测试预览不受影响。':''}</p></div><button class="btn btn-primary" ${nav(next?.view||'build')}>${next?'现在去做':'查看构建版本'} →</button></div>`+
|
||||
`<div class="stats">${stat('网站内容',count,'▧')}${stat('已构建版本',state.builds.length,'↗')}${stat('当前主题',themeName[s.theme]||'企业品牌','◈')}${stat('域名状态',hasDomain?'已填写':'待设置','◎')}</div>`+
|
||||
`<div class="section-heading"><div><h2>四步完成网站制作</h2><p>步骤反映已有数据,设计可以随时修改,不会虚报“设计完成”。</p></div></div>`+
|
||||
`<div class="wizard-grid">${steps.map((step,i)=>`<div class="wizard-step ${step.done?'done':next===step?'next':''}"><span class="wizard-index">${step.done?'✓':step.optional?'◈':i+1}</span><h3>${step.title}${step.optional?'(持续优化)':''}</h3><p>${step.desc}</p><button ${nav(step.view)}>打开 →</button></div>`).join('')}</div>`+
|
||||
`<div class="section-heading"><div><h2>常用操作</h2><p>通常只需要下面三个入口。</p></div></div>`+
|
||||
`<div class="quick-grid">${overviewShortcut('content','▧','添加和编辑内容','管理页面、产品、新闻和案例。')}${overviewShortcut('design','◈','修改网站样式','更换主题、编辑页面和移动端样式。')}${overviewShortcut('build','↗','查看和下载网站','查看任务状态、预览及下载静态网站。')}</div>`;
|
||||
function overview(){
|
||||
const s=state.site;if(!s)return noSite();
|
||||
const journey=window.SiteForgeJourney?.plan({site:s,entries:state.entries,builds:state.builds,jobs:state.jobs,delivery:state.delivery});
|
||||
const next=journey?.next||{label:'查看操作指引',hint:'检查各步骤是否已经完成。',view:'guide'};
|
||||
const contentCount=state.entries.filter(e=>e.source!=='starter').length;
|
||||
return htmlHeading(s.name,'所有网站操作都从这里开始;保存内容、生成网站和正式上线是不同的步骤。',`<button type="button" class="btn btn-primary" data-action="navigate" data-view="guide">查看完整操作指引 →</button>`)+
|
||||
`<section class="sf-journey-intro"><div><span class="sf-journey-eyebrow">建议下一步</span><h2>${esc(next.label)}</h2><p>${esc(next.hint)}</p></div><button type="button" class="btn btn-primary" data-action="navigate" data-view="${esc(next.view)}">现在去做 →</button></section>`+
|
||||
`<div class="stats">${stat('人工添加的内容',contentCount,'▧')}${stat('静态构建版本',state.builds.length,'↗')}${stat('当前主题',themeName[s.theme]||'企业品牌','◈')}${stat('正式域名',s.domain?'已填写':'未设置','◎')}</div>`+
|
||||
`<div class="section-heading"><div><h2>最常用的三项操作</h2><p>不需要了解技术细节,选择你想完成的事情即可。</p></div></div>`+
|
||||
`<div class="quick-grid">${overviewShortcut('content','▧','修改文字和产品','填写网站内容,修改已经创建的页面。')}${overviewShortcut('design','◈','调整网页外观','编辑图片、版式、导航和手机端。')}${overviewShortcut('launch','↗','检查并上线网站','一步一步完成预览、审批和正式部署。')}</div>`+
|
||||
`<div class="sf-journey-notice" role="note"><strong>关于上线</strong>:页面编辑和保存不会自动修改线上网站。上线仍需生成静态版本、检查、独立审批和部署。</div>`;
|
||||
}
|
||||
function guide(){
|
||||
if(!currentSite())return noSite();
|
||||
const journey=window.SiteForgeJourney?.plan({site:state.site,entries:state.entries,builds:state.builds,jobs:state.jobs,delivery:state.delivery});
|
||||
if(!journey)return htmlHeading('建站操作指引','操作引导暂时无法加载,请刷新网页。');
|
||||
const checked=journey.completed;
|
||||
const rows=journey.steps.map((step,i)=>{
|
||||
const label=step.optional?'可选:随时完善':step.done?'已记录完成':step.id===journey.next.id?'建议现在进行':'等待前面的步骤';
|
||||
return `<li class="sf-journey-step ${step.done?'is-done':''} ${step.id===journey.next.id?'is-next':''}">
|
||||
<span class="sf-journey-number" aria-hidden="true">${step.done?'✓':i+1}</span>
|
||||
<div><div class="sf-journey-title"><strong>${esc(step.label)}</strong><span>${esc(label)}</span></div>
|
||||
<p>${esc(step.hint)}</p></div>
|
||||
<button type="button" class="btn ${step.id===journey.next.id?'btn-primary':'btn-muted'}" data-action="navigate" data-view="${esc(step.view)}">${step.id===journey.next.id?'开始操作':'查看 / 修改'} →</button>
|
||||
</li>`;
|
||||
}).join('');
|
||||
return htmlHeading('建站操作指引','按照实际操作顺序完成网站制作,系统只根据已保存的数据与任务记录显示进度。')+
|
||||
`<section class="sf-journey-intro"><div><span class="sf-journey-eyebrow">当前项目</span><h2>${esc(state.site.name)}</h2><p>${esc(journey.next.hint)}</p></div>
|
||||
<div class="sf-journey-progress"><strong>${checked}/4</strong><span>已记录的必要步骤</span></div></section>
|
||||
<ol class="sf-journey-list">${rows}</ol>
|
||||
<section class="sf-journey-after"><h2>保存、构建和上线有什么不同?</h2><div class="sf-glossary-grid">
|
||||
<p><strong>保存内容</strong><span>把修改写入管理系统,外部访客还看不到新版。</span></p>
|
||||
<p><strong>生成网站</strong><span>制作一份可预览、可下载的静态网站版本。</span></p>
|
||||
<p><strong>检查与审批</strong><span>核对页面、图片、网址和 SEO,并取得相应审批。</span></p>
|
||||
<p><strong>正式上线</strong><span>把批准的版本部署到正式服务器,再核对公网访问。</span></p></div></section>
|
||||
<section class="sf-journey-notice" role="note"><strong>重要说明</strong>:${journey.live?'系统发现正式发布记录,但无法仅凭该记录确认当前公网网站可访问,请人工检查实际域名。':'当前没有可确认的正式发布记录;生成 ZIP 或点击“发布设计”都不代表网站已上线。'}${journey.changedAfterBuild?' 网站内容在最近一次构建后可能有变化,建议重新构建并预览。':''}${!journey.domain?' 正式域名尚未填写,不影响预览。':''}</section>`;
|
||||
}
|
||||
// V8.28 guided delivery: one deliberate action per stage, existing secure APIs only.
|
||||
function launch(){
|
||||
if(!currentSite())return noSite();
|
||||
const buildList=state.builds.filter(b=>b.status==='complete');
|
||||
const selection=buildList.find(b=>b.id===state.launchBuildId)||buildList[0]||null;
|
||||
const selected=selection?.id||'';
|
||||
if(selected&&!state.launchBuildId){state.launchBuildId=selected;state.launchReview=false;state.launchPreflight=null;}
|
||||
const requests=state.releaseRequests.filter(r=>r.build_id===selected);
|
||||
const result=window.SiteForgeLaunch?.plan({site:state.site,build:selection,requests,requestsKnown:state.releaseRequestsKnown,
|
||||
delivery:state.delivery,target:state.launchTarget,previewChecked:state.launchReview,preflight:state.launchPreflight});
|
||||
const row=(index,name,body,controls,kind='')=>`<section class="sf-launch-step ${kind}"><div class="sf-launch-index">${index}</div><div class="sf-launch-detail"><h2>${esc(name)}</h2><p>${esc(body)}</p><div class="sf-launch-controls">${controls}</div></div></section>`;
|
||||
const buildOptions=buildList.map(b=>`<option value="${esc(b.id)}" ${selected===b.id?'selected':''}>${esc(fmtDate(b.created_at))} · ${esc(b.id.slice(0,12))} · ${Number(b.total_pages)||0} 页</option>`).join('');
|
||||
const select=`<label class="sf-launch-field">静态构建版本<select id="launchBuildSelect" ${buildList.length?'':'disabled'}><option value="">${buildList.length?'请选择版本':'暂无成功构建'}</option>${buildOptions}</select></label>`;
|
||||
const checked=Boolean(state.launchReview&&selected);
|
||||
const canRequestApproval=Boolean(selected&&checked&&state.releaseRequestsKnown&&['none','rejected'].includes(result?.approval));
|
||||
const preview=selected?`<a class="btn" target="_blank" rel="noopener" href="/preview/${encodeURIComponent(state.site.id)}/${encodeURIComponent(selected)}/">打开网站预览 ↗</a><button type="button" class="btn" data-action="launch-report" data-id="${esc(selected)}">检查质量报告</button>`:`<button class="btn btn-primary" data-action="launch-build">去生成网站</button>`;
|
||||
const approvalText=result?.approval==='approved'?'已由其他审核人批准。':result?.approval==='pending'?'已提交审批,请由其他有审核权限的人员处理。':result?.approval==='rejected'?'最近申请被驳回,请解决问题后重新申请。':!state.releaseRequestsKnown?'审批记录暂时无法确认,请刷新或到审批中心查询。':'尚无已批准的申请。';
|
||||
const config=Boolean(state.delivery.configured?.[state.launchTarget]);
|
||||
const domainUrl=/^https:\/\/[^\s<>]+$/i.test(state.site.domain||'')?state.site.domain:'';
|
||||
const deploymentText=state.launchTarget==='production'?'正式环境':'测试环境';
|
||||
return htmlHeading('上线操作中心','按顺序完成检查和审核,每一步都解释操作用途。网站不会因为保存、预览或提交审批而自动上线。',`<button type="button" class="btn" data-action="launch-refresh">刷新状态</button>`)+
|
||||
`<div class="sf-launch-hero"><div><span class="sf-journey-eyebrow">当前网站</span><h2>${esc(state.site.name)}</h2><p>上线属于独立审批和服务器部署流程;如有不确定之处,可以随时离开,已经保存的记录不会消失。</p></div><strong>${result?.approval==='approved'?'已获审批':result?.approval==='pending'?'等待审批':selected?'准备上线':'请先构建'}</strong></div>`+
|
||||
row('1','选择网站版本','选择已生成成功、准备交付的静态版本。内容有变更时需要重新构建。',select+`<button class="btn" data-action="launch-build">${selected?'重新构建版本':'开始构建网站'}</button>`,selected?'is-ready':'')+
|
||||
row('2','预览网站并检查质量','在新窗口检查页面、图片、移动端、链接和 SEO。质量报告是自动检查,仍需要人工确认。',preview+`<label class="sf-launch-check"><input id="launchReviewCheck" type="checkbox" ${checked?'checked':''} ${selected?'':'disabled'}> 我已经检查此版本的预览与质量报告</label>`,checked?'is-ready':'')+
|
||||
row('3','提交给其他人审批',approvalText,`<button class="btn" data-action="launch-request-approval" ${canRequestApproval?'':'disabled'}>${result?.approval==='pending'?'已提交,等待审核':result?.approval==='approved'?'此版本已批准':'提交审批申请'}</button><a class="btn" href="/operations">查看审批中心 ↗</a>`,result?.approval==='approved'?'is-ready':'')+
|
||||
row('4','检查正式发布条件','审批通过后,再检查构建质量、旧站迁移复核和发布归档是否满足条件。',`<button class="btn" data-action="launch-preflight" ${result?.approval==='approved'&&checked?'':'disabled'}>执行发布前检查</button><span class="sf-launch-status">${esc(result?.preflightMessage||'尚未完成此版本的发布前检查')}</span>`,result?.preflightReady?'is-ready':'')+
|
||||
row('5','提交服务器部署','只有目标环境已配置、审批通过且发布前检查成功,才能从这里提交部署任务。',`<label class="sf-launch-field">选择发布环境<select id="launchTarget"><option value="staging" ${state.launchTarget==='staging'?'selected':''}>测试环境</option><option value="production" ${state.launchTarget==='production'?'selected':''}>正式环境</option></select></label><button class="btn btn-primary" data-action="launch-deploy" ${result?.canDeploy&&!state.launchDeploying?'':'disabled'}>${state.launchDeploying?'正在提交…':'提交部署到'}${deploymentText}</button><span class="sf-launch-status">${state.delivery.unavailable?'无法读取发布目标状态,请刷新或联系管理员。':config?'目标服务器已配置':'当前环境没有配置可用的发布目标,请联系管理员。'}</span>`,result?.hasRemoteRecord?'is-ready':'')+
|
||||
row('6','核对网站是否真正上线','提交部署只是创建任务。请确认任务成功,再在真实域名检查页面和联系表单。',`<button class="btn" data-action="launch-refresh">刷新任务</button>${domainUrl?`<a class="btn" href="${esc(domainUrl)}" target="_blank" rel="noopener noreferrer">打开正式域名 ↗</a>`:'<button class="btn" data-action="navigate" data-view="settings">设置正式域名</button>'}<p class="sf-launch-status">${esc(result?.remoteDescription||'未确认有当前环境的部署记录。')} 系统不会仅凭发布记录声称公网验收通过。</p>`)+
|
||||
`<div class="sf-journey-notice" role="note"><strong>注意</strong>:这里的确认只影响当前操作界面,正式发布仍由服务器重新验证权限、审批、质量、文件和目标配置。页面预览正常也不代表公网已上线。</div><div id="launchNotice" class="sf-launch-notice" role="status" aria-live="polite" ${state.launchMessage?'':'hidden'}>${esc(state.launchMessage||'')}</div><div id="launchReport"></div>`;
|
||||
}
|
||||
|
||||
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'))+
|
||||
`<div class="panel"><div class="panel-head"><div><h2>内容列表</h2><p>共 ${state.entries.length} 条内容。点击“设计页面”可进行可视化排版。</p></div></div><div class="toolbar"><input id="contentSearch" aria-label="搜索内容" placeholder="搜索标题或页面路径…" value="${esc(state.search)}"><select id="contentType" aria-label="按类型筛选"><option value="">全部类型</option>${Object.entries(typeName).map(([key,value])=>`<option value="${key}" ${state.typ===key?'selected':''}>${value}</option>`).join('')}</select></div>`+
|
||||
@@ -80,20 +134,20 @@ function imports(){if(!currentSite())return noSite();return htmlHeading('导入
|
||||
`<div class="help-hint" style="margin-top:17px"><strong>迁移旧网站请注意:</strong>如果需要保留旧 URL、原有 SEO、复杂插件字段和全部图片,请优先使用 WordPress 迁移工作台,而不是仅上传一个内容文件。</div>`;
|
||||
}
|
||||
function builds(){if(!currentSite())return noSite();const latest=latestDone();const pending=activeJobs();const first=pending[0];
|
||||
return htmlHeading('构建与交付','网站构建由独立 Worker 执行。离开页面或刷新后仍可查看任务状态。',`<button type="button" class="btn btn-primary" data-action="build-now" ${pending.length||state.building?'disabled':''}>${pending.length?'已有构建在运行':'↗ 开始构建'}</button>`)+
|
||||
return htmlHeading('生成与预览','先生成网站版本,确认效果后再使用上线操作中心提交审批。任务会在后台执行。',`<button type="button" class="btn btn-primary" data-action="build-now" ${pending.length||state.building?'disabled':''}>${pending.length?'已有构建在运行':'↗ 开始构建'}</button>`)+
|
||||
`<div class="task-note"><div><strong>${first?'当前:'+buildStatusLabel(first.state):latest?'已有构建版本,可以预览':'还没有已完成的网站构建'}</strong><p>${first?'当前任务 '+esc(first.id.slice(0,12))+';Worker 在后台执行,您可以关闭此页面。':'内容或设计有更新时,重新生成网站版本。'}</p></div><div class="inline-actions"><button class="btn" data-action="check-readiness">交付前检查</button><a class="btn" href="/operations">任务与发布审批 ↗</a></div></div>`+
|
||||
`<div class="panel"><div class="panel-head"><div><h2>后台任务</h2><p>可以查看任务状态。仅取消当前仍在队列或运行中的构建。</p></div><button class="btn btn-tiny" data-action="refresh-builds">刷新状态</button></div><div id="jobNotice" class="message" ${state.job?'':'hidden'}>${esc(state.job||'')}</div>`+
|
||||
(state.jobs.length?state.jobs.slice(0,8).map(j=>`<div class="build-row"><span class="quick-ico">${j.state==='done'?'✓':j.state==='failed'?'!':'◷'}</span><div class="build-meta"><h3>${esc(buildStatusLabel(j.state))}</h3><p>${esc(fmtDate(j.created_at))} · 任务 ${esc(j.id.slice(0,12))}${j.state==='failed'&&j.error?' · '+esc(j.error):''}</p></div>${['queued','running','retrying'].includes(j.state)?`<button class="btn btn-tiny btn-danger" data-action="cancel-job" data-id="${esc(j.id)}">取消任务</button>`:''}</div>`).join(''):'<p class="muted">当前网站还没有后台构建任务。</p>')+`</div>`+
|
||||
`<div class="panel" style="margin-top:18px"><div class="panel-head"><div><h2>构建版本</h2><p>建议先预览和核对质量报告,再下载 ZIP。预览不代表正式发布。</p></div></div>`+
|
||||
(state.builds.length?state.builds.slice(0,20).map(b=>`<div class="build-row"><span class="quick-ico">${['done','completed','success','complete'].includes(b.status)?'✓':'◷'}</span><div class="build-meta"><h3>${esc(fmtDate(b.created_at))} · ${esc(b.total_pages||'—')} 个页面</h3><p>版本 ${esc(b.id.slice(0,12))} · ${esc(b.status||'未知')}</p></div><div class="inline-actions">${['done','completed','success','complete'].includes(b.status)?`<a class="btn btn-tiny" href="/preview/${encodeURIComponent(state.site.id)}/${encodeURIComponent(b.id)}/" target="_blank" rel="noopener">预览 ↗</a><button class="btn btn-tiny" data-action="report" data-id="${esc(b.id)}">质量报告</button><a class="btn btn-tiny btn-primary" href="/api/sites/${encodeURIComponent(state.site.id)}/builds/${encodeURIComponent(b.id)}/download">下载 ZIP</a>`:'<span class="muted">尚未完成,暂不能下载</span>'}</div></div>`).join(''):emptyState('尚无完成版本','准备好网站数据后,在上方提交构建任务。'))+
|
||||
`<div id="reportContent"></div></div>`+
|
||||
`<div class="panel" style="margin-top:18px"><div class="panel-head"><div><h2>正式发布与回滚</h2><p>先完成质量检查与独立审批,Worker 负责上传、校验和切换版本。没有配置目标服务器时不能发布。</p></div><a href="/operations" class="btn btn-tiny">审批与复核工具 ↗</a></div>
|
||||
`<div class="sf-launch-followup"><strong>下一步:预览检查后提交审批</strong><p>完成构建后,请在上线操作中心按照步骤检查质量、提交审批并部署。</p><button class="btn btn-primary" data-action="navigate" data-view="launch">前往上线操作中心 →</button></div><details class="form-expand sf-advanced-publish"><summary>高级:直接进行发布和历史版本回滚</summary><div class="panel" style="margin-top:18px"><div class="panel-head"><div><h2>正式发布与回滚</h2><p>先完成质量检查与独立审批,Worker 负责上传、校验和切换版本。没有配置目标服务器时不能发布。</p></div><a href="/operations" class="btn btn-tiny">审批与复核工具 ↗</a></div>
|
||||
<div class="field-grid"><label>选择已完成构建<select id="deliveryBuild">${state.builds.filter(b=>b.status==='complete').map(b=>`<option value="${esc(b.id)}">${esc(b.id.slice(0,12))} · ${esc(fmtDate(b.created_at))}</option>`).join('')}</select></label>
|
||||
<label>目标环境<select id="deliveryTarget"><option value="staging">测试环境(Staging)</option><option value="production">正式环境(Production)</option></select></label></div>
|
||||
<div class="form-actions"><button class="btn" data-action="release-preflight">检查发布条件</button><button class="btn" data-action="release-approval">提交独立审批</button><button class="btn btn-primary" data-action="release-deploy">提交发布任务</button></div>
|
||||
<div id="deliveryNotice" class="message" aria-live="polite" hidden></div>
|
||||
<h3>最近发布任务</h3>${(state.delivery.jobs||[]).length?state.delivery.jobs.slice(0,8).map(j=>`<div class="build-row"><div class="build-meta"><strong>${j.kind==='remote_rollback'?'回滚':'部署'} · ${esc(buildStatusLabel(j.state))}</strong><p>${esc(j.id.slice(0,12))} · ${esc(j.created_at)}${j.error?' · '+esc(j.error):''}</p></div>${['queued','running','retrying'].includes(j.state)?`<button class="btn btn-tiny btn-danger" data-action="cancel-job" data-id="${esc(j.id)}">取消</button>`:''}</div>`).join(''):'<p class="muted">暂无发布任务。</p>'}
|
||||
<h3>已发布版本</h3>${(state.delivery.history||[]).length?state.delivery.history.slice(0,8).map(r=>`<div class="build-row"><div class="build-meta"><strong>${esc(r.target)} · ${esc(r.release_id)}</strong><p>构建 ${esc(r.build_id.slice(0,12))} · ${esc(r.deployed_at)}</p></div><button class="btn btn-tiny" data-action="release-rollback" data-target="${esc(r.target)}" data-id="${esc(r.release_id)}">回滚到此版本</button></div>`).join(''):'<p class="muted">当前没有远程部署记录。</p>'}</div>`;
|
||||
<h3>已发布版本</h3>${(state.delivery.history||[]).length?state.delivery.history.slice(0,8).map(r=>`<div class="build-row"><div class="build-meta"><strong>${esc(r.target)} · ${esc(r.release_id)}</strong><p>构建 ${esc(r.build_id.slice(0,12))} · ${esc(r.deployed_at)}</p></div><button class="btn btn-tiny" data-action="release-rollback" data-target="${esc(r.target)}" data-id="${esc(r.release_id)}">回滚到此版本</button></div>`).join(''):'<p class="muted">当前没有远程部署记录。</p>'}</div></details>`;
|
||||
}
|
||||
function settings(){if(!currentSite())return noSite();const s=state.site;return htmlHeading('网站设置','这里只管理公司资料、域名与站点属性。网页样式请到“外观与设计”修改。')+
|
||||
`<div class="panel"><h2>基本资料</h2><p class="panel-desc">没有域名也可以制作与预览网站,正式上线前再填写即可。</p><form id="settingsForm"><div class="field-grid"><label class="full">网站名称<input name="name" required value="${esc(s.name)}"></label><label class="full">正式域名<input name="domain" placeholder="https://www.example.com" value="${esc(s.domain||'')}"></label><label class="full">企业介绍 / 网站标语<textarea name="tagline" rows="3">${esc(s.tagline||'')}</textarea></label><label class="full">Logo 图片地址<input name="logo" value="${esc(s.logo||'')}"></label><label>网站类型<select name="mode"><option value="new" ${s.mode==='new'?'selected':''}>新建网站</option><option value="migration" ${s.mode==='migration'?'selected':''}>旧站迁移</option><option value="hybrid" ${s.mode==='hybrid'?'selected':''}>混合建站</option></select></label><label>主题色<input type="color" name="accent" value="${esc(s.accent||'#245ac6')}" style="padding:4px;height:43px"></label></div><div class="form-actions"><button class="btn btn-primary" type="submit">保存网站设置</button></div></form></div>`;
|
||||
@@ -125,29 +179,93 @@ async function submitLanguageUnit(ev){ev.preventDefault();const d=Object.fromEnt
|
||||
async function submitLanguageLink(ev){ev.preventDefault();const d=Object.fromEntries(new FormData(ev.target));try{await api('/api/i18n/groups/'+state.language.group.group_id+'/units/'+encodeURIComponent(d.unit_id)+'/members','POST',{site_id:d.site_id,entry_id:d.entry_id,state:d.state});toast('内容翻译关系已保存');await loadLanguage()}catch(e){notifyError(e)}}
|
||||
function rerender(){const area=$('#mainContent');const selected=state.view;$('#breadcrumbTitle').textContent=headings[selected]||'工作台';$('#sideSiteName').textContent=state.site?.name||'请先选择网站';
|
||||
document.querySelectorAll('[data-view].nav-item').forEach(el=>{const yes=el.dataset.view===selected;el.classList.toggle('active',yes);yes?el.setAttribute('aria-current','page'):el.removeAttribute('aria-current')});
|
||||
area.innerHTML=({home,sites,overview,content,design,import:imports,build:builds,languages,settings}[selected]||home)();
|
||||
area.innerHTML=({home,sites,overview,guide,launch,content,design,import:imports,build:builds,languages,settings}[selected]||home)();
|
||||
}
|
||||
async function loadSites(){state.sites=await api('/api/sites');const picker=$('#sitePicker'),prev=state.site?.id||'';picker.innerHTML='<option value="">选择网站…</option>'+state.sites.map(s=>`<option value="${esc(s.id)}">${esc(s.name)}</option>`).join('');picker.value=state.sites.some(s=>s.id===prev)?prev:''}
|
||||
async function readDelivery(siteId){try{return await api(`/api/production/sites/${encodeURIComponent(siteId)}/delivery-status`)}catch(e){return {jobs:[],history:[],configured:{}}}}
|
||||
async function readDelivery(siteId){try{return await api(`/api/production/sites/${encodeURIComponent(siteId)}/delivery-status`)}catch(e){return {jobs:[],history:[],configured:{},unavailable:true}}}
|
||||
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.delivery={jobs:[],history:[],configured:{}};state.job=null;state.view=go==='home'?'home':'sites';$('#sitePicker').value='';rerender();if(historyMode!=='none')rememberView(historyMode==='replace');return}
|
||||
if(!id){state.site=null;resetLaunch();state.entries=[];state.builds=[];state.jobs=[];state.delivery={jobs:[],history:[],configured:{}};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,delivery]=await Promise.all([api(`/api/sites/${encodeURIComponent(id)}`),api(`/api/sites/${encodeURIComponent(id)}/entries`),api(`/api/sites/${encodeURIComponent(id)}/builds`),readJobs(id),readDelivery(id)]);
|
||||
if(token!==state.selectionToken)return;
|
||||
state.site=site;sfRemember(id);state.entries=entries;state.builds=builds;state.jobs=jobs;state.delivery=delivery;state.job=null;state.view=go;state.search='';state.typ='';$('#sitePicker').value=id;rerender();if(historyMode!=='none')rememberView(historyMode==='replace');if(go==='languages')loadLanguage();closeMobile();window.scrollTo({top:0,behavior:'instant'});
|
||||
state.site=site;resetLaunch();sfRemember(id);state.entries=entries;state.builds=builds;state.jobs=jobs;state.delivery=delivery;state.job=null;state.view=go;state.search='';state.typ='';$('#sitePicker').value=id;rerender();if(historyMode!=='none')rememberView(historyMode==='replace');if(go==='languages')loadLanguage();if(go==='launch')loadReleaseRequests();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();if(to==='languages')loadLanguage();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,delivery]=await Promise.all([readJobs(id),api(`/api/sites/${encodeURIComponent(id)}/builds`),readDelivery(id)]);if(state.site?.id!==id)return;state.jobs=jobs;state.builds=builds;state.delivery=delivery;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 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'||to==='launch')refreshBuilds();if(to==='launch')loadReleaseRequests();if(to==='languages')loadLanguage();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,delivery]=await Promise.all([readJobs(id),api(`/api/sites/${encodeURIComponent(id)}/builds`),readDelivery(id)]);if(state.site?.id!==id)return;state.jobs=jobs;state.builds=builds;state.delivery=delivery;if(state.view==='launch'){const selected=state.launchBuildId;const stillValid=builds.some(b=>b.id===selected&&b.status==='complete');if(selected&&!stillValid){state.launchBuildId='';state.launchReview=false;state.launchPreflight=null;state.launchMessage='原先选择的构建版本已不可用,请重新选择并核对。'}}if(previouslyRunning&&!activeJobs().length){const last=jobs[0];state.job=last?.state==='done'?'后台构建已完成,请预览并查看质量报告。':last?.state==='failed'?'本次构建失败,请在任务中心检查原因。':last?.state==='cancelled'?'本次构建已取消。':null}if(['build','launch'].includes(state.view))rerender()}catch(e){notifyError(e)}}
|
||||
function resetLaunch(){state.releaseRequestSeq++;state.launchDeploying=false;state.releaseRequests=[];state.releaseRequestsKnown=false;state.launchBuildId='';state.launchTarget='staging';state.launchReview=false;state.launchPreflight=null;state.launchMessage=''}
|
||||
async function loadReleaseRequests(){const siteId=state.site?.id;if(!siteId)return;const seq=++state.releaseRequestSeq;try{
|
||||
const rows=await api(`/api/factory/sites/${encodeURIComponent(siteId)}/release-requests`);
|
||||
if(state.site?.id!==siteId||seq!==state.releaseRequestSeq)return;
|
||||
state.releaseRequests=Array.isArray(rows)?rows:[];state.releaseRequestsKnown=true;
|
||||
}catch(e){if(state.site?.id!==siteId||seq!==state.releaseRequestSeq)return;state.releaseRequestsKnown=false;state.releaseRequests=[];if(![403,404].includes(Number(e.status)))console.warn('读取审批记录失败',e)}
|
||||
if(state.view==='launch')rerender();
|
||||
}
|
||||
async function launchRefresh(){if(!state.site)return;const siteId=state.site.id;await Promise.all([refreshBuilds(),loadReleaseRequests()]);if(state.site?.id!==siteId)return;state.launchMessage='状态已刷新。审批记录、部署任务及公网验收是不同状态。';rerender()}
|
||||
function launchPickBuild(id){const known=state.builds.some(b=>b.id===id&&b.status==='complete');state.launchBuildId=known?id:'';state.launchReview=false;state.launchPreflight=null;state.launchMessage='切换构建版本后,请重新检查预览并获取审批。';rerender()}
|
||||
function launchPickTarget(target){if(!['staging','production'].includes(target))return;state.launchTarget=target;state.launchPreflight=null;state.launchMessage='已切换目标环境。发布前检查将重新确认。';rerender()}
|
||||
function launchChosenBuild(){const id=state.launchBuildId||state.builds.find(b=>b.status==='complete')?.id;if(!id)throw Error('请先生成一个完整的静态构建版本');return id}
|
||||
async function launchRequestApproval(){const sid=state.site?.id,bid=launchChosenBuild();if(!state.launchReview)throw Error('请先检查网站预览并勾选人工核对');if(!state.releaseRequestsKnown)throw Error('审批状态读取失败,请刷新后再操作');if(state.releaseRequests.some(r=>r.build_id===bid&&['pending','approved'].includes(r.state)))throw Error('当前版本已经存在审批申请,无需重复提交');if(!confirm('确定为此构建版本提交独立审批吗?提交后必须由另一位审核人员批准。'))return;
|
||||
const r=await api(`/api/factory/sites/${encodeURIComponent(sid)}/release-requests`,'POST',{build_id:bid});if(state.site?.id!==sid)return;
|
||||
state.launchMessage=`已创建审批申请 ${r.id},当前仍在等待他人审核。`;
|
||||
await loadReleaseRequests();rerender();
|
||||
}
|
||||
async function launchPreflight(){const sid=state.site?.id,bid=launchChosenBuild(),target=state.launchTarget;if(!state.launchReview)throw Error('请先完成人工预览检查');
|
||||
const r=await api(`/api/production/sites/${encodeURIComponent(sid)}/preflight/${encodeURIComponent(bid)}`);if(state.site?.id!==sid||state.launchTarget!==target||launchChosenBuild()!==bid)return;
|
||||
state.launchPreflight={siteId:sid,buildId:bid,ready:r.ready===true,message:r.message||'',target};
|
||||
state.launchMessage=r.ready?'发布前检查通过;提交任务时服务器仍会再次校验。':'尚不能发布:'+r.message;
|
||||
rerender();
|
||||
}
|
||||
async function launchDeploy(){const sid=state.site?.id,bid=launchChosenBuild(),target=state.launchTarget;const approvals=state.releaseRequests.filter(r=>r.build_id===bid);
|
||||
const model=window.SiteForgeLaunch?.plan({site:state.site,build:state.builds.find(b=>b.id===bid),requests:approvals,requestsKnown:state.releaseRequestsKnown,delivery:state.delivery,target,previewChecked:state.launchReview,preflight:state.launchPreflight});
|
||||
if(!model?.canDeploy)throw Error('请先完成预览检查、独立审批、发布前检查,并确认目标服务器已配置');
|
||||
if(state.launchDeploying)return;
|
||||
if(!confirm(`确定将版本 ${bid.slice(0,12)} 提交到${target==='production'?'正式':'测试'}环境吗?这会触发服务器部署。`))return;
|
||||
state.launchDeploying=true;rerender();
|
||||
try{const r=await api(`/api/production/sites/${encodeURIComponent(sid)}/deploy`,'POST',{build_id:bid,target},{'Idempotency-Key':crypto.randomUUID()});if(state.site?.id!==sid)return;
|
||||
await launchRefresh();state.launchMessage=`部署任务 ${r.id} 已进入后台队列。请刷新任务后,再核查真实网站。`;
|
||||
}finally{if(state.site?.id===sid){state.launchDeploying=false;rerender();}}
|
||||
}
|
||||
async function launchReport(id){const sid=state.site?.id;if(!sid||!state.builds.some(b=>b.id===id))return;const r=await api(`/api/sites/${encodeURIComponent(sid)}/builds/${encodeURIComponent(id)}/report`);if(state.site?.id!==sid||state.view!=='launch')return;
|
||||
const t=$('#launchReport');if(!t)return;t.innerHTML=`<section class="sf-journey-after"><h2>版本质量报告</h2><p>HTML 页面:${esc(r.pages||0)} · 警告 ${esc((r.warnings||[]).length)} 项</p>${r.warnings?.length?`<ul>${r.warnings.slice(0,20).map(w=>`<li>${esc(w)}</li>`).join('')}</ul>`:'<p>自动检查未返回警告,仍需核对业务内容和手机页面。</p>'}</section>`;
|
||||
}
|
||||
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')}
|
||||
async function saveEntry(ev){ev.preventDefault();if(!state.site)return;const el=ev.currentTarget;const data=Object.fromEntries(new FormData(el));const id=data.entry_id;delete data.entry_id;
|
||||
if(!data.route?.trim())delete data.route;
|
||||
try{await api(`/api/sites/${state.site.id}/entries${id?'/'+encodeURIComponent(id):''}`,id?'PATCH':'POST',data);closeModal('entryModal');toast(id?'内容已更新':'内容已添加');await selectSite(state.site.id,'content')}catch(e){notifyError(e)}}
|
||||
async function saveSite(ev){ev.preventDefault();try{const f=ev.currentTarget;const data=Object.fromEntries(new FormData(f));const s=await api('/api/sites','POST',data);closeModal('siteModal');toast('网站创建成功,现在可以添加内容');await loadSites();await selectSite(s.id,'overview')}catch(e){notifyError(e)}}
|
||||
let createStep=0;
|
||||
function createValidate(){
|
||||
const f=$('#siteForm'),name=f.elements.name.value.trim(),domain=f.elements.domain.value.trim();
|
||||
if(!name){return '请填写网站名称。'}
|
||||
if(name.length>150)return '网站名称过长,请缩短后再继续。';
|
||||
if(domain&&!/^https:\/\/[^\s/]+(?:[\/?#][^\s]*)?$/i.test(domain))return '正式域名请填写完整的 https:// 地址,或先留空。';
|
||||
return '';
|
||||
}
|
||||
function renderCreateStep(){
|
||||
const form=$('#siteForm');if(!form)return;
|
||||
form.querySelectorAll('[data-create-step]').forEach(el=>{el.hidden=Number(el.dataset.createStep)!==createStep});
|
||||
form.querySelectorAll('[data-create-indicator]').forEach(el=>{const current=Number(el.dataset.createIndicator)===createStep;if(current)el.setAttribute('aria-current','step');else el.removeAttribute('aria-current')});
|
||||
$('#createProgressText').textContent=`第 ${createStep+1} 步,共 3 步 · ${['填写信息','选择风格','确认创建'][createStep]}`;
|
||||
$('#createBack').hidden=createStep===0;
|
||||
$('#createNext').hidden=createStep===2;
|
||||
$('#createNext').textContent=createStep===0?'下一步:选择风格 →':'下一步:确认信息 →';
|
||||
$('#createSubmit').hidden=createStep!==2;
|
||||
$('#createError').hidden=true;
|
||||
if(createStep===2){const data=Object.fromEntries(new FormData(form));$('#createSummary').innerHTML=`<p><strong>网站名称:</strong>${esc(data.name)}</p><p><strong>网站类型:</strong>${esc(({new:'全新制作网站',migration:'迁移旧站',hybrid:'旧站内容重新设计'})[data.mode]||data.mode)}</p><p><strong>网站风格:</strong>${esc(themeName[data.theme]||'企业品牌')}</p>`}
|
||||
}
|
||||
function openCreateWizard(){const form=$('#siteForm');form.reset();createStep=0;renderCreateStep();openModal('siteModal')}
|
||||
async function saveSite(ev){ev.preventDefault();const error=createValidate();if(error){$('#createError').textContent=error;$('#createError').hidden=false;return}
|
||||
if(createStep!==2){createStep=2;renderCreateStep();return}
|
||||
const button=$('#createSubmit');if(button.disabled)return;button.disabled=true;button.textContent='正在创建…';
|
||||
try{const data=Object.fromEntries(new FormData(ev.currentTarget));const s=await api('/api/sites','POST',data);closeModal('siteModal');toast('网站已建立,下一步请完善真实内容');await loadSites();await selectSite(s.id,'guide')}
|
||||
catch(e){notifyError(e);$('#createError').textContent=e.message||'创建失败,请重试';$('#createError').hidden=false}
|
||||
finally{button.disabled=false;button.textContent='确认创建网站'}
|
||||
}
|
||||
|
||||
async function setTheme(theme){if(!state.site||theme===state.site.theme)return;if(!confirm(`切换到「${themeName[theme]}」?网站内容及已有 URL 将保持不变,外观会在下次构建后更新。`))return;try{await api(`/api/sites/${state.site.id}`,'PATCH',{theme});toast('主题已切换,重新构建后生效');await selectSite(state.site.id,'design')}catch(e){notifyError(e)}}
|
||||
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)}}
|
||||
@@ -178,23 +296,25 @@ function closeMobile(){$('#sideMenu').classList.remove('open');$('#mobileBackdro
|
||||
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;
|
||||
// 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','languages','settings']);
|
||||
const target=params.get('view');const allowed=new Set(['home','sites','overview','guide','launch','content','design','import','build','languages','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,'<button class="btn btn-primary" onclick="location.reload()">重新加载</button>')}}
|
||||
// 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==='language-refresh')await loadLanguage();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);if(action==='release-preflight')await releasePreflight();if(action==='release-approval')await releaseApproval();if(action==='release-deploy')await releaseDeploy();if(action==='release-rollback')await releaseRollback(control.dataset.target,control.dataset.id)}catch(e){notifyError(e)}});
|
||||
document.addEventListener('click',async ev=>{const sidebar=ev.target.closest('.nav-item[data-view]');if(sidebar&&!sidebar.hasAttribute('data-action')){navigate(sidebar.dataset.view);return;}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==='language-refresh')await loadLanguage();if(action==='new-site')openCreateWizard();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);if(action==='release-preflight')await releasePreflight();if(action==='release-approval')await releaseApproval();if(action==='release-deploy')await releaseDeploy();if(action==='release-rollback')await releaseRollback(control.dataset.target,control.dataset.id);if(action==='launch-refresh')await launchRefresh();if(action==='launch-build')await buildNow();if(action==='launch-report')await launchReport(control.dataset.id);if(action==='launch-request-approval')await launchRequestApproval();if(action==='launch-preflight')await launchPreflight();if(action==='launch-deploy')await launchDeploy()}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);if(ev.target.id==='langGroupForm')submitLanguageGroup(ev);if(ev.target.id==='langJoinForm')submitLanguageJoin(ev);if(ev.target.id==='langUnitForm')submitLanguageUnit(ev);if(ev.target.id==='langLinkForm')submitLanguageLink(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()}if(ev.target.id==='langMemberSite')loadLanguageEntries(ev.target.value)});
|
||||
document.addEventListener('change',ev=>{if(ev.target.id==='launchBuildSelect')launchPickBuild(ev.target.value);if(ev.target.id==='launchTarget')launchPickTarget(ev.target.value);if(ev.target.id==='launchReviewCheck'){state.launchReview=ev.target.checked;state.launchPreflight=null;rerender()}if(ev.target.id==='contentType'){state.typ=ev.target.value;rerender()}if(ev.target.id==='langMemberSite')loadLanguageEntries(ev.target.value)});
|
||||
$('#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','languages','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()}});
|
||||
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','guide','launch','content','design','import','build','languages','settings'];if(site&&state.sites.some(s=>s.id===site))await selectSite(site,allowed.includes(view)?view:'overview','none');else{state.selectionToken++;resetLaunch();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'));
|
||||
$('#newSiteTop').addEventListener('click',openCreateWizard);
|
||||
$('#createNext').addEventListener('click',()=>{const error=createValidate();if(error){$('#createError').textContent=error;$('#createError').hidden=false;return}createStep=Math.min(2,createStep+1);renderCreateStep()});
|
||||
$('#createBack').addEventListener('click',()=>{createStep=Math.max(0,createStep-1);renderCreateStep()});
|
||||
$('#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))});
|
||||
$('#mobileBackdrop').addEventListener('click',closeMobile);
|
||||
|
||||
+45
-14
@@ -1,9 +1,10 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SiteForge Studio · 可视化建站</title><link rel="stylesheet" href="/ui/editor.css"><script defer src="/ui/editor-helpers.js"></script><script defer src="/ui/editor.js"></script><link rel="stylesheet" href="/ui/professional-ui.css"><script defer src="/ui/professional-ui.js"></script></head>
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SiteForge Studio · 可视化建站</title><link rel="stylesheet" href="/ui/editor.css"><script defer src="/ui/editor-helpers.js"></script><script defer src="/ui/studio-experience-v829.js"></script><script defer src="/ui/editor.js"></script><link rel="stylesheet" href="/ui/professional-ui.css"><script defer src="/ui/professional-ui.js"></script></head>
|
||||
<body data-sf-page="editor">
|
||||
<div class="studio">
|
||||
<header class="studio-top sf-easy-top">
|
||||
<div class="brand"><a href="/" title="返回网站管理">◈</a><div><b>SiteForge</b><small>页面编辑</small></div></div>
|
||||
<div class="file-name sf-easy-file"><small id="siteName">加载中</small><strong id="pageName">页面编辑器</strong></div>
|
||||
<button type="button" id="studioThemePreviewButton" class="ui-btn ghost sf-theme-preview-entry" onclick="Studio.showThemePreview()" title="不影响当前网站,先比较四套主题">◫ 主题效果</button>
|
||||
<div class="sf-pane-controls"><button type="button" onclick="Studio.togglePane('left')">☰ 内容</button><button type="button" onclick="Studio.togglePane('right')">⚙ 编辑</button></div>
|
||||
<div class="top-center sf-easy-device" role="group" aria-label="预览设备">
|
||||
<button id="deviceDesktop" class="icon-btn on" onclick="Studio.device('desktop')" title="电脑预览" aria-label="电脑预览">电脑</button>
|
||||
@@ -13,10 +14,11 @@
|
||||
<span id="saveStatus" class="save-status sf-easy-save-status" role="status" aria-live="polite">读取中…</span>
|
||||
<button id="saveDraftButton" type="button" class="ui-btn ghost sf-save-btn" onclick="Studio.save()">保存草稿</button>
|
||||
<button id="publishDesignButton" type="button" class="ui-btn primary sf-publish-btn" onclick="Studio.publishDesign()">发布设计</button>
|
||||
<details class="studio-actions"><summary title="其他功能">更多 ▾</summary><div class="studio-actions-menu">
|
||||
<button type="button" id="quickUndo" class="sf-history-quick" onclick="Studio.undo()" title="撤销上一步 (Ctrl+Z)" aria-label="撤销上一步" disabled>↶</button><button type="button" id="quickRedo" class="sf-history-quick" onclick="Studio.redo()" title="重做 (Ctrl+Shift+Z)" aria-label="重做" disabled>↷</button>
|
||||
<details class="studio-actions"><summary title="其他功能">更多 ▾</summary><div class="studio-actions-menu"><button type="button" class="ui-btn ghost sf-more-theme" onclick="Studio.showThemePreview()">◫ 比较四套主题效果</button>
|
||||
<button id="studioUndo" class="ui-btn ghost" onclick="Studio.undo()" disabled>↶ 撤销</button>
|
||||
<button id="studioRedo" class="ui-btn ghost" onclick="Studio.redo()" disabled>↷ 重做</button>
|
||||
<button class="ui-btn ghost" onclick="Studio.showHistory()">版本历史</button>
|
||||
<button class="ui-btn ghost" onclick="Studio.showHistory()">版本历史</button><button class="ui-btn ghost" onclick="Studio.showSharedComponents()">管理公共组件</button>
|
||||
<button class="ui-btn ghost" onclick="Studio.showSiteDesign()">全站页眉与页脚</button>
|
||||
<button class="ui-btn ghost" onclick="Studio.showTokens()">全站设计规范</button>
|
||||
<button class="ui-btn ghost" onclick="Studio.refreshPreview()">刷新预览</button>
|
||||
@@ -24,32 +26,39 @@
|
||||
<button class="ui-btn ghost" onclick="Studio.discardDraft()">放弃草稿</button>
|
||||
</div></details>
|
||||
</header>
|
||||
<div class="sf-steps" aria-label="编辑操作指引"><div class="sf-steps-label">只需 3 步</div><span class="sf-step" id="sfStep1">① 选择或添加区块</span><span class="sf-step-separator">→</span><span class="sf-step" id="sfStep2">② 修改右侧内容</span><span class="sf-step-separator">→</span><span class="sf-step" id="sfStep3">③ 保存并发布</span><button type="button" class="sf-guide-link" onclick="Studio.showHelp()">新手使用指南 ?</button></div>
|
||||
<div class="sf-steps" aria-label="编辑操作指引"><div class="sf-steps-label">只需 3 步</div><span class="sf-step" id="sfStep1">① 点击页面中的内容</span><span class="sf-step-separator">→</span><span class="sf-step" id="sfStep2">② 直接编辑或调整布局</span><span class="sf-step-separator">→</span><span class="sf-step" id="sfStep3">③ 保存并发布</span><button type="button" class="sf-guide-link" onclick="Studio.showHelp()">新手使用指南 ?</button></div>
|
||||
<div class="studio-body">
|
||||
<aside class="left-pane" aria-label="页面内容和区块">
|
||||
<div class="pane-heading"><div><span class="overline">页面内容</span><h3>编辑页面</h3></div><span class="chip" id="blockCount">0 个区块</span></div>
|
||||
<div class="field sf-page-picker"><label for="pageSelect">当前编辑的页面</label><select id="pageSelect" onchange="Studio.choosePage(this.value)"></select></div>
|
||||
<div class="sf-global-entry"><button type="button" class="ui-btn ghost" onclick="Studio.showSiteDesign()">☰ 编辑全站菜单、页眉与页脚</button><small>改一次,全站下一次构建统一生效</small></div>
|
||||
<div class="sf-add-area"><button type="button" id="sfAddBlock" class="sf-add-button" aria-expanded="false" aria-controls="sfLibraryPanel" onclick="Studio.toggleLibrary()">+ 添加内容区块</button><p>例如图片横幅、公司简介、产品列表等</p></div>
|
||||
<section id="sfLibraryPanel" class="sf-library-panel" hidden aria-label="添加区块"><div class="sf-panel-caption">点击类型即可添加到当前页面 <button type="button" onclick="Studio.toggleLibrary(false)" aria-label="收起添加面板">收起 ×</button></div><div class="palette" id="palette"></div><details class="sf-subsection"><summary>从我的组件库添加</summary><div class="palette" id="reusablePalette"></div></details></section>
|
||||
<section id="sfLibraryPanel" class="sf-library-panel" hidden aria-label="添加区块"><div class="sf-panel-caption">点击类型即可添加到当前页面 <button type="button" onclick="Studio.toggleLibrary(false)" aria-label="收起添加面板">收起 ×</button></div><div class="sf-palette-filter"><label class="sr-only" for="paletteSearch">查找内容组件</label><input id="paletteSearch" type="search" placeholder="搜索:横幅、图片、文字…" autocomplete="off" oninput="Studio.filterPalette(this.value)"><button type="button" aria-label="清空组件搜索" onclick="Studio.clearPaletteSearch()">清除</button></div><div id="paletteSummary" class="sf-palette-summary" role="status"></div><div class="palette sf-palette-categories" id="palette"></div><details class="sf-subsection"><summary>从我的组件库添加</summary><p style="font-size:11px;padding:0 9px;color:#7a8795">复制:独立编辑 · 关联插入:后续可选择同步新版</p><div class="palette" id="reusablePalette"></div></details></section>
|
||||
<div class="section-label">当前页面的内容 <span class="section-caption">点击一项进行编辑</span></div>
|
||||
<div class="structure-filter"><label for="blockSearch" class="sr-only">搜索页面内容</label><input id="blockSearch" type="search" placeholder="查找横幅、文字、产品…" autocomplete="off" oninput="Studio.filter(this.value)"><button type="button" onclick="Studio.clearFilter()" aria-label="清除搜索">×</button></div>
|
||||
<div id="blockList" class="block-list" role="list" aria-label="当前页面区块"></div>
|
||||
<p class="structure-hint">点击区块进入右侧编辑;拖动 ⠿ 或使用 ↑ ↓ 调整顺序。</p>
|
||||
<div id="sfInsertHint" class="sf-insert-hint" role="status" hidden></div><div id="blockList" class="block-list" role="list" aria-label="当前页面区块"></div>
|
||||
<p class="structure-hint">支持从左侧拖动排序,也可以直接使用画布上的操作条。</p>
|
||||
<details class="sf-advanced"><summary>高级页面设置 <span>模板、布局和组件保存</span></summary>
|
||||
<div class="field"><label for="pageTemplate">页面模板</label><select id="pageTemplate" onchange="Studio.changeTemplateKind(this.value)"></select></div>
|
||||
<div class="field"><label for="layoutMode">排版方式</label><select id="layoutMode" onchange="Studio.setLayoutMode(this.value)"><option value="append">保留原页面,追加区块</option><option value="replace">用这些区块重新排版页面</option></select></div>
|
||||
<div class="sf-advanced-buttons"><button type="button" class="ui-btn ghost" onclick="Studio.resetPreset()">使用模板预设布局</button><button type="button" class="ui-btn ghost" onclick="Studio.saveReusable()">将所选区块存为组件</button></div>
|
||||
<div class="sf-advanced-buttons"><button type="button" class="ui-btn ghost" onclick="Studio.showSharedComponents()">管理公共组件与同步更新</button><button type="button" class="ui-btn ghost" onclick="Studio.resetPreset()">使用模板预设布局</button><button type="button" class="ui-btn ghost" onclick="Studio.saveReusable()">将所选区块存为组件</button></div>
|
||||
<p class="sf-advanced-note">更换模板预设可能替换当前草稿,请先保存;不会直接改变在线网站。</p>
|
||||
</details>
|
||||
</aside>
|
||||
<section class="preview-workspace"><div class="preview-head"><span class="live-dot" id="previewDot"></span><span id="previewState" role="status" aria-live="polite">实时预览</span><code id="routeDisplay">/</code><span class="preview-note">修改后自动预览 · 不会直接改变线上网站</span></div><div class="stage"><div class="device-frame" id="deviceFrame"><iframe id="sitePreview" title="页面预览" sandbox="allow-scripts" referrerpolicy="no-referrer"></iframe></div></div>
|
||||
<section class="preview-workspace"><div class="preview-head"><span class="live-dot" id="previewDot"></span><span id="previewState" role="status" aria-live="polite">实时预览</span><code id="routeDisplay">/</code><span class="preview-note">单击区块或列内组件即可编辑 · 拖动 ⠿ 可排序 · 拖动列间分隔线可调宽</span></div><div class="stage"><div class="device-frame" id="deviceFrame"><iframe id="sitePreview" title="页面预览" sandbox="allow-scripts" referrerpolicy="no-referrer"></iframe></div></div>
|
||||
<div class="stage-footer"><span id="deviceDetails">桌面端 · 自适应宽度</span><span>当前是草稿预览,不是线上网站</span></div></section>
|
||||
<aside class="right-pane"><div class="pane-heading"><div><span class="overline">步骤 2 · 修改内容</span><h3>编辑选中的区块</h3></div><span class="chip" id="blockTypeLabel">页面</span></div>
|
||||
<div class="inspector-nav" role="tablist" aria-label="属性编辑分类"><button type="button" role="tab" id="tabContent" aria-selected="true" onclick="Studio.changeInspectorTab('content')">内容</button><button type="button" role="tab" id="tabStyle" aria-selected="false" onclick="Studio.changeInspectorTab('style')">样式</button><button type="button" role="tab" id="tabResponsive" aria-selected="false" onclick="Studio.changeInspectorTab('responsive')">手机/平板</button></div>
|
||||
<div class="inspector" id="inspector"><div class="no-selection">先从左侧点击一个内容区块,或者点击“添加内容区块”。</div></div>
|
||||
<div class="inspector" id="inspector"><div class="no-selection">直接点击中间画布的任意区块,即可开始编辑。</div></div>
|
||||
<details class="right-foot sf-advanced-foot"><summary>高级:动态数据变量</summary><p>支持 <code>{{site.name}}</code>、<code>{{page.title}}</code> 等变量;动态内容列表可以按网站内容类型绑定。</p></details>
|
||||
</aside>
|
||||
</div>
|
||||
<dialog id="themePreviewModal" class="sf-theme-showcase" aria-labelledby="themePreviewTitle">
|
||||
<div class="sf-showcase-head"><div><small>仅预览 · 不修改网站内容</small><h2 id="themePreviewTitle">选择适合网站的主题效果</h2><p>先比较布局和手机显示效果,再决定是否到「网站设计」中应用主题。</p></div><button type="button" class="ui-btn ghost" onclick="Studio.closeThemePreview()" aria-label="关闭主题预览">关闭 ×</button></div>
|
||||
<div class="sf-showcase-tools"><label>主题风格<select id="themePreviewTheme" onchange="Studio.updateThemePreview()"><option value="automotive">汽车与出行</option><option value="industrial">工业与制造</option><option value="corporate">企业品牌</option><option value="minimal">简约展示</option></select></label><label>参考页面<select id="themePreviewPage" onchange="Studio.updateThemePreview()"><option value="home">首页</option><option value="listing">产品列表</option><option value="product">产品详情</option><option value="article">文章详情</option><option value="about">关于我们</option><option value="contact">联系我们</option></select></label><div class="sf-showcase-device" role="group" aria-label="主题参考设备"><button type="button" data-theme-device="desktop" aria-pressed="true" onclick="Studio.themeDevice('desktop')">电脑</button><button type="button" data-theme-device="tablet" aria-pressed="false" onclick="Studio.themeDevice('tablet')">平板</button><button type="button" data-theme-device="phone" aria-pressed="false" onclick="Studio.themeDevice('phone')">手机</button></div></div>
|
||||
<p class="sf-showcase-status" id="themePreviewStatus" role="status" aria-live="polite">示例使用主题最新版本,可能与你网站锁定的主题版本不同。不会修改当前页面。</p><div class="sf-showcase-stage"><div id="themePreviewFrameShell" class="sf-showcase-frame"><iframe id="themePreviewFrame" title="主题示例页面" sandbox="allow-scripts" referrerpolicy="no-referrer" loading="lazy"></iframe></div></div>
|
||||
<div class="sf-showcase-footer"><span>预览不会保存、更换或发布主题。确认效果后请到网站设计里操作。</span><a class="ui-btn ghost" href="/theme-gallery" target="_blank" rel="noopener">打开完整主题图库 ↗</a></div>
|
||||
</dialog>
|
||||
<dialog id="sfGuideDialog" class="sf-help-dialog" aria-labelledby="sfGuideTitle">
|
||||
<div class="sf-help-head"><div><span class="overline">新手指南</span><h2 id="sfGuideTitle">3 分钟学会编辑页面</h2></div><button type="button" class="ui-btn ghost" onclick="document.querySelector('#sfGuideDialog').close()">关闭 ×</button></div>
|
||||
<ol class="sf-help-steps"><li><b>先选页面</b><p>左侧顶部切换要编辑的页面;选择现有内容区块,或点击橙色的“添加内容区块”。</p></li><li><b>修改内容</b><p>在右侧“内容”里改标题、文案和图片;切换“样式”或“手机/平板”调整外观。中间自动展示效果。</p></li><li><b>保存草稿,再发布</b><p>点击右上角“保存草稿”可以保留工作。核对后点击“发布设计”,它会更新内容库;正式网站还需要执行静态构建和部署。</p></li></ol>
|
||||
@@ -70,9 +79,13 @@
|
||||
<label><input type="checkbox" name="show_cta"> 显示联系按钮</label>
|
||||
<label>按钮文字 <input name="cta_text" maxlength="40"></label>
|
||||
<label>按钮路径 <input name="cta_href" placeholder="/contact/"></label>
|
||||
<label>导航结构(JSON,每项含 title、route、可选 children)
|
||||
<textarea name="nav" rows="6" style="display:block;width:100%;font-family:monospace" spellcheck="false"></textarea>
|
||||
</label>
|
||||
<section class="sf-visual-nav" aria-label="可视化全站导航管理">
|
||||
<div class="sf-visual-nav-header"><strong>网站菜单</strong><span>直接编辑文字和链接,支持三级菜单</span></div>
|
||||
<div id="siteNavRows" role="group" aria-label="导航菜单列表"></div>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.addSiteNav()">+ 添加一级菜单</button>
|
||||
<p class="sf-visual-nav-help">点击“添加子菜单”即可建立下拉菜单。使用 ↑ ↓ 排序;保存草稿前会校验路径。</p>
|
||||
<textarea name="nav" hidden aria-hidden="true"></textarea>
|
||||
</section>
|
||||
</fieldset>
|
||||
<fieldset style="display:grid;gap:9px;border:1px solid #e2e8f0;border-radius:8px"><legend>全站页脚</legend>
|
||||
<label>企业名称或页脚标题 <input name="footer_title" maxlength="120" style="width:100%"></label>
|
||||
@@ -100,11 +113,24 @@
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.previewSiteDesign()">预览草稿</button>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.discardSiteDesign()">放弃全站草稿</button>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.saveSiteDesign()">保存全站草稿</button>
|
||||
<button class="ui-btn primary" type="button" onclick="Studio.publishSiteDesign()">发布全站设计</button>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.showSiteDesignHistory()">历史版本</button>
|
||||
<button class="ui-btn primary" type="button" onclick="Studio.publishSiteDesign()">审核差异并发布</button>
|
||||
<button class="ui-btn ghost" value="cancel">关闭</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="siteDesignReviewModal" class="sf-global-review" aria-labelledby="siteDesignReviewTitle">
|
||||
<h2 id="siteDesignReviewTitle">全站设计发布审核</h2>
|
||||
<p>本次变更将在下一次静态构建后影响本站所有页面,线上已发布网站不会立即改变。</p>
|
||||
<div id="siteDesignReviewBody" role="status" aria-live="polite"></div>
|
||||
<div class="sf-review-buttons"><button type="button" class="ui-btn ghost" onclick="document.querySelector('#siteDesignReviewModal').close()">返回编辑</button>
|
||||
<button type="button" class="ui-btn primary" onclick="Studio.confirmReviewedSiteDesign()">确认更新全站设计</button></div>
|
||||
</dialog>
|
||||
<dialog id="siteDesignHistoryModal" class="sf-global-review" aria-label="全站设计发布历史">
|
||||
<h2>全站设计历史</h2><p>恢复历史版本只会生成新草稿,不修改已上线的网站。</p>
|
||||
<div id="siteDesignHistoryBody" aria-live="polite"></div>
|
||||
<button type="button" class="ui-btn ghost" onclick="document.querySelector('#siteDesignHistoryModal').close()">关闭</button>
|
||||
</dialog>
|
||||
<dialog id="siteMediaModal" style="border:1px solid #dce5f1;border-radius:14px;padding:24px;width:min(720px,94vw);max-height:85vh;overflow:auto">
|
||||
<h2>本站图片素材库</h2><p style="font-size:13px;color:#66758a">选择图片后更新当前组件;保存草稿、发布设计后才会参与静态构建。</p>
|
||||
<div id="siteMediaGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(135px,1fr));gap:12px"></div>
|
||||
@@ -116,5 +142,10 @@
|
||||
<div id="studioHistoryItems" aria-live="polite"></div>
|
||||
<button class="ui-btn ghost" type="button" onclick="document.querySelector('#studioHistoryModal').close()">关闭</button>
|
||||
</dialog>
|
||||
<dialog id="sharedComponentModal" class="sf-shared-dialog" aria-label="公共组件版本与引用管理">
|
||||
<div class="sf-shared-dialog-head"><div><span class="overline">全站设计系统</span><h2>公共组件管理</h2></div><button class="ui-btn ghost" type="button" onclick="document.querySelector('#sharedComponentModal').close()">关闭 ×</button></div>
|
||||
<p>选择组件查看版本和引用页面。更新组件源文件不会自动改变任何已发布网站。</p>
|
||||
<div id="sharedComponentBody"></div><section id="sharedComponentDetail" aria-live="polite"></section>
|
||||
</dialog>
|
||||
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
||||
</div></body></html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>SiteForge · 网站工作台</title><meta name="description" content="统一管理网站、内容、设计、迁移和构建。">
|
||||
<link rel="icon" href="/favicon.ico"><link rel="stylesheet" href="/ui/workspace.css"><script defer src="/ui/workspace.js"></script></head>
|
||||
<link rel="icon" href="/favicon.ico"><link rel="stylesheet" href="/ui/workspace.css"><script defer src="/ui/workspace-guide.js"></script><script defer src="/ui/workspace-launch.js"></script><script defer src="/ui/workspace.js"></script></head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<aside class="nav-side" id="sideMenu">
|
||||
@@ -12,10 +12,12 @@
|
||||
</div>
|
||||
<div class="nav-group" id="websiteGroup"><div class="nav-label">当前网站</div><div id="sideSiteName" class="site-mini">请先选择网站</div>
|
||||
<button data-view="overview" class="nav-item"><span class="nav-ico">◎</span><span>网站概览</span></button>
|
||||
<button data-view="guide" class="nav-item"><span class="nav-ico">✓</span><span>建站操作指引</span></button>
|
||||
<button data-view="content" class="nav-item"><span class="nav-ico">▧</span><span>内容与页面</span></button>
|
||||
<button data-view="design" class="nav-item"><span class="nav-ico">◈</span><span>外观与设计</span></button>
|
||||
<button data-view="import" class="nav-item"><span class="nav-ico">⇩</span><span>导入与迁移</span></button>
|
||||
<button data-view="build" class="nav-item"><span class="nav-ico">↗</span><span>构建与交付</span></button>
|
||||
<button data-view="build" class="nav-item"><span class="nav-ico">◴</span><span>生成与预览</span></button>
|
||||
<button data-view="launch" class="nav-item"><span class="nav-ico">↗</span><span>检查并上线</span></button>
|
||||
<button data-view="languages" class="nav-item"><span class="nav-ico">文</span><span>多语言管理</span></button>
|
||||
<button data-view="settings" class="nav-item"><span class="nav-ico">⚙</span><span>网站设置</span></button>
|
||||
</div>
|
||||
@@ -38,7 +40,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-backdrop" id="mobileBackdrop"></div><div id="toast" class="toast" role="status" aria-live="polite"></div>
|
||||
<dialog id="siteModal" class="modal"><form id="siteForm" class="modal-inner"><div class="modal-top"><div><p class="overline">CREATE A WEBSITE</p><h2>创建新网站</h2><p class="subtle">只需填写基本信息,其他内容都可以稍后完善。</p></div><button type="button" data-close="siteModal" class="x-btn" aria-label="关闭">×</button></div>
|
||||
<div class="form-fields"><label class="full">网站名称 <input name="name" required maxlength="150" placeholder="例如:HESU Auto 全球官网"></label><label class="full">这次要做什么? <select name="mode"><option value="new">搭建一个全新网站</option><option value="migration">把旧网站迁移过来</option><option value="hybrid">旧内容 + 新内容混合建站</option></select></label><label class="full">选择行业风格 <select name="theme"><option value="automotive">汽车与出行</option><option value="industrial">工业与制造</option><option value="corporate" selected>企业品牌</option><option value="minimal">极简展示</option><option value="trade-pro">Trade Pro · 外贸 B2B(英文)</option></select></label><label class="full">网站域名 <span class="optional">选填</span><input name="domain" placeholder="https://example.com"></label><label class="full">一句话介绍 <span class="optional">选填</span><input name="tagline" placeholder="例如:全球汽车出口与服务"></label></div><div class="modal-actions"><button type="button" data-close="siteModal" class="btn btn-muted">取消</button><button type="submit" class="btn btn-primary">创建并开始 →</button></div></form></dialog>
|
||||
<dialog id="siteModal" class="modal sf-create-dialog"><form id="siteForm" class="modal-inner" novalidate>
|
||||
<div class="modal-top"><div><p class="overline">开始建立网站</p><h2>创建新网站</h2><p class="subtle" id="createProgressText" aria-live="polite">第 1 步,共 3 步 · 选择建站方式</p></div><button type="button" data-close="siteModal" class="x-btn" aria-label="关闭创建窗口">×</button></div>
|
||||
<ol class="sf-create-progress" aria-label="创建网站进度"><li data-create-indicator="0" aria-current="step">基本信息</li><li data-create-indicator="1">选择风格</li><li data-create-indicator="2">确认创建</li></ol>
|
||||
<section class="sf-create-step" data-create-step="0"><h3>你想制作什么网站?</h3><p class="sf-guide-desc">先选目的,再填名称,后续内容都能修改。</p>
|
||||
<div class="form-fields"><label class="full">网站名称 <span class="sf-required">必填</span><input name="name" required maxlength="150" placeholder="例如:HESU Auto 全球官网"></label>
|
||||
<label class="full">建站方式<select name="mode"><option value="new">全新制作网站</option><option value="migration">迁移已有 WordPress 网站</option><option value="hybrid">在旧内容基础上重新设计</option></select></label></div></section>
|
||||
<section class="sf-create-step" data-create-step="1" hidden><h3>选择一种网站风格</h3><p class="sf-guide-desc">这里只是起始模板,建好网站仍可以更换。</p>
|
||||
<div class="form-fields"><label class="full">行业主题<select name="theme"><option value="automotive">汽车与出行</option><option value="industrial">工业与制造</option><option value="corporate" selected>企业品牌</option><option value="minimal">极简展示</option><option value="trade-pro">Trade Pro · 外贸 B2B(英文)</option></select></label>
|
||||
<label class="full">一句话介绍 <span class="optional">选填</span><input name="tagline" placeholder="例如:全球汽车出口与服务"></label></div></section>
|
||||
<section class="sf-create-step" data-create-step="2" hidden><h3>确认网站信息</h3><p class="sf-guide-desc">创建只会生成一个网站项目,不会自动发布到互联网。</p>
|
||||
<div class="sf-create-summary" id="createSummary"></div>
|
||||
<div class="form-fields"><label class="full">正式域名 <span class="optional">可稍后设置</span><input name="domain" type="url" placeholder="https://www.example.com"></label></div>
|
||||
<div class="sf-guide-safety">创建完成后建议先添加真实内容,再编辑页面,最后预览、检查和发布。</div></section>
|
||||
<p id="createError" class="sf-create-error" role="alert" hidden></p>
|
||||
<div class="modal-actions"><button type="button" data-close="siteModal" class="btn btn-muted">取消</button><button type="button" id="createBack" class="btn btn-muted" hidden>上一步</button><button type="button" id="createNext" class="btn btn-primary">下一步:选择风格 →</button><button type="submit" id="createSubmit" class="btn btn-primary" hidden>确认创建网站</button></div>
|
||||
</form></dialog>
|
||||
<dialog id="entryModal" class="modal"><form id="entryForm" class="modal-inner"><div class="modal-top"><div><p class="overline">SITE CONTENT</p><h2 id="entryModalTitle">添加内容</h2><p class="subtle">按业务填写信息即可。详细布局可以进入可视化编辑器修改。</p></div><button type="button" data-close="entryModal" class="x-btn" aria-label="关闭">×</button></div><input type="hidden" name="entry_id"><div class="form-fields"><label>内容类型<select name="type"><option value="page">网站页面</option><option value="product">产品</option><option value="vehicle">车型</option><option value="post">新闻文章</option><option value="brand">品牌</option><option value="case">案例</option><option value="service">服务项目</option></select></label><label>状态<select name="status"><option value="published">可参与网站构建</option><option value="draft">内容草稿</option></select></label><label class="full">标题<input name="title" required maxlength="250" placeholder="输入页面或产品标题"></label><label class="full">页面地址 <span class="optional">新内容可留空</span><input name="route" placeholder="/products/example/"></label><label class="full">简短介绍<textarea name="summary" rows="2" placeholder="显示在列表和摘要中的介绍"></textarea></label><label class="full">正文内容 <span class="optional">可选</span><textarea name="body" rows="5" placeholder="填写业务正文,复杂排版请使用可视化设计器"></textarea></label><details class="form-expand full"><summary>SEO 设置(进阶)</summary><div class="form-fields"><label class="full">SEO 标题<input name="seo_title" placeholder="默认使用内容标题"></label><label class="full">Meta 描述<textarea name="seo_description" rows="2"></textarea></label></div></details></div><div class="modal-actions"><button type="button" data-close="entryModal" class="btn btn-muted">取消</button><button type="submit" class="btn btn-primary">保存内容</button></div></form></dialog>
|
||||
</body></html>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
"""Single source of truth for the SiteForge platform release identity."""
|
||||
import os
|
||||
VERSION = "8.21.3-dev.1"
|
||||
VERSION = "8.31.1-dev.1"
|
||||
PLATFORM_NAME = "SiteForge Factory"
|
||||
BUILD_SHA = os.environ.get("SITEFORGE_GIT_SHA", "unknown").strip()[:40] or "unknown"
|
||||
|
||||
@@ -6,7 +6,7 @@ services:
|
||||
dockerfile: Dockerfile.enterprise
|
||||
args:
|
||||
SITEFORGE_GIT_SHA: ${SITEFORGE_GIT_SHA:-unknown}
|
||||
image: siteforge/web:8.21.3-dev.1
|
||||
image: siteforge/web:8.31.0-dev.1
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@@ -48,7 +48,7 @@ services:
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
worker:
|
||||
image: siteforge/web:8.21.3-dev.1
|
||||
image: siteforge/web:8.31.0-dev.1
|
||||
command: ['python','-m','app.worker']
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
|
||||
+2
-3
@@ -6,7 +6,7 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
SITEFORGE_GIT_SHA: ${SITEFORGE_GIT_SHA:-unknown}
|
||||
image: siteforge/web:8.21.3-dev.1
|
||||
image: siteforge/web:8.31.0-dev.1
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@@ -16,7 +16,6 @@ services:
|
||||
SITEFORGE_MAX_PENDING_JOBS: ${SITEFORGE_MAX_PENDING_JOBS:-100}
|
||||
SITEFORGE_MAX_PENDING_JOBS_PER_SITE: ${SITEFORGE_MAX_PENDING_JOBS_PER_SITE:-5}
|
||||
SITEFORGE_OUTBOUND_PORTS: ${SITEFORGE_OUTBOUND_PORTS:-80,443}
|
||||
SITEFORGE_BUILD_TIMEOUT_SECONDS: ${SITEFORGE_BUILD_TIMEOUT_SECONDS:-300}
|
||||
SITEFORGE_MYSQL_ALLOWED_HOSTS: ${SITEFORGE_MYSQL_ALLOWED_HOSTS:-}
|
||||
SITEFORGE_MYSQL_ALLOWED_TABLES: ${SITEFORGE_MYSQL_ALLOWED_TABLES:-}
|
||||
SITEFORGE_ADMIN_PASSWORD: ${SITEFORGE_ADMIN_PASSWORD:?Set SITEFORGE_ADMIN_PASSWORD in .env}
|
||||
@@ -46,7 +45,7 @@ services:
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
worker:
|
||||
image: siteforge/web:8.21.3-dev.1
|
||||
image: siteforge/web:8.31.0-dev.1
|
||||
depends_on:
|
||||
siteforge: {condition: service_started}
|
||||
init: true
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# V8.22 高级可视化编辑器 — 开发实现说明
|
||||
|
||||
**继承基线**:SiteForge V8.21.3,四套主题与移动端导航不变。
|
||||
|
||||
## 文件
|
||||
|
||||
- `app/static/studio-canvas-bridge.js`:注入 sandbox iframe 的独立画布工具层。浏览器只传受限指令到上级编辑器;禁止执行任意 HTML/JS 指令。
|
||||
- `app/static/editor-helpers.js`:纯函数校验、按 ID 排序、列宽边界、画布命令白名单。
|
||||
- `app/static/editor.js`:受信 iframe 来源检查、按区块 ID 查找并操作草稿,撤销与版本继续复用原有机制。
|
||||
- `app/main.py`:编辑器预览 HTML 注入上述脚本,不向真实静态构建添加编辑工具。
|
||||
- `app/templates/editor.html`、`app/static/editor.css`:更清晰的引导和插入反馈。
|
||||
- `tests/test_v822_canvas.py`、`tests_js/editor_ux.test.cjs`:新增专项测试。
|
||||
- `scripts/studio_canvas_smoke_v822.py`:需要本机 HTTP 访问许可的真实 Chromium E2E。
|
||||
- `scripts/studio_canvas_isolated_browser_v822.py`:无需网络的真实 Chromium iframe 交互检查。
|
||||
- `examples/Studio_V8_22_画布级可视化编辑器_交互演示.html`:完全独立可打开、模拟数据。
|
||||
|
||||
## 操作
|
||||
|
||||
1. 登录后台选站点、打开「页面设计」。
|
||||
2. 点击画布区块显示橙色边框;顶部黑色工具栏可直接移动/拖拽、插入、复制或删除。
|
||||
3. 双击顶层区块标题(如 Hero 标题)改文字,Enter 确认、Esc 取消。
|
||||
4. 桌面端选择多列布局,用相邻列之间的橙色握柄拖动列宽,也可用方向键逐步调整。
|
||||
5. 切换平板/手机预览后检查样式,按原流程「保存草稿」→「发布设计」→「构建」→「部署」。
|
||||
|
||||
## 限制
|
||||
|
||||
- 保留声明式区块模型:不允许任意像素绝对定位和任意脚本插入。
|
||||
- 画布内层子组件尚不支持直接双击修改,需右侧属性编辑。
|
||||
- 列宽拖拽仅针对桌面端;平板/手机采用既有响应式字段。
|
||||
- 草稿发布到内容库并非直接上线,需要构建并通过发布审批。
|
||||
- 最终验收详情:`reports/V8_22_验收报告.md`。
|
||||
@@ -0,0 +1,50 @@
|
||||
# SiteForge V8.23 — 高级可视化编辑器第二阶段
|
||||
|
||||
**版本**:`8.23.0-dev.1`
|
||||
**基线**:V8.22.0-dev.1 完整源码
|
||||
**阶段**:开发集成版;未通过生产环境验收
|
||||
|
||||
## 1. 用户操作方式
|
||||
|
||||
在站点工作台打开 `/editor/{site_id}/{entry_id}`,添加一个「多列自由布局」区块,并在任一列添加标题、图文组合或行动号召等子组件。
|
||||
|
||||
- 点击中间预览中的**列内组件**,右侧「内容」自动显示相应组件的普通属性表单。
|
||||
- 在组件上悬浮显示快捷工具条,允许复制、删除、同列上下排序。选中态使用蓝绿色边框与顶层橙色选中态区分。
|
||||
- 对支持纯文本编辑的标题/描述双击,直接输入。富文本 HTML 内容仍使用安全受控的富文本配置,不直接将 DOM 反写到数据模型。
|
||||
- 选中顶层多列布局,切换右侧「手机/平板」,可选择均分、左宽右窄、左窄右宽,并设置列间距、手机及平板列数、跨列、顺序和显隐。
|
||||
- 更改均属于未保存草稿。保存草稿后可以发布设计,但正式站点还需构建、审批与部署。
|
||||
|
||||
## 2. 实际文件修改
|
||||
|
||||
| 文件 | 改动 |
|
||||
| --- | --- |
|
||||
| `app/static/studio-canvas-bridge.js` | 列内直接子组件工具栏、选中与文字修改事件,继续使用 sandbox iframe |
|
||||
| `app/static/editor-helpers.js` | 查找/校验父子 ID、子组件移动、布局预设等纯函数 |
|
||||
| `app/static/editor.js` | 嵌套操作命令接收、内容属性卡片、列宽预设、草稿状态更新 |
|
||||
| `app/static/editor.css` | 嵌套编辑卡片与预设按钮样式 |
|
||||
| `app/templates/editor.html` | 画布帮助文案 |
|
||||
| `tests/test_nested_v823.py` | 嵌套区块 Schema、草稿接口和安全界面回归 |
|
||||
| `tests/test_nested_helpers_v823.cjs` | 过期 ID、拒绝非法操作、列宽、移动及复制安全检查 |
|
||||
| `scripts/studio_nested_browser_v823.py` | Chromium 沙箱 `srcdoc` 直接交互验证 |
|
||||
| `scripts/regression_v823.py` | 43 个测试模块独立分批执行及原始日志 |
|
||||
| `scripts/check.sh`、`scripts/check.ps1`、CI | 加入新版回归入口、升级版本标识 |
|
||||
|
||||
## 3. 兼容性与安全边界
|
||||
|
||||
1. 不改变已有 SQL Schema,不启用任意 HTML/JavaScript 组件,不修改静态站发布器对安全媒体与链接的控制。
|
||||
2. 父组件 ID 与子组件 ID 必须同时匹配当前页面内的真实布局。不存在的组件、非法字段与未知操作在父编辑器拒绝。
|
||||
3. 目前 **只支持多列布局直接子组件的画布操作**。更深层嵌套允许在后端声明式模型中存在,但尚不提供多层画布操作路径。
|
||||
4. 布局预设限于 2 列的左宽/右宽;1–4 列可使用等宽。当前未实现任意绝对定位、多组件跨列拖放或像素级自由布局。
|
||||
5. 组件库仍然是「复制一个快照到页面」模式,**不是跨站点引用、自动同步或协作编辑**;这部分不能算作完成。
|
||||
6. 浏览器传递操作通过 `iframe.contentWindow` 来源验证;沙箱仅设置 `allow-scripts`,不授予同源访问权限。保存/发布仍走已有认证、CSRF、修订冲突验证。
|
||||
|
||||
## 4. 验收记录
|
||||
|
||||
- 完整 Python 历史和新增回归:**43 个测试模块、352 项用例、15 个隔离批次,全部通过**,详见 `reports/v823-regression/`。
|
||||
- JavaScript 历史测试:9 项通过;另有嵌套 JS 专项断言脚本通过。
|
||||
- Chromium 隔离画布:点击子组件、复制、双击修改标题的事件链路通过。
|
||||
- HTML 离线交互演示:通过 Chromium `set_content` 执行检查,组件点击、标题编辑、列宽预设和设备切换无脚本错误。
|
||||
- HTTP + Worker:真实启动服务、登录/CSRF、建站、构建、ZIP 下载与预览全部通过。
|
||||
- **未通过/未执行**:Chromium 导航到 localhost 的真实后台 E2E,被运行环境管理员策略阻止;真实 Docker、PostgreSQL/Redis、生产服务器和官方 Art Design Pro 完整编译未在本轮执行。
|
||||
|
||||
**建议下一步**:完善复杂多层节点定位、可视化布局栅格和跨页面共享组件版本引用,然后把 Art Design Pro 官方工程加入真实集成编译和自动化浏览器测试。
|
||||
@@ -0,0 +1,59 @@
|
||||
# SiteForge Enterprise V8.24 — 深层布局编辑与内层组件复用
|
||||
|
||||
**版本:** 8.24.0-dev.1
|
||||
**状态:** 开发集成版。无需数据库结构迁移。来源:V8.23 完整项目源码。
|
||||
|
||||
## 1. 改造目标与边界
|
||||
|
||||
使使用者在编辑器中**看得到、选得中、改得了**两层多列布局:
|
||||
|
||||
- 在顶层「多列布局」的第 1 层列中增加「内层布局」;
|
||||
- 内层布局的各列可直接添加富文本、图文、优势、行动号召组件;
|
||||
- 点击第二层组件(画布或右侧结构卡片),可以直接修改文字、复制、排序、删除;
|
||||
- 内层布局本身支持等宽 / 左宽右窄 / 左窄右宽、列间距、手机和平板列数;
|
||||
- 从站点媒体库选择图片时,应用到**正在选中的组件**,不误改上层布局;
|
||||
- 可以将当前选中的列内组件保存到本站复用组件库,并在选中列内组件时将组件副本插入其后。
|
||||
|
||||
**限制:** 当前支持「外层 layout → 内层 layout → 内容块」,不开放第三层 layout(遵循原服务端 `blueprints.validate_blocks` 的嵌套深度限制)。组件库使用**复制入页**,不是跨页自动同步。不会添加任意 HTML、JavaScript、自由绝对定位、数据库新表或新的发布捷径。
|
||||
|
||||
## 2. 编辑器操作
|
||||
|
||||
1. 进入 `/editor/{site_id}/{entry_id}`,选中或添加「多列自由布局」。
|
||||
2. 在某一列点击「+ 内层布局」,右侧「画布组件」列表会显示内层布局。
|
||||
3. 选中内层布局,用「+ 富文本」「+ 图文组合」「+ 核心优势」「+ 行动号召」添加内容。
|
||||
4. 点击中间画布中的第二层组件、修改标题与描述。可点复制、上移、下移、删除。
|
||||
5. 选中内层布局,选择布局比例与手机/平板列数。更改外层设置不会覆写内层布局数据。
|
||||
6. 需要重复利用时,选中子组件,点击「☆ 保存当前组件到组件库」;再次选择组件库条目,复制进入所选列内。
|
||||
7. 保存草稿 → 审核设计 → 发布设计 → 执行静态构建;**仅点击发布设计不会直接更新正式网站**。
|
||||
|
||||
## 3. 代码变化
|
||||
|
||||
- `app/static/editor-helpers.js`:ID 安全定位(`findNestedChild`、`findChildInRoot`、`findLayout`),限制从画布传来的非法操作;客户端从不接受事件中夹带的数据库位置索引。
|
||||
- `app/static/editor.js`:任意已支持层级的点击编辑与树状卡片、内层响应式控件和添加操作、子组件素材选择及复用组件保存/插入。
|
||||
- `app/static/studio-canvas-bridge.js`:继续在受限 iframe 中分发可靠的 ID 消息,保留事件源比对和区块高亮。
|
||||
- `tests/test_deep_nested_v824.py`:静态渲染、草稿、预览、安全嵌套深度及组件库行为。
|
||||
- `tests_js/deep_nested_v824.test.cjs`:深层定位、非法命令、列宽和 ID 复制校验。
|
||||
- `scripts/studio_nested_browser_v824.py`:离线 Chromium 交互演示验收,**不是已登录后台浏览器 E2E**。
|
||||
- `scripts/regression_v824.py`:独立分批自动化回归、超时与原始日志。
|
||||
|
||||
## 4. 验证命令
|
||||
|
||||
```bash
|
||||
python -m compileall -q app
|
||||
node --check app/static/editor.js
|
||||
node --test tests_js/*.test.cjs
|
||||
python -m pytest tests/test_deep_nested_v824.py -q
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/studio_nested_browser_v824.py
|
||||
python scripts/regression_v824.py --timeout 65 --group-size 2
|
||||
```
|
||||
|
||||
查看 `reports/v824-regression/result.json`。各模块进程相互隔离,但这不代替 Docker、PostgreSQL/Redis 及生产备份/发布验收。
|
||||
|
||||
## 5. 上线前阻断项
|
||||
|
||||
- 使用真实后台会话与正式 iframe,在常规浏览器完成点击、拖拽、撤销、保存、版本回滚流程;本轮的隔离 Chromium 演示不足以关闭此项。
|
||||
- 确认四套主题在全部页面的桌面/手机实际效果,尤其是内外层栅格混用与窄屏折行。
|
||||
- 测试大型页面接近 120 个区块上限、重复 ID、非法媒体 URL 与编辑冲突。
|
||||
- 官方 Art Design Pro 完整工程尚未随本项目提供,安装器不等于官方工程已完成编译。
|
||||
- 企业级 PostgreSQL、Redis、多 Worker、远端发布与恢复仍需部署环境的独立验收。
|
||||
@@ -0,0 +1,91 @@
|
||||
# SiteForge Enterprise V8.25 — 公共组件与选择性同步
|
||||
|
||||
**版本:** `8.25.0-dev.1`
|
||||
**状态:** 开发集成版;在 V8.24 完整源码基础上增量开发。
|
||||
**生产约束:** 未经 PostgreSQL/Redis、正式浏览器登录和远程部署验收,不得标记为生产可上线。
|
||||
|
||||
## 目标和本轮交付
|
||||
|
||||
将原先的“复制一次、以后完全独立”的组件库升级为两个明确选项:
|
||||
|
||||
- **复制插入:** 区块是当前页面的独立副本,未来不会随源组件更新。
|
||||
- **关联插入:** 页面保存一个合法的 `shared_ref`,用于之后**预检、挑选页面、生成新草稿**。不会在页面构建时远程引用组件,也不会在源组件修改时自动更改任何已发布页面。
|
||||
|
||||
新增源组件不可变版本快照、历史版本查询与恢复为新版、引用页面列表、本地修改检测、过期预检保护、跨站点隔离、事务式选择性同步、解除关联按钮。保留原四套主题、移动端抽屉导航与可选 Tabs、两层安全布局、URL/SEO、历史版本及独立静态发布。
|
||||
|
||||
## 管理入口与操作
|
||||
|
||||
1. 选择网站和页面 → 打开原来的 `/editor/{site_id}/{entry_id}`。
|
||||
2. 选中一个区块(也可以是双层多列布局中的列内组件) → “保存当前组件到组件库”。
|
||||
3. 左侧“添加内容” → “从我的组件库添加”,根据需要选择“复制”或“关联插入”。
|
||||
4. 顶部“更多 → 管理公共组件”,或左侧高级设置中的同名按钮。
|
||||
5. 选中公共组件 → 查看历史版本、查看引用/同步;可使用当前选中区块的内容创建新版本,也可以将一个历史快照“恢复为新版本”。历史版本不被覆盖。
|
||||
6. 检查引用页。**存在本地修改、发布冲突或已是最新版的页面不可直接勾选**。
|
||||
7. 选择允许同步的页面 → 确认 → 这些页面生成新的设计草稿;逐页预览、保存与发布。网站仍然需要重新构建、执行正式审批部署。
|
||||
8. 需要完全独立时,在属性栏点击“解除关联、保留当前内容”。
|
||||
|
||||
演示:[公共组件版本与同步的离线交互页](../examples/V8_25_公共组件_版本与同步演示.html)。该演示使用模拟数据,不连接 SiteForge 后台。
|
||||
|
||||
## 数据与接口
|
||||
|
||||
### 增量 Schema
|
||||
|
||||
沿用 `reusable_components`,新增:
|
||||
|
||||
`reusable_component_versions(component_id, version, block_json, snapshot_hash, created_at)`,主键 `(component_id, version)`。针对现存旧组件仅能为**当时当前版本**补快照,无法凭空还原过去没有保存的历史版本。
|
||||
|
||||
页面区块新增一个**可选、严格验证**的 `shared_ref`:
|
||||
|
||||
```json
|
||||
{"id":"source_component_id","version":2,"hash":"sha256_64_hex"}
|
||||
```
|
||||
|
||||
引用元数据随安全区块数据一同保存,不产生运行时动态远程引用。指纹排除拷贝后重新生成的块 ID 和来源引用,比较实际区块内容。不能通过本功能执行任意 HTML/JS。
|
||||
|
||||
### API(所有接口要求登录/CSRF 和相应站点权限)
|
||||
|
||||
| 方法 | 路径 | 权限 | 功能 |
|
||||
|---|---|---|---|
|
||||
| GET | `/api/sites/{site}/components` | Viewer | 本站组件与当前快照 SHA |
|
||||
| POST | `/api/sites/{site}/components` | Editor | 新建组件,初始版本 v1 |
|
||||
| PUT | `/api/sites/{site}/components/{id}` | Editor | 按 `expected_version` 乐观锁创建下一版本 |
|
||||
| GET | `/api/sites/{site}/components/{id}/versions` | Viewer | 查询不可变版本列表 |
|
||||
| GET | `/api/sites/{site}/components/{id}/versions/{version}` | Viewer | 读取已校验 SHA 的历史快照 |
|
||||
| GET | `/api/sites/{site}/components/{id}/usage` | Viewer | 预检被引用页面、版本、冲突、`plan_token` |
|
||||
| POST | `/api/sites/{site}/components/{id}/sync` | Editor | 按 `plan_token` 和页面 ID 列表同步到草稿 |
|
||||
| DELETE | `/api/sites/{site}/components/{id}` | Editor | 无任何引用时允许删除 |
|
||||
|
||||
同步请求样例:
|
||||
|
||||
```json
|
||||
{"plan_token":"64位预检SHA256","entry_ids":["页面ID1","页面ID2"]}
|
||||
```
|
||||
|
||||
### 同步安全边界
|
||||
|
||||
- 源组件版本发生变化、预检结果不一致、被选页面出现本地修改或页面草稿乐观锁冲突 → 409 拒绝。
|
||||
- 所选页面最多 100 个;事务内重新核对完整页面哈希与草稿修订号。
|
||||
- `BEGIN IMMEDIATE` 的读/比较/写窗口和事务性快照日志;在 SQLite 下启用写锁,在 PostgreSQL 兼容层中使用事务顾问锁。
|
||||
- 事务任一页面失败会整体回滚。只有新草稿变化,发布的 `entries.meta_json` 不变。
|
||||
- 对于一个页面中的同一组件多个引用,同步所有满足条件的该组件实例;任何一个已在本地编辑都会拒绝整页,避免部分覆盖。
|
||||
- 原页面的 URL、SEO、正文等未更改,组件源只在设计层按明确操作应用。
|
||||
|
||||
## 验收与运行
|
||||
|
||||
```bash
|
||||
python -m pytest -q tests/test_v825_shared_components.py
|
||||
node --check app/static/editor.js
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/verify_release.py
|
||||
python scripts/regression_v825.py --timeout 55 --group-size 2
|
||||
```
|
||||
|
||||
本轮已完成:23 批共 370 项 Python 回归;13 项 Node JavaScript 回归;HTTP + Worker E2E;隔离 Chromium HTML 演示的 PC 和 390px 手机布局。参见 [`V8_25_验收报告.html`](../reports/V8_25_验收报告.html)。
|
||||
|
||||
## 尚未完成
|
||||
|
||||
- 当前同步是**选择性草稿更新**,不是一个“所有页面实时引用同一可变组件”的无版本动态系统。设计成这样是为了防止意外批量改动已发布站点。
|
||||
- 全站页眉、页脚、导航仍由现有 `site_design_v87` 管理,尚未纳入本次公共组件源版本与跨页面同步模型。
|
||||
- 没有完成多人实时协同编辑、视觉差异对比、正式 Art Design Pro 官方工程编译或真实 HTTPS 已登录 Chromium 后台验收。
|
||||
- PostgreSQL/Redis 多 Worker、真实 S3/SFTP/Agent 发布、灾备恢复仍需独立生产化测试。
|
||||
@@ -0,0 +1,32 @@
|
||||
# V8.26 全站设计管理 — 实现与验收
|
||||
|
||||
**基线:** SiteForge V8.25.0-dev.1。当前源码版本:V8.26.0-dev.1。
|
||||
|
||||
## 核心架构
|
||||
|
||||
已有 `site_design_drafts` 与 `site_design_publications` 保存全站草稿与发布快照。本轮**不改变数据库 Schema**,主要在 `app/site_design_v87.py` 增加审核差异、审核令牌、历史快照查询与安全恢复。前端主要在 `app/templates/editor.html`、`app/static/editor.js`、`app/static/editor.css`。
|
||||
|
||||
- 导航采用树形数据,层级不超过 3,总元素不超过 48、同层最多 18;仍执行原 URL 白名单校验。
|
||||
- `GET /api/sites/{site}/site-design/review` 将已发布配置与当前草稿逐字段比较,返回变更清单、影响范围及绑定 site_id、草稿版本、当前站点设置哈希的 `plan_token`。
|
||||
- `POST /api/sites/{site}/site-design/publish-reviewed` 仅 Owner 可调用;在同一数据库事务内校验草稿版本和 token 后发布。旧发布接口为兼容保留,但当前编辑器改用审核发布接口。
|
||||
- `GET /api/sites/{site}/site-design/publications/{id}` 返回所属站点历史版本及结构化差异。
|
||||
- `GET /api/sites/{site}/site-design/live-hash` 返回当前已发布站点设置哈希。
|
||||
- `POST /api/sites/{site}/site-design/publications/{id}/restore` 在单一事务中检查历史归属、当前正式配置哈希、预期草稿版本及目标快照是否合法,然后写入新的全站草稿。不会直接修改 `sites.settings_json`。
|
||||
|
||||
## 安全保证
|
||||
|
||||
1. **跨站点隔离**:历史记录必须属于当前 site_id,否则返回 404;鉴权由 `enterprise.require` 处理。
|
||||
2. **审核过期保护**:草稿版本或站点已发布 settings 变化,审核令牌失效;`publish-reviewed` 返回 409。
|
||||
3. **原子发布与恢复**:读/校验/更新使用 `BEGIN IMMEDIATE` 事务窗口,降低读写竞态。
|
||||
4. **原有发布安全不变**:全站设计发布只影响内容库和下一次静态构建,不会替换线上目录或跳过正式发布审批。
|
||||
5. **前端差异防 XSS**:所有用户输入展示均使用 `textContent`,不将差异数据当作 HTML 执行。
|
||||
|
||||
## 验收结果与限制
|
||||
|
||||
- 23 个隔离 Python 测试批次:376 项通过,0 失败、0 超时。
|
||||
- JavaScript:13 项通过;`node --check` 通过。
|
||||
- HTTP + Worker:健康检查、登录、CSRF、站点/页面创建、异步构建、ZIP 下载和预览通过。
|
||||
- Chromium:离线页面通过 `set_content` 隔离加载验证新增导航、子菜单、保存、审核与历史模拟(0 脚本错误);**没有完成真实登录后台的浏览器 E2E**。
|
||||
- **未执行**真实 PostgreSQL/Redis、远程正式部署与 Art Design Pro 官方工程编译。
|
||||
|
||||
**兼容提醒:** 历史发布快照恢复不会修改已经发布的网站;为了让恢复内容上线,必须在站点设计审核后重新发布、构建和部署。
|
||||
@@ -0,0 +1,73 @@
|
||||
# SiteForge Enterprise V8.27 — 易上手工作台与操作指引
|
||||
|
||||
## 本轮交付性质
|
||||
|
||||
V8.27.0-dev.1 是 V8.26 完整源码上的**增量集成版**,不替换原后台 API,也不改变原数据库结构和静态构建引擎。历史版本功能继续保留。
|
||||
|
||||
## 改动范围
|
||||
|
||||
| 位置 | 已实现的操作 |
|
||||
|---|---|
|
||||
| 网站创建弹窗 | 拆成三步:基本信息 → 选择行业风格 → 预览和确认;支持上一步、必填校验、避免重复提交 |
|
||||
| 当前网站操作指引 | 根据已保存内容、构建状态、Worker 任务与远程发布历史生成步骤;逐项有“开始操作”跳转 |
|
||||
| 网站概览 | 突出“下一步应该做什么”,常用操作缩减为内容、设计、生成与预览 |
|
||||
| 术语说明 | 区分内容保存、静态构建、检查与审批、正式部署;不把下载 ZIP、发布设计误称为上线 |
|
||||
| 数据状态 | 仅自动建立的示例首页不会使“内容准备”自动完成;用户主动修改初始首页或添加正式内容才计入 |
|
||||
| 手机界面 | 创建设计向导与引导列表适配窄屏,侧栏默认关闭,不增加底部固定遮挡 |
|
||||
|
||||
### 操作路径
|
||||
|
||||
1. 登录 → 工作台 → 创建网站。
|
||||
2. 分三步填写网站名称、模式、主题和可选域名。
|
||||
3. 系统创建项目,自动切换到“建站操作指引”。
|
||||
4. 根据引导准备内容(旧站模式自动跳转迁移入口),可进入可视化编辑器修改样式。
|
||||
5. 执行静态构建,进入构建页面查看任务、预览和质量报告。
|
||||
6. 人工检查后由审批人员批准,再发起正式环境发布;必须核查实际域名。
|
||||
|
||||
### 使用者可读的状态定义
|
||||
|
||||
- **已保存**:写入 SiteForge 内容数据库;访客网站可能尚未变化。
|
||||
- **已构建**:成功生成了一个静态版本;不能推断质量检查或发布已通过。
|
||||
- **正式发布记录**:有成功记录;仍需要在实际域名核实当前可用性。
|
||||
- **构建可能过期**:已保存内容/站点在最近完成构建之后变更,提示人工复核。
|
||||
- **设计**:始终视为可优化,不因为选择主题自动声明设计验收完成。
|
||||
|
||||
## 代码结构
|
||||
|
||||
- `app/templates/workspace.html`:三步创建表单、引导导航入口。
|
||||
- `app/static/workspace-guide.js`:纯函数 `SiteForgeJourney.plan`;无本地跨用户数据缓存、无额外业务接口。
|
||||
- `app/static/workspace.js`:向导控制、流程状态渲染、已有 API 调用。
|
||||
- `app/static/workspace.css`:清晰的步骤条、状态列表、移动端布局。
|
||||
- `tests_js/workspace_guide_v827.test.cjs`:引导状态单元测试。
|
||||
- `tests/test_v827_guided_workspace.py`:真实测试客户端页面/接口联通。
|
||||
- `scripts/browser_guide_v827.py`:实际工作台 JS 的 Chromium 隔离交互检查(模拟 API,不经过用户真实账号)。
|
||||
- `scripts/regression_v827.py`:原项目分批 pytest 入口。
|
||||
|
||||
## 本轮测试及证据
|
||||
|
||||
- 原项目 Python 测试模块合计 **379 条用例通过**(24 批运行,其中 22 批直接通过;第 10 批组合运行超时、两模块拆开后 7+7 通过;第 22 批原断言对老结构写死而失败,更新以新模块校验后重新运行 21 项通过)。**一键回归稳定性未通过**。
|
||||
- Node.js JavaScript 测试 **23 项通过**(原有 13 项 + 新增 10 项)。
|
||||
- 原项目 `scripts/http_end_to_end.py` 已通过真实 Web + Worker 隔离端到端链路。
|
||||
- `scripts/browser_guide_v827.py`:实际 V8.27 workspace 代码,模拟网络接口在 Chromium 隔离页测试,验证三步表单、空名称阻断、只在最后创建、创建后显示状态、手机不溢出、0 页面脚本错误。
|
||||
- `scripts/verify_release.py`、`python -m compileall -q app` 和 `node --check` 通过。
|
||||
|
||||
## 尚未完成/生产上线限制
|
||||
|
||||
- 正式 Art Design Pro 官方工程的 `pnpm build` 及统一 SSO 登录未验收。
|
||||
- 已登录真实服务器浏览器 E2E、真实手机测试尚未完成;Chromium 测试使用了模拟 API。
|
||||
- PostgreSQL/Redis 多 Worker 真实压力及灾备、远程发布/回滚尚未在真实生产环境完整验收。
|
||||
- 生产上线路径需要继续按原 P5 发布审批执行;本轮**不绕过**角色、审批或安全检查。
|
||||
- 因原回归执行器存在组合批次偶发停滞,**不得将本轮 Python 回归描述为一键全绿**。
|
||||
|
||||
## 推荐复核命令
|
||||
|
||||
```bash
|
||||
python -m pytest -q tests/test_v827_guided_workspace.py tests/test_workspace_v812.py
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/browser_guide_v827.py
|
||||
python scripts/verify_release.py
|
||||
python scripts/regression_v827.py --group-size 2 --timeout 35
|
||||
```
|
||||
|
||||
> 最后一条命令的运行环境可能出现分组超时,查看 `reports/v827-regression/result.json` 与原始批次日志。正式发布前仍需要真实 Web/Art/PG/Redis/发布服务器的独立验收。
|
||||
@@ -0,0 +1,67 @@
|
||||
# SiteForge Enterprise V8.28 — 新手友好上线操作中心
|
||||
|
||||
**版本**:8.28.0-dev.1
|
||||
**交付类型**:V8.27 完整源码的增量集成版(开发验收,不是生产验收)
|
||||
**设计原则**:用户只看到当前应执行的操作;用日常中文解释状态;不能把构建、审批或任务排队错误地称为“已上线”。
|
||||
|
||||
## 1. 打开方式
|
||||
|
||||
登录工作台 → 选择当前网站 → 左侧 **检查并上线**。也可从网站概览的快捷卡片、建站操作指引(审核并上线)进入。
|
||||
|
||||
## 2. 六个步骤
|
||||
|
||||
| 步骤 | 使用者要做的事 | 系统做的检查 | 常见问题 |
|
||||
|---|---|---|---|
|
||||
| 1. 选择网站版本 | 选择已完成的静态构建;如果没有则点击“开始构建” | 只列出 `status=complete` 的构建 | 网站保存成功不代表已构建 |
|
||||
| 2. 预览并检查质量 | 打开预览、核对图片和 SEO;勾选本地检查框 | 人工确认只存在于当前页面会话,不是服务器审批 | 检查不同设备和关键链接 |
|
||||
| 3. 提交审批 | 向另一位审核人提交申请 | 读取服务器审批状态;重复 pending/approved 不重新申请 | 自己不能审批自己提交的申请 |
|
||||
| 4. 发布前检查 | 审批通过后点击“执行发布前检查” | 调用既有 `/api/production/sites/{site}/preflight/{build}` | 会重新校验构建、质量、迁移和审批 |
|
||||
| 5. 提交部署 | 选择测试或正式环境,再提交 | 只有前述条件全部满足且目标已配置时才启用;服务器仍重新校验 | 未配置目标时联系运维配置 Agent/SFTP/S3 |
|
||||
| 6. 真实网址核查 | 等待 Worker 完成,再到网站地址检查 | 读取发布记录,但**不**把发布记录视为外网验收成功 | 检查 DNS、TLS、CDN 缓存、主页、联系表单 |
|
||||
|
||||
**请注意正确顺序**:先提交审批、由其他有权限的人员通过,再执行发布前检查。之前页面中“检查发布条件”放在“提交审批”之前,实际服务器会因为缺乏审批而拒绝,是本版修正的重要体验问题。
|
||||
|
||||
## 3. 安全边界及兼容性
|
||||
|
||||
- **没有新开任何发布后门**。申请、检查、部署都调用原有服务端端点,继续受登录、CSRF、站点角色、质量检查、独立审核、Worker 任务与安全出网限制。
|
||||
- 所有临时确认与发布前检查结果仅用于当前会话的按钮状态,不写入业务数据库;浏览器刷新后需重新操作。换站、换构建或换环境会清除相关确认,防止复用错误状态。
|
||||
- 发布前检查与部署存在时间差,服务端部署操作必须再次校验。客户端按钮仅用于简化引导,不能替代后端检查。
|
||||
- 读取审批状态失败时不宣称“未申请”或者“已批准”;读取目标配置失败时不宣称目标已经配置。
|
||||
- 只显示当前网站对应版本的审批/发布记录,跳过不属于所选站点的结果。
|
||||
- 已有“构建与交付”的专业管理功能继续保留在高级折叠区域,原有 API、数据库 Schema、URL、构建产物和发布审批策略均未改变。
|
||||
- 正式域名按钮只接受符合 HTTPS 格式的地址;在离线演示中使用 example.com 占位地址,不会访问生产环境。
|
||||
|
||||
## 4. 非技术使用者视角的改善
|
||||
|
||||
原流程需要在“建站指引、构建、审批中心、质量报告、部署记录”之间来回跳转,也容易把构建成功当作上线。现在正常日常操作可以在一个页面内完成,进阶操作仍可进入专业工具。
|
||||
|
||||
导航明确命名为 **“生成与预览”** 和 **“检查并上线”**,避免技术用户和普通用户混淆。移动端在 390px 宽度验证按钮、表单自适应,无横向溢出。
|
||||
|
||||
## 5. 模块与回归入口
|
||||
|
||||
- `app/static/workspace-launch.js`:可单独测试的发布流程状态模型。
|
||||
- `app/static/workspace.js`:上线操作中心 UI、API 操作、审批状态读取及站点切换保护。
|
||||
- `app/templates/workspace.html`、`app/static/workspace.css`:统一入口与响应式布局。
|
||||
- `tests_js/workspace_launch_v828.test.cjs`:上线流程前置条件、防跨站/跨版本、目标环境、是否真正上线等场景。
|
||||
- `tests/test_v828_launch_center.py`:页面/脚本/API 连接检查。
|
||||
- `scripts/browser_launch_v828.py`:使用真实工作台 JS 的隔离 Chromium 测试(模拟 API,不触碰生产)。
|
||||
- `scripts/regression_v828.py`:独立 pytest 批次回归执行器,保留原始结果。
|
||||
- `examples/V8_28_简洁上线操作中心_交互演示.html`:双击即可打开的模拟数据界面,可点击“模拟另一位审核人批准”。
|
||||
|
||||
### 本地检查命令
|
||||
|
||||
```bash
|
||||
python -m pytest -q tests/test_v828_launch_center.py tests/test_v827_guided_workspace.py
|
||||
node --test tests_js/*.test.cjs
|
||||
python scripts/browser_launch_v828.py
|
||||
python scripts/http_end_to_end.py
|
||||
python scripts/verify_release.py
|
||||
python scripts/regression_v828.py --group-size 2 --timeout 25
|
||||
```
|
||||
|
||||
## 6. 未关闭的正式交付门槛
|
||||
|
||||
- Chromium 本次模拟原始后台 Web/API,不能代替真实账号登录、真正不同账号审批、实际公网部署与回滚的端到端演练。
|
||||
- Art Design Pro 官方完整工程未随项目提供,官方 `pnpm build` 尚无实测证据。
|
||||
- PostgreSQL/Redis 多 Worker、多实例、真实 Agent/SFTP/S3 与备份恢复、CDN、公网 DNS/TLS 仍需独立验收。
|
||||
- 复杂 WordPress 站点的全量 URL/SEO/图片及插件迁移还不能直接宣称 1:1 全部完成。
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SiteForge Studio V8.23 · 嵌套组件编辑</title><style>
|
||||
*{box-sizing:border-box}body{margin:0;background:#f3f5f8;color:#263447;font:14px system-ui,-apple-system,'Microsoft YaHei',sans-serif}button,input,select{font:inherit}button{cursor:pointer}header{height:70px;display:flex;align-items:center;gap:16px;padding:0 22px;background:white;border-bottom:1px solid #e3e8ef}header strong{font-size:19px}header small{color:#8795a6}header .sep{flex:1}header button{border:1px solid #e0e5eb;border-radius:8px;padding:9px 15px;background:white;color:#34485e}header .save{background:#ec772f;border-color:#ec772f;color:#fff;font-weight:750}.step{padding:11px 25px;background:#fff8f2;border-bottom:1px solid #f1d9c4;color:#ad642e;font-weight:700}main{display:grid;grid-template-columns:246px minmax(0,1fr) 290px;height:calc(100vh - 112px);min-height:620px}aside{background:#fff;overflow-y:auto;padding:21px;border-right:1px solid #e7eaf0}aside:last-child{border-right:0;border-left:1px solid #e7eaf0}h3{margin:0 0 20px;font-size:16px}p.hint{color:#8995a6;font-size:12px;line-height:1.7;margin-top:6px}.group{margin:16px 0 9px;color:#8b99a9;font-size:11px;font-weight:800;letter-spacing:1px}select,input{border:1px solid #dce3ed;border-radius:8px;padding:10px;width:100%;background:white;color:#293d55}.navitem,.childlist{border:1px solid #e3e9f0;border-radius:9px;padding:11px;margin-bottom:8px;background:#fbfcfe;cursor:pointer}.navitem.selected,.childlist.selected{border-color:#e98037;background:#fff8f2}.navitem small,.childlist small{display:block;color:#8898a6;margin-top:5px}.add{width:100%;margin-top:13px;background:#e8782f;color:white;padding:12px;border:0;border-radius:9px;font-weight:800}.canvas-shell{display:flex;flex-direction:column;min-width:0}.canvas-head{background:#fff;height:51px;display:flex;align-items:center;justify-content:space-between;padding:0 24px;border-bottom:1px solid #e7ebf0;font-size:12px;color:#77879a}.switch button{background:#f1f4f8;padding:8px 10px;border:0;border-radius:6px;margin:0 2px;color:#63768a}.switch button.on{background:#ed7e37;color:#fff}.stage{flex:1;overflow:auto;padding:28px;background:radial-gradient(#d6dde8 1px,transparent 1px);background-size:20px 20px}.page{margin:auto;max-width:1050px;min-width:0;background:white;box-shadow:0 12px 48px #2437471c;transition:width .2s}.page.phone{width:min(390px,100%)}.page.tablet{width:min(780px,100%)}.sitehead{height:70px;border-bottom:1px solid #e7e7e7;display:flex;align-items:center;justify-content:space-between;padding:0 7%;color:#25364d}.logo{font-size:18px;font-weight:900;color:#e8752f}.sitehead small{font-size:12px;color:#6f7e8d}.hero{padding:40px 7% 22px}.hero h1{font-size:clamp(25px,3vw,36px);margin:0 0 8px}.hero p{color:#8793a1;margin:0}.layout{padding:12px 7% 60px;display:grid;gap:16px;grid-template-columns:var(--cols,1fr 1fr)}.page.phone .layout{grid-template-columns:1fr!important}.page.tablet .layout{grid-template-columns:1fr 1fr}.unit{border:2px dashed transparent;background:#f5f7fa;min-height:125px;border-radius:12px;padding:20px 17px;cursor:pointer;position:relative;overflow:visible}.unit:hover{border-color:#e8b48e}.unit.active{border-color:#e8752f;background:#fffaf6}.unit h2{font-size:18px;margin:12px 0 8px}.unit p{color:#7c8b9b;font-size:13px;line-height:1.65}.unit b{font-size:11px;color:#e27530}.unit .mini{display:none;gap:4px;position:absolute;right:7px;top:7px;z-index:2}.unit:hover .mini,.unit.active .mini{display:flex}.mini button{font-size:11px;border:0;background:#34465a;color:#fff;border-radius:5px;padding:5px 7px}.placeholder{background:linear-gradient(135deg,#f7d2aa,#e9edf5);height:100px;border-radius:6px;margin:6px 0}.ctrl{margin-bottom:17px}.ctrl label{display:block;font-size:12px;color:#78899b;font-weight:750;margin-bottom:7px}.ctrl input{font-size:13px}.buttons{display:flex;gap:6px;flex-wrap:wrap}.buttons button{background:#fff8f1;color:#965522;border:1px solid #edc9aa;border-radius:7px;font-size:11px;padding:8px 10px}.buttons button:hover{background:#ffe9d8}.notice{font-size:12px;color:#ba712d;background:#fff4e9;padding:10px 12px;border-radius:9px;line-height:1.6}.meta{padding:12px 25px;color:#8b95a4;font-size:11px;border-top:1px solid #dfe5ed;background:#fafbfd}@media(max-width:1100px){main{grid-template-columns:200px minmax(0,1fr) 240px}aside{padding:15px}.stage{padding:10px}}@media(max-width:750px){main{display:flex;flex-direction:column;height:auto}aside{border:0;overflow:visible}.stage{min-height:480px}.canvas-shell{order:-1}.sitehead{height:50px}}
|
||||
</style></head><body>
|
||||
<header><strong><span style="color:#ea792e">◈</span> SiteForge Studio</strong><small>V8.23 · 嵌套画布编辑演示</small><span class="sep"></span><span id="status" style="font-size:12px;color:#8392a1">演示草稿</span><button onclick="undo()">↶ 撤销</button><button onclick="save()" class="save">保存草稿</button></header>
|
||||
<div class="step">① 点击画布中的组件 → ② 直接编辑右侧内容或选择布局 → ③ 保存草稿</div>
|
||||
<main><aside><h3>页面内容</h3><label style="font-size:12px;color:#70839a">当前页面</label><select><option>首页 /</option></select><div class="group">页面结构</div><div class="navitem">▤ 封面横幅<small>网站主标题</small></div><div class="navitem selected">▥ 双列布局<small>可编辑下面的列内组件</small></div><div id="tree"></div><button class="add" onclick="add()">+ 在本列新增组件</button><p class="hint">此页面使用本地演示数据。实际系统具备草稿、版本校验与构建接口。</p></aside>
|
||||
<div class="canvas-shell"><div class="canvas-head"><span>● 可视化预览 · 点击卡片直接编辑</span><span class="switch"><button class="on" onclick="device('desktop',this)">电脑</button><button onclick="device('tablet',this)">平板</button><button onclick="device('phone',this)">手机</button></span></div><div class="stage"><div class="page" id="page"><div class="sitehead"><span class="logo">HESU AUTO</span><small>HOME VEHICLES ABOUT CONTACT ☰</small></div><div class="hero"><h1>China Sourcing. Global Delivery.</h1><p>拖动布局、点击组件、修改文字,立即看到页面变化。</p></div><div class="layout" id="layout"></div></div></div><div class="meta">画布修改只影响演示数据 · 不会连接、修改或发布正式网站</div></div>
|
||||
<aside><h3>编辑选中的组件</h3><div id="selectedInfo" class="notice">点击中间任一组件开始编辑。</div><div class="group">内容</div><div class="ctrl"><label>组件标题</label><input id="title" oninput="field('title',this.value)" placeholder="请输入标题"></div><div class="ctrl"><label>说明文字</label><input id="description" oninput="field('description',this.value)" placeholder="请输入简介"></div><div class="group">列宽布局</div><div class="buttons"><button onclick="preset('equal')">等宽两列</button><button onclick="preset('left')">左宽右窄</button><button onclick="preset('right')">左窄右宽</button></div><p class="hint">手机布局独立显示为单列,不会覆盖电脑端的列宽设置。</p><div class="group">组件操作</div><div class="buttons"><button onclick="duplicate()">复制组件</button><button onclick="up()">上移</button><button onclick="remove()">删除</button></div></aside></main>
|
||||
<script>
|
||||
const data=[{id:1,title:'车辆采购',description:'严格筛选、全球品牌、稳定采购渠道',col:0},{id:2,title:'国际物流',description:'集装箱、滚装及多式联运方案',col:1},{id:3,title:'海外交付',description:'海外网络、本地服务与配件支持',col:0}];let state={rows:data,selected:1,preset:'equal'},history=[];
|
||||
const $=id=>document.getElementById(id);const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));function snapshot(){history.push(JSON.stringify(state));if(history.length>25)history.shift()};function changed(){document.querySelector('#status').textContent='有未保存的修改';draw()}
|
||||
function draw(){const col=state.preset==='left'?'2fr 1fr':state.preset==='right'?'1fr 2fr':'1fr 1fr';$('layout').style.setProperty('--cols',col);const buckets=[[],[]];state.rows.forEach(r=>buckets[r.col].push(r));$('layout').innerHTML=buckets.map((items,ci)=>`<div>${items.map(r=>`<section class="unit ${state.selected===r.id?'active':''}" onclick="select(${r.id})"><div class="mini"><button onclick="event.stopPropagation();select(${r.id})">✎</button><button onclick="event.stopPropagation();clone(${r.id})">⧉</button><button onclick="event.stopPropagation();drop(${r.id})">×</button></div><b>第 ${ci+1} 列 · 内容组件</b><h2>${esc(r.title)}</h2><p>${esc(r.description)}</p></section>`).join('')}</div>`).join('');$('tree').innerHTML=state.rows.map(r=>`<div class="childlist ${state.selected===r.id?'selected':''}" onclick="select(${r.id})">第${r.col+1}列 · ${esc(r.title)}<small>点击选中后编辑</small></div>`).join('');const item=state.rows.find(r=>r.id===state.selected);$('selectedInfo').textContent=item?'已选择:'+item.title:'请先点击一个组件';$('title').value=item?.title||'';$('description').value=item?.description||'';}
|
||||
function select(id){state.selected=id;draw()}function field(k,v){const r=state.rows.find(r=>r.id===state.selected);if(!r)return;r[k]=v;changed()}function preset(v){snapshot();state.preset=v;changed()}function clone(id){const r=state.rows.find(r=>r.id===id);if(!r)return;snapshot();const next={...r,id:Math.max(0,...state.rows.map(x=>x.id))+1};state.rows.push(next);state.selected=next.id;changed()}function duplicate(){clone(state.selected)}function drop(id){snapshot();state.rows=state.rows.filter(r=>r.id!==id);state.selected=state.rows[0]?.id||null;changed()}function remove(){drop(state.selected)}function up(){const i=state.rows.findIndex(x=>x.id===state.selected);if(i<1)return;snapshot();[state.rows[i-1],state.rows[i]]=[state.rows[i],state.rows[i-1]];changed()}function add(){snapshot();const selected=state.rows.find(r=>r.id===state.selected);const n={id:Math.max(0,...state.rows.map(x=>x.id))+1,title:'新内容组件',description:'点击右侧填写内容',col:selected?.col||0};state.rows.push(n);state.selected=n.id;changed()}function undo(){if(!history.length)return;state=JSON.parse(history.pop());changed()}function save(){$('status').textContent='演示草稿已保存(仅当前页面)'}function device(name,button){$('page').className='page '+(name==='desktop'?'':name);document.querySelectorAll('.switch button').forEach(b=>b.classList.toggle('on',b===button))}draw();</script></body></html>
|
||||
@@ -0,0 +1,54 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SiteForge Studio V8.24 · 深层画布编辑</title><style>
|
||||
*{box-sizing:border-box}body{margin:0;background:#f3f5f8;color:#263447;font:14px system-ui,-apple-system,'Microsoft YaHei',sans-serif}button,input,select{font:inherit}button{cursor:pointer}header{height:70px;display:flex;align-items:center;gap:16px;padding:0 22px;background:white;border-bottom:1px solid #e3e8ef}header strong{font-size:19px}header small{color:#8795a6}header .sep{flex:1}header button{border:1px solid #e0e5eb;border-radius:8px;padding:9px 15px;background:white;color:#34485e}header .save{background:#ec772f;border-color:#ec772f;color:#fff;font-weight:750}.step{padding:11px 25px;background:#fff8f2;border-bottom:1px solid #f1d9c4;color:#ad642e;font-weight:700}main{display:grid;grid-template-columns:246px minmax(0,1fr) 290px;height:calc(100vh - 112px);min-height:620px}aside{background:#fff;overflow-y:auto;padding:21px;border-right:1px solid #e7eaf0}aside:last-child{border-right:0;border-left:1px solid #e7eaf0}h3{margin:0 0 20px;font-size:16px}p.hint{color:#8995a6;font-size:12px;line-height:1.7;margin-top:6px}.group{margin:16px 0 9px;color:#8b99a9;font-size:11px;font-weight:800;letter-spacing:1px}select,input{border:1px solid #dce3ed;border-radius:8px;padding:10px;width:100%;background:white;color:#293d55}.navitem,.childlist{border:1px solid #e3e9f0;border-radius:9px;padding:11px;margin-bottom:8px;background:#fbfcfe;cursor:pointer}.navitem.selected,.childlist.selected{border-color:#e98037;background:#fff8f2}.navitem small,.childlist small{display:block;color:#8898a6;margin-top:5px}.add{width:100%;margin-top:13px;background:#e8782f;color:white;padding:12px;border:0;border-radius:9px;font-weight:800}.canvas-shell{display:flex;flex-direction:column;min-width:0}.canvas-head{background:#fff;height:51px;display:flex;align-items:center;justify-content:space-between;padding:0 24px;border-bottom:1px solid #e7ebf0;font-size:12px;color:#77879a}.switch button{background:#f1f4f8;padding:8px 10px;border:0;border-radius:6px;margin:0 2px;color:#63768a}.switch button.on{background:#ed7e37;color:#fff}.stage{flex:1;overflow:auto;padding:28px;background:radial-gradient(#d6dde8 1px,transparent 1px);background-size:20px 20px}.page{margin:auto;max-width:1050px;min-width:0;background:white;box-shadow:0 12px 48px #2437471c;transition:width .2s}.page.phone{width:min(390px,100%)}.page.tablet{width:min(780px,100%)}.sitehead{height:70px;border-bottom:1px solid #e7e7e7;display:flex;align-items:center;justify-content:space-between;padding:0 7%;color:#25364d}.logo{font-size:18px;font-weight:900;color:#e8752f}.sitehead small{font-size:12px;color:#6f7e8d}.hero{padding:40px 7% 22px}.hero h1{font-size:clamp(25px,3vw,36px);margin:0 0 8px}.hero p{color:#8793a1;margin:0}.layout{padding:12px 7% 60px;display:grid;gap:16px;grid-template-columns:var(--cols,1fr 1fr)}.page.phone .layout{grid-template-columns:1fr!important}.page.phone .inner-grid{grid-template-columns:1fr!important}.page.phone .inner-grid[style*="1fr 1fr"]{grid-template-columns:1fr 1fr!important}.page.tablet .layout{grid-template-columns:1fr 1fr}.unit{border:2px dashed transparent;background:#f5f7fa;min-height:125px;border-radius:12px;padding:20px 17px;cursor:pointer;position:relative;overflow:visible}.unit:hover{border-color:#e8b48e}.unit.active{border-color:#e8752f;background:#fffaf6}.unit h2{font-size:18px;margin:12px 0 8px}.unit p{color:#7c8b9b;font-size:13px;line-height:1.65}.unit b{font-size:11px;color:#e27530}.unit .mini{display:none;gap:4px;position:absolute;right:7px;top:7px;z-index:2}.unit:hover .mini,.unit.active .mini{display:flex}.mini button{font-size:11px;border:0;background:#34465a;color:#fff;border-radius:5px;padding:5px 7px}.placeholder{background:linear-gradient(135deg,#f7d2aa,#e9edf5);height:100px;border-radius:6px;margin:6px 0}.ctrl{margin-bottom:17px}.ctrl label{display:block;font-size:12px;color:#78899b;font-weight:750;margin-bottom:7px}.ctrl input{font-size:13px}.buttons{display:flex;gap:6px;flex-wrap:wrap}.buttons button{background:#fff8f1;color:#965522;border:1px solid #edc9aa;border-radius:7px;font-size:11px;padding:8px 10px}.buttons button:hover{background:#ffe9d8}.notice{font-size:12px;color:#ba712d;background:#fff4e9;padding:10px 12px;border-radius:9px;line-height:1.6}.meta{padding:12px 25px;color:#8b95a4;font-size:11px;border-top:1px solid #dfe5ed;background:#fafbfd}@media(max-width:1100px){main{grid-template-columns:200px minmax(0,1fr) 240px}aside{padding:15px}.stage{padding:10px}}@media(max-width:750px){main{display:flex;flex-direction:column;height:auto}aside{border:0;overflow:visible}.stage{min-height:480px}.canvas-shell{order:-1}.sitehead{height:50px}}
|
||||
</style></head><body>
|
||||
<header><strong><span style="color:#ea792e">◈</span> SiteForge Studio</strong><small>V8.24 · 深层画布编辑演示</small><span class="sep"></span><span id="status" style="font-size:12px;color:#8392a1">演示草稿</span><button onclick="undo()">↶ 撤销</button><button onclick="save()" class="save">保存草稿</button></header>
|
||||
<div class="step">① 点击画布中的组件 → ② 直接编辑右侧内容或选择布局 → ③ 保存草稿</div>
|
||||
<main><aside><h3>页面内容</h3><label style="font-size:12px;color:#70839a">当前页面</label><select><option>首页 /</option></select><div class="group">页面结构</div><div class="navitem">▤ 封面横幅<small>网站主标题</small></div><div class="navitem selected">▥ 双列布局<small>可直接操作内层布局和组件</small></div><div id="tree"></div><button class="add" onclick="add()">+ 在本列新增组件</button><p class="hint">此页面使用本地演示数据。实际系统具备草稿、版本校验与构建接口。</p></aside>
|
||||
<div class="canvas-shell"><div class="canvas-head"><span>● 可视化预览 · 点击卡片直接编辑</span><span class="switch"><button class="on" onclick="device('desktop',this)">电脑</button><button onclick="device('tablet',this)">平板</button><button onclick="device('phone',this)">手机</button></span></div><div class="stage"><div class="page" id="page"><div class="sitehead"><span class="logo">HESU AUTO</span><small>HOME VEHICLES ABOUT CONTACT ☰</small></div><div class="hero"><h1>China Sourcing. Global Delivery.</h1><p>内外层布局、手机列数与媒体选择均可直接操作。</p></div><div class="layout" id="layout"></div></div></div><div class="meta">画布修改只影响演示数据 · 不会连接、修改或发布正式网站</div></div>
|
||||
<aside><h3>编辑选中的组件</h3><div id="selectedInfo" class="notice">点击中间任一组件开始编辑。</div><div class="group">内容</div><div class="ctrl"><label>组件标题</label><input id="title" oninput="field('title',this.value)" placeholder="请输入标题"></div><div class="ctrl"><label>说明文字</label><input id="description" oninput="field('description',this.value)" placeholder="请输入简介"></div><div class="group">布局设置</div><div class="buttons"><button onclick="preset('equal')">等宽两列</button><button onclick="preset('left')">左宽右窄</button><button onclick="preset('right')">左窄右宽</button></div><p class="hint">手机布局独立显示为单列,不会覆盖电脑端的列宽设置。</p><div class="group">内层响应式</div><div class="buttons"><button onclick="mobileCols(1)">手机 1 列</button><button onclick="mobileCols(2)">手机 2 列</button></div><p id="mobileStatus" class="hint">手机 1 列</p><div class="group">组件操作</div><div class="buttons"><button onclick="duplicate()">复制组件</button><button onclick="up()">上移</button><button onclick="remove()">删除</button></div></aside></main>
|
||||
<script>
|
||||
// This is a deliberately isolated offline demonstration. It never calls a server.
|
||||
let state={
|
||||
rows:[{id:1,title:'采购流程',description:'中国供应链与整车采购方案',col:0},{id:2,title:'海外枢纽',description:'覆盖不同地区的本地服务网络',col:1}],
|
||||
nested:[{id:4,title:'车辆选择',description:'帮助客户选到合适车型',col:0},{id:5,title:'运输方案',description:'灵活选择运输方式',col:1}],
|
||||
selected:4,preset:'equal',innerPreset:'equal',mobile:1
|
||||
};
|
||||
let history=[];
|
||||
const $=id=>document.getElementById(id);
|
||||
const esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
const current=()=>[...state.rows,...state.nested].find(x=>x.id===state.selected);
|
||||
function snapshot(){history.push(JSON.stringify(state));if(history.length>30)history.shift()}
|
||||
function changed(){ $('status').textContent='未保存的演示修改';draw() }
|
||||
function card(r,name){return `<section class="unit ${state.selected===r.id?'active':''}" onclick="selectItem(${r.id})"><div class="mini"><button onclick="event.stopPropagation();selectItem(${r.id})">编辑</button><button onclick="event.stopPropagation();cloneItem(${r.id})">复制</button><button onclick="event.stopPropagation();dropItem(${r.id})">删除</button></div><b>${esc(name)}</b><h2 contenteditable="false" ondblclick="inlineTitle(event,${r.id})">${esc(r.title)}</h2><p>${esc(r.description)}</p></section>`}
|
||||
function draw(){
|
||||
const widths=v=>v==='left'?'2fr 1fr':v==='right'?'1fr 2fr':'1fr 1fr';
|
||||
$('layout').style.setProperty('--cols',widths(state.preset));
|
||||
const deepcols=[[],[]];state.nested.forEach(r=>deepcols[r.col].push(r));
|
||||
const inner=`<div class="group" style="padding-top:5px;margin-bottom:10px">▥ 第二层布局 · 可直接点击里面的组件</div><div style="display:grid;grid-template-columns:${widths(state.innerPreset)};gap:10px" class="inner-grid" id="innerGrid">${deepcols.map((items,ci)=>`<div>${items.map(r=>card(r,'内层第 '+(ci+1)+' 列')).join('')}</div>`).join('')}</div>`;
|
||||
const cols=[[],[]];state.rows.forEach(r=>cols[r.col].push(r));
|
||||
$('layout').innerHTML=cols.map((items,ci)=>`<div>${items.map(r=>card(r,'外层第 '+(ci+1)+' 列')).join('')}${ci===0?inner:''}</div>`).join('');
|
||||
if($('page').classList.contains('phone')){
|
||||
$('innerGrid').style.gridTemplateColumns=state.mobile===1?'1fr':'1fr 1fr';
|
||||
}
|
||||
const both=[...state.rows.map(r=>({r,depth:0})),...state.nested.map(r=>({r,depth:1}))];
|
||||
$('tree').innerHTML=both.map(({r,depth})=>`<div class="childlist ${state.selected===r.id?'selected':''}" onclick="selectItem(${r.id})" style="margin-left:${depth*13}px">${depth?'↳ 第二层':'第1层'} · ${esc(r.title)}<small>第${r.col+1}列 · 点击编辑</small></div>`).join('');
|
||||
let row=current();$('selectedInfo').textContent=row?(state.nested.includes(row)?'第二层组件:':'外层组件:')+row.title:'点击画布上的组件';
|
||||
$('title').value=row?.title||'';$('description').value=row?.description||'';
|
||||
$('mobileStatus').textContent='手机设置:'+state.mobile+' 列,仅影响演示中的手机视图';
|
||||
}
|
||||
function selectItem(id){state.selected=id;draw()}
|
||||
function field(key,value){const row=current();if(!row)return;row[key]=value;changed()}
|
||||
function preset(value){snapshot();const row=current();if(row && state.nested.includes(row))state.innerPreset=value;else state.preset=value;changed()}
|
||||
function mobileCols(n){snapshot();state.mobile=n;changed()}
|
||||
function inlineTitle(event,id){event.stopPropagation();selectItem(id);const el=document.querySelector('.unit.active h2');if(!el)return;el.contentEditable='true';el.focus();const finish=()=>{const row=current();if(row){snapshot();row.title=el.textContent.trim().slice(0,140)}el.contentEditable='false';changed()};el.addEventListener('blur',finish,{once:true});el.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();el.blur()}})}
|
||||
function cloneItem(id){const row=[...state.rows,...state.nested].find(x=>x.id===id);if(!row)return;snapshot();const next={...row,id:Math.max(...[...state.rows,...state.nested].map(x=>x.id))+1};(state.nested.includes(row)?state.nested:state.rows).push(next);state.selected=next.id;changed()}
|
||||
function duplicate(){cloneItem(state.selected)}
|
||||
function dropItem(id){snapshot();state.rows=state.rows.filter(x=>x.id!==id);state.nested=state.nested.filter(x=>x.id!==id);state.selected=[...state.nested,...state.rows][0]?.id||null;changed()}
|
||||
function remove(){dropItem(state.selected)}
|
||||
function up(){const group=state.nested.some(r=>r.id===state.selected)?state.nested:state.rows;const i=group.findIndex(r=>r.id===state.selected);if(i<1)return;snapshot();[group[i],group[i-1]]=[group[i-1],group[i]];changed()}
|
||||
function add(){snapshot();const group=current()&&state.nested.includes(current())?state.nested:state.rows;const r={id:Math.max(...[...state.rows,...state.nested].map(x=>x.id))+1,title:'新组件',description:'点击并输入需要展示的内容',col:current()?.col||0};group.push(r);state.selected=r.id;changed()}
|
||||
function undo(){if(!history.length)return;state=JSON.parse(history.pop());changed()}
|
||||
function save(){$('status').textContent='演示草稿已保存(未连接服务器)'}
|
||||
function device(name,button){$('page').className='page '+(name==='desktop'?'':name);document.querySelectorAll('.switch button').forEach(b=>b.classList.toggle('on',b===button));draw()}
|
||||
draw();
|
||||
</script></body></html>
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SiteForge V8.25 · 公共组件管理演示</title><style>
|
||||
:root{color-scheme:light;font-family:system-ui,'Microsoft Yahei',sans-serif;color:#24354a}*{box-sizing:border-box}body{margin:0;background:#f4f6f9;font-size:14px}.top{background:#fff;border-bottom:1px solid #e5eaf1;padding:16px max(16px,calc((100vw - 1160px)/2));display:flex;justify-content:space-between;gap:12px;align-items:center}.brand{font-weight:800;font-size:19px}.brand b{color:#db762e}.chip{border-radius:50px;padding:6px 11px;background:#f1f3f6;color:#65758b;font-size:11px}.wrap{max-width:1160px;margin:30px auto;padding:0 20px}.heading{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:flex-end;margin:0 0 22px}.heading h1{font-size:27px;margin:0 0 8px}.muted{color:#728096;font-size:13px;margin:0;line-height:1.6}.columns{display:grid;grid-template-columns:260px 1fr;gap:20px}.card{background:white;border:1px solid #e4e9f0;border-radius:13px;padding:20px;box-shadow:0 5px 24px #28354a05}.mini{color:#8e9aab;font-size:10px;letter-spacing:.08em;text-transform:uppercase;font-weight:700}.list{display:grid;gap:7px;margin-top:15px}.item{width:100%;background:transparent;border:1px solid transparent;padding:13px;border-radius:9px;text-align:left;cursor:pointer;color:#3d4c63}.item.active{border-color:#f0c8ab;background:#fff5ed;color:#b95e26}.item span{display:block;font-weight:700}.item small{color:#8090a0;display:block;margin-top:6px}.btn{border:1px solid #d7dfea;border-radius:9px;background:white;padding:10px 14px;font-weight:700;color:#43556a;cursor:pointer;font-size:12px}.btn.primary{background:#e77731;border-color:#e77731;color:white}.btn:disabled{opacity:.4;cursor:not-allowed}.controls{display:flex;gap:8px;flex-wrap:wrap}.intro{display:grid;grid-template-columns:1fr auto;gap:12px;align-items:center}.intro h2{margin:8px 0;font-size:19px}.num{font-weight:800;color:#dc7831;font-size:23px}.steps{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin:14px 0 18px}.steps span{font-size:11px;background:#f0f3f8;border-radius:7px;padding:7px 10px;color:#67788c}.table-row{display:grid;grid-template-columns:26px 1fr 115px;align-items:center;gap:14px;border:1px solid #e2e8f0;border-radius:10px;padding:13px 15px;margin:10px 0}.table-row input{width:17px;height:17px;accent-color:#e77731}.table-row strong{display:block;font-size:13px}.table-row small{font-size:11px;color:#718096;line-height:1.8}.status{padding:6px 8px;text-align:center;border-radius:7px;background:#eaf7ef;color:#23704b;font-size:11px}.status.warn{background:#fff0e6;color:#af642c}.status.neutral{background:#f1f4f7;color:#68778b}.hint{border-top:1px solid #eef0f5;margin-top:19px;padding-top:18px;color:#68768b;font-size:12px;line-height:1.8}.log{min-height:38px;margin-top:14px;color:#286d4d;font-size:12px}.versionline{display:flex;gap:8px;margin:13px 0;flex-wrap:wrap}.versionline span{border:1px solid #e1e6ed;padding:9px 12px;border-radius:8px;color:#6b7b90;background:#fff}.versionline .current{background:#fff4eb;border-color:#edd0b8;color:#b95c2d}a{color:inherit}@media(max-width:750px){.columns{grid-template-columns:1fr}.intro{grid-template-columns:1fr}.top{padding:15px 18px}.table-row{grid-template-columns:20px 1fr;gap:8px}.status{grid-column:2;justify-self:start}.heading h1{font-size:24px}}</style></head><body>
|
||||
<div class="top"><div class="brand"><b>◈</b> SiteForge <span style="font-weight:400;color:#8590a0">/ Studio</span></div><div class="chip">V8.25 · 交互演示(模拟数据)</div></div>
|
||||
<main class="wrap"><div class="heading"><div><div class="mini">全站设计系统 · 组件库</div><h1>公共组件,集中维护</h1><p class="muted">更新一个组件后,先确认受影响的页面,再选择同步到草稿。已发布页面不会自动更改。</p></div><div class="controls"><button class="btn" id="versionBtn">查看历史版本</button><button class="btn primary" id="publishBtn">创建新版组件</button></div></div>
|
||||
<div class="columns"><aside class="card"><div class="mini">我的公共组件</div><div class="list" id="leftList"></div><p class="hint">关联插入:允许将来选择同步。<br>复制插入:独立编辑,不跟随组件版本。</p></aside><section class="card"><div class="intro"><div><div class="mini">当前选中组件</div><h2 id="currentTitle"></h2><p id="note" class="muted"></p></div><div><div class="num" id="currentVersion"></div><small class="muted">当前版本</small></div></div><div class="steps"><span>① 编辑组件</span><span>→</span><span>② 查看引用与冲突</span><span>→</span><span>③ 勾选页面同步草稿</span></div><div id="versionList" hidden></div><h3 style="font-size:15px;margin:18px 0">引用该组件的页面</h3><div id="pageRows"></div><div class="controls"><button id="selectBtn" class="btn">全选可同步页面</button><button id="syncBtn" class="btn primary">同步到选中的页面草稿</button></div><div class="log" role="status" id="log"></div><p class="hint">安全规则:本地修改和有冲突的页面无法被覆盖;同步后还需进入页面编辑器检查、保存并发布。<br>这是无需服务器的操作演示,不会修改真实网站。</p></section></div></main>
|
||||
<script>
|
||||
const data=[{title:'全站行动按钮',version:3,desc:'页尾联系方式 CTA',pages:[{name:'首页',path:'/',v:1,status:'ready'},{name:'产品中心',path:'/products/',v:2,status:'ready'},{name:'关于我们',path:'/about/',v:1,status:'modified'},{name:'新闻详情',path:'/news/example/',v:3,status:'current'}]},{title:'通用优势卡片',version:2,desc:'企业优势组件',pages:[{name:'首页',path:'/',v:1,status:'ready'},{name:'服务介绍',path:'/services/',v:1,status:'ready'}]},{title:'产品咨询区',version:1,desc:'询盘联系模块',pages:[{name:'产品详情',path:'/products/example/',v:1,status:'current'}]}];let active=0;
|
||||
function render(){const c=data[active];document.querySelector('#leftList').innerHTML=data.map((v,i)=>`<button class="item ${active===i?'active':''}" onclick="active=${i};render()"><span>${v.title}</span><small>版本 v${v.version} · ${v.pages.length} 个引用</small></button>`).join('');document.querySelector('#currentTitle').textContent=c.title;document.querySelector('#currentVersion').textContent='v'+c.version;document.querySelector('#note').textContent=c.desc;document.querySelector('#pageRows').innerHTML=c.pages.map((p,i)=>{let ready=p.status==='ready';let msg=ready?'可同步':p.status==='current'?'已是最新版':p.status==='modified'?'有本地修改':'已同步为草稿';return `<label class="table-row"><input type="checkbox" data-index="${i}" ${ready?'':'disabled'}><span><strong>${p.name}</strong><small>${p.path} · 当前 v${p.v} → 最新 v${c.version}</small></span><span class="status ${ready?'':p.status==='current'?'neutral':'warn'}">${msg}</span></label>`}).join('');document.querySelector('#versionList').hidden=true;document.querySelector('#log').textContent='';}
|
||||
document.querySelector('#selectBtn').onclick=()=>{document.querySelectorAll('#pageRows input:not(:disabled)').forEach(x=>x.checked=true)};
|
||||
document.querySelector('#syncBtn').onclick=()=>{let c=data[active];let indexes=[...document.querySelectorAll('#pageRows input:checked')].map(x=>Number(x.dataset.index));if(!indexes.length){document.querySelector('#log').textContent='请选择至少一个可同步的页面。';return;}if(!confirm(`只更新 ${indexes.length} 个页面的草稿,确认吗?`))return;for(const i of indexes){c.pages[i].v=c.version;c.pages[i].status='synced'}render();document.querySelector('#log').textContent=`✓ 已创建 ${indexes.length} 个新版页面草稿,已发布内容保持不变。`};
|
||||
document.querySelector('#versionBtn').onclick=()=>{let c=data[active],el=document.querySelector('#versionList');el.hidden=!el.hidden;el.innerHTML=`<div class="versionline">${Array.from({length:c.version},(_,i)=>`<span class="${i===c.version-1?'current':''}">历史快照 v${i+1}</span>`).join('')}</div>`};
|
||||
document.querySelector('#publishBtn').onclick=()=>{let c=data[active];if(!confirm('模拟创建组件新版本?不会改变任何已发布页面。'))return;c.version++;for(const p of c.pages)if(p.status==='current'||p.status==='synced')p.status='ready';render();document.querySelector('#log').textContent='已创建组件新版本。请检查引用后选择同步。'};
|
||||
render();
|
||||
</script></body></html>
|
||||
@@ -0,0 +1,703 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SiteForge V8.26 · 全站设计</title>
|
||||
<style>
|
||||
*{box-sizing:border-box}body{margin:0;background:#f4f6f9;font-family:system-ui,-apple-system,"Microsoft YaHei",sans-serif;color:#25364a}button,input,textarea{font:inherit}button{cursor:pointer}.ui-btn{padding:9px 14px;border:1px solid #cfd8e4;border-radius:9px;background:#fff;color:#233749;font-size:13px}.ui-btn.primary{background:#ea792e;border-color:#ea792e;color:#fff}.ui-btn:hover{filter:brightness(.97)}.ui-btn.ghost{background:white}.shell{max-width:1120px;margin:auto;padding:30px 18px}.top{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;margin-bottom:28px}.top strong{font-size:20px}.tag{font-size:12px;padding:8px 11px;background:#e6f2ed;color:#226a50;border-radius:20px}.intro{background:linear-gradient(110deg,#173c53,#1c6375);color:#fff;border-radius:16px;padding:26px 30px;margin-bottom:20px}.intro h1{margin:0 0 8px;font-size:28px}.intro p{margin:0;line-height:1.7;color:#e0eef3}.grid{display:grid;grid-template-columns:1.9fr .75fr;gap:20px}.panel{background:white;border-radius:15px;padding:24px;border:1px solid #e1e6ec;box-shadow:0 10px 30px #142e4309}.panel h2{margin:0 0 8px}.panel p{color:#657689;font-size:13px}.note{border-radius:10px;background:#fff8eb;padding:15px;font-size:13px;line-height:1.7}.sf-visual-nav{padding:15px;border:1px solid #e2e8f0;border-radius:12px;background:#fafcff}.sf-visual-nav-header{display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;margin-bottom:12px}.sf-visual-nav-header span,.sf-visual-nav-help{font-size:12px;color:#64748b}.sf-nav-item{padding:8px;margin-bottom:6px;border:1px solid #dde5ed;background:#fff;border-radius:10px;display:flex;align-items:center;flex-wrap:wrap;gap:6px}.sf-nav-item input{min-width:100px;flex:1 1 135px;padding:8px;border:1px solid #cbd5e1;border-radius:7px}.sf-nav-action{font-size:12px;padding:6px 9px}.sf-global-review{width:min(720px,95vw);max-height:85vh;overflow:auto;border:1px solid #dce5ed;border-radius:16px;padding:24px;box-shadow:0 18px 54px #132c4540}.sf-review-change{padding:12px;border:1px solid #dfe6ee;border-radius:10px;margin:9px 0;background:#f9fbfe;overflow-wrap:anywhere}.sf-review-change p{font-size:12px;color:#64748b;margin:7px 0;white-space:pre-wrap}.sf-review-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:16px}#siteDesignModal{position:static;display:block;width:100%!important;max-width:none!important;max-height:none!important;margin:0;padding:0!important;overflow:visible!important;box-shadow:none!important;border:none!important}#siteDesignModal::backdrop{display:none}#siteDesignForm input:not([type=checkbox]),#siteDesignForm textarea{padding:8px;border:1px solid #d1dbe5;border-radius:7px;max-width:100%}#siteDesignForm fieldset{min-width:0;padding:15px}#siteDesignForm label{font-size:13px}#siteDesignForm legend{font-weight:700}#siteDesignStatus{color:#1f866b}.sf-global-review::backdrop{background:#12263b8c}#toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);padding:11px 18px;background:#23435b;color:white;border-radius:8px;display:none}#toast.show{display:block}.mobile-tab-row input{max-width:100%}@media(max-width:850px){.grid{grid-template-columns:1fr}.shell{padding:15px}.intro{padding:20px}.sf-nav-item{margin-left:0!important}}
|
||||
</style></head><body><div class="shell"><header class="top"><strong>◈ SiteForge <span style="color:#e87a33">V8.26</span></strong><span class="tag">可操作演示 · 不连接正式网站</span></header><section class="intro"><h1>全站统一设计,更容易维护</h1><p>直观编辑菜单、页眉和页脚。变更先保存草稿,再查看差异并确认发布。历史版本可恢复成新草稿。</p></section><div class="grid"><main class="panel"> <dialog id="siteDesignModal" style="border:1px solid #e2e8f0;border-radius:14px;padding:22px;width:min(750px,96vw);max-height:88vh;overflow:auto;box-shadow:0 18px 56px #142a492f">
|
||||
<h2>全站设计 · 页眉、导航、页脚与响应式</h2>
|
||||
<p style="color:#64748b">先保存独立草稿;预览只显示在编辑器内。发布后,所有页面的下次静态构建才会使用新设计。</p>
|
||||
<div id="siteDesignStatus" role="status" style="font-weight:600;margin-bottom:12px">读取中...</div>
|
||||
<form id="siteDesignForm" method="dialog" style="display:grid;gap:12px">
|
||||
<fieldset style="display:grid;gap:9px;border:1px solid #e2e8f0;border-radius:8px"><legend>页眉与导航</legend>
|
||||
<label><input type="checkbox" name="show_announcement"> 显示顶部提示栏</label>
|
||||
<label>提示栏文字 <input name="announcement" maxlength="140" style="width:100%"></label>
|
||||
<label><input type="checkbox" name="show_search"> 显示站内搜索</label>
|
||||
<label><input type="checkbox" name="show_cta"> 显示联系按钮</label>
|
||||
<label>按钮文字 <input name="cta_text" maxlength="40"></label>
|
||||
<label>按钮路径 <input name="cta_href" placeholder="/contact/"></label>
|
||||
<section class="sf-visual-nav" aria-label="可视化全站导航管理">
|
||||
<div class="sf-visual-nav-header"><strong>网站菜单</strong><span>直接编辑文字和链接,支持三级菜单</span></div>
|
||||
<div id="siteNavRows" role="group" aria-label="导航菜单列表"></div>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.addSiteNav()">+ 添加一级菜单</button>
|
||||
<p class="sf-visual-nav-help">点击“添加子菜单”即可建立下拉菜单。使用 ↑ ↓ 排序;保存草稿前会校验路径。</p>
|
||||
<textarea name="nav" hidden aria-hidden="true"></textarea>
|
||||
</section>
|
||||
</fieldset>
|
||||
<fieldset style="display:grid;gap:9px;border:1px solid #e2e8f0;border-radius:8px"><legend>全站页脚</legend>
|
||||
<label>企业名称或页脚标题 <input name="footer_title" maxlength="120" style="width:100%"></label>
|
||||
<label>页脚简介 <textarea name="footer_description" rows="2" maxlength="650" style="display:block;width:100%"></textarea></label>
|
||||
<label>联系邮箱 <input name="footer_email" type="email" maxlength="150"></label>
|
||||
<label><input name="footer_show_nav" type="checkbox"> 页脚显示导航链接</label>
|
||||
</fieldset>
|
||||
<fieldset style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;border:1px solid #e2e8f0;border-radius:8px"><legend>不同设备布局</legend>
|
||||
<label>平板板块间距 <input name="tablet_gap" type="number" min="20" max="120"></label>
|
||||
<label>手机板块间距 <input name="mobile_gap" type="number" min="16" max="100"></label>
|
||||
<label>手机内容边距 <input name="mobile_container_padding" type="number" min="8" max="48"></label>
|
||||
<label><input name="mobile_hide_announcement" type="checkbox"> 手机隐藏提示栏</label>
|
||||
<label><input name="mobile_hide_footer_links" type="checkbox"> 手机隐藏页脚导航</label>
|
||||
</fieldset>
|
||||
<fieldset style="border:1px solid #e2e8f0;border-radius:10px;padding:15px;display:grid;gap:12px"><legend>手机底部快捷导航 Tabs(可选)</legend>
|
||||
<p style="margin:0;color:#64748b;font-size:13px">默认关闭。需要时开启后添加 1–5 个按钮。它们只显示在手机上,平板和电脑不显示。</p>
|
||||
<label><input name="mobile_tabs_enabled" type="checkbox" onchange="Studio.toggleMobileTabsForm()"> 开启手机底部 Tabs</label>
|
||||
<div id="mobileTabsEditor" hidden style="display:grid;gap:10px">
|
||||
<div id="mobileTabsRows" style="display:grid;gap:9px"></div>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.addMobileTab()">+ 添加一个 Tab(最多 5 个)</button>
|
||||
<small style="color:#64748b">标题建议不超过 4 个字;链接使用站内路径(如 /products/)。菜单按钮会打开右侧导航抽屉。</small>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px">
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.previewSiteDesign()">预览草稿</button>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.discardSiteDesign()">放弃全站草稿</button>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.saveSiteDesign()">保存全站草稿</button>
|
||||
<button class="ui-btn ghost" type="button" onclick="Studio.showSiteDesignHistory()">历史版本</button>
|
||||
<button class="ui-btn primary" type="button" onclick="Studio.publishSiteDesign()">审核差异并发布</button>
|
||||
<button class="ui-btn ghost" value="cancel">关闭</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="siteDesignReviewModal" class="sf-global-review" aria-labelledby="siteDesignReviewTitle">
|
||||
<h2 id="siteDesignReviewTitle">全站设计发布审核</h2>
|
||||
<p>本次变更将在下一次静态构建后影响本站所有页面,线上已发布网站不会立即改变。</p>
|
||||
<div id="siteDesignReviewBody" role="status" aria-live="polite"></div>
|
||||
<div class="sf-review-buttons"><button type="button" class="ui-btn ghost" onclick="document.querySelector('#siteDesignReviewModal').close()">返回编辑</button>
|
||||
<button type="button" class="ui-btn primary" onclick="Studio.confirmReviewedSiteDesign()">确认更新全站设计</button></div>
|
||||
</dialog>
|
||||
<dialog id="siteDesignHistoryModal" class="sf-global-review" aria-label="全站设计发布历史">
|
||||
<h2>全站设计历史</h2><p>恢复历史版本只会生成新草稿,不修改已上线的网站。</p>
|
||||
<div id="siteDesignHistoryBody" aria-live="polite"></div>
|
||||
<button type="button" class="ui-btn ghost" onclick="document.querySelector('#siteDesignHistoryModal').close()">关闭</button>
|
||||
</dialog>
|
||||
</main><aside class="panel"><h2>操作说明</h2><p>按照日常编辑流程操作,全部使用模拟数据:</p><ol style="line-height:2;color:#546578;font-size:13px"><li>编辑导航名称或站内路径</li><li>点击“+ 子菜单”添加下拉项</li><li>调整页眉、页脚及手机底部 Tabs</li><li>点击“保存全站草稿”</li><li>点击“审核差异并发布”查看影响范围</li><li>到“历史版本”恢复旧设置</li></ol><div class="note">本演示所有操作只在浏览器内模拟。正式系统发布设计后,还需要静态构建和部署才会更新线上网站。</div><h3>当前演示状态</h3><p id="demoStatus">未修改正式站点</p><button class="ui-btn" type="button" onclick="location.reload()">重置演示</button></aside></div><div id="toast" role="status"></div></div>
|
||||
<script>
|
||||
window.SiteForgeEditorHelpers={};
|
||||
let mockConfig={"header": {"announcement": "China Sourcing. Global Delivery.", "show_announcement": true, "show_search": true, "show_cta": true, "cta_text": "Contact Us", "cta_href": "/contact/"}, "footer": {"title": "HESU Auto", "description": "Automotive export and global delivery", "email": "info@example.test", "show_nav": true}, "nav": [{"title": "Home", "route": "/", "children": []}, {"title": "Vehicles", "route": "/products/", "children": [{"title": "SUV", "route": "/suv/", "children": []}, {"title": "Pickup Trucks", "route": "/pickup-truck/", "children": []}]}, {"title": "About", "route": "/about/", "children": []}], "mobile_tabs": {"enabled": true, "items": [{"title": "Home", "route": "/", "icon": "home", "action": "link"}, {"title": "Vehicles", "route": "/products/", "icon": "products", "action": "link"}, {"title": "Menu", "route": "/", "icon": "menu", "action": "menu"}]}, "tokens": {"font": "system", "heading_scale": 1.0, "radius": 12, "section_spacing": 72, "container_width": 1200, "button_style": "rounded", "header_style": "standard"}, "responsive": {"tablet_gap": 64, "mobile_gap": 40, "mobile_hide_announcement": true, "mobile_hide_footer_links": false, "mobile_container_padding": 20}}, mockPublished=structuredClone(mockConfig),mockRevision=0,mockHistory=[];
|
||||
const success=(data,code=200)=>new Response(JSON.stringify(data),{status:code,headers:{'Content-Type':'application/json'}});
|
||||
window.fetch=async (url,options={})=>{
|
||||
const p=String(url),method=(options.method||'GET').toUpperCase(),value=options.body?JSON.parse(options.body):{};
|
||||
if(p.endsWith('/site-design')){
|
||||
if(method==='PUT'){if(value.expected_revision!==mockRevision)return success({detail:'版本已改变,请刷新'},409);mockConfig=value.config;mockRevision++;document.querySelector('#demoStatus').textContent='草稿已保存(演示数据)';return success({revision:mockRevision});}
|
||||
if(method==='DELETE'){mockConfig=structuredClone(mockPublished);mockRevision=0;return success({discarded:true});}
|
||||
return success({config:mockConfig,revision:mockRevision,state:mockRevision?'draft':'published',conflict:false});
|
||||
}
|
||||
if(p.endsWith('/site-design/review')){
|
||||
const changes=[];for(const k of Object.keys(mockConfig))if(JSON.stringify(mockConfig[k])!==JSON.stringify(mockPublished[k]))changes.push({field:k,before:mockPublished[k],after:mockConfig[k]});
|
||||
return success({revision:mockRevision,changed_fields:changes,changed_sections:changes.map(c=>c.field),change_count:changes.length,plan_token:'demo-review-token'});
|
||||
}
|
||||
if(p.endsWith('/publish-reviewed')){
|
||||
if(value.plan_token!=='demo-review-token')return success({detail:'审核已过期'},409);
|
||||
mockHistory.unshift({id:'demo-'+Date.now(),revision:mockRevision,published_at:new Date().toLocaleString(),snapshot:structuredClone(mockConfig),old:structuredClone(mockPublished)});
|
||||
mockPublished=structuredClone(mockConfig);mockRevision=0;document.querySelector('#demoStatus').textContent='已模拟发布设计(仅本地演示)';return success({published:true});
|
||||
}
|
||||
if(p.endsWith('/site-design/publications'))return success(mockHistory);
|
||||
if(p.endsWith('/site-design/live-hash'))return success({live_hash:'0'.repeat(64)});
|
||||
if(p.includes('/site-design/publications/')&&p.endsWith('/restore')){const id=p.split('/').at(-2),item=mockHistory.find(x=>x.id===id);if(!item)return success({detail:'记录不存在'},404);mockConfig=structuredClone(value.snapshot==='before'?item.old:item.snapshot);mockRevision++;return success({restored_as_draft:true,revision:mockRevision});}
|
||||
if(p.includes('/site-design/publications/')){const id=p.split('/').at(-1),item=mockHistory.find(x=>x.id===id);return success(item?{id:item.id,changes:[{field:'header, nav, footer',before:item.old,after:item.snapshot}]}:{detail:'找不到记录'},item?200:404)}
|
||||
return success({});
|
||||
};
|
||||
</script><script>'use strict';
|
||||
const $=id=>document.querySelector(id);
|
||||
const html=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
const pathParts=location.pathname.split('/').filter(Boolean);
|
||||
const siteId=pathParts[1],initialPage=pathParts[2];
|
||||
let csrf='',site=null,pages=[],schema=null,reusables=[],pageId=initialPage,blocks=[],kind='standard',layoutMode='append',selected=-1,dirty=false,timer=null;
|
||||
const api=async(p,opt={})=>{if(!['GET','HEAD','OPTIONS'].includes((opt.method||'GET').toUpperCase()))opt.headers={...(opt.headers||{}),'X-SiteForge-CSRF':csrf};let r=await fetch(p,opt);if(r.status===401){location.assign('/login');throw Error('会话已过期')}let t=r.headers.get('content-type')||'';let body=t.includes('json')?await r.json():await r.text();if(!r.ok)throw Error(body.detail||body);return body};
|
||||
const req=(method,data)=>({method,headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
||||
const toast=(s,err=false)=>{const t=$('#toast');t.textContent=s;t.style.background=err?'#a23c44':'#1c3555';t.classList.add('show');setTimeout(()=>t.classList.remove('show'),3400)};
|
||||
const activePage=()=>pages.find(e=>e.id===pageId);
|
||||
const editorTools=window.SiteForgeEditorHelpers;
|
||||
let libraryOpen=false;
|
||||
let sharedPreview=null;
|
||||
let siteNavDraft=[],siteDesignReviewToken=null;
|
||||
let selectedChildId=null;
|
||||
let inspectorTab='content',blockSearch='',previewRequest=0,previewController=null,savePromise=null,lastContinuousEdit=0,insertAfterId=null;
|
||||
|
||||
let snapshots=[],snapshotCursor=-1,draggedChild=null,draftRevision=0,siteDesign=null,siteDesignRevision=0,siteDesignPreview=null;
|
||||
function recordSnapshot(){const current=JSON.stringify({blocks,kind,layoutMode});if(snapshotCursor>=0 && snapshots[snapshotCursor]===current)return;snapshots=snapshots.slice(0,snapshotCursor+1);snapshots.push(current);if(snapshots.length>75)snapshots.shift();snapshotCursor=snapshots.length-1}
|
||||
function restoreSnapshot(index){if(index<0||index>=snapshots.length)return;snapshotCursor=index;const state=JSON.parse(snapshots[index]);blocks=state.blocks;kind=state.kind;layoutMode=state.layoutMode;selected=Math.min(selected,blocks.length-1);selectedChildId=null;$('#pageTemplate').value=kind;$('#layoutMode').value=layoutMode;dirty=true;$('#saveStatus').textContent='未保存更改';$('#saveStatus').classList.add('dirty');Studio.updateHistoryButtons();Studio.draw();Studio.refreshPreview()}
|
||||
const Studio={
|
||||
highlightCanvas(){const block=blocks[selected];const frame=$('#sitePreview');if(block&&frame?.contentWindow)frame.contentWindow.postMessage({source:'siteforge-studio-host',action:'highlight',blockId:block.id,childId:selectedChildId},'*')},
|
||||
showInsertHint(){const box=$('#sfInsertHint');if(!box)return;const item=blocks.find(x=>x.id===insertAfterId);box.hidden=!item;box.textContent=item?'正在「'+(item.title||schema?.blocks[item.type]?.name||'当前区块')+'」下方插入:选择一种内容。':''},
|
||||
beginAddAfter(blockId){if(editorTools.findTopIndex(blocks,blockId)<0)return;insertAfterId=blockId;Studio.showInsertHint();Studio.toggleLibrary(true);document.body.classList.add('show-structure');document.body.classList.remove('show-inspector');$('#sfAddBlock').scrollIntoView({behavior:'smooth',block:'nearest'})},
|
||||
canvasCommand(command){
|
||||
if(String(command?.action||'').startsWith('child-')){Studio.childCanvasCommand(command);return;}
|
||||
if(command?.action==='resize-columns'){
|
||||
const nestedLayout=editorTools.findLayout(blocks,command.blockId);
|
||||
if(!nestedLayout)return;
|
||||
const widths=editorTools.adjacentWidths(nestedLayout.layout,command.widths);
|
||||
if(!widths)return;
|
||||
if(nestedLayout.layout.columns.every((c,k)=>Math.abs(c.width-widths[k])<.001))return;
|
||||
nestedLayout.layout.columns.forEach((c,k)=>c.width=widths[k]);
|
||||
selected=nestedLayout.rootIndex;
|
||||
selectedChildId=nestedLayout.depth?command.blockId:null;
|
||||
Studio.modified();Studio.draw();return;
|
||||
}
|
||||
if(!editorTools.safeCanvasCommand(command,blocks,schema.blocks))return;
|
||||
const i=editorTools.findTopIndex(blocks,command.blockId),block=blocks[i];
|
||||
if(command.action==='select'||command.action==='edit'){Studio.select(i);if(command.action==='edit'){inspectorTab='content';Studio.applyInspectorTab();}return;}
|
||||
if(command.action==='up'||command.action==='down'){Studio.moveBlock(i,command.action==='up'?-1:1);return;}
|
||||
if(command.action==='add-after'){Studio.beginAddAfter(command.blockId);return;}
|
||||
if(command.action==='duplicate'){Studio.duplicate(i);return;}
|
||||
if(command.action==='delete'){Studio.remove(i);return;}
|
||||
if(command.action==='reorder'){
|
||||
const to=editorTools.findTopIndex(blocks,command.targetId);
|
||||
const next=editorTools.reorderById(blocks,command.blockId,command.targetId);
|
||||
if(!next)return;blocks=next;selected=to;Studio.modified();Studio.draw();return;
|
||||
}
|
||||
if(command.action==='resize-columns'){
|
||||
const widths=editorTools.adjacentWidths(block,command.widths);
|
||||
if(!widths)return;
|
||||
if(block.columns.every((c,k)=>Math.abs(c.width-widths[k])<.001))return;
|
||||
block.columns.forEach((col,j)=>col.width=widths[j]);selected=i;
|
||||
Studio.modified();Studio.inspector();return;
|
||||
}
|
||||
if(command.action==='inline-text'){
|
||||
if(block[command.field]===command.value)return;
|
||||
block[command.field]=command.value;selected=i;Studio.modified();Studio.draw();
|
||||
toast('文字已修改,记得保存草稿');
|
||||
}
|
||||
},
|
||||
childCanvasCommand(command){
|
||||
if(!editorTools.safeNestedCanvasCommand(command,blocks,schema.blocks))return;
|
||||
const found=editorTools.findNestedChild(blocks,command.blockId,command.childId);
|
||||
if(!found)return;
|
||||
const {parentIndex,parent,columnIndex,childIndex,child}=found;
|
||||
if(command.action==='child-select'||command.action==='child-edit'){
|
||||
selected=parentIndex;selectedChildId=child.id;inspectorTab='content';Studio.draw();Studio.highlightCanvas();
|
||||
const card=document.querySelector('#sf-nested-active');card?.scrollIntoView({block:'nearest'});
|
||||
if(window.matchMedia('(max-width:1060px)').matches)document.body.classList.add('show-inspector');
|
||||
return;
|
||||
}
|
||||
if(command.action==='child-inline-text'){
|
||||
if(child[command.field]===command.value)return;
|
||||
child[command.field]=command.value;selected=parentIndex;selectedChildId=child.id;
|
||||
Studio.modified();Studio.draw();return;
|
||||
}
|
||||
if(command.action==='child-delete'){
|
||||
if(!confirm('确定删除这一列中的组件?'))return;
|
||||
parent.columns[columnIndex].blocks.splice(childIndex,1);selectedChildId=null;
|
||||
}else if(command.action==='child-duplicate'){
|
||||
const copy=editorTools.duplicate(child,()=>crypto.randomUUID().replace(/-/g,'').slice(0,10));
|
||||
parent.columns[columnIndex].blocks.splice(childIndex+1,0,copy);selectedChildId=copy.id;
|
||||
}else{
|
||||
const next=editorTools.moveNested(parent,columnIndex,childIndex,command.action==='child-up'?-1:1);
|
||||
if(!next)return;parent.columns[columnIndex].blocks=next;selectedChildId=child.id;
|
||||
}
|
||||
selected=parentIndex;Studio.modified();Studio.draw();
|
||||
},
|
||||
setLayoutGap(value){const block=blocks[selected];if(!block||block.type!=='layout')return;block.gap=Math.max(0,Math.min(64,Math.round(Number(value)||0)));Studio.modified();Studio.inspector()},
|
||||
setLayoutPreset(preset){
|
||||
const block=blocks[selected];const widths=editorTools.layoutPreset(block,preset);
|
||||
if(!widths)return toast('当前列数不支持这种预设',true);
|
||||
block.columns.forEach((col,i)=>col.width=widths[i]);Studio.modified();Studio.inspector();
|
||||
},
|
||||
// One inspector for both levels, with a readable path back to the parent.
|
||||
childQuickEditor(layout){
|
||||
if(layout.type!=='layout')return '';
|
||||
const cards=[];
|
||||
const walk=(container,trail=[],depth=0)=>{
|
||||
if(depth>2)return;
|
||||
container.columns.forEach((column,ci)=>(column.blocks||[]).forEach((child,ri)=>{
|
||||
const active=selectedChildId===child.id;
|
||||
const label=schema.blocks[child.type]?.name||child.type;
|
||||
const breadcrumb=[...trail,`第${ci+1}列`].join(' › ');
|
||||
cards.push(`<div class="sf-nested-card ${active?'active':''}" style="margin-left:${Math.min(depth,1)*12}px" ${active?'id="sf-nested-active"':''}><div class="sf-nested-heading"><button type="button" onclick="Studio.selectChild('${html(child.id)}')">${active?'✓ ':''}${html(breadcrumb)} · ${html(label)}</button><span><button type="button" title="上移" ${ri===0?'disabled':''} onclick="Studio.childCanvasCommand({action:'child-up',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">↑</button><button type="button" title="下移" ${ri===column.blocks.length-1?'disabled':''} onclick="Studio.childCanvasCommand({action:'child-down',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">↓</button></span></div>`);
|
||||
if(active){
|
||||
if(child.type==='layout'){
|
||||
cards.push(`<div class="sf-nested-field">内层布局 · ${child.columns.length} 列</div><div class="sf-nested-actions"><button type="button" onclick="Studio.setNestedLayoutPreset('${html(child.id)}','equal')">均分列宽</button>${child.columns.length===2?`<button type="button" onclick="Studio.setNestedLayoutPreset('${html(child.id)}','left-wide')">左宽右窄</button><button type="button" onclick="Studio.setNestedLayoutPreset('${html(child.id)}','right-wide')">左窄右宽</button>`:''}</div><label class="sf-nested-field">列间距(像素)<input type="number" min="0" max="64" value="${Number(child.gap)||0}" onchange="Studio.setNestedLayoutGap('${html(child.id)}',this.value)"></label><div class="sf-nested-actions"><label>手机列数 <select onchange="Studio.setNestedDeviceColumns('${html(child.id)}','mobile',this.value)"><option value="1" ${child.mobile_columns===1?'selected':''}>1 列</option><option value="2" ${child.mobile_columns===2?'selected':''}>2 列</option></select></label><label>平板列数 <select onchange="Studio.setNestedDeviceColumns('${html(child.id)}','tablet',this.value)">${[1,2,3].map(n=>`<option value="${n}" ${child.tablet_columns===n?'selected':''}>${n} 列</option>`).join('')}</select></label></div>`);
|
||||
}else{
|
||||
const fields=(schema.blocks[child.type]?.fields||[]).filter(k=>['title','subtitle','description','image','button','href'].includes(k));
|
||||
fields.forEach(key=>cards.push(`<label class="sf-nested-field">${html(({title:'标题',subtitle:'副标题',description:'描述',image:'图片地址',button:'按钮文字',href:'跳转链接'})[key])}<input type="text" value="${html(child[key]??'')}" oninput="Studio.childFieldById('${html(child.id)}','${key}',this.value)"></label>`));
|
||||
}
|
||||
if(child.type==='layout'){
|
||||
child.columns.forEach((col,index)=>cards.push(`<div class="sf-nested-actions" role="group" aria-label="内层第${index+1}列添加内容"><span>第 ${index+1} 列</span>${['richtext','image_text','features','cta'].map(t=>`<button type="button" onclick="Studio.addNestedChild('${html(child.id)}',${index},'${t}')">+ ${html(schema.blocks[t]?.name||t)}</button>`).join('')}</div>`));
|
||||
}
|
||||
cards.push(`<button type="button" class="ui-btn ghost" onclick="Studio.saveReusable()">☆ 保存当前组件到组件库</button>`);
|
||||
if(schema.blocks[child.type]?.fields?.includes('image')){
|
||||
cards.push(`<button type="button" class="ui-btn ghost" onclick="Studio.chooseMedia()">🖼 从素材库更换选中图片</button>`);
|
||||
}
|
||||
cards.push(`<div class="sf-nested-actions"><button type="button" onclick="Studio.childCanvasCommand({action:'child-duplicate',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">复制组件</button><button type="button" onclick="Studio.childCanvasCommand({action:'child-delete',blockId:'${html(container.id)}',childId:'${html(child.id)}'})">删除组件</button></div>`);
|
||||
}
|
||||
cards.push('</div>');
|
||||
if(child.type==='layout')walk(child,[...trail,`第${ci+1}列 · 内层布局`],depth+1);
|
||||
}));
|
||||
};
|
||||
walk(layout);
|
||||
return `<div class="sf-nested-edit"><div class="subheading">画布组件 · 选择任意一层内容直接编辑</div>${cards.join('')||'<p class="prop-tip">当前没有子组件,请添加内容。</p>'}</div>`;
|
||||
},
|
||||
setNestedLayoutPreset(layoutId,preset){
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
const widths=editorTools.layoutPreset(found.layout,preset);
|
||||
if(!widths)return toast('当前内层列数不支持此预设',true);
|
||||
found.layout.columns.forEach((c,k)=>c.width=widths[k]);selectedChildId=layoutId;
|
||||
Studio.modified();Studio.draw();
|
||||
},
|
||||
setNestedLayoutGap(layoutId,value){
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
found.layout.gap=Math.round(Math.max(0,Math.min(64,Number(value)||0)));
|
||||
selectedChildId=layoutId;Studio.modified();Studio.draw();
|
||||
},
|
||||
addNestedChild(layoutId,columnIndex,type){
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
// A second-level layout cannot contain another layout. Use only the safe
|
||||
// declarative blocks offered in the UI and validate limits before mutation.
|
||||
if(!['richtext','image_text','features','cta'].includes(type)||
|
||||
!Number.isInteger(columnIndex)||columnIndex<0||columnIndex>=found.layout.columns.length)return;
|
||||
const column=found.layout.columns[columnIndex];
|
||||
if(column.blocks.length>=60)return toast('这一列的组件数量已达到限制',true);
|
||||
const item=structuredClone(schema.blocks[type].defaults);
|
||||
item.id=crypto.randomUUID().replace(/-/g,'').slice(0,10);
|
||||
item.type=type;item.visible=true;
|
||||
column.blocks.push(item);selectedChildId=item.id;
|
||||
Studio.modified();Studio.draw();
|
||||
toast('已添加内层组件,请在右侧修改内容');
|
||||
},
|
||||
setNestedDeviceColumns(layoutId,device,value){
|
||||
if(!['mobile','tablet'].includes(device))return;
|
||||
const found=editorTools.findLayout(blocks,layoutId);
|
||||
if(!found||found.rootIndex!==selected||found.depth!==1)return;
|
||||
const max=device==='mobile'?2:3;
|
||||
const count=Math.max(1,Math.min(max,Math.round(Number(value)||1)));
|
||||
found.layout[device+'_columns']=count;
|
||||
found.layout.columns.forEach(c=>c[device+'_span']=Math.min(count,c[device+'_span']||1));
|
||||
selectedChildId=layoutId;Studio.modified();Studio.draw();
|
||||
},
|
||||
selectChild(childId){const root=blocks[selected];const found=editorTools.findChildInRoot(blocks,root?.id,childId);if(!found)return;Studio.childCanvasCommand({action:'child-select',blockId:found.parent.id,childId})},
|
||||
childFieldById(childId,key,value){
|
||||
const parent=blocks[selected];const found=editorTools.findChildInRoot(blocks,parent?.id,childId);
|
||||
if(!found||!schema.blocks[found.child.type]?.fields?.includes(key)||!['title','subtitle','description','image','button','href'].includes(key))return;
|
||||
found.child[key]=String(value).slice(0,1500);selectedChildId=childId;Studio.modified(true);
|
||||
},
|
||||
async init(){try{csrf=(await api('/api/auth/me')).csrf;site=await api(`/api/sites/${siteId}`);pages=await api(`/api/sites/${siteId}/entries`);schema=await api('/api/editor/blocks');$('#siteName').textContent=site.name;const select=$('#pageSelect');select.innerHTML=pages.map(e=>`<option value="${html(e.id)}">${html(e.title)} (${html(e.route)})</option>`).join('');for(let [k,v] of Object.entries(schema.page_templates))$('#pageTemplate').innerHTML+=`<option value="${k}">${html(v.name)}</option>`;Studio.renderPalette();await Studio.loadReusables();await Studio.choosePage(pageId||pages[0]?.id)}catch(e){toast('初始化失败:'+e.message,true);console.error(e)}},
|
||||
async choosePage(id){if(savePromise){toast('保存仍在进行,请稍后切换页面',true);$('#pageSelect').value=pageId;return}if(dirty&&!confirm('当前页面尚未保存,确定切换?')){$('#pageSelect').value=pageId;return}pageId=id;selectedChildId=null;insertAfterId=null;Studio.showInsertHint();$('#pageSelect').value=id;const data=await api(`/api/sites/${siteId}/pages/${id}/studio-draft`);draftRevision=data.revision;blocks=data.blocks.map(b=>structuredClone(b));kind=data.page_template;layoutMode=data.layout_mode||'append';$('#layoutMode').value=layoutMode;selected=blocks.length?0:-1;$('#pageTemplate').value=kind;$('#pageName').textContent=data.entry.title;$('#routeDisplay').textContent=data.entry.route;dirty=false;snapshots=[];snapshotCursor=-1;lastContinuousEdit=0;blockSearch='';$('#blockSearch').value='';recordSnapshot();Studio.updateHistoryButtons();$('#saveStatus').textContent=draftRevision?'草稿 v'+draftRevision:'未修改';$('#saveStatus').classList.remove('dirty');Studio.draw();Studio.refreshPreview()},
|
||||
async loadReusables(){try{reusables=await api(`/api/sites/${siteId}/components`);$('#reusablePalette').innerHTML=reusables.map(x=>`<div class="sf-shared-palette-row"><strong>${html(x.name)} <small>v${x.version}</small></strong><div><button type="button" onclick="Studio.insertReusable('${html(x.id)}')" title="复制后独立编辑">复制</button><button type="button" onclick="Studio.insertReusable('${html(x.id)}',true)" title="可以在检查后选择性同步的公共组件">关联插入</button></div></div>`).join('')||'<span style="font-size:12px;opacity:.55;padding:10px">暂无保存的组件</span>'}catch(e){console.warn(e)}},
|
||||
async saveReusable(){
|
||||
const root=blocks[selected];
|
||||
const found=selectedChildId?editorTools.findChildInRoot(blocks,root?.id,selectedChildId):null;
|
||||
if(selectedChildId&&!found)return toast('当前子组件已失效,请重新选中后保存',true);
|
||||
const block=found?.child||root;
|
||||
if(!block)return toast('请先选择需要保存的组件',true);
|
||||
const name=prompt('为此组件命名',block.title||schema.blocks[block.type]?.name);
|
||||
if(!name?.trim())return;
|
||||
try{await api(`/api/sites/${siteId}/components`,req('POST',{name,block}));
|
||||
await Studio.loadReusables();toast('已保存独立组件副本,其他页面不会自动更新');
|
||||
}catch(e){toast('保存组件失败:'+e.message,true)}
|
||||
},
|
||||
insertReusable(id,linked=false){
|
||||
const saved=reusables.find(x=>x.id===id);if(!saved)return;
|
||||
const copy=editorTools.duplicate(saved.block,()=>crypto.randomUUID().replace(/-/g,'').slice(0,10));
|
||||
if(linked)copy.shared_ref={id:saved.id,version:saved.version,hash:saved.snapshot_hash};
|
||||
const root=blocks[selected];
|
||||
const found=selectedChildId?editorTools.findChildInRoot(blocks,root?.id,selectedChildId):null;
|
||||
if(selectedChildId&&!found)return toast('当前子组件已失效,请重新选中后插入',true);
|
||||
// A nested reusable must never introduce a third layout container level.
|
||||
// Layout templates remain insertable as top-level blocks.
|
||||
if(found && copy.type!=='layout'){
|
||||
const parent=found.child.type==='layout'?found.child:found.parent;
|
||||
const column=found.child.type==='layout'?parent.columns[0]:parent.columns[found.columnIndex];
|
||||
if(!column||column.blocks.length>=60)return toast('此列已达到组件数量限制',true);
|
||||
const after=found.child.type==='layout'?column.blocks.length:found.childIndex+1;
|
||||
column.blocks.splice(after,0,copy);
|
||||
selected=found.parentIndex;selectedChildId=copy.id;
|
||||
}else{
|
||||
const foundIndex=insertAfterId?editorTools.findTopIndex(blocks,insertAfterId):-1;
|
||||
const at=foundIndex>=0?foundIndex+1:blocks.length;
|
||||
if(blocks.length>=60)return toast('页面顶层最多60个组件',true);
|
||||
blocks.splice(at,0,copy);selected=at;selectedChildId=null;
|
||||
}
|
||||
insertAfterId=null;Studio.showInsertHint();Studio.modified();Studio.draw();Studio.toggleLibrary(false);
|
||||
toast(linked?'已关联公共组件:修改源组件后仍需要审核并选择同步':'已插入独立组件副本;不会自动跟随更新');
|
||||
},
|
||||
detachShared(){
|
||||
const item=selectedChildId?editorTools.findChildInRoot(blocks,blocks[selected]?.id,selectedChildId)?.child:blocks[selected];
|
||||
if(!item?.shared_ref)return toast('当前区块未关联公共组件',true);
|
||||
if(!confirm('解除关联后,本区块仍保留当前内容,但以后不会参与公共组件同步。确定吗?'))return;
|
||||
delete item.shared_ref;Studio.modified();Studio.draw();toast('已解除关联。请保存草稿');
|
||||
},
|
||||
async showSharedComponents(){
|
||||
if(!reusables.length)await Studio.loadReusables();
|
||||
const dlg=$('#sharedComponentModal');if(!dlg.open)dlg.showModal();sharedPreview=null;
|
||||
$('#sharedComponentBody').innerHTML=reusables.map(x=>`<article class="sf-shared-card"><div><strong>${html(x.name)}</strong><small>当前版本 v${x.version} · 不会自动更新页面</small></div><div class="sf-shared-actions"><button type="button" onclick="Studio.sharedUsage('${html(x.id)}')">查看引用与同步</button><button type="button" onclick="Studio.sharedVersions('${html(x.id)}')">版本记录</button><button type="button" onclick="Studio.sharedReplace('${html(x.id)}')">用当前选中区块创建新版</button></div></article>`).join('')||'<p>暂无公共组件。先在页面里选择一个区块,再通过左侧「保存当前组件」创建。</p>';
|
||||
},
|
||||
async sharedVersions(id){
|
||||
try{const r=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/versions`);
|
||||
$('#sharedComponentDetail').innerHTML='<h3>组件历史版本</h3><p>恢复历史不会覆盖已有版本,而是创建新的源组件版本。</p>'+r.items.map(x=>`<div class="sf-shared-history"><span>v${x.version} · ${html(x.created_at)} · SHA ${html(x.snapshot_hash.slice(0,12))}</span><button type="button" class="btn ui-btn ghost" onclick="Studio.sharedRestore('${html(id)}',${x.version})">恢复为新版本</button></div>`).join('');
|
||||
}catch(e){toast(e.message,true)}
|
||||
},
|
||||
async sharedRestore(id,version){
|
||||
const current=reusables.find(x=>x.id===id);
|
||||
if(!current)return toast('组件信息已过期,请重新加载',true);
|
||||
if(!confirm(`将历史 v${version} 的内容恢复为全新的组件版本?所有网站页面仍保持不变。`))return;
|
||||
try{const snapshot=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/versions/${version}`);
|
||||
await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}`,req('PUT',{
|
||||
name:current.name,expected_version:current.version,block:snapshot.block}));
|
||||
await Studio.loadReusables();await Studio.showSharedComponents();await Studio.sharedVersions(id);
|
||||
toast('历史组件已恢复为新的源版本,请检查页面后再同步');
|
||||
}catch(e){toast('恢复失败:'+e.message,true)}
|
||||
},
|
||||
async sharedReplace(id){
|
||||
const chosen=selectedChildId?editorTools.findChildInRoot(blocks,blocks[selected]?.id,selectedChildId)?.child:blocks[selected];
|
||||
const current=reusables.find(x=>x.id===id);if(!chosen||!current)return toast('请先选择页面中的区块',true);
|
||||
if(!confirm(`将当前选中的区块保存为「${current.name}」的新版本?已发布页面不会变化。`))return;
|
||||
try{await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}`,req('PUT',{
|
||||
name:current.name,expected_version:current.version,block:chosen}));
|
||||
await Studio.loadReusables();await Studio.showSharedComponents();toast('新版本已保存。请检查引用页面后选择性同步');
|
||||
}catch(e){toast(e.message,true)}
|
||||
},
|
||||
async sharedUsage(id){
|
||||
try{sharedPreview=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/usage`);
|
||||
const preview=sharedPreview;
|
||||
$('#sharedComponentDetail').innerHTML=`<h3>${html(preview.component.name)} · 同步前检查</h3><p>只同步勾选的页面,并新建草稿;已发布网站不会立即变动。</p>`+
|
||||
(preview.references.length?preview.references.map(x=>`<label class="sf-shared-usage"><input type="checkbox" class="sf-shared-select" value="${html(x.entry_id)}" ${x.eligible?'':'disabled'}><span><strong>${html(x.title)}</strong><small>${html(x.route)} · v${html(x.current_version)} → v${html(preview.component.version)} ${x.reason?' · '+html(x.reason):''}</small></span></label>`).join(''):'<p>尚无关联页面。插入时请选择「关联插入」。</p>')+
|
||||
'<button type="button" class="ui-btn primary" onclick="Studio.sharedApply()">确认并同步勾选的页面草稿</button>';
|
||||
}catch(e){toast('读取引用失败:'+e.message,true)}
|
||||
},
|
||||
async sharedApply(){
|
||||
if(!sharedPreview)return;
|
||||
if(dirty)return toast('请先保存当前页面修改,再进行组件同步',true);
|
||||
const ids=[...new Set([...document.querySelectorAll('.sf-shared-select:checked')].map(x=>x.value))];
|
||||
if(!ids.length)return toast('请先选择可以同步的页面',true);
|
||||
if(!confirm(`确认更新 ${ids.length} 个页面的设计草稿?已发布页面不变,稍后需要逐页审核发布。`))return;
|
||||
try{const id=sharedPreview.component.id;
|
||||
const resp=await api(`/api/sites/${siteId}/components/${encodeURIComponent(id)}/sync`,req('POST',{plan_token:sharedPreview.plan_token,entry_ids:ids}));
|
||||
$('#sharedComponentModal').close();sharedPreview=null;
|
||||
if(ids.includes(pageId))await Studio.choosePage(pageId);
|
||||
toast(`已为 ${resp.updated.length} 个页面保存新草稿;请分别检查并发布`);
|
||||
}catch(e){toast('同步取消:'+e.message,true);await Studio.sharedUsage(sharedPreview.component.id)}
|
||||
},
|
||||
renderPalette(){let p=$('#palette');p.innerHTML=Object.entries(schema.blocks).map(([key,b])=>`<button onclick="Studio.add('${key}')"><span>${html(b.icon)}</span>${html(b.name)}</button>`).join('')},
|
||||
toggleLibrary(force){libraryOpen=typeof force==='boolean'?force:!libraryOpen;const el=$('#sfLibraryPanel'),btn=$('#sfAddBlock');if(el)el.hidden=!libraryOpen;if(btn){btn.setAttribute('aria-expanded',String(libraryOpen));btn.textContent=libraryOpen?'× 收起区块库':'+ 添加内容区块'};Studio.updateGuidance()},
|
||||
showHelp(){const dlg=$('#sfGuideDialog');if(dlg&&!dlg.open)dlg.showModal()},
|
||||
updateGuidance(){const one=$('#sfStep1'),two=$('#sfStep2'),three=$('#sfStep3');if(!one||!two||!three)return;one.classList.toggle('active',selected<0||libraryOpen);two.classList.toggle('active',selected>=0&&!dirty&&!libraryOpen);three.classList.toggle('active',dirty);},
|
||||
draw(){
|
||||
const list=$('#blockList');$('#blockCount').textContent=blocks.length+' 区块';
|
||||
const found=editorTools.filterBlocks(blocks,blockSearch,schema.blocks);
|
||||
list.innerHTML=found.map(({block:b,index:i})=>`<div class="block-row ${selected===i?'selected':''} ${b.visible===false?'is-hidden':''}" role="listitem" tabindex="0" draggable="true" data-index="${i}" ondragstart="Studio.drag(event,${i})" ondragend="this.classList.remove('dragging')" ondragover="event.preventDefault()" ondrop="Studio.drop(event,${i})" onkeydown="Studio.blockKeydown(event,${i})" onclick="Studio.select(${i})"><div class="drag-handle" title="拖动排序" aria-hidden="true">⠿</div><div class="block-icon">${html(schema.blocks[b.type]?.icon||'▣')}</div><div class="block-copy"><strong>${html(schema.blocks[b.type]?.name||b.type)}</strong><small>${html(b.title||b.subtitle||b.type)}</small></div><div class="row-actions"><button type="button" aria-label="向上移动区块" title="上移" ${i===0?'disabled':''} onclick="event.stopPropagation();Studio.moveBlock(${i},-1)">↑</button><button type="button" aria-label="向下移动区块" title="下移" ${i===blocks.length-1?'disabled':''} onclick="event.stopPropagation();Studio.moveBlock(${i},1)">↓</button><button type="button" title="${b.visible===false?'显示':'隐藏'}" aria-label="切换区块可见性" onclick="event.stopPropagation();Studio.toggleVisible(${i})">${b.visible===false?'◌':'◉'}</button><button type="button" title="复制" aria-label="复制区块" onclick="event.stopPropagation();Studio.duplicate(${i})">⧉</button><button type="button" title="删除" aria-label="删除区块" onclick="event.stopPropagation();Studio.remove(${i})">×</button></div></div>`).join('')||`<div class="no-selection">${blockSearch?'没有找到匹配区块,试试清除搜索。':'当前没有区块,点击下方按钮添加。'}</div>`;
|
||||
Studio.inspector();Studio.updateGuidance();
|
||||
},
|
||||
filter(value){blockSearch=String(value||'').slice(0,100);Studio.draw()},
|
||||
clearFilter(){blockSearch='';$('#blockSearch').value='';Studio.draw();$('#blockSearch').focus()},
|
||||
moveBlock(i,offset){const target=i+offset;const next=editorTools.reorder(blocks,i,target);if(!next||target===i)return;blocks=next;selected=target;Studio.modified();Studio.draw();},
|
||||
toggleVisible(i){if(!blocks[i])return;blocks[i].visible=blocks[i].visible===false;Studio.modified();Studio.draw()},
|
||||
blockKeydown(event,i){if(event.target!==event.currentTarget)return;if(event.key==='Enter'||event.key===' '){event.preventDefault();Studio.select(i)}else if(event.altKey && (event.key==='ArrowUp'||event.key==='ArrowDown')){event.preventDefault();Studio.moveBlock(i,event.key==='ArrowUp'?-1:1)}},
|
||||
changeInspectorTab(tab){if(!['content','style','responsive'].includes(tab))return;inspectorTab=tab;Studio.applyInspectorTab()},
|
||||
applyInspectorTab(){for(const key of ['content','style','responsive']){const active=key===inspectorTab;const btn=$('#tab'+key.charAt(0).toUpperCase()+key.slice(1));btn?.setAttribute('aria-selected',String(active));const pane=$('[data-inspector-section="'+key+'"]');if(pane)pane.hidden=!active}},
|
||||
togglePane(which){const type=which==='left'?'show-structure':'show-inspector';document.body.classList.toggle(type);if(type==='show-structure')document.body.classList.remove('show-inspector');else document.body.classList.remove('show-structure');},
|
||||
updateHistoryButtons(){const state=editorTools.historyState(snapshots,snapshotCursor);$('#studioUndo').disabled=!state.canUndo;$('#studioRedo').disabled=!state.canRedo},
|
||||
|
||||
select(i){selected=i;selectedChildId=null;Studio.draw();if(window.matchMedia('(max-width:950px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}},
|
||||
add(type){let data=structuredClone(schema.blocks[type].defaults);data.id=crypto.randomUUID().replace(/-/g,'').slice(0,10);data.type=type;data.visible=true;const found=insertAfterId?editorTools.findTopIndex(blocks,insertAfterId):-1;const at=found>=0?found+1:blocks.length;blocks.splice(at,0,data);selected=at;insertAfterId=null;Studio.showInsertHint();Studio.modified();Studio.draw();Studio.toggleLibrary(false);if(window.matchMedia('(max-width:950px)').matches){document.body.classList.add('show-inspector');document.body.classList.remove('show-structure')}},
|
||||
duplicate(i){const b=editorTools.duplicate(blocks[i],()=>crypto.randomUUID().replace(/-/g,'').slice(0,10));blocks.splice(i+1,0,b);selected=i+1;Studio.modified();Studio.draw()},
|
||||
remove(i){if(!confirm('确定删除这个区块?'))return;blocks.splice(i,1);selected=Math.min(i,blocks.length-1);Studio.modified();Studio.draw()},
|
||||
drag(e,i){e.dataTransfer.setData('text/plain',String(i));e.dataTransfer.effectAllowed='move';e.currentTarget.classList.add('dragging')},
|
||||
drop(e,i){e.preventDefault();let src=Number(e.dataTransfer.getData('text/plain'));if(!Number.isInteger(src)||src<0||src>=blocks.length||src===i)return;const moved=editorTools.reorder(blocks,src,i);if(!moved)return;blocks=moved;selected=i;Studio.modified();Studio.draw()},
|
||||
field(key,value){const b=blocks[selected];if(!b)return;b[key]=value;Studio.modified();Studio.draw()},
|
||||
fieldQuiet(key,value){const b=blocks[selected];if(!b)return;b[key]=value;Studio.modified(true);const row=$('#blockList').querySelector(`[data-index="${selected}"]`);if(row)row.querySelector('small').textContent=b.title||b.subtitle||b.type},
|
||||
arrayEditor(field,value,type){
|
||||
const rows=Array.isArray(value)?value:[];
|
||||
const labels={title:'标题',text:'文字介绍',value:'数字',label:'名称',question:'问题',answer:'回答',url:'图片地址',alt:'图片说明',image:'图片地址',caption:'图片说明',name:'名称',price:'价格',features:'方案包含的服务',href:'按钮跳转路径'};
|
||||
const spec=schema.blocks[type];const sample=(spec?.defaults?.[field]||[])[0]||{url:'',alt:''};
|
||||
const keys=Object.keys(sample);
|
||||
return `<div class="sf-array-editor"><p class="sf-array-hint">无需填写 JSON:按顺序编辑每一项,修改会自动显示在预览中。</p>`+
|
||||
rows.map((row,i)=>`<div class="sf-array-card"><div class="sf-array-card-head"><b>第 ${i+1} 项</b><div><button type="button" title="上移" ${i===0?'disabled':''} onclick="Studio.moveItem('${field}',${i},-1)">↑</button><button type="button" title="下移" ${i===rows.length-1?'disabled':''} onclick="Studio.moveItem('${field}',${i},1)">↓</button><button type="button" title="删除这一项" onclick="Studio.removeItem('${field}',${i})">删除</button></div></div>`+
|
||||
keys.map(key=>`<label class="sf-array-field">${html(labels[key]||key)}${key==='text'||key==='answer'||key==='features'?`<textarea oninput="Studio.itemField('${field}',${i},'${key}',this.value)">${html(Array.isArray(row?.[key])?(row[key]||[]).join('\n'):row?.[key]??'')}</textarea>`:`<input type="text" value="${html(row?.[key]??'')}" oninput="Studio.itemField('${field}',${i},'${key}',this.value)">`}</label>`).join('')+
|
||||
`</div>`).join('')+`<button type="button" class="sf-array-add" onclick="Studio.addItem('${field}')">+ 添加${field==='images'?'图片':'一项'}</button></div>`;
|
||||
},
|
||||
itemField(field,index,key,value){const block=blocks[selected],row=block?.[field]?.[index];if(!row)return;row[key]=key==='features'?String(value).split('\n').map(x=>x.trim()).filter(Boolean):String(value).slice(0,1500);Studio.modified(true)},
|
||||
addItem(field){const block=blocks[selected];if(!block)return;const rows=block[field];if(!Array.isArray(rows))return;const limit=field==='images'?24:16;if(rows.length>=limit)return toast(`最多添加 ${limit} 项`,true);const sample=schema.blocks[block.type]?.defaults?.[field]?.[0]||{url:'',alt:''};const next={};for(const [key,val] of Object.entries(sample))next[key]=Array.isArray(val)?[]:'';rows.push(next);Studio.modified();Studio.inspector()},
|
||||
removeItem(field,index){const rows=blocks[selected]?.[field];if(!Array.isArray(rows)||!rows[index])return;rows.splice(index,1);Studio.modified();Studio.inspector()},
|
||||
moveItem(field,index,by){const rows=blocks[selected]?.[field],target=index+by;if(!Array.isArray(rows)||target<0||target>=rows.length)return;[rows[index],rows[target]]=[rows[target],rows[index]];Studio.modified();Studio.inspector()},
|
||||
jsonField(key,value){try{let data=JSON.parse(value);if(!Array.isArray(data))throw Error('必须为数组');Studio.fieldQuiet(key,data);$('#jsonError').textContent='✓ JSON 格式有效';$('#jsonError').style.color='#238460'}catch(e){$('#jsonError').textContent='JSON 格式有误:'+e.message;$('#jsonError').style.color='#bf5a58'}},
|
||||
styleField(key,value){const b=blocks[selected];b.style={...(b.style||{}),[key]:value};Studio.modified()},
|
||||
inspector(){let target=$('#inspector');let b=blocks[selected];$('#blockTypeLabel').textContent=b?schema.blocks[b.type].name:'页面';if(!b){target.innerHTML='<div class="no-selection"><b>还没有选中内容</b><p>先到左侧选择一个区块,或点击“添加内容区块”。</p><button type="button" class="ui-btn primary" onclick="Studio.toggleLibrary(true)">+ 添加内容</button></div>';return}const spec=schema.blocks[b.type];let out=`<div class="inspector-title"><h3>${html(spec.name)}</h3><label class="toggle"><input type="checkbox" ${b.visible!==false?'checked':''} onchange="Studio.field('visible',this.checked)">显示</label></div><div data-inspector-section="content">`;
|
||||
for(const field of spec.fields.filter(f=>b.type!=='layout'||!['columns','gap','tablet_columns','mobile_columns'].includes(f))){let value=b[field]??'',label=({title:'标题',subtitle:'副标题',description:'简介',image:'图片地址',body:'正文 HTML',button:'按钮文字',href:'跳转路径',source:'绑定内容类型',limit:'显示条数',columns:'每行列数',items:'重复条目',images:'画廊图片',image_side:'图片方向',align:'标题对齐',height:'间隔高度'})[field]||field;
|
||||
out+=`<div class="prop"><label>${html(label)}</label>`;
|
||||
if(field==='items'||field==='images')out+=Studio.arrayEditor(field,value,b.type);
|
||||
else if(field==='source')out+=`<select onchange="Studio.field('${field}',this.value)">${['product','post','page','vehicle','brand','case','service','other','all'].map(t=>`<option value="${t}" ${t===value?'selected':''}>${({product:'产品',post:'文章',page:'普通页面',vehicle:'车型',brand:'品牌',case:'案例',service:'服务',other:'其他',all:'全部类型'})[t]}</option>`).join('')}</select><span class="prop-tip">网站已有内容将自动绑定并生成卡片。</span>`;
|
||||
else if(field==='image_side'||field==='align')out+=`<select onchange="Studio.field('${field}',this.value)">${(field==='align'?['left','center']:['left','right']).map(v=>`<option value="${v}" ${value===v?'selected':''}>${v==='left'?'左侧':v==='right'?'右侧':'居中'}</option>`).join('')}</select>`;
|
||||
else if(field==='body'||field==='description')out+=`<textarea onchange="Studio.fieldQuiet('${field}',this.value)">${html(value)}</textarea>`;
|
||||
else if(['limit','columns','height'].includes(field))out+=`<input type="number" min="1" max="120" value="${html(value)}" onchange="Studio.fieldQuiet('${field}',Number(this.value))">`;
|
||||
else out+=`<input type="text" value="${html(value)}" oninput="Studio.fieldQuiet('${field}',this.value)">`;
|
||||
if(['title','subtitle','description','image','body'].includes(field))out+='<small class="prop-tip">可输入 {{site.name}}、{{page.title}} 等变量。</small>';
|
||||
out+='</div>';
|
||||
}
|
||||
if(b.type==='layout')out+=Studio.childQuickEditor(b);
|
||||
out+='</div><div data-inspector-section="responsive">';
|
||||
if(b.type==='layout'){
|
||||
out+=`<div class="subheading">快捷布局</div><p class="prop-tip">常用排版一键设置,也可直接拖动画布中的列宽分隔线。</p><div class="sf-layout-presets"><button type="button" onclick="Studio.setLayoutPreset('equal')">均分列宽</button><button type="button" ${b.columns.length===2?'':'disabled'} onclick="Studio.setLayoutPreset('left-wide')">左宽右窄</button><button type="button" ${b.columns.length===2?'':'disabled'} onclick="Studio.setLayoutPreset('right-wide')">左窄右宽</button></div><div class="prop"><label>列间距(0–64px)</label><input type="number" min="0" max="64" value="${html(b.gap??24)}" onchange="Studio.setLayoutGap(this.value)"></div><div class="subheading">手机与平板</div><p class="prop-tip">选 1 列更适合手机阅读。以下设置只影响对应设备,不更改桌面列宽。</p><div class="prop"><label>平板每行列数 <input type="number" min="1" max="3" value="${html(b.tablet_columns||2)}" onchange="Studio.setDeviceColumns('tablet',this.value)"></label><label>手机每行列数 <input type="number" min="1" max="2" value="${html(b.mobile_columns||1)}" onchange="Studio.setDeviceColumns('mobile',this.value)"></label></div>`;
|
||||
b.columns.forEach((column,ci)=>{
|
||||
out+=`<div class="layout-editor-col" ondragover="event.preventDefault()" ondrop="Studio.dropChild(event,${ci})"><div class="layout-editor-title"><strong>第${ci+1}列</strong><label>宽度 <input type="number" min="0.5" max="4" step="0.5" value="${html(column.width)}" onchange="Studio.setColumnWidth(${ci},this.value)"></label><button onclick="Studio.removeColumn(${ci})">删除列</button></div>`;
|
||||
out+=`<div class="prop"><label>平板排序 <input type="number" min="0" max="20" value="${html(column.tablet_order??(ci+1))}" onchange="Studio.setColumnDevice(${ci},'tablet_order',Number(this.value))"></label><label>手机排序 <input type="number" min="0" max="20" value="${html(column.mobile_order??(ci+1))}" onchange="Studio.setColumnDevice(${ci},'mobile_order',Number(this.value))"></label><label>平板跨列 <input type="number" min="1" max="${html(b.tablet_columns||2)}" value="${html(column.tablet_span||1)}" onchange="Studio.setColumnDevice(${ci},'tablet_span',Number(this.value))"></label><label>手机跨列 <input type="number" min="1" max="${html(b.mobile_columns||1)}" value="${html(column.mobile_span||1)}" onchange="Studio.setColumnDevice(${ci},'mobile_span',Number(this.value))"></label><label><input type="checkbox" ${column.hide_tablet?'checked':''} onchange="Studio.setColumnDevice(${ci},'hide_tablet',this.checked)"> 平板隐藏此列</label><label><input type="checkbox" ${column.hide_mobile?'checked':''} onchange="Studio.setColumnDevice(${ci},'hide_mobile',this.checked)"> 手机隐藏此列</label></div>`;
|
||||
column.blocks.forEach((child,j)=>{out+=`<div class="layout-child" draggable="true" ondragstart="Studio.dragChild(event,${ci},${j})"><strong>${html(schema.blocks[child.type]?.name||child.type)}</strong><button onclick="Studio.removeChild(${ci},${j})" title="删除">×</button>`;
|
||||
for(const key of (schema.blocks[child.type]?.fields||[]).filter(x=>['title','subtitle','description','image','body','href','button'].includes(x))){out+=`<label>${html(key)}<input value="${html(child[key]??'')}" onchange="Studio.childField(${ci},${j},'${key}',this.value)"></label>`}
|
||||
if(b.columns.length>1){out+=`<select onchange="Studio.moveChild(${ci},${j},Number(this.value))"><option value="">移动到其他列</option>${b.columns.map((c,ki)=>ki===ci?'':`<option value="${ki}">第${ki+1}列</option>`).join('')}</select>`}
|
||||
out+='</div>';
|
||||
});
|
||||
out+=`<div class="layout-child-toolbar">${['richtext','image_text','collection','cta','features','layout'].map(t=>`<button onclick="Studio.addChild(${ci},'${t}')">+${t==='layout'?'内层布局':html(schema.blocks[t].name)}</button>`).join('')}</div></div>`;
|
||||
});
|
||||
out+='<button class="ui-btn ghost" onclick="Studio.addColumn()">+ 添加列</button>';
|
||||
}
|
||||
out+=`<div class="subheading">响应式样式</div><div class="prop"><label>平板上下间距(px)</label><input type="number" min="0" max="120" value="${html(b.style?.tablet_padding??50)}" onchange="Studio.styleField('tablet_padding',Number(this.value))"></div><div class="prop"><label>手机上下间距(px)</label><input type="number" min="0" max="120" value="${html(b.style?.mobile_padding??28)}" onchange="Studio.styleField('mobile_padding',Number(this.value))"></div><div class="prop"><label><input type="checkbox" ${b.style?.hide_mobile?'checked':''} onchange="Studio.styleField('hide_mobile',this.checked)"> 手机上隐藏区块</label></div>`;
|
||||
out+=`</div><div data-inspector-section="style"><div class="subheading">外观设置</div><div class="prop"><label>背景颜色</label><input type="color" value="${html(b.style?.background||'#ffffff')}" onchange="Studio.styleField('background',this.value)"></div><div class="prop"><label>上下间距(px)</label><input type="number" min="0" max="120" value="${html(b.style?.padding??82)}" onchange="Studio.styleField('padding',Number(this.value))"></div><div class="prop"><label>从图库选择图片</label><button type="button" class="ui-btn ghost" onclick="Studio.chooseMedia()">打开本站素材库</button></div><div class="prop"><label>上传图片</label><input type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/avif" onchange="Studio.uploadImage(this.files[0])"></div></div>`;
|
||||
const sharing=selectedChildId?editorTools.findChildInRoot(blocks,b.id,selectedChildId)?.child?.shared_ref:b.shared_ref;
|
||||
if(sharing)out+=`<div class="sf-share-info"><strong>已关联公共组件 · v${html(sharing.version)}</strong><p>此区块将来可以按需同步新版。直接修改后会被视为本地定制。</p><button type="button" class="ui-btn ghost" onclick="Studio.detachShared()">解除关联,保留当前内容</button></div>`;
|
||||
out+='<div style="display:flex;gap:8px;margin-bottom:22px"><button class="small-btn" onclick="Studio.duplicate('+selected+')">复制区块</button><button class="small-btn red-btn" onclick="Studio.remove('+selected+')">删除区块</button></div>';
|
||||
target.innerHTML=out;Studio.applyInspectorTab();
|
||||
},
|
||||
undo(){restoreSnapshot(snapshotCursor-1)},
|
||||
redo(){restoreSnapshot(snapshotCursor+1)},
|
||||
modified(continuous=false){
|
||||
const now=Date.now(),snap=JSON.stringify({blocks,kind,layoutMode});
|
||||
if(continuous&&lastContinuousEdit&&now-lastContinuousEdit<850&&snapshotCursor>0&&snapshotCursor===snapshots.length-1){snapshots[snapshotCursor]=snap;}
|
||||
else recordSnapshot();
|
||||
lastContinuousEdit=continuous?now:0;
|
||||
dirty=true;$('#saveStatus').textContent='未保存更改';$('#saveStatus').classList.add('dirty');
|
||||
Studio.updateHistoryButtons();Studio.updateGuidance();clearTimeout(timer);timer=setTimeout(()=>Studio.refreshPreview(),550);
|
||||
},
|
||||
async refreshPreview(){
|
||||
if(!pageId)return;
|
||||
const page=pageId,seq=++previewRequest;
|
||||
if(previewController)previewController.abort();previewController=new AbortController();
|
||||
const frame=$('#sitePreview');$('#previewState').textContent='正在更新预览…';$('#previewDot').classList.add('loading');
|
||||
try{
|
||||
const doc=await api(`/api/sites/${siteId}/pages/${page}/editor-preview`,{...req('POST',{blocks,page_template:kind,layout_mode:layoutMode,site_design:siteDesignPreview}),signal:previewController.signal});
|
||||
if(!editorTools.shouldAcceptPreview(page,seq,pageId,previewRequest))return;
|
||||
frame.onload=()=>Studio.highlightCanvas();frame.srcdoc=doc;$('#previewState').textContent='实时预览 · 已更新';$('#previewDot').classList.remove('loading','error');
|
||||
}catch(e){
|
||||
if(!editorTools.shouldAcceptPreview(page,seq,pageId,previewRequest)||e.name==='AbortError')return;
|
||||
$('#previewState').textContent='预览更新失败';$('#previewDot').classList.remove('loading');$('#previewDot').classList.add('error');toast('预览失败:'+e.message,true);
|
||||
}
|
||||
},
|
||||
device(type){$('#deviceFrame').className='device-frame '+(type==='desktop'?'':type);$('#deviceDetails').textContent=({desktop:'桌面端 · 自适应宽度',tablet:'平板端 · 780px',phone:'手机端 · 390px'})[type]||'桌面端';for(let name of ['Desktop','Tablet','Phone'])$('#device'+name).classList.toggle('on',name.toLowerCase()===type)},
|
||||
changeTemplateKind(t){kind=t;Studio.modified()},
|
||||
setLayoutMode(v){layoutMode=v;Studio.modified()},
|
||||
async resetPreset(){if(!confirm('用当前主题的预设布局替换草稿?不会更改正式页面。'))return;try{const result=await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft/preset?page_template=${encodeURIComponent(kind)}`);blocks=result.blocks;selected=blocks.length?0:-1;Studio.modified();Studio.draw();toast('已加载预设到草稿,记得保存')}catch(e){toast(e.message,true)}},
|
||||
async showHistory(){
|
||||
if(!pageId)return toast('请先选择页面',true);
|
||||
$('#studioHistoryModal').showModal();$('#studioHistoryItems').textContent='读取历史记录中…';
|
||||
try{const data=await api(`/api/sites/${siteId}/pages/${pageId}/studio-history?limit=50`);
|
||||
$('#studioHistoryItems').innerHTML=(data.items||[]).map(x=>`<div style="padding:12px 0;border-bottom:1px solid #e2e8f0;display:flex;gap:12px;align-items:center;justify-content:space-between"><div><strong>${x.kind==='published'?'已发布':'草稿'} · v${html(x.revision)}</strong><div style="font-size:12px;color:#64748b">${html(x.created_at)} · ${html(x.actor)} · SHA ${html(x.snapshot_hash.slice(0,12))}</div></div><button class="ui-btn ghost" type="button" onclick="Studio.restoreHistory('${html(x.id)}')">恢复为新草稿</button></div>`).join('')||'<p>当前页面尚无保存记录。</p>';
|
||||
}catch(e){$('#studioHistoryItems').textContent='读取失败:'+e.message}
|
||||
},
|
||||
async restoreHistory(id){
|
||||
if(dirty&&!confirm('当前有未保存的本地修改。恢复历史记录将舍弃这些本地修改,继续吗?'))return;
|
||||
if(!confirm('确定以这个历史快照创建一个新的设计草稿?已发布的页面不会立即改变。'))return;
|
||||
try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-history/${encodeURIComponent(id)}/restore`,req('POST',{expected_revision:draftRevision}));
|
||||
dirty=false;$('#studioHistoryModal').close();await Studio.choosePage(pageId);
|
||||
toast('历史版本已恢复为新草稿。核对后再点击发布设计。');
|
||||
}catch(e){toast('历史恢复失败:'+e.message,true)}
|
||||
},
|
||||
async save(){
|
||||
if(savePromise)return savePromise;
|
||||
const page=pageId;const snapshot=JSON.stringify({blocks,kind,layoutMode});const frozen=JSON.parse(snapshot);
|
||||
$('#saveStatus').textContent='保存中…';
|
||||
savePromise=(async()=>{
|
||||
try{
|
||||
const result=await api(`/api/sites/${siteId}/pages/${page}/studio-draft`,req('PUT',{blocks:frozen.blocks,page_template:frozen.kind,layout_mode:frozen.layoutMode,expected_revision:draftRevision}));
|
||||
if(page!==pageId)return;
|
||||
draftRevision=result.revision;
|
||||
const unchanged=snapshot===JSON.stringify({blocks,kind,layoutMode});
|
||||
dirty=!unchanged;$('#saveStatus').textContent=unchanged?'草稿 v'+draftRevision:'有新修改待保存';
|
||||
$('#saveStatus').classList.toggle('dirty',dirty);
|
||||
toast(unchanged?'设计草稿已保存,正式构建不受影响':'草稿已保存,但有新修改,需再次保存');
|
||||
}catch(e){dirty=true;$('#saveStatus').textContent='保存失败';$('#saveStatus').classList.add('dirty');toast('草稿保存失败:'+e.message,true)}
|
||||
finally{savePromise=null;Studio.updateGuidance()}
|
||||
})();return savePromise;
|
||||
},
|
||||
async publishDesign(){if(dirty){await Studio.save();if(dirty)return}if(!draftRevision)return toast('当前没有待发布草稿');if(!confirm('发布设计将更新内容库中的页面布局,下次静态构建才会生效。是否发布?'))return;try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft/publish`,req('POST',{expected_revision:draftRevision}));draftRevision=0;$('#saveStatus').textContent='已发布到内容库';Studio.updateGuidance();toast('设计已发布到内容库。要更新线上网站,请在“更多”中构建并部署。')}catch(e){toast('发布失败:'+e.message,true)}},
|
||||
async discardDraft(){if(!draftRevision)return;if(!confirm('确定放弃草稿?'))return;try{await api(`/api/sites/${siteId}/pages/${pageId}/studio-draft`,{method:'DELETE'});dirty=false;await Studio.choosePage(pageId);toast('草稿已放弃')}catch(e){toast(e.message,true)}},
|
||||
dragChild(event,col,index){draggedChild={container:selected,col,index};event.dataTransfer.setData('application/siteforge-child','1');event.dataTransfer.effectAllowed='move';event.stopPropagation()},
|
||||
dropChild(event,destCol){if(!draggedChild)return;event.preventDefault();event.stopPropagation();const drag=draggedChild;draggedChild=null;const src=blocks[drag.container],dest=blocks[selected];if(!src||!dest||src.type!=='layout'||dest.type!=='layout')return;const moved=src.columns?.[drag.col]?.blocks.splice(drag.index,1)[0];if(!moved)return;dest.columns[destCol].blocks.push(moved);Studio.modified();Studio.draw()},
|
||||
drawSiteNav(){
|
||||
const root=$('#siteNavRows');root.replaceChildren();
|
||||
const draw=(items,depth=0)=>items.forEach((node,index)=>{
|
||||
const line=document.createElement('div');line.className='sf-nav-item';line.style.marginLeft=`${depth*16}px`;
|
||||
const title=document.createElement('input');title.placeholder='菜单名称';title.maxLength=48;title.value=node.title||'';title.setAttribute('aria-label','菜单名称');title.oninput=()=>node.title=title.value;
|
||||
const route=document.createElement('input');route.placeholder='/products/';route.value=node.route||'/';route.setAttribute('aria-label','站内路径');route.oninput=()=>node.route=route.value;
|
||||
line.append(title,route);
|
||||
const op=(label,fn)=>{const button=document.createElement('button');button.type='button';button.className='ui-btn ghost sf-nav-action';button.textContent=label;button.onclick=fn;line.append(button)};
|
||||
if(depth<2)op('+ 子菜单',()=>{node.children=node.children||[];node.children.push({title:'新菜单',route:'/',children:[]});Studio.drawSiteNav()});
|
||||
if(index)op('↑',()=>{[items[index-1],items[index]]=[items[index],items[index-1]];Studio.drawSiteNav()});
|
||||
if(index<items.length-1)op('↓',()=>{[items[index+1],items[index]]=[items[index],items[index+1]];Studio.drawSiteNav()});
|
||||
op('删除',()=>{items.splice(index,1);Studio.drawSiteNav()});
|
||||
root.append(line);if(node.children?.length)draw(node.children,depth+1);
|
||||
});
|
||||
draw(siteNavDraft);
|
||||
},
|
||||
addSiteNav(){siteNavDraft.push({title:'新菜单',route:'/',children:[]});Studio.drawSiteNav()},
|
||||
async showSiteDesign(){try{
|
||||
const result=await api(`/api/sites/${siteId}/site-design`);
|
||||
siteDesign=result.config;siteDesignRevision=result.revision;siteDesignPreview=structuredClone(siteDesign);
|
||||
const form=$('#siteDesignForm'),h=siteDesign.header,f=siteDesign.footer,r=siteDesign.responsive;
|
||||
for(const [name,value] of Object.entries(h)){const el=form.elements.namedItem(name);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
|
||||
for(const [name,value] of Object.entries(f)){const key='footer_'+name,el=form.elements.namedItem(key);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
|
||||
for(const [name,value] of Object.entries(r)){const el=form.elements.namedItem(name);if(el){if(el.type==='checkbox')el.checked=value;else el.value=value}}
|
||||
siteNavDraft=structuredClone(siteDesign.nav||[]);Studio.drawSiteNav();
|
||||
form.elements.namedItem('mobile_tabs_enabled').checked=Boolean(siteDesign.mobile_tabs?.enabled);
|
||||
Studio.drawMobileTabs(siteDesign.mobile_tabs?.items||[]);
|
||||
Studio.toggleMobileTabsForm();
|
||||
$('#siteDesignStatus').textContent=result.state==='draft'?`待发布的全站草稿 v${siteDesignRevision}${result.conflict?' · 网站已被其他人修改':''}`:'当前为已发布设计';
|
||||
$('#siteDesignModal').showModal();
|
||||
}catch(e){toast('全站设计读取失败:'+e.message,true)}},
|
||||
drawMobileTabs(items){const root=$('#mobileTabsRows');root.replaceChildren();items.forEach((item,i)=>Studio.addMobileTab(item,i));},
|
||||
toggleMobileTabsForm(){const on=$('#siteDesignForm').elements.namedItem('mobile_tabs_enabled').checked;$('#mobileTabsEditor').hidden=!on;},
|
||||
addMobileTab(item={title:'新导航',icon:'grid',route:'/',action:'link'},index){
|
||||
const root=$('#mobileTabsRows');if(root.children.length>=5){toast('底部最多支持 5 个 Tab',true);return}
|
||||
const n=root.children.length;
|
||||
const row=document.createElement('div');row.className='mobile-tab-row';row.style.cssText='display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:12px;border:1px solid #dce3ed;border-radius:10px;background:#fafcff';
|
||||
const label=document.createElement('label');label.textContent='按钮名称';const title=document.createElement('input');title.className='mt-title';title.maxLength=12;title.value=item.title||'';label.append(title);
|
||||
const iconLabel=document.createElement('label');iconLabel.textContent='按钮图标';const icon=document.createElement('select');icon.className='mt-icon';
|
||||
for(const [v,t] of Object.entries({home:'⌂ 首页',grid:'▦ 栏目',products:'▤ 产品',news:'☷ 文章',contact:'✉ 联系',phone:'☎ 电话',info:'ⓘ 关于',search:'⌕ 搜索',menu:'☰ 菜单',star:'☆ 收藏'})){const opt=document.createElement('option');opt.value=v;opt.textContent=t;icon.append(opt)}icon.value=item.icon||'grid';iconLabel.append(icon);
|
||||
const actionLabel=document.createElement('label');actionLabel.textContent='点击操作';const action=document.createElement('select');action.className='mt-action';for(const [v,t] of [['link','打开站内页面'],['menu','打开导航抽屉']]){const o=document.createElement('option');o.value=v;o.textContent=t;action.append(o)}action.value=item.action||'link';actionLabel.append(action);
|
||||
const routeLabel=document.createElement('label');routeLabel.textContent='链接地址';const route=document.createElement('input');route.className='mt-route';route.placeholder='/products/、https://wa.me/... 或 tel:+...';route.value=item.route||'/';routeLabel.append(route);
|
||||
function updateRoute(){routeLabel.hidden=action.value==='menu'}action.addEventListener('change',updateRoute);updateRoute();
|
||||
const tools=document.createElement('div');tools.style.cssText='grid-column:1/-1;display:flex;gap:7px;justify-content:flex-end';
|
||||
for(const [name,fn] of [['↑ 上移',()=>{const prev=row.previousElementSibling;if(prev)root.insertBefore(row,prev)}],['↓ 下移',()=>{const next=row.nextElementSibling;if(next)root.insertBefore(next,row)}],['删除',()=>row.remove()]]){const b=document.createElement('button');b.type='button';b.className='ui-btn ghost';b.textContent=name;b.addEventListener('click',fn);tools.append(b)}
|
||||
row.append(label,iconLabel,actionLabel,routeLabel,tools);root.append(row);
|
||||
},
|
||||
collectSiteDesign(){const form=$('#siteDesignForm');if(!siteDesign)throw Error('请先打开全站设计');
|
||||
const v=(n)=>form.elements.namedItem(n).value;
|
||||
const yes=(n)=>form.elements.namedItem(n).checked;
|
||||
const nav=structuredClone(siteNavDraft);
|
||||
const tabs=[...$('#mobileTabsRows').children].map(row=>({title:row.querySelector('.mt-title').value.trim(),icon:row.querySelector('.mt-icon').value,route:row.querySelector('.mt-route').value.trim(),action:row.querySelector('.mt-action').value}));
|
||||
if(yes('mobile_tabs_enabled')&&!tabs.length)throw Error('开启底部 Tabs 后,请至少添加一个按钮');
|
||||
return {tokens:structuredClone(siteDesign.tokens),mobile_tabs:{enabled:yes('mobile_tabs_enabled'),items:tabs},header:{announcement:v('announcement'),show_announcement:yes('show_announcement'),show_search:yes('show_search'),
|
||||
show_cta:yes('show_cta'),cta_text:v('cta_text'),cta_href:v('cta_href')},
|
||||
footer:{title:v('footer_title'),description:v('footer_description'),email:v('footer_email'),show_nav:yes('footer_show_nav')},nav,
|
||||
responsive:{tablet_gap:Number(v('tablet_gap')),mobile_gap:Number(v('mobile_gap')),
|
||||
mobile_container_padding:Number(v('mobile_container_padding')),mobile_hide_announcement:yes('mobile_hide_announcement'),mobile_hide_footer_links:yes('mobile_hide_footer_links')}};
|
||||
},
|
||||
async previewSiteDesign(){try{siteDesignPreview=Studio.collectSiteDesign();await Studio.refreshPreview();
|
||||
$('#siteDesignStatus').textContent='编辑器正在预览未发布的全站设置;静态网站保持不变';toast('已在编辑器预览全站设计')
|
||||
}catch(e){toast(e.message,true)}},
|
||||
async saveSiteDesign(){try{const config=Studio.collectSiteDesign();let saved=await api(`/api/sites/${siteId}/site-design`,req('PUT',{config,expected_revision:siteDesignRevision}));
|
||||
siteDesignRevision=saved.revision;siteDesign=config;siteDesignPreview=structuredClone(config);
|
||||
$('#siteDesignStatus').textContent=`草稿 v${siteDesignRevision} 已保存,正式构建保持不变`;toast('全站设计草稿保存成功');return true;
|
||||
}catch(e){toast('全站草稿保存失败:'+e.message,true);return false}},
|
||||
async publishSiteDesign(){
|
||||
if(!await Studio.saveSiteDesign())return;
|
||||
try{
|
||||
const review=await api(`/api/sites/${siteId}/site-design/review`);
|
||||
siteDesignReviewToken=review.plan_token;
|
||||
const box=$('#siteDesignReviewBody');box.replaceChildren();
|
||||
const lead=document.createElement('p');lead.textContent=`草稿 v${review.revision} · ${review.change_count} 处字段变更 · 影响本站下一次构建的全部页面`;box.append(lead);
|
||||
if(!review.change_count){const empty=document.createElement('p');empty.textContent='没有任何实际配置变化。可以返回编辑。';box.append(empty);}
|
||||
review.changed_fields.forEach(change=>{
|
||||
const article=document.createElement('article');article.className='sf-review-change';
|
||||
const label=document.createElement('strong');label.textContent=Studio.designFieldLabel(change.field);article.append(label);
|
||||
const old=document.createElement('p');old.textContent='原配置:'+Studio.formatDesignDifference(change.before,change.field);
|
||||
const newer=document.createElement('p');newer.textContent='新配置:'+Studio.formatDesignDifference(change.after,change.field);
|
||||
article.append(old,newer);box.append(article);
|
||||
});
|
||||
$('#siteDesignReviewModal').showModal();
|
||||
}catch(e){toast('审核预览失败:'+e.message,true)}
|
||||
},
|
||||
designFieldLabel(key){const names={header:'页眉',footer:'页脚',nav:'网站导航',mobile_tabs:'手机底部导航',tokens:'全站风格',responsive:'响应式布局',announcement:'提示文字',show_announcement:'显示提示栏',show_search:'显示搜索',show_cta:'显示联系按钮',cta_text:'按钮文字',cta_href:'按钮地址',description:'介绍文字',email:'邮箱',title:'标题',items:'导航项目',enabled:'是否启用'};return key.split('.').map(v=>names[v]||v).join(' · ')},
|
||||
formatDesignDifference(value,field=''){
|
||||
if(typeof value==='boolean')return value?'已开启':'已关闭';
|
||||
if(value==null)return '未设置';
|
||||
if(field==='nav'&&Array.isArray(value)){
|
||||
const parts=[];const walk=(items,depth)=>items.forEach(n=>{parts.push(' '.repeat(depth)+(n.title||'未命名')+' → '+(n.route||'/'));walk(n.children||[],depth+1)});walk(value,0);
|
||||
return parts.length?parts.join('\n'):'(未配置导航)';
|
||||
}
|
||||
if(field==='mobile_tabs'&&value&&typeof value==='object'){
|
||||
return value.enabled?'已启用:'+(value.items||[]).map(v=>v.title+' → '+(v.action==='menu'?'打开菜单':v.route)).join(' · '):'未启用';
|
||||
}
|
||||
if(Array.isArray(value))return value.map(v=>typeof v==='object'?(v.title||JSON.stringify(v)):v).join('、')||'空列表';
|
||||
const text=typeof value==='string'?value:JSON.stringify(value);
|
||||
return text.length>440?text.slice(0,440)+'…':text;
|
||||
},
|
||||
async confirmReviewedSiteDesign(){
|
||||
if(!siteDesignReviewToken)return toast('请先查看发布差异',true);
|
||||
try{
|
||||
await api(`/api/sites/${siteId}/site-design/publish-reviewed`,req('POST',{
|
||||
expected_revision:siteDesignRevision,plan_token:siteDesignReviewToken}));
|
||||
siteDesignReviewToken=null;siteDesignRevision=0;siteDesignPreview=null;
|
||||
$('#siteDesignReviewModal').close();$('#siteDesignModal').close();
|
||||
$('#siteDesignStatus').textContent='全站设计已发布到内容库,下一次构建后生效';
|
||||
await Studio.refreshPreview();toast('全站设计已审核并发布,请构建及部署网站。');
|
||||
}catch(e){toast('发布失败:'+e.message,true)}
|
||||
},
|
||||
async showSiteDesignHistory(){
|
||||
try{
|
||||
const items=await api(`/api/sites/${siteId}/site-design/publications`),box=$('#siteDesignHistoryBody');box.replaceChildren();
|
||||
if(!items.length){box.textContent='尚无发布记录';}
|
||||
for(const item of items){
|
||||
const row=document.createElement('div');row.className='sf-review-change';
|
||||
const title=document.createElement('strong');title.textContent=`${item.published_at} · 版本 ${item.revision}`;row.append(title);
|
||||
const show=document.createElement('button');show.type='button';show.className='ui-btn ghost';show.textContent='查看变更';
|
||||
show.onclick=async()=>{try{const detail=await api(`/api/sites/${siteId}/site-design/publications/${item.id}`);const p=document.createElement('p');p.textContent=detail.changes.map(x=>x.field).join('、')||'无字段变化';row.append(p);show.disabled=true;}catch(e){toast(e.message,true)}};
|
||||
const restore=document.createElement('button');restore.type='button';restore.className='ui-btn ghost';restore.textContent='恢复为新草稿';
|
||||
restore.onclick=async()=>{
|
||||
if(!confirm('确认以这个历史版本生成新的全站设计草稿?不会直接更改线上页面。'))return;
|
||||
try{const live=await api(`/api/sites/${siteId}/site-design/live-hash`);
|
||||
await api(`/api/sites/${siteId}/site-design/publications/${item.id}/restore`,req('POST',{
|
||||
snapshot:'after',expected_revision:siteDesignRevision,expected_live_hash:live.live_hash}));
|
||||
$('#siteDesignHistoryModal').close();await Studio.showSiteDesign();toast('历史版本已恢复为新草稿,请检查后发布');
|
||||
}catch(e){toast(e.message,true)}
|
||||
};
|
||||
row.append(show,restore);box.append(row);
|
||||
}
|
||||
$('#siteDesignHistoryModal').showModal();
|
||||
}catch(e){toast(e.message,true)}
|
||||
},
|
||||
async discardSiteDesign(){if(!confirm('确定删除尚未发布的全站草稿?'))return;
|
||||
try{await api(`/api/sites/${siteId}/site-design`,{method:'DELETE'});siteDesignPreview=null;
|
||||
await Studio.showSiteDesign();await Studio.refreshPreview();toast('全站草稿已放弃')
|
||||
}catch(e){toast(e.message,true)}},
|
||||
async showTokens(){try{let current=await api(`/api/sites/${siteId}/site-design`);siteDesign=current.config;siteDesignRevision=current.revision;const tokens=siteDesign.tokens;const modal=$('#designModal');for(const [k,v] of Object.entries(tokens)){const field=modal.querySelector(`[name="${k}"]`);if(field)field.value=v}modal.showModal()}catch(e){toast(e.message,true)}},
|
||||
async saveTokens(){const form=$('#designForm'),tokens={};for(const input of form.querySelectorAll('[name]'))tokens[input.name]=input.type==='number'?Number(input.value):input.value;try{let r=await api(`/api/sites/${siteId}/site-design`);siteDesign=r.config;siteDesignRevision=r.revision;siteDesign.tokens=tokens;let saved=await api(`/api/sites/${siteId}/site-design`,req('PUT',{config:siteDesign,expected_revision:siteDesignRevision}));siteDesignRevision=saved.revision;siteDesignPreview=structuredClone(siteDesign);$('#designModal').close();Studio.refreshPreview();toast('设计规范已保存为全站草稿;请在“全站页眉与页脚”中发布')}catch(e){toast(e.message,true)}},
|
||||
setDeviceColumns(device,value){const b=blocks[selected];if(!b||b.type!=='layout')return;const max=device==='tablet'?3:2;const next=Math.min(max,Math.max(1,Number(value)||1));b[device+'_columns']=next;b.columns.forEach(c=>c[device+'_span']=Math.min(next,c[device+'_span']||1));Studio.modified();Studio.inspector()},
|
||||
setColumnDevice(i,key,value){const b=blocks[selected];if(!b||b.type!=='layout')return;const col=b.columns[i];if(!col)return;col[key]=value;Studio.modified();Studio.inspector()},
|
||||
addColumn(){const b=blocks[selected];if(!b||b.type!=='layout'||b.columns.length>=4)return;b.columns.push({width:1,blocks:[]});Studio.modified();Studio.inspector()},
|
||||
removeColumn(i){const b=blocks[selected];if(!b||b.type!=='layout'||b.columns.length<=1)return;if(b.columns[i].blocks.length&&!confirm('这一列含有组件,确认删除?'))return;b.columns.splice(i,1);Studio.modified();Studio.inspector()},
|
||||
setColumnWidth(i,value){const b=blocks[selected];if(!b||b.type!=='layout'||!b.columns[i])return;b.columns[i].width=Math.round(Math.max(.5,Math.min(4,Number(value)||1))*100)/100;Studio.modified();Studio.inspector()},
|
||||
addChild(i,type){const b=blocks[selected];if(!b||b.type!=='layout')return;const item=structuredClone(schema.blocks[type].defaults);item.type=type;item.id=crypto.randomUUID().replace(/-/g,'').slice(0,10);item.visible=true;b.columns[i].blocks.push(item);Studio.modified();Studio.inspector()},
|
||||
removeChild(i,j){const b=blocks[selected];if(!b||b.type!=='layout')return;b.columns[i].blocks.splice(j,1);Studio.modified();Studio.inspector()},
|
||||
childField(i,j,key,value){const b=blocks[selected];if(!b||b.type!=='layout')return;b.columns[i].blocks[j][key]=value;Studio.modified()},
|
||||
moveChild(ci,ri,target){const b=blocks[selected];if(!b||b.type!=='layout'||target<0||target>=b.columns.length)return;let [component]=b.columns[ci].blocks.splice(ri,1);b.columns[target].blocks.push(component);Studio.modified();Studio.inspector()},
|
||||
async chooseMedia(){try{const data=await api(`/api/sites/${siteId}/media`);const grid=$('#siteMediaGrid');grid.replaceChildren();if(!data.length){grid.textContent='暂无上传图片。可先在当前组件中上传图片。'}for(const item of data){const button=document.createElement('button');button.type='button';button.style.cssText='border:1px solid #e2e8f0;border-radius:9px;background:white;padding:7px;cursor:pointer';const img=document.createElement('img');img.alt=item.name;img.loading='lazy';img.style.cssText='width:100%;height:105px;object-fit:contain';img.src=`/local-media/${encodeURIComponent(siteId)}/${encodeURIComponent(item.name)}`;const label=document.createElement('small');label.textContent=item.name;label.style.cssText='display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';button.append(img,label);button.addEventListener('click',()=>Studio.applyMedia(item.url));grid.append(button)}$('#siteMediaModal').showModal()}catch(e){toast(e.message,true)}},
|
||||
applyMedia(url){
|
||||
const root=blocks[selected];if(!root)return;
|
||||
const found=selectedChildId?editorTools.findChildInRoot(blocks,root.id,selectedChildId):null;
|
||||
if(selectedChildId&&!found)return toast('当前子组件已失效,请重新选中后添加图片',true);
|
||||
const block=found?.child||root;
|
||||
const fields=schema?.blocks?.[block.type]?.fields||[];
|
||||
if(block.type==='gallery')block.images=[...(block.images||[]),{url,alt:''}];
|
||||
else if(block.type==='carousel')block.items=[...(block.items||[]),{image:url,alt:'',caption:''}];
|
||||
else if(fields.includes('image'))block.image=url;
|
||||
else return toast('选中的组件不支持图片,请先选择一个图片组件',true);
|
||||
Studio.modified();Studio.draw();$('#siteMediaModal').close();toast('已应用素材到草稿');
|
||||
},
|
||||
async uploadImage(file){if(!file)return;try{const form=new FormData();form.append('file',file);let r=await api(`/api/sites/${siteId}/media`,{method:'POST',body:form});Studio.applyMedia(r.url);toast('图片已上传,保存页面后参与构建')}catch(e){toast(e.message,true)}},
|
||||
async buildSite(){if(dirty||draftRevision||siteDesignRevision)return toast('有尚未发布的页面或全站草稿,请先发布,再构建网站',true);try{
|
||||
toast('已提交后台构建任务');const task=await api(`/api/sites/${siteId}/builds`,req('POST',{mirror_images:false}));
|
||||
for(let i=0;i<240;i++){
|
||||
await new Promise(resolve=>setTimeout(resolve,1500));
|
||||
const status=await api(`/api/enterprise/sites/${siteId}/jobs/${task.id}`);
|
||||
if(status.state==='done'){const result=JSON.parse(status.result_json||'{}');toast('构建完成:'+result.pages+' 个页面');if(result.version)window.open(`/preview/${siteId}/${result.version}/`,'_blank','noopener');return}
|
||||
if(['failed','cancelled'].includes(status.state)){toast('构建'+status.state+':'+(status.error||''),true);return}
|
||||
}toast('后台任务仍在运行,请稍后查看构建中心');
|
||||
}catch(e){toast('构建失败:'+e.message,true)}},
|
||||
};
|
||||
window.Studio=Studio;
|
||||
// Standalone demo: server startup disabled
|
||||
window.addEventListener('beforeunload',e=>{if(dirty){e.preventDefault();e.returnValue='';}});
|
||||
|
||||
window.addEventListener('message',event=>{
|
||||
const frame=document.querySelector('#sitePreview');
|
||||
if(!frame||event.source!==frame.contentWindow)return;
|
||||
const data=event.data;
|
||||
if(data?.source==='siteforge-studio-canvas')Studio.canvasCommand(data);
|
||||
});
|
||||
|
||||
window.addEventListener('keydown',event=>{
|
||||
if(!(event.ctrlKey||event.metaKey)||event.altKey)return;
|
||||
const key=event.key.toLowerCase();
|
||||
if(key==='s'){event.preventDefault();Studio.save();return}
|
||||
const el=document.activeElement;
|
||||
if(el&&(['INPUT','TEXTAREA'].includes(el.tagName)||el.isContentEditable))return;
|
||||
if(key==='z'){event.preventDefault();event.shiftKey?Studio.redo():Studio.undo()}
|
||||
else if(key==='y'){event.preventDefault();Studio.redo()}
|
||||
});
|
||||
</script><script>Studio.refreshPreview=async()=>{document.querySelector("#demoStatus").textContent="草稿预览已刷新(模拟)";};Studio.showSiteDesign();</script></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"automotive": {
|
||||
"page_count": 36,
|
||||
"warnings": [],
|
||||
"quality": {
|
||||
"files": 38,
|
||||
"errors": 0,
|
||||
"warnings": 8,
|
||||
"passed": true,
|
||||
"issues": [
|
||||
{
|
||||
"file": "cases/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "cases/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/2/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-8d6647722ef3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-79e34764f8ce/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-95acf900fafc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-8d6647722ef3/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-a68a8656a4dc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-95acf900fafc/index.html"
|
||||
},
|
||||
{
|
||||
"file": "index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "404.html"
|
||||
},
|
||||
{
|
||||
"file": "news/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "news/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/2/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"industrial": {
|
||||
"page_count": 36,
|
||||
"warnings": [],
|
||||
"quality": {
|
||||
"files": 38,
|
||||
"errors": 0,
|
||||
"warnings": 8,
|
||||
"passed": true,
|
||||
"issues": [
|
||||
{
|
||||
"file": "cases/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "cases/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/2/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-8d6647722ef3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-79e34764f8ce/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-95acf900fafc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-8d6647722ef3/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-a68a8656a4dc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-95acf900fafc/index.html"
|
||||
},
|
||||
{
|
||||
"file": "index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "404.html"
|
||||
},
|
||||
{
|
||||
"file": "news/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "news/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/2/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"corporate": {
|
||||
"page_count": 36,
|
||||
"warnings": [],
|
||||
"quality": {
|
||||
"files": 38,
|
||||
"errors": 0,
|
||||
"warnings": 8,
|
||||
"passed": true,
|
||||
"issues": [
|
||||
{
|
||||
"file": "cases/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "cases/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/2/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-8d6647722ef3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-79e34764f8ce/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-95acf900fafc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-8d6647722ef3/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-a68a8656a4dc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-95acf900fafc/index.html"
|
||||
},
|
||||
{
|
||||
"file": "index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "404.html"
|
||||
},
|
||||
{
|
||||
"file": "news/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "news/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/2/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"minimal": {
|
||||
"page_count": 36,
|
||||
"warnings": [],
|
||||
"quality": {
|
||||
"files": 38,
|
||||
"errors": 0,
|
||||
"warnings": 8,
|
||||
"passed": true,
|
||||
"issues": [
|
||||
{
|
||||
"file": "cases/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "cases/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "cases/2/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-8d6647722ef3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-79e34764f8ce/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-95acf900fafc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-8d6647722ef3/index.html"
|
||||
},
|
||||
{
|
||||
"file": "categories/term-a68a8656a4dc/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "categories/term-95acf900fafc/index.html"
|
||||
},
|
||||
{
|
||||
"file": "index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "404.html"
|
||||
},
|
||||
{
|
||||
"file": "news/2/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/1/index.html"
|
||||
},
|
||||
{
|
||||
"file": "news/3/index.html",
|
||||
"type": "duplicate_description",
|
||||
"severity": "warning",
|
||||
"other": "news/2/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Options -MultiViews
|
||||
RewriteEngine On
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>404 · 页面不存在</title><meta name="description" content="探索更广阔的出行可能"><meta property="og:type" content="website"><meta property="og:title" content="404 · 页面不存在"><meta property="og:description" content="探索更广阔的出行可能"><meta property="og:url" content="https://automotive.example.test/404.html"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="noindex,follow"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-404 pro-wrap"><span>ERROR / 404</span><h1>404</h1><p>抱歉,你访问的页面不存在,或者已被移动。</p><a class="pro-button" href="/">返回首页 ↗</a></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>关于我们</title><meta name="description" content="企业介绍、发展理念、合作能力与团队故事。"><meta property="og:type" content="website"><meta property="og:title" content="关于我们"><meta property="og:description" content="企业介绍、发展理念、合作能力与团队故事。"><meta property="og:url" content="https://automotive.example.test/about/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/about/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="4f10f17be161adfe" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-about pro-v30-about-automotive"><section class="pro-v30-about-opening"><div class="pro-wrap"><span class="pro-eyebrow">OUR COMPANY / APPROACH</span><div class="pro-v30-about-heading"><h1>关于我们</h1><p>企业介绍、发展理念、合作能力与团队故事。</p></div></div></section>
|
||||
<section class="pro-section pro-v30-about-story"><div class="pro-wrap pro-v30-about-grid"><div class="pro-v30-about-media"><div class="pro-v30-media-placeholder" aria-hidden="true"><span>OUR / STORY</span></div></div><div class="pro-v30-about-copy"><span class="pro-eyebrow">ABOUT / 关于我们</span><h2>值得深入了解的汽车合作伙伴</h2><div class="pro-rich"><p>本页为演示内容。您可以在网站工厂中直接修改和替换。</p></div><a class="pro-v30-text-action" href="/contact/">联系与合作 ↗</a></div></div></section>
|
||||
<section class="pro-section pro-v30-about-services"><div class="pro-wrap"><div class="pro-section-head"><div><span class="pro-eyebrow">WHAT WE DO</span><h2>相关服务</h2></div><a class="pro-text-link" href="/services/">浏览全部 <span>↗</span></a></div><div class="pro-grid pro-grid-service">
|
||||
<a class="pro-card pro-card-service" href="/services/solution/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-service"><span class="pro-art-ring"></span><span class="pro-art-center">供应</span><span class="pro-art-label">SERVICE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>服务与解决方案</span><span>01</span></div>
|
||||
<h3>供应与交付方案</h3><p>灵活、可配置的供应交付服务。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></section>
|
||||
<section class="pro-section pro-v30-about-cases"><div class="pro-wrap"><div class="pro-section-head"><div><span class="pro-eyebrow">SELECTED WORK</span><h2>相关项目</h2></div><a class="pro-text-link" href="/cases/">浏览全部 <span>↗</span></a></div><div class="pro-grid pro-grid-case">
|
||||
<a class="pro-card pro-card-case" href="/cases/1/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-case"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CASE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>01</span></div>
|
||||
<h3>精选项目 · 01</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-case" href="/cases/2/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-case"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CASE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>02</span></div>
|
||||
<h3>精选项目 · 02</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-case" href="/cases/3/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-case"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CASE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>03</span></div>
|
||||
<h3>精选项目 · 03</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Portable SiteForge blocks, loaded with every theme (all styles scoped to sf-). */
|
||||
.sf-section{background:var(--sf-bg,transparent);color:var(--sf-fg,inherit);padding:var(--sf-pad,82px) 0;margin:0}.sf-section:nth-of-type(even){background:var(--sf-bg,#f4f6f9)}.sf-container{max-width:1200px;margin:auto;padding:0 32px}.sf-section h1,.sf-section h2,.sf-section h3,.sf-section p{color:inherit;line-height:1.3}.sf-section h1{font-size:clamp(38px,5vw,76px);font-weight:790;letter-spacing:-.052em}.sf-section h2{font-size:clamp(27px,3.2vw,44px);font-weight:760;letter-spacing:-.037em}.sf-kicker{display:block;font-size:12px;letter-spacing:.17em;text-transform:uppercase;font-weight:740;color:var(--accent,#3569d3);margin-bottom:18px}.sf-hero-grid,.sf-image-text{display:grid;grid-template-columns:1.08fr .92fr;gap:68px;align-items:center}.sf-hero-copy p{font-size:18px;line-height:1.75;max-width:520px;opacity:.75}.sf-hero-img,.sf-image-side{overflow:hidden;border-radius:9px;min-height:380px;background:linear-gradient(130deg,#d5e1ee,#eef3f9);display:grid;place-items:center}.sf-hero-img img,.sf-image-side img{width:100%;height:100%;max-height:580px;object-fit:cover}.sf-hero-abstract{font-size:110px;color:#a7bdd4}.sf-button{display:inline-flex;align-items:center;justify-content:center;gap:28px;background:var(--accent,#315ee2);color:white!important;padding:16px 26px;border-radius:6px;font-size:14px;text-decoration:none;font-weight:720;margin-top:24px}.sf-section-head{display:flex;align-items:end;justify-content:space-between;margin-bottom:32px;gap:24px}.sf-section-head h2{margin:0}.sf-section-head>a,.sf-link{color:var(--accent,#3569d3);font-weight:700;text-decoration:none}.sf-prose{font-size:16px;line-height:1.85;max-width:930px}.sf-prose p{line-height:1.85}.sf-features,.sf-collection,.sf-stats{display:grid;gap:20px}.sf-features{grid-template-columns:repeat(3,minmax(0,1fr))}.sf-feature{border:1px solid #e0e6eb;padding:32px 28px;border-radius:8px;background:#fff;color:#253247}.sf-feature>span{font-size:12px;font-weight:800;color:var(--accent,#346add)}.sf-feature h3{font-size:22px;margin:30px 0 8px}.sf-feature p{font-size:14px;line-height:1.7;color:#647181}.sf-stats{grid-template-columns:repeat(4,1fr);padding:40px 0;border-top:1px solid #b7c0cd66;border-bottom:1px solid #b7c0cd66}.sf-stats>div{display:flex;flex-direction:column;gap:8px}.sf-stats strong{font-size:clamp(28px,4vw,53px);letter-spacing:-.04em}.sf-stats span{font-size:12px;opacity:.65;letter-spacing:.04em}.sf-collection{grid-template-columns:repeat(var(--sf-cols,3),minmax(0,1fr))}.sf-item{display:block;text-decoration:none;color:#233044;background:#fff;border:1px solid #e8edf1;border-radius:8px;overflow:hidden;transition:transform .2s,box-shadow .2s}.sf-item:hover{transform:translateY(-4px);box-shadow:0 18px 38px #344d7018}.sf-item-img{height:210px;overflow:hidden;background:#e4eaf0}.sf-item-img img{height:100%;width:100%;object-fit:cover}.sf-placeholder{display:grid;place-items:center;width:100%;height:100%;min-height:190px;background:linear-gradient(130deg,#e2eaf2,#c9d7e6);font-size:72px;color:#8399b2;font-weight:800}.sf-large{min-height:360px}.sf-item-copy{padding:25px}.sf-item-copy small{text-transform:uppercase;letter-spacing:.13em;color:var(--accent,#376edc)}.sf-item-copy h3{font-size:22px;margin:12px 0 7px}.sf-item-copy p{font-size:14px;line-height:1.6;color:#7a8796;min-height:42px}.sf-item-copy span{font-size:12px;color:var(--accent,#376edc);font-weight:800}.sf-image-text>div>p{font-size:17px;line-height:1.8;max-width:520px;opacity:.7}.sf-reverse>div:first-child{order:2}.sf-gallery{display:grid;grid-template-columns:repeat(3,1fr);gap:15px}.sf-gallery img{width:100%;height:230px;object-fit:cover;border-radius:6px}.sf-faq{max-width:920px}.sf-faq details{border-bottom:1px solid #dce3eb;padding:22px 0}.sf-faq summary{font-size:17px;cursor:pointer;font-weight:700}.sf-faq p{color:#647181;line-height:1.75}.sf-cta-wrap{display:flex;justify-content:space-between;align-items:center;gap:24px}.sf-cta-wrap h2{margin:0}.sf-cta-wrap p{font-size:16px;opacity:.75}.sf-section a{cursor:pointer}.sf-muted{opacity:.65}
|
||||
@media(max-width:820px){.sf-container{padding:0 20px}.sf-section{padding:50px 0}.sf-hero-grid,.sf-image-text{grid-template-columns:1fr;gap:26px}.sf-reverse>div:first-child{order:initial}.sf-hero-img,.sf-image-side{min-height:250px}.sf-features,.sf-collection{grid-template-columns:repeat(2,minmax(0,1fr))}.sf-stats{grid-template-columns:repeat(2,1fr)}.sf-cta-wrap{align-items:flex-start;flex-direction:column}}
|
||||
@media(max-width:540px){.sf-features,.sf-collection,.sf-gallery{grid-template-columns:1fr}.sf-section-head{display:block}.sf-section-head>a{display:block;margin-top:13px}.sf-item-img{height:220px}.sf-hero-grid{gap:22px}.sf-hero-copy p{font-size:16px}.sf-stats{gap:28px}}
|
||||
|
||||
.sf-attributes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 32px;border-top:1px solid #e5eaf0}.sf-attribute{display:flex;justify-content:space-between;align-items:center;gap:18px;border-bottom:1px solid #e5eaf0;padding:19px 10px;font-size:14px}.sf-attribute span{color:#748497}.sf-attribute strong{font-weight:720;text-align:right}@media(max-width:640px){.sf-attributes{grid-template-columns:1fr}}
|
||||
|
||||
/* SiteForge responsive no-code grid container */
|
||||
.sf-layout-grid{display:grid;grid-template-columns:var(--sf-layout-cols,1fr 1fr);gap:var(--sf-layout-gap,24px);align-items:stretch}
|
||||
.sf-layout-column{min-width:0}.sf-layout-column>.sf-section{padding:0;margin:0 0 18px}.sf-layout-column .sf-container{width:100%;padding:0}
|
||||
.sf-layout-column>.sf-section:last-child{margin-bottom:0}
|
||||
@media(max-width:820px){.sf-layout-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}
|
||||
@media(max-width:540px){.sf-layout-grid{grid-template-columns:minmax(0,1fr)!important}}
|
||||
|
||||
/* V8.6: accessible, script-free commercial design blocks */
|
||||
.sf-slides{display:flex;gap:18px;overflow-x:auto;scroll-snap-type:x mandatory;padding:6px 0 18px;scrollbar-width:thin}
|
||||
.sf-slide{flex:0 0 min(78%,720px);min-width:220px;margin:0;scroll-snap-align:start}
|
||||
.sf-slide img{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:10px}
|
||||
.sf-slide figcaption{padding:10px 2px;opacity:.7;font-size:14px}
|
||||
.sf-video{aspect-ratio:16/9;max-width:1000px;background:#ebf0f5;border-radius:10px;overflow:hidden}
|
||||
.sf-video iframe{width:100%;height:100%;border:0}
|
||||
.sf-tab{padding:19px 0;border-bottom:1px solid #dde4ec;max-width:960px}
|
||||
.sf-tab summary{font-size:18px;font-weight:700;cursor:pointer}.sf-tab p{max-width:850px;line-height:1.8}
|
||||
.sf-prices{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px}
|
||||
.sf-price{padding:28px;background:#fff;border:1px solid #dee5ef;border-radius:10px;color:#29394b}
|
||||
.sf-price strong{display:block;font-size:27px;margin:14px 0}.sf-price li{margin:11px 0}.sf-price ul{padding-left:19px}
|
||||
.sf-map-address{font-style:normal;font-size:18px;white-space:pre-line;line-height:1.8}
|
||||
@media (prefers-reduced-motion:reduce){.sf-slides{scroll-behavior:auto}.sf-item{transition:none}}
|
||||
@media(max-width:540px){.sf-slide{flex-basis:92%}}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* SiteForge commercial theme UX layer: accessibility, layout rhythm, media, responsive navigation. */
|
||||
:root{--sf-shadow:0 20px 55px rgba(17,33,55,.08);--sf-shadow-hover:0 26px 65px rgba(17,33,55,.15)}
|
||||
html{scroll-padding-top:104px}body{font-feature-settings:"kern";text-rendering:optimizeLegibility}
|
||||
a,button,input,select,textarea{-webkit-tap-highlight-color:transparent}
|
||||
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid var(--pro-accent);outline-offset:4px}
|
||||
.pro-wrap{width:100%;max-width:min(var(--pro-wide),100%)}.pro-header{isolation:isolate;box-shadow:0 1px 0 rgba(15,35,60,.035)}.pro-head-inner{gap:clamp(15px,2vw,29px)}.pro-logo{min-width:0;flex-shrink:0;gap:12px}.pro-brand-logo{max-width:155px;max-height:46px;width:auto;height:auto;object-fit:contain}.pro-logo-name{overflow-wrap:anywhere}.pro-navigation{gap:clamp(11px,1.6vw,24px)}.pro-header-cta{white-space:nowrap}
|
||||
.pro-mobile-toggle,.pro-catalog-toggle,.pro-nav-disclose{border:0;background:transparent;color:inherit;line-height:1.2}
|
||||
.pro-mobile-toggle{min-height:44px;min-width:44px;border:1px solid currentColor;border-radius:6px;font-size:19px}.pro-nav-group{position:relative;display:flex;align-items:center;gap:2px}
|
||||
.pro-nav-disclose{padding:9px;font-size:13px}.pro-nav-children{position:absolute;top:100%;left:0;min-width:215px;max-width:330px;display:grid;gap:2px;padding:13px;box-shadow:var(--sf-shadow);border-radius:10px;z-index:45;background:var(--pro-background);border:1px solid var(--pro-border);color:var(--pro-text)}
|
||||
.pro-nav-children[hidden],.pro-mega[hidden]{display:none!important}.pro-nav-children a{display:block;font-size:13px;white-space:normal;padding:10px;border-radius:5px}.pro-nav-children a:hover{background:var(--pro-surface)}
|
||||
.pro-catalog-toggle{padding:11px 3px;white-space:nowrap;font-size:12px;font-weight:700}.pro-mega{position:absolute;top:100%;left:50%;transform:translateX(-50%);width:min(100%,var(--pro-wide));box-shadow:var(--sf-shadow);z-index:41;display:grid;grid-template-columns:215px 1fr;background:var(--pro-background);color:var(--pro-text);border:1px solid var(--pro-border);border-radius:0 0 15px 15px;padding:29px;align-items:start}.pro-mega>span{font-size:12px;letter-spacing:.06em}.pro-mega>div{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px 22px;overflow:visible}.pro-mega>div a{white-space:normal;padding:10px 5px;border-bottom:1px solid var(--pro-border)}
|
||||
.pro-home-hero{overflow:hidden}.pro-home-hero .pro-home-grid{min-height:660px;gap:5%}.pro-home-hero .pro-hero-copy{position:relative;z-index:2}.pro-home-hero .pro-hero-copy h1{font-size:clamp(46px,5.7vw,92px);line-height:1.06;letter-spacing:-.064em;max-width:15ch}.pro-home-hero .pro-hero-copy h1 em{font-style:normal;display:block}.pro-home-hero .pro-hero-copy p{font-size:clamp(15px,1.3vw,19px);line-height:1.75;max-width:44ch}.pro-home-hero .pro-hero-art{min-width:0;min-height:425px;position:relative;isolation:isolate}.pro-home-hero .pro-hero-art>img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}.pro-hero-art:has(.pro-abstract-car),.pro-hero-art:has(.pro-technical),.pro-hero-art:has(.pro-corporate-shape){background-position:center;background-size:cover;background-repeat:no-repeat}.pro-abstract-car,.pro-technical,.pro-corporate-shape{position:relative;z-index:1;pointer-events:none}
|
||||
.pro-hero-actions{display:flex;flex-wrap:wrap;align-items:center;gap:15px}.pro-button,.pro-header-cta,.pro-outline-button{min-height:48px;transition:transform .24s ease,box-shadow .24s ease,opacity .24s ease}.pro-button:hover,.pro-header-cta:hover,.pro-outline-button:hover{transform:translateY(-2px);box-shadow:0 10px 22px rgba(0,0,0,.10)}
|
||||
.pro-section{padding-top:clamp(76px,7vw,112px)!important;padding-bottom:clamp(76px,7vw,112px)!important}.pro-section-head{gap:16px}.pro-section-head h2{font-size:clamp(32px,3.5vw,53px);max-width:22ch}.pro-section-head .pro-text-link{align-self:end}
|
||||
.pro-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}.pro-card{min-width:0;display:flex;flex-direction:column;transition:transform .28s ease,box-shadow .28s ease;border:1px solid var(--pro-border);background:var(--pro-background);overflow:hidden}.pro-card:hover{transform:translateY(-6px);box-shadow:var(--sf-shadow-hover)}.pro-card-cover{aspect-ratio:1.42;overflow:hidden;background:var(--pro-surface);position:relative}.pro-card-cover .pro-photo{width:100%;height:100%;object-fit:cover;transition:transform .45s ease}.pro-card:hover .pro-photo{transform:scale(1.045)}.pro-card-body{display:flex;flex-direction:column;flex:1}.pro-card-body h3{font-size:clamp(19px,1.6vw,24px);line-height:1.26}.pro-card-body p{font-size:14px}.pro-card-link{margin-top:auto;padding-top:14px;font-size:13px;font-weight:700}
|
||||
.pro-art{min-height:100%;width:100%;position:relative;overflow:hidden;display:grid;place-items:center;isolation:isolate}.pro-art-ring{width:60%;aspect-ratio:1;border:1px solid currentColor;opacity:.13;border-radius:50%;transform:rotate(20deg);box-shadow:0 0 0 23px #ffffff10,0 0 0 57px #ffffff07}.pro-art-center{font-size:clamp(38px,5vw,80px);position:absolute;font-weight:800;letter-spacing:-.065em;opacity:.65}.pro-art-label{position:absolute;bottom:19px;left:22px;font-size:10px;letter-spacing:.22em;opacity:.7}
|
||||
.pro-breadcrumb{display:flex;gap:11px;flex-wrap:wrap;font-size:12px;align-items:center}.pro-breadcrumb a{text-decoration:underline;text-underline-offset:4px}.pro-product-hero{align-items:start;gap:clamp(28px,5vw,80px)}.pro-product-media{aspect-ratio:1.12;background:var(--pro-surface);overflow:hidden}.pro-product-media>img,.pro-product-media>.pro-art{width:100%;height:100%;object-fit:contain}.pro-product-summary{padding:clamp(12px,3vw,50px) 0}.pro-product-summary h1{font-size:clamp(36px,4.3vw,72px)}.pro-detail-layout{border-top:1px solid var(--pro-border);padding-top:50px}.pro-specs>div{display:grid;grid-template-columns:minmax(100px,40%) 1fr;gap:10px;align-items:baseline;padding:15px 0;border-bottom:1px solid var(--pro-border)}.pro-specs strong{overflow-wrap:anywhere}.pro-gallery-thumbs button{flex-basis:96px;height:76px}.pro-gallery-lightbox{backdrop-filter:blur(6px)}
|
||||
.pro-filter-panel{display:flex;align-items:end;flex-wrap:wrap;gap:16px;background:var(--pro-surface);border:1px solid var(--pro-border);padding:20px;margin-bottom:25px;border-radius:var(--pro-radius)}.pro-filter-panel label{display:grid;gap:7px;min-width:180px;flex:1}.pro-filter-panel input,.pro-filter-panel select{width:100%;background:var(--pro-background);border:1px solid var(--pro-border);padding:12px 14px;color:var(--pro-text);border-radius:6px}.pro-filter-count{white-space:nowrap;font-size:13px;padding:12px 0}.pro-card[hidden]{display:none!important}.pro-filter-empty{padding:38px;border:1px dashed var(--pro-border);text-align:center;grid-column:1/-1}
|
||||
.pro-inquiry input,.pro-inquiry textarea{min-height:46px}.pro-inquiry textarea{min-height:150px}.pro-inquiry-wrap{gap:clamp(32px,7vw,110px)}.pro-inquiry .pro-button{border:0;justify-self:start}.pro-footer-links a{padding-block:5px;display:inline-block}
|
||||
.pro-end-cta{overflow:hidden}.pro-end-cta h2{font-size:clamp(30px,4vw,64px)}
|
||||
.pro-rich{line-height:1.85}.pro-rich img{border-radius:var(--pro-radius);margin-block:24px}.pro-rich table{width:100%;display:block;overflow:auto;border-collapse:collapse}.pro-rich td,.pro-rich th{border:1px solid var(--pro-border);padding:10px 14px}.pro-rich a{text-decoration:underline;text-underline-offset:3px}
|
||||
@media(min-width:1025px){.pro-navigation>.pro-nav-group:hover .pro-nav-children:not([hidden]){visibility:visible}}
|
||||
@media(max-width:1150px){.pro-head-inner{gap:15px}.pro-navigation{gap:12px}.pro-logo-name{font-size:15px}.pro-header-cta{padding:13px 16px}}
|
||||
@media(max-width:1024px){.pro-head-inner{min-height:72px;flex-wrap:wrap}.pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto}.pro-navigation{display:none;width:100%;flex-basis:100%;flex-direction:column;align-items:stretch;gap:0;order:5;margin:0;padding:8px 0 20px;border-top:1px solid var(--pro-border)}.pro-navigation.is-open{display:flex}.pro-navigation>a,.pro-nav-group>a{padding:14px 8px;font-size:15px}.pro-nav-group{display:flex;flex-wrap:wrap;justify-content:space-between}.pro-nav-children{position:static;flex-basis:100%;max-width:none;width:100%;box-shadow:none;border-radius:0;border-left:2px solid var(--pro-accent);margin:0 0 12px 8px}.pro-nav-disclose{min-width:44px;min-height:44px}.pro-catalog-toggle{margin-left:auto}.pro-mega{top:100%;display:block;padding:20px}.pro-mega>div{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:12px}.pro-home-hero .pro-home-grid{min-height:560px}.pro-home-hero .pro-hero-art{min-height:325px}.pro-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
@media(max-width:700px){.pro-wrap{padding-left:20px;padding-right:20px}.pro-utility{display:none}.pro-head-inner{min-height:66px}.pro-logo-name{max-width:140px;font-size:14px}.pro-brand-logo{max-width:125px}.pro-header-cta{display:none}.pro-catalog-toggle{font-size:11px;padding-inline:7px;margin-left:0}.pro-home-hero .pro-home-grid{display:flex;flex-direction:column;gap:36px;padding-top:48px;padding-bottom:48px}.pro-home-hero .pro-hero-copy h1{font-size:clamp(43px,10vw,68px)}.pro-home-hero .pro-hero-copy p{font-size:15px}.pro-home-hero .pro-hero-art{width:100%;min-height:290px}.pro-home-hero .pro-hero-bottom{display:none}.pro-section{padding-top:68px!important;padding-bottom:68px!important}.pro-section-head{align-items:flex-start}.pro-grid{grid-template-columns:1fr;gap:18px}.pro-card-cover{aspect-ratio:1.45}.pro-product-hero,.pro-detail-layout,.pro-info-grid,.pro-inquiry-wrap{display:grid;grid-template-columns:1fr;gap:30px}.pro-product-summary{padding:0}.pro-specs>div{grid-template-columns:1fr 1fr}.pro-mega{top:100%;max-height:65vh;overflow-y:auto}.pro-mega>div{grid-template-columns:1fr}.pro-filter-panel{display:grid;grid-template-columns:1fr;gap:12px}.pro-filter-panel label{min-width:0}.pro-footer-top{display:grid;grid-template-columns:1fr;gap:27px}.pro-footer-bottom{gap:12px;flex-wrap:wrap}}
|
||||
@media(max-width:410px){.pro-logo-name{max-width:115px}.pro-logo-symbol{font-size:28px}.pro-catalog-toggle{font-size:10px}.pro-mobile-toggle{min-width:42px}}
|
||||
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation-duration:.01ms!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}
|
||||
|
||||
/* Accessible static catalogue pagination for large inventories. */
|
||||
.pro-pages-nav{display:flex;gap:1rem;justify-content:center;align-items:center;margin:2rem 0;flex-wrap:wrap}
|
||||
.pro-pages-nav a{padding:.7rem 1.1rem;border:1px solid currentColor;border-radius:.4rem;text-decoration:none}
|
||||
.pro-pages-nav a:focus-visible{outline:3px solid var(--accent,#2563eb);outline-offset:3px}
|
||||
.pro-pages-nav span{font-size:.875rem}
|
||||
|
||||
/* P6 international sites: small, keyboard-operable language switch. */
|
||||
.sf-language-switch{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;margin:8px 12px;padding:4px;border:1px solid rgba(120,130,145,.22);border-radius:10px;background:rgba(255,255,255,.08)}
|
||||
.sf-language-switch a{font:inherit;font-size:12px;line-height:1.5;padding:4px 7px;border-radius:6px;color:inherit;text-decoration:none}
|
||||
.sf-language-switch a[aria-current="page"]{font-weight:700;text-decoration:underline;text-underline-offset:3px}
|
||||
.sf-language-switch a:focus-visible{outline:2px solid #2563eb;outline-offset:3px}
|
||||
@media(max-width:640px){.sf-language-switch{margin:6px 0;width:auto}}
|
||||
|
||||
/* V8.21.3 — Four-theme unified mobile off-canvas navigation & optional tab bar.
|
||||
Desktop remains unchanged. Phone / tablet use one accessible drawer. */
|
||||
.sf-mobile-tabs{display:none!important}
|
||||
.sf-drawer-header,.sf-mobile-drawer-backdrop,.sf-drawer-extra{display:none}
|
||||
@media(max-width:1024px){
|
||||
.pro-head-inner{flex-wrap:nowrap!important;gap:12px!important}
|
||||
.pro-mobile-toggle{display:inline-flex;align-items:center;justify-content:center;margin-left:auto!important;flex:0 0 44px;min-width:44px;min-height:44px;position:relative;z-index:3}
|
||||
.pro-catalog-toggle{display:none!important}
|
||||
.pro-navigation,.pro-navigation.is-open{display:flex!important;position:fixed!important;inset:0 0 0 auto!important;width:min(88vw,360px)!important;height:100dvh!important;height:100vh;max-height:100dvh!important;flex:unset!important;flex-direction:column!important;align-items:stretch!important;order:unset!important;margin:0!important;padding:0 0 calc(20px + env(safe-area-inset-bottom,0px))!important;overflow-x:hidden;overflow-y:auto!important;background:var(--pro-background,#fff)!important;color:var(--pro-text,#19283c)!important;border:0!important;border-left:1px solid var(--pro-border,#ddd)!important;box-shadow:-15px 0 45px #0002!important;z-index:1201!important;transform:translateX(105%);visibility:hidden;pointer-events:none;transition:transform .24s ease,visibility .24s ease!important;overscroll-behavior:contain}
|
||||
.pro-navigation.is-open{transform:translateX(0);visibility:visible;pointer-events:auto}
|
||||
.sf-drawer-header{display:flex!important;align-items:center;justify-content:space-between;gap:12px;padding:20px 18px;border-bottom:1px solid var(--pro-border,#dde3e9);font-size:17px;font-weight:750;position:sticky;top:0;background:var(--pro-background,#fff);z-index:2}
|
||||
.sf-drawer-close{min-width:44px;min-height:44px;border:1px solid var(--pro-border,#dde3e9);color:inherit;background:transparent;border-radius:10px;font-size:24px;cursor:pointer}
|
||||
.sf-mobile-drawer-backdrop{display:block;position:fixed;inset:0;z-index:1200;background:#0b1b31a3;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .24s ease,visibility .24s ease}
|
||||
.sf-mobile-drawer-backdrop.is-open{opacity:1;visibility:visible;pointer-events:auto}
|
||||
body.sf-drawer-open{overflow:hidden!important}
|
||||
.pro-navigation>a,.pro-navigation .pro-nav-group>a,.pro-navigation .sf-drawer-extra>a{display:flex;align-items:center;min-height:50px;padding:13px 20px!important;font-size:15px!important;color:inherit!important;white-space:normal;border-bottom:1px solid var(--pro-border,#dde3e9)!important;text-decoration:none!important}
|
||||
.pro-navigation .pro-nav-group{display:flex;position:relative;flex-wrap:wrap!important;width:100%;gap:0;align-items:center;justify-content:space-between}
|
||||
.pro-navigation .pro-nav-group>a{flex:1;min-width:0}
|
||||
.pro-navigation .pro-nav-disclose{margin-right:12px;min-width:44px;min-height:44px;border-radius:9px;background:var(--pro-surface,#f4f6f8)}
|
||||
.pro-navigation .pro-nav-children{position:static!important;width:100%!important;max-width:100%!important;box-shadow:none!important;border-left:3px solid var(--pro-accent,#e77730);border-radius:0!important;margin:0 0 8px 14px!important;flex-basis:calc(100% - 14px)!important;background:var(--pro-surface,#f6f8fa)}
|
||||
.pro-navigation .pro-nav-children a{padding:12px 16px!important;font-size:14px!important;color:inherit!important;min-height:44px}
|
||||
.sf-drawer-extra{display:block;padding:12px 0 5px}.sf-drawer-extra strong{display:block;font-size:12px;letter-spacing:.06em;padding:12px 20px;opacity:.65}.sf-drawer-extra a{font-size:14px!important}
|
||||
.sf-drawer-close:focus-visible,.pro-mobile-toggle:focus-visible,.pro-nav-disclose:focus-visible,.sf-mobile-tab:focus-visible{outline:3px solid var(--pro-accent,#ff8200);outline-offset:2px}
|
||||
}
|
||||
@media(max-width:767px){
|
||||
.pro-header-cta{display:none!important}
|
||||
.pro-head-inner .pro-logo{min-width:0;max-width:calc(100% - 65px)}
|
||||
.pro-logo-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pro-home-hero .pro-home-grid{min-height:0!important}
|
||||
.pro-home-hero .pro-hero-art{min-height:230px}
|
||||
.pro-card,.pro-card-cover,.pro-product-media{max-width:100%;min-width:0}
|
||||
.pro-rich img,.pro-content img,.sf-canvas img{max-width:100%;height:auto}
|
||||
.pro-rich table{max-width:100%;overflow:auto}
|
||||
.pro-grid,.pro-footer-top{min-width:0}
|
||||
.sf-mobile-tabs{display:grid!important;position:fixed;z-index:1100;bottom:0;left:0;right:0;grid-template-columns:repeat(var(--sf-mobile-tab-count,4),minmax(0,1fr));align-items:stretch;padding:5px 6px calc(5px + env(safe-area-inset-bottom,0px));min-height:63px;background:var(--pro-background,#fff);color:var(--pro-text,#263446);border-top:1px solid var(--pro-border,#dfe4eb);box-shadow:0 -6px 24px #13223914}
|
||||
.sf-mobile-tab{appearance:none;border:0!important;background:transparent!important;color:inherit!important;text-decoration:none!important;padding:6px 2px;min-height:50px;min-width:0;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:2px;border-radius:9px;cursor:pointer;font-family:inherit}
|
||||
.sf-tab-icon{font-size:23px;line-height:1.15;font-weight:500}
|
||||
.sf-tab-label{font-size:11px;font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.sf-mobile-tab[aria-current=page]{color:var(--pro-accent,#e47730)!important;background:var(--pro-surface,#f5f6f8)!important}
|
||||
body:has(> .sf-mobile-tabs){padding-bottom:calc(76px + env(safe-area-inset-bottom,0px))!important}
|
||||
}
|
||||
@media(min-width:768px){.sf-mobile-tabs{display:none!important}}
|
||||
@media(prefers-reduced-motion:reduce){.pro-navigation,.sf-mobile-drawer-backdrop{transition:none!important}}
|
||||
@@ -0,0 +1 @@
|
||||
:root{--accent:#315f92;--sf-font:system-ui,-apple-system,sans-serif;--sf-radius:12px;--sf-space:72px;--sf-width:1200px;--sf-heading-scale:1.0;}body{font-family:var(--sf-font)} .sf-canvas .sf-section,.sf-canvas .section{padding-block:var(--sf-space)}.container,.sf-container{max-width:var(--sf-width)}.btn,.primary,.request,.button,.nav-btn{background-color:#315f92;border-radius:12px}body .sf-canvas h1{font-size:clamp(2.5rem,5vw,calc(4.8rem * var(--sf-heading-scale)))}body .pro-utility[hidden]{display:none!important}@media(max-width:1023px){.sf-canvas .sf-section{padding-block:64px}}@media(max-width:767px){.sf-canvas .sf-section{padding-block:40px}.pro-wrap,.sf-container{padding-left:20px;padding-right:20px}body .pro-utility{display:none!important}}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SiteForge trusted global shell. Site themes remain visually independent. */
|
||||
:root{--sf-shell:#12243d;--sf-shell-accent:#dcae71;--sf-shell-soft:#edf2f8;--sf-shell-fg:#173049}
|
||||
html{scroll-behavior:smooth}body{overflow-x:hidden}body>.header,body>.utility,body>.top,body>header,body>footer{display:none!important}
|
||||
.sf-theme-industrial{--sf-shell:#114e79;--sf-shell-accent:#92d5ed;--sf-shell-soft:#ebf6fa;--sf-shell-fg:#0c4267}
|
||||
.sf-theme-corporate{--sf-shell:#232d62;--sf-shell-accent:#b8bdff;--sf-shell-soft:#f3f3fa;--sf-shell-fg:#333468}
|
||||
.sf-theme-minimal{--sf-shell:#1e1e1c;--sf-shell-accent:#ded6c5;--sf-shell-soft:#f6f4ef;--sf-shell-fg:#252520}
|
||||
.sf-site-head{position:relative;z-index:25;background:var(--sf-shell);color:#fff;font-family:system-ui,-apple-system,sans-serif}
|
||||
.sf-head-inner{max-width:1240px;margin:0 auto;min-height:78px;padding:0 28px;display:flex;align-items:center;gap:28px}
|
||||
.sf-logo{display:flex;align-items:center;gap:11px;font-size:18px;text-decoration:none!important;color:#fff!important;white-space:nowrap}
|
||||
.sf-logo-mark{width:36px;height:36px;display:grid;place-items:center;background:var(--sf-shell-accent);color:var(--sf-shell);font-size:22px;font-weight:900;border-radius:9px}
|
||||
.sf-primary-links{display:flex;gap:23px;margin-left:auto;align-items:center}
|
||||
.sf-primary-links a,.sf-mega a{color:#fff!important;text-decoration:none!important;font-size:13px;font-weight:650;white-space:nowrap}
|
||||
.sf-primary-links a:hover{color:var(--sf-shell-accent)!important}
|
||||
.sf-head-cta{font-size:13px;font-weight:750;text-decoration:none;background:var(--sf-shell-accent);color:var(--sf-shell)!important;padding:13px 18px;border-radius:4px;white-space:nowrap}
|
||||
.sf-menu-toggle{display:none;margin-left:auto;color:white;border:1px solid #ffffff55;background:none;border-radius:4px;padding:8px 12px;font-size:19px;cursor:pointer}
|
||||
.sf-mega{display:flex;gap:18px;max-width:1240px;margin:auto;padding:0 28px 9px;overflow-x:auto}
|
||||
.sf-mega a{display:block;min-width:120px;border-top:1px solid #ffffff2e;padding:12px 8px}.sf-mega small{display:block;color:#ffffff99;max-width:160px;overflow:hidden;text-overflow:ellipsis;font-size:10px;margin-top:6px}
|
||||
.sf-site-foot{background:var(--sf-shell);color:white!important;font:14px/1.7 system-ui,-apple-system,sans-serif;padding:72px 28px 26px}
|
||||
.sf-foot-grid{max-width:1200px;margin:auto;display:grid;grid-template-columns:2fr 1fr 1fr;gap:70px}.sf-foot-grid h2{font-size:25px;color:white;margin:0 0 17px}.sf-foot-grid h4{font-size:13px;color:var(--sf-shell-accent);letter-spacing:.07em;margin:0 0 17px}.sf-foot-grid p{color:#ffffffae;max-width:380px}.sf-foot-grid a{display:block;width:fit-content;color:#fff!important;text-decoration:none;margin-bottom:8px}.sf-foot-lower{border-top:1px solid #ffffff33;max-width:1200px;margin:45px auto 0;padding-top:20px;display:flex;justify-content:space-between;color:#ffffff9e;font-size:12px}
|
||||
.sf-filters{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin:0 0 30px}.sf-filters input,.sf-filters select{padding:11px 13px;border-radius:6px;border:1px solid #cfd9e4;background:#fff;color:#263a51;max-width:280px}.sf-filter-empty{padding:22px;color:#778598}
|
||||
.sf-search-page{min-height:76vh;max-width:1060px;padding:90px 24px;margin:auto;font-family:system-ui,sans-serif;color:var(--sf-shell-fg)}.sf-search-page h1{font-size:clamp(35px,5vw,62px);margin:20px 0}.sf-search-page>label{display:block;margin:18px 0 8px}.sf-search-page input{display:block;width:100%;padding:17px;font-size:18px;border:1px solid #b7c6d7;border-radius:8px}.sf-search-page #sf-search-count{margin:22px 0;color:#607589}.sf-search-result{padding:22px 0;border-bottom:1px solid #dae1ea}.sf-search-result a{color:var(--sf-shell-fg);font-size:21px;font-weight:750;text-decoration:none}.sf-search-result small{display:block;color:#7992a7}.sf-search-result p{margin:7px 0;color:#657487}
|
||||
@media(max-width:1050px){.sf-head-inner{gap:13px}.sf-primary-links{gap:12px}.sf-primary-links a{font-size:12px}.sf-head-cta{padding:10px}}
|
||||
@media(max-width:850px){.sf-head-inner{min-height:68px}.sf-menu-toggle{display:block}.sf-head-cta{display:none}.sf-primary-links{display:none;position:absolute;top:68px;left:0;right:0;margin:0;background:var(--sf-shell);padding:20px 28px;flex-direction:column;align-items:stretch;box-shadow:0 20px 24px #0002}.sf-primary-links.is-open{display:flex}.sf-mega{display:none}.sf-foot-grid{grid-template-columns:1fr 1fr;gap:35px}}
|
||||
@media(max-width:560px){.sf-foot-grid{grid-template-columns:1fr}.sf-foot-lower{display:block}.sf-site-foot{padding-top:48px}.sf-head-inner{padding:0 16px}}
|
||||
|
||||
.sf-page-intro{padding:80px 28px;background:var(--sf-shell-soft);color:var(--sf-shell-fg);font-family:system-ui,sans-serif}.sf-page-intro>div{max-width:1200px;margin:auto}.sf-page-intro p{font-size:11px;font-weight:850;letter-spacing:.19em;text-transform:uppercase;color:var(--sf-shell-fg);opacity:.58}.sf-page-intro h1{font-size:clamp(38px,5vw,67px);letter-spacing:-.04em;line-height:1.08;margin:13px 0}.sf-page-intro span{font-size:16px;opacity:.72}
|
||||
.sf-item[hidden]{display:none!important}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,92 @@
|
||||
:root{--pro-text:#1c2a3b;--pro-muted:#718094;--pro-border:#e2e8ef;--pro-background:#fff;--pro-surface:#f2f5f9;--pro-accent:#bb8d50;--pro-hero:#0c1b2e;--pro-radius:5px;--pro-headings:'Arial Narrow',Arial,sans-serif;--pro-wide:1320px}
|
||||
.pro-header{background:#0e1e30;color:#fff;border-color:#26374b}.pro-logo-symbol{color:#e0ae6c}.pro-logo-symbol span{color:#fff}.pro-logo-name small{color:#b3bfcf}.pro-header-cta{background:#c59b63;color:#0f1a26!important}.pro-navigation a:hover{color:#dfb97d}.pro-mega{background:#13263b;border-color:#2a4058;color:#c2d0de}.pro-mega>span{color:#d5aa72}.pro-mobile-toggle{color:#fff}
|
||||
.pro-auto-hero{background:radial-gradient(circle at 68% 50%,#243b54 0%,#112539 34%,#081728 100%);color:#fff}.pro-auto-hero .pro-eyebrow{color:#d5a366}.pro-auto-hero .pro-hero-copy h1 em{color:#d2a16b}.pro-auto-hero .pro-hero-copy p{color:#b9c7d6}.pro-auto-hero .pro-button{color:#0d1b2d!important;background:#d8ad72}.pro-link-light{color:#d8e4ec;font-size:13px;font-weight:750}.pro-auto-hero .pro-hero-art{background:linear-gradient(160deg,#243e59,#0f2033);border:1px solid #ffffff25;box-shadow:0 50px 80px #0005;border-radius:3px;overflow:visible}.pro-abstract-car{position:absolute;inset:0;overflow:hidden;background:radial-gradient(ellipse at 50% 80%,#45648599,transparent 63%)}.pro-abstract-car:before{content:'';position:absolute;left:10%;right:9%;bottom:29%;height:29%;border:3px solid #d3dfefad;border-radius:53% 49% 10% 12%;transform:skewX(-9deg);box-shadow:0 23px 15px #05121f88,0 0 0 3px #182c41 inset}.pro-abstract-car:after{content:'';position:absolute;left:23%;right:23%;bottom:56%;height:17%;border:3px solid #d3dfef72;border-radius:85% 85% 0 0;transform:skewX(-20deg)}.pro-abstract-car>span{position:absolute;top:26px;right:22px;font-size:10px;letter-spacing:.2em;color:#ccd9e5}.auto-wheel{position:absolute;bottom:18%;width:16%;aspect-ratio:1;border-radius:50%;border:11px solid #6f8499;background:#111e2c;box-shadow:0 0 0 4px #111a25}.auto-wheel.a{left:19%}.auto-wheel.b{right:17%}.pro-hero-art>b{position:absolute;bottom:-31px;right:0;font-size:11px;letter-spacing:.2em;color:#bdcde0}.pro-featured{background:#f4f6f8}.pro-featured .pro-card{border:0;box-shadow:0 7px 30px #0f2e4710}.pro-featured .pro-card-cover{background:#d9e2eb}.pro-featured .pro-art{background:linear-gradient(140deg,#dce5ed,#a9baca)}.pro-featured .pro-art-ring{border-color:#eef3f8bb}.pro-statement{background:#12243b;color:#fff}.pro-statement .pro-eyebrow{color:#d3a975}.pro-statement p{color:#aebfd3}.pro-statement h2{font-size:clamp(42px,5vw,70px);max-width:700px}.pro-statement .pro-text-link{color:#dfba83}.pro-brand-section{background:white}.pro-brand-pills a{min-width:150px;text-align:center;background:#f5f7fa}.pro-inner-hero{background:linear-gradient(120deg,#101e31,#273b51);color:#fff}.pro-inner-hero .pro-eyebrow{color:#d0a46b}.pro-inner-hero p,.pro-inner-hero .pro-breadcrumb{color:#c9d1df}.pro-inner-hero .pro-breadcrumb strong{color:#fff}.pro-article-hero{background:#14243a;color:#fff}.pro-article-hero p{color:#bed0df}.pro-footer{background:#071421}.pro-footer-bottom{color:#98aebf}.pro-end-cta{background:#1a324b}.pro-end-cta a{background:#d4a56d;color:#132339!important}.pro-404 h1{color:#b8905c}
|
||||
|
||||
/* SiteForge V5 commercial signature */
|
||||
.pro-auto-hero .pro-hero-art{background-image:url("./visual/hero.svg")!important;background-position:center;background-size:cover;}
|
||||
|
||||
.pro-auto-hero{background:linear-gradient(117deg,#081729 0%,#102a43 100%)}.pro-auto-hero .pro-hero-art{border:1px solid #52667a55;border-radius:3px;box-shadow:0 30px 90px #0004;transform:translateX(0)}.pro-auto-hero .pro-hero-art .pro-abstract-car{opacity:0}.pro-hero-bottom{border-top:1px solid #ffffff1c}.pro-brand-pills a{transition:background .2s}.pro-brand-pills a:hover{background:#d6a974;color:#10233a}.pro-card{border-color:#e0e6ee;border-radius:5px}.pro-card-cover{background:linear-gradient(135deg,#edf1f6,#dbe4eb)}.pro-art{background:linear-gradient(130deg,#e0e8ee,#c4d0dc);color:#37536b}.pro-featured{background:#f8fafc}.pro-statement{background:#0d2134;color:white}.pro-statement p{color:#c6d0dd}.pro-statement .pro-text-link{color:#f0b870}.pro-section-head h2{font-weight:800}.pro-footer{background:#08182a;color:#e5e9ed}
|
||||
|
||||
/* V8.29 presentation refinement: automotive. Preserve per-theme design language. */
|
||||
body.pro-automotive{--pro-studio-ring:rgba(209,161,99,.28);--pro-focus-color:#d1a163;--pro-interact-radius:6px}
|
||||
body.pro-automotive .pro-wrap{max-width:100%;min-width:0}
|
||||
body.pro-automotive .pro-hero-copy,body.pro-automotive .pro-card-body,body.pro-automotive .pro-card-copy{min-width:0;overflow-wrap:anywhere}
|
||||
body.pro-automotive .pro-hero-copy h1{font-size:clamp(2.45rem,5.15vw,5.6rem);line-height:1.08;max-width:18ch;text-wrap:balance}
|
||||
body.pro-automotive .pro-section-head h2{font-size:clamp(1.85rem,3vw,3.25rem);text-wrap:balance}
|
||||
body.pro-automotive .pro-card,body.pro-automotive .pro-product-card{transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease}
|
||||
@media(hover:hover){body.pro-automotive .pro-card:hover,body.pro-automotive .pro-product-card:hover{transform:translateY(-3px);box-shadow:0 14px 36px var(--pro-studio-ring)}}
|
||||
body.pro-automotive :where(button,a,input,select,textarea):focus-visible{outline:3px solid var(--pro-focus-color);outline-offset:3px}
|
||||
body.pro-automotive img{max-width:100%;height:auto}
|
||||
body.pro-automotive .pro-card-cover img,body.pro-automotive .pro-gallery-main img{object-fit:cover;max-height:100%}
|
||||
body.pro-automotive .pro-mobile-toggle,body.pro-automotive .pro-nav-disclose{min-height:44px;min-width:44px}
|
||||
@media(max-width:860px){body.pro-automotive .pro-home-grid,body.pro-automotive .pro-product-layout,body.pro-automotive .pro-inquiry-wrap{min-width:0}body.pro-automotive .pro-card-grid,body.pro-automotive .pro-grid{gap:clamp(12px,3vw,23px)}}
|
||||
@media(max-width:560px){body.pro-automotive .pro-wrap{padding-left:max(16px,env(safe-area-inset-left));padding-right:max(16px,env(safe-area-inset-right))}body.pro-automotive .pro-hero-copy h1{font-size:clamp(2rem,9vw,3.2rem)}body.pro-automotive .pro-section-head h2{font-size:clamp(1.75rem,7vw,2.5rem)}body.pro-automotive .pro-section{padding-block:clamp(34px,9vw,60px)}body.pro-automotive .pro-card-cover{height:auto;aspect-ratio:4/3}body.pro-automotive .pro-card-cover img{width:100%;height:100%;object-fit:cover}}
|
||||
@media(prefers-reduced-motion:reduce){body.pro-automotive .pro-card,body.pro-automotive .pro-product-card{transition:none!important;transform:none!important}}
|
||||
|
||||
|
||||
/* V8.30 REAL PAGE COMPOSITION. Built-in theme version 1.3.0 only.
|
||||
Content-driven sections do not invent client names, certifications or KPIs. */
|
||||
.pro-v30-page{--v30-ink:var(--pro-text);--v30-secondary:var(--pro-muted);--v30-line:var(--pro-border);--v30-panel:var(--pro-surface);--v30-max:1180px;color:var(--v30-ink);overflow:clip}
|
||||
.pro-v30-page .pro-wrap{max-width:var(--v30-max);min-width:0}
|
||||
.pro-v30-page h1,.pro-v30-page h2{overflow-wrap:anywhere;text-wrap:balance;line-height:1.11}
|
||||
.pro-v30-page h1{font-size:clamp(2.55rem,5.2vw,5.6rem);margin:18px 0 22px;letter-spacing:-.035em}
|
||||
.pro-v30-page h2{font-size:clamp(1.8rem,3.3vw,3rem);margin:16px 0 26px;letter-spacing:-.02em}
|
||||
.pro-v30-page p{line-height:1.75;overflow-wrap:anywhere}
|
||||
.pro-v30-page img{max-width:100%}
|
||||
.pro-v30-intro-layout{display:grid;grid-template-columns:minmax(0,1fr) 190px;gap:8vw;align-items:end}
|
||||
.pro-v30-page-intro{padding:clamp(68px,10vw,128px) 0 clamp(55px,8vw,94px);background:var(--v30-panel)}
|
||||
.pro-v30-intro-main>p{max-width:610px;font-size:clamp(16px,2vw,21px);color:var(--v30-secondary)}
|
||||
.pro-v30-intro-aside{border-left:1px solid var(--v30-line);padding-left:27px;display:grid;gap:8px;align-content:end;min-height:120px}
|
||||
.pro-v30-overline,.pro-v30-caption{font-size:11px;letter-spacing:.12em;color:var(--v30-secondary)}
|
||||
.pro-v30-index-number{font-size:64px;line-height:1;font-weight:650;letter-spacing:-.08em}
|
||||
.pro-v30-index-toolbar{display:flex;justify-content:space-between;align-items:end;gap:22px;margin-bottom:28px}
|
||||
.pro-v30-index-body{background:var(--pro-background)}
|
||||
.pro-v30-index-body .pro-section-head,.pro-v30-index-toolbar h2{margin:8px 0}
|
||||
.pro-v30-subtle-link,.pro-v30-text-action{display:inline-flex;gap:10px;text-decoration:none;font-weight:750;font-size:13px;align-items:center;min-height:44px}
|
||||
.pro-v30-subtle-link:hover,.pro-v30-text-action:hover{text-decoration:underline}
|
||||
.pro-v30-results{margin-top:clamp(28px,5vw,54px)}
|
||||
.pro-v30-product-top{padding:26px 0 clamp(60px,8vw,110px);background:var(--v30-panel)}
|
||||
.pro-v30-crumb{display:flex;flex-wrap:wrap;gap:11px;align-items:center;font-size:12px;color:var(--v30-secondary);margin:8px 0 46px}
|
||||
.pro-v30-crumb a{text-decoration:underline;text-underline-offset:4px}
|
||||
.pro-v30-product-head{display:grid;grid-template-columns:minmax(290px,.78fr) minmax(0,1.22fr);align-items:center;gap:clamp(30px,6vw,90px)}
|
||||
.pro-v30-product-intro>p{font-size:clamp(16px,2vw,19px);color:var(--v30-secondary);max-width:48ch}
|
||||
.pro-v30-tags{display:flex;gap:8px;flex-wrap:wrap;margin:24px 0 30px}.pro-v30-tags span{padding:8px 13px;border:1px solid var(--v30-line);font-size:12px;border-radius:2px}
|
||||
.pro-v30-action{display:inline-flex;align-items:center;justify-content:center;min-height:48px}
|
||||
.pro-v30-main-media{background:var(--pro-surface);aspect-ratio:4/3;overflow:hidden;display:grid;place-items:center;border-radius:var(--pro-radius)}
|
||||
.pro-v30-main-media .pro-photo{width:100%;height:100%;object-fit:contain}
|
||||
.pro-v30-product-details{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(270px,.7fr);gap:clamp(30px,7vw,104px)}
|
||||
.pro-v30-details-main .pro-rich{max-width:760px}.pro-v30-details-side{align-self:start;border-top:3px solid var(--pro-accent);padding-top:25px}
|
||||
.pro-v30-details-side .pro-specs>div{grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);gap:16px}.pro-v30-details-side .pro-specs strong{overflow-wrap:anywhere}
|
||||
.pro-v30-reading-head{padding:clamp(50px,8vw,100px) 0 clamp(60px,8vw,100px);background:var(--v30-panel)}
|
||||
.pro-v30-reading-title{max-width:940px;margin:auto}.pro-v30-reading-title>p{font-size:clamp(16px,2.1vw,22px);max-width:65ch;color:var(--v30-secondary)}
|
||||
.pro-v30-reading-title time{display:block;margin-top:24px;font-size:13px;color:var(--v30-secondary)}
|
||||
.pro-v30-reading-layout{display:grid;grid-template-columns:180px minmax(0,750px);gap:clamp(35px,7vw,110px);padding-block:clamp(55px,8vw,110px)}
|
||||
.pro-v30-reading-aside{display:flex;flex-direction:column;gap:18px;align-self:start;position:sticky;top:30px;font-size:13px}
|
||||
.pro-v30-reading-rule{height:1px;background:var(--v30-line)}
|
||||
.pro-v30-reading-content{min-width:0}.pro-v30-reading-content figure{margin:0 0 45px}.pro-v30-reading-content figure img{display:block;width:100%;height:auto;max-height:550px;object-fit:cover}
|
||||
.pro-v30-reading-content .pro-rich{line-height:1.9;font-size:clamp(16px,1.75vw,18px)}.pro-v30-reading-content .pro-rich>*+*{margin-top:1.3em}
|
||||
.pro-v30-about-opening{padding:clamp(75px,10vw,130px) 0;background:var(--v30-panel)}
|
||||
.pro-v30-about-heading{display:grid;grid-template-columns:1.3fr .7fr;gap:9%;align-items:end}.pro-v30-about-heading>p{font-size:clamp(17px,2vw,23px);color:var(--v30-secondary);padding-bottom:12px}
|
||||
.pro-v30-about-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(36px,8vw,120px);align-items:center}
|
||||
.pro-v30-about-media{min-height:360px;aspect-ratio:5/4;background:var(--v30-panel);overflow:hidden}.pro-v30-about-media img{width:100%;height:100%;object-fit:cover}
|
||||
.pro-v30-media-placeholder{height:100%;min-height:360px;display:grid;place-items:center;color:var(--v30-secondary);letter-spacing:.18em;font-size:12px;border:1px solid var(--v30-line)}
|
||||
.pro-v30-about-copy .pro-rich{max-width:620px}.pro-v30-about-cases{background:var(--v30-panel)}
|
||||
.pro-v30-contact-header{background:var(--v30-panel);padding:clamp(75px,10vw,135px) 0}.pro-v30-contact-header p{font-size:clamp(17px,2vw,21px);max-width:58ch;color:var(--v30-secondary)}
|
||||
.pro-v30-contact-layout{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);gap:clamp(40px,8vw,120px)}
|
||||
.pro-v30-contact-channel{display:grid;gap:10px;margin-top:45px;border-top:1px solid var(--v30-line);padding-top:25px}.pro-v30-contact-channel span{font-size:12px;color:var(--v30-secondary)}.pro-v30-contact-channel a{font-size:clamp(15px,2vw,24px);overflow-wrap:anywhere;font-weight:650}
|
||||
.pro-v30-contact-form-wrap{padding:clamp(22px,4vw,48px);background:var(--v30-panel);min-width:0}
|
||||
.pro-v30-contact-form-wrap .pro-inquiry{margin-top:22px}.pro-v30-contact-form-wrap .pro-inquiry input,.pro-v30-contact-form-wrap textarea{background:var(--pro-background)}
|
||||
.pro-v30-contact-note{display:grid;align-content:space-between;min-height:270px;gap:24px}.pro-v30-decorative-number{font-size:90px;line-height:1;justify-self:end;color:var(--pro-accent)}
|
||||
.pro-v30-home-editorial{background:var(--pro-surface)}
|
||||
.pro-v30-page :where(a,button,input,textarea,select):focus-visible{outline:3px solid var(--pro-accent);outline-offset:3px}
|
||||
@media(max-width:950px){.pro-v30-product-head,.pro-v30-product-details,.pro-v30-about-grid,.pro-v30-contact-layout{grid-template-columns:1fr;gap:38px}.pro-v30-product-visual{order:-1}.pro-v30-about-heading{grid-template-columns:1fr;gap:4px}.pro-v30-about-media{max-height:430px}.pro-v30-reading-layout{grid-template-columns:1fr;gap:24px}.pro-v30-reading-aside{position:static;flex-direction:row;align-items:center;flex-wrap:wrap}.pro-v30-reading-rule{display:none}}
|
||||
@media(max-width:600px){.pro-v30-page .pro-wrap{padding-inline:max(18px,env(safe-area-inset-left));}.pro-v30-page h1{font-size:clamp(2.2rem,9.2vw,3.4rem)}.pro-v30-intro-layout{grid-template-columns:1fr;gap:18px}.pro-v30-intro-aside{display:none}.pro-v30-index-toolbar{align-items:start;flex-direction:column;gap:0}.pro-v30-crumb{margin-bottom:24px}.pro-v30-product-top{padding-bottom:42px}.pro-v30-main-media{aspect-ratio:5/4}.pro-v30-product-details{gap:45px}.pro-v30-reading-title{margin:0}.pro-v30-reading-content figure{margin-bottom:22px}.pro-v30-about-media,.pro-v30-media-placeholder{min-height:220px}.pro-v30-contact-form-wrap{padding:20px}.pro-v30-contact-layout{gap:30px}}
|
||||
@media(prefers-reduced-motion:reduce){.pro-v30-page *{scroll-behavior:auto!important}}
|
||||
|
||||
/* Theme-specific editorial composition */
|
||||
.pro-v30-product-automotive .pro-v30-main-media{background:linear-gradient(155deg,#eaf0f5,#d8e4ed)}.pro-v30-product-automotive .pro-v30-product-head{grid-template-columns:.7fr 1.3fr}.pro-v30-article-automotive .pro-v30-reading-head{background:linear-gradient(145deg,#0d2339,#213a55);color:#fff}.pro-v30-article-automotive .pro-v30-reading-title>p,.pro-v30-article-automotive .pro-v30-crumb{color:#d9e4ed}.pro-v30-vehicle-index .pro-v30-page-intro{background:#11283d;color:#f7f9fb}.pro-v30-vehicle-index .pro-v30-intro-main>p,.pro-v30-vehicle-index .pro-v30-intro-aside{color:#cbd6e0}.pro-v30-vehicle-index .pro-v30-intro-aside{border-color:#ffffff39}.pro-v30-vehicle-index .pro-eyebrow{color:#e2b47b}.pro-v30-product-automotive .pro-v30-product-intro h1{letter-spacing:-.055em}
|
||||
|
||||
@media(max-width:950px){.pro-v30-product-automotive .pro-v30-product-head{grid-template-columns:1fr}}
|
||||
|
||||
/* Retain existing gallery runtime behavior with re-composed product layout. */
|
||||
.pro-v30-page .pro-product-media{height:auto;min-height:0}
|
||||
@@ -0,0 +1,72 @@
|
||||
'use strict';(()=>{
|
||||
const menu=document.querySelector('[data-pro-menu]'); const nav=document.querySelector('#pro-navigation');
|
||||
if(menu&&nav){menu.addEventListener('click',()=>{const open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));nav.classList.toggle('is-open',open);menu.setAttribute('aria-label',open?'关闭导航':'打开导航');});}
|
||||
// Drawer navigation. The same markup is used across the four professional themes.
|
||||
const mobileNav=document.querySelector('#pro-navigation');
|
||||
let drawerBackdrop=null,drawerClose=null,returnFocus=null;
|
||||
if(menu&&mobileNav){
|
||||
const originalParent=mobileNav.parentNode,originalNext=mobileNav.nextSibling;
|
||||
const heading=document.createElement('div');heading.className='sf-drawer-header';
|
||||
const title=document.createElement('strong');title.textContent='网站导航';
|
||||
drawerClose=document.createElement('button');drawerClose.type='button';drawerClose.className='sf-drawer-close';drawerClose.textContent='×';drawerClose.setAttribute('aria-label','关闭网站导航');
|
||||
heading.append(title,drawerClose);mobileNav.prepend(heading);
|
||||
drawerBackdrop=document.createElement('div');drawerBackdrop.className='sf-mobile-drawer-backdrop';drawerBackdrop.hidden=false;document.body.append(drawerBackdrop);
|
||||
function setDrawer(open,focusBack=true){
|
||||
const isMobile=window.matchMedia('(max-width:1024px)').matches;
|
||||
open=Boolean(open&&isMobile);
|
||||
if(open){returnFocus=document.activeElement;document.body.append(mobileNav);}
|
||||
else if(mobileNav.parentNode!==originalParent){if(originalNext&&originalNext.parentNode===originalParent)originalParent.insertBefore(mobileNav,originalNext);else originalParent.append(mobileNav);}
|
||||
mobileNav.classList.toggle('is-open',open);drawerBackdrop.classList.toggle('is-open',open);
|
||||
document.body.classList.toggle('sf-drawer-open',open);
|
||||
menu.setAttribute('aria-expanded',String(open));menu.setAttribute('aria-label',open?'关闭导航':'打开导航');
|
||||
mobileNav.setAttribute('aria-hidden',String(!open&&isMobile));
|
||||
if('inert' in mobileNav)mobileNav.inert=!open&&isMobile;
|
||||
if(open)drawerClose.focus();
|
||||
else if(focusBack&&returnFocus instanceof HTMLElement){returnFocus.focus();returnFocus=null;}
|
||||
}
|
||||
// Replace the old inline-expansion listener: use capture to stop the old listener.
|
||||
menu.addEventListener('click',e=>{if(!window.matchMedia('(max-width:1024px)').matches)return;e.stopImmediatePropagation();setDrawer(!mobileNav.classList.contains('is-open'));},true);
|
||||
drawerClose.addEventListener('click',()=>setDrawer(false));
|
||||
drawerBackdrop.addEventListener('click',()=>setDrawer(false));
|
||||
document.querySelectorAll('[data-mobile-open-menu]').forEach(b=>b.addEventListener('click',()=>setDrawer(true)));
|
||||
mobileNav.addEventListener('click',e=>{if(e.target.closest('a'))setDrawer(false,false)});
|
||||
document.addEventListener('keydown',e=>{
|
||||
if(e.key==='Escape'&&mobileNav.classList.contains('is-open')){e.preventDefault();setDrawer(false);}
|
||||
if(e.key!=='Tab'||!mobileNav.classList.contains('is-open'))return;
|
||||
const els=[...mobileNav.querySelectorAll('button:not([disabled]),a[href]')].filter(el=>el.getClientRects().length&&!el.closest('[hidden]'));
|
||||
if(!els.length)return;const first=els[0],last=els[els.length-1];
|
||||
if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
|
||||
else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
|
||||
});
|
||||
window.matchMedia('(max-width:1024px)').addEventListener('change',()=>setDrawer(false,false));
|
||||
if('inert' in mobileNav&&window.matchMedia('(max-width:1024px)').matches)mobileNav.inert=true;
|
||||
// Do not strand catalogue links when the old mobile mega-button is hidden.
|
||||
const megaLinks=document.querySelectorAll('[data-pro-mega] a');
|
||||
if(megaLinks.length){const wrap=document.createElement('div');wrap.className='sf-drawer-extra';const h=document.createElement('strong');h.textContent='更多分类与系列';wrap.append(h);
|
||||
[...megaLinks].slice(0,20).forEach(a=>{const clone=a.cloneNode(true);wrap.append(clone);});mobileNav.append(wrap);}
|
||||
}
|
||||
const tabbar=document.querySelector('[data-mobile-tabs]');
|
||||
if(tabbar){tabbar.style.setProperty('--sf-mobile-tab-count',String(tabbar.children.length));}
|
||||
const megaButton=document.querySelector('[data-pro-catalog]'), mega=document.querySelector('[data-pro-mega]');
|
||||
if(megaButton&&mega){function setMega(open){mega.hidden=!open;mega.classList.toggle('is-open',open);megaButton.setAttribute('aria-expanded',String(open));}
|
||||
megaButton.addEventListener('click',()=>setMega(mega.hidden));
|
||||
document.addEventListener('keydown',e=>{if(e.key==='Escape')setMega(false);});
|
||||
document.addEventListener('click',e=>{if(!mega.hidden&&!mega.contains(e.target)&&!megaButton.contains(e.target))setMega(false);});
|
||||
}
|
||||
document.querySelectorAll('.pro-nav-disclose').forEach(button=>{button.addEventListener('click',()=>{let panel=button.parentElement.querySelector('.pro-nav-children');let visible=button.getAttribute('aria-expanded')==='true';button.setAttribute('aria-expanded',String(!visible));panel.hidden=visible;});});
|
||||
document.querySelectorAll('.pro-gallery-thumbs').forEach(container=>{const main=container.parentElement.querySelector('.pro-product-media img');const buttons=[...container.querySelectorAll('[data-gallery-image]')];
|
||||
buttons.forEach(b=>{b.addEventListener('click',()=>{buttons.forEach(x=>x.setAttribute('aria-pressed',String(x===b)));if(main){main.src=b.dataset.galleryImage;main.alt=b.querySelector('img')?.alt||'';}});});
|
||||
if(!main)return;main.tabIndex=0;main.style.cursor='zoom-in';const open=()=>{const pane=document.createElement('div');pane.className='pro-gallery-lightbox';pane.setAttribute('role','dialog');pane.setAttribute('aria-label','产品大图预览');let img=document.createElement('img');img.src=main.src;img.alt=main.alt;const close=document.createElement('button');close.type='button';close.textContent='关闭 ×';pane.append(img,close);document.body.append(pane);const done=()=>{pane.remove();document.removeEventListener('keydown',esc);main.focus();};const esc=e=>{if(e.key==='Escape')done();};document.addEventListener('keydown',esc);close.addEventListener('click',done);pane.addEventListener('click',e=>{if(e.target===pane)done();});close.focus();};main.addEventListener('click',open);main.addEventListener('keydown',e=>{if(e.key==='Enter')open();});
|
||||
});
|
||||
document.querySelectorAll('.pro-filter-scope').forEach(scope=>{
|
||||
const panel=scope.previousElementSibling;if(!panel||!panel.classList.contains('pro-filter-panel'))return;
|
||||
const cards=Array.from(scope.querySelectorAll('.pro-card'));
|
||||
const blank=document.createElement('div');blank.className='pro-filter-empty';blank.hidden=true;blank.textContent='没有找到符合条件的内容,请调整关键词或筛选条件。';scope.append(blank);
|
||||
const q=panel.querySelector('[data-pro-filter-search]'),b=panel.querySelector('[data-pro-filter-brand]'),c=panel.querySelector('[data-pro-filter-category]'),count=panel.querySelector('[data-pro-filter-count]');
|
||||
function options(sel,key){const vals=[...new Set(cards.map(x=>x.dataset[key]||'').filter(Boolean))].sort();vals.forEach(x=>sel.add(new Option(x,x)));if(!vals.length)sel.closest('label').hidden=true;}
|
||||
options(b,'brand');options(c,'category');
|
||||
function run(){const str=q.value.trim().toLocaleLowerCase();let visible=0;
|
||||
cards.forEach(el=>{const hit=el.textContent.toLocaleLowerCase().includes(str)&&(!b.value||el.dataset.brand===b.value)&&(!c.value||el.dataset.category===c.value);el.hidden=!hit;visible+=Number(hit);});count.textContent=`共 ${visible} 条`;blank.hidden=visible!==0;}
|
||||
[q,b,c].forEach(x=>x.addEventListener(x===q?'input':'change',run));run();
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
(()=>{
|
||||
const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
|
||||
const menu=$('.sf-menu-toggle');if(menu){menu.addEventListener('click',()=>{let open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));$('#sf-primary-links')?.classList.toggle('is-open',open);});}
|
||||
// Filter actual static content cards, no API is needed at runtime.
|
||||
$$('.sf-collection').forEach(grid=>{
|
||||
const cards=$$('.sf-item',grid);
|
||||
if(cards.length<2)return;
|
||||
const toolbar=document.createElement('div');toolbar.className='sf-filters';
|
||||
const search=document.createElement('input');search.type='search';search.placeholder='筛选当前列表';search.setAttribute('aria-label','筛选当前列表');
|
||||
const status=document.createElement('span');status.setAttribute('aria-live','polite');status.style.fontSize='12px';
|
||||
const brand=document.createElement('select');brand.setAttribute('aria-label','按品牌筛选');
|
||||
const category=document.createElement('select');category.setAttribute('aria-label','按分类筛选');
|
||||
function fill(el,field,all){el.append(new Option(all,''));const vals=[...new Set(cards.map(x=>x.dataset[field]||'').filter(Boolean))].sort();vals.forEach(v=>el.append(new Option(v,v)));if(!vals.length)el.hidden=true;}
|
||||
fill(brand,'brand','全部品牌');fill(category,'category','全部分类');
|
||||
toolbar.append(search,brand,category,status);grid.parentNode.insertBefore(toolbar,grid);
|
||||
function apply(){let val=search.value.trim().toLocaleLowerCase();let visible=0;cards.forEach(card=>{let hit=card.textContent.toLocaleLowerCase().includes(val)&&(!brand.value||card.dataset.brand===brand.value)&&(!category.value||card.dataset.category===category.value);card.hidden=!hit;if(hit)visible++;});status.textContent=`共 ${visible} 项`;}
|
||||
search.addEventListener('input',apply);brand.addEventListener('change',apply);category.addEventListener('change',apply);apply();
|
||||
});
|
||||
const input=$('#sf-search'),root=$('#sf-search-results'),count=$('#sf-search-count');
|
||||
if(input&&root){
|
||||
let data=[];const params=new URLSearchParams(location.search);input.value=params.get('q')||'';
|
||||
let debounce=null;
|
||||
const render=()=>{let q=input.value.trim().toLocaleLowerCase();let found=q?data.filter(d=>`${d.title} ${d.summary} ${d.brand} ${d.type}`.toLocaleLowerCase().includes(q)).slice(0,100):data.slice(0,30);
|
||||
root.replaceChildren();count.textContent=`${found.length} 条结果${!q?'(显示前30条)':''}`;
|
||||
found.forEach(d=>{let article=document.createElement('article'),a=document.createElement('a'),small=document.createElement('small'),p=document.createElement('p');article.className='sf-search-result';a.textContent=d.title;a.href=d.url.replace(/^\//,'');small.textContent=d.type;p.textContent=d.summary;article.append(a,small,p);root.append(article);});
|
||||
};const base=document.querySelector('base')?.href||location.href;
|
||||
fetch(new URL('search-index.json',base)).then(r=>{if(!r.ok)throw Error('index unavailable');return r.json();}).then(v=>{data=v;render()}).catch(()=>{count.textContent='搜索索引加载失败,请检查站点文件部署路径';});
|
||||
input.addEventListener('input',()=>{clearTimeout(debounce);debounce=setTimeout(render,100)});
|
||||
}
|
||||
})();
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 740"><defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#162c42"/><stop offset="1" stop-color="#081a2b"/></linearGradient><linearGradient id="s" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#acc8d2"/><stop offset=".48" stop-color="#516b82"/><stop offset="1" stop-color="#182f46"/></linearGradient><radialGradient id="lit"><stop stop-color="#e8ad68" stop-opacity=".3"/><stop offset="1" stop-color="#e8ad68" stop-opacity="0"/></radialGradient></defs><rect width="1000" height="740" fill="url(#bg)"/><circle cx="600" cy="290" r="420" fill="url(#lit)"/><g fill="none" stroke="#d8e6f0" stroke-opacity=".09"><path d="M0 580h1000M0 612h1000M0 644h1000M0 676h1000"/><path d="M150 740 500 490 860 740M0 740 470 490 1000 740"/></g><g transform="translate(45 80)"><ellipse cx="500" cy="520" rx="425" ry="45" fill="#000" opacity=".4"/><path d="M105 393q59-48 133-65l95-118q29-37 79-42l205-3q52-1 91 45l91 109q70 13 94 45l30 75-18 44H99L74 446Z" fill="url(#s)" stroke="#c4d5dc" stroke-width="5"/><path d="m285 323 98-109q19-20 52-20h168q35 0 66 35l70 93Z" fill="#132b40" stroke="#95b4c4" stroke-width="4"/><path d="M506 198v126" stroke="#a7c1cc" stroke-width="5"/><path d="M104 394q104-26 187-17m424-6q98 0 162 21" stroke="#d4e3e7" stroke-opacity=".7" stroke-width="4"/><path d="M96 419h113M806 413h90" stroke="#fff5d9" stroke-width="11" stroke-linecap="round"/><path d="M316 387h322" stroke="#dce6e9" stroke-opacity=".2" stroke-width="2"/><circle cx="253" cy="478" r="78" fill="#0c1825" stroke="#b6c0c5" stroke-width="9"/><circle cx="253" cy="478" r="39" fill="#667c89" stroke="#d1e0e6" stroke-width="8"/><circle cx="735" cy="478" r="78" fill="#0c1825" stroke="#b6c0c5" stroke-width="9"/><circle cx="735" cy="478" r="39" fill="#667c89" stroke="#d1e0e6" stroke-width="8"/></g><text x="50" y="62" fill="#d7a76d" font-size="14" font-family="Arial" letter-spacing="7">CONCEPT / MOBILITY</text><path d="M52 80h220" stroke="#b98756" opacity=".55"/></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,41 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>品牌展示</title><meta name="description" content="Explore our brand"><meta property="og:type" content="website"><meta property="og:title" content="品牌展示"><meta property="og:description" content="Explore our brand"><meta property="og:url" content="https://automotive.example.test/brand/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/brand/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="065f86de6b0dfd7f" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>品牌展示</h1><p>Explore our brand</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">03</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/brands/arc/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">AR</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>品牌中心</span><span>01</span></div>
|
||||
<h3>ARC</h3><p>ARC 品牌产品与应用。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/brands/mono/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">MO</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>品牌中心</span><span>02</span></div>
|
||||
<h3>MONO</h3><p>MONO 品牌产品与应用。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/brands/nova/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">NO</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>品牌中心</span><span>03</span></div>
|
||||
<h3>NOVA</h3><p>NOVA 品牌产品与应用。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,38 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>ARC</title><meta name="description" content="ARC 品牌产品与应用。"><meta property="og:type" content="website"><meta property="og:title" content="ARC"><meta property="og:description" content="ARC 品牌产品与应用。"><meta property="og:url" content="https://automotive.example.test/brands/arc/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/brands/arc/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="1eb13c60b6139cd9" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-brand-hero"><div class="pro-wrap pro-brand-hero-grid"><div><span class="pro-eyebrow">BRAND PROFILE / PARTNERS</span><h1>ARC</h1><p>ARC 品牌产品与应用。</p></div><div class="pro-brand-media">
|
||||
<div class="pro-art pro-art-brand"><span class="pro-art-ring"></span><span class="pro-art-center">AR</span><span class="pro-art-label">BRAND</span></div>
|
||||
</div></div></section><section class="pro-section"><div class="pro-wrap"><p class="pro-lead">ARC 品牌产品与应用。</p><div class="pro-section-head"><div><span class="pro-eyebrow">PRODUCT LINE</span><h2>相关产品</h2></div><a class="pro-text-link" href="/products/">浏览全部 <span>↗</span></a></div><div class="pro-grid pro-grid-product">
|
||||
<a class="pro-card pro-card-product" href="/products/4/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>01</span></div>
|
||||
<h3>Cars Delta</h3><p>Cars Delta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-product" href="/products/3/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>02</span></div>
|
||||
<h3>Cars Gamma</h3><p>Cars Gamma — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-product" href="/products/8/" data-category="专业系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>03</span></div>
|
||||
<h3>Cars Theta</h3><p>Cars Theta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>品牌中心</title><meta name="description" content="探索与产品相关的品牌。"><meta property="og:type" content="website"><meta property="og:title" content="品牌中心"><meta property="og:description" content="探索与产品相关的品牌。"><meta property="og:url" content="https://automotive.example.test/brands/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/brands/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="88efda36f9cd0ea2" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>品牌中心</h1><p>探索与产品相关的品牌。</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">03</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/brands/arc/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">AR</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>品牌中心</span><span>01</span></div>
|
||||
<h3>ARC</h3><p>ARC 品牌产品与应用。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/brands/mono/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">MO</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>品牌中心</span><span>02</span></div>
|
||||
<h3>MONO</h3><p>MONO 品牌产品与应用。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/brands/nova/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">NO</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>品牌中心</span><span>03</span></div>
|
||||
<h3>NOVA</h3><p>NOVA 品牌产品与应用。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>MONO</title><meta name="description" content="MONO 品牌产品与应用。"><meta property="og:type" content="website"><meta property="og:title" content="MONO"><meta property="og:description" content="MONO 品牌产品与应用。"><meta property="og:url" content="https://automotive.example.test/brands/mono/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/brands/mono/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="2a23b349e6c3d9a3" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-brand-hero"><div class="pro-wrap pro-brand-hero-grid"><div><span class="pro-eyebrow">BRAND PROFILE / PARTNERS</span><h1>MONO</h1><p>MONO 品牌产品与应用。</p></div><div class="pro-brand-media">
|
||||
<div class="pro-art pro-art-brand"><span class="pro-art-ring"></span><span class="pro-art-center">MO</span><span class="pro-art-label">BRAND</span></div>
|
||||
</div></div></section><section class="pro-section"><div class="pro-wrap"><p class="pro-lead">MONO 品牌产品与应用。</p><div class="pro-section-head"><div><span class="pro-eyebrow">PRODUCT LINE</span><h2>相关产品</h2></div><a class="pro-text-link" href="/products/">浏览全部 <span>↗</span></a></div><div class="pro-grid pro-grid-product">
|
||||
<a class="pro-card pro-card-product" href="/products/5/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>01</span></div>
|
||||
<h3>Cars Epsilon</h3><p>Cars Epsilon — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-product" href="/products/6/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>02</span></div>
|
||||
<h3>Cars Zeta</h3><p>Cars Zeta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,38 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>NOVA</title><meta name="description" content="NOVA 品牌产品与应用。"><meta property="og:type" content="website"><meta property="og:title" content="NOVA"><meta property="og:description" content="NOVA 品牌产品与应用。"><meta property="og:url" content="https://automotive.example.test/brands/nova/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/brands/nova/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="e83679c4b5fb916f" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-brand-hero"><div class="pro-wrap pro-brand-hero-grid"><div><span class="pro-eyebrow">BRAND PROFILE / PARTNERS</span><h1>NOVA</h1><p>NOVA 品牌产品与应用。</p></div><div class="pro-brand-media">
|
||||
<div class="pro-art pro-art-brand"><span class="pro-art-ring"></span><span class="pro-art-center">NO</span><span class="pro-art-label">BRAND</span></div>
|
||||
</div></div></section><section class="pro-section"><div class="pro-wrap"><p class="pro-lead">NOVA 品牌产品与应用。</p><div class="pro-section-head"><div><span class="pro-eyebrow">PRODUCT LINE</span><h2>相关产品</h2></div><a class="pro-text-link" href="/products/">浏览全部 <span>↗</span></a></div><div class="pro-grid pro-grid-product">
|
||||
<a class="pro-card pro-card-product" href="/products/1/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>01</span></div>
|
||||
<h3>Cars Alpha</h3><p>Cars Alpha — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-product" href="/products/2/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>02</span></div>
|
||||
<h3>Cars Beta</h3><p>Cars Beta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-product" href="/products/7/" data-category="专业系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>03</span></div>
|
||||
<h3>Cars Eta</h3><p>Cars Eta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>案例展示</title><meta name="description" content="Explore our case"><meta property="og:type" content="website"><meta property="og:title" content="案例展示"><meta property="og:description" content="Explore our case"><meta property="og:url" content="https://automotive.example.test/case/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/case/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="4772ae7ce839ce12" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>案例展示</h1><p>Explore our case</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">03</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/cases/1/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>01</span></div>
|
||||
<h3>精选项目 · 01</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/cases/2/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>02</span></div>
|
||||
<h3>精选项目 · 02</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/cases/3/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>03</span></div>
|
||||
<h3>精选项目 · 03</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>精选项目 · 01</title><meta name="description" content="展示项目背景、过程与成果。"><meta property="og:type" content="article"><meta property="og:title" content="精选项目 · 01"><meta property="og:description" content="展示项目背景、过程与成果。"><meta property="og:url" content="https://automotive.example.test/cases/1/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/cases/1/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="24618b5fad2ef8ce" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero"><div class="pro-wrap"><span class="pro-eyebrow">SELECTED WORK / PROJECT</span><h1>精选项目 · 01</h1><p>展示项目背景、过程与成果。</p></div></section><section class="pro-section"><div class="pro-wrap pro-case-detail"><div class="pro-case-meta"><span>项目地点<br><strong>Demo location</strong></span><span>所属行业<br><strong>Demo industry</strong></span></div><div class="pro-rich"><p>本页仅为项目结构示例,并非实际完成的工程项目。</p></div><a class="pro-text-link" href="/cases/">浏览更多案例 →</a></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>精选项目 · 02</title><meta name="description" content="展示项目背景、过程与成果。"><meta property="og:type" content="article"><meta property="og:title" content="精选项目 · 02"><meta property="og:description" content="展示项目背景、过程与成果。"><meta property="og:url" content="https://automotive.example.test/cases/2/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/cases/2/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="dcd42f7ea79b2828" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero"><div class="pro-wrap"><span class="pro-eyebrow">SELECTED WORK / PROJECT</span><h1>精选项目 · 02</h1><p>展示项目背景、过程与成果。</p></div></section><section class="pro-section"><div class="pro-wrap pro-case-detail"><div class="pro-case-meta"><span>项目地点<br><strong>Demo location</strong></span><span>所属行业<br><strong>Demo industry</strong></span></div><div class="pro-rich"><p>本页仅为项目结构示例,并非实际完成的工程项目。</p></div><a class="pro-text-link" href="/cases/">浏览更多案例 →</a></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>精选项目 · 03</title><meta name="description" content="展示项目背景、过程与成果。"><meta property="og:type" content="article"><meta property="og:title" content="精选项目 · 03"><meta property="og:description" content="展示项目背景、过程与成果。"><meta property="og:url" content="https://automotive.example.test/cases/3/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/cases/3/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="94e8201e195f19ae" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero"><div class="pro-wrap"><span class="pro-eyebrow">SELECTED WORK / PROJECT</span><h1>精选项目 · 03</h1><p>展示项目背景、过程与成果。</p></div></section><section class="pro-section"><div class="pro-wrap pro-case-detail"><div class="pro-case-meta"><span>项目地点<br><strong>Demo location</strong></span><span>所属行业<br><strong>Demo industry</strong></span></div><div class="pro-rich"><p>本页仅为项目结构示例,并非实际完成的工程项目。</p></div><a class="pro-text-link" href="/cases/">浏览更多案例 →</a></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>项目案例</title><meta name="description" content="精选项目展示。"><meta property="og:type" content="website"><meta property="og:title" content="项目案例"><meta property="og:description" content="精选项目展示。"><meta property="og:url" content="https://automotive.example.test/cases/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/cases/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="4de05a93c7154e52" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>项目案例</h1><p>精选项目展示。</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">03</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/cases/1/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>01</span></div>
|
||||
<h3>精选项目 · 01</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/cases/2/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>02</span></div>
|
||||
<h3>精选项目 · 02</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/cases/3/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">精选</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>项目案例</span><span>03</span></div>
|
||||
<h3>精选项目 · 03</h3><p>展示项目背景、过程与成果。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>旗舰系列</title><meta name="description" content="分类产品与相关信息"><meta property="og:type" content="website"><meta property="og:title" content="旗舰系列"><meta property="og:description" content="分类产品与相关信息"><meta property="og:url" content="https://automotive.example.test/categories/term-79e34764f8ce/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/categories/term-79e34764f8ce/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="fbdf3983ec7546a2" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-listing-hero"><div class="pro-wrap"><span class="pro-eyebrow">DISCOVER / CATEGORY</span><h1>旗舰系列</h1><p>分类产品与相关信息</p><div class="pro-breadcrumb"><a href="/">首页</a><span>/</span><strong>旗舰系列</strong></div></div></section>
|
||||
<section class="pro-section pro-list-section"><div class="pro-wrap"><div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/products/1/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>01</span></div>
|
||||
<h3>Cars Alpha</h3><p>Cars Alpha — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/2/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>02</span></div>
|
||||
<h3>Cars Beta</h3><p>Cars Beta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>专业系列</title><meta name="description" content="分类产品与相关信息"><meta property="og:type" content="website"><meta property="og:title" content="专业系列"><meta property="og:description" content="分类产品与相关信息"><meta property="og:url" content="https://automotive.example.test/categories/term-8d6647722ef3/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/categories/term-8d6647722ef3/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="1956a76416a5e9c8" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-listing-hero"><div class="pro-wrap"><span class="pro-eyebrow">DISCOVER / CATEGORY</span><h1>专业系列</h1><p>分类产品与相关信息</p><div class="pro-breadcrumb"><a href="/">首页</a><span>/</span><strong>专业系列</strong></div></div></section>
|
||||
<section class="pro-section pro-list-section"><div class="pro-wrap"><div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/products/7/" data-category="专业系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>01</span></div>
|
||||
<h3>Cars Eta</h3><p>Cars Eta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/8/" data-category="专业系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>02</span></div>
|
||||
<h3>Cars Theta</h3><p>Cars Theta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>畅销系列</title><meta name="description" content="分类产品与相关信息"><meta property="og:type" content="website"><meta property="og:title" content="畅销系列"><meta property="og:description" content="分类产品与相关信息"><meta property="og:url" content="https://automotive.example.test/categories/term-95acf900fafc/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/categories/term-95acf900fafc/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="28920c5d3e90fad4" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-listing-hero"><div class="pro-wrap"><span class="pro-eyebrow">DISCOVER / CATEGORY</span><h1>畅销系列</h1><p>分类产品与相关信息</p><div class="pro-breadcrumb"><a href="/">首页</a><span>/</span><strong>畅销系列</strong></div></div></section>
|
||||
<section class="pro-section pro-list-section"><div class="pro-wrap"><div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/products/4/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>01</span></div>
|
||||
<h3>Cars Delta</h3><p>Cars Delta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/3/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>02</span></div>
|
||||
<h3>Cars Gamma</h3><p>Cars Gamma — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>定制系列</title><meta name="description" content="分类产品与相关信息"><meta property="og:type" content="website"><meta property="og:title" content="定制系列"><meta property="og:description" content="分类产品与相关信息"><meta property="og:url" content="https://automotive.example.test/categories/term-a68a8656a4dc/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/categories/term-a68a8656a4dc/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="19f285074eb607ae" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-listing-hero"><div class="pro-wrap"><span class="pro-eyebrow">DISCOVER / CATEGORY</span><h1>定制系列</h1><p>分类产品与相关信息</p><div class="pro-breadcrumb"><a href="/">首页</a><span>/</span><strong>定制系列</strong></div></div></section>
|
||||
<section class="pro-section pro-list-section"><div class="pro-wrap"><div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/products/5/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>01</span></div>
|
||||
<h3>Cars Epsilon</h3><p>Cars Epsilon — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/6/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>02</span></div>
|
||||
<h3>Cars Zeta</h3><p>Cars Zeta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"ready_for_review": true,
|
||||
"commercially_certified": false,
|
||||
"blockers": [],
|
||||
"warnings": [
|
||||
{
|
||||
"code": "brand_logo",
|
||||
"message": "尚未替换成企业正式 Logo"
|
||||
},
|
||||
{
|
||||
"code": "placeholder_images",
|
||||
"count": 75,
|
||||
"message": "部分页面仍使用示意图,正式发布前请替换为真实且有使用权的图片"
|
||||
},
|
||||
{
|
||||
"code": "demo_content",
|
||||
"message": "检测到演示或默认文案,正式发布前请逐页核对"
|
||||
},
|
||||
{
|
||||
"code": "form_not_configured",
|
||||
"message": "尚未配置 HTTPS 询盘接收服务;目前仅提供邮件联系"
|
||||
},
|
||||
{
|
||||
"code": "legal_pages",
|
||||
"message": "请根据目标国家/地区法律审查是否需要隐私政策与 Cookie 声明"
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"html_pages": 38,
|
||||
"artwork_fallbacks": 75,
|
||||
"pages_with_body": 18
|
||||
},
|
||||
"note": "仅为本地结构化交付清单,不等于合法合规、内容准确、无障碍、性能或生产上线认证"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>联系我们</title><meta name="description" content="欢迎通过邮件与我们的演示团队取得联系。"><meta property="og:type" content="website"><meta property="og:title" content="联系我们"><meta property="og:description" content="欢迎通过邮件与我们的演示团队取得联系。"><meta property="og:url" content="https://automotive.example.test/contact/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/contact/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="cfd191cd4f982fc1" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-contact pro-v30-contact-automotive"><section class="pro-v30-contact-header"><div class="pro-wrap"><span class="pro-eyebrow">CONNECT / CONTACT</span><h1>联系我们</h1><p>欢迎通过邮件与我们的演示团队取得联系。</p></div></section>
|
||||
<section class="pro-section pro-v30-contact-content"><div class="pro-wrap pro-v30-contact-layout"><div class="pro-v30-contact-info"><span class="pro-eyebrow">LET'S START A CONVERSATION</span><h2>咨询与合作</h2><div class="pro-rich"><p>本页为演示内容。您可以在网站工厂中直接修改和替换。</p></div>
|
||||
<div class="pro-v30-contact-channel"><span>电子邮箱</span><a href="mailto:hello@example.com">hello@example.com ↗</a></div>
|
||||
|
||||
</div><div class="pro-v30-contact-form-wrap"><div class="pro-v30-contact-note"><span class="pro-eyebrow">HOW TO REACH US</span><p>您可以通过本页提供的联系渠道咨询产品、项目或合作事项。</p><span class="pro-v30-decorative-number" aria-hidden="true">↗</span></div></div></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>常见问题</title><meta name="description" content="更多问题可以联系我们。"><meta property="og:type" content="website"><meta property="og:title" content="常见问题"><meta property="og:description" content="更多问题可以联系我们。"><meta property="og:url" content="https://automotive.example.test/faq/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/faq/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="ec0c20057cc1eb0c" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero pro-info-hero"><div class="pro-wrap"><span class="pro-eyebrow">EXPLORE</span><h1>常见问题</h1><p>更多问题可以联系我们。</p></div></section>
|
||||
<section class="pro-section pro-info-section"><div class="pro-wrap pro-info-grid"><div><span class="pro-eyebrow">ABOUT / DISCOVER</span><h2>常见问题</h2></div><div class="pro-info-copy"><div class="pro-rich"><p>本页为演示内容。您可以在网站工厂中直接修改和替换。</p></div></div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,89 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>首页</title><meta name="description" content="探索更广阔的出行可能"><meta property="og:type" content="website"><meta property="og:title" content="首页"><meta property="og:description" content="探索更广阔的出行可能"><meta property="og:url" content="https://automotive.example.test/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="8a5edab282632443" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-home-hero pro-auto-hero"><div class="pro-wrap pro-home-grid"><div class="pro-hero-copy"><span class="pro-eyebrow">01 / THE WORLD IN MOTION</span><h1>NOVA MOBILITY<em> MOVE BEYOND.</em></h1><p>探索更广阔的出行可能</p><div class="pro-hero-actions"><a class="pro-button" href="/products/">探索车型 <span>↗</span></a><a class="pro-link-light" href="/about/">了解企业 →</a></div></div><div class="pro-hero-art"><div class="pro-abstract-car"><span>THE NEXT CHAPTER</span><div class="auto-wheel a"></div><div class="auto-wheel b"></div></div><b>DRIVE / 01</b></div></div><div class="pro-wrap pro-hero-bottom"><span>DESIGN / TRUST / DELIVERY</span><span>SCROLL TO DISCOVER ↓</span></div></section>
|
||||
<section class="pro-section pro-featured"><div class="pro-wrap"><div class="pro-section-head"><div><span class="pro-eyebrow">02 / COLLECTION</span><h2>探索精选车型</h2></div><a class="pro-text-link" href="/products/">浏览全部 <span>↗</span></a></div><div class="pro-grid pro-grid-vehicle">
|
||||
<a class="pro-card pro-card-vehicle" href="/products/1/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-vehicle"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">VEHICLE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>01</span></div>
|
||||
<h3>Cars Alpha</h3><p>Cars Alpha — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-vehicle" href="/products/2/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-vehicle"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">VEHICLE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>02</span></div>
|
||||
<h3>Cars Beta</h3><p>Cars Beta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-vehicle" href="/products/4/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-vehicle"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">VEHICLE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>03</span></div>
|
||||
<h3>Cars Delta</h3><p>Cars Delta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-vehicle" href="/products/5/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-vehicle"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">VEHICLE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>04</span></div>
|
||||
<h3>Cars Epsilon</h3><p>Cars Epsilon — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-vehicle" href="/products/7/" data-category="专业系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-vehicle"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">VEHICLE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>05</span></div>
|
||||
<h3>Cars Eta</h3><p>Cars Eta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-vehicle" href="/products/3/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-vehicle"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">VEHICLE</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>06</span></div>
|
||||
<h3>Cars Gamma</h3><p>Cars Gamma — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></section>
|
||||
<section class="pro-section pro-statement"><div class="pro-wrap pro-statement-layout"><span class="pro-eyebrow">THE COMPANY</span><div><h2>把产品带向更广阔的市场。</h2><p>探索更广阔的出行可能</p><a class="pro-text-link" href="/about/">认识我们 ↗</a></div></div></section>
|
||||
<section class="pro-section pro-brand-section"><div class="pro-wrap"><div class="pro-section-head"><div><span class="pro-eyebrow">03 / BRANDS</span><h2>品牌与合作伙伴</h2></div><a class="pro-text-link" href="/brands/">浏览全部 <span>↗</span></a></div><div class="pro-brand-pills"><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a></div></div></section>
|
||||
|
||||
<section class="pro-section pro-v30-home-editorial"><div class="pro-wrap"><div class="pro-section-head"><div><span class="pro-eyebrow">FIELD NOTES / NEWS</span><h2>行业资讯</h2></div><a class="pro-text-link" href="/news/">浏览全部 <span>↗</span></a></div><div class="pro-grid pro-grid-news">
|
||||
<a class="pro-card pro-card-news" href="/news/1/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-news"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">NEWS</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>01</span></div>
|
||||
<h3>企业动态 · 01</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-news" href="/news/2/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-news"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">NEWS</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>02</span></div>
|
||||
<h3>企业动态 · 02</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-news" href="/news/3/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-news"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">NEWS</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>03</span></div>
|
||||
<h3>企业动态 · 03</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div></section>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>企业动态 · 01</title><meta name="description" content="在这里发布新闻资讯与品牌动态。"><meta property="og:type" content="article"><meta property="og:title" content="企业动态 · 01"><meta property="og:description" content="在这里发布新闻资讯与品牌动态。"><meta property="og:url" content="https://automotive.example.test/news/1/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/news/1/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="5a84377e8f9cd95a" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-article pro-v30-article-automotive"><section class="pro-article-hero pro-v30-reading-head"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/news/">行业资讯</a></nav><div class="pro-v30-reading-title"><span class="pro-eyebrow">IDEAS / JOURNAL</span><h1>企业动态 · 01</h1><p>在这里发布新闻资讯与品牌动态。</p><time datetime="2026-10-11">2026-10-11</time></div></div></section>
|
||||
<div class="pro-wrap pro-v30-reading-layout"><aside class="pro-v30-reading-aside"><span class="pro-eyebrow">READ / EXPLORE</span><a href="/news/">← 返回资讯</a><span class="pro-v30-reading-rule" aria-hidden="true"></span><a href="/contact/">联系沟通 ↗</a></aside><article id="article-body" class="pro-v30-reading-content"><div class="pro-rich"><p>这是可替换的新闻正文内容,可包含图片、表格和链接。</p></div><a class="pro-v30-subtle-link" href="/news/">← 返回资讯</a></article></div></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>企业动态 · 02</title><meta name="description" content="在这里发布新闻资讯与品牌动态。"><meta property="og:type" content="article"><meta property="og:title" content="企业动态 · 02"><meta property="og:description" content="在这里发布新闻资讯与品牌动态。"><meta property="og:url" content="https://automotive.example.test/news/2/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/news/2/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="d9fbb07507e36434" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-article pro-v30-article-automotive"><section class="pro-article-hero pro-v30-reading-head"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/news/">行业资讯</a></nav><div class="pro-v30-reading-title"><span class="pro-eyebrow">IDEAS / JOURNAL</span><h1>企业动态 · 02</h1><p>在这里发布新闻资讯与品牌动态。</p><time datetime="2026-10-12">2026-10-12</time></div></div></section>
|
||||
<div class="pro-wrap pro-v30-reading-layout"><aside class="pro-v30-reading-aside"><span class="pro-eyebrow">READ / EXPLORE</span><a href="/news/">← 返回资讯</a><span class="pro-v30-reading-rule" aria-hidden="true"></span><a href="/contact/">联系沟通 ↗</a></aside><article id="article-body" class="pro-v30-reading-content"><div class="pro-rich"><p>这是可替换的新闻正文内容,可包含图片、表格和链接。</p></div><a class="pro-v30-subtle-link" href="/news/">← 返回资讯</a></article></div></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>企业动态 · 03</title><meta name="description" content="在这里发布新闻资讯与品牌动态。"><meta property="og:type" content="article"><meta property="og:title" content="企业动态 · 03"><meta property="og:description" content="在这里发布新闻资讯与品牌动态。"><meta property="og:url" content="https://automotive.example.test/news/3/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/news/3/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="5f4318b4ebbb1300" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-article pro-v30-article-automotive"><section class="pro-article-hero pro-v30-reading-head"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/news/">行业资讯</a></nav><div class="pro-v30-reading-title"><span class="pro-eyebrow">IDEAS / JOURNAL</span><h1>企业动态 · 03</h1><p>在这里发布新闻资讯与品牌动态。</p><time datetime="2026-10-13">2026-10-13</time></div></div></section>
|
||||
<div class="pro-wrap pro-v30-reading-layout"><aside class="pro-v30-reading-aside"><span class="pro-eyebrow">READ / EXPLORE</span><a href="/news/">← 返回资讯</a><span class="pro-v30-reading-rule" aria-hidden="true"></span><a href="/contact/">联系沟通 ↗</a></aside><article id="article-body" class="pro-v30-reading-content"><div class="pro-rich"><p>这是可替换的新闻正文内容,可包含图片、表格和链接。</p></div><a class="pro-v30-subtle-link" href="/news/">← 返回资讯</a></article></div></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>新闻资讯</title><meta name="description" content="了解最新消息与团队动态。"><meta property="og:type" content="website"><meta property="og:title" content="新闻资讯"><meta property="og:description" content="了解最新消息与团队动态。"><meta property="og:url" content="https://automotive.example.test/news/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/news/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="3da6b66744f34a78" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>新闻资讯</h1><p>了解最新消息与团队动态。</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">03</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/news/1/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>01</span></div>
|
||||
<h3>企业动态 · 01</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/news/2/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>02</span></div>
|
||||
<h3>企业动态 · 02</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/news/3/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>03</span></div>
|
||||
<h3>企业动态 · 03</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,4 @@
|
||||
# SiteForge 生成:将本文件内容合并到 Nginx server {} 内
|
||||
index index.html;
|
||||
error_page 404 /404.html;
|
||||
location / { try_files $uri $uri/ =404; }
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>新闻展示</title><meta name="description" content="Explore our post"><meta property="og:type" content="website"><meta property="og:title" content="新闻展示"><meta property="og:description" content="Explore our post"><meta property="og:url" content="https://automotive.example.test/post/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/post/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="6e84e7496696e83e" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>新闻展示</h1><p>Explore our post</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">03</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/news/1/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>01</span></div>
|
||||
<h3>企业动态 · 01</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/news/2/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>02</span></div>
|
||||
<h3>企业动态 · 02</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/news/3/" data-category="" data-brand="">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">企业</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>新闻资讯</span><span>03</span></div>
|
||||
<h3>企业动态 · 03</h3><p>在这里发布新闻资讯与品牌动态。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,81 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>产品展示</title><meta name="description" content="Explore our product"><meta property="og:type" content="website"><meta property="og:title" content="产品展示"><meta property="og:description" content="Explore our product"><meta property="og:url" content="https://automotive.example.test/product/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/product/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="5595bb05772acd85" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>产品展示</h1><p>Explore our product</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">08</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/products/1/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>01</span></div>
|
||||
<h3>Cars Alpha</h3><p>Cars Alpha — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/2/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>02</span></div>
|
||||
<h3>Cars Beta</h3><p>Cars Beta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/4/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>03</span></div>
|
||||
<h3>Cars Delta</h3><p>Cars Delta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/5/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>04</span></div>
|
||||
<h3>Cars Epsilon</h3><p>Cars Epsilon — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/7/" data-category="专业系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>05</span></div>
|
||||
<h3>Cars Eta</h3><p>Cars Eta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/3/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>06</span></div>
|
||||
<h3>Cars Gamma</h3><p>Cars Gamma — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/8/" data-category="专业系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>07</span></div>
|
||||
<h3>Cars Theta</h3><p>Cars Theta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/6/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>08</span></div>
|
||||
<h3>Cars Zeta</h3><p>Cars Zeta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Alpha</title><meta name="description" content="Cars Alpha — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Alpha"><meta property="og:description" content="Cars Alpha — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/1/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/1/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="1d4b9f8ed76169f3" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Alpha</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Alpha</h1><p>Cars Alpha — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>NOVA</span><span>旗舰系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Beta</title><meta name="description" content="Cars Beta — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Beta"><meta property="og:description" content="Cars Beta — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/2/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/2/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="f7532c34a401efcd" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Beta</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Beta</h1><p>Cars Beta — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>NOVA</span><span>旗舰系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Gamma</title><meta name="description" content="Cars Gamma — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Gamma"><meta property="og:description" content="Cars Gamma — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/3/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/3/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="1dcf2af711747f90" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Gamma</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Gamma</h1><p>Cars Gamma — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>ARC</span><span>畅销系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Delta</title><meta name="description" content="Cars Delta — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Delta"><meta property="og:description" content="Cars Delta — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/4/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/4/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="f0935cefcc323a6f" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Delta</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Delta</h1><p>Cars Delta — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>ARC</span><span>畅销系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Epsilon</title><meta name="description" content="Cars Epsilon — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Epsilon"><meta property="og:description" content="Cars Epsilon — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/5/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/5/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="b3f8b14c1c21cbfd" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Epsilon</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Epsilon</h1><p>Cars Epsilon — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>MONO</span><span>定制系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Zeta</title><meta name="description" content="Cars Zeta — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Zeta"><meta property="og:description" content="Cars Zeta — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/6/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/6/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="c32d73751f4fcc45" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Zeta</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Zeta</h1><p>Cars Zeta — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>MONO</span><span>定制系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Eta</title><meta name="description" content="Cars Eta — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Eta"><meta property="og:description" content="Cars Eta — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/7/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/7/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="b4665fed00bedf79" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Eta</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Eta</h1><p>Cars Eta — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>NOVA</span><span>专业系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>Cars Theta</title><meta name="description" content="Cars Theta — 针对多样应用的演示产品。"><meta property="og:type" content="website"><meta property="og:title" content="Cars Theta"><meta property="og:description" content="Cars Theta — 针对多样应用的演示产品。"><meta property="og:url" content="https://automotive.example.test/products/8/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/8/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="2aac50ce3502245f" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-product-layout pro-v30-page pro-v30-product pro-v30-product-automotive"><section class="pro-v30-product-top"><div class="pro-wrap"><nav class="pro-v30-crumb" aria-label="面包屑导航"><a href="/">首页</a><span aria-hidden="true">/</span><a href="/products/">车型与出行</a><span aria-hidden="true">/</span><span aria-current="page">Cars Theta</span></nav>
|
||||
<div class="pro-v30-product-head"><div class="pro-v30-product-intro"><span class="pro-eyebrow">VEHICLE COLLECTION / DETAILS</span><h1>Cars Theta</h1><p>Cars Theta — 针对多样应用的演示产品。</p><div class="pro-v30-tags"><span>ARC</span><span>专业系列</span></div><a class="pro-button pro-v30-action" href="/contact/">咨询相关信息 ↗</a></div>
|
||||
<div class="pro-v30-product-visual"><div class="pro-product-media pro-v30-main-media">
|
||||
<div class="pro-art pro-art-product"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">PRODUCT</span></div>
|
||||
</div></div></div></div></section>
|
||||
<section class="pro-section pro-v30-product-information"><div class="pro-wrap pro-v30-product-details"><div class="pro-v30-details-main"><span class="pro-eyebrow">OVERVIEW / 车型概览</span><h2>车型概览</h2><div class="pro-rich"><h2>产品说明</h2><p>此处可以展示产品特点、应用场景和可用规格。</p></div></div><aside class="pro-v30-details-side"><span class="pro-eyebrow">TECHNICAL / FACTS</span><h2>车辆参数</h2><div class="pro-specs"><div><span>Material</span><strong>Example / Demo</strong></div><div><span>Version</span><strong>2026</strong></div><div><span>Energy</span><strong>EV</strong></div></div></aside></div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,81 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>产品中心</title><meta name="description" content="查看系列、类别及产品资料。"><meta property="og:type" content="website"><meta property="og:title" content="产品中心"><meta property="og:description" content="查看系列、类别及产品资料。"><meta property="og:url" content="https://automotive.example.test/products/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="index,follow"><link rel="canonical" href="https://automotive.example.test/products/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body data-siteforge-route="6309e27858673231" class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<div class="pro-v30-page pro-v30-listing pro-v30-vehicle-index">
|
||||
<section class="pro-v30-page-intro"><div class="pro-wrap pro-v30-intro-layout"><div class="pro-v30-intro-main"><span class="pro-eyebrow">VEHICLE COLLECTION / INDEX</span><h1>产品中心</h1><p>查看系列、类别及产品资料。</p></div><div class="pro-v30-intro-aside"><span class="pro-v30-overline">BROWSE / EXPLORE</span><span class="pro-v30-index-number">08</span><span class="pro-v30-caption">本页展示内容</span></div></div></section>
|
||||
<section class="pro-section pro-v30-index-body"><div class="pro-wrap"><div class="pro-v30-index-toolbar"><div class="pro-v30-section-title"><span class="pro-eyebrow">CONTENTS / FIND YOUR FIT</span><h2>车型与出行</h2></div><a class="pro-v30-subtle-link" href="/contact/">需要协助?联系沟通 ↗</a></div>
|
||||
<div class="pro-filter-panel"><label><span>搜索</span><input type="search" data-pro-filter-search placeholder="按名称或内容筛选"></label><label><span>品牌</span><select data-pro-filter-brand><option value="">全部品牌</option></select></label><label><span>分类</span><select data-pro-filter-category><option value="">全部分类</option></select></label><span class="pro-filter-count" data-pro-filter-count role="status"></span></div><div class="pro-filter-scope pro-v30-results" aria-live="polite"><div class="pro-grid pro-grid-catalog">
|
||||
<a class="pro-card pro-card-catalog" href="/products/1/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>01</span></div>
|
||||
<h3>Cars Alpha</h3><p>Cars Alpha — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/2/" data-category="旗舰系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>02</span></div>
|
||||
<h3>Cars Beta</h3><p>Cars Beta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/4/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>03</span></div>
|
||||
<h3>Cars Delta</h3><p>Cars Delta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/5/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>04</span></div>
|
||||
<h3>Cars Epsilon</h3><p>Cars Epsilon — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/7/" data-category="专业系列" data-brand="NOVA">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>NOVA</span><span>05</span></div>
|
||||
<h3>Cars Eta</h3><p>Cars Eta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/3/" data-category="畅销系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>06</span></div>
|
||||
<h3>Cars Gamma</h3><p>Cars Gamma — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/8/" data-category="专业系列" data-brand="ARC">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>ARC</span><span>07</span></div>
|
||||
<h3>Cars Theta</h3><p>Cars Theta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
|
||||
<a class="pro-card pro-card-catalog" href="/products/6/" data-category="定制系列" data-brand="MONO">
|
||||
<div class="pro-card-cover">
|
||||
<div class="pro-art pro-art-catalog"><span class="pro-art-ring"></span><span class="pro-art-center">Ca</span><span class="pro-art-label">CATALOG</span></div>
|
||||
</div>
|
||||
<div class="pro-card-body"><div class="pro-card-meta"><span>MONO</span><span>08</span></div>
|
||||
<h3>Cars Zeta</h3><p>Cars Zeta — 针对多样应用的演示产品。</p><span class="pro-card-link">查看详情 <span aria-hidden="true">↗</span></span></div>
|
||||
</a>
|
||||
</div></div>
|
||||
|
||||
</div></section></div>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
<section class="pro-end-cta"><div class="pro-wrap"><div><span class="pro-eyebrow">LET'S CONNECT</span><h2>从这里开始下一步。</h2><p>探索更广阔的出行可能</p></div><a href="/contact/">联系我们 <span>↗</span></a></div></section>
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://automotive.example.test/sitemap.xml
|
||||
@@ -0,0 +1 @@
|
||||
[{"title": "ARC", "url": "/brands/arc/", "summary": "ARC 品牌产品与应用。", "type": "brand", "brand": ""}, {"title": "MONO", "url": "/brands/mono/", "summary": "MONO 品牌产品与应用。", "type": "brand", "brand": ""}, {"title": "NOVA", "url": "/brands/nova/", "summary": "NOVA 品牌产品与应用。", "type": "brand", "brand": ""}, {"title": "精选项目 · 01", "url": "/cases/1/", "summary": "展示项目背景、过程与成果。", "type": "case", "brand": ""}, {"title": "精选项目 · 02", "url": "/cases/2/", "summary": "展示项目背景、过程与成果。", "type": "case", "brand": ""}, {"title": "精选项目 · 03", "url": "/cases/3/", "summary": "展示项目背景、过程与成果。", "type": "case", "brand": ""}, {"title": "产品中心", "url": "/products/", "summary": "查看系列、类别及产品资料。", "type": "page", "brand": ""}, {"title": "关于我们", "url": "/about/", "summary": "企业介绍、发展理念、合作能力与团队故事。", "type": "page", "brand": ""}, {"title": "品牌中心", "url": "/brands/", "summary": "探索与产品相关的品牌。", "type": "page", "brand": ""}, {"title": "常见问题", "url": "/faq/", "summary": "更多问题可以联系我们。", "type": "page", "brand": ""}, {"title": "新闻资讯", "url": "/news/", "summary": "了解最新消息与团队动态。", "type": "page", "brand": ""}, {"title": "服务与解决方案", "url": "/services/", "summary": "从咨询到交付的全链路服务。", "type": "page", "brand": ""}, {"title": "联系我们", "url": "/contact/", "summary": "欢迎通过邮件与我们的演示团队取得联系。", "type": "page", "brand": ""}, {"title": "项目案例", "url": "/cases/", "summary": "精选项目展示。", "type": "page", "brand": ""}, {"title": "企业动态 · 01", "url": "/news/1/", "summary": "在这里发布新闻资讯与品牌动态。", "type": "post", "brand": ""}, {"title": "企业动态 · 02", "url": "/news/2/", "summary": "在这里发布新闻资讯与品牌动态。", "type": "post", "brand": ""}, {"title": "企业动态 · 03", "url": "/news/3/", "summary": "在这里发布新闻资讯与品牌动态。", "type": "post", "brand": ""}, {"title": "Cars Alpha", "url": "/products/1/", "summary": "Cars Alpha — 针对多样应用的演示产品。", "type": "product", "brand": "NOVA"}, {"title": "Cars Beta", "url": "/products/2/", "summary": "Cars Beta — 针对多样应用的演示产品。", "type": "product", "brand": "NOVA"}, {"title": "Cars Delta", "url": "/products/4/", "summary": "Cars Delta — 针对多样应用的演示产品。", "type": "product", "brand": "ARC"}, {"title": "Cars Epsilon", "url": "/products/5/", "summary": "Cars Epsilon — 针对多样应用的演示产品。", "type": "product", "brand": "MONO"}, {"title": "Cars Eta", "url": "/products/7/", "summary": "Cars Eta — 针对多样应用的演示产品。", "type": "product", "brand": "NOVA"}, {"title": "Cars Gamma", "url": "/products/3/", "summary": "Cars Gamma — 针对多样应用的演示产品。", "type": "product", "brand": "ARC"}, {"title": "Cars Theta", "url": "/products/8/", "summary": "Cars Theta — 针对多样应用的演示产品。", "type": "product", "brand": "ARC"}, {"title": "Cars Zeta", "url": "/products/6/", "summary": "Cars Zeta — 针对多样应用的演示产品。", "type": "product", "brand": "MONO"}, {"title": "供应与交付方案", "url": "/services/solution/", "summary": "灵活、可配置的供应交付服务。", "type": "service", "brand": ""}]
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><base href="/"><title>站内搜索</title><meta name="description" content="搜索本站内容"><meta property="og:type" content="website"><meta property="og:title" content="站内搜索"><meta property="og:description" content="搜索本站内容"><meta property="og:url" content="https://automotive.example.test/search/"><meta name="theme-color" content="#0e1c2c"><meta name="robots" content="noindex,follow"><link rel="canonical" href="https://automotive.example.test/search/"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/blocks.css"><link rel="stylesheet" href="assets/factory.css"><link rel="stylesheet" href="assets/pro-base.css"><link rel="stylesheet" href="assets/pro.css"><link rel="stylesheet" href="assets/commercial.css"><link rel="stylesheet" href="assets/custom.css"></head><body class="sf-theme-automotive pro-automotive"><a class="pro-skip" href="#main-content">跳转至正文</a>
|
||||
|
||||
<div class="pro-utility"><div class="pro-wrap"><span>GLOBAL MOBILITY / CURATED SUPPLY</span><span>DRIVEN BY WHAT'S NEXT <i>✦</i></span></div></div>
|
||||
<header class="sf-site-head pro-header"><div class="pro-wrap pro-head-inner"><a class="pro-logo" href="/" aria-label="NOVA MOBILITY 首页"><span class="pro-logo-symbol">S<span>.</span></span><span class="pro-logo-name">NOVA MOBILITY<small> AUTOMOTIVE</small></span></a><button class="pro-mobile-toggle" type="button" data-pro-menu aria-controls="pro-navigation" aria-expanded="false" aria-label="打开导航">☰</button><nav id="pro-navigation" class="pro-navigation" aria-label="主导航"><a href="/">首页</a><a href="/products/">产品</a><a href="/brands/">品牌</a><a href="/news/">新闻</a><a href="/cases/">案例</a><a href="/about/">关于</a><a href="/contact/">联系</a><a class="pro-search-link" href="/search/" aria-label="站内搜索">⌕</a></nav><button class="pro-catalog-toggle" type="button" data-pro-catalog aria-controls="pro-mega-content" aria-expanded="false">品牌与系列 ▾</button><a href="/contact/" class="pro-header-cta">取得联系 <span>↗</span></a></div><div class="pro-mega pro-wrap" data-pro-mega id="pro-mega-content" hidden><span>探索品牌与系列</span><div><a href="/brands/arc/">ARC ↗</a><a href="/brands/mono/">MONO ↗</a><a href="/brands/nova/">NOVA ↗</a><a href="/categories/term-8d6647722ef3/">专业系列</a><a href="/categories/term-a68a8656a4dc/">定制系列</a><a href="/categories/term-79e34764f8ce/">旗舰系列</a><a href="/categories/term-95acf900fafc/">畅销系列</a></div></div></header>
|
||||
<section class="pro-inner-hero"><div class="pro-wrap"><span class="pro-eyebrow">SEARCH / CONTENT</span><h1>站内搜索</h1><p>搜索本网站的产品、品牌、新闻和案例。</p></div></section>
|
||||
<main id="main-content" class="sf-search-page pro-wrap"><label for="sf-search">输入关键词</label><input type="search" id="sf-search" placeholder="搜索产品、品牌、新闻、案例……" autofocus><div id="sf-search-count" aria-live="polite"></div><div id="sf-search-results"></div></main>
|
||||
|
||||
<main id="main-content" class="sf-canvas pro-custom-content" aria-hidden="true"></main>
|
||||
|
||||
<footer class="sf-site-foot pro-footer"><div class="pro-wrap"><div class="pro-footer-top"><div><span class="pro-eyebrow">DRIVEN BY PURPOSE</span><h2>NOVA MOBILITY</h2><p>此为 SiteForge 主题演示网站。公司、产品及内容均为虚构示例。</p></div><div class="pro-footer-links"><ul><li><a href="/">首页</a></li><li><a href="/products/">产品</a></li><li><a href="/brands/">品牌</a></li><li><a href="/news/">新闻</a></li><li><a href="/cases/">案例</a></li><li><a href="/about/">关于</a></li><li><a href="/contact/">联系</a></li></ul></div><div><span class="pro-eyebrow">LET'S TALK</span><p>hello@example.com</p></div></div><div class="pro-footer-bottom"><span>© 2026 NOVA MOBILITY</span><span>SITEFORGE / STATIC SITE</span></div></div></footer>
|
||||
|
||||
|
||||
<script defer src="assets/site-runtime.js"></script><script defer src="assets/pro.js"></script></body></html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user