|
|
@@ -1,437 +1,268 @@
|
|
|
<template>
|
|
|
- <div class="monitor-report-container" v-loading="loading">
|
|
|
- <div class="status-banner" :class="'banner-' + overallStatus">
|
|
|
- <div class="banner-left">
|
|
|
- <div class="status-visual">
|
|
|
- <div class="status-icon-wrapper">
|
|
|
- <i :class="overallStatus === 'success' ? 'el-icon-circle-check' : 'el-icon-warning-outline'"></i>
|
|
|
- </div>
|
|
|
- <div class="pulse-ring"></div>
|
|
|
- </div>
|
|
|
- <div class="banner-content">
|
|
|
- <div class="banner-title">
|
|
|
- {{ reportData.statusSummary || '核心基础设施巡检' }}
|
|
|
- <el-tag size="mini" :type="overallStatus === 'success' ? 'success' : 'warning'" effect="plain">
|
|
|
- {{ overallStatus === 'success' ? 'HEALTHY' : 'ACTION REQUIRED' }}
|
|
|
+ <div class="dashboard-wrapper">
|
|
|
+ <el-row :gutter="20" class="stat-row">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-card shadow="never" class="role-card">
|
|
|
+ <div class="role-container">
|
|
|
+ <span class="role-label"><i class="el-icon-medal"></i> 当前权限角色:</span>
|
|
|
+ <el-tag
|
|
|
+ v-for="(item, index) in roles"
|
|
|
+ :key="index"
|
|
|
+ effect="dark"
|
|
|
+ size="medium"
|
|
|
+ class="role-tag"
|
|
|
+ @click="goToRole(item)"
|
|
|
+ >
|
|
|
+ {{ item }}
|
|
|
</el-tag>
|
|
|
</div>
|
|
|
- <div class="banner-desc">
|
|
|
- <span>{{ overallDescription }}</span>
|
|
|
- <span class="update-time"><i class="el-icon-time"></i> 最后更新: {{ reportData.lastUpdateTime || '-' }}</span>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20" class="process-row">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <span><i class="el-icon-refresh"></i> CI/CD 流水线</span>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="banner-stats">
|
|
|
- <div class="stat-item">
|
|
|
- <div class="stat-label">巡检项</div>
|
|
|
- <div class="stat-value">{{ cardConfigs.length }}</div>
|
|
|
- </div>
|
|
|
- <div class="stat-divider"></div>
|
|
|
- <div class="stat-item">
|
|
|
- <div class="stat-label">风险项</div>
|
|
|
- <div class="stat-value" :class="hasAnyIssue ? 'risk-text' : ''">{{ totalIssuesCount }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="banner-right">
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- size="medium"
|
|
|
- icon="el-icon-refresh"
|
|
|
- @click="loadReport"
|
|
|
- :loading="loading"
|
|
|
- class="refresh-glow"
|
|
|
- >
|
|
|
- 重新巡检
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div v-for="cat in categories" :key="cat" class="category-group">
|
|
|
- <div class="category-header">
|
|
|
- <div class="category-title-box">
|
|
|
- <span class="category-name">{{ cat }}</span>
|
|
|
- <el-badge
|
|
|
- :value="getCategoryIssueCount(cat)"
|
|
|
- :hidden="getCategoryIssueCount(cat) === 0"
|
|
|
- type="danger"
|
|
|
- is-dot
|
|
|
- class="cat-badge"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="category-line"></div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-row :gutter="24" class="card-grid-row">
|
|
|
- <el-col
|
|
|
- :xs="24" :sm="12" :md="8"
|
|
|
- v-for="config in getCardsByCategory(cat)"
|
|
|
- :key="config.key"
|
|
|
- class="card-col"
|
|
|
- >
|
|
|
- <el-card shadow="hover" class="nice-card" :class="getShadowClass(config)">
|
|
|
- <div slot="header" class="card-header">
|
|
|
- <div class="title-wrapper">
|
|
|
- <i :class="[config.icon, 'icon-accent', config.textColor]"></i>
|
|
|
- <div class="text-group">
|
|
|
- <span class="main-title">{{ config.title }}</span>
|
|
|
- <span class="sub-title">{{ config.subTitle }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="header-ops">
|
|
|
- <el-tooltip content="排查建议" placement="top">
|
|
|
- <el-button
|
|
|
- type="text"
|
|
|
- icon="el-icon-question"
|
|
|
- class="advice-btn"
|
|
|
- @click="showAdvice(config)"
|
|
|
- ></el-button>
|
|
|
- </el-tooltip>
|
|
|
- <el-tag v-if="getData(config.key).length > 0" :type="config.tagType" size="mini" effect="dark">
|
|
|
- {{ getData(config.key).length }}
|
|
|
- </el-tag>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="card-body scroll-style">
|
|
|
- <template v-if="getData(config.key).length > 0">
|
|
|
- <div v-for="(item, index) in getData(config.key)" :key="index" class="metric-entry">
|
|
|
- <div class="entry-info">
|
|
|
- <span class="entry-name" :title="item.name || item.instance">
|
|
|
- {{ item.name || item.instance }}
|
|
|
- </span>
|
|
|
- <span class="entry-val" :class="config.textColor">
|
|
|
- {{ item.value }}<small>{{ config.unit }}</small>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <el-progress
|
|
|
- :percentage="config.isRatio ? (item.value > 100 ? 100 : item.value) : 100"
|
|
|
- :show-text="false"
|
|
|
- :color="config.progColor"
|
|
|
- :stroke-width="6"
|
|
|
- />
|
|
|
+ <div class="steps-wrapper">
|
|
|
+ <el-steps :active="1" finish-status="success" align-center>
|
|
|
+ <el-step title="更新代码" icon="el-icon-edit-outline" />
|
|
|
+ <el-step title="编译代码" icon="el-icon-set-up" />
|
|
|
+ <el-step title="应用打包" icon="el-icon-box" />
|
|
|
+ <el-step title="推送应用" icon="el-icon-upload" />
|
|
|
+ <el-step title="拉取应用" icon="el-icon-download" />
|
|
|
+ <el-step title="部署应用" icon="el-icon-monitor" />
|
|
|
+ </el-steps>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :md="12" :sm="24">
|
|
|
+ <el-card shadow="hover" class="data-card">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <span><i class="el-icon-cpu"></i> 机器节点状态</span>
|
|
|
+ </div>
|
|
|
+ <el-table :data="machineStatList" border stripe size="small">
|
|
|
+ <el-table-column prop="env" label="运行环境">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag size="mini" effect="plain">{{ scope.row.env }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="total" label="资产总数" align="center" />
|
|
|
+ <el-table-column label="健康状况" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <div class="status-cell">
|
|
|
+ <span class="status-dot online"></span>
|
|
|
+ <span class="status-count">{{ scope.row.onlineCount }}</span>
|
|
|
+ <span class="status-divider">/</span>
|
|
|
+ <span class="status-dot offline"></span>
|
|
|
+ <span class="status-count">{{ scope.row.offlineCount }}</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <div v-else class="empty-state">
|
|
|
- <i class="el-icon-circle-check"></i>
|
|
|
- <p>{{ config.emptyText }}</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-dialog
|
|
|
- :title="'💡 排查建议: ' + currentAdvice.title"
|
|
|
- :visible.sync="dialogVisible"
|
|
|
- width="550px"
|
|
|
- custom-class="advice-dialog"
|
|
|
- append-to-body
|
|
|
- >
|
|
|
- <div class="advice-container">
|
|
|
- <div v-if="currentAdvice.text" class="advice-box">
|
|
|
- <p v-html="formatAdvice(currentAdvice.text)"></p>
|
|
|
- </div>
|
|
|
- <el-empty v-else description="暂无指引" :image-size="60"></el-empty>
|
|
|
- </div>
|
|
|
- <span slot="footer">
|
|
|
- <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
|
|
|
- </span>
|
|
|
- </el-dialog>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :md="12" :sm="24">
|
|
|
+ <el-card shadow="hover" class="data-card">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <span><i class="el-icon-info"></i> 系统核心信息</span>
|
|
|
+ </div>
|
|
|
+ <el-descriptions v-if="sysInfo !== null" :column="1" border size="small">
|
|
|
+ <el-descriptions-item label="应用版本">
|
|
|
+ <el-link
|
|
|
+ type="primary"
|
|
|
+ target="_blank"
|
|
|
+ :href="`https://git.reghao.cn/reghao/devops/commit/${sysInfo.commitId}`"
|
|
|
+ icon="el-icon-link"
|
|
|
+ >
|
|
|
+ {{ sysInfo.commitId.substring(0, 8) }}
|
|
|
+ </el-link>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="机器地址">
|
|
|
+ <code>{{ sysInfo.ipv4 }}</code>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="操作系统">
|
|
|
+ <i class="el-icon-monitor"></i> {{ sysInfo.osInfo }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="JVM 环境">
|
|
|
+ <el-tooltip effect="dark" :content="sysInfo.jvmInfo" placement="top">
|
|
|
+ <span class="text-truncate">{{ sysInfo.jvmInfo }}</span>
|
|
|
+ </el-tooltip>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="启动/PID">
|
|
|
+ <el-tag size="mini" type="success">{{ sysInfo.startAt }}</el-tag>
|
|
|
+ <el-tag size="mini" type="warning" style="margin-left:5px">PID: {{ sysInfo.pid }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import { getDashboard } from '@/api/devops'
|
|
|
+import { getAuthedUser } from '@/utils/auth'
|
|
|
|
|
|
export default {
|
|
|
+ name: 'Dashboard',
|
|
|
data() {
|
|
|
return {
|
|
|
- loading: false,
|
|
|
- dialogVisible: false,
|
|
|
- currentAdvice: { title: '', text: '' },
|
|
|
- // 业务分类
|
|
|
- categories: ['计算资源', '内存指标', '存储文件', '网络协议', '基础环境'],
|
|
|
- reportData: {
|
|
|
- advices: {},
|
|
|
- // 动态数据将通过接口覆盖
|
|
|
- },
|
|
|
- // 完整的 22 个指标配置
|
|
|
- cardConfigs: [
|
|
|
- /* 计算资源 */
|
|
|
- { category: '计算资源', key: 'cpuThrottled', title: 'CPU 性能节流', subTitle: '24H Throttled', icon: 'el-icon-cpu', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 's', isRatio: false, emptyText: '无节流限制' },
|
|
|
- { category: '计算资源', key: 'nodeLoadRisk', title: '节点负载饱和度', subTitle: 'Load5 / Cores', icon: 'el-icon-odometer', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '', isRatio: false, emptyText: '系统负载极低' },
|
|
|
- { category: '计算资源', key: 'contextSwitchRisk', title: '上下文切换', subTitle: 'Context Switches', icon: 'el-icon-refresh', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '次/s', isRatio: false, emptyText: '调度平稳' },
|
|
|
- { category: '计算资源', key: 'zombieRisk', title: '僵尸进程', subTitle: 'Z States', icon: 'el-icon-stopwatch', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '个', isRatio: false, emptyText: '无僵尸进程' },
|
|
|
-
|
|
|
- /* 内存指标 */
|
|
|
- { category: '内存指标', key: 'memRisk', title: '内存水位线', subTitle: '> 85% Usage', icon: 'el-icon-box', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '水位正常' },
|
|
|
- { category: '内存指标', key: 'memPsiRisk', title: '内存紧缩压力', subTitle: 'PSI Stall %', icon: 'el-icon-timer', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '无访问延迟' },
|
|
|
- { category: '内存指标', key: 'oomEvents', title: 'OOM Kill 事件', subTitle: 'Kernel OOM', icon: 'el-icon-warning', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无内存强杀' },
|
|
|
- { category: '内存指标', key: 'pidLimitRisk', title: 'PID 进程数限制', subTitle: 'Forks / Max', icon: 'el-icon-connection', textColor: 'primary-text', tagType: 'primary', progColor: '#409eff', unit: '%', isRatio: true, emptyText: '池空间充足' },
|
|
|
-
|
|
|
- /* 存储文件 */
|
|
|
- { category: '存储文件', key: 'diskUsageRisk', title: '根分区空间', subTitle: '( / ) Usage', icon: 'el-icon-pie-chart', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '空间充沛' },
|
|
|
- { category: '存储文件', key: 'diskPredictRisk', title: '磁盘存满预测', subTitle: 'Predict 24H', icon: 'el-icon-magic-stick', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '', isRatio: false, emptyText: '无写满风险' },
|
|
|
- { category: '存储文件', key: 'inodeRisk', title: 'inode 使用率', subTitle: 'Index Nodes', icon: 'el-icon-files', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '%', isRatio: true, emptyText: '索引节点充足' },
|
|
|
- { category: '存储文件', key: 'diskIo', title: '磁盘 IO 响应', subTitle: 'IO Wait', icon: 'el-icon-receiving', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 'ms', isRatio: false, emptyText: '响应极快' },
|
|
|
- { category: '存储文件', key: 'fdRisk', title: '文件句柄 (FD)', subTitle: 'FD Ratio', icon: 'el-icon-link', textColor: 'primary-text', tagType: 'primary', progColor: '#409eff', unit: '%', isRatio: true, emptyText: '占用正常' },
|
|
|
- { category: '存储文件', key: 'readOnlyFsRisk', title: '文件系统只读', subTitle: 'RO Status', icon: 'el-icon-lock', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '', isRatio: false, emptyText: '挂载正常' },
|
|
|
-
|
|
|
- /* 网络协议 */
|
|
|
- { category: '网络协议', key: 'conntrackRisk', title: '连接跟踪表', subTitle: 'Conntrack', icon: 'el-icon-attract', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '状态健康' },
|
|
|
- { category: '网络协议', key: 'netEstMax', title: 'TCP EST 数量', subTitle: 'Established', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '未超限' },
|
|
|
- { category: '网络协议', key: 'netTwMax', title: 'TCP TIME_WAIT', subTitle: 'Wait Status', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '未超限' },
|
|
|
- { category: '网络协议', key: 'netBandwidthRisk', title: '网卡带宽水位', subTitle: 'Traffic Ratio', icon: 'el-icon-sort', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: '%', isRatio: true, emptyText: '流量平稳' },
|
|
|
- { category: '网络协议', key: 'tcpRetransRisk', title: 'TCP 重传率', subTitle: 'Retrans Ratio', icon: 'el-icon-refresh-left', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '%', isRatio: true, emptyText: '传输稳定' },
|
|
|
- { category: '网络协议', key: 'netOverflows', title: 'TCP 队列溢出', subTitle: 'Listen Overflow', icon: 'el-icon-warning-outline', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无溢出' },
|
|
|
- { category: '网络协议', key: 'netDrops', title: 'TCP 数据包丢弃', subTitle: 'Packet Drops', icon: 'el-icon-circle-close', textColor: 'danger-text', tagType: 'danger', progColor: '#f56c6c', unit: '次', isRatio: false, emptyText: '无丢弃' },
|
|
|
-
|
|
|
- /* 基础环境 */
|
|
|
- { category: '基础环境', key: 'clockSkewRisk', title: '时钟偏移 (NTP)', subTitle: 'Clock Offset', icon: 'el-icon-alarm-clock', textColor: 'warning-text', tagType: 'warning', progColor: '#e6a23c', unit: 's', isRatio: false, emptyText: '同步精确' }
|
|
|
- ]
|
|
|
+ roles: [],
|
|
|
+ machineStatList: [],
|
|
|
+ sysInfo: null
|
|
|
}
|
|
|
},
|
|
|
- computed: {
|
|
|
- totalIssuesCount() {
|
|
|
- return this.cardConfigs.filter(c => this.getData(c.key).length > 0).length
|
|
|
- },
|
|
|
- hasAnyIssue() { return this.totalIssuesCount > 0 },
|
|
|
- overallStatus() { return this.hasAnyIssue ? 'warning' : 'success' },
|
|
|
- overallDescription() {
|
|
|
- return this.hasAnyIssue ? `当前环境存在异常项,请优先处理风险卡片。` : '核心基础设施所有参数均在安全范围内。'
|
|
|
- }
|
|
|
+ created() {
|
|
|
+ const { roles } = getAuthedUser()
|
|
|
+ this.roles = roles
|
|
|
+ document.title = 'Dashboard'
|
|
|
+ this.getData()
|
|
|
},
|
|
|
- created() { this.loadReport() },
|
|
|
methods: {
|
|
|
- getCardsByCategory(cat) {
|
|
|
- return this.cardConfigs.filter(config => config.category === cat)
|
|
|
- },
|
|
|
- getCategoryIssueCount(cat) {
|
|
|
- return this.getCardsByCategory(cat).filter(config => this.getData(config.key).length > 0).length
|
|
|
+ getData() {
|
|
|
+ this.getDevopsDashboard()
|
|
|
},
|
|
|
- getData(key) { return this.reportData[key] || [] },
|
|
|
- getShadowClass(config) {
|
|
|
- return this.getData(config.key).length > 0 ? config.tagType + '-shadow' : ''
|
|
|
- },
|
|
|
- async loadReport() {
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- const resp = await getDashboard()
|
|
|
+ getDevopsDashboard() {
|
|
|
+ getDashboard().then(resp => {
|
|
|
if (resp.code === 0) {
|
|
|
- this.reportData = Object.assign({}, this.reportData, resp.data)
|
|
|
+ this.sysInfo = resp.data.sysInfo
|
|
|
+ this.machineStatList = resp.data.machineStatList
|
|
|
+ } else {
|
|
|
+ this.$message.error(resp.msg)
|
|
|
}
|
|
|
- } finally { this.loading = false }
|
|
|
+ }).catch(error => {
|
|
|
+ this.$message.error(error.message)
|
|
|
+ })
|
|
|
},
|
|
|
- showAdvice(config) {
|
|
|
- this.currentAdvice = {
|
|
|
- title: config.title,
|
|
|
- text: this.reportData.advices[config.key] || '暂无详细指引。'
|
|
|
- }
|
|
|
- this.dialogVisible = true
|
|
|
+ goToRole(data) {
|
|
|
+ this.$message.info('当前操作角色: ' + data)
|
|
|
},
|
|
|
- formatAdvice(text) { return text ? text.replace(/\n/g, '<br>') : '' }
|
|
|
+ // 其余逻辑保持不变...
|
|
|
+ goToDisk() { const path = '/disk'; this.handleRoute(path) },
|
|
|
+ goToVod() { const path = '/vod'; this.handleRoute(path) },
|
|
|
+ goToBlog() { const path = '/blog'; this.handleRoute(path) },
|
|
|
+ handleRoute(path) {
|
|
|
+ if (this.$route.path === path) { this.$router.go(0); return }
|
|
|
+ this.$router.push(path)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-/* 1. 基础容器与间距控制 */
|
|
|
-.monitor-report-container {
|
|
|
- padding: 32px;
|
|
|
- background-color: #f8fafc;
|
|
|
- min-height: 100vh;
|
|
|
+.dashboard-wrapper {
|
|
|
+ padding: 10px;
|
|
|
}
|
|
|
|
|
|
-/* 分类组间距 */
|
|
|
-.category-group {
|
|
|
- margin-bottom: 40px;
|
|
|
- background: rgba(255, 255, 255, 0.4);
|
|
|
- padding: 20px;
|
|
|
- border-radius: 16px;
|
|
|
+.stat-row {
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
-/* 分类头部标题 */
|
|
|
-.category-header {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 24px;
|
|
|
- padding: 0 4px;
|
|
|
+.role-card {
|
|
|
+ background: #fcfcfc;
|
|
|
+ border-left: 4px solid #409EFF;
|
|
|
}
|
|
|
|
|
|
-.category-title-box {
|
|
|
+.role-container {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- background: #1e293b;
|
|
|
- padding: 8px 16px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
-.category-name {
|
|
|
- color: #f8fafc;
|
|
|
- font-weight: 700;
|
|
|
- font-size: 15px;
|
|
|
- letter-spacing: 0.5px;
|
|
|
+.role-label {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #606266;
|
|
|
+ margin-right: 15px;
|
|
|
}
|
|
|
|
|
|
-.category-line {
|
|
|
- flex: 1;
|
|
|
- height: 2px;
|
|
|
- background: linear-gradient(90deg, #cbd5e1 0%, transparent 100%);
|
|
|
- margin-left: 16px;
|
|
|
+.role-tag {
|
|
|
+ margin-right: 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
-/* 卡片容器间距:确保上下卡片不粘连 */
|
|
|
-.card-col {
|
|
|
- margin-bottom: 24px;
|
|
|
+.role-tag:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
-/* 2. 卡片内部样式优化 */
|
|
|
-.nice-card {
|
|
|
- height: 300px;
|
|
|
- border-radius: 12px;
|
|
|
- border: 1px solid #e2e8f0;
|
|
|
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+.card-header {
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
|
|
|
|
-.nice-card:hover {
|
|
|
- transform: translateY(-4px);
|
|
|
+.process-row {
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
-.nice-card >>> .el-card__header {
|
|
|
- padding: 16px 20px;
|
|
|
- background-color: #ffffff;
|
|
|
- border-bottom: 1px solid #f1f5f9;
|
|
|
+.steps-wrapper {
|
|
|
+ padding: 20px 0;
|
|
|
}
|
|
|
|
|
|
-.card-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
+.data-card {
|
|
|
+ min-height: 380px;
|
|
|
}
|
|
|
|
|
|
-.title-wrapper {
|
|
|
+/* 状态点样式 */
|
|
|
+.status-cell {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 12px;
|
|
|
-}
|
|
|
-
|
|
|
-.text-group {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
|
|
|
-.main-title {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 600;
|
|
|
- color: #334155;
|
|
|
+.status-dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin: 0 5px;
|
|
|
}
|
|
|
|
|
|
-.sub-title {
|
|
|
- font-size: 11px;
|
|
|
- color: #94a3b8;
|
|
|
- margin-top: 2px;
|
|
|
+.online {
|
|
|
+ background-color: #67C23A;
|
|
|
+ box-shadow: 0 0 5px #67C23A;
|
|
|
}
|
|
|
|
|
|
-.card-body {
|
|
|
- padding: 16px 20px;
|
|
|
- flex: 1;
|
|
|
- overflow-y: auto;
|
|
|
+.offline {
|
|
|
+ background-color: #F56C6C;
|
|
|
+ box-shadow: 0 0 5px #F56C6C;
|
|
|
}
|
|
|
|
|
|
-/* 指标条目 */
|
|
|
-.metric-entry {
|
|
|
- margin-bottom: 16px;
|
|
|
+.status-count {
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
|
|
|
|
-.entry-info {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- margin-bottom: 6px;
|
|
|
- font-size: 12px;
|
|
|
+.status-divider {
|
|
|
+ margin: 0 8px;
|
|
|
+ color: #DCDFE6;
|
|
|
}
|
|
|
|
|
|
-.entry-name {
|
|
|
- color: #64748b;
|
|
|
+.text-truncate {
|
|
|
+ display: inline-block;
|
|
|
+ max-width: 250px;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
- max-width: 160px;
|
|
|
+ vertical-align: middle;
|
|
|
}
|
|
|
|
|
|
-.entry-val {
|
|
|
- font-weight: 700;
|
|
|
+code {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ padding: 2px 4px;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #e6a23c;
|
|
|
+ font-family: monospace;
|
|
|
}
|
|
|
-.entry-val small { font-weight: 400; font-size: 10px; margin-left: 2px; }
|
|
|
-
|
|
|
-/* 3. 顶部 Banner 样式 (沿用并微调) */
|
|
|
-.status-banner {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 24px 32px;
|
|
|
- background: #ffffff;
|
|
|
- border-radius: 16px;
|
|
|
- margin-bottom: 32px;
|
|
|
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
|
|
|
- border: 1px solid #e2e8f0;
|
|
|
-}
|
|
|
-
|
|
|
-.banner-success { border-left: 6px solid #22c55e; }
|
|
|
-.banner-warning { border-left: 6px solid #f59e0b; }
|
|
|
-
|
|
|
-.banner-stats {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- background: #f1f5f9;
|
|
|
- padding: 12px 24px;
|
|
|
- border-radius: 12px;
|
|
|
- gap: 20px;
|
|
|
-}
|
|
|
-
|
|
|
-.stat-item { text-align: center; }
|
|
|
-.stat-label { font-size: 11px; color: #94a3b8; text-transform: uppercase; }
|
|
|
-.stat-value { font-size: 18px; font-weight: 800; color: #1e293b; }
|
|
|
-.stat-divider { width: 1px; height: 24px; background: #cbd5e1; }
|
|
|
-
|
|
|
-/* 颜色状态与阴影 */
|
|
|
-.warning-shadow {
|
|
|
- border-top: 4px solid #e6a23c !important;
|
|
|
- box-shadow: 0 8px 20px -6px rgba(230, 162, 60, 0.2) !important;
|
|
|
-}
|
|
|
-.danger-shadow {
|
|
|
- border-top: 4px solid #f56c6c !important;
|
|
|
- box-shadow: 0 8px 20px -6px rgba(245, 108, 108, 0.2) !important;
|
|
|
-}
|
|
|
-.primary-shadow {
|
|
|
- border-top: 4px solid #409eff !important;
|
|
|
- box-shadow: 0 8px 20px -6px rgba(64, 158, 255, 0.2) !important;
|
|
|
-}
|
|
|
-
|
|
|
-.warning-text { color: #d97706; }
|
|
|
-.danger-text { color: #dc2626; }
|
|
|
-.primary-text { color: #2563eb; }
|
|
|
-
|
|
|
-/* 其他辅助类 */
|
|
|
-.empty-state {
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- color: #cbd5e1;
|
|
|
-}
|
|
|
-.empty-state i { font-size: 28px; margin-bottom: 8px; }
|
|
|
-
|
|
|
-.scroll-style::-webkit-scrollbar { width: 4px; }
|
|
|
-.scroll-style::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 10px; }
|
|
|
|
|
|
-.refresh-glow:hover {
|
|
|
- box-shadow: 0 0 15px rgba(64, 158, 255, 0.4);
|
|
|
+::v-deep .el-descriptions-item__label {
|
|
|
+ width: 120px;
|
|
|
+ background-color: #fafafa !important;
|
|
|
}
|
|
|
</style>
|