<script lang="ts" setup>
|
import { onMounted, ref } from 'vue';
|
|
import { CountTo } from '@vben/common-ui';
|
import { IconifyIcon } from '@vben/icons';
|
|
import { Col, Row } from 'ant-design-vue';
|
|
import { getTaskTodoPage } from '#/api/bpm/task';
|
import { getReimbursementPage } from '#/api/finance/reimbursement';
|
import { getApplicationPage } from '#/api/research/application';
|
import { getGuidelinePage } from '#/api/research/guideline';
|
|
defineOptions({ name: 'DashboardKpiCards' });
|
|
const emit = defineEmits<{
|
navigate: [name: string];
|
}>();
|
|
const todoCount = ref(0);
|
const applicationCount = ref(0);
|
const reimbursementCount = ref(0);
|
const guidelineCount = ref(0);
|
|
async function loadTodo() {
|
try {
|
const result = await getTaskTodoPage({ pageNo: 1, pageSize: 1 });
|
todoCount.value = result.total ?? 0;
|
} catch {
|
/* 无权限或接口异常,保持 0 */
|
}
|
}
|
|
async function loadApplication() {
|
try {
|
const result = await getApplicationPage({ pageNo: 1, pageSize: 1 });
|
applicationCount.value = result.total ?? 0;
|
} catch {
|
/* 无权限或接口异常,保持 0 */
|
}
|
}
|
|
async function loadReimbursement() {
|
try {
|
const result = await getReimbursementPage({ pageNo: 1, pageSize: 1 });
|
reimbursementCount.value = result.total ?? 0;
|
} catch {
|
/* 无权限或接口异常,保持 0 */
|
}
|
}
|
|
async function loadGuideline() {
|
try {
|
const result = await getGuidelinePage({ pageNo: 1, pageSize: 1 });
|
guidelineCount.value = result.total ?? 0;
|
} catch {
|
/* 无权限或接口异常,保持 0 */
|
}
|
}
|
|
onMounted(() => {
|
loadTodo();
|
loadApplication();
|
loadReimbursement();
|
loadGuideline();
|
});
|
</script>
|
|
<template>
|
<Row :gutter="16">
|
<!-- 我的待办 -->
|
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
|
<div
|
class="elite-card group cursor-pointer"
|
@click="emit('navigate', 'BpmTaskTodo')"
|
>
|
<div class="flex items-center gap-4 p-5">
|
<div class="elite-icon-tile">
|
<IconifyIcon class="size-6" icon="lucide:list-todo" />
|
</div>
|
<div class="min-w-0 flex-1">
|
<div class="elite-label">我的待办</div>
|
<div class="flex items-baseline gap-1">
|
<CountTo
|
class="elite-value"
|
:class="todoCount > 0 ? 'text-[#b8901f] dark:text-[#e3c567]' : ''"
|
:duration="1200"
|
:end-val="todoCount"
|
/>
|
<span class="elite-unit">项</span>
|
</div>
|
<div class="elite-meta">审批中心 · 待处理任务</div>
|
</div>
|
</div>
|
</div>
|
</Col>
|
|
<!-- 课题申报 -->
|
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
|
<div
|
class="elite-card group cursor-pointer"
|
@click="emit('navigate', 'ResearchApplication')"
|
>
|
<div class="flex items-center gap-4 p-5">
|
<div class="elite-icon-tile">
|
<IconifyIcon class="size-6" icon="lucide:file-text" />
|
</div>
|
<div class="min-w-0 flex-1">
|
<div class="elite-label">课题申报</div>
|
<div class="flex items-baseline gap-1">
|
<CountTo
|
class="elite-value"
|
:duration="1200"
|
:end-val="applicationCount"
|
/>
|
<span class="elite-unit">项</span>
|
</div>
|
<div class="elite-meta">课题管理 · 申报书</div>
|
</div>
|
</div>
|
</div>
|
</Col>
|
|
<!-- 财务报销 -->
|
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
|
<div
|
class="elite-card group cursor-pointer"
|
@click="emit('navigate', 'FinanceReimbursement')"
|
>
|
<div class="flex items-center gap-4 p-5">
|
<div class="elite-icon-tile">
|
<IconifyIcon class="size-6" icon="lucide:receipt" />
|
</div>
|
<div class="min-w-0 flex-1">
|
<div class="elite-label">财务报销</div>
|
<div class="flex items-baseline gap-1">
|
<CountTo
|
class="elite-value"
|
:duration="1200"
|
:end-val="reimbursementCount"
|
/>
|
<span class="elite-unit">单</span>
|
</div>
|
<div class="elite-meta">财务报销 · 报销单</div>
|
</div>
|
</div>
|
</div>
|
</Col>
|
|
<!-- 课题指南 -->
|
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
|
<div
|
class="elite-card group cursor-pointer"
|
@click="emit('navigate', 'ResearchGuideline')"
|
>
|
<div class="flex items-center gap-4 p-5">
|
<div class="elite-icon-tile">
|
<IconifyIcon class="size-6" icon="lucide:book-open" />
|
</div>
|
<div class="min-w-0 flex-1">
|
<div class="elite-label">课题指南</div>
|
<div class="flex items-baseline gap-1">
|
<CountTo
|
class="elite-value"
|
:duration="1200"
|
:end-val="guidelineCount"
|
/>
|
<span class="elite-unit">份</span>
|
</div>
|
<div class="elite-meta">课题管理 · 申报指南</div>
|
</div>
|
</div>
|
</div>
|
</Col>
|
</Row>
|
</template>
|
|
<style scoped>
|
/* ===== 学术典雅卡片 ===== */
|
.elite-card {
|
background: #fdfcfa;
|
border: 1px solid rgba(212, 175, 55, 0.22);
|
border-radius: 14px;
|
box-shadow: 0 2px 14px rgba(15, 36, 64, 0.05);
|
transition:
|
border-color 0.3s ease,
|
box-shadow 0.3s ease;
|
}
|
|
.elite-card:hover {
|
border-color: rgba(212, 175, 55, 0.5);
|
box-shadow: 0 6px 20px rgba(15, 36, 64, 0.09);
|
}
|
|
.dark .elite-card {
|
background: #101a2b;
|
border-color: rgba(212, 175, 55, 0.2);
|
box-shadow: 0 2px 14px rgba(0, 0, 0, 0.3);
|
}
|
|
/* 图标底:藏青渐变 + 金色图标 */
|
.elite-icon-tile {
|
display: flex;
|
width: 48px;
|
height: 48px;
|
flex-shrink: 0;
|
align-items: center;
|
justify-content: center;
|
border-radius: 12px;
|
color: #e3c567;
|
background: linear-gradient(135deg, #1e4571, #0f2440);
|
border: 1px solid rgba(212, 175, 55, 0.3);
|
box-shadow: 0 4px 12px rgba(15, 36, 64, 0.18);
|
transition: border-color 0.3s ease;
|
}
|
|
/* 悬停只让金色描边微微提亮,不做缩放 */
|
.group:hover .elite-icon-tile {
|
border-color: rgba(212, 175, 55, 0.6);
|
}
|
|
.elite-label {
|
margin-bottom: 4px;
|
font-size: 12px;
|
font-weight: 500;
|
letter-spacing: 1px;
|
color: #8a94a6;
|
}
|
|
.elite-value {
|
font-size: 24px;
|
font-weight: 700;
|
line-height: 1.2;
|
color: #0f2440;
|
}
|
|
.dark .elite-value {
|
color: #e3c567;
|
}
|
|
.elite-unit {
|
font-size: 12px;
|
color: #8a94a6;
|
}
|
|
.elite-meta {
|
margin-top: 4px;
|
font-size: 12px;
|
color: #8a94a6;
|
}
|
</style>
|