/** * 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({ isLogin: true, username: '', password: '', email: '', full_name: '', 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 { if (state.isLogin) { 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('/'); } else { if (!state.email) { state.error = '请填写邮箱'; state.loading = false; return; } const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: state.username, password: state.password, email: state.email, full_name: state.full_name || null }) }); if (!res.ok) { const error = await res.json(); throw new Error(error.detail || '注册失败'); } addNotification('注册成功,请登录', 'success'); state.isLogin = true; } } catch (e) { state.error = e.message; addNotification(e.message, 'error'); } finally { state.loading = false; } }; return { state, handleSubmit }; }, template: `
◆

{{ state.isLogin ? '登录' : '注册' }}

{{ state.isLogin ? '登录到 Gemold 系统' : '创建新账户' }}

{{ state.error }}
{{ state.isLogin ? '没有账户?' : '已有账户?' }} {{ state.isLogin ? '立即注册' : '立即登录' }}
` }; 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: [], loading: true }); const loadUsers = async () => { try { state.users = await apiRequest('/api/auth/users'); } catch (e) { handleApiError(e, '加载用户列表'); } finally { state.loading = false; } }; const toggleActive = async (user) => { try { const updated = await apiRequest(`/api/auth/users/${user.id}/toggle-active`, { method: 'PUT' }); const index = state.users.findIndex(u => u.id === user.id); if (index > -1) state.users[index] = updated; addNotification(`用户 ${user.username} 已${updated.is_active ? '启用' : '禁用'}`, 'success'); } catch (e) { handleApiError(e, '切换用户状态'); } }; const toggleAdmin = async (user) => { try { const updated = await apiRequest(`/api/auth/users/${user.id}/toggle-admin`, { method: 'PUT' }); const index = state.users.findIndex(u => u.id === user.id); if (index > -1) state.users[index] = updated; addNotification(`用户 ${user.username} ${updated.is_superuser ? '已设为管理员' : '已取消管理员'}`, 'success'); } catch (e) { handleApiError(e, '切换管理员权限'); } }; onMounted(() => { if (!appState.user?.is_superuser) { router.push('/'); return; } loadUsers(); }); return { state, appState, toggleActive, toggleAdmin, formatDateTime }; }, template: `

用户管理

管理系统用户和权限

加载中...
用户名 邮箱 姓名 状态 角色 注册时间 操作
{{ user.username }} {{ user.email }} {{ user.full_name || '-' }} {{ user.is_active ? '正常' : '禁用' }} {{ user.is_superuser ? '管理员' : '普通用户' }} {{ formatDateTime(user.created_at) }}
` }; 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('/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("/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(`/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(`/status/${route.params.taskId}`, { method: 'POST' }); } 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) }}

几何数据

顶点数 {{ formatNumber(state.task.geometry_data.vertex_count) }}
面数 {{ formatNumber(state.task.geometry_data.face_count) }}
边数 {{ formatNumber(state.task.geometry_data.edge_count) }}

3D 预览

` }; 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");