zyc 44464dd334 feat: DevPerf Dashboard 研发人效看板 v1.0
- 后端:Bun + Hono + Drizzle ORM + SQLite
- 前端:Vue 3 + Naive UI + ECharts
- 项目管理:创建项目 + 绑定 Git 仓库
- OKR 系统:目标/关键结果 CRUD + 进度追踪
- Git 同步:Gitea API 自动同步 commit/PR + 作者关联
- 数据看板:项目 OKR 进度 + KR 状态分布 + 代码活动
- 权限体系:admin/manager/developer/viewer 四级
- Docker 部署:docker-compose + nginx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:57:14 +08:00

83 lines
1.7 KiB
Vue

<script setup lang="ts">
defineProps<{
title: string;
subtitle?: string;
loading?: boolean;
}>();
</script>
<template>
<div class="data-card">
<div class="card-header">
<div>
<h3 class="card-title">{{ title }}</h3>
<p class="card-subtitle" v-if="subtitle">{{ subtitle }}</p>
</div>
<slot name="header-extra" />
</div>
<div class="card-body">
<div v-if="loading" class="loading-skeleton">
<div class="skeleton-bar" style="width: 80%; height: 200px" />
</div>
<slot v-else />
</div>
</div>
</template>
<style scoped>
.data-card {
background: var(--color-bg-card);
border-radius: var(--radius-card);
border: 1px solid var(--color-border);
padding: var(--space-5);
transition: box-shadow var(--duration-hover) var(--ease-default), transform var(--duration-hover) var(--ease-default);
}
.data-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
transform: translateY(-1px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--space-4);
}
.card-title {
font-size: 14px;
font-weight: 700;
color: var(--color-text-primary);
margin: 0;
}
.card-subtitle {
font-size: 12px;
color: var(--color-text-secondary);
margin: 2px 0 0;
}
.card-body {
min-height: 180px;
}
.loading-skeleton {
display: flex;
align-items: center;
justify-content: center;
}
.skeleton-bar {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 8px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>