feat: improve layout shell, navigation composables and add UI assets
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
149
src/App.vue
149
src/App.vue
@@ -1,29 +1,46 @@
|
||||
<template>
|
||||
<!-- Login Page -->
|
||||
<LoginView v-if="!loggedIn" @login="handleLogin" />
|
||||
<LoginView
|
||||
v-if="!loggedIn"
|
||||
@login="handleLogin"
|
||||
@recover-password="handleRecoverPassword"
|
||||
@sso-login="handleSsoLogin"
|
||||
/>
|
||||
|
||||
<!-- Main App -->
|
||||
<div v-else class="app">
|
||||
<SidebarRail
|
||||
:nav-items="navItems"
|
||||
:active-view="activeView"
|
||||
@navigate="setView"
|
||||
@open-chat="openChat"
|
||||
@navigate="handleNavigate"
|
||||
@open-chat="handleOpenChat"
|
||||
/>
|
||||
|
||||
<main class="main">
|
||||
<main
|
||||
class="main"
|
||||
:class="{
|
||||
'chat-main': activeView === 'chat',
|
||||
'overview-main': activeView === 'overview',
|
||||
'requests-main': activeView === 'requests'
|
||||
}"
|
||||
>
|
||||
<TopBar
|
||||
:current-view="currentView"
|
||||
:current-view="topBarView"
|
||||
:search="search"
|
||||
:active-view="activeView"
|
||||
:ranges="ranges"
|
||||
:active-range="activeRange"
|
||||
:custom-range="customRange"
|
||||
@update:search="search = $event"
|
||||
@update:active-range="activeRange = $event"
|
||||
@update:custom-range="customRange = $event"
|
||||
@batch-approve="toast('已筛出 23 个低风险单据,可进入批量通过确认。')"
|
||||
@open-chat="openChat"
|
||||
@new-application="openNewChat"
|
||||
@open-chat="handleOpenChat"
|
||||
@new-application="openTravelCreate"
|
||||
/>
|
||||
|
||||
<FilterBar
|
||||
v-if="activeView !== 'chat'"
|
||||
v-if="activeView !== 'chat' && activeView !== 'overview' && activeView !== 'requests'"
|
||||
:compact="activeView === 'overview'"
|
||||
:filters="filters"
|
||||
:ranges="ranges"
|
||||
@@ -31,23 +48,26 @@
|
||||
@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">
|
||||
<section
|
||||
class="workarea"
|
||||
:class="{
|
||||
'chat-workarea': activeView === 'chat',
|
||||
'requests-workarea': activeView === 'requests'
|
||||
}"
|
||||
>
|
||||
<OverviewView
|
||||
v-if="activeView === 'overview'"
|
||||
:filtered-requests="filteredRequests"
|
||||
@ask="openChat"
|
||||
@ask="handleOpenChat"
|
||||
@approve="handleApprove"
|
||||
@reject="handleReject"
|
||||
/>
|
||||
|
||||
<TravelReimbursementCreateView
|
||||
v-else-if="activeView === 'chat' && travelCreateMode"
|
||||
@back-to-requests="backToRequests"
|
||||
/>
|
||||
|
||||
<ChatView
|
||||
v-else-if="activeView === 'chat'"
|
||||
:documents="filteredDocuments"
|
||||
@@ -61,7 +81,7 @@
|
||||
@send="sendMessage"
|
||||
@upload="handleUpload"
|
||||
@draft="draft = $event"
|
||||
@select-case="openChat"
|
||||
@select-case="handleOpenChat"
|
||||
@approve-case="toast(`${activeCase?.id} 已生成通过意见。`)"
|
||||
@reject-case="toast(`${activeCase?.id} 已转人工复核。`)"
|
||||
/>
|
||||
@@ -69,9 +89,10 @@
|
||||
<RequestsView
|
||||
v-else-if="activeView === 'requests'"
|
||||
:filtered-requests="filteredRequests"
|
||||
@ask="openChat"
|
||||
@ask="handleOpenChat"
|
||||
@approve="handleApprove"
|
||||
@reject="handleReject"
|
||||
@create-request="openTravelCreate"
|
||||
/>
|
||||
|
||||
<PoliciesView v-else-if="activeView === 'policies'" />
|
||||
@@ -80,8 +101,9 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<ToastNotification :toast-text="toastText" />
|
||||
</div>
|
||||
|
||||
<ToastNotification :toast-text="toastText" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -90,26 +112,37 @@ 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 TravelReimbursementCreateView from './views/TravelReimbursementCreateView.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 { ref, computed } 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'
|
||||
import { documents } from './data/requests.js'
|
||||
|
||||
const loggedIn = ref(false)
|
||||
const travelCreateMode = ref(false)
|
||||
|
||||
function handleLogin() {
|
||||
loggedIn.value = true
|
||||
function handleLogin(credentials) {
|
||||
if (credentials.username && credentials.password) {
|
||||
loggedIn.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function handleRecoverPassword() {
|
||||
toast('请联系系统管理员重置密码。')
|
||||
}
|
||||
|
||||
function handleSsoLogin() {
|
||||
toast('SSO 登录通道建设中。')
|
||||
}
|
||||
|
||||
const { activeView, currentView, setView } = useNavigation()
|
||||
@@ -117,19 +150,25 @@ const { requests, search, filters, ranges, activeRange, filteredRequests, approv
|
||||
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 customRange = ref({ start: '2024-07-06', end: '2024-07-12' })
|
||||
const travelPrompts = ['帮我提交出差申请', '预订机票', '预订酒店', '预订火车票', '查询差旅政策']
|
||||
|
||||
const topBarView = computed(() => {
|
||||
if (travelCreateMode.value) {
|
||||
return {
|
||||
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)
|
||||
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
|
||||
return matchesSearch
|
||||
})
|
||||
})
|
||||
|
||||
@@ -142,20 +181,62 @@ function handleReject(request) {
|
||||
const msg = rejectRequest(request)
|
||||
toast(msg)
|
||||
}
|
||||
|
||||
function handleNavigate(view) {
|
||||
travelCreateMode.value = false
|
||||
setView(view)
|
||||
}
|
||||
|
||||
function handleOpenChat(request) {
|
||||
travelCreateMode.value = false
|
||||
openChat(request)
|
||||
}
|
||||
|
||||
function openTravelCreate() {
|
||||
travelCreateMode.value = true
|
||||
activeView.value = 'chat'
|
||||
}
|
||||
|
||||
function backToRequests() {
|
||||
travelCreateMode.value = false
|
||||
activeView.value = 'requests'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app {
|
||||
min-height: 100dvh;
|
||||
display: grid;
|
||||
grid-template-columns: 256px minmax(0, 1fr);
|
||||
grid-template-columns: 220px minmax(0, 1fr);
|
||||
background: var(--bg);
|
||||
}
|
||||
.main { min-width: 0; display: grid; grid-template-rows: auto auto minmax(0, 1fr); }
|
||||
.main.overview-main {
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
}
|
||||
.main.chat-main {
|
||||
height: 100dvh;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
overflow: hidden;
|
||||
}
|
||||
.main.requests-main {
|
||||
height: 100dvh;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
overflow: hidden;
|
||||
}
|
||||
.workarea { overflow: auto; padding: 24px; }
|
||||
.workarea.chat-workarea {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.workarea.requests-workarea {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.app { grid-template-columns: 236px minmax(0, 1fr); }
|
||||
.app { grid-template-columns: 220px minmax(0, 1fr); }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.app { display: block; }
|
||||
|
||||
Reference in New Issue
Block a user