diff --git a/tests/routes/hicad-manufacturing-extension.test.ts b/tests/routes/hicad-manufacturing-extension.test.ts index f86ab885d..72cc4508e 100644 --- a/tests/routes/hicad-manufacturing-extension.test.ts +++ b/tests/routes/hicad-manufacturing-extension.test.ts @@ -90,6 +90,8 @@ test.describe('HiCAD manufacturing extension', () => { projectName: '<工作台>', generatedAt: '2026-07-27 20:00', overallDimensions: { x: 120, y: 60, z: 92 }, + brandName: '由壹点AI', + brandLogoUrl: '/api/favicon?surface=hicad', snapshotDataUrl: 'data:image/png;base64,abc', bomRows: rows, }) @@ -101,6 +103,8 @@ test.describe('HiCAD manufacturing extension', () => { expect(html).toContain('右视图') expect(html).toContain('120 mm') expect(html).toContain('配件明细表(BOM)') + expect(html).toContain('由壹点AI') + expect(html).toContain('/api/favicon?surface=hicad') expect(html).toContain('@page { size: A3 landscape;') }) @@ -112,6 +116,9 @@ test.describe('HiCAD manufacturing extension', () => { projectName: '工作台生产图', generatedAt: '2026-07-27 20:00', overallDimensions: { x: 120, y: 60, z: 92 }, + brandName: '由壹点AI', + brandLogoUrl: + 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="80" height="80"%3E%3Crect width="80" height="80" fill="%23111827"/%3E%3Cpath d="M20 20h40v40H20z" fill="white"/%3E%3C/svg%3E', bomRows: rows, notes: '材料、表面处理、公差、连接方式和检验标准须在投产前由工程人员复核。', @@ -125,11 +132,13 @@ test.describe('HiCAD manufacturing extension', () => { clientWidth: document.documentElement.clientWidth, tableCount: document.querySelectorAll('table').length, dimensionViewCount: document.querySelectorAll('.dimension-view').length, + brandCount: document.querySelectorAll('.brand-name').length, })) expect(layout.scrollWidth).toBeLessThanOrEqual(layout.clientWidth) expect(layout.tableCount).toBe(1) expect(layout.dimensionViewCount).toBe(3) + expect(layout.brandCount).toBe(1) await page.screenshot({ path: testInfo.outputPath('engineering-sheet.png'), fullPage: true, diff --git a/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx b/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx index 84bc0b044..c10b8b65a 100644 --- a/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx +++ b/vendor/zoo-modeling-app/HicadManufacturingPanel.tsx @@ -5,7 +5,7 @@ import { useSingletons } from '@src/lib/boot' import { isModelingResponse } from '@src/lib/kcSdkGuards' import { engineStreamZoomToFit, engineViewIsometric } from '@src/lib/utils' import { uuidv4 } from '@src/lib/utils' -import { useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import toast from 'react-hot-toast' import { buildExplodeOffsets, @@ -31,6 +31,11 @@ type BomOverrides = Record< } > +type BrandSettings = { + name: string + logoUrl: string +} + function downloadText(filename: string, text: string, type: string) { const blob = new Blob([text], { type }) const url = URL.createObjectURL(blob) @@ -116,12 +121,48 @@ export function HicadManufacturingPanel({ const [snapshot, setSnapshot] = useState() const [exploded, setExploded] = useState(false) const [overrides, setOverrides] = useState({}) + const [brand, setBrand] = useState({ + name: '由壹点AI', + logoUrl: '/api/favicon?surface=hicad', + }) const [notes, setNotes] = useState( '材料、表面处理、公差、连接方式和检验标准须在投产前由工程人员复核。' ) const explodeOffsetsRef = useRef([]) const resolvedProjectName = projectName || 'HiCAD 模型' + useEffect(() => { + let active = true + void fetch('/api/site-settings?scope=chat', { + credentials: 'same-origin', + }) + .then((response) => { + if (!response.ok) throw new Error('Unable to load site settings') + return response.json() as Promise> + }) + .then((settings) => { + if (!active) return + const name = + typeof settings.siteName === 'string' && settings.siteName.trim() + ? settings.siteName.trim() + : '由壹点AI' + const configuredLogo = + typeof settings.siteLogo === 'string' && settings.siteLogo.trim() + ? settings.siteLogo.trim() + : typeof settings.siteLogoDark === 'string' && + settings.siteLogoDark.trim() + ? settings.siteLogoDark.trim() + : '/api/favicon?surface=hicad' + setBrand({ name, logoUrl: configuredLogo }) + }) + .catch(() => { + // Keep the built-in website branding when settings are unavailable. + }) + return () => { + active = false + } + }, []) + const sendModelingCommand = async (cmd: ModelingCmd) => { const response = await kclManager.engineCommandManager.sendSceneCommand({ type: 'modeling_cmd_req', @@ -387,6 +428,8 @@ export function HicadManufacturingPanel({ projectName: resolvedProjectName, generatedAt: new Date().toLocaleString('zh-CN'), overallDimensions: overall.dimensions, + brandName: brand.name, + brandLogoUrl: brand.logoUrl, snapshotDataUrl: snapshot, bomRows, notes, diff --git a/vendor/zoo-modeling-app/manufacturing.ts b/vendor/zoo-modeling-app/manufacturing.ts index 09b6f683e..e15c3d27d 100644 --- a/vendor/zoo-modeling-app/manufacturing.ts +++ b/vendor/zoo-modeling-app/manufacturing.ts @@ -35,6 +35,8 @@ export type EngineeringSheetInput = { projectName: string generatedAt: string overallDimensions: Point3d + brandName?: string + brandLogoUrl?: string snapshotDataUrl?: string bomRows: BomRow[] notes?: string @@ -328,6 +330,10 @@ export function createEngineeringSheetHtml(input: EngineeringSheetInput) { const snapshot = input.snapshotDataUrl ? `爆炸图` : '
暂无爆炸图快照
' + const brandName = escapeHtml(input.brandName || '由壹点AI') + const brandLogo = input.brandLogoUrl + ? `` + : '' return ` @@ -338,13 +344,20 @@ export function createEngineeringSheetHtml(input: EngineeringSheetInput) { @page { size: A3 landscape; margin: 12mm; } * { box-sizing: border-box; } body { margin: 0; color: #111827; font-family: Arial, "Microsoft YaHei", sans-serif; } - header { display: flex; align-items: end; justify-content: space-between; border-bottom: 2px solid #111827; padding-bottom: 8px; } + header { display: flex; align-items: end; justify-content: space-between; gap: 24px; border-bottom: 2px solid #111827; padding-bottom: 8px; } + .brand { display: flex; align-items: center; gap: 10px; min-width: 170px; } + .brand-logo { width: 38px; height: 38px; object-fit: contain; } + .brand-name { font-size: 18px; font-weight: 700; white-space: nowrap; } + .title { flex: 1; } h1 { margin: 0; font-size: 24px; } h2 { margin: 0 0 8px; font-size: 16px; } p { margin: 4px 0; } .meta { color: #4b5563; font-size: 12px; text-align: right; } .sheet-grid { display: grid; grid-template-columns: 1.05fr 1fr; gap: 16px; margin-top: 14px; } section { break-inside: avoid; } + .snapshot-wrap { position: relative; } + .snapshot-brand { position: absolute; left: 10px; top: 10px; z-index: 1; display: flex; align-items: center; gap: 6px; padding: 5px 8px; background: rgba(255,255,255,.9); border: 1px solid #d1d5db; font-size: 11px; font-weight: 700; } + .snapshot-brand img { width: 20px; height: 20px; object-fit: contain; } .snapshot { width: 100%; height: 310px; object-fit: contain; border: 1px solid #9ca3af; } .snapshot-empty { height: 310px; display: grid; place-items: center; border: 1px dashed #9ca3af; color: #6b7280; } .views { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } @@ -362,7 +375,11 @@ export function createEngineeringSheetHtml(input: EngineeringSheetInput) {
-
+
+ ${brandLogo} + ${brandName} +
+

${escapeHtml(input.projectName)} · 生产工艺图

总体尺寸:${formatDimension(dimensions.x)} × ${formatDimension(dimensions.y)} × ${formatDimension(dimensions.z)}

@@ -371,7 +388,10 @@ export function createEngineeringSheetHtml(input: EngineeringSheetInput) {

装配爆炸图

- ${snapshot} +
+
${brandLogo}${brandName}
+ ${snapshot} +

总体三视图与尺寸