DeployHelperFront/src/components/ProjectCard.vue

175 lines
3.5 KiB
Vue

<template>
<el-card class="project-card" shadow="hover">
<div class="project-header">
<div class="project-info">
<h3 class="project-name">{{ project.name || '未命名项目' }}</h3>
<p class="project-description">{{ project.description || '暂无描述' }}</p>
</div>
<div class="project-status">
<el-tag
:type="getStatusType(project.status)"
:effect="project.status === 'success' ? 'light' : 'plain'"
>
{{ getStatusText(project.status) }}
</el-tag>
</div>
</div>
<div class="project-details">
<div class="detail-item">
<el-icon><Folder /></el-icon>
<span>{{ project.path || '/' }}</span>
</div>
<div class="detail-item">
<el-icon><Clock /></el-icon>
<span>{{ formatTime(project.updatedAt || project.createdAt) }}</span>
</div>
<div class="detail-item" v-if="project.version">
<el-icon><PriceTag /></el-icon>
<span>v{{ project.version }}</span>
</div>
</div>
<div class="project-actions">
<el-button size="small" @click="$emit('view', project)">
查看详情
</el-button>
<el-button
size="small"
type="primary"
@click="$emit('deploy', project)"
:loading="deploying"
>
{{ deploying ? '部署中...' : '部署' }}
</el-button>
</div>
</el-card>
</template>
<script setup>
import { ref } from 'vue'
import { Folder, Clock, PriceTag } from '@element-plus/icons-vue'
import { formatDistanceToNow } from '@/utils/format'
const props = defineProps({
project: {
type: Object,
required: true
}
})
const emit = defineEmits(['view', 'deploy'])
const deploying = ref(false)
// 获取状态类型
const getStatusType = (status) => {
const types = {
success: 'success',
running: 'warning',
failed: 'danger',
pending: 'info'
}
return types[status] || 'info'
}
// 获取状态文本
const getStatusText = (status) => {
const texts = {
success: '部署成功',
running: '部署中',
failed: '部署失败',
pending: '待部署'
}
return texts[status] || '未知状态'
}
// 格式化时间
const formatTime = (time) => {
if (!time) return '未知时间'
try {
return formatDistanceToNow(new Date(time))
} catch (error) {
return '时间格式错误'
}
}
</script>
<style scoped>
.project-card {
height: 100%;
transition: all 0.3s ease;
cursor: pointer;
}
.project-card:hover {
transform: translateY(-2px);
}
.project-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
}
.project-info {
flex: 1;
min-width: 0;
}
.project-name {
font-size: 16px;
font-weight: 600;
color: #262626;
margin: 0 0 8px 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.project-description {
font-size: 14px;
color: #8c8c8c;
margin: 0;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.project-status {
flex-shrink: 0;
margin-left: 12px;
}
.project-details {
margin-bottom: 16px;
}
.detail-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-size: 12px;
color: #8c8c8c;
}
.detail-item:last-child {
margin-bottom: 0;
}
.detail-item span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.project-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
</style>