Skip to main content
Glama
DocumentStatusProgress.vue8.62 kB
<template> <div class="document-status-progress"> <div class="progress-container"> <div class="progress-line" :class="{ 'progress-line--active': isProgressActive }" ></div> <!-- 进度节点 --> <div v-for="(step, index) in steps" :key="step.key" class="progress-step" :class="getStepClass(step, index)" > <div class="step-icon"> <el-icon v-if="step.icon" :size="14"> <component :is="step.icon" /> </el-icon> <span v-else class="step-number">{{ index + 1 }}</span> </div> <div class="step-label">{{ step.label }}</div> </div> </div> <!-- 状态描述 --> <div class="status-description" :class="`status-description--${currentStatus}`" > <span class="status-text">{{ getStatusText(status) }}</span> <el-icon v-if="status === 'pending'" class="loading-icon"> <Loading /> </el-icon> </div> </div> </template> <script setup lang="ts"> import { computed } from "vue"; import { useI18n } from "vue-i18n"; import { DocumentAdd, Clock, Loading, Check, Close, } from "@element-plus/icons-vue"; interface Props { status: "valid" | "invalid" | "pending" | "unknown"; size?: "small" | "default" | "large"; } const props = withDefaults(defineProps<Props>(), { size: "default", }); const { t } = useI18n(); // 进度步骤定义 const steps = computed(() => [ { key: "created", label: t("openapi.progress.created"), icon: DocumentAdd, }, { key: "pending", label: t("openapi.progress.pending"), icon: Clock, }, { key: "validating", label: t("openapi.progress.validating"), icon: Loading, }, { key: "completed", label: t("openapi.progress.completed"), icon: props.status === "valid" ? Check : Close, }, ]); // 当前状态映射到步骤索引 const currentStepIndex = computed(() => { switch (props.status) { case "unknown": return 0; // 创建状态 case "pending": return 2; // 验证中状态 case "valid": case "invalid": return 3; // 完成状态 default: return 0; } }); // 计算进度条填充百分比 const progressPercentage = computed(() => { switch (props.status) { case "unknown": return 0; case "pending": return 66; // 到验证中节点 case "valid": case "invalid": return 100; // 完成状态充满 default: return 0; } }); const currentStatus = computed(() => props.status); // 判断进度线是否激活 const isProgressActive = computed(() => { return props.status !== "unknown"; }); // 判断是否已完成 const isCompleted = computed(() => { return props.status === "valid" || props.status === "invalid"; }); // 获取步骤样式类 const getStepClass = (step: any, index: number) => { const classes = [`step-${props.size}`]; if (index < currentStepIndex.value) { classes.push("step--completed"); } else if (index === currentStepIndex.value) { classes.push("step--current"); if (props.status === "valid") { classes.push("step--success"); } else if (props.status === "invalid") { classes.push("step--error"); } else if (props.status === "pending") { classes.push("step--processing"); } } else { classes.push("step--pending"); } return classes; }; // 获取状态文本 const getStatusText = (status: string) => { const statusMap: Record<string, string> = { valid: t("openapi.status.valid"), invalid: t("openapi.status.invalid"), pending: t("openapi.status.pending"), unknown: t("openapi.status.unknown"), }; return statusMap[status] || t("openapi.status.unknown"); }; </script> <style scoped> .document-status-progress { display: flex; flex-direction: column; gap: 12px; } .progress-container { position: relative; display: flex; align-items: center; justify-content: space-between; padding: 0 8px; } .progress-line { position: absolute; top: 50%; left: 14px; right: 14px; height: 6px; background: var(--el-border-color-light); transform: translateY(-50%); transition: all 0.3s ease; z-index: 1; border-radius: 3px; } .progress-line--active { background: linear-gradient( to right, var(--el-color-success) 0%, var(--el-color-success) v-bind(progressPercentage + "%"), var(--el-border-color-light) v-bind(progressPercentage + "%"), var(--el-border-color-light) 100% ); } .progress-step { position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px; z-index: 2; transform: translateY(10px); } .progress-container:has(.progress-line) .progress-step { transform: translateY(10px); } .progress-container:not(:has(.progress-line)) .progress-step { transform: translateY(0); } .step-icon { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; background: var(--el-bg-color); border: 3px solid var(--el-border-color); transition: all 0.3s ease; font-size: 12px; font-weight: 500; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .step-small .step-icon { width: 24px; height: 24px; font-size: 10px; border-width: 2px; } .step-large .step-icon { width: 32px; height: 32px; font-size: 14px; border-width: 3px; } .step-number { color: var(--el-text-color-regular); font-size: inherit; } .step-label { font-size: 11px; color: var(--el-text-color-regular); text-align: center; white-space: nowrap; transition: color 0.3s ease; } .step-small .step-label { font-size: 10px; } .step-large .step-label { font-size: 12px; } /* 步骤状态样式 */ .step--completed .step-icon { background: var(--el-color-success); border-color: var(--el-color-success); color: white; border-width: 0; } .step--completed .step-label { color: var(--el-color-success); } .step--current.step--success .step-icon { background: var(--el-color-success); border-color: var(--el-color-success); color: white; border-width: 0; box-shadow: 0 0 0 3px var(--el-color-success-light-8); } .step--current.step--success .step-label { color: var(--el-color-success); font-weight: 500; } .step--current.step--error .step-icon { background: var(--el-color-danger); border-color: var(--el-color-danger); color: white; border-width: 0; box-shadow: 0 0 0 3px var(--el-color-danger-light-8); } .step--current.step--error .step-label { color: var(--el-color-danger); font-weight: 500; } .step--current.step--processing .step-icon { background: var(--el-color-warning); border-color: var(--el-color-warning); color: white; box-shadow: 0 0 0 3px var(--el-color-warning-light-8); animation: pulse 1.5s ease-in-out infinite; } .step--current.step--processing .step-label { color: var(--el-color-warning); font-weight: 500; } .step--pending .step-icon { background: var(--el-bg-color); border-color: var(--el-border-color-light); color: var(--el-text-color-placeholder); } .step--pending .step-label { color: var(--el-text-color-placeholder); } /* 状态描述样式 */ .status-description { display: flex; align-items: center; justify-content: center; gap: 4px; padding: 6px 12px; border-radius: var(--el-border-radius-base); font-size: 12px; font-weight: 500; transition: all 0.3s ease; margin-top: 4px; } .status-description--valid { background: var(--el-color-success-light-9); color: var(--el-color-success); border: 1px solid var(--el-color-success-light-7); } .status-description--invalid { background: var(--el-color-danger-light-9); color: var(--el-color-danger); border: 1px solid var(--el-color-danger-light-7); } .status-description--pending { background: var(--el-color-warning-light-9); color: var(--el-color-warning); border: 1px solid var(--el-color-warning-light-7); } .status-description--unknown { background: var(--el-color-info-light-9); color: var(--el-color-info); border: 1px solid var(--el-color-info-light-7); } .loading-icon { animation: rotate 1s linear infinite; } /* 动画效果 */ @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* 响应式设计 */ @media (max-width: 768px) { .progress-container { padding: 0 4px; } .step-label { font-size: 10px; } .status-description { font-size: 11px; } } </style>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zaizaizhao/mcp-swagger-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server