From af355f7f2980f91c6d659bec4cb62d8f8ccbde5c Mon Sep 17 00:00:00 2001 From: chenjw28 <792430652@qq.com> Date: Mon, 11 May 2026 16:31:56 +0800 Subject: [PATCH] x --- static/vue-app.js | 188 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 static/vue-app.js diff --git a/static/vue-app.js b/static/vue-app.js new file mode 100644 index 0000000..904e097 --- /dev/null +++ b/static/vue-app.js @@ -0,0 +1,188 @@ +const { createApp, ref, reactive, computed, onMounted } = Vue; +const { createRouter, createWebHashHistory } = VueRouter; + +function api(method, url, body) { + const opts = { method, headers: { 'Content-Type': 'application/json' } }; + const t = localStorage.getItem('gemold_token'); + if (t) opts.headers.Authorization = 'Bearer ' + t; + if (body) opts.body = JSON.stringify(body); + return fetch(url, opts).then(r => { + if (r.status === 401) { localStorage.removeItem('gemold_token'); localStorage.removeItem('gemold_user'); router.push('/login'); } + if (!r.ok) return r.json().then(e => Promise.reject(e)); + const ct = r.headers.get('content-type') || ''; + return ct.includes('application/json') ? r.json() : r.text(); + }); +} + +function apiForm(url, form) { + const opts = { method: 'POST', body: form }; + const t = localStorage.getItem('gemold_token'); + if (t) opts.headers = { Authorization: 'Bearer ' + t }; + return fetch(url, opts).then(r => { + if (r.status === 401) { localStorage.removeItem('gemold_token'); localStorage.removeItem('gemold_user'); router.push('/login'); } + if (!r.ok) return r.json().then(e => Promise.reject(e)); + return r.json(); + }); +} + +const store = reactive({ + user: JSON.parse(localStorage.getItem('gemold_user') || 'null'), + tasks: [], + uploading: false, + async login(username, password) { + const form = new FormData(); + form.append('username', username); + form.append('password', password); + const r = await apiForm('/api/auth/login', form); + store.user = r.user; + localStorage.setItem('gemold_token', r.access_token); + localStorage.setItem('gemold_user', JSON.stringify(r.user)); + return r; + }, + logout() { + localStorage.removeItem('gemold_token'); + localStorage.removeItem('gemold_user'); + store.user = null; + store.tasks = []; + router.push('/login'); + }, + async fetchTasks() { + const r = await api('GET', '/api/debug/tasks'); + store.tasks = Object.values(r.tasks || {}).sort((a, b) => { + const da = a.created_at || a.upload_time || ''; + const db = b.created_at || b.upload_time || ''; + return db.localeCompare(da); + }); + }, + async uploadFile(file, material) { + store.uploading = true; + try { + const form = new FormData(); + form.append('file', file); + if (material) form.append('material', material); + const r = await apiForm('/api/upload', form); + await store.fetchTasks(); + return r; + } finally { + store.uploading = false; + } + }, +}); + +const LoginPage = { + setup() { + const username = ref(''); + const password = ref(''); + const error = ref(''); + const loading = ref(false); + const doLogin = async () => { + loading.value = true; error.value = ''; + try { await store.login(username.value, password.value); router.push('/dashboard'); } + catch (e) { error.value = e.detail || '登录失败'; } + finally { loading.value = false; } + }; + return { username, password, error, loading, doLogin }; + }, + template: ` +
+
+

Gemold

+

模具制造管理系统

+
{{ error }}
+ + + +
+
` +}; + +const DashboardPage = { + setup() { + const material = ref('ABS'); + const fileInput = ref(null); + const triggerUpload = () => fileInput.value?.click(); + const handleFile = async (e) => { + const f = e.target.files[0]; + if (!f) return; + await store.uploadFile(f, material.value); + e.target.value = ''; + }; + + onMounted(() => { store.fetchTasks(); const t = setInterval(() => store.fetchTasks(), 10000); onUnmounted(() => clearInterval(t)); }); + const { onUnmounted } = Vue; + + const formatSize = (s) => s ? (s > 1048576 ? (s/1048576).toFixed(1)+' MB' : (s/1024).toFixed(1)+' KB') : '-'; + const statusLabel = (s) => ({ processing: '处理中', completed: '已完成', failed: '失败', pending: '等待中' }[s] || (s || '未知')); + const statusStyle = (s) => { + if (s === 'completed') return { background: '#d4edda', color: '#155724' }; + if (s === 'failed') return { background: '#f8d7da', color: '#721c24' }; + return { background: '#d1ecf1', color: '#0c5460' }; + }; + + return { tasks: computed(() => store.tasks), uploading: computed(() => store.uploading), material, fileInput, triggerUpload, handleFile, formatSize, statusLabel, statusStyle }; + }, + template: ` +
+
+

模具分析

+
+ {{ store.user?.username || '' }} + +
+
+
+
+ 上传 STP 文件: + + + +
+
+ +
+

处理任务 ({{ tasks.length }})

+ +
+ +
暂无任务,请上传 STP 文件开始分析
+ +
+
+
+ {{ t.filename || 'unknown' }} + {{ statusLabel(t.status) }} + {{ t.progress }}% +
+
+ {{ formatSize(t.file_size) }} + {{ t.created_at }} +
+
+
{{ t.current_step }}
+
{{ t.error }}
+
+ 查看分析结果 +
+
{{ t.llm_report }}
+
+
` +}; + +const routes = [ + { path: '/login', component: LoginPage }, + { path: '/dashboard', component: DashboardPage, meta: { requiresAuth: true } }, + { path: '/:pathMatch(.*)*', redirect: '/dashboard' }, +]; + +const router = createRouter({ history: createWebHashHistory(), routes }); +router.beforeEach((to, from, next) => { + if (to.meta.requiresAuth && !store.user) return next('/login'); + if (to.path === '/login' && store.user) return next('/dashboard'); + next(); +}); + +const app = createApp({ template: '' }); +app.use(router); +app.mount('#app');