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:
2026-05-06 22:23:42 +08:00
parent 83d7da3d62
commit ae63766c91
35 changed files with 3762 additions and 404 deletions

View File

@@ -1,75 +1,74 @@
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useNavigation, navItems } from './useNavigation.js'
import { useRequests } from './useRequests.js'
import { useChat } from './useChat.js'
import { useToast } from './useToast.js'
import { documents } from '../data/requests.js'
import { normalizeRequestForUi } from '../utils/requestViewModel.js'
export function useAppShell() {
const loggedIn = ref(false)
const route = useRoute()
const router = useRouter()
const travelCreateMode = ref(false)
const detailMode = ref(false)
const selectedTravelRequest = ref(null)
const smartEntryOpen = ref(false)
const smartEntryContext = ref({ prompt: '', source: 'requests', request: null })
const smartEntrySessionId = ref(0)
const { activeView, currentView, setView } = useNavigation()
const { requests, search, filters, ranges, activeRange, filteredRequests, approveRequest, rejectRequest } = useRequests()
const { messages, draft, uploadedFiles, messageList, activeCase, prompts, sendMessage, handleUpload, openChat, openNewChat } = useChat(activeView)
const { toastText, toast } = useToast()
const { requests, search, filters, ranges, activeRange, filteredRequests, approveRequest, rejectRequest } =
useRequests()
const { messages, draft, uploadedFiles, messageList, activeCase, prompts, sendMessage, handleUpload, openChat, openNewChat } =
useChat(activeView)
const { toast } = useToast()
const docSearch = ref('')
const customRange = ref({ start: '2024-07-06', end: '2024-07-12' })
const travelPrompts = ['帮我提交出差申请', '预订机票', '预订酒店', '预订火车票', '查询差旅政策']
const travelPrompts = ['生成差旅摘要', '识别报销风险', '核对审批链', '提取随附票据', '生成沟通建议']
const selectedTravelRequest = computed(() => {
const requestId = String(route.params.requestId || '')
if (!requestId) {
return null
}
const rawRequest = requests.value.find((item) => String(item.id) === requestId)
return normalizeRequestForUi(rawRequest)
})
const detailMode = computed(() => route.name === 'app-request-detail')
const topBarView = computed(() => {
if (detailMode.value) {
return {
title: '差旅报销详情',
desc: '查看报销单据详情、票据识别与审批进度'
title: '差旅申请详情',
desc: '查看申请单、票据、审批意见与风控提示。'
}
}
return currentView.value
})
const filteredDocuments = computed(() => {
const key = docSearch.value.trim().toLowerCase()
return documents.filter((doc) => {
const matchesSearch = !key || `${doc.id}${doc.applicant}${doc.destination}${doc.type}`.toLowerCase().includes(key)
return matchesSearch
})
return documents.filter((doc) => !key || `${doc.id}${doc.applicant}${doc.destination}${doc.type}`.toLowerCase().includes(key))
})
function handleLogin(credentials) {
if (credentials.username && credentials.password) {
loggedIn.value = true
}
}
function handleRecoverPassword() {
toast('请联系系统管理员重置密码。')
}
function handleSsoLogin() {
toast('SSO 登录通道建设中。')
}
function handleApprove(request) {
const msg = approveRequest(request)
toast(msg)
const message = approveRequest(request)
toast(message)
}
function handleReject(request) {
const msg = rejectRequest(request)
toast(msg)
const message = rejectRequest(request)
toast(message)
}
function handleNavigate(view) {
travelCreateMode.value = false
detailMode.value = false
selectedTravelRequest.value = null
smartEntryOpen.value = false
setView(view)
}
@@ -82,8 +81,6 @@ export function useAppShell() {
function openTravelCreate() {
smartEntryOpen.value = true
travelCreateMode.value = false
detailMode.value = false
selectedTravelRequest.value = null
smartEntryContext.value = { prompt: '', source: 'topbar', request: null }
smartEntrySessionId.value += 1
}
@@ -91,10 +88,7 @@ export function useAppShell() {
function openSmartEntry(payload = {}) {
smartEntryOpen.value = true
travelCreateMode.value = false
if (payload.source !== 'detail') {
detailMode.value = false
selectedTravelRequest.value = null
}
smartEntryContext.value = {
prompt: payload.prompt ?? '',
source: payload.source ?? 'workbench',
@@ -108,14 +102,14 @@ export function useAppShell() {
}
function openRequestDetail(request) {
selectedTravelRequest.value = request
detailMode.value = true
activeView.value = 'requests'
router.push({
name: 'app-request-detail',
params: { requestId: request.id }
})
}
function closeRequestDetail() {
detailMode.value = false
selectedTravelRequest.value = null
router.push({ name: 'app-requests' })
}
return {
@@ -133,14 +127,10 @@ export function useAppShell() {
filteredRequests,
filters,
handleApprove,
handleLogin,
handleNavigate,
handleOpenChat,
handleRecoverPassword,
handleReject,
handleSsoLogin,
handleUpload,
loggedIn,
messageList,
messages,
navItems,
@@ -160,7 +150,6 @@ export function useAppShell() {
smartEntryOpen,
smartEntrySessionId,
toast,
toastText,
topBarView,
travelCreateMode,
travelPrompts,