/**
* 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 }}
`,
};
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: `
`
};
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: `
📦
{{ 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) }} |
|
`
};
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: `
{{ state.selectedFile.name }}
{{ formatFileSize(state.selectedFile.size) }}
{{ state.error }}
分析历史
| 文件名 |
上传次数 |
文件大小 |
最新状态 |
最新分析时间 |
操作 |
| {{ file.filename }} |
{{ file.upload_count }} 次
|
{{ formatFileSize(file.file_size) }} |
{{ file.latest_status }}
|
{{ formatDateTime(file.latest_upload_time) }} |
|
| 上传时间 |
文件大小 |
状态 |
体积 (mm³) |
表面积 (mm²) |
重量 (g) |
操作 |
| {{ formatDateTime(record.upload_time) }} |
{{ formatFileSize(record.file_size) }} |
{{ record.status }}
|
{{ record.volume ? formatNumber(record.volume) : '-' }} |
{{ record.surface_area ? formatNumber(record.surface_area) : '-' }} |
{{ record.product_weight ? record.product_weight.toFixed(2) : '-' }} |
|
|
`
};
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: `
文件信息
文件大小
{{ 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 预览
分析结果详情
体积差异
{{ 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,
products: [],
suppliers: [],
customers: [],
warehouses: [],
inventory: [],
movements: [],
loading: false,
showModal: false,
modalType: '',
editingItem: null,
form: {}
});
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');
} catch (e) {
handleApiError(e, '加载产品');
} finally {
state.loading = false;
}
};
const loadSuppliers = async () => {
state.loading = true;
try {
state.suppliers = await apiRequest('/api/suppliers');
} 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 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;
}
};
const openModal = (type, item = null) => {
state.modalType = type;
state.editingItem = item;
if (item) {
state.form = { ...item };
} else {
state.form = {};
}
state.showModal = true;
};
const closeModal = () => {
state.showModal = false;
state.modalType = '';
state.editingItem = null;
state.form = {};
};
const saveProduct = async () => {
try {
if (state.editingItem) {
await apiRequest(`/api/products/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify(state.form)
});
showNotification('产品更新成功', 'success');
} else {
await apiRequest('/api/products', {
method: 'POST',
body: JSON.stringify(state.form)
});
showNotification('产品创建成功', 'success');
}
closeModal();
loadProducts();
} catch (e) {
handleApiError(e, '保存产品');
}
};
const deleteProduct = async (id) => {
if (!confirm('确定要删除这个产品吗?')) return;
try {
await apiRequest(`/api/products/${id}`, { method: 'DELETE' });
showNotification('产品已删除', 'success');
loadProducts();
} catch (e) {
handleApiError(e, '删除产品');
}
};
const saveSupplier = async () => {
try {
if (state.editingItem) {
await apiRequest(`/api/suppliers/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify(state.form)
});
showNotification('供应商更新成功', 'success');
} else {
await apiRequest('/api/suppliers', {
method: 'POST',
body: JSON.stringify(state.form)
});
showNotification('供应商创建成功', 'success');
}
closeModal();
loadSuppliers();
} catch (e) {
handleApiError(e, '保存供应商');
}
};
const deleteSupplier = async (id) => {
if (!confirm('确定要删除这个供应商吗?')) return;
try {
await apiRequest(`/api/suppliers/${id}`, { method: 'DELETE' });
showNotification('供应商已删除', '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)
});
showNotification('客户更新成功', 'success');
} else {
await apiRequest('/api/customers', {
method: 'POST',
body: JSON.stringify(state.form)
});
showNotification('客户创建成功', 'success');
}
closeModal();
loadCustomers();
} catch (e) {
handleApiError(e, '保存客户');
}
};
const deleteCustomer = async (id) => {
if (!confirm('确定要删除这个客户吗?')) return;
try {
await apiRequest(`/api/customers/${id}`, { method: 'DELETE' });
showNotification('客户已删除', 'success');
loadCustomers();
} catch (e) {
handleApiError(e, '删除客户');
}
};
const stockIn = async () => {
try {
await apiRequest('/api/stock-movements', {
method: 'POST',
body: JSON.stringify({ ...state.form, movement_type: 'in' })
});
showNotification('入库成功', 'success');
closeModal();
loadInventory();
loadMovements();
} catch (e) {
handleApiError(e, '入库操作');
}
};
const stockOut = async () => {
try {
await apiRequest('/api/stock-movements', {
method: 'POST',
body: JSON.stringify({ ...state.form, movement_type: 'out' })
});
showNotification('出库成功', 'success');
closeModal();
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,
saveSupplier,
deleteSupplier,
saveCustomer,
deleteCustomer,
stockIn,
stockOut
};
},
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");