diff --git a/index.html b/index.html
index 98d8148..257f3c4 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
-
+
Classworks KV
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..8fe5a93
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico
new file mode 100644
index 0000000..8fe5a93
Binary files /dev/null and b/src/assets/favicon.ico differ
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000..a5f23ae
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/assets/logo.svg b/src/assets/logo.svg
new file mode 100644
index 0000000..a33474c
--- /dev/null
+++ b/src/assets/logo.svg
@@ -0,0 +1,23 @@
+
diff --git a/src/assets/vue.svg b/src/assets/vue.svg
deleted file mode 100644
index 770e9d3..0000000
--- a/src/assets/vue.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/api.js b/src/lib/api.js
index cc03606..ada7c6e 100644
--- a/src/lib/api.js
+++ b/src/lib/api.js
@@ -1,3 +1,5 @@
+import axiosInstance from './axios'
+
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3030'
const SITE_KEY = import.meta.env.VITE_SITE_KEY || ''
@@ -8,27 +10,23 @@ class ApiClient {
}
async fetch(endpoint, options = {}) {
- const headers = {
- 'Content-Type': 'application/json',
- 'x-site-key': this.siteKey,
- ...options.headers,
- }
+ const method = options.method || 'GET'
+ const headers = { ...options.headers }
+ const data = options.body
+ const params = options.params
- const response = await fetch(`${this.baseUrl}${endpoint}`, {
- ...options,
+ // 通过 axios 实例发起请求(已内置 baseURL 与 x-site-key)
+ const result = await axiosInstance.request({
+ url: endpoint,
+ method,
headers,
+ data,
+ params,
})
- if (!response.ok) {
- const error = await response.json().catch(() => ({ message: 'Unknown error' }))
- throw new Error(error.message || `HTTP ${response.status}`)
- }
-
- if (response.status === 204) {
- return {}
- }
-
- return response.json()
+ // axios 响应拦截器已返回 response.data,这里做空值统一
+ if (result === '' || result === undefined || result === null) return {}
+ return result
}
// 带认证的fetch
diff --git a/src/lib/axios.js b/src/lib/axios.js
index 67a6b02..893761a 100644
--- a/src/lib/axios.js
+++ b/src/lib/axios.js
@@ -1,4 +1,5 @@
import axios from 'axios'
+import { deviceStore } from './deviceStore'
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3030'
const SITE_KEY = import.meta.env.VITE_SITE_KEY || ''
@@ -23,13 +24,121 @@ axiosInstance.interceptors.request.use(
}
)
-// 响应拦截器
+// 设备注册去重(避免并发重复注册)
+const registrationLocks = new Map()
+
+function getHeaderIgnoreCase(headers = {}, key) {
+ if (!headers) return undefined
+ const lowerKey = key.toLowerCase()
+ for (const k of Object.keys(headers)) {
+ if (k.toLowerCase() === lowerKey) return headers[k]
+ }
+ return undefined
+}
+
+function extractUuidFromUrl(url = '') {
+ try {
+ const path = url || ''
+ // /apps/devices/{uuid}/...
+ let m = path.match(/\/apps\/devices\/([0-9a-fA-F-]{8,})/)
+ if (m) return m[1]
+ // /devices/{uuid}/...
+ m = path.match(/\/devices\/([0-9a-fA-F-]{8,})/)
+ if (m) return m[1]
+ // /accounts/device/{uuid}
+ m = path.match(/\/accounts\/device\/([0-9a-fA-F-]{8,})/)
+ if (m) return m[1]
+
+ // query ?uuid=...
+ const qIndex = path.indexOf('?')
+ if (qIndex >= 0) {
+ const usp = new URLSearchParams(path.slice(qIndex + 1))
+ const q = usp.get('uuid')
+ if (q) return q
+ }
+ } catch (e) {
+ // ignore
+ }
+ return undefined
+}
+
+async function ensureDeviceRegistered(uuid, authHeader) {
+ if (!uuid) return false
+ if (registrationLocks.has(uuid)) {
+ try {
+ await registrationLocks.get(uuid)
+ return true
+ } catch {
+ return false
+ }
+ }
+ const deviceName = '未命名设备'
+ const headers = {}
+ if (authHeader) headers['Authorization'] = authHeader
+
+ const p = axiosInstance.post(
+ '/devices',
+ { uuid, deviceName },
+ { headers, // 避免递归触发注册重试
+ skipDeviceRegistrationRetry: true,
+ __isRegistrationRequest: true }
+ )
+ registrationLocks.set(uuid, p)
+ try {
+ await p
+ // 保存UUID到本地存储,确保后续可用
+ try { deviceStore.setDeviceUuid(uuid) } catch {}
+ return true
+ } catch (e) {
+ return false
+ } finally {
+ registrationLocks.delete(uuid)
+ }
+}
+
+// 响应拦截器(含自动注册并重试)
axiosInstance.interceptors.response.use(
(response) => {
return response.data
},
- (error) => {
- const message = error.response?.data?.message || error.message || 'Unknown error'
+ async (error) => {
+ const config = error.config || {}
+ const skip = config.skipDeviceRegistrationRetry || config.__isRegistrationRequest
+ const backendMessage = error.response?.data?.message
+ const message = backendMessage || error.message || 'Unknown error'
+
+ // 仅在后端提示设备不存在时尝试注册并重试,且保证只重试一次
+ if (!skip && !config.__retriedAfterRegistration && typeof backendMessage === 'string' && backendMessage.startsWith('设备不存在')) {
+ // 从 headers / url / body 提取 uuid
+ const uuidFromHeader = getHeaderIgnoreCase(config.headers, 'x-device-uuid')
+ const uuidFromUrl = extractUuidFromUrl(config.url)
+ let uuid = uuidFromHeader || uuidFromUrl || deviceStore.getDeviceUuid()
+ if (!uuid && config.data) {
+ try {
+ const body = typeof config.data === 'string' ? JSON.parse(config.data) : config.data
+ if (body && typeof body === 'object' && body.uuid) uuid = body.uuid
+ } catch {}
+ }
+
+ // 可能需要账户授权头
+ const authHeader = getHeaderIgnoreCase(config.headers, 'Authorization')
+
+ if (uuid) {
+ const ok = await ensureDeviceRegistered(uuid, authHeader)
+ if (ok) {
+ try {
+ config.__retriedAfterRegistration = true
+ // 原样重试
+ const retryResp = await axiosInstance.request(config)
+ return retryResp
+ } catch (retryErr) {
+ const retryMsg = retryErr?.response?.data?.message || retryErr.message || message
+ return Promise.reject(new Error(retryMsg))
+ }
+ }
+ }
+ }
+
return Promise.reject(new Error(message))
}
)
diff --git a/src/pages/index.vue b/src/pages/index.vue
index 1ae2d8d..6af1694 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -389,9 +389,9 @@ onMounted(async () => {