refactor: frontend TS migration + Tailwind4/daisyUI5 unify + BUILDPLATFORM docker build

- migrate frontend to TypeScript (vue-tsc strict in build), upgrade all deps to latest (Vite 8, Tailwind 4, daisyUI 5, Pinia 4, vue-router 5)
- restructure frontend dirs (api/components/common/layouts/styles/types/views)
- drop Element Plus, add daisyUI TagInput; main CSS 490KB->163KB, entry JS 618KB->1.3KB
- rewrite Dockerfile(.cn): frontend/backend stages pinned to $BUILDPLATFORM, CGO_ENABLED=0 cross-compile, no QEMU in multi-arch builds; add .dockerignore
- local dev: Vite /api proxy + make dev targets; go:embed all:dist with .gitkeep so backend runs without prior frontend build
- fix latent bugs: Keys.vue users ref, Settings.vue undefined userStore, Login.vue Ref-as-error display, res.error misuse
- add REFACTOR_PLAN.md (phased refactor log)
This commit is contained in:
Sakurasan
2026-08-29 23:27:31 +08:00
parent 4a68ff6162
commit ac0a6808ec
55 changed files with 2497 additions and 2318 deletions
@@ -1,20 +1,21 @@
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import request from '@/utils/request'
import type { AxiosResponse } from 'axios';
import request from '@/api/client'
import type { UserInfo, AuthCredentials, TokenPayload, TokenInfo } from '@/types';
import { useRouter } from 'vue-router';
// import { jwtDecode } from 'jwt-decode';
export const useAuthStore = defineStore('auth', () => {
const router = useRouter();
const user = ref(null);
const role = computed(() => {
const user = ref<UserInfo | null>(null);
const role = computed<number>(() => {
if (!user.value) return 0;
return user.value.role;
})
const token = ref(localStorage.getItem('token') || '');
const token = ref<string>(localStorage.getItem('token') || '');
const isAdmin = computed(() => {
if (!user.value || user.value.role === 0) return false;
@@ -23,34 +24,34 @@ export const useAuthStore = defineStore('auth', () => {
const isLoggedIn = computed(() => !!token.value);
const loading = ref(false);
const error = ref(null);
const error = ref<string | null>(null);
const setToken = (newToken) => {
const setToken = (newToken: string) => {
token.value = newToken;
localStorage.setItem('token', newToken);
}
const loadTokenFromStorage=()=> {
const loadTokenFromStorage = () => {
const storedToken = localStorage.getItem('token');
if (storedToken) {
token.value = storedToken;
}
}
const register = async (userInfo) => {
const register = async (userInfo: AuthCredentials) => {
error.value = null;
try {
const res = await request.post('/auth/register', userInfo)
return res
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '注册失败';
throw error // 或者您可以在这里处理错误,例如显示错误消息
}finally{
} finally {
loading.value = false;
}
}
const login = async (userInfo) => {
const login = async (userInfo: AuthCredentials) => {
error.value = null;
try {
const res = await request.post('/auth/login', userInfo)
@@ -59,10 +60,10 @@ export const useAuthStore = defineStore('auth', () => {
}
await getProfile() // 登录成功后获取用户信息
return res
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '登录失败';
throw error // 或者您可以在这里处理错误,例如显示错误消息
}finally{
} finally {
loading.value = false;
}
}
@@ -78,11 +79,11 @@ export const useAuthStore = defineStore('auth', () => {
user.value = res.data.data
return res
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '获取用户信息失败';
throw error
}finally {
} finally {
loading.value = false;
}
}
@@ -96,99 +97,99 @@ export const useAuthStore = defineStore('auth', () => {
user.value = res.data.data
}
return res
} catch (err) {
error.value = err.response?.data?.error || '获取用户信息失败';
} catch (err: any) {
error.value = err.response?.data?.error || '获取用户信息失败';
throw error
}finally{
} finally {
loading.value = false;
}
}
const updateProfile = async (userInfo) => {
const updateProfile = async (userInfo: Partial<UserInfo>) => {
loading.value = true;
error.value = null;
try {
const res = await request.post('/profile/update', userInfo)
console.log('auth.js updateProfile', res.data);
console.log('auth updateProfile', res.data);
return res
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '更新用户信息失败';
throw error
}finally{
} finally {
loading.value = false;
}
}
const updatePassword = async (payload) => {
const updatePassword = async (payload: { password: string; newpassword: string }) => {
loading.value = true;
error.value = null;
try {
const res = await request.post('/profile/update/password', payload)
console.log('auth.js updatePassword', res.data);
console.log('auth updatePassword', res.data);
return res
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '更新密码失败';
throw error
}finally{
} finally {
loading.value = false;
}
}
const createToken = async (newToken) => {
const createToken = async (newToken: TokenPayload) => {
loading.value = true;
error.value = null;
try {
const response = await request.post('/tokens', newToken)
const response: AxiosResponse = await request.post('/tokens', newToken)
console.log('createToken', response.data);
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '创建token失败';
throw error
}finally{
} finally {
loading.value = false;
}
}
const resetToken = async (id) => {
const resetToken = async (id: number | string) => {
loading.value = true;
error.value = null;
try {
const response = await request.post(`/tokens/reset/${id}`)
const response: AxiosResponse = await request.post(`/tokens/reset/${id}`)
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '重置token失败';
throw error
}finally{
} finally {
loading.value = false;
}
}
const updateToken = async (token) => {
const updateToken = async (tokenInfo: Partial<TokenInfo>) => {
loading.value = true;
error.value = null;
try {
const response = await request.put(`/tokens/${token.id}`, token)
const response: AxiosResponse = await request.put(`/tokens/${tokenInfo.id}`, tokenInfo)
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '更新token失败';
throw error
}finally {
} finally {
loading.value = false;
}
}
const deleteToken = async (id) => {
const deleteToken = async (id: number | string) => {
loading.value = true;
error.value = null;
try {
const response = await request.delete(`/tokens/${id}`)
const response: AxiosResponse = await request.delete(`/tokens/${id}`)
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '删除token失败';
throw err
}finally {
} finally {
loading.value = false;
}
}
@@ -206,14 +207,14 @@ export const useAuthStore = defineStore('auth', () => {
}
return {
loading,error,
user,role,token,
isLoggedIn,
setToken,loadTokenFromStorage,
login,register,
getProfile,updateProfile,updatePassword,refreshProfile,
createToken,deleteToken,resetToken,updateToken,
loading, error,
user, role, token,
isLoggedIn, isAdmin,
setToken, loadTokenFromStorage,
login, register,
getProfile, updateProfile, updatePassword, refreshProfile,
createToken, deleteToken, resetToken, updateToken,
clear,
logout
}
});
});
@@ -1,16 +1,18 @@
// src/stores/key.js
// src/stores/key.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
import request from '@/utils/request';
import type { AxiosResponse } from 'axios';
import request from '@/api/client';
import type { ApiKey, NewApiKeyPayload } from '@/types';
export const useKeyStore = defineStore('key', () => {
const loading = ref(false);
const error = ref(null);
const error = ref<string | null>(null);
const totalKeys = ref(0);
const keys = ref([]);
const key = ref(null);
const keys = ref<ApiKey[]>([]);
const key = ref<ApiKey | null>(null);
const fetchKeys = async (pageSize = 20, page = 1, active) => {
const fetchKeys = async (pageSize = 20, page = 1, active?: boolean | boolean[]) => {
loading.value = true;
error.value = null;
try {
@@ -21,10 +23,10 @@ export const useKeyStore = defineStore('key', () => {
active,
},
});
keys.value = response.data.data?.keys;
totalKeys.value = response.data.data?.total;
} catch (err) {
keys.value = response.data.data?.keys ?? [];
totalKeys.value = response.data.data?.total ?? 0;
} catch (err: any) {
error.value = err.response?.data?.error || '获取ApiKeys失败';
throw error
} finally {
@@ -32,7 +34,7 @@ export const useKeyStore = defineStore('key', () => {
}
};
const fetchKey = async (id) => {
const fetchKey = async (id: number | string) => {
if (keys.value.length > 0) {
const findkey = keys.value.find(item => item.id === id)
if (findkey) {
@@ -42,19 +44,17 @@ export const useKeyStore = defineStore('key', () => {
}
loading.value = true;
error.value = null;
// const findkey = keys.find(item=>item.id === id)
// console.log('findkey',findkey)
try {
const response = await request.get(`/keys/${id}`);
key.value = response.data.data;
if (key.value.support_models.length < 3) {
if (key.value && (key.value.support_models?.length ?? 0) < 3) {
key.value.support_models = key.value.support_models_array ? JSON.stringify(key.value.support_models) : ''
}
if (!key.value.support_models_array) {
if (key.value && !key.value.support_models_array) {
key.value.support_models_array = key.value.support_models ? JSON.parse(key.value.support_models) : []
}
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '获取ApiKey失败';
throw error
} finally {
@@ -62,19 +62,19 @@ export const useKeyStore = defineStore('key', () => {
}
};
const refreshKey = async (id) => {
const refreshKey = async (id: number | string) => {
loading.value = true;
error.value = null;
try {
const response = await request.get(`/keys/${id}`);
key.value = response.data.data;
if (key.value.support_models.length < 3) {
if (key.value && (key.value.support_models?.length ?? 0) < 3) {
key.value.support_models = key.value.support_models_array ? JSON.stringify(key.value.support_models) : ''
}
if (!key.value.support_models_array) {
if (key.value && !key.value.support_models_array) {
key.value.support_models_array = key.value.support_models ? JSON.parse(key.value.support_models) : []
}
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '获取ApiKey失败';
throw error
} finally {
@@ -82,13 +82,13 @@ export const useKeyStore = defineStore('key', () => {
}
}
const createKey = async (data) => {
const createKey = async (data: NewApiKeyPayload) => {
loading.value = true;
error.value = null;
try {
const response = await request.post('/keys', data);
const response: AxiosResponse = await request.post('/keys', data);
return response;
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '创建ApiKey失败';
throw error
} finally {
@@ -96,13 +96,13 @@ export const useKeyStore = defineStore('key', () => {
}
}
const updateKey = async (key) => {
const updateKey = async (keyInfo: ApiKey) => {
loading.value = true;
error.value = null;
try {
const response = await request.put(`/keys/${key.id}`, key);
const response: AxiosResponse = await request.put(`/keys/${keyInfo.id}`, keyInfo);
return response;
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '更新ApiKey失败';
throw error
} finally {
@@ -110,14 +110,14 @@ export const useKeyStore = defineStore('key', () => {
}
}
const keyOption = async (option, ids) => {
const keyOption = async (option: string, ids: (number | string)[]) => {
loading.value = true;
error.value = null;
try {
const response = await request.post(`/keys/batch/${option}`, { ids });
const response: AxiosResponse = await request.post(`/keys/batch/${option}`, { ids });
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '操作失败';
throw err;
} finally {
@@ -137,4 +137,3 @@ export const useKeyStore = defineStore('key', () => {
keyOption,
}
});
@@ -1,30 +1,32 @@
// src/stores/user.js
// src/stores/user.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
import request from '@/utils/request';
import type { AxiosResponse } from 'axios';
import request from '@/api/client';
import type { UserInfo, NewUserPayload } from '@/types';
export const useUserStore = defineStore('user', () => {
const users = ref([]);
const users = ref<UserInfo[]>([]);
const totalUsers = ref(0);
const user = ref(null);
const user = ref<UserInfo | null>(null);
const loading = ref(false);
const error = ref(null);
const error = ref<string | null>(null);
async function createUser(userData) {
async function createUser(userData: NewUserPayload) {
loading.value = true;
error.value = null;
try {
const response = await request.post('/users', userData);
const response: AxiosResponse = await request.post('/users', userData);
return response
} catch (err) {
error.value = err.response?.data?.error || '创建用户失败'
} catch (err: any) {
error.value = err.response?.data?.error || '创建用户失败'
throw err;
} finally {
loading.value = false;
}
}
async function listUser(pageSize = 20, page = 1, active) {
async function listUser(pageSize = 20, page = 1, active?: boolean | boolean[]) {
loading.value = true;
error.value = null;
try {
@@ -35,9 +37,9 @@ export const useUserStore = defineStore('user', () => {
active,
},
});
users.value = response.data.data?.users;
totalUsers.value = response.data.data?.total;
} catch (err) {
users.value = response.data.data?.users ?? [];
totalUsers.value = response.data.data?.total ?? 0;
} catch (err: any) {
error.value = err.response?.data?.error || '获取用户列表失败';
throw err;
} finally {
@@ -45,15 +47,15 @@ export const useUserStore = defineStore('user', () => {
}
}
async function getUser(id) {
async function getUser(id: number | string) {
loading.value = true;
error.value = null;
try {
const response = await request.get(`/users/${id}`);
console.log('getUser response',response);
console.log('getUser response', response);
user.value = response.data.data;
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '获取用户信息失败';
throw err;
} finally {
@@ -61,15 +63,15 @@ export const useUserStore = defineStore('user', () => {
}
}
async function refreshUser(id) {
async function refreshUser(id: number | string) {
loading.value = true;
error.value = null;
try {
const response = await request.get(`/users/${id}`);
console.log('getUser response',response);
console.log('getUser response', response);
user.value = response.data.data;
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '获取用户信息失败';
throw err;
} finally {
@@ -77,14 +79,14 @@ export const useUserStore = defineStore('user', () => {
}
}
async function editUser(id, userData) {
async function editUser(id: number | string, userData: Partial<UserInfo>) {
loading.value = true;
error.value = null;
try {
const response= await request.put(`/users/${id}`, userData);
console.log('editUser',response);
const response: AxiosResponse = await request.put(`/users/${id}`, userData);
console.log('editUser', response);
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '编辑用户失败';
throw err;
} finally {
@@ -92,13 +94,13 @@ export const useUserStore = defineStore('user', () => {
}
}
async function deleteUser(id) {
async function deleteUser(id: number | string) {
loading.value = true;
error.value = null;
try {
const response = await request.delete(`/users/${id}`);
const response: AxiosResponse = await request.delete(`/users/${id}`);
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '删除用户失败';
throw err;
} finally {
@@ -106,14 +108,14 @@ export const useUserStore = defineStore('user', () => {
}
}
async function userOption(option, ids) {
async function userOption(option: string, ids: (number | string)[]) {
loading.value = true;
error.value = null;
try {
const response = await request.post(`/users/batch/${option}`, { ids });
const response: AxiosResponse = await request.post(`/users/batch/${option}`, { ids });
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '操作失败';
throw err;
} finally {
@@ -135,4 +137,4 @@ export const useUserStore = defineStore('user', () => {
deleteUser,
userOption,
};
});
});
@@ -1,18 +1,20 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import request from "@/utils/request";
import type { AxiosResponse } from "axios";
import request from "@/api/client";
import { useRouter } from "vue-router";
import { startRegistration, startAuthentication } from "@simplewebauthn/browser";
import { useAuthStore } from "./auth";
import type { PasskeyInfo } from "@/types";
export const useWebAuthStore = defineStore("webauth", () => {
const router = useRouter();
// const token = ref(localStorage.getItem("token") || "");
const passkeys = ref(null);
const passkeys = ref<PasskeyInfo[] | null>(null);
const loading = ref(false);
const error = ref(null);
const error = ref<string | null>(null);
const addPasskey = async () => {
error.value = "";
@@ -29,10 +31,10 @@ export const useWebAuthStore = defineStore("webauth", () => {
let attestation;
try {
// Pass 'undefined' as the second argument if you are not using an AbortSignal
attestation = await startRegistration({optionsJSON: options});
attestation = await startRegistration({ optionsJSON: options });
// console.log("WebAuthn 注册结果 (Attestation):", JSON.stringify(attestation));
error.value = null;
} catch (regError) {
} catch (regError: any) {
// console.log("WebAuthn 注册失败或取消:", regError);
if (regError.name === "NotAllowedError") {
error.value = "Passkey 操作被取消或不允许。";
@@ -43,11 +45,11 @@ export const useWebAuthStore = defineStore("webauth", () => {
}
// 3. 将注册结果 (Attestation) 发送到后端进行验证和保存
const res2 = await request.post("/profile/passkey", attestation);
const res2: AxiosResponse = await request.post("/profile/passkey", attestation);
// console.log("end:", res2);
return res2;
} catch (err) {
error.value =err.response?.data?.error || "添加 Passkey 失败,请稍后重试。";
} catch (err: any) {
error.value = err.response?.data?.error || "添加 Passkey 失败,请稍后重试。";
throw error
} finally {
loading.value = false;
@@ -62,13 +64,13 @@ export const useWebAuthStore = defineStore("webauth", () => {
const res = await request.get("/auth/passkey/begin");
// console.log("login begin:", res.data);
const options = res.data.data.publicKey;
// 2. 调用 Web Authentication API 进行认证
let assertion;
try {
assertion = await startAuthentication({ optionsJSON: options });
// console.log("WebAuthn 认证结果 (Assertion):", JSON.stringify(assertion));
} catch (loginError) {
} catch (loginError: any) {
if (loginError.name === "NotAllowedError") {
error.value = "Passkey 登录被取消或不允许。";
} else {
@@ -76,11 +78,11 @@ export const useWebAuthStore = defineStore("webauth", () => {
}
throw error;
}
// 3. 将认证结果 (Assertion) 发送到后端进行验证并获取 Token
const challenge = options.challenge; // 从 begin 接口返回的 options 中获取 challenge
const res2 = await request.post(`/auth/passkey/finish?challenge=${challenge}`, assertion);
const res2: AxiosResponse = await request.post(`/auth/passkey/finish?challenge=${challenge}`, assertion);
// 4. 处理登录成功的响应,通常包含 Token
if (res2.status === 200 && !!res2.data.data?.token) {
const token = res2.data.data.token
@@ -89,7 +91,7 @@ export const useWebAuthStore = defineStore("webauth", () => {
await authStore.getProfile()
return res2.data
}
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || err.value || "Passkey 登录失败,请稍后重试。";
throw error;
} finally {
@@ -104,24 +106,24 @@ export const useWebAuthStore = defineStore("webauth", () => {
const response = await request.get('/profile/passkeys')
// console.log('getPasskeys',response.data.data)
passkeys.value = response.data.data
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || '获取token列表失败';
throw error
}finally {
} finally {
loading.value = false;
}
}
const deletePasskey = async (id) => {
const deletePasskey = async (id: string | number) => {
loading.value = true;
error.value = null;
try {
const response = await request.delete(`/profile/passkeys/${id}`)
const response: AxiosResponse = await request.delete(`/profile/passkeys/${id}`)
return response
} catch (err) {
} catch (err: any) {
error.value = err.response?.data?.error || `删除passkey ${id} 失败`;
throw error
}finally {
} finally {
loading.value = false;
}
}
@@ -135,4 +137,4 @@ export const useWebAuthStore = defineStore("webauth", () => {
getPasskeys,
deletePasskey,
};
});
});