/** * 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 { return new Date(dateString).toLocaleString('zh-CN'); } 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(); }); return { route, router, appState, menuItems, isActive, handleLogout, 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 }}
◆
Gemold
模具制造管理系统
{{ item.icon }} {{ item.label }}
Gemold v4.0.0 模具制造管理系统
`, }; 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: `
◆

登录

登录到 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/inventory/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, uploading: false, error: "", currentTask: null, polling: false, dragOver: false, progress: 0, history: null }); const loadHistory = async () => { try { state.history = await apiRequest('/api/history'); } catch (e) { console.error('加载历史记录失败:', e); } }; 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); 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) => { 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; if (task.status === "completed") { state.polling = false; state.progress = 100; addNotification('分析完成', 'success'); 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(); }); return { state, handleFileChange, handleDrop, uploadFile, formatFileSize, formatDateTime }; }, template: `

MoldInsight

STP 模具几何分析

📁
点击选择或拖拽文件 支持 .stp, .step 格式,最大 100MB
{{ state.selectedFile.name }} {{ formatFileSize(state.selectedFile.size) }}
{{ state.error }}

最近分析

文件名 大小 状态 分析时间 操作
{{ file.filename }} {{ formatFileSize(file.file_size) }} {{ file.status }} {{ formatDateTime(file.created_at) }}
` }; const ResultView = { setup() { const route = useRoute(); const router = useRouter(); const state = reactive({ task: null, loading: true, error: '' }); const loadTask = async () => { 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); } catch (e) { state.error = handleApiError(e, '加载任务详情'); } finally { state.loading = false; } }; onMounted(() => { if (!appState.user) { router.push('/login'); return; } loadTask(); }); return { state, formatFileSize, formatDateTime, formatNumber }; }, 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 预览

分析结果详情

1. 产品特征识别

整体轮廓和尺寸比例 {{ state.task.analysis_result?.geometry_data?.bounding_box ? (state.task.analysis_result.geometry_data.bounding_box.x?.toFixed(1) || 0) + ' × ' + (state.task.analysis_result.geometry_data.bounding_box.y?.toFixed(1) || 0) + ' × ' + (state.task.analysis_result.geometry_data.bounding_box.z?.toFixed(1) || 0) + ' mm' : 'N/A' }}
壁厚分布 {{ state.task.analysis_result.detected_features?.find(f => f.type === 'wall_thickness')?.description || '待分析' }}
加强筋位置和密度 {{ state.task.analysis_result.detected_features?.filter(f => f.type === 'rib').length || 0 }} 个加强筋
孔洞和凹槽位置 {{ state.task.analysis_result.detected_features?.filter(f => f.type === 'hole' || f.type === 'pocket').length || 0 }} 个孔洞/凹槽
倒扣区域检测 {{ state.task.analysis_result.detected_features?.filter(f => f.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.type === '定位').length || 0 }} 个定位特征
分型面选择 基于产品几何:自动推荐最优分型面

3. 模具工程决策

型腔数量建议 {{ state.task.cavity_data?.mold_cavities ? Object.keys(state.task.cavity_data.mold_cavities).length : 1 }} 腔
模架尺寸 基于泡沫外形自动计算
顶出系统 顶针顶出(自动布局)
冷却水路设计 {{ state.task.analysis_result.geometry_data?.volume > 1000000 ? '需要冷却水路' : '自然冷却' }}
材料选择 {{ state.task.analysis_result.quality_metrics?.volume ? '根据产量和精度自动推荐' : '7075 铝合金' }}
` }; const InventoryView = { setup() { const router = useRouter(); const route = useRoute(); const state = reactive({ activeTab: 'dashboard', dashboard: null, products: [], suppliers: [], customers: [], warehouses: [], inventory: [], movements: [], loading: false }); const loadDashboard = async () => { state.loading = true; try { state.dashboard = await apiRequest('/api/inventory/dashboard'); } catch (e) { handleApiError(e, '加载仪表盘'); } finally { state.loading = false; } }; const loadProducts = async () => { state.loading = true; try { state.products = await apiRequest('/api/inventory/products'); } catch (e) { handleApiError(e, '加载产品'); } finally { state.loading = false; } }; const loadSuppliers = async () => { state.loading = true; try { state.suppliers = await apiRequest('/api/inventory/suppliers'); } catch (e) { handleApiError(e, '加载供应商'); } finally { state.loading = false; } }; const loadCustomers = async () => { state.loading = true; try { state.customers = await apiRequest('/api/inventory/customers'); } catch (e) { handleApiError(e, '加载客户'); } finally { state.loading = false; } }; const loadInventory = async () => { state.loading = true; try { state.inventory = await apiRequest('/api/inventory/inventory'); } catch (e) { handleApiError(e, '加载库存'); } finally { state.loading = false; } }; const loadMovements = async () => { state.loading = true; try { state.movements = await apiRequest('/api/inventory/stock-movements'); } catch (e) { handleApiError(e, '加载变动记录'); } finally { state.loading = false; } }; 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; } }; onMounted(() => { if (!appState.user) { router.push('/login'); return; } loadDashboard(); }); return { state, switchTab, formatNumber, formatCurrency, formatDateTime }; }, 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.sku }} {{ product.name }} {{ product.category || '-' }} {{ product.unit }} {{ formatCurrency(product.cost_price) }} {{ formatCurrency(product.sale_price) }}
SKU 产品 仓库 数量 可用
{{ item.product_sku }} {{ item.product_name }} {{ item.warehouse_name }} {{ item.quantity }} {{ item.available_quantity }}
编码 名称 联系人 电话 邮箱
{{ supplier.code }} {{ supplier.name }} {{ supplier.contact_person || '-' }} {{ supplier.phone || '-' }} {{ supplier.email || '-' }}
编码 名称 联系人 电话 邮箱
{{ customer.code }} {{ customer.name }} {{ customer.contact_person || '-' }} {{ customer.phone || '-' }} {{ customer.email || '-' }}
产品 类型 数量 变动前 变动后 时间
{{ movement.product_name }} {{ movement.movement_type === 'in' ? '入库' : movement.movement_type === 'out' ? '出库' : '调整' }} {{ movement.quantity }} {{ movement.before_quantity }} {{ movement.after_quantity }} {{ formatDateTime(movement.created_at) }}
` }; 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");