Laservall_solidworks_inject/bom-ui/src/components/ToolBar.tsx

206 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { Space, Button, Segmented, Badge, message, Modal, Table, Tag } from 'antd';
import { apiPost, getExportUrl } from '../api/client';
import type { SaveChangeDto, BomItemDto, MatchMaterialRequest, MatchMaterialResponse, MatchMaterialResultDto } from '../api/types';
interface ToolBarProps {
dirtyCount: number;
viewMode: 'hierarchical' | 'flat';
onViewModeChange: (mode: 'hierarchical' | 'flat') => void;
onRefresh: () => void;
onOpenConfig: () => void;
getDirtyChanges: () => SaveChangeDto[];
onSaveSuccess: (changes: SaveChangeDto[]) => void;
items: BomItemDto[];
}
export const ToolBar: React.FC<ToolBarProps> = ({
dirtyCount,
viewMode,
onViewModeChange,
onRefresh,
onOpenConfig,
getDirtyChanges,
onSaveSuccess,
items,
}) => {
const [saving, setSaving] = useState(false);
const [matching, setMatching] = useState(false);
const [matchResults, setMatchResults] = useState<MatchMaterialResultDto[]>([]);
const [matchModalVisible, setMatchModalVisible] = useState(false);
const [matchSaving, setMatchSaving] = useState(false);
const handleSave = async () => {
if (dirtyCount === 0) return;
setSaving(true);
try {
const changes = getDirtyChanges();
const res = await apiPost<any>('/api/bom/save', { Changes: changes });
if (res.Success) {
message.success(`保存成功,共更新 ${res.SavedCount}`);
onSaveSuccess(changes);
} else {
message.error(`保存失败: ${res.Error}`);
}
} catch (e: any) {
message.error(`保存失败: ${e.message}`);
} finally {
setSaving(false);
}
};
const handleExport = () => {
window.open(getExportUrl(viewMode === 'flat'), '_blank');
};
const handleMatchMaterial = async () => {
const emptyItems = items.filter(
(item) => !item.IsAssembly && !item.Props?.['物料编码']
);
if (emptyItems.length === 0) {
message.info('所有零件的物料编码均已填写');
return;
}
setMatching(true);
try {
const request: MatchMaterialRequest = {
Items: emptyItems.map((item) => ({
DrawingNo: item.DrawingNo,
DocPath: item.DocPath,
})),
};
const res = await apiPost<MatchMaterialResponse>('/api/bom/match-material', request);
if (!res.Success) {
message.error(`匹配失败: ${res.Error}`);
return;
}
const matched = res.Results.filter((r) => r.Matched);
if (matched.length === 0) {
message.info('未找到匹配的物料编码');
return;
}
setMatchResults(matched);
setMatchModalVisible(true);
} catch (e: any) {
message.error(`匹配失败: ${e.message}`);
} finally {
setMatching(false);
}
};
const handleConfirmMatch = async () => {
setMatchSaving(true);
try {
const changes: SaveChangeDto[] = matchResults.map((r) => ({
DocPath: r.DocPath,
Key: '物料编码',
Value: r.PartCode,
}));
const res = await apiPost<any>('/api/bom/save', { Changes: changes });
if (res.Success) {
message.success(`写入成功,共更新 ${res.Results?.length ?? changes.length} 项物料编码`);
onSaveSuccess(changes);
setMatchModalVisible(false);
setMatchResults([]);
} else {
message.error(`写入失败: ${res.Error}`);
}
} catch (e: any) {
message.error(`写入失败: ${e.message}`);
} finally {
setMatchSaving(false);
}
};
const matchColumns = [
{
title: '图号',
dataIndex: 'DrawingNo',
key: 'DrawingNo',
width: 260,
},
{
title: '物料编码',
dataIndex: 'PartCode',
key: 'PartCode',
width: 180,
},
{
title: 'PLM零件名称',
dataIndex: 'PartName',
key: 'PartName',
width: 180,
},
{
title: '状态',
key: 'status',
width: 80,
render: (_: unknown, record: MatchMaterialResultDto) =>
record.Matched ? <Tag color="green"></Tag> : <Tag color="red"></Tag>,
},
];
return (
<>
<Space style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<Space>
<Badge count={dirtyCount}>
<Button type="primary" onClick={handleSave} disabled={dirtyCount === 0} loading={saving}>
</Button>
</Badge>
<Button onClick={handleExport}></Button>
<Button onClick={onRefresh}></Button>
<Button onClick={onOpenConfig}></Button>
<Button onClick={handleMatchMaterial} loading={matching}>
</Button>
</Space>
<Space>
<Segmented
options={[
{ label: '层级视图', value: 'hierarchical' },
{ label: '扁平视图', value: 'flat' },
]}
value={viewMode}
onChange={(val) => onViewModeChange(val as 'hierarchical' | 'flat')}
/>
<div style={{ marginLeft: 16, color: '#52c41a' }}> </div>
</Space>
</Space>
<Modal
title={`匹配物料编号 (${matchResults.length} 项匹配)`}
open={matchModalVisible}
onOk={handleConfirmMatch}
onCancel={() => {
setMatchModalVisible(false);
setMatchResults([]);
}}
okText="确认写入"
cancelText="取消"
confirmLoading={matchSaving}
width={760}
>
<p style={{ marginBottom: 12, color: '#666' }}>
SolidWorks文件属性
</p>
<Table
dataSource={matchResults}
columns={matchColumns}
rowKey="DocPath"
size="small"
pagination={false}
scroll={{ y: 400 }}
/>
</Modal>
</>
);
};