| | |
| | | clearable |
| | | ></el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="项目分组" prop="groups"> |
| | | <el-select |
| | | v-model="projectForm.groups" |
| | | placeholder="请选择项目分组(可多选)" |
| | | filterable |
| | | clearable |
| | | multiple |
| | | :style="{ width: '100%' }" |
| | | > |
| | | <el-option |
| | | v-for="(item, index) in groupOptions" |
| | | :key="index" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <span |
| | |
| | | > |
| | | </el-input> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="项目分组" prop="groups"> |
| | | <el-select |
| | | v-model="projectForm.groups" |
| | | placeholder="请选择项目分组(可多选)" |
| | | filterable |
| | | clearable |
| | | multiple |
| | | :style="{ width: '100%' }" |
| | | > |
| | | <el-option |
| | | v-for="(item, index) in groupOptions" |
| | | :key="index" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | ></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <span |
| | | slot="footer" |
| | |
| | | </div> |
| | | </el-header> |
| | | |
| | | <div style="display: flex; align-items: center; margin: 10px"> |
| | | <!-- 新增的启动任务提示牌 --> |
| | | <el-alert |
| | | v-if="recentEnabledProjects.length > 0" |
| | | title="正在启动的定时任务数" |
| | | type="success" |
| | | :closable="false" |
| | | style="flex: 1; margin-right: 10px; border: 1px solid #d9f7be; border-radius: 8px; background: linear-gradient(135deg, #f6ffed, #e6ffd3);"> |
| | | <div class="scroll-wrapper" style="min-width: 180%"> |
| | | <!-- 现代化警示牌区域 --> |
| | | <div class="dashboard-alerts"> |
| | | <!-- 运行状态监控牌 --> |
| | | <div v-if="recentEnabledProjects.length > 0" class="alert-card success-card"> |
| | | <div class="alert-header"> |
| | | <div class="alert-icon"> |
| | | <i class="el-icon-success"></i> |
| | | </div> |
| | | <div class="alert-title"> |
| | | <span class="title-text">运行状态监控</span> |
| | | <span class="title-sub">正在执行的定时任务</span> |
| | | </div> |
| | | <div class="alert-badge">{{ recentEnabledProjects.length }}</div> |
| | | </div> |
| | | <div class="alert-content"> |
| | | <div class="scroll-container"> |
| | | <div class="scroll-content" :style="{ animationDuration: animationDuration, animationPlayState: hoveringSuccess ? 'paused' : 'running' }" |
| | | @mouseenter="hoveringSuccess = true" |
| | | @mouseleave="hoveringSuccess = false"> |
| | | <div v-for="(item, index) in recentEnabledProjects" |
| | | :key="'enabled'+index" |
| | | class="scroll-item" |
| | | @click="$router.push(`/lunarlink/tasks/${item.id}`)" |
| | | style="cursor: pointer;"> |
| | | <el-icon name="el-icon-success" |
| | | style="color: #52c41a; margin-right: 8px;"></el-icon> |
| | | <span class="alert-text"> |
| | | 【{{ item.name }}】启动任务数: |
| | | <strong class="highlight">{{ item.enabled_task_count }}</strong> |
| | | </span> |
| | | <div class="separator" v-if="index !== recentEnabledProjects.length -1"> |
| | | <el-icon name="el-icon-caret-right" |
| | | style="color: #b7eb8f; margin: 0 20px;"></el-icon> |
| | | <div class="scroll-content" |
| | | :style="{ animationDuration: animationDuration, animationPlayState: hoveringSuccess ? 'paused' : 'running' }" |
| | | @mouseenter="hoveringSuccess = true" |
| | | @mouseleave="hoveringSuccess = false"> |
| | | <div v-for="(item, index) in recentEnabledProjects" |
| | | :key="'enabled'+index" |
| | | class="alert-item" |
| | | @click="$router.push(`/lunarlink/tasks/${item.id}`)"> |
| | | <div class="item-content"> |
| | | <div class="project-name">{{ item.name }}</div> |
| | | <div class="task-count"> |
| | | <span class="count-number">{{ item.enabled_task_count }}</span> |
| | | <span class="count-label">个任务运行中</span> |
| | | </div> |
| | | </div> |
| | | <div class="item-divider" v-if="index !== recentEnabledProjects.length - 1"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-alert> |
| | | </div> |
| | | |
| | | <!-- 原有失败巡检预警牌 --> |
| | | <el-alert |
| | | v-if="recentFailedProjects.length > 0" |
| | | title="近24小时失败巡检预警" |
| | | type="error" |
| | | :closable="false" |
| | | style="flex: 1; border: 1px solid #ffd3d4; border-radius: 8px; background: linear-gradient(135deg, #fff6f6, #ffecec);"> |
| | | <div class="scroll-wrapper" style="min-width: 210%"> |
| | | <!-- 风险预警监控牌 --> |
| | | <div v-if="recentFailedProjects.length > 0" class="alert-card error-card"> |
| | | <div class="alert-header"> |
| | | <div class="alert-icon"> |
| | | <i class="el-icon-warning"></i> |
| | | </div> |
| | | <div class="alert-title"> |
| | | <span class="title-text">风险预警监控</span> |
| | | <span class="title-sub">近24小时失败巡检</span> |
| | | </div> |
| | | <div class="alert-badge">{{ recentFailedProjects.length }}</div> |
| | | </div> |
| | | <div class="alert-content"> |
| | | <div class="scroll-container"> |
| | | <div class="scroll-content" :style="{ animationDuration: '25s', animationPlayState: hoveringError ? 'paused' : 'running' }" |
| | | @mouseenter="hoveringError = true" |
| | | @mouseleave="hoveringError = false"> |
| | | <div v-for="(item, index) in recentFailedProjects" |
| | | :key="index" |
| | | class="scroll-item" |
| | | @click="$router.push(`/lunarlink/reports/${item.id}`)" |
| | | style="cursor: pointer;"> |
| | | <el-icon name="el-icon-warning" |
| | | style="color: #ff4d4f; margin-right: 8px;"></el-icon> |
| | | <span class="alert-text"> |
| | | 【{{ item.name }}】在最近24小时中有 |
| | | <strong class="highlight">{{ item.recent_failed_count }}</strong> |
| | | 条失败报告,请及时处理!【最新一条失败巡检报告产生时间为:{{ formatDateTime(item.report_time) }}】 |
| | | </span> |
| | | <div class="separator" v-if="index !== recentFailedProjects.length -1"> |
| | | <el-icon name="el-icon-caret-right" |
| | | style="color: #ffa39e; margin: 0 20px;"></el-icon> |
| | | <div class="scroll-content" |
| | | :style="{ animationDuration: '30s', animationPlayState: hoveringError ? 'paused' : 'running' }" |
| | | @mouseenter="hoveringError = true" |
| | | @mouseleave="hoveringError = false"> |
| | | <div v-for="(item, index) in recentFailedProjects" |
| | | :key="index" |
| | | class="alert-item" |
| | | @click="$router.push(`/lunarlink/reports/${item.id}`)"> |
| | | <div class="item-content"> |
| | | <div class="project-name">{{ item.name }}</div> |
| | | <div class="failure-info"> |
| | | <span class="failure-count">{{ item.recent_failed_count }}</span> |
| | | <span class="failure-label">次失败</span> |
| | | <span class="failure-time">{{ formatDateTime(item.report_time) }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="item-divider" v-if="index !== recentFailedProjects.length - 1"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-alert> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-drawer |
| | |
| | | yapi_base_url: "", |
| | | yapi_openapi_token: "", |
| | | jira_bearer_token: "", |
| | | jira_project_key: "" |
| | | jira_project_key: "", |
| | | groups: [] |
| | | }, |
| | | responsibleOptions: [], |
| | | groupOptions: [], |
| | | rules: { |
| | | name: [ |
| | | { |
| | |
| | | this.projectForm.yapi_openapi_token = row["yapi_openapi_token"]; |
| | | this.projectForm.jira_project_key = row["jira_project_key"]; |
| | | this.projectForm.jira_bearer_token = row["jira_bearer_token"]; |
| | | this.projectForm.groups = row["groups"] || []; |
| | | }, |
| | | handleDelete(index, row) { |
| | | this.$confirm("此操作将永久删除该项目, 是否继续?", "提示", { |
| | |
| | | this.loading = false; |
| | | this.$nextTick(() => { |
| | | if (this.recentFailedProjects.length > 0 || this.recentEnabledProjects.length > 0) { |
| | | const contentWidth = Math.max( |
| | | this.recentFailedProjects.length * 400, |
| | | this.recentEnabledProjects.length * 300 |
| | | ); |
| | | // 根据项目数量和内容长度计算动画持续时间 |
| | | const enabledWidth = this.recentEnabledProjects.length * 280; |
| | | const failedWidth = this.recentFailedProjects.length * 350; |
| | | const contentWidth = Math.max(enabledWidth, failedWidth); |
| | | const container = document.querySelector('.scroll-container'); |
| | | const viewportWidth = container ? container.offsetWidth : 1200; |
| | | this.animationDuration = `${Math.max(10, (contentWidth / viewportWidth) * 20)}s`; |
| | | const viewportWidth = container ? container.offsetWidth : 600; |
| | | this.animationDuration = `${Math.max(15, (contentWidth / viewportWidth) * 25)}s`; |
| | | } |
| | | }); |
| | | } |
| | |
| | | this.projectForm.yapi_openapi_token = ""; |
| | | this.projectForm.jira_bearer_token = ""; |
| | | this.projectForm.jira_project_key = ""; |
| | | this.projectForm.groups = []; |
| | | }, |
| | | closeEditDialog(formName) { |
| | | this.editVisible = false; |
| | |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | getGroupList() { |
| | | this.$api.getGroupList().then(resp => { |
| | | this.groupOptions = resp; |
| | | }); |
| | | } |
| | | }, |
| | | created() { |
| | | this.getProjectList(); |
| | | this.getUserList(); |
| | | this.getGroupList(); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* 公共标题样式 */ |
| | | :deep(.el-alert__title) { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | padding-left: 8px; |
| | | border-left: 3px solid; |
| | | line-height: 1.5; |
| | | /* 现代化警示牌样式 */ |
| | | .dashboard-alerts { |
| | | display: flex; |
| | | gap: 16px; |
| | | margin: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | /* 失败预警样式 */ |
| | | :deep(.el-alert[type="error"]) .el-alert__title { |
| | | color: #ff4d4f !important; |
| | | border-left-color: #ff4d4f; |
| | | } |
| | | :deep(.el-alert[type="error"]) .scroll-item { |
| | | background: rgba(255, 77, 79, 0.08); |
| | | } |
| | | :deep(.el-alert[type="error"]) .alert-text { |
| | | color: #ff4d4f; |
| | | } |
| | | :deep(.el-alert[type="error"]) .separator i { |
| | | color: #ffa39e !important; |
| | | .alert-card { |
| | | flex: 1; |
| | | min-width: 300px; |
| | | background: #ffffff; |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); |
| | | border: 1px solid #f0f0f0; |
| | | overflow: hidden; |
| | | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| | | } |
| | | |
| | | /* 成功预警样式 */ |
| | | :deep(.el-alert[type="success"]) .el-alert__title { |
| | | color: #52c41a !important; |
| | | border-left-color: #52c41a; |
| | | .alert-card:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); |
| | | } |
| | | :deep(.el-alert[type="success"]) .scroll-item { |
| | | background: rgba(82, 196, 26, 0.08); |
| | | |
| | | /* 成功状态卡片 */ |
| | | .success-card { |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | :deep(.el-alert[type="success"]) .alert-text { |
| | | |
| | | .success-card .alert-header { |
| | | background: linear-gradient(135deg, #f6ffed 0%, #e6ffd3 100%); |
| | | } |
| | | |
| | | .success-card .alert-icon { |
| | | background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%); |
| | | } |
| | | |
| | | .success-card .alert-badge { |
| | | background: #52c41a; |
| | | } |
| | | |
| | | .success-card .count-number { |
| | | color: #52c41a; |
| | | } |
| | | :deep(.el-alert[type="success"]) .separator i { |
| | | color: #b7eb8f !important; |
| | | |
| | | /* 错误状态卡片 */ |
| | | .error-card { |
| | | border-left: 4px solid #ff4d4f; |
| | | } |
| | | |
| | | /* 公共滚动样式 */ |
| | | .scroll-wrapper { |
| | | display: inline-block; |
| | | min-width: 100%; |
| | | margin: 0 -20px; |
| | | overflow: hidden; |
| | | position: relative; |
| | | padding: 8px 0; |
| | | .error-card .alert-header { |
| | | background: linear-gradient(135deg, #fff6f6 0%, #ffecec 100%); |
| | | } |
| | | |
| | | .error-card .alert-icon { |
| | | background: linear-gradient(135deg, #ff4d4f 0%, #ff7a7a 100%); |
| | | } |
| | | |
| | | .error-card .alert-badge { |
| | | background: #ff4d4f; |
| | | } |
| | | |
| | | .error-card .failure-count { |
| | | color: #ff4d4f; |
| | | } |
| | | |
| | | /* 卡片头部样式 */ |
| | | .alert-header { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f5f5f5; |
| | | } |
| | | |
| | | .alert-icon { |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 12px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .alert-icon i { |
| | | font-size: 20px; |
| | | color: white; |
| | | } |
| | | |
| | | .alert-title { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .title-text { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #262626; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .title-sub { |
| | | font-size: 12px; |
| | | color: #8c8c8c; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .alert-badge { |
| | | background: #1890ff; |
| | | color: white; |
| | | padding: 4px 8px; |
| | | border-radius: 12px; |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | min-width: 24px; |
| | | text-align: center; |
| | | } |
| | | |
| | | /* 内容区域样式 */ |
| | | .alert-content { |
| | | padding: 0; |
| | | height: 80px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .scroll-container { |
| | | height: 100%; |
| | | overflow: hidden; |
| | | position: relative; |
| | | width: 100%; |
| | | } |
| | | |
| | | .scroll-content { |
| | | display: inline-block; |
| | | white-space: nowrap; |
| | | animation: project-list-scroll linear infinite; |
| | | display: flex; |
| | | align-items: center; |
| | | height: 100%; |
| | | animation: modern-scroll linear infinite; |
| | | animation-play-state: running; |
| | | white-space: nowrap; |
| | | } |
| | | .scroll-item { |
| | | display: inline-flex; |
| | | |
| | | .alert-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 6px 12px; |
| | | border-radius: 20px; |
| | | margin: 0 10px; |
| | | transition: all 0.3s; |
| | | vertical-align: middle; |
| | | padding: 0 24px; |
| | | height: 100%; |
| | | cursor: pointer; |
| | | transition: background-color 0.2s; |
| | | } |
| | | .alert-text { |
| | | |
| | | .alert-item:hover { |
| | | background: rgba(0, 0, 0, 0.02); |
| | | } |
| | | |
| | | .item-content { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .project-name { |
| | | font-size: 14px; |
| | | font-family: 'Microsoft YaHei', sans-serif; |
| | | font-weight: 500; |
| | | color: #262626; |
| | | min-width: 120px; |
| | | } |
| | | .highlight { |
| | | font-weight: 700; |
| | | text-shadow: 0 1px 1px rgba(0,0,0,0.1); |
| | | } |
| | | .separator { |
| | | display: inline-flex; |
| | | |
| | | .task-count { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | @keyframes project-list-scroll { |
| | | 0% { transform: translateX(180%); } |
| | | 100% { transform: translateX(-100%); } |
| | | |
| | | .count-number { |
| | | font-size: 20px; |
| | | font-weight: 700; |
| | | line-height: 1; |
| | | } |
| | | .scroll-item:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 3px 6px rgba(0,0,0,0.15); |
| | | |
| | | .count-label { |
| | | font-size: 12px; |
| | | color: #8c8c8c; |
| | | } |
| | | |
| | | .failure-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .failure-count { |
| | | font-size: 20px; |
| | | font-weight: 700; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .failure-label { |
| | | font-size: 12px; |
| | | color: #8c8c8c; |
| | | } |
| | | |
| | | .failure-time { |
| | | font-size: 11px; |
| | | color: #bfbfbf; |
| | | background: #f5f5f5; |
| | | padding: 2px 6px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .item-divider { |
| | | width: 1px; |
| | | height: 24px; |
| | | background: #f0f0f0; |
| | | margin: 0 0 0 24px; |
| | | } |
| | | |
| | | /* 滚动动画 */ |
| | | @keyframes modern-scroll { |
| | | 0% { |
| | | transform: translateX(100%); |
| | | } |
| | | 100% { |
| | | transform: translateX(-100%); |
| | | } |
| | | } |
| | | |
| | | /* 响应式设计 */ |
| | | @media (max-width: 768px) { |
| | | .dashboard-alerts { |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .alert-card { |
| | | min-width: auto; |
| | | } |
| | | |
| | | .item-content { |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .project-name { |
| | | min-width: auto; |
| | | } |
| | | } |
| | | |
| | | /* 表格相关样式保持不变 */ |
| | | .metric-card { |
| | | padding: 12px; |
| | | border-radius: 8px; |