Add vue-router, login/setup flow and backend logging
Refactor frontend to route-based navigation with vue-router, add system setup and login pages with API integration. Add structured logging, access-log middleware and startup lifecycle to FastAPI backend.
This commit is contained in:
278
web/src/composables/useSystemState.js
Normal file
278
web/src/composables/useSystemState.js
Normal file
@@ -0,0 +1,278 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import {
|
||||
loginBootstrapAdmin,
|
||||
saveBootstrapConfig,
|
||||
testBootstrapDatabase,
|
||||
testBootstrapRuntime
|
||||
} from '../services/bootstrap.js'
|
||||
import { useToast } from './useToast.js'
|
||||
|
||||
const AUTH_STORAGE_KEY = 'x-financial-authenticated'
|
||||
|
||||
function readClientBootstrapState() {
|
||||
const env = import.meta.env
|
||||
|
||||
return {
|
||||
initialized: String(env.VITE_SETUP_COMPLETED || '').toLowerCase() === 'true',
|
||||
company: {
|
||||
name: env.VITE_COMPANY_NAME || '',
|
||||
code: env.VITE_COMPANY_CODE || '',
|
||||
admin_email: env.VITE_ADMIN_EMAIL || ''
|
||||
},
|
||||
web: {
|
||||
host: env.VITE_WEB_HOST || '127.0.0.1',
|
||||
port: Number(env.VITE_WEB_PORT || 5173)
|
||||
},
|
||||
server: {
|
||||
host: env.VITE_SERVER_HOST || '127.0.0.1',
|
||||
port: Number(env.VITE_SERVER_PORT || 8000)
|
||||
},
|
||||
database: {
|
||||
driver: 'postgresql',
|
||||
host: env.VITE_POSTGRES_HOST || '127.0.0.1',
|
||||
port: Number(env.VITE_POSTGRES_PORT || 5432),
|
||||
name: env.VITE_POSTGRES_DB || 'x_financial',
|
||||
username: env.VITE_POSTGRES_USER || 'postgres',
|
||||
password_configured: false
|
||||
},
|
||||
redis: {
|
||||
enabled: Boolean(env.VITE_REDIS_URL),
|
||||
url: env.VITE_REDIS_URL || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readAuthState() {
|
||||
if (typeof window === 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
return window.sessionStorage.getItem(AUTH_STORAGE_KEY) === 'true'
|
||||
}
|
||||
|
||||
function persistAuthState(value) {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
if (value) {
|
||||
window.sessionStorage.setItem(AUTH_STORAGE_KEY, 'true')
|
||||
return
|
||||
}
|
||||
|
||||
window.sessionStorage.removeItem(AUTH_STORAGE_KEY)
|
||||
}
|
||||
|
||||
const bootstrapState = ref(readClientBootstrapState())
|
||||
const setupSubmitting = ref(false)
|
||||
const setupError = ref('')
|
||||
const runtimeTesting = ref(false)
|
||||
const databaseTesting = ref(false)
|
||||
const runtimeTestPassed = ref(false)
|
||||
const databaseTestPassed = ref(false)
|
||||
const runtimeTestMessage = ref('')
|
||||
const databaseTestMessage = ref('')
|
||||
const loginSubmitting = ref(false)
|
||||
const loginError = ref('')
|
||||
const loggedIn = ref(readAuthState())
|
||||
|
||||
const { toast } = useToast()
|
||||
|
||||
const companyProfile = computed(() => ({
|
||||
name: bootstrapState.value.company?.name || '',
|
||||
code: bootstrapState.value.company?.code || '',
|
||||
adminEmail: bootstrapState.value.company?.admin_email || ''
|
||||
}))
|
||||
|
||||
const isInitialized = computed(() => Boolean(bootstrapState.value.initialized))
|
||||
|
||||
function applyBootstrapState(state) {
|
||||
bootstrapState.value = state
|
||||
|
||||
if (!state.initialized) {
|
||||
loggedIn.value = false
|
||||
persistAuthState(false)
|
||||
}
|
||||
}
|
||||
|
||||
function clearSetupRuntimeState() {
|
||||
runtimeTesting.value = false
|
||||
databaseTesting.value = false
|
||||
runtimeTestPassed.value = false
|
||||
databaseTestPassed.value = false
|
||||
runtimeTestMessage.value = ''
|
||||
databaseTestMessage.value = ''
|
||||
setupError.value = ''
|
||||
}
|
||||
|
||||
function resetFromClientEnv() {
|
||||
applyBootstrapState(readClientBootstrapState())
|
||||
clearSetupRuntimeState()
|
||||
loginError.value = ''
|
||||
}
|
||||
|
||||
async function handleSetupSubmit(payload) {
|
||||
if (!runtimeTestPassed.value) {
|
||||
setupError.value = '请先完成运行端口检测。'
|
||||
toast(setupError.value)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!databaseTestPassed.value) {
|
||||
setupError.value = '请先完成数据库连接检测。'
|
||||
toast(setupError.value)
|
||||
return false
|
||||
}
|
||||
|
||||
setupSubmitting.value = true
|
||||
setupError.value = ''
|
||||
|
||||
try {
|
||||
const state = await saveBootstrapConfig(payload)
|
||||
applyBootstrapState(state)
|
||||
toast('初始化配置已写入。现在可以进入登录页。')
|
||||
return true
|
||||
} catch (error) {
|
||||
setupError.value = error.message || '初始化配置写入失败,请稍后重试。'
|
||||
toast(setupError.value)
|
||||
return false
|
||||
} finally {
|
||||
setupSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRuntimeTest(payload) {
|
||||
runtimeTesting.value = true
|
||||
runtimeTestMessage.value = ''
|
||||
setupError.value = ''
|
||||
|
||||
try {
|
||||
const result = await testBootstrapRuntime(payload)
|
||||
runtimeTestPassed.value = true
|
||||
runtimeTestMessage.value = result.detail || '端口占用检测通过。'
|
||||
toast(runtimeTestMessage.value)
|
||||
} catch (error) {
|
||||
runtimeTestPassed.value = false
|
||||
runtimeTestMessage.value = error.message || '端口占用检测失败。'
|
||||
toast(runtimeTestMessage.value)
|
||||
} finally {
|
||||
runtimeTesting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDatabaseTest(payload) {
|
||||
databaseTesting.value = true
|
||||
databaseTestMessage.value = ''
|
||||
setupError.value = ''
|
||||
|
||||
try {
|
||||
const result = await testBootstrapDatabase(payload)
|
||||
databaseTestPassed.value = true
|
||||
databaseTestMessage.value = result.detail || '数据库连接检测通过。'
|
||||
toast(databaseTestMessage.value)
|
||||
} catch (error) {
|
||||
databaseTestPassed.value = false
|
||||
databaseTestMessage.value = error.message || '数据库连接检测失败。'
|
||||
toast(databaseTestMessage.value)
|
||||
} finally {
|
||||
databaseTesting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleRuntimeDirty() {
|
||||
runtimeTestPassed.value = false
|
||||
runtimeTestMessage.value = ''
|
||||
|
||||
if (setupError.value === '请先完成运行端口检测。') {
|
||||
setupError.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function handleDatabaseDirty() {
|
||||
databaseTestPassed.value = false
|
||||
databaseTestMessage.value = ''
|
||||
|
||||
if (setupError.value === '请先完成数据库连接检测。') {
|
||||
setupError.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogin(credentials) {
|
||||
loginSubmitting.value = true
|
||||
loginError.value = ''
|
||||
|
||||
try {
|
||||
await loginBootstrapAdmin({
|
||||
username: credentials.username,
|
||||
password: credentials.password
|
||||
})
|
||||
|
||||
loggedIn.value = true
|
||||
persistAuthState(true)
|
||||
return true
|
||||
} catch (error) {
|
||||
loggedIn.value = false
|
||||
persistAuthState(false)
|
||||
loginError.value = error.message || '登录失败,请检查管理员账号和密码。'
|
||||
toast(loginError.value)
|
||||
return false
|
||||
} finally {
|
||||
loginSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
loggedIn.value = false
|
||||
persistAuthState(false)
|
||||
}
|
||||
|
||||
function handleRecoverPassword() {
|
||||
toast('请联系系统管理员重置密码。管理员密码不会写入 .env。')
|
||||
}
|
||||
|
||||
function handleSsoLogin() {
|
||||
toast('SSO 登录暂未启用。')
|
||||
}
|
||||
|
||||
function resolveEntryRoute() {
|
||||
if (!isInitialized.value) {
|
||||
return { name: 'setup' }
|
||||
}
|
||||
|
||||
if (!loggedIn.value) {
|
||||
return { name: 'login' }
|
||||
}
|
||||
|
||||
return { name: 'app-overview' }
|
||||
}
|
||||
|
||||
export function useSystemState() {
|
||||
return {
|
||||
bootstrapState,
|
||||
companyProfile,
|
||||
databaseTestMessage,
|
||||
databaseTestPassed,
|
||||
databaseTesting,
|
||||
handleDatabaseDirty,
|
||||
handleDatabaseTest,
|
||||
handleLogin,
|
||||
handleRecoverPassword,
|
||||
handleRuntimeDirty,
|
||||
handleRuntimeTest,
|
||||
handleSetupSubmit,
|
||||
handleSsoLogin,
|
||||
isInitialized,
|
||||
loggedIn,
|
||||
loginError,
|
||||
loginSubmitting,
|
||||
logout,
|
||||
resetFromClientEnv,
|
||||
resolveEntryRoute,
|
||||
runtimeTestMessage,
|
||||
runtimeTestPassed,
|
||||
runtimeTesting,
|
||||
setupError,
|
||||
setupSubmitting
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user