feat: add employee management, backend health check, and UI improvements
This commit is contained in:
@@ -28,14 +28,23 @@
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<button class="rail-user" type="button" aria-label="打开用户菜单">
|
||||
<span class="user-avatar">张</span>
|
||||
<span class="user-copy">
|
||||
<strong>张晓明</strong>
|
||||
<span>财务管理员</span>
|
||||
</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<div class="rail-user">
|
||||
<div class="user-menu" role="menu" aria-label="用户菜单">
|
||||
<button class="user-menu-item" type="button" @click="emit('logout')">
|
||||
<i class="mdi mdi-logout-variant"></i>
|
||||
<span>退出系统</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="user-summary" tabindex="0" aria-label="用户信息">
|
||||
<span class="user-avatar">{{ displayUser.avatar }}</span>
|
||||
<span class="user-copy">
|
||||
<strong>{{ displayUser.name }}</strong>
|
||||
<span>{{ displayUser.role }}</span>
|
||||
</span>
|
||||
<i class="mdi mdi-chevron-up"></i>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -44,17 +53,25 @@ import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
navItems: { type: Array, required: true },
|
||||
activeView: { type: String, required: true }
|
||||
activeView: { type: String, required: true },
|
||||
currentUser: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
name: '系统管理员',
|
||||
role: '财务管理员',
|
||||
avatar: '管'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['navigate', 'openChat'])
|
||||
const emit = defineEmits(['navigate', 'openChat', 'logout'])
|
||||
|
||||
const sidebarMeta = {
|
||||
overview: { label: '总览' },
|
||||
workbench: { label: '个人工作台' },
|
||||
requests: { label: '差旅申请/报销' },
|
||||
approval: { label: '审批中心', badge: '12' },
|
||||
chat: { label: 'AI助手' },
|
||||
chat: { label: 'AI 助手' },
|
||||
policies: { label: '知识管理' },
|
||||
audit: { label: '技能中心' },
|
||||
employees: { label: '员工管理' }
|
||||
@@ -67,6 +84,12 @@ const decoratedNavItems = computed(() =>
|
||||
badge: sidebarMeta[item.id]?.badge
|
||||
}))
|
||||
)
|
||||
|
||||
const displayUser = computed(() => ({
|
||||
name: props.currentUser?.name || '系统管理员',
|
||||
role: props.currentUser?.role || '财务管理员',
|
||||
avatar: props.currentUser?.avatar || '管'
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -77,10 +100,10 @@ const decoratedNavItems = computed(() =>
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255,255,255,.98), rgba(248,251,250,.96)),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 251, 250, 0.96)),
|
||||
#fff;
|
||||
border-right: 1px solid #dbe4ee;
|
||||
box-shadow: 1px 0 0 rgba(15,23,42,.02);
|
||||
box-shadow: 1px 0 0 rgba(15, 23, 42, 0.02);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@@ -164,13 +187,13 @@ const decoratedNavItems = computed(() =>
|
||||
}
|
||||
|
||||
.nav-btn:hover {
|
||||
background: rgba(16,185,129,.07);
|
||||
background: rgba(16, 185, 129, 0.07);
|
||||
color: #0f9f78;
|
||||
}
|
||||
|
||||
.nav-btn.active {
|
||||
background: linear-gradient(90deg, rgba(16,185,129,.16), rgba(16,185,129,.08));
|
||||
border-color: rgba(16,185,129,.10);
|
||||
background: linear-gradient(90deg, rgba(16, 185, 129, 0.16), rgba(16, 185, 129, 0.08));
|
||||
border-color: rgba(16, 185, 129, 0.1);
|
||||
color: #059669;
|
||||
box-shadow: inset 3px 0 0 #10b981;
|
||||
}
|
||||
@@ -221,25 +244,31 @@ const decoratedNavItems = computed(() =>
|
||||
}
|
||||
|
||||
.rail-user {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 74px;
|
||||
display: grid;
|
||||
grid-template-columns: 38px minmax(0, 1fr) 22px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 78px;
|
||||
margin: 0;
|
||||
padding: 16px 20px 18px;
|
||||
border: 0;
|
||||
border-top: 1px solid transparent;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
text-align: left;
|
||||
transition: background 180ms var(--ease), border-color 180ms var(--ease);
|
||||
border-top: 1px solid #edf2f7;
|
||||
}
|
||||
|
||||
.rail-user:hover {
|
||||
border-top-color: #e2e8f0;
|
||||
background: rgba(255,255,255,.72);
|
||||
.user-summary {
|
||||
min-width: 0;
|
||||
min-height: 42px;
|
||||
display: grid;
|
||||
grid-template-columns: 38px minmax(0, 1fr) 18px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 4px 0 0;
|
||||
color: #64748b;
|
||||
border-radius: 12px;
|
||||
outline: none;
|
||||
transition: background 180ms var(--ease);
|
||||
}
|
||||
|
||||
.rail-user:hover .user-summary,
|
||||
.rail-user:focus-within .user-summary {
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
@@ -250,7 +279,7 @@ const decoratedNavItems = computed(() =>
|
||||
border: 2px solid #fff;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, #0f9f78, #65d6b4);
|
||||
box-shadow: 0 6px 14px rgba(15,159,120,.18);
|
||||
box-shadow: 0 6px 14px rgba(15, 159, 120, 0.18);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
@@ -281,10 +310,88 @@ const decoratedNavItems = computed(() =>
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rail-user .mdi {
|
||||
.user-summary .mdi {
|
||||
justify-self: end;
|
||||
color: #718096;
|
||||
color: #94a3b8;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
transition: transform 180ms var(--ease), color 180ms var(--ease);
|
||||
}
|
||||
|
||||
.rail-user:hover .user-summary .mdi,
|
||||
.rail-user:focus-within .user-summary .mdi {
|
||||
color: #0f9f78;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.user-menu {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: calc(100% - 6px);
|
||||
min-width: 132px;
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(226, 232, 240, 0.96);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
box-shadow:
|
||||
0 16px 32px rgba(15, 23, 42, 0.10),
|
||||
0 2px 8px rgba(15, 23, 42, 0.04);
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 180ms var(--ease),
|
||||
transform 180ms var(--ease),
|
||||
box-shadow 180ms var(--ease);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.user-menu::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
bottom: -6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-right: 1px solid rgba(226, 232, 240, 0.96);
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.96);
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.rail-user:hover .user-menu,
|
||||
.rail-user:focus-within .user-menu {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.user-menu-item {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
padding: 0 12px;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
color: #dc2626;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
transition: background 180ms var(--ease), color 180ms var(--ease);
|
||||
}
|
||||
|
||||
.user-menu-item:hover {
|
||||
background: #fff5f5;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.user-menu-item .mdi {
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
|
||||
@@ -117,6 +117,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isEmployees">
|
||||
<div class="kpi-chips">
|
||||
<div v-for="kpi in employeeKpis" :key="kpi.label" class="kpi-chip" :style="{ '--chip-color': kpi.color }">
|
||||
<span class="chip-value">{{ kpi.value }}<small>{{ kpi.unit }}</small></span>
|
||||
<span class="chip-label">{{ kpi.label }}</span>
|
||||
<span class="chip-delta" :class="kpi.trend">{{ kpi.meta }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@@ -130,6 +140,10 @@ const props = defineProps({
|
||||
activeView: { type: String, default: '' },
|
||||
ranges: { type: Array, default: () => [] },
|
||||
activeRange: { type: String, default: '' },
|
||||
employeeSummary: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
customRange: {
|
||||
type: Object,
|
||||
default: () => ({ start: '2024-07-06', end: '2024-07-12' })
|
||||
@@ -150,6 +164,7 @@ const isOverview = computed(() => props.activeView === 'overview')
|
||||
const isRequests = computed(() => props.activeView === 'requests')
|
||||
const isApproval = computed(() => props.activeView === 'approval')
|
||||
const isPolicies = computed(() => props.activeView === 'policies')
|
||||
const isEmployees = computed(() => props.activeView === 'employees')
|
||||
|
||||
const requestKpis = [
|
||||
{ label: '全部单据', value: 30, delta: '+8', trend: 'up', arrow: 'mdi mdi-arrow-up', color: '#10b981' },
|
||||
@@ -178,6 +193,51 @@ const knowledgeKpis = [
|
||||
{ label: '问答总量', value: '8,562', meta: '较上周 +321', trend: 'up', icon: 'mdi mdi-comment-text-multiple-outline', color: '#8b5cf6' },
|
||||
{ label: '知识命中率', value: '87.3%', meta: '较上周 +1.2%', trend: 'up', icon: 'mdi mdi-bullseye-arrow', color: '#f59e0b' }
|
||||
]
|
||||
|
||||
const employeeKpis = computed(() => {
|
||||
const summary = props.employeeSummary ?? {}
|
||||
const total = Number(summary.total ?? 0)
|
||||
const active = Number(summary.active ?? 0)
|
||||
const onboarding = Number(summary.onboarding ?? 0)
|
||||
const disabled = Number(summary.disabled ?? 0)
|
||||
const followUp = Number(summary.followUp ?? 0)
|
||||
const departments = Number(summary.departments ?? 0)
|
||||
|
||||
return [
|
||||
{
|
||||
label: '员工总数',
|
||||
value: total,
|
||||
unit: '人',
|
||||
meta: `覆盖 ${departments} 个部门`,
|
||||
trend: 'up',
|
||||
color: '#10b981'
|
||||
},
|
||||
{
|
||||
label: '在职账号',
|
||||
value: active,
|
||||
unit: '人',
|
||||
meta: total ? `占比 ${Math.round((active / total) * 100)}%` : '等待数据',
|
||||
trend: 'up',
|
||||
color: '#3b82f6'
|
||||
},
|
||||
{
|
||||
label: '待处理状态',
|
||||
value: onboarding + disabled,
|
||||
unit: '人',
|
||||
meta: `试用 ${onboarding} / 停用 ${disabled}`,
|
||||
trend: onboarding + disabled > 0 ? 'down' : 'up',
|
||||
color: '#f59e0b'
|
||||
},
|
||||
{
|
||||
label: '同步待处理',
|
||||
value: followUp,
|
||||
unit: '人',
|
||||
meta: followUp > 0 ? '存在待同步账号' : '资料已同步',
|
||||
trend: followUp > 0 ? 'down' : 'up',
|
||||
color: '#8b5cf6'
|
||||
}
|
||||
]
|
||||
})
|
||||
const calendarOpen = ref(false)
|
||||
const draftStart = ref(props.customRange.start)
|
||||
const draftEnd = ref(props.customRange.end)
|
||||
|
||||
Reference in New Issue
Block a user