/** * Gemold - 模具制造管理系统 * 版本: 4.0.0 */ const { createApp, ref, computed, onMounted, reactive, watch, nextTick } = Vue; const { createRouter, createWebHistory, useRoute, useRouter } = VueRouter; const appState = reactive({ user: null, token: null, loading: false, notifications: [], initialized: false }); function formatFileSize(bytes) { if (!bytes || bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } function formatNumber(num) { if (num === null || num === undefined) return "N/A"; if (num >= 1_000_000) return (num / 1_000_000).toFixed(2) + "M"; if (num >= 1_000) return (num / 1_000).toFixed(2) + "K"; return num.toFixed ? num.toFixed(2) : String(num); } function formatDateTime(dateString) { if (!dateString) return "N/A"; try { const date = new Date(dateString); if (isNaN(date.getTime())) return dateString; const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } catch { return dateString; } } function formatDate(dateString) { if (!dateString) return "N/A"; try { return new Date(dateString).toLocaleDateString('zh-CN'); } catch { return dateString; } } function formatCurrency(amount) { if (amount === null || amount === undefined) return "¥0.00"; return "¥" + Number(amount).toFixed(2); } let notificationId = 0; function addNotification(message, type = 'info') { const id = ++notificationId; const notification = { id, message, type, timestamp: new Date(), visible: true }; appState.notifications.push(notification); setTimeout(() => { const index = appState.notifications.findIndex(n => n.id === id); if (index > -1) { appState.notifications[index].visible = false; setTimeout(() => { const idx = appState.notifications.findIndex(n => n.id === id); if (idx > -1) appState.notifications.splice(idx, 1); }, 300); } }, 5000); } function handleApiError(error, context = '') { console.error(`API错误 [${context}]:`, error); const message = error.message || '请求失败,请稍后重试'; addNotification(message, 'error'); return message; } async function apiRequest(url, options = {}) { const headers = { 'Content-Type': 'application/json', ...options.headers }; if (appState.token) { headers['Authorization'] = `Bearer ${appState.token}`; } const response = await fetch(url, { ...options, headers }); if (response.status === 401) { appState.user = null; appState.token = null; localStorage.removeItem('token'); localStorage.removeItem('user'); throw new Error('登录已过期,请重新登录'); } if (!response.ok) { const error = await response.json().catch(() => ({ detail: '请求失败' })); throw new Error(error.detail || '请求失败'); } return response.json(); } function saveAuth(token, user) { appState.token = token; appState.user = user; localStorage.setItem('token', token); localStorage.setItem('user', JSON.stringify(user)); } function clearAuth() { appState.token = null; appState.user = null; localStorage.removeItem('token'); localStorage.removeItem('user'); } function initAuth() { const token = localStorage.getItem('token'); const userStr = localStorage.getItem('user'); if (token && userStr) { try { appState.token = token; appState.user = JSON.parse(userStr); } catch { clearAuth(); } } appState.initialized = true; } const App = { setup() { const route = useRoute(); const router = useRouter(); const menuItems = computed(() => { const items = [ { path: '/', label: '首页', icon: '⌂' }, { path: '/moldinsight', label: 'MoldInsight', icon: '◈' }, { path: '/inventory', label: '进销存', icon: '⊞' } ]; if (appState.user?.is_superuser) { items.push({ path: '/users', label: '用户管理', icon: '👤' }); } return items; }); const isActive = (path) => { if (path === '/') return route.path === '/'; return route.path.startsWith(path); }; const handleLogout = async () => { try { await apiRequest('/api/auth/logout', { method: 'POST' }); } catch {} clearAuth(); addNotification('已退出登录', 'success'); router.push('/login'); }; onMounted(() => { initAuth(); }); const getPriorityText = (priority) => { const priorityMap = { 'critical': '紧急', 'high': '高', 'medium': '中', 'low': '低' }; return priorityMap[priority] || priority; }; return { route, router, appState, menuItems, isActive, handleLogout, getPriorityText, dismissNotification: (id) => { const index = appState.notifications.findIndex(n => n.id === id); if (index > -1) { appState.notifications[index].visible = false; setTimeout(() => { const idx = appState.notifications.findIndex(n => n.id === id); if (idx > -1) appState.notifications.splice(idx, 1); }, 300); } } }; }, template: `
{{ notification.type === 'success' ? '✓' : notification.type === 'error' ? '✕' : notification.type === 'warning' ? '!' : 'i' }}
{{ notification.message }}
G
Gemold
模具制造管理系统
{{ item.label }}
`, }; const LoginView = { setup() { const router = useRouter(); const state = reactive({ username: '', password: '', loading: false, error: '' }); onMounted(() => { if (appState.user) { router.push('/'); } }); const handleSubmit = async () => { if (!state.username || !state.password) { state.error = '请填写用户名和密码'; return; } state.loading = true; state.error = ''; try { const res = await fetch('/api/auth/login/json', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: state.username, password: state.password }) }); if (!res.ok) { const error = await res.json(); throw new Error(error.detail || '登录失败'); } const data = await res.json(); saveAuth(data.access_token, data.user); addNotification('登录成功', 'success'); router.push('/'); } catch (e) { state.error = e.message; addNotification(e.message, 'error'); } finally { state.loading = false; } }; return { state, handleSubmit }; }, template: `
G

欢迎回来

登录到 Gemold 系统

{{ state.error }}

如需开通账号,请联系管理员

` }; const HomeView = { setup() { const router = useRouter(); const state = reactive({ stats: null, loading: true }); const loadStats = async () => { try { const [inventoryStats, health] = await Promise.all([ apiRequest('/api/dashboard').catch(() => null), apiRequest('/health').catch(() => null) ]); state.stats = { inventory: inventoryStats, health }; } catch (e) { handleApiError(e, '加载统计数据'); } finally { state.loading = false; } }; onMounted(() => { if (!appState.user) { router.push('/login'); return; } loadStats(); }); return { state, formatNumber, formatCurrency, appState }; }, template: `

欢迎回来,{{ appState.user?.full_name || appState.user?.username }}

系统概览

正在加载数据...

📦
{{ state.stats?.inventory?.product_count || 0 }}
产品数量
📊
{{ state.stats?.inventory?.total_stock || 0 }}
库存总量
💰
{{ formatCurrency(state.stats?.inventory?.total_value || 0) }}
库存价值
⚙️
{{ state.stats?.health?.total_tasks || 0 }}
分析任务
🏭
{{ state.stats?.inventory?.supplier_count || 0 }}
供应商
👥
{{ state.stats?.inventory?.customer_count || 0 }}
客户

低库存预警

SKU 产品名称 当前库存 最低库存
{{ item.sku }} {{ item.name }} {{ item.quantity }} {{ item.min_stock }}

快速操作

` }; const UsersView = { setup() { const router = useRouter(); const state = reactive({ users: [], roles: [], loading: true, showUserModal: false, editingUser: null, userForm: { username: '', email: '', password: '', full_name: '', role_ids: [] } }); const loadUsers = async () => { try { state.users = await apiRequest('/api/auth/users'); } catch (e) { handleApiError(e, '加载用户列表'); } finally { state.loading = false; } }; const loadRoles = async () => { try { state.roles = await apiRequest('/api/auth/roles'); } catch (e) { handleApiError(e, '加载角色列表'); } }; const openUserModal = (user = null) => { state.editingUser = user; if (user) { state.userForm = { username: user.username, email: user.email, password: '', full_name: user.full_name || '', role_ids: user.roles.map(r => { const role = state.roles.find(role => role.code === r); return role ? role.id : null; }).filter(id => id !== null) }; } else { state.userForm = { username: '', email: '', password: '', full_name: '', role_ids: [] }; } state.showUserModal = true; }; const saveUser = async () => { if (!state.userForm.username || !state.userForm.email) { addNotification('请填写用户名和邮箱', 'error'); return; } if (!state.editingUser && !state.userForm.password) { addNotification('请填写密码', 'error'); return; } try { if (state.editingUser) { await apiRequest(`/api/auth/users/${state.editingUser.id}`, { method: 'PUT', body: JSON.stringify({ email: state.userForm.email, full_name: state.userForm.full_name || null, role_ids: state.userForm.role_ids }) }); addNotification('用户更新成功', 'success'); } else { await apiRequest('/api/auth/users', { method: 'POST', body: JSON.stringify(state.userForm) }); addNotification('用户创建成功', 'success'); } state.showUserModal = false; loadUsers(); } catch (e) { handleApiError(e, '保存用户'); } }; const deleteUser = async (user) => { if (!confirm(`确定要删除用户 ${user.username} 吗?`)) return; try { await apiRequest(`/api/auth/users/${user.id}`, { method: 'DELETE' }); addNotification('用户已删除', 'success'); loadUsers(); } catch (e) { handleApiError(e, '删除用户'); } }; const resetPassword = async (user) => { const newPassword = prompt(`请输入 ${user.username} 的新密码:`); if (!newPassword || newPassword.length < 6) { addNotification('密码长度至少6位', 'error'); return; } try { await apiRequest(`/api/auth/users/${user.id}/reset-password`, { method: 'PUT', body: JSON.stringify(newPassword) }); addNotification('密码已重置', 'success'); } catch (e) { handleApiError(e, '重置密码'); } }; onMounted(async () => { if (!appState.user?.is_superuser) { router.push('/'); return; } await loadRoles(); loadUsers(); }); return { state, appState, openUserModal, saveUser, deleteUser, resetPassword, formatDateTime }; }, template: `

用户管理

管理系统用户和权限

加载中...
用户名 邮箱 姓名 状态 角色 注册时间 操作
{{ user.username }} {{ user.email }} {{ user.full_name || '-' }} {{ user.is_active ? '正常' : '禁用' }} {{ role }} {{ formatDateTime(user.created_at) }}

{{ state.editingUser ? '编辑用户' : '添加用户' }}

` }; const MoldInsightView = { setup() { const router = useRouter(); const state = reactive({ selectedFile: null, selectedMaterial: 'ABS', moldParams: { draftAngle: 2.0, shrinkageRate: 0.5, partingPrecision: 0.1, cavityMatch: 95 }, uploading: false, error: "", currentTask: null, polling: false, dragOver: false, progress: 0, history: null, expandedFiles: {} }); const loadHistory = async () => { try { state.history = await apiRequest('/api/history'); } catch (e) { console.error('加载历史记录失败:', e); } }; const toggleFileHistory = async (filename) => { if (state.expandedFiles[filename]) { state.expandedFiles[filename] = null; } else { try { const records = await apiRequest(`/api/history/${encodeURIComponent(filename)}`); state.expandedFiles[filename] = records; } catch (e) { handleApiError(e, '加载文件历史'); } } }; const viewResult = (record) => { router.push(`/moldinsight/result/${record.task_id}`); }; const handleFileChange = (event) => { const file = event.target.files[0]; if (!file) return; validateAndSelectFile(file); }; const validateAndSelectFile = (file) => { if (!file.name.toLowerCase().endsWith(".stp") && !file.name.toLowerCase().endsWith(".step")) { state.error = "请选择 STP 或 STEP 格式文件"; state.selectedFile = null; return; } if (file.size > 100 * 1024 * 1024) { state.error = "文件大小不能超过 100MB"; state.selectedFile = null; return; } state.error = ""; state.selectedFile = file; addNotification(`已选择文件: ${file.name}`, 'success'); }; const handleDrop = (event) => { event.preventDefault(); state.dragOver = false; const files = event.dataTransfer.files; if (files.length > 0) validateAndSelectFile(files[0]); }; const uploadFile = async () => { if (!state.selectedFile) return; state.uploading = true; state.error = ""; state.progress = 0; const formData = new FormData(); formData.append("file", state.selectedFile); formData.append("material", state.selectedMaterial); try { const res = await fetch("/api/upload", { method: "POST", headers: appState.token ? { 'Authorization': `Bearer ${appState.token}` } : {}, body: formData }); if (!res.ok) throw new Error(`上传失败: ${res.status}`); const data = await res.json(); state.currentTask = { task_id: data.task_id, status: "processing", filename: data.file_info?.filename }; addNotification('文件上传成功,开始分析...', 'success'); startPolling(data.task_id); } catch (e) { state.error = handleApiError(e, '文件上传'); } finally { state.uploading = false; } }; const startPolling = async (taskId) => { console.log('[Polling] 开始轮询任务:', taskId); state.polling = true; state.progress = 10; let pollCount = 0; const poll = async () => { try { pollCount++; state.progress = Math.min(90, 10 + pollCount * 0.5); const task = await apiRequest(`/api/status/${taskId}`, { method: 'POST' }); state.currentTask = task; state.task = task; if (task.status === "completed") { state.polling = false; state.progress = 100; addNotification('分析完成', 'success'); loadHistory(); router.push(`/moldinsight/result/${taskId}`); return; } if (task.status === "failed") { state.polling = false; state.error = task.error || "分析失败"; addNotification('分析失败', 'error'); return; } if (pollCount < 300) setTimeout(poll, 2000); } catch (e) { state.polling = false; state.error = handleApiError(e, '轮询状态'); } }; poll(); }; onMounted(() => { if (!appState.user) { router.push('/login'); return; } loadHistory(); }); const isFoamMaterial = (material) => { const foamMaterials = ['AlSi10Mg', 'AlSi12', 'Pure Al Foam', 'AlSi7Mg']; return foamMaterials.includes(material); }; return { state, handleFileChange, handleDrop, uploadFile, formatFileSize, formatDateTime, formatNumber, toggleFileHistory, viewResult, isFoamMaterial }; }, template: `

MoldInsight

STP 模具几何分析

📁
点击选择或拖拽文件 支持 .stp, .step 格式,最大 100MB
{{ state.selectedFile.name }} {{ formatFileSize(state.selectedFile.size) }}
📦 材料与参数设置
⚙️ 铝泡沫专用参数
{{ state.moldParams.draftAngle }}°
{{ state.moldParams.shrinkageRate }}%
{{ state.moldParams.partingPrecision }} mm
{{ state.moldParams.cavityMatch }}%
{{ state.error }}

分析历史

文件名 上传次数 文件大小 最新状态 最新分析时间 操作
` }; const ResultView = { setup() { const route = useRoute(); const router = useRouter(); const state = reactive({ task: null, loading: true, error: '' }); const loadTask = async () => { console.log('[ResultView] loadTask 开始, taskId:', route.params.taskId); try { state.task = await apiRequest(`/api/status/${route.params.taskId}`, { method: 'POST' }); console.log('任务数据:', state.task); console.log('key_info:', state.task.key_info); console.log('cavity_data:', state.task.cavity_data); console.log('analysis_result:', state.task.analysis_result); console.log('geometry_data:', state.task.geometry_data); } catch (e) { state.error = handleApiError(e, '加载任务详情'); } finally { state.loading = false; } }; onMounted(() => { if (!appState.user) { router.push('/login'); return; } loadTask(); }); const getPriorityText = (priority) => { const priorityMap = { 'critical': '紧急', 'high': '高', 'medium': '中', 'low': '低' }; return priorityMap[priority] || priority; }; const isFoamMaterial = (material) => { return material === 'aluminum_foam'; }; return { state, formatFileSize, formatDateTime, formatNumber, getPriorityText, isFoamMaterial }; }, template: `

分析结果

加载中...

{{ state.error }}

{{ state.task.filename }}

{{ state.task.status }}

文件信息

文件大小 {{ formatFileSize(state.task.file_size) }}
分析时间 {{ formatDateTime(state.task.completed_at) }}
状态 {{ state.task.status }}

几何数据

顶点数 {{ formatNumber(state.task.mesh_summary.vertex_count) }}
面数 {{ formatNumber(state.task.mesh_summary.face_count) }}
边数 {{ formatNumber(state.task.geometry_data?.topology?.edge_count || state.task.geometry_data?.topology?.edges || state.task.mesh_summary?.edge_count || 'N/A') }}
体积 {{ formatNumber(state.task.geometry_data.volume) }} mm³
表面积 {{ formatNumber(state.task.geometry_data.surface_area) }} mm²

模具信息

基本信息

零件名称 {{ state.task.cavity_data?.metadata?.part_name || state.task.key_info?.metadata?.part_name || 'N/A' }}
材料 {{ state.task.cavity_data?.metadata?.material || state.task.key_info?.metadata?.material || 'N/A' }}

型腔信息

型腔数量 {{ Object.keys(state.task.cavity_data?.mold_cavities || state.task.key_info?.mold_cavities || {}).length }}

3D 预览

分析结果详情

FreeCAD 几何验证

{{ state.task.verification.status === 'passed' ? '验证通过' : state.task.verification.status === 'failed' ? '验证失败' : state.task.verification.status === 'error' ? '验证错误' : state.task.verification.status === 'skipped' ? '已跳过' : '未知' }}
体积差异 {{ state.task.verification.comparison.volume.difference_percent?.toFixed(4) || 0 }}%
表面积差异 {{ state.task.verification.comparison.surface_area.difference_percent?.toFixed(4) || 0 }}%

分析摘要

📋
{{ state.task.analysis_result.analysis_summary }}

1. 产品特征识别

整体轮廓和尺寸比例 {{ state.task.analysis_result?.geometry_data?.bounding_box?.dimensions ? (state.task.analysis_result.geometry_data.bounding_box.dimensions[0]?.toFixed(1) || 0) + ' × ' + (state.task.analysis_result.geometry_data.bounding_box.dimensions[1]?.toFixed(1) || 0) + ' × ' + (state.task.analysis_result.geometry_data.bounding_box.dimensions[2]?.toFixed(1) || 0) + ' mm' : 'N/A' }}
壁厚分布 {{ state.task.analysis_result.detected_features?.find(f => f.feature_type === 'wall_thickness')?.description || '待分析' }}
加强筋位置和密度 {{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === 'rib').length || 0 }} 个加强筋
孔洞和凹槽位置 {{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === 'hole' || f.feature_type === 'pocket').length || 0 }} 个孔洞/凹槽
倒扣区域检测 {{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === 'undercut').length || 0 }} 个倒扣区域
对称性分析 {{ state.task.analysis_result.geometry_data?.symmetry || '非对称' }}
重心位置 {{ state.task.analysis_result.geometry_data?.center_of_mass ? '(' + (state.task.analysis_result.geometry_data.center_of_mass[0]?.toFixed(2) || 0) + ', ' + (state.task.analysis_result.geometry_data.center_of_mass[1]?.toFixed(2) || 0) + ', ' + (state.task.analysis_result.geometry_data.center_of_mass[2]?.toFixed(2) || 0) + ') mm' : 'N/A' }}

2. 泡沫包装设计决策

泡沫厚度建议 {{ state.task.analysis_result.design_recommendations?.find(r => r.priority === 'high')?.recommendation || '根据产品重量和脆弱程度自动计算' }}
加强筋布局 基于产品薄弱区域自动布置
取手槽位置 基于重心位置:{{ state.task.analysis_result.geometry_data?.center_of_mass ? '自动优化' : '手动设置' }}
通风孔位置 防止真空吸附:建议在产品最大平面区域设置通风孔
定位结构设计 {{ state.task.analysis_result.detected_features?.filter(f => f.feature_type === '定位').length || 0 }} 个定位特征
分型面选择 基于产品几何:自动推荐最优分型面
详细设计建议
{{ getPriorityText(rec.priority) }} {{ rec.description }} ({{ rec.reason }})

3. 模具工程决策

型腔数量建议 {{ state.task.cavity_data?.mold_cavities ? Object.keys(state.task.cavity_data.mold_cavities).length : 1 }} 腔
模架尺寸 {{ state.task.key_info?.mold_parameters?.mold_size || '基于泡沫外形自动计算' }}
预估锁模力 {{ state.task.key_info.geometric_characteristics.estimated_clamping_force || '自动计算' }} 吨
顶出系统 顶针顶出(自动布局)
冷却水路设计 {{ state.task.analysis_result.geometry_data?.volume > 1000000 ? '需要冷却水路' : '自然冷却' }}
材料选择 {{ state.task.analysis_result.quality_metrics?.volume ? '根据产量和精度自动推荐' : '7075 铝合金' }}
体积利用率 {{ (state.task.analysis_result.quality_metrics.volume_utilization * 100)?.toFixed(1) || 'N/A' }}%
拓扑复杂度 {{ state.task.analysis_result.quality_metrics.topology_complexity?.toFixed(2) || 'N/A' }}
壁厚均匀性 {{ (state.task.analysis_result.quality_metrics.wall_uniformity * 100)?.toFixed(1) || 'N/A' }}%
翘曲风险 {{ state.task.key_info.quality_considerations.warpage_risk || '低风险' }}
潜在焊缝线 {{ state.task.key_info.quality_considerations.potential_weld_lines || 0 }} 条
` }; const InventoryView = { setup() { const router = useRouter(); const route = useRoute(); const state = reactive({ activeTab: 'dashboard', dashboard: null, financeSummary: null, financePeriod: { year: new Date().getFullYear(), quarter: '' }, financeTransactions: [], receivables: [], payables: [], customerFinanceStatement: [], supplierFinanceStatement: [], customerProductStatement: [], supplierProductStatement: [], products: [], materials: [], purchaseOrders: [], purchaseWarehouseId: null, purchaseReceiveItems: [], productionOrders: [], productionPlan: null, productionWarehouseId: null, suppliers: [], customers: [], warehouses: [], inventory: [], movements: [], loading: false, showModal: false, modalType: '', editingItem: null, productBomItems: [], form: {} }); const getMovementTypeLabel = (movementType) => { const movementLabelMap = { in: '其他入库', out: '其他出库', adjust: '库存调整', purchase_in: '采购入库', return_from_production: '生产退料入库', outsource_return: '外协回库', finish_in: '完工入库', issue_to_production: '生产领料出库', outsource_send: '外协发料出库', shipment_out: '销售出库', scrap_out: '报废出库' }; return movementLabelMap[movementType] || movementType; }; const getMovementBadgeClass = (movementType) => { if (['purchase_in', 'return_from_production', 'outsource_return', 'finish_in', 'in'].includes(movementType)) { return 'badge-success'; } if (['issue_to_production', 'outsource_send', 'shipment_out', 'scrap_out', 'out'].includes(movementType)) { return 'badge-error'; } return 'badge-warning'; }; const loadDashboard = async () => { state.loading = true; try { state.dashboard = await apiRequest('/api/dashboard'); } catch (e) { handleApiError(e, '加载仪表盘'); } finally { state.loading = false; } }; const loadProducts = async () => { state.loading = true; try { state.products = await apiRequest('/api/products?limit=100'); } catch (e) { handleApiError(e, '加载产品'); } finally { state.loading = false; } }; const loadMaterials = async () => { state.loading = true; try { state.materials = await apiRequest('/api/products?item_type=material&limit=100'); } catch (e) { handleApiError(e, '加载物料'); } finally { state.loading = false; } }; const loadWarehouses = async () => { state.loading = true; try { state.warehouses = await apiRequest('/api/warehouses'); } catch (e) { handleApiError(e, '加载仓库'); } finally { state.loading = false; } }; const ensureStockBaseData = async () => { if (!state.materials.length) { await loadMaterials(); } if (!state.warehouses.length) { await loadWarehouses(); } if (!state.warehouses.length) { try { await apiRequest('/api/warehouses', { method: 'POST', body: JSON.stringify({ name: '默认仓库' }) }); await loadWarehouses(); addNotification('已自动创建默认仓库', 'success'); } catch (e) { handleApiError(e, '自动创建默认仓库'); } } }; const loadSuppliers = async () => { state.loading = true; try { state.suppliers = await apiRequest('/api/suppliers'); } catch (e) { handleApiError(e, '加载供应商'); } finally { state.loading = false; } }; const loadProductionOrders = async () => { state.loading = true; try { const [orders, warehouses] = await Promise.all([ apiRequest('/api/sales-orders?limit=100'), apiRequest('/api/warehouses') ]); state.productionOrders = orders || []; state.warehouses = warehouses || []; if (!state.productionWarehouseId) { state.productionWarehouseId = state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null; } } catch (e) { handleApiError(e, '加载按单生产数据'); } finally { state.loading = false; } }; const loadPurchaseOrders = async () => { state.loading = true; try { const [orders, warehouses] = await Promise.all([ apiRequest('/api/purchase-orders?limit=100'), apiRequest('/api/warehouses') ]); state.purchaseOrders = orders || []; state.warehouses = warehouses || []; if (!state.purchaseWarehouseId) { state.purchaseWarehouseId = state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null; } } catch (e) { handleApiError(e, '加载采购订单'); } finally { state.loading = false; } }; const loadCustomers = async () => { state.loading = true; try { state.customers = await apiRequest('/api/customers'); } catch (e) { handleApiError(e, '加载客户'); } finally { state.loading = false; } }; const loadInventory = async () => { state.loading = true; try { state.inventory = await apiRequest('/api/inventory'); } catch (e) { handleApiError(e, '加载库存'); } finally { state.loading = false; } }; const loadMovements = async () => { state.loading = true; try { state.movements = await apiRequest('/api/stock-movements'); } catch (e) { handleApiError(e, '加载变动记录'); } finally { state.loading = false; } }; const loadFinance = async () => { state.loading = true; try { const selectedYear = Number(state.financePeriod.year) || new Date().getFullYear(); const selectedQuarter = state.financePeriod.quarter ? Number(state.financePeriod.quarter) : null; const periodQuery = selectedQuarter ? `year=${selectedYear}&quarter=${selectedQuarter}` : `year=${selectedYear}`; const [ summary, transactions, receivables, payables, customerStatement, supplierStatement, customerProductStatement, supplierProductStatement ] = await Promise.all([ apiRequest(`/api/finance/summary?${periodQuery}`), apiRequest(`/api/finance/transactions?status=confirmed&limit=20&${periodQuery}`), apiRequest(`/api/finance/receivables?limit=20&${periodQuery}`), apiRequest(`/api/finance/payables?limit=20&${periodQuery}`), apiRequest(`/api/finance/partner-statement/customer?${periodQuery}`), apiRequest(`/api/finance/partner-statement/supplier?${periodQuery}`), apiRequest(`/api/finance/partner-product-statement/customer?${periodQuery}`), apiRequest(`/api/finance/partner-product-statement/supplier?${periodQuery}`) ]); state.financeSummary = summary; state.financeTransactions = transactions; state.receivables = receivables; state.payables = payables; state.customerFinanceStatement = customerStatement.items || []; state.supplierFinanceStatement = supplierStatement.items || []; state.customerProductStatement = customerProductStatement.items || []; state.supplierProductStatement = supplierProductStatement.items || []; } catch (e) { handleApiError(e, '加载财务数据'); } finally { state.loading = false; } }; const refreshFinanceByPeriod = () => { if (state.activeTab === 'finance') { loadFinance(); } }; const switchTab = (tab) => { state.activeTab = tab; switch (tab) { case 'dashboard': loadDashboard(); break; case 'products': loadProducts(); break; case 'suppliers': loadSuppliers(); break; case 'customers': loadCustomers(); break; case 'inventory': loadInventory(); break; case 'movements': loadMovements(); break; case 'purchases': loadPurchaseOrders(); break; case 'production': loadProductionOrders(); break; case 'finance': loadFinance(); break; } }; const loadOrderProductionPlan = async (orderId) => { try { state.productionPlan = await apiRequest(`/api/sales-orders/${orderId}/production-plan`); } catch (e) { handleApiError(e, '加载领料建议'); } }; const issueOrderMaterials = async (order) => { if (!state.productionWarehouseId) { addNotification('请先选择领料仓库', 'warning'); return; } try { const result = await apiRequest(`/api/sales-orders/${order.id}/issue-materials`, { method: 'POST', body: JSON.stringify({ warehouse_id: state.productionWarehouseId, production_no: order.production_no || undefined }) }); addNotification(`领料成功,成本偏差率 ${(result.cost_deviation_rate * 100).toFixed(2)}%`, 'success'); await loadProductionOrders(); await loadMovements(); state.productionPlan = await apiRequest(`/api/sales-orders/${order.id}/production-plan`); } catch (e) { handleApiError(e, '执行领料'); } }; const openModal = async (type, item = null) => { state.modalType = type; state.editingItem = item; if (item) { if (type === 'salesOrder') { await loadCustomers(); await loadProducts(); const detail = await apiRequest(`/api/sales-orders/${item.id}`); state.form = { customer_id: detail.customer_id, delivery_date: detail.delivery_date ? new Date(detail.delivery_date).toISOString().slice(0, 16) : '', remark: detail.remark || '', items: (detail.items || []).map(line => ({ product_id: line.product_id, quantity: line.quantity, unit_price: line.unit_price, remark: line.remark || '' })) }; } else if (type === 'purchaseOrder') { await loadSuppliers(); await loadMaterials(); const detail = await apiRequest(`/api/purchase-orders/${item.id}`); state.form = { supplier_id: detail.supplier_id, expected_date: detail.expected_date ? new Date(detail.expected_date).toISOString().slice(0, 16) : '', remark: detail.remark || '', items: (detail.items || []).map(line => ({ product_id: line.product_id, quantity: line.quantity, unit_price: line.unit_price, remark: line.remark || '' })) }; } else if (type === 'purchaseReceive') { await loadWarehouses(); const detail = await apiRequest(`/api/purchase-orders/${item.id}`); state.purchaseReceiveItems = (detail.items || []) .map(line => ({ item_id: line.id, material_label: `${line.product_sku || line.product_id} - ${line.product_name || ''}`.trim(), remaining_quantity: Math.max((line.quantity || 0) - (line.received_quantity || 0), 0), receive_quantity: Math.max((line.quantity || 0) - (line.received_quantity || 0), 0) })) .filter(line => line.remaining_quantity > 0); state.form = { warehouse_id: state.purchaseWarehouseId || state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null, remark: '' }; } else { state.form = { ...item }; } } else { state.form = {}; if (type === 'inventoryItem') { await ensureStockBaseData(); state.form = { product_id: state.materials[0]?.id || null, warehouse_id: state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null, quantity: 0, locked_quantity: 0, batch_number: '', location: '' }; } if (type === 'product') { state.form = { item_type: 'finished', unit: '件', min_stock: 0, max_stock: 1000, cost_price: 0, sale_price: 0 }; } if (type === 'salesOrder') { await loadCustomers(); await loadProducts(); state.form = { customer_id: state.customers[0]?.id || null, delivery_date: '', remark: '', items: [{ product_id: state.products.find(p => p.item_type === 'finished')?.id || null, quantity: 1, unit_price: 0, remark: '' }] }; } if (type === 'purchaseOrder') { await loadSuppliers(); await loadMaterials(); state.form = { supplier_id: state.suppliers[0]?.id || null, expected_date: '', remark: '', items: [{ product_id: state.materials[0]?.id || null, quantity: 1, unit_price: 0, remark: '' }] }; } } state.showModal = true; }; const closeModal = () => { state.showModal = false; state.modalType = ''; state.editingItem = null; state.productBomItems = []; state.purchaseReceiveItems = []; state.form = {}; }; const saveProduct = async () => { try { if (state.editingItem) { await apiRequest(`/api/products/${state.editingItem.id}`, { method: 'PUT', body: JSON.stringify(state.form) }); addNotification('产品更新成功', 'success'); } else { await apiRequest('/api/products', { method: 'POST', body: JSON.stringify(state.form) }); addNotification('产品创建成功', 'success'); } closeModal(); loadProducts(); loadMaterials(); } catch (e) { handleApiError(e, '保存产品'); } }; const deleteProduct = async (id) => { if (!confirm('确定要删除这个产品吗?')) return; try { await apiRequest(`/api/products/${id}`, { method: 'DELETE' }); addNotification('产品已删除', 'success'); loadProducts(); loadMaterials(); } catch (e) { handleApiError(e, '删除产品'); } }; const editProductBom = async (product) => { try { await loadMaterials(); const bom = await apiRequest(`/api/products/${product.id}/materials`); state.modalType = 'productBom'; state.editingItem = product; state.productBomItems = (bom.items || []).map(item => ({ material_id: item.material_id, quantity: item.quantity, loss_rate: item.loss_rate })); state.showModal = true; } catch (e) { handleApiError(e, '加载产品BOM'); } }; const addBomItem = () => { state.productBomItems.push({ material_id: state.materials[0]?.id || null, quantity: 1, loss_rate: 0 }); }; const removeBomItem = (idx) => { state.productBomItems.splice(idx, 1); }; const saveProductBom = async () => { try { await apiRequest(`/api/products/${state.editingItem.id}/materials`, { method: 'PUT', body: JSON.stringify({ items: state.productBomItems }) }); addNotification('产品BOM保存成功', 'success'); closeModal(); loadProducts(); } catch (e) { handleApiError(e, '保存产品BOM'); } }; const saveSupplier = async () => { try { if (state.editingItem) { await apiRequest(`/api/suppliers/${state.editingItem.id}`, { method: 'PUT', body: JSON.stringify(state.form) }); addNotification('供应商更新成功', 'success'); } else { await apiRequest('/api/suppliers', { method: 'POST', body: JSON.stringify(state.form) }); addNotification('供应商创建成功', 'success'); } closeModal(); loadSuppliers(); } catch (e) { handleApiError(e, '保存供应商'); } }; const deleteSupplier = async (id) => { if (!confirm('确定要删除这个供应商吗?')) return; try { await apiRequest(`/api/suppliers/${id}`, { method: 'DELETE' }); addNotification('供应商已删除', 'success'); loadSuppliers(); } catch (e) { handleApiError(e, '删除供应商'); } }; const saveCustomer = async () => { try { if (state.editingItem) { await apiRequest(`/api/customers/${state.editingItem.id}`, { method: 'PUT', body: JSON.stringify(state.form) }); addNotification('客户更新成功', 'success'); } else { await apiRequest('/api/customers', { method: 'POST', body: JSON.stringify(state.form) }); addNotification('客户创建成功', 'success'); } closeModal(); loadCustomers(); } catch (e) { handleApiError(e, '保存客户'); } }; const deleteCustomer = async (id) => { if (!confirm('确定要删除这个客户吗?')) return; try { await apiRequest(`/api/customers/${id}`, { method: 'DELETE' }); addNotification('客户已删除', 'success'); loadCustomers(); } catch (e) { handleApiError(e, '删除客户'); } }; const saveInventoryItem = async () => { try { if (state.editingItem) { await apiRequest(`/api/inventory/${state.editingItem.id}`, { method: 'PUT', body: JSON.stringify({ quantity: state.form.quantity, locked_quantity: state.form.locked_quantity, batch_number: state.form.batch_number, location: state.form.location }) }); addNotification('物料库存更新成功', 'success'); } else { await apiRequest('/api/inventory', { method: 'POST', body: JSON.stringify(state.form) }); addNotification('物料库存创建成功', 'success'); } closeModal(); loadInventory(); } catch (e) { handleApiError(e, '保存物料库存'); } }; const deleteInventoryItem = async (id) => { if (!confirm('确定要删除这个物料库存记录吗?')) return; try { await apiRequest(`/api/inventory/${id}`, { method: 'DELETE' }); addNotification('物料库存已删除', 'success'); loadInventory(); } catch (e) { handleApiError(e, '删除物料库存'); } }; const addSalesOrderItem = () => { state.form.items = state.form.items || []; state.form.items.push({ product_id: state.products.find(p => p.item_type === 'finished')?.id || null, quantity: 1, unit_price: 0, remark: '' }); }; const removeSalesOrderItem = (index) => { state.form.items.splice(index, 1); }; const saveSalesOrder = async () => { try { if (!state.form.items || !state.form.items.length) { addNotification('请至少添加一个成品明细', 'warning'); return; } const payload = { customer_id: state.form.customer_id, delivery_date: state.form.delivery_date ? new Date(state.form.delivery_date).toISOString() : null, remark: state.form.remark, items: state.form.items }; if (state.editingItem) { await apiRequest(`/api/sales-orders/${state.editingItem.id}`, { method: 'PUT', body: JSON.stringify(payload) }); addNotification('销售订单更新成功', 'success'); } else { await apiRequest('/api/sales-orders', { method: 'POST', body: JSON.stringify(payload) }); addNotification('销售订单创建成功并已自动扣减物料', 'success'); } closeModal(); loadProductionOrders(); loadInventory(); loadMovements(); } catch (e) { handleApiError(e, '保存销售订单'); } }; const deleteSalesOrder = async (orderId) => { if (!confirm('确定删除这个销售订单吗?系统会自动回补已扣减物料。')) return; try { await apiRequest(`/api/sales-orders/${orderId}`, { method: 'DELETE' }); addNotification('销售订单已删除并回补物料', 'success'); if (state.productionPlan?.sales_order_id === orderId) { state.productionPlan = null; } loadProductionOrders(); loadInventory(); loadMovements(); } catch (e) { handleApiError(e, '删除销售订单'); } }; const addPurchaseOrderItem = () => { state.form.items = state.form.items || []; state.form.items.push({ product_id: state.materials[0]?.id || null, quantity: 1, unit_price: 0, remark: '' }); }; const removePurchaseOrderItem = (index) => { state.form.items.splice(index, 1); }; const savePurchaseOrder = async () => { try { if (!state.form.items || !state.form.items.length) { addNotification('请至少添加一个物料明细', 'warning'); return; } const payload = { supplier_id: state.form.supplier_id, expected_date: state.form.expected_date ? new Date(state.form.expected_date).toISOString() : null, remark: state.form.remark, items: state.form.items }; if (state.editingItem) { await apiRequest(`/api/purchase-orders/${state.editingItem.id}`, { method: 'PUT', body: JSON.stringify(payload) }); addNotification('采购订单更新成功', 'success'); } else { await apiRequest('/api/purchase-orders', { method: 'POST', body: JSON.stringify(payload) }); addNotification('采购订单创建成功', 'success'); } closeModal(); loadPurchaseOrders(); } catch (e) { handleApiError(e, '保存采购订单'); } }; const deletePurchaseOrder = async (orderId) => { if (!confirm('确定删除这个采购订单吗?')) return; try { await apiRequest(`/api/purchase-orders/${orderId}`, { method: 'DELETE' }); addNotification('采购订单已删除', 'success'); loadPurchaseOrders(); } catch (e) { handleApiError(e, '删除采购订单'); } }; const receivePurchaseOrder = async () => { try { if (!state.editingItem?.id) return; const items = (state.purchaseReceiveItems || []) .filter(line => Number(line.receive_quantity) > 0) .map(line => ({ item_id: line.item_id, receive_quantity: Number(line.receive_quantity) })); if (!items.length) { addNotification('请填写本次入库数量', 'warning'); return; } await apiRequest(`/api/purchase-orders/${state.editingItem.id}/receive`, { method: 'POST', body: JSON.stringify({ warehouse_id: state.form.warehouse_id || state.purchaseWarehouseId, items, remark: state.form.remark || '' }) }); addNotification('采购到货入库成功', 'success'); closeModal(); loadPurchaseOrders(); loadInventory(); loadMovements(); } catch (e) { handleApiError(e, '采购到货入库'); } }; onMounted(() => { if (!appState.user) { router.push('/login'); return; } loadDashboard(); }); return { state, switchTab, formatNumber, formatCurrency, formatDateTime, openModal, closeModal, saveProduct, deleteProduct, editProductBom, addBomItem, removeBomItem, saveProductBom, saveSupplier, deleteSupplier, saveCustomer, deleteCustomer, saveInventoryItem, deleteInventoryItem, addSalesOrderItem, removeSalesOrderItem, saveSalesOrder, deleteSalesOrder, addPurchaseOrderItem, removePurchaseOrderItem, savePurchaseOrder, deletePurchaseOrder, receivePurchaseOrder, loadPurchaseOrders, loadProductionOrders, loadOrderProductionPlan, issueOrderMaterials, refreshFinanceByPeriod, getMovementTypeLabel, getMovementBadgeClass }; }, template: `

进销存管理

库存、采购、销售管理

加载中...
📦
{{ state.dashboard?.product_count || 0 }}
成品数量
📊
{{ state.dashboard?.total_stock || 0 }}
物料库存总量
💰
{{ formatCurrency(state.dashboard?.total_value || 0) }}
库存价值
🏭
{{ state.dashboard?.supplier_count || 0 }}
供应商
👥
{{ state.dashboard?.customer_count || 0 }}
客户
🏪
{{ state.dashboard?.warehouse_count || 0 }}
仓库
类型 SKU 名称 分类 单位 成本价 销售价 基础物料成本 操作
{{ product.item_type === 'material' ? '物料' : '成品' }} {{ product.sku }} {{ product.name }} {{ product.category || '-' }} {{ product.unit }} {{ formatCurrency(product.cost_price) }} {{ formatCurrency(product.sale_price) }} {{ product.item_type === 'finished' ? formatCurrency(product.material_cost || 0) : '-' }}
SKU 物料 仓库 数量 可用 操作
{{ item.product_sku }} {{ item.product_name }} {{ item.warehouse_name }} {{ item.quantity }} {{ item.available_quantity }}
采购单 供应商 状态 总金额 已付款 操作
{{ order.order_no }} {{ order.supplier_name }} {{ order.status }} {{ formatCurrency(order.total_amount || 0) }} {{ formatCurrency(order.paid_amount || 0) }}
编码 名称 联系人 电话 邮箱 操作
{{ supplier.code }} {{ supplier.name }} {{ supplier.contact_person || '-' }} {{ supplier.phone || '-' }} {{ supplier.email || '-' }}
编码 名称 联系人 电话 邮箱 操作
{{ customer.code }} {{ customer.name }} {{ customer.contact_person || '-' }} {{ customer.phone || '-' }} {{ customer.email || '-' }}
销售单 客户 生产单号 生产状态 计划物料成本 实际领料成本 操作
{{ order.order_no }} {{ order.customer_name }} {{ order.production_no || '-' }} {{ order.production_status || '-' }} {{ formatCurrency(order.planned_material_cost || 0) }} {{ formatCurrency(order.actual_material_cost || 0) }}

领料建议:{{ state.productionPlan.order_no }}({{ state.productionPlan.production_no }})

计划物料成本:{{ formatCurrency(state.productionPlan.planned_material_cost || 0) }}
物料 需求 可用 缺口 单位成本 需求成本
{{ item.material_sku }} - {{ item.material_name }} {{ item.required_quantity }} {{ item.available_quantity }} {{ item.shortage_quantity }} {{ formatCurrency(item.unit_cost) }} {{ formatCurrency(item.required_cost) }}
统计周期:{{ state.financeSummary?.period_label || '-' }}
🧾
{{ formatCurrency(state.financeSummary?.receivable_total || 0) }}
应收总额
💸
{{ formatCurrency(state.financeSummary?.payable_total || 0) }}
应付总额
💵
{{ formatCurrency(state.financeSummary?.period_receipt_total || 0) }}
周期收款
🏦
{{ formatCurrency(state.financeSummary?.period_payment_total || 0) }}
周期付款

客户账款(周期)

客户 订单数 流水数 订单金额 订单已收 实收流水 应收余额
{{ item.partner_name }} {{ item.order_count }} {{ item.transaction_count }} {{ formatCurrency(item.order_total) }} {{ formatCurrency(item.settled_total) }} {{ formatCurrency(item.transaction_total) }} {{ formatCurrency(item.outstanding_total) }}

供应商账款(周期)

供应商 订单数 流水数 订单金额 订单已付 实付流水 应付余额
{{ item.partner_name }} {{ item.order_count }} {{ item.transaction_count }} {{ formatCurrency(item.order_total) }} {{ formatCurrency(item.settled_total) }} {{ formatCurrency(item.transaction_total) }} {{ formatCurrency(item.outstanding_total) }}

客户-商品追溯(周期)

客户 SKU 商品 订单数 数量 订单金额 已结款 未结款
{{ item.partner_name }} {{ item.product_sku || '-' }} {{ item.product_name }} {{ item.order_count }} {{ formatNumber(item.order_quantity) }} {{ formatCurrency(item.order_amount) }} {{ formatCurrency(item.settled_amount) }} {{ formatCurrency(item.outstanding_amount) }}

供应商-商品追溯(周期)

供应商 SKU 商品 订单数 数量 订单金额 已结款 未结款
{{ item.partner_name }} {{ item.product_sku || '-' }} {{ item.product_name }} {{ item.order_count }} {{ formatNumber(item.order_quantity) }} {{ formatCurrency(item.order_amount) }} {{ formatCurrency(item.settled_amount) }} {{ formatCurrency(item.outstanding_amount) }}

最近财务流水

单号 类型 往来方 金额 状态 日期
{{ txn.txn_no }} {{ txn.txn_type === 'receipt' ? '收款' : '付款' }} {{ txn.partner_type === 'customer' ? '客户' : '供应商' }}#{{ txn.partner_id }} {{ formatCurrency(txn.amount) }} {{ txn.status === 'confirmed' ? '已确认' : '已作废' }} {{ formatDateTime(txn.txn_date) }}
物料 类型 数量 变动前 变动后 时间
{{ movement.product_name }}{{ movement.product_sku ? ' (' + movement.product_sku + ')' : '' }} {{ getMovementTypeLabel(movement.movement_type) }} {{ movement.quantity }} {{ movement.before_quantity }} {{ movement.after_quantity }} {{ formatDateTime(movement.created_at) }}

{{ state.editingItem ? '编辑' : '新增' }}{{ state.modalType === 'product' ? '产品/物料' : state.modalType === 'productBom' ? '产品BOM' : state.modalType === 'inventoryItem' ? '物料库存' : state.modalType === 'salesOrder' ? '销售订单' : state.modalType === 'purchaseOrder' ? '采购订单' : state.modalType === 'purchaseReceive' ? '采购到货入库' : state.modalType === 'supplier' ? '供应商' : '客户' }}

成品 数量 单价 备注 操作
物料 数量 单价 备注 操作
物料 明细ID 剩余待入库 本次入库
{{ line.material_label }} {{ line.item_id }} {{ line.remaining_quantity }}
物料 数量 损耗率 操作
` }; const routes = [ { path: "/", component: HomeView }, { path: "/login", component: LoginView }, { path: "/users", component: UsersView }, { path: "/moldinsight", component: MoldInsightView }, { path: "/moldinsight/result/:taskId", component: ResultView }, { path: "/inventory", component: InventoryView } ]; const router = createRouter({ history: createWebHistory(), routes }); router.beforeEach((to, from, next) => { const publicPages = ['/login']; const authRequired = !publicPages.includes(to.path); if (authRequired && !appState.user) { return next('/login'); } if (to.path === '/login' && appState.user) { return next('/'); } next(); }); const app = createApp(App); app.use(router); app.mount("#app");