From 7fd0f9b706455843f0e955a58625d667efc06fbb Mon Sep 17 00:00:00 2001 From: v6ole Date: Sun, 12 Jul 2026 13:15:50 +0800 Subject: [PATCH] feat: add sidebar entry + route + dashboard leave card + on_leave status Co-Authored-By: Claude --- frontend/src/components/DesktopLayout.vue | 1 + frontend/src/router/index.ts | 1 + frontend/src/views/desktop/Dashboard.vue | 40 +++++++++++++++++++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/DesktopLayout.vue b/frontend/src/components/DesktopLayout.vue index a411523..153b097 100644 --- a/frontend/src/components/DesktopLayout.vue +++ b/frontend/src/components/DesktopLayout.vue @@ -28,6 +28,7 @@ const menuGroups = computed(() => { { path: '/work-plans', label: '工作计划', icon: '' }, { path: '/mini-business', label: '商机跟单', icon: '' }, { path: '/key-visits', label: '要客拜访', icon: '' }, + { path: '/leaves', label: '请假管理', icon: '' }, ], }, ] diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index e753ebc..3ff56e7 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -55,6 +55,7 @@ const router = createRouter({ { path: 'work-plans', name: 'WorkPlans', component: () => import('@/views/desktop/WorkPlans.vue') }, { path: 'mini-business', name: 'MiniBusiness', component: () => import('@/views/desktop/MiniBusiness.vue') }, { path: 'key-visits', name: 'KeyVisits', component: () => import('@/views/desktop/KeyVisits.vue') }, + { path: 'leaves', name: 'Leaves', component: () => import('@/views/desktop/Leaves.vue') }, { path: 'workspace', name: 'ManagerWorkspace', component: () => import('@/views/desktop/ManagerWorkspace.vue') }, { path: 'customers', name: 'CustomerManage', component: () => import('@/views/desktop/CustomerManage.vue') }, { path: 'users', name: 'UserManage', component: () => import('@/views/desktop/UserManage.vue') }, diff --git a/frontend/src/views/desktop/Dashboard.vue b/frontend/src/views/desktop/Dashboard.vue index fb51313..fcdee82 100644 --- a/frontend/src/views/desktop/Dashboard.vue +++ b/frontend/src/views/desktop/Dashboard.vue @@ -9,7 +9,7 @@ import api from '@/api/index' const router = useRouter() const auth = useAuthStore() const loading = ref(false) -const stats = ref({ week_visits: 0, work_plans: 0, mini_business: 0, key_visits: 0, week_start: '', week_end: '' }) +const stats = ref({ week_visits: 0, work_plans: 0, mini_business: 0, key_visits: 0, week_leaves: 0, week_start: '', week_end: '' }) const progress = ref([]) const weekOffset = ref(0) // 0 = current week, -1 = last week, etc. @@ -76,7 +76,8 @@ async function handleExport() { } catch (e: any) { ElMessage.error('导出失败') } } -function rowState(p: any): 'full' | 'catching' | 'missing' { +function rowState(p: any): 'full' | 'catching' | 'missing' | 'on_leave' { + if (p.on_leave) return 'on_leave' if (p.has_reported_today && p.completed) return 'full' if (p.has_reported_today && !p.completed) return 'catching' return 'missing' @@ -170,6 +171,17 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { 要客拜访 +
+
+ + + +
+
+ {{ stats.week_leaves }} + 本周请假 +
+
@@ -194,24 +206,26 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { >
{{ p.manager_name }} - {{ { full: '本周已满', catching: '今日已填', missing: '今日未填' }[rowState(p)] }} + {{ { full: '本周已满', catching: '今日已填', missing: '今日未填', on_leave: '请假中' }[rowState(p)] }} - {{ p.visit_count }} / {{ p.expected }} 条 + {{ p.visit_count }} / {{ p.expected }} 条
+ 请假中 @@ -241,7 +255,7 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { /* ═══ Stat Grid ═══ */ .stat-grid { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 24px; } @@ -258,6 +272,7 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { .stat-card:hover { box-shadow: var(--shadow-md); } .stat-card--clickable { cursor: pointer; } .stat-card--clickable:hover { border-color: var(--gold); } +.stat-card--slate:hover { border-color: #5B7FA5; } .stat-glyph { width: 48px; height: 48px; @@ -268,6 +283,7 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { .stat-glyph--sage { background: #EDF2EC; color: var(--sage); } .stat-glyph--gold { background: #FBF6EE; color: var(--gold); } .stat-glyph--vermilion { background: #FBF1EE; color: var(--vermilion); } +.stat-glyph--slate { color: #5B7FA5; } .stat-body { display: flex; flex-direction: column; } .stat-num { @@ -308,6 +324,7 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { .progress-row--missing { border-left-color: var(--vermilion); background: rgba(184,71,46,0.02); } .progress-row--catching { border-left-color: var(--gold); background: rgba(196,147,74,0.03); } .progress-row--full { border-left-color: var(--sage); background: rgba(74,103,65,0.02); } +.progress-row--on_leave { border-left-color: #5B7FA5; } /* ═══ Chinese Seal Stamp Watermark ═══ */ .seal-stamp { @@ -357,6 +374,13 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { color: var(--sage); } +/* On-leave stamp — slate blue-gray ink */ +.seal-stamp--on_leave { + border-color: #5B7FA5; + outline-color: #5B7FA5; + color: #5B7FA5; +} + .seal-char { font-family: var(--font-heading); font-size: 20px; @@ -382,6 +406,7 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { .progress-status--missing { color: var(--vermilion); } .progress-status--catching { color: var(--gold); } .progress-status--full { color: var(--sage); } +.progress-status--on_leave { color: #5B7FA5; } .progress-count { font-family: var(--font-mono); @@ -389,5 +414,6 @@ function rowState(p: any): 'full' | 'catching' | 'missing' { position: relative; z-index: 1; } +.leave-text { font-size: 13px; color: #5B7FA5; font-family: var(--font-body); } .empty { text-align: center; color: var(--c-text-muted); padding: 40px 0; font-family: var(--font-body); }