feat: enhance layout components, data layer and global styles

- SidebarRail, TopBar, FilterBar: improved navigation and filtering UX

- metrics.js, requests.js: expanded data with multi-range trend series

- composables: enhanced useChat, useNavigation, useRequests

- global.css: refined design tokens and utility classes

- Add DocFilterBar component and LoginView page

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-29 23:35:56 +08:00
parent 7141e1d11a
commit e54ebd072a
15 changed files with 845 additions and 199 deletions

View File

@@ -1,5 +1,9 @@
<template>
<div class="app">
<!-- Login Page -->
<LoginView v-if="!loggedIn" @login="handleLogin" />
<!-- Main App -->
<div v-else class="app">
<SidebarRail
:nav-items="navItems"
:active-view="activeView"
@@ -11,19 +15,30 @@
<TopBar
:current-view="currentView"
:search="search"
:active-view="activeView"
@update:search="search = $event"
@batch-approve="toast('已筛出 23 个低风险单据可进入批量通过确认')"
@open-chat="openChat"
@new-application="openNewChat"
/>
<FilterBar
v-if="activeView !== 'chat'"
:compact="activeView === 'overview'"
:filters="filters"
:ranges="ranges"
:active-range="activeRange"
@update:active-range="activeRange = $event"
/>
<DocFilterBar
v-if="activeView === 'chat'"
:doc-filters="docFilters"
:doc-months="docMonths"
:doc-types="docTypes"
:doc-statuses="docStatuses"
/>
<section class="workarea">
<OverviewView
v-if="activeView === 'overview'"
@@ -35,16 +50,18 @@
<ChatView
v-else-if="activeView === 'chat'"
ref="chatViewRef"
:documents="filteredDocuments"
:doc-search="docSearch"
:messages="messages"
:uploaded-files="uploadedFiles"
:active-case="activeCase"
:quick-prompts="prompts"
:quick-prompts="travelPrompts"
:draft="draft"
:message-list="messageList"
@send="sendMessage"
@upload="handleUpload"
@draft="draft = $event"
@select-case="openChat"
@approve-case="toast(`${activeCase?.id} 已生成通过意见`)"
@reject-case="toast(`${activeCase?.id} 已转人工复核`)"
/>
@@ -73,23 +90,49 @@ import './assets/styles/global.css'
import SidebarRail from './components/layout/SidebarRail.vue'
import TopBar from './components/layout/TopBar.vue'
import FilterBar from './components/layout/FilterBar.vue'
import DocFilterBar from './components/layout/DocFilterBar.vue'
import ToastNotification from './components/shared/ToastNotification.vue'
import LoginView from './views/LoginView.vue'
import OverviewView from './views/OverviewView.vue'
import ChatView from './views/ChatView.vue'
import RequestsView from './views/RequestsView.vue'
import PoliciesView from './views/PoliciesView.vue'
import AuditView from './views/AuditView.vue'
import { ref, computed, reactive } from 'vue'
import { useNavigation, navItems } from './composables/useNavigation.js'
import { useRequests } from './composables/useRequests.js'
import { useChat } from './composables/useChat.js'
import { useToast } from './composables/useToast.js'
import { documents, docTypes, docStatuses, docMonths } from './data/requests.js'
const loggedIn = ref(false)
function handleLogin() {
loggedIn.value = true
}
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 } = useChat(activeView)
const { messages, draft, uploadedFiles, messageList, activeCase, prompts, sendMessage, handleUpload, openChat, openNewChat } = useChat(activeView)
const { toastText, toast } = useToast()
/* ── Document management state ── */
const docSearch = ref('')
const docFilters = reactive({ month: '2026-04', type: '全部类型', status: '全部状态' })
const travelPrompts = ['帮我提交出差申请', '预订机票', '预订酒店', '预订火车票', '查询差旅政策']
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)
const matchesType = docFilters.type === '全部类型' || doc.type === docFilters.type
const matchesStatus = docFilters.status === '全部状态' || doc.status === docFilters.status
const matchesMonth = doc.date.startsWith(docFilters.month)
return matchesSearch && matchesType && matchesStatus && matchesMonth
})
})
function handleApprove(request) {
const msg = approveRequest(request)
toast(msg)
@@ -105,18 +148,17 @@ function handleReject(request) {
.app {
min-height: 100dvh;
display: grid;
grid-template-columns: 76px minmax(0, 1fr);
background:
radial-gradient(circle at 22% -12%, rgba(51,92,255,.10), transparent 34%),
linear-gradient(180deg, #fff 0, var(--bg) 260px);
grid-template-columns: 256px minmax(0, 1fr);
background: var(--bg);
}
.main { min-width: 0; display: grid; grid-template-rows: auto auto minmax(0, 1fr); }
.workarea { overflow: auto; padding: 22px 28px 34px; }
.workarea { overflow: auto; padding: 24px; }
@media (max-width: 1180px) {
.app { grid-template-columns: 72px minmax(0, 1fr); }
.app { grid-template-columns: 236px minmax(0, 1fr); }
}
@media (max-width: 760px) {
.app { display: block; }
.workarea { padding: 18px 16px 28px; }
}
</style>