本次提交对图纸工作台进行了全面的功能重构和交互体验升级,主要涵盖了数据结构优化、核心逻辑增强、以及全新的可视化交互功能。
### 主要更新内容:
#### 1. **核心应用逻辑 (app.js)**
- **数据结构优化**:
- 重构了初始化逻辑,现在每个图片被视为一个独立的 \drawing\ 对象,包含自身的坐标边界和关联图片,使数据模型更清晰。
- **状态管理**:
- 引入 \currentDrawingId\ 状态,用于精确追踪和控制当前显示的图纸。
- **核心功能增强**:
- **自动图纸切换**: 实现了 \getDrawingForItem\ 映射逻辑,当用户在结构树中选择不同层级的件号时,系统能自动切换到其关联的图纸。
- **件号重命名 (handleItemRename)**: 彻底重写了重命名逻辑,现在支持递归更新所有子孙件号的ID和层级关系,保证了复杂结构下数据的一致性。
- **件号更新 (handleItemUpdate)**: 增强了件号创建和更新的鲁棒性,在添加深层级子件号时,会自动创建不存在的父级装配件,简化了操作流程。
- **图片拖拽**: 新增 \updateImagePosition\ 处理器,允许用户在画布上直接拖动图片并更新其世界坐标。
#### 2. **数据编辑器 (components/DataEditor.js)**
- **UI/UX 改进**:
- 大幅扩展了可编辑字段,增加了中文名、英文名、规格、材料、比例和备注等详细属性。
- 界面现在能够智能区分零件和装配件,并对装配件的重量输入框进行只读处理,因为其重量由子件自动汇总计算。
- 为只读字段添加了明确的提示信息,优化了用户体验。
#### 3. **图纸查看器 (components/DrawingViewer.js)**
- **全新交互功能**:
- **画布平移与缩放**: 实现了完整的画布拖拽平移和鼠标滚轮缩放功能,操作更流畅。
- **表格与图片拖拽**: 用户现在可以直接在画布上拖动明细表 (\GraphicalTable\) 和图纸中的图片,实现自由布局。
- **点击创建件号**: 在画布空白处单击,会弹出输入框,允许用户在指定位置快速创建新的件号,并记录其在图纸上的标记位置 (\markerPosition\)。
- **UI 浮层与控件**:
- 在画布上增加了多个交互浮层,包括左上角的图纸-件号绑定工具、右上角的缩放按钮、以及两侧用于切换图纸的箭头按钮。
- 新增了动态输入框 (\inputBox\),用于响应画布点击事件,提升了操作的直观性。
- **渲染与状态**:
- \GraphicalTable\ 和 \<image>\ 元素现在具备了拖拽所需的所有属性和事件处理。
- 内部状态管理重构,以支持新的视图变换 (\iewTransform\) 和交互状态。
#### 4. **主页面 (index.html)**
- **项目依赖**:
- 将 React 的 CDN 依赖切换为本地 \endor\ 目录下的文件,提高了应用的稳定性和加载速度。
- 移除了 Babel 依赖,因为项目已统一使用 \React.createElement\ API,无需 JSX 编译。
- **样式系统**:
- 全面拥抱 Tailwind CSS 的 \@layer\ 规则,对样式进行了分层(theme, base, components, utilities),使 CSS 结构更清晰、更易于维护。
- 定义了全局 CSS 变量,统一了颜色、尺寸等设计规范。
162 lines
7.8 KiB
JavaScript
162 lines
7.8 KiB
JavaScript
function DataEditor({ item, items, setItems }) {
|
|
|
|
if (!item) {
|
|
return React.createElement('div', { className: 'workspace-panel flex items-center justify-center h-full' },
|
|
React.createElement('p', { className: 'text-gray-500' }, '未选择任何件号')
|
|
);
|
|
}
|
|
|
|
const isRoot = item.id === '__ROOT__';
|
|
|
|
if (isRoot) {
|
|
return React.createElement('div', { className: 'workspace-panel p-4 h-full' },
|
|
React.createElement('h2', { className: 'text-lg font-semibold mb-4' }, '编辑总装图'),
|
|
React.createElement('div', { className: 'space-y-4' },
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '总装图名称'),
|
|
React.createElement('input', {
|
|
type: 'text',
|
|
name: 'name',
|
|
value: item.name,
|
|
onChange: (e) => setItems(prev => ({
|
|
...prev,
|
|
['__ROOT__']: { ...prev['__ROOT__'], name: e.target.value }
|
|
})),
|
|
className: 'form-input'
|
|
})
|
|
),
|
|
React.createElement('p', { className: 'text-xs text-gray-500 mt-1' }, '这是项目的根节点,只可修改名称。')
|
|
)
|
|
);
|
|
}
|
|
|
|
const handleInputChange = (e) => {
|
|
const { name, value } = e.target;
|
|
const isNumber = ['quantity', 'weight'].includes(name);
|
|
|
|
// 添加输入验证
|
|
if (isNumber && value !== '' && (isNaN(value) || Number(value) < 0)) {
|
|
console.warn('请输入有效的正数');
|
|
return;
|
|
}
|
|
|
|
setItems(prevItems => ({
|
|
...prevItems,
|
|
[item.id]: {
|
|
...prevItems[item.id],
|
|
[name]: isNumber ? (value === '' ? 0 : Number(value)) : value
|
|
}
|
|
}));
|
|
};
|
|
|
|
const hasChildren = Array.isArray(item.children) && item.children.length > 0;
|
|
const isLeafPart = !isRoot && !hasChildren;
|
|
const isAssemblyPart = !isRoot && hasChildren;
|
|
|
|
return React.createElement('div', { className: 'workspace-panel p-4 h-full' },
|
|
React.createElement('h2', { className: 'text-lg font-semibold mb-1' }, `编辑: ${item.id}`),
|
|
React.createElement('p', { className: 'text-xs text-gray-500 mb-3' }, isLeafPart ? '类型:零件' : (isAssemblyPart ? '类型:装配件(含子件号)' : '')),
|
|
React.createElement('div', { className: 'space-y-4' },
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '件号 (不可修改)'),
|
|
React.createElement('input', { type: 'text', readOnly: true, value: item.id, className: 'form-input bg-gray-100' })
|
|
),
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '名称'),
|
|
React.createElement('input', { type: 'text', name: 'name', value: item.name, onChange: handleInputChange, className: 'form-input' })
|
|
),
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '数量'),
|
|
React.createElement('input', { type: 'number', name: 'quantity', value: item.quantity, onChange: handleInputChange, className: 'form-input' })
|
|
),
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '重量 (kg)'),
|
|
React.createElement('input', {
|
|
type: 'number',
|
|
name: 'weight',
|
|
value: item.weight,
|
|
onChange: handleInputChange,
|
|
className: 'form-input',
|
|
readOnly: hasChildren, // 装配件重量由子项计算
|
|
title: hasChildren ? '装配件重量由子项自动汇总' : ''
|
|
}),
|
|
hasChildren && React.createElement('p', { className: 'text-xs text-gray-500 mt-1' }, '装配件重量由子项自动汇总,此处不可编辑。')
|
|
),
|
|
|
|
// 添加表格需要的额外字段
|
|
React.createElement('div', { className: 'grid grid-cols-2 gap-3' },
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '中文名称'),
|
|
React.createElement('input', {
|
|
type: 'text',
|
|
name: 'chinese_name',
|
|
value: item.chinese_name || '',
|
|
onChange: handleInputChange,
|
|
className: 'form-input',
|
|
placeholder: '零件中文名称'
|
|
})
|
|
),
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '英文名称'),
|
|
React.createElement('input', {
|
|
type: 'text',
|
|
name: 'english_name',
|
|
value: item.english_name || '',
|
|
onChange: handleInputChange,
|
|
className: 'form-input',
|
|
placeholder: 'English Name'
|
|
})
|
|
)
|
|
),
|
|
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '规格描述'),
|
|
React.createElement('input', {
|
|
type: 'text',
|
|
name: 'specification',
|
|
value: item.specification || '',
|
|
onChange: handleInputChange,
|
|
className: 'form-input',
|
|
placeholder: '零件规格和技术要求'
|
|
})
|
|
),
|
|
|
|
React.createElement('div', { className: 'grid grid-cols-2 gap-3' },
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '材料'),
|
|
React.createElement('input', {
|
|
type: 'text',
|
|
name: 'material',
|
|
value: item.material || '',
|
|
onChange: handleInputChange,
|
|
className: 'form-input',
|
|
placeholder: '如: Q235'
|
|
})
|
|
),
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '比例'),
|
|
React.createElement('input', {
|
|
type: 'text',
|
|
name: 'scale',
|
|
value: item.scale || '1:1',
|
|
onChange: handleInputChange,
|
|
className: 'form-input'
|
|
})
|
|
)
|
|
),
|
|
|
|
React.createElement('div', {},
|
|
React.createElement('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, '备注'),
|
|
React.createElement('textarea', {
|
|
name: 'remark',
|
|
value: item.remark || '',
|
|
onChange: handleInputChange,
|
|
className: 'form-input resize-none',
|
|
rows: 2,
|
|
placeholder: '其他说明信息'
|
|
})
|
|
)
|
|
)
|
|
);
|
|
}
|