Version: 0.7.8.dev.260325
后端: 迁移了schedule_plan逻辑并探索了新的架构组织思路 删除了一些Codex测试时产生的单测文件 前端: 做了一些改进
This commit is contained in:
136
frontend/src/views/AssistantView.vue
Normal file
136
frontend/src/views/AssistantView.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import AssistantPanel from '@/components/dashboard/AssistantPanel.vue'
|
||||
|
||||
interface PageSwitchItem {
|
||||
key: 'dashboard' | 'assistant'
|
||||
label: string
|
||||
short: string
|
||||
to: '/dashboard' | '/assistant'
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const switchItems: PageSwitchItem[] = [
|
||||
{
|
||||
key: 'dashboard',
|
||||
label: '排程',
|
||||
short: '排',
|
||||
to: '/dashboard',
|
||||
},
|
||||
{
|
||||
key: 'assistant',
|
||||
label: '对话',
|
||||
short: 'AI',
|
||||
to: '/assistant',
|
||||
},
|
||||
]
|
||||
|
||||
const activeSwitchKey = computed<PageSwitchItem['key']>(() =>
|
||||
route.path.startsWith('/assistant') ? 'assistant' : 'dashboard',
|
||||
)
|
||||
|
||||
function handlePageSwitch(targetPath: PageSwitchItem['to']) {
|
||||
if (route.path !== targetPath) {
|
||||
router.push(targetPath)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="assistant-view">
|
||||
<section class="assistant-view__layout">
|
||||
<aside class="assistant-view__switch-rail glass-panel">
|
||||
<button
|
||||
v-for="item in switchItems"
|
||||
:key="item.key"
|
||||
type="button"
|
||||
class="assistant-view__switch-item"
|
||||
:class="{ 'assistant-view__switch-item--active': activeSwitchKey === item.key }"
|
||||
@click="handlePageSwitch(item.to)"
|
||||
>
|
||||
<span>{{ item.short }}</span>
|
||||
<small>{{ item.label }}</small>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<AssistantPanel class="assistant-view__panel" view-mode="standalone" />
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.assistant-view {
|
||||
height: 100vh;
|
||||
padding: 12px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, #f6f8fb 0%, #eef2f7 100%);
|
||||
}
|
||||
|
||||
.assistant-view__layout {
|
||||
height: calc(100vh - 24px);
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(56px, 0.3fr) minmax(0, 6fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.assistant-view__switch-rail {
|
||||
min-height: 0;
|
||||
border-radius: 18px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
background: linear-gradient(180deg, rgba(249, 250, 252, 0.95), rgba(243, 247, 252, 0.98));
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
|
||||
padding: 14px 7px;
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.assistant-view__switch-item {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
color: #6b7789;
|
||||
padding: 10px 4px 9px;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.assistant-view__switch-item span {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
background: rgba(86, 101, 126, 0.1);
|
||||
}
|
||||
|
||||
.assistant-view__switch-item small {
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.assistant-view__switch-item--active {
|
||||
color: #335fc2;
|
||||
background: linear-gradient(180deg, rgba(88, 126, 224, 0.16), rgba(88, 126, 224, 0.08));
|
||||
}
|
||||
|
||||
.assistant-view__switch-item--active span {
|
||||
background: rgba(58, 95, 184, 0.2);
|
||||
}
|
||||
|
||||
.assistant-view__panel {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border-radius: 18px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import AssistantPanel from '@/components/dashboard/AssistantPanel.vue'
|
||||
import TaskQuadrantCard from '@/components/dashboard/TaskQuadrantCard.vue'
|
||||
@@ -13,6 +13,7 @@ import type { TaskItem, TodayEvent } from '@/types/dashboard'
|
||||
import { formatHeaderDate } from '@/utils/date'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const pageLoading = ref(false)
|
||||
@@ -39,13 +40,27 @@ const taskForm = reactive<{
|
||||
deadline_at: null,
|
||||
})
|
||||
|
||||
const sidebarItems = [
|
||||
{ key: 'home', label: '总览', short: '总' },
|
||||
interface SidebarItem {
|
||||
key: 'home' | 'task' | 'calendar' | 'ai'
|
||||
label: string
|
||||
short: string
|
||||
to?: '/dashboard' | '/assistant'
|
||||
}
|
||||
|
||||
const sidebarItems: SidebarItem[] = [
|
||||
{ key: 'home', label: '总览', short: '总', to: '/dashboard' },
|
||||
{ key: 'task', label: '任务', short: '任' },
|
||||
{ key: 'calendar', label: '日程', short: '程' },
|
||||
{ key: 'ai', label: '助手', short: 'AI' },
|
||||
{ key: 'ai', label: '助手', short: 'AI', to: '/assistant' },
|
||||
]
|
||||
|
||||
const activeSidebarKey = computed<SidebarItem['key']>(() => {
|
||||
if (route.path.startsWith('/assistant')) {
|
||||
return 'ai'
|
||||
}
|
||||
return 'home'
|
||||
})
|
||||
|
||||
const quadrantOrder = [1, 2, 3, 4] as const
|
||||
|
||||
const quadrantMeta: Record<
|
||||
@@ -217,6 +232,20 @@ function handleCourseImportEntry() {
|
||||
ElMessage.info('课表导入入口已预留,下一步我可以继续把导入流程页接出来')
|
||||
}
|
||||
|
||||
function handleSidebarNavigate(item: SidebarItem) {
|
||||
// 1. 已接通路由的入口直接跳转,避免侧栏按钮成为“仅装饰”元素。
|
||||
// 2. 未接通的入口先给出明确提示,防止用户误以为点击失效。
|
||||
// 3. 同路由不重复 push,避免产生无意义导航与日志噪音。
|
||||
if (item.to) {
|
||||
if (route.path !== item.to) {
|
||||
void router.push(item.to)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.info(`${item.label} 页面正在开发中`)
|
||||
}
|
||||
|
||||
function clampSidebarWidth(nextWidth: number) {
|
||||
return Math.min(110, Math.max(68, nextWidth))
|
||||
}
|
||||
@@ -320,7 +349,8 @@ onBeforeUnmount(() => {
|
||||
:key="item.key"
|
||||
type="button"
|
||||
class="dashboard-sidebar__nav-item"
|
||||
:class="{ 'dashboard-sidebar__nav-item--active': item.key === 'home' }"
|
||||
:class="{ 'dashboard-sidebar__nav-item--active': item.key === activeSidebarKey }"
|
||||
@click="handleSidebarNavigate(item)"
|
||||
>
|
||||
<span>{{ item.short }}</span>
|
||||
<small>{{ item.label }}</small>
|
||||
|
||||
Reference in New Issue
Block a user