feat: brand HiCAD production sheets
This commit is contained in:
parent
7435e7b0f0
commit
d93b2e8ea7
@ -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,
|
||||
|
||||
@ -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<string>()
|
||||
const [exploded, setExploded] = useState(false)
|
||||
const [overrides, setOverrides] = useState<BomOverrides>({})
|
||||
const [brand, setBrand] = useState<BrandSettings>({
|
||||
name: '由壹点AI',
|
||||
logoUrl: '/api/favicon?surface=hicad',
|
||||
})
|
||||
const [notes, setNotes] = useState(
|
||||
'材料、表面处理、公差、连接方式和检验标准须在投产前由工程人员复核。'
|
||||
)
|
||||
const explodeOffsetsRef = useRef<ExplodeOffset[]>([])
|
||||
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<Record<string, unknown>>
|
||||
})
|
||||
.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,
|
||||
|
||||
26
vendor/zoo-modeling-app/manufacturing.ts
vendored
26
vendor/zoo-modeling-app/manufacturing.ts
vendored
@ -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
|
||||
? `<img class="snapshot" src="${escapeHtml(input.snapshotDataUrl)}" alt="爆炸图"/>`
|
||||
: '<div class="snapshot-empty">暂无爆炸图快照</div>'
|
||||
const brandName = escapeHtml(input.brandName || '由壹点AI')
|
||||
const brandLogo = input.brandLogoUrl
|
||||
? `<img class="brand-logo" src="${escapeHtml(input.brandLogoUrl)}" alt="${brandName}"/>`
|
||||
: ''
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
@ -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) {
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div>
|
||||
<div class="brand">
|
||||
${brandLogo}
|
||||
<span class="brand-name">${brandName}</span>
|
||||
</div>
|
||||
<div class="title">
|
||||
<h1>${escapeHtml(input.projectName)} · 生产工艺图</h1>
|
||||
<p>总体尺寸:${formatDimension(dimensions.x)} × ${formatDimension(dimensions.y)} × ${formatDimension(dimensions.z)}</p>
|
||||
</div>
|
||||
@ -371,7 +388,10 @@ export function createEngineeringSheetHtml(input: EngineeringSheetInput) {
|
||||
<main class="sheet-grid">
|
||||
<section>
|
||||
<h2>装配爆炸图</h2>
|
||||
${snapshot}
|
||||
<div class="snapshot-wrap">
|
||||
<div class="snapshot-brand">${brandLogo}<span>${brandName}</span></div>
|
||||
${snapshot}
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>总体三视图与尺寸</h2>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user