From bf8ebc5e84588e22306d3c1a474279934e9eaf28 Mon Sep 17 00:00:00 2001 From: xiaosi <2652281683@qq.com> Date: Thu, 20 Aug 2026 15:23:12 +0800 Subject: [PATCH] fix: login token fields, commonRefs init, admin detail 403 - accept accessToken/token/jToken; reject empty credentials - initialize commonRefs with constructor fields for Vite HMR - skip admin alarms/commands on DeviceDetail (backend user-only) - proxy via agent when http_proxy is set; target LAN backend --- package-lock.json | 46 +++++++++++++++++++ package.json | 1 + src/stores/modules/userStore.js | 8 +++- src/utils/commonRefs.js | 34 +++++++------- .../DeviceDetailView/DeviceDetailView.vue | 11 +++-- src/views/LoginView/LoginView.vue | 12 +++-- vite.config.js | 11 ++++- 7 files changed, 95 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2978b0..898f5b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.4", + "http-proxy-agent": "^9.1.0", "unplugin-auto-import": "^21.1.0", "unplugin-vue-components": "^32.1.0", "vite": "^5.2.11" @@ -1230,6 +1231,7 @@ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -1456,6 +1458,31 @@ "node": ">= 0.4" } }, + "node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1891,6 +1918,24 @@ "node": ">= 4.4.x" } }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -1957,6 +2002,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "1.0.9" }, diff --git a/package.json b/package.json index d2aca51..751c9e8 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.4", + "http-proxy-agent": "^9.1.0", "unplugin-auto-import": "^21.1.0", "unplugin-vue-components": "^32.1.0", "vite": "^5.2.11" diff --git a/src/stores/modules/userStore.js b/src/stores/modules/userStore.js index ffa71b3..2076289 100644 --- a/src/stores/modules/userStore.js +++ b/src/stores/modules/userStore.js @@ -6,9 +6,13 @@ export const useUserStore = defineStore('user', () => { const user = ref(JSON.parse(localStorage.getItem('user') || 'null')) function setLogin(tokenVal, userVal) { - token.value = tokenVal + const nextToken = tokenVal == null ? '' : String(tokenVal) + if (!nextToken) { + throw new Error('登录凭证为空') + } + token.value = nextToken user.value = userVal - localStorage.setItem('token', tokenVal) + localStorage.setItem('token', nextToken) localStorage.setItem('user', JSON.stringify(userVal)) } diff --git a/src/utils/commonRefs.js b/src/utils/commonRefs.js index 26877ab..d85f4c5 100644 --- a/src/utils/commonRefs.js +++ b/src/utils/commonRefs.js @@ -3,28 +3,26 @@ * 异步获取引用对象 */ -const refs = Symbol('refs') -const pendingList = Symbol('pendingList') - class CommonRefs { - [refs] = {} - - [pendingList] = {} + constructor() { + this._refs = {} + this._pendingList = {} + } clearPendingList(key = '') { if (!key) { - this[pendingList] = {} + this._pendingList = {} } else { - this[pendingList][key] = [] + this._pendingList[key] = [] } } setRef(key, ref) { // 允许显式 set null 唤醒 pending(无 token 等失败路径) - if (key in this[refs] && this[refs][key] != null) return + if (key in this._refs && this._refs[key] != null) return - this[refs][key] = ref - const { [key]: resolves } = this[pendingList] + this._refs[key] = ref + const resolves = this._pendingList[key] ;(resolves || []).forEach((resolve, index) => { if (!resolve) return resolve(ref) @@ -34,21 +32,21 @@ class CommonRefs { getRef(key) { return new Promise((resolve) => { - if (key in this[refs]) { - resolve(this[refs][key]) + if (key in this._refs) { + resolve(this._refs[key]) return } - if (key in this[pendingList]) { - this[pendingList][key].push(resolve) + if (key in this._pendingList) { + this._pendingList[key].push(resolve) } else { - this[pendingList][key] = [resolve] + this._pendingList[key] = [resolve] } }) } removeRef(key) { - if (key in this[refs]) { - delete this[refs][key] + if (key in this._refs) { + delete this._refs[key] } } } diff --git a/src/views/DeviceDetailView/DeviceDetailView.vue b/src/views/DeviceDetailView/DeviceDetailView.vue index f1981b1..9c7d495 100644 --- a/src/views/DeviceDetailView/DeviceDetailView.vue +++ b/src/views/DeviceDetailView/DeviceDetailView.vue @@ -461,12 +461,15 @@ function chargeText(value) { onMounted(async () => { try { await devices.load() - await Promise.all([loadLogs(), loadAlarms()]) + if (!isAdmin.value) { + await Promise.all([loadLogs(), loadAlarms()]) + } } catch (e) { ui.toast(e.message || '加载设备失败') } }) + const COMMAND_NAMES = { 'dock.open': '打开舱门', 'dock.close': '关闭舱门', @@ -519,7 +522,7 @@ function fmtTime(t) { async function loadLogs() { const current = record.value - if (!current) return + if (!current || isAdmin.value) return try { const page = await request.get(urls.COMMANDS, { params: { pageNum: 1, pageSize: 100, dockId: current.dockId } }) logs.value = (page?.records || []).filter((log) => current.kind !== 'drone' || log.droneSn === current.droneSn).map((log) => { @@ -537,13 +540,14 @@ async function loadLogs() { } }) } catch (e) { + if (e?.response?.status === 403) return ui.toast(e.message || '加载设备操作记录失败') } } async function loadAlarms() { const current = record.value - if (!current) return + if (!current || isAdmin.value) return try { const page = await request.get(urls.ALARMS, { params: { @@ -570,6 +574,7 @@ async function loadAlarms() { } }) } catch (e) { + if (e?.response?.status === 403) return ui.toast(e.message || '加载告警记录失败') } } diff --git a/src/views/LoginView/LoginView.vue b/src/views/LoginView/LoginView.vue index e660df4..1609b1f 100644 --- a/src/views/LoginView/LoginView.vue +++ b/src/views/LoginView/LoginView.vue @@ -69,7 +69,12 @@ async function submitLogin() { phone: account.value, password: password.value, }) - userStore.setLogin(data.accessToken, data.user) + const token = data?.accessToken || data?.token || data?.jToken + if (!token) { + loginError.value = '登录成功但未返回 token,请检查后端响应字段。' + return + } + userStore.setLogin(token, data.user || data.userInfo || null) ui.toast('登录成功') router.push('/monitor') } catch (e) { @@ -101,8 +106,9 @@ async function submitRegister() { email: reg.email, password: reg.password, }) - if (data && data.accessToken) { - userStore.setLogin(data.accessToken, data.user) + const token = data?.accessToken || data?.token || data?.jToken + if (token) { + userStore.setLogin(token, data.user || data.userInfo || null) ui.toast('注册成功') router.push('/monitor') } else { diff --git a/vite.config.js b/vite.config.js index 457c78f..652cb2d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,6 +4,11 @@ import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { TDesignResolver } from 'unplugin-vue-components/resolvers' +import { HttpProxyAgent } from 'http-proxy-agent' + +// 本机若配置了 http_proxy(如 7890),Vite 代理出站需显式走 agent,否则直连局域网可能 EHOSTUNREACH +const outboundProxy = process.env.http_proxy || process.env.HTTP_PROXY || '' +const proxyAgent = outboundProxy ? new HttpProxyAgent(outboundProxy) : undefined export default defineConfig({ plugins: [ @@ -25,10 +30,12 @@ export default defineConfig({ host: '0.0.0.0', port: 5173, proxy: { + // 浏览器请求 /api/* → 后端 /*(去掉 /api 前缀) '/api': { - target: 'http://127.0.0.1:8080', + target: 'http://192.168.10.29:8080', changeOrigin: true, - rewrite: (p) => p.replace(/^\/api/, ''), + agent: proxyAgent, + rewrite: (path) => path.replace(/^\/api/, ''), }, }, },