diff --git a/docs/todo.md b/docs/todo.md
new file mode 100644
index 0000000..f34b59a
--- /dev/null
+++ b/docs/todo.md
@@ -0,0 +1,24 @@
+# Frontend TODOs and Missing APIs
+
+## Missing APIs
+
+### Dashboard Statistics
+- **Endpoint**: `GET /api/stats/dashboard`
+- **Description**: Returns summary statistics for the dashboard (e.g., total users, active orgs, api calls, revenue).
+- **Reason**: The dashboard needs high-level metrics to display to the user immediately upon login. Currently using mock data in `ui/page/dashboard/index.html`.
+
+## Future Improvements
+
+### UI
+- Add proper form validation for all inputs.
+- Implement pagination for lists (Users, Orgs, OAuth Clients).
+- Add search and filter functionality for lists.
+- Improve error handling and user feedback messages.
+- Add loading states for all async operations.
+
+### Features
+- Implement Organization editing (currently placeholder).
+- Implement Organization member management (add/remove members).
+- Implement User creation modal (currently placeholder).
+- Implement User editing (currently placeholder).
+- Implement detailed OAuth client management (scopes, secrets).
diff --git a/ui/env.js b/ui/env.js
index fdbcbd2..c0add29 100644
--- a/ui/env.js
+++ b/ui/env.js
@@ -1,33 +1,62 @@
-import token from './token.js'
-export default ($env) => {
- token.wrapAxios($env.$axios)
- $env.$G.token = token
- let user = token.body()
- $env.$G.user = user
+
+import VBase from './vbase.js'
+
+export default async ($env) => {
+ // Load i18n
+ try {
+ const langs = await (await fetch('/langs.json')).json()
+ $env.$i18n.load(langs)
+ } catch (e) {
+ console.error('Failed to load langs.json', e)
+ }
+
+ // Initialize VBase Service
+ const vbase = new VBase(''); // Relative path
+ $env.$vbase = vbase;
+
+ // Wrap Axios
+ vbase.wrapAxios($env.$axios);
+
+ // Router Guard
$env.$router.beforeEnter = async (to, from, next) => {
- if (to.meta && to.meta.auth) {
- if (token.isExpired()) {
- await token.refresh()
+ const isAuth = to.meta && to.meta.auth;
+ const isGuest = to.meta && to.meta.guest;
+ const roles = to.meta && to.meta.roles; // Array of required roles
+
+ if (isAuth) {
+ if (vbase.isExpired()) {
+ try {
+ await vbase.refresh();
+ } catch (e) {
+ vbase.logout(to.fullPath);
+ return false;
+ }
}
- if (token.isExpired()) {
- token.logout(to.fullPath)
- return false
+
+ if (!vbase.user) {
+ try {
+ await vbase.fetchUser();
+ } catch (e) {
+ vbase.logout(to.fullPath);
+ return false;
+ }
}
- if (!token.check('app', '', 2)) {
- next('/')
+
+ // Role Check
+ if (roles && roles.length > 0) {
+ const hasRole = roles.some(role => vbase.hasRole(role));
+ if (!hasRole) {
+ $env.$router.push('/403');
+ return false;
+ }
+ }
+ } else if (isGuest) {
+ if (!vbase.isExpired()) {
+ next('/');
+ return false;
}
- } else {
- next();
}
+
+ next();
};
- $env.$axios.interceptors.response.use(function(response) {
- return response?.data || response;
- }, function(error) {
- error = error?.response?.data || error?.response || error
- return Promise.reject(error);
- });
- $env.$axios.get('/api/cfg').then(res => {
- $env.$G.cfg = res
- console.log(res)
- })
}
diff --git a/ui/ico.html b/ui/ico.html
index 1dce40f..37ebc9a 100644
--- a/ui/ico.html
+++ b/ui/ico.html
@@ -36,11 +36,11 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
diff --git a/ui/layout/public.html b/ui/layout/public.html
index 264a6b7..fa2ab99 100644
--- a/ui/layout/public.html
+++ b/ui/layout/public.html
@@ -1,10 +1,35 @@
-
-
- Public Layout
-
+
+
+
VBase
+
+
+
+
+
+
+
+
diff --git a/ui/page/403.html b/ui/page/403.html
new file mode 100644
index 0000000..51061c8
--- /dev/null
+++ b/ui/page/403.html
@@ -0,0 +1,41 @@
+
+
+
+
+
403 Forbidden
+
+
+
+
+
+
diff --git a/ui/page/404.html b/ui/page/404.html
new file mode 100644
index 0000000..439d1b8
--- /dev/null
+++ b/ui/page/404.html
@@ -0,0 +1,41 @@
+
+
+
+
+
404 Not Found
+
+
+
+
+
+
diff --git a/ui/page/auth/login.html b/ui/page/auth/login.html
new file mode 100644
index 0000000..af37257
--- /dev/null
+++ b/ui/page/auth/login.html
@@ -0,0 +1,116 @@
+
+
+
+
+
+
{{ $t('auth.login') }}
+
+
+
+
+ 123
+
{{ $t('auth.login') }}
+
+
{{ error }}
+
+
+
+
+
+
+
+
diff --git a/ui/page/auth/register.html b/ui/page/auth/register.html
new file mode 100644
index 0000000..34999de
--- /dev/null
+++ b/ui/page/auth/register.html
@@ -0,0 +1,130 @@
+
+
+
+
+
{{ $t('auth.register') }}
+
+
+
+
{{ $t('auth.register') }}
+
+
{{ error }}
+
+
+
+
+
+
+
diff --git a/ui/page/dashboard/index.html b/ui/page/dashboard/index.html
new file mode 100644
index 0000000..deb6a8e
--- /dev/null
+++ b/ui/page/dashboard/index.html
@@ -0,0 +1,115 @@
+
+
+
+
+
{{ $t('nav.dashboard') }}
+
+
+
+
+
{{ $t('nav.dashboard') }}
+
+
+
+
{{ stat.title }}
+
{{ stat.value }}
+
+
+
+
+
+
+
+
+
diff --git a/ui/page/login.html b/ui/page/login.html
deleted file mode 100644
index 366be36..0000000
--- a/ui/page/login.html
+++ /dev/null
@@ -1,708 +0,0 @@
-
-
-
-
-
-
-
-
登录与注册
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
已有账户?
-
请使用您的个人信息登录,保持连接。
-
去登录
-
-
-
新朋友?
-
输入您的个人信息,开始您的旅程。
-
去注册
-
-
-
-
-
-
-
-
-
diff --git a/ui/page/sys/oauth/index.html b/ui/page/sys/oauth/index.html
new file mode 100644
index 0000000..6930af4
--- /dev/null
+++ b/ui/page/sys/oauth/index.html
@@ -0,0 +1,128 @@
+
+
+
+
+
{{ $t('nav.oauth') }}
+
+
+
+
+
+
+
+
+
ID: {{ app.client_id }}
+
Callback: {{ app.redirect_uri }}
+
+
+
+
+
+
diff --git a/ui/page/sys/org/detail.html b/ui/page/sys/org/detail.html
new file mode 100644
index 0000000..c25bcba
--- /dev/null
+++ b/ui/page/sys/org/detail.html
@@ -0,0 +1,165 @@
+
+
+
+
+
{{ $t('org.detail') }}
+
+
+
+
+
+
+
{{ $t('org.info') }}
+
+
+ ID
+ {{ org.id }}
+
+
+ Name
+ {{ org.name }}
+
+
+ Created At
+ {{ new Date(org.created_at).toLocaleDateString() }}
+
+
+
+
+
+
{{ $t('org.members') }}
+
+
+
+ | Username |
+ Role |
+ Actions |
+
+
+
+
+ | {{ member.username }} |
+ {{ member.role }} |
+
+
+ |
+
+
+
+
+
+
+
+
diff --git a/ui/page/sys/org/index.html b/ui/page/sys/org/index.html
new file mode 100644
index 0000000..c7116f5
--- /dev/null
+++ b/ui/page/sys/org/index.html
@@ -0,0 +1,148 @@
+
+
+
+
+
{{ $t('nav.org') }}
+
+
+
+
+
+
+
+
{{ org.name }}
+
ID: {{ org.id }}
+
Role: {{ org.role || 'Member' }}
+
+
+
+
+
+
+
{{ $t('org.create') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/page/sys/user/index.html b/ui/page/sys/user/index.html
new file mode 100644
index 0000000..cbd267f
--- /dev/null
+++ b/ui/page/sys/user/index.html
@@ -0,0 +1,110 @@
+
+
+
+
+
{{ $t('nav.users') }}
+
+
+
+
+
+
+
+
+ | ID |
+ Username |
+ Email |
+ Status |
+ Actions |
+
+
+
+
+ | {{ u.id }} |
+ {{ u.username }} |
+ {{ u.email }} |
+ {{ u.status || 'Active' }} |
+
+
+
+ |
+
+
+
+
+
+
+
diff --git a/ui/page/user/profile.html b/ui/page/user/profile.html
new file mode 100644
index 0000000..1f7c5aa
--- /dev/null
+++ b/ui/page/user/profile.html
@@ -0,0 +1,133 @@
+
+
+
+
+
{{ $t('user.profile') }}
+
+
+
+
+
{{ $t('user.profile') }}
+
+
+
+ {{ user.username ? user.username.charAt(0).toUpperCase() : 'U' }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/root.html b/ui/root.html
index 5b62b06..e6736e7 100644
--- a/ui/root.html
+++ b/ui/root.html
@@ -3,16 +3,16 @@
-
oa
-
-
-
-
+
vbase
+
+
+
+
-
+
diff --git a/ui/routes.js b/ui/routes.js
index c99e213..42d907d 100644
--- a/ui/routes.js
+++ b/ui/routes.js
@@ -1,25 +1,40 @@
-/*
- * routes.js
- * Copyright (C) 2025 veypi
- *
- * Distributed under terms of the MIT license.
- */
-
const routes = [
- { path: '/', component: '/page/index.html', name: 'home', layout: 'public' },
- { path: '/login', component: '/page/login.html', name: 'login', meta: { auth: false } },
- { path: '/profile', component: '/page/profile.html', layout: 'default', meta: { auth: true } },
- { path: '/app', component: '/page/app.html', name: 'app', layout: 'default', meta: { auth: true } },
- { path: '/settings', component: '/page/settings.html', layout: 'default', meta: { auth: true } },
+ // Public
+ { path: '/login', component: '/page/auth/login.html', layout: 'public', meta: { guest: true } },
+ { path: '/register', component: '/page/auth/register.html', layout: 'public', meta: { guest: true } },
+
+ // Dashboard (Default Layout)
+ {
+ path: '/',
+ layout: 'default',
+ meta: { auth: true },
+ component: '/page/dashboard/index.html'
+ },
+
+ // Org Management
+ { path: '/org', component: '/page/sys/org/index.html', layout: 'default', meta: { auth: true } },
{
- path: '/app/:id', layout: 'app', meta: { auth: true },
- children: [
- { path: '/', component: '/page/app/index.html' },
- { path: '/user', component: '/page/app/user.html' },
- { path: '/auth', component: '/page/app/auth.html' },
- { path: '/settings', component: '/page/app/settings.html' },
- ]
+ path: '/org/:id',
+ component: '/page/sys/org/detail.html',
+ layout: 'default',
+ meta: { auth: true }
},
- { path: '*', component: '/page/404.html', name: '404' },
+
+ // User System
+ { path: '/profile', component: '/page/user/profile.html', layout: 'default', meta: { auth: true } },
+ {
+ path: '/users',
+ component: '/page/sys/user/index.html',
+ layout: 'default',
+ meta: { auth: true, roles: ['admin'] }
+ },
+
+ // OAuth Management
+ { path: '/oauth/apps', component: '/page/sys/oauth/index.html', layout: 'default', meta: { auth: true } },
+
+ // Errors
+ { path: '/403', component: '/page/403.html', layout: 'public' },
+ { path: '*', component: '/page/404.html', layout: 'public' }
]
+
export default routes
diff --git a/ui/token.js b/ui/token.js
deleted file mode 100644
index c1c8604..0000000
--- a/ui/token.js
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * auth.js
- * Copyright (C) 2025 veypi
- *
- * Distributed under terms of the MIT license.
- */
-
-class TokenService {
- #login_url = '/login'
- #refresh_url = '/api/token'
- constructor() {
- this.tokenKey = 'access';
- this.refreshTokenKey = 'refresh';
- }
-
- check(domain, id, level) {
- let body = this.body();
- if (!body || !body.access) {
- return false;
- }
- for (let a of body.access) {
- if (a.name === domain && (a.tid === '' || a.tid === id) && a.level >= level) {
- return true
- }
- }
- return false
- }
-
- setToken(token) {
- localStorage.setItem(this.tokenKey, token);
- }
-
- getToken() {
- return localStorage.getItem(this.tokenKey);
- }
-
- toJSON() {
- return this.getToken()
- }
-
- toString() {
- return this.getToken()
- }
-
- setRefreshToken(refreshToken) {
- localStorage.setItem(this.refreshTokenKey, refreshToken);
- }
-
- getRefreshToken() {
- return localStorage.getItem(this.refreshTokenKey);
- }
-
- clearToken() {
- localStorage.removeItem(this.tokenKey);
- localStorage.removeItem(this.refreshTokenKey);
- }
-
- hasToken() {
- return !!this.getToken();
- }
-
- parseToken(token) {
- try {
- if (!token || typeof token !== 'string') return null;
- const base64Url = token.split('.')[1];
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
- return JSON.parse(window.atob(base64));
- } catch (error) {
- console.info('Token解析失败:', error);
- return null;
- }
- }
- __cache = null
- body() {
- if (!this.__cache) {
- this.__cache = this.parseToken(this.getToken());
- }
- if (!this.__cache) {
- this.__cache = this.parseToken(this.getRefreshToken());
- }
- return this.__cache
- }
- logout(to, querys) {
- this.clearToken();
- let url = new URL(this.#login_url, window.location.origin)
- let redirect = to || window.location.pathname
- url.searchParams.set('redirect', redirect)
- for (let key in querys) {
- url.searchParams.set(key, querys[key]);
- }
- location.href = url.toString()
- }
-
- async refresh() {
- const refreshToken = this.getRefreshToken();
- if (!refreshToken) {
- this.logout()
- return;
- }
- try {
- let data = await fetch(this.#refresh_url, {
- method: 'post',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ refresh: refreshToken })
- }).then(res => {
- if (res.status !== 200) {
- throw new Error(`Token刷新失败,状态码: ${res.status}`);
- }
- return res.text()
- })
- this.__cache = null; // 清除缓存
- this.setToken(data);
- } catch (e) {
- console.error('Token刷新失败:', e);
- this.clearToken()
- logout();
- }
- }
- isExpired() {
- const decoded = this.body();
- if (!decoded) return true;
-
- const currentTime = Date.now() / 1000;
- return decoded.exp < currentTime;
- }
- wrapAxios(instance) {
-
- // 定义一个标志,用于防止在刷新令牌时发送多个刷新请求
- let isRefreshing = false;
- // 定义一个队列,用于存储在刷新令牌期间失败的请求
- let failedQueue = [];
-
- /**
- * 处理等待队列中的请求。
- * 刷新令牌成功后,将队列中的请求重新发送;刷新失败则拒绝这些请求。
- * @param {Error|null} error - 如果刷新令牌失败,则为错误对象。
- * @param {string|null} token - 刷新成功后获取的新令牌。
- */
- const processQueue = (error, token = null) => {
- failedQueue.forEach(prom => {
- if (error) {
- // 刷新失败,拒绝队列中的所有请求
- prom.reject(error);
- } else {
- // 刷新成功,使用新令牌解决队列中的所有请求
- prom.resolve(token);
- }
- });
- // 清空队列
- failedQueue = [];
- };
-
- // 请求拦截器:在发送请求前添加认证令牌
- instance.interceptors.request.use(config => {
- const token = this.getToken();
- if (token) {
- config.headers.Authorization = `Bearer ${token}`;
- }
- return config;
- }, error => {
- // 对请求错误做些什么
- return Promise.reject(error);
- });
-
- let that = this
- // 响应拦截器:处理响应数据,特别是针对 401 状态码进行令牌刷新和重试
- instance.interceptors.response.use((response) => {
- // 任何 2xx 范围内的状态码都会触发此函数
- // 这里可以添加其他全局的成功响应处理逻辑
- return response;
- }, async function(error) {
- // 任何超出 2xx 范围的状态码都会触发此函数
- const originalRequest = error.config;
-
- // 检查错误响应状态码是否为 401 (未授权)
- // 并且确保这不是一个已经重试过的请求 (通过 originalRequest._retry 标记)
-
- if (error.response && error.response.status === 401 && !originalRequest.noretry) {
-
- // 统计该请求的重试次数
- originalRequest.__retryCount = originalRequest.__retryCount || 0;
- originalRequest.__retryCount++;
-
-
- // 如果重试次数超过 3 次,则不再重试,直接跳转到登录页
- if (originalRequest.__retryCount >= 3) {
- // that.logout()
- // 拒绝原始请求的 Promise,停止后续处理
- return Promise.reject(error);
- }
-
- // 如果当前正在进行令牌刷新
- if (isRefreshing) {
- // 将当前失败的请求添加到队列中,等待新令牌
- return new Promise(resolve => {
- failedQueue.push({ resolve, reject: (err) => { throw err; } });
- }).then(token => {
- // 刷新成功后,使用新令牌更新请求头
- originalRequest.headers.Authorization = `Bearer ${token}`;
- // 重新发送原始请求
- return instance(originalRequest);
- }).catch(err => {
- // 如果队列中的请求被拒绝,则抛出错误
- return Promise.reject(err);
- });
- }
-
- // 如果没有正在进行令牌刷新,则设置标志为 true,开始刷新
- isRefreshing = true;
-
- try {
- // 发送请求来刷新令牌
- await that.refresh();
- const newToken = that.getToken();
-
- // 更新原始请求的 Authorization 头为新的令牌
- originalRequest.headers.Authorization = `Bearer ${newToken}`;
- // 处理等待队列中的所有请求,用新的令牌重新发送
- processQueue(null, newToken);
- // 重新发送最初导致 401 错误的请求
- return instance(originalRequest);
- } catch (refreshError) {
- // 如果刷新令牌本身也失败了 (例如,refresh token 已过期)
- // 清除本地令牌
- // 拒绝等待队列中的所有请求
- processQueue(refreshError);
- // that.clearToken();
- that.logout();
- // 拒绝原始请求的 Promise
- return Promise.reject(refreshError);
- } finally {
- // 无论成功或失败,最后都要将刷新标志重置为 false
- isRefreshing = false;
- }
- }
- // 对于其他类型的错误,或不是需要重试的 401 错误,直接拒绝 Promise
- return Promise.reject(error);
- });
- }
-}
-
-export default new TokenService();
diff --git a/ui/vbase.js b/ui/vbase.js
new file mode 100644
index 0000000..e50366e
--- /dev/null
+++ b/ui/vbase.js
@@ -0,0 +1,217 @@
+
+class VBase {
+ constructor(baseURL) {
+ this.baseURL = baseURL || '';
+ this.tokenKey = 'vbase_access_token';
+ this.refreshTokenKey = 'vbase_refresh_token';
+ this.userKey = 'vbase_user_info';
+ this.orgKey = 'vbase_current_org';
+
+ this._token = localStorage.getItem(this.tokenKey) || '';
+ this._refreshToken = localStorage.getItem(this.refreshTokenKey) || '';
+ this._user = JSON.parse(localStorage.getItem(this.userKey) || 'null');
+ this._currentOrg = JSON.parse(localStorage.getItem(this.orgKey) || 'null');
+ }
+
+ // Getters
+ get token() { return this._token; }
+ get refreshToken() { return this._refreshToken; }
+ get user() { return this._user; }
+ get currentOrg() { return this._currentOrg; }
+
+ // Setters
+ set token(val) {
+ this._token = val;
+ if (val) localStorage.setItem(this.tokenKey, val);
+ else localStorage.removeItem(this.tokenKey);
+ }
+
+ set refreshToken(val) {
+ this._refreshToken = val;
+ if (val) localStorage.setItem(this.refreshTokenKey, val);
+ else localStorage.removeItem(this.refreshTokenKey);
+ }
+
+ set user(val) {
+ this._user = val;
+ if (val) localStorage.setItem(this.userKey, JSON.stringify(val));
+ else localStorage.removeItem(this.userKey);
+ }
+
+ set currentOrg(val) {
+ this._currentOrg = val;
+ if (val) localStorage.setItem(this.orgKey, JSON.stringify(val));
+ else localStorage.removeItem(this.orgKey);
+ }
+
+ // API Helpers
+ async request(method, path, data = null, headers = {}) {
+ const url = `${this.baseURL}${path}`;
+ const config = {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...this.getAuthHeaders(),
+ ...headers
+ }
+ };
+ if (data) config.body = JSON.stringify(data);
+
+ const response = await fetch(url, config);
+ const resData = await response.json();
+
+ if (!response.ok) {
+ throw resData || new Error(`Request failed: ${response.status}`);
+ }
+
+ if (resData.code && resData.code !== 200) {
+ throw new Error(resData.message || 'API Error');
+ }
+
+ return resData.data || resData;
+ }
+
+ // Auth Actions
+ async login(username, password) {
+ try {
+ const data = await this.request('POST', '/api/auth/login', { username, password });
+ if (data.access) {
+ this.token = data.access;
+ if (data.refresh) this.refreshToken = data.refresh;
+ await this.fetchUser();
+ return true;
+ }
+ return false;
+ } catch (e) {
+ throw e;
+ }
+ }
+
+ async logout(redirect) {
+ try {
+ // Optional: Call server logout
+ // await this.request('POST', '/api/auth/logout');
+ } catch (e) {
+ console.warn('Logout API failed', e);
+ } finally {
+ this.clear();
+ if (redirect) {
+ location.href = redirect;
+ } else {
+ location.reload();
+ }
+ }
+ }
+
+ async refresh() {
+ if (!this.refreshToken) throw new Error("No refresh token");
+ try {
+ const data = await this.request('POST', '/api/auth/refresh', { refresh: this.refreshToken });
+ if (data.access) {
+ this.token = data.access;
+ if (data.refresh) this.refreshToken = data.refresh;
+ return true;
+ }
+ return false;
+ } catch (e) {
+ this.logout();
+ throw e;
+ }
+ }
+
+ async fetchUser() {
+ const user = await this.request('GET', '/api/auth/me');
+ this.user = user;
+ return user;
+ }
+
+ // Auth Headers
+ getAuthHeaders() {
+ const headers = {};
+ if (this.token) {
+ headers['Authorization'] = `Bearer ${this.token}`;
+ }
+ if (this.currentOrg && this.currentOrg.id) {
+ headers['X-Org-ID'] = this.currentOrg.id;
+ }
+ return headers;
+ }
+
+ // Permission Check
+ hasPermission(permission) {
+ if (!this.user) return false;
+ if (this.user.is_admin) return true;
+ if (!permission) return true;
+ const userPerms = this.user.permissions || [];
+ return userPerms.includes(permission);
+ }
+
+ hasRole(role) {
+ if (!this.user) return false;
+ if (this.user.is_admin) return true;
+ const userRoles = this.user.roles || [];
+ return userRoles.includes(role);
+ }
+
+ // State Management
+ clear() {
+ this.token = '';
+ this.refreshToken = '';
+ this.user = null;
+ this.currentOrg = null;
+ }
+
+ isExpired(token) {
+ if (!token) token = this.token;
+ if (!token) return true;
+ try {
+ const base64Url = token.split('.')[1];
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
+ const payload = JSON.parse(window.atob(base64));
+ const now = Math.floor(Date.now() / 1000);
+ return payload.exp && payload.exp < now;
+ } catch (e) {
+ return true;
+ }
+ }
+
+ wrapAxios(axiosInstance) {
+ // Request Interceptor
+ axiosInstance.interceptors.request.use(config => {
+ const headers = this.getAuthHeaders();
+ for (const key in headers) {
+ config.headers[key] = headers[key];
+ }
+ return config;
+ }, error => Promise.reject(error));
+
+ // Response Interceptor
+ axiosInstance.interceptors.response.use(response => {
+ const res = response.data;
+ if (res && res.code === 200) {
+ return res.data;
+ }
+ if (res && res.code && res.code !== 200) {
+ return Promise.reject(new Error(res.message || 'Error'));
+ }
+ return res || response;
+ }, async error => {
+ const originalRequest = error.config;
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
+ try {
+ await this.refresh();
+ const headers = this.getAuthHeaders();
+ originalRequest.headers['Authorization'] = headers['Authorization'];
+ return axiosInstance(originalRequest);
+ } catch (e) {
+ this.logout(window.location.pathname);
+ return Promise.reject(e);
+ }
+ }
+ return Promise.reject(error?.response?.data || error);
+ });
+ }
+}
+
+export default VBase;