|
|
|
|
@ -0,0 +1,607 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
<!-- 返回导航栏 -->
|
|
|
|
|
<div class="breadcrumb-nav">
|
|
|
|
|
<el-page-header @back="goBack" content="工艺快照">
|
|
|
|
|
<template slot="title">
|
|
|
|
|
<span style="cursor: pointer;" @click="goBack">返回监控</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-page-header>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索表单 -->
|
|
|
|
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" class="search-form">
|
|
|
|
|
<el-form-item label="设备" prop="deviceCode">
|
|
|
|
|
<el-select v-model="queryParams.deviceCode" placeholder="请选择设备" clearable filterable style="width: 200px;">
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="item in deviceList"
|
|
|
|
|
:key="item.deviceCode"
|
|
|
|
|
:label="item.deviceName || item.deviceCode"
|
|
|
|
|
:value="item.deviceCode"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="快照类型" prop="snapshotType">
|
|
|
|
|
<el-select v-model="queryParams.snapshotType" placeholder="请选择类型" clearable style="width: 140px;">
|
|
|
|
|
<el-option label="开始生产" value="START" />
|
|
|
|
|
<el-option label="结束生产" value="END" />
|
|
|
|
|
<el-option label="手动创建" value="MANUAL" />
|
|
|
|
|
<el-option label="参数变化" value="CHANGE" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="时间范围" prop="dateRange">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="dateRange"
|
|
|
|
|
type="datetimerange"
|
|
|
|
|
range-separator="至"
|
|
|
|
|
start-placeholder="开始时间"
|
|
|
|
|
end-placeholder="结束时间"
|
|
|
|
|
value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
|
|
:default-time="['00:00:00', '23:59:59']"
|
|
|
|
|
style="width: 360px;"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
|
|
|
|
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
|
|
|
|
<el-button type="success" icon="el-icon-plus" @click="handleAdd">创建快照</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<!-- 主体内容:左侧快照列表 + 右侧快照详情 -->
|
|
|
|
|
<div class="main-content">
|
|
|
|
|
<!-- 左侧快照列表 -->
|
|
|
|
|
<div class="snapshot-list-panel">
|
|
|
|
|
<div class="panel-header">
|
|
|
|
|
<span class="panel-title">快照列表 ({{ total }})</span>
|
|
|
|
|
<el-button v-if="compareMode" type="text" size="mini" @click="cancelCompare">取消对比</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="snapshot-list" v-loading="loading">
|
|
|
|
|
<div
|
|
|
|
|
v-for="snapshot in snapshotList"
|
|
|
|
|
:key="snapshot.snapshotId"
|
|
|
|
|
:class="['snapshot-item', {
|
|
|
|
|
'active': selectedSnapshot && selectedSnapshot.snapshotId === snapshot.snapshotId,
|
|
|
|
|
'compare-selected': compareSnapshots.includes(snapshot.snapshotId)
|
|
|
|
|
}]"
|
|
|
|
|
@click="selectSnapshot(snapshot)"
|
|
|
|
|
>
|
|
|
|
|
<div class="snapshot-header">
|
|
|
|
|
<el-tag :type="getSnapshotTypeTag(snapshot.snapshotType)" size="mini">
|
|
|
|
|
{{ getSnapshotTypeText(snapshot.snapshotType) }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
<el-checkbox
|
|
|
|
|
v-if="compareMode"
|
|
|
|
|
:value="compareSnapshots.includes(snapshot.snapshotId)"
|
|
|
|
|
@change="toggleCompareSnapshot(snapshot.snapshotId)"
|
|
|
|
|
@click.native.stop
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="snapshot-device">{{ snapshot.deviceName || snapshot.deviceCode }}</div>
|
|
|
|
|
<div class="snapshot-time">{{ parseTime(snapshot.snapshotTime) }}</div>
|
|
|
|
|
<div class="snapshot-actions">
|
|
|
|
|
<el-button type="text" size="mini" @click.stop="handleCompareWith(snapshot)">对比</el-button>
|
|
|
|
|
<el-button type="text" size="mini" style="color: #f56c6c;" @click.stop="handleDelete(snapshot)">删除</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="snapshotList.length === 0" class="no-data">
|
|
|
|
|
<i class="el-icon-camera"></i>
|
|
|
|
|
<span>暂无快照数据</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<pagination
|
|
|
|
|
v-show="total > 0"
|
|
|
|
|
:total="total"
|
|
|
|
|
:page.sync="queryParams.pageNum"
|
|
|
|
|
:limit.sync="queryParams.pageSize"
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
:pager-count="5"
|
|
|
|
|
@pagination="getList"
|
|
|
|
|
style="padding: 8px;"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 右侧快照详情 -->
|
|
|
|
|
<div class="snapshot-detail-panel">
|
|
|
|
|
<template v-if="selectedSnapshot">
|
|
|
|
|
<div class="detail-header">
|
|
|
|
|
<div class="detail-title">
|
|
|
|
|
<span>快照详情</span>
|
|
|
|
|
<el-tag :type="getSnapshotTypeTag(selectedSnapshot.snapshotType)" size="small">
|
|
|
|
|
{{ getSnapshotTypeText(selectedSnapshot.snapshotType) }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="detail-meta">
|
|
|
|
|
<span><b>设备:</b> {{ selectedSnapshot.deviceName || selectedSnapshot.deviceCode }}</span>
|
|
|
|
|
<span><b>时间:</b> {{ parseTime(selectedSnapshot.snapshotTime) }}</span>
|
|
|
|
|
<span v-if="selectedSnapshot.orderCode"><b>工单:</b> {{ selectedSnapshot.orderCode }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-divider content-position="left">参数列表 ({{ snapshotParams.length }})</el-divider>
|
|
|
|
|
<div class="param-list" v-loading="detailLoading">
|
|
|
|
|
<el-table :data="snapshotParams" border stripe size="small" max-height="400">
|
|
|
|
|
<el-table-column label="参数编号" prop="paramCode" width="100" />
|
|
|
|
|
<el-table-column label="参数名称" prop="paramName" min-width="180" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="参数值" prop="paramValue" width="120" align="center">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<span class="param-value">{{ scope.row.paramValue }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="采集时间" prop="collectTime" width="160">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
{{ parseTime(scope.row.collectTime, '{m}-{d} {h}:{i}:{s}') }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<div class="no-selection">
|
|
|
|
|
<i class="el-icon-document"></i>
|
|
|
|
|
<span>请从左侧选择一个快照查看详情</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 创建快照对话框 -->
|
|
|
|
|
<el-dialog title="创建快照" :visible.sync="addDialogVisible" width="500px" append-to-body>
|
|
|
|
|
<el-form ref="addForm" :model="addForm" :rules="addRules" label-width="100px">
|
|
|
|
|
<el-form-item label="设备" prop="deviceCode">
|
|
|
|
|
<el-select v-model="addForm.deviceCode" placeholder="请选择设备" filterable style="width: 100%;">
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="item in deviceList"
|
|
|
|
|
:key="item.deviceCode"
|
|
|
|
|
:label="item.deviceName || item.deviceCode"
|
|
|
|
|
:value="item.deviceCode"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="快照类型" prop="snapshotType">
|
|
|
|
|
<el-select v-model="addForm.snapshotType" placeholder="请选择类型" style="width: 100%;">
|
|
|
|
|
<el-option label="开始生产" value="START" />
|
|
|
|
|
<el-option label="结束生产" value="END" />
|
|
|
|
|
<el-option label="手动创建" value="MANUAL" />
|
|
|
|
|
<el-option label="参数变化" value="CHANGE" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="关联工单" prop="orderCode">
|
|
|
|
|
<el-input v-model="addForm.orderCode" placeholder="请输入关联工单号(可选)" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="备注" prop="remark">
|
|
|
|
|
<el-input v-model="addForm.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<div slot="footer">
|
|
|
|
|
<el-button @click="addDialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="submitAdd" :loading="submitLoading">确定</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 快照对比对话框 -->
|
|
|
|
|
<el-dialog title="快照对比" :visible.sync="compareDialogVisible" width="900px" append-to-body>
|
|
|
|
|
<div v-loading="compareLoading" class="compare-content">
|
|
|
|
|
<div v-if="compareResult && compareResult.length > 0">
|
|
|
|
|
<div class="compare-header">
|
|
|
|
|
<span><b>快照1:</b> {{ compareSnapshot1 ? parseTime(compareSnapshot1.snapshotTime) : '-' }}</span>
|
|
|
|
|
<span style="margin: 0 20px;">→</span>
|
|
|
|
|
<span><b>快照2:</b> {{ compareSnapshot2 ? parseTime(compareSnapshot2.snapshotTime) : '-' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<el-table :data="compareResult" border stripe size="small" max-height="400">
|
|
|
|
|
<el-table-column label="参数编号" prop="paramCode" width="100" />
|
|
|
|
|
<el-table-column label="参数名称" prop="paramName" min-width="180" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="快照1值" prop="beforeValue" width="120" align="center" />
|
|
|
|
|
<el-table-column label="快照2值" prop="afterValue" width="120" align="center" />
|
|
|
|
|
<el-table-column label="变化" prop="isChanged" width="80" align="center">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<el-tag v-if="scope.row.isChanged === 'Y'" type="danger" size="mini">已变化</el-tag>
|
|
|
|
|
<el-tag v-else type="info" size="mini">未变化</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<div class="compare-summary">
|
|
|
|
|
<span>变化参数: <b style="color: #f56c6c;">{{ compareResult.filter(r => r.isChanged === 'Y').length }}</b></span>
|
|
|
|
|
<span>未变化参数: <b>{{ compareResult.filter(r => r.isChanged !== 'Y').length }}</b></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="no-data">
|
|
|
|
|
<i class="el-icon-warning-outline"></i>
|
|
|
|
|
<span>暂无对比数据</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div slot="footer">
|
|
|
|
|
<el-button @click="compareDialogVisible = false">关闭</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import { listProcessSnapshot, getProcessSnapshotDetail, addProcessSnapshot, delProcessSnapshot, compareSnapshots } from "@/api/base/processSnapshot";
|
|
|
|
|
import { getDeviceLedgerList } from "@/api/base/deviceLedger";
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: "ProcessSnapshot",
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
loading: false,
|
|
|
|
|
detailLoading: false,
|
|
|
|
|
submitLoading: false,
|
|
|
|
|
compareLoading: false,
|
|
|
|
|
total: 0,
|
|
|
|
|
snapshotList: [],
|
|
|
|
|
deviceList: [],
|
|
|
|
|
selectedSnapshot: null,
|
|
|
|
|
snapshotParams: [],
|
|
|
|
|
dateRange: [],
|
|
|
|
|
// 创建快照
|
|
|
|
|
addDialogVisible: false,
|
|
|
|
|
addForm: {
|
|
|
|
|
deviceCode: '',
|
|
|
|
|
snapshotType: 'MANUAL',
|
|
|
|
|
orderCode: '',
|
|
|
|
|
remark: ''
|
|
|
|
|
},
|
|
|
|
|
addRules: {
|
|
|
|
|
deviceCode: [{ required: true, message: '请选择设备', trigger: 'change' }],
|
|
|
|
|
snapshotType: [{ required: true, message: '请选择快照类型', trigger: 'change' }]
|
|
|
|
|
},
|
|
|
|
|
// 对比
|
|
|
|
|
compareMode: false,
|
|
|
|
|
compareSnapshots: [],
|
|
|
|
|
compareDialogVisible: false,
|
|
|
|
|
compareSnapshot1: null,
|
|
|
|
|
compareSnapshot2: null,
|
|
|
|
|
compareResult: [],
|
|
|
|
|
queryParams: {
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
deviceCode: '',
|
|
|
|
|
snapshotType: '',
|
|
|
|
|
params: {}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
this.loadDeviceList();
|
|
|
|
|
// 接收从监控页面传来的设备信息
|
|
|
|
|
if (this.$route.query.deviceCode) {
|
|
|
|
|
this.queryParams.deviceCode = this.$route.query.deviceCode;
|
|
|
|
|
}
|
|
|
|
|
this.getList();
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
goBack() {
|
|
|
|
|
this.$router.push('/device/Monitor/val');
|
|
|
|
|
},
|
|
|
|
|
loadDeviceList() {
|
|
|
|
|
getDeviceLedgerList().then(response => {
|
|
|
|
|
this.deviceList = response.data || response.rows || [];
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
getList() {
|
|
|
|
|
this.loading = true;
|
|
|
|
|
const params = { ...this.queryParams };
|
|
|
|
|
if (this.dateRange && this.dateRange.length === 2) {
|
|
|
|
|
params.params = {
|
|
|
|
|
beginTime: this.dateRange[0],
|
|
|
|
|
endTime: this.dateRange[1]
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
listProcessSnapshot(params).then(response => {
|
|
|
|
|
this.snapshotList = response.rows || [];
|
|
|
|
|
this.total = response.total || 0;
|
|
|
|
|
this.loading = false;
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
this.loading = false;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
handleQuery() {
|
|
|
|
|
this.queryParams.pageNum = 1;
|
|
|
|
|
this.getList();
|
|
|
|
|
},
|
|
|
|
|
resetQuery() {
|
|
|
|
|
this.dateRange = [];
|
|
|
|
|
this.queryParams = {
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
deviceCode: '',
|
|
|
|
|
snapshotType: '',
|
|
|
|
|
params: {}
|
|
|
|
|
};
|
|
|
|
|
this.selectedSnapshot = null;
|
|
|
|
|
this.snapshotParams = [];
|
|
|
|
|
this.getList();
|
|
|
|
|
},
|
|
|
|
|
selectSnapshot(snapshot) {
|
|
|
|
|
this.selectedSnapshot = snapshot;
|
|
|
|
|
this.loadSnapshotDetail(snapshot.snapshotId);
|
|
|
|
|
},
|
|
|
|
|
loadSnapshotDetail(snapshotId) {
|
|
|
|
|
this.detailLoading = true;
|
|
|
|
|
getProcessSnapshotDetail(snapshotId).then(response => {
|
|
|
|
|
const data = response.data;
|
|
|
|
|
if (data && data.paramList) {
|
|
|
|
|
this.snapshotParams = data.paramList;
|
|
|
|
|
} else {
|
|
|
|
|
this.snapshotParams = [];
|
|
|
|
|
}
|
|
|
|
|
this.detailLoading = false;
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
this.detailLoading = false;
|
|
|
|
|
this.snapshotParams = [];
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
handleAdd() {
|
|
|
|
|
this.addDialogVisible = true;
|
|
|
|
|
this.addForm = {
|
|
|
|
|
deviceCode: this.queryParams.deviceCode || '',
|
|
|
|
|
snapshotType: 'MANUAL',
|
|
|
|
|
orderCode: '',
|
|
|
|
|
remark: ''
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
submitAdd() {
|
|
|
|
|
this.$refs.addForm.validate(valid => {
|
|
|
|
|
if (!valid) return;
|
|
|
|
|
this.submitLoading = true;
|
|
|
|
|
addProcessSnapshot(this.addForm).then(() => {
|
|
|
|
|
this.$message.success('快照创建成功');
|
|
|
|
|
this.addDialogVisible = false;
|
|
|
|
|
this.submitLoading = false;
|
|
|
|
|
this.getList();
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
this.submitLoading = false;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
handleDelete(snapshot) {
|
|
|
|
|
this.$confirm('确认删除该快照吗?', '提示', {
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'warning'
|
|
|
|
|
}).then(() => {
|
|
|
|
|
delProcessSnapshot(snapshot.snapshotId).then(() => {
|
|
|
|
|
this.$message.success('删除成功');
|
|
|
|
|
if (this.selectedSnapshot && this.selectedSnapshot.snapshotId === snapshot.snapshotId) {
|
|
|
|
|
this.selectedSnapshot = null;
|
|
|
|
|
this.snapshotParams = [];
|
|
|
|
|
}
|
|
|
|
|
this.getList();
|
|
|
|
|
});
|
|
|
|
|
}).catch(() => {});
|
|
|
|
|
},
|
|
|
|
|
handleCompareWith(snapshot) {
|
|
|
|
|
if (!this.selectedSnapshot) {
|
|
|
|
|
this.$message.warning('请先选择一个快照作为对比基准');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this.selectedSnapshot.snapshotId === snapshot.snapshotId) {
|
|
|
|
|
this.$message.warning('不能与自身对比');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this.selectedSnapshot.deviceCode !== snapshot.deviceCode) {
|
|
|
|
|
this.$message.warning('只能对比同一设备的快照');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.compareSnapshot1 = this.selectedSnapshot;
|
|
|
|
|
this.compareSnapshot2 = snapshot;
|
|
|
|
|
this.compareDialogVisible = true;
|
|
|
|
|
this.loadCompareResult();
|
|
|
|
|
},
|
|
|
|
|
loadCompareResult() {
|
|
|
|
|
this.compareLoading = true;
|
|
|
|
|
compareSnapshots(this.compareSnapshot1.snapshotId, this.compareSnapshot2.snapshotId).then(response => {
|
|
|
|
|
this.compareResult = response.data || [];
|
|
|
|
|
this.compareLoading = false;
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
this.compareLoading = false;
|
|
|
|
|
this.compareResult = [];
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
toggleCompareSnapshot(snapshotId) {
|
|
|
|
|
const index = this.compareSnapshots.indexOf(snapshotId);
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
this.compareSnapshots.splice(index, 1);
|
|
|
|
|
} else {
|
|
|
|
|
if (this.compareSnapshots.length >= 2) {
|
|
|
|
|
this.compareSnapshots.shift();
|
|
|
|
|
}
|
|
|
|
|
this.compareSnapshots.push(snapshotId);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
cancelCompare() {
|
|
|
|
|
this.compareMode = false;
|
|
|
|
|
this.compareSnapshots = [];
|
|
|
|
|
},
|
|
|
|
|
getSnapshotTypeText(type) {
|
|
|
|
|
const map = { 'START': '开始生产', 'END': '结束生产', 'MANUAL': '手动创建', 'CHANGE': '参数变化' };
|
|
|
|
|
return map[type] || type;
|
|
|
|
|
},
|
|
|
|
|
getSnapshotTypeTag(type) {
|
|
|
|
|
const map = { 'START': 'success', 'END': 'info', 'MANUAL': 'warning', 'CHANGE': 'danger' };
|
|
|
|
|
return map[type] || '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.breadcrumb-nav {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-form {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
height: calc(100vh - 280px);
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-list-panel {
|
|
|
|
|
width: 320px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
border: 1px solid #e8e8e8;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-list {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-item {
|
|
|
|
|
padding: 12px;
|
|
|
|
|
border: 1px solid #e8e8e8;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-item:hover {
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-item.active {
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
background: #e6f7ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-item.compare-selected {
|
|
|
|
|
border-color: #722ed1;
|
|
|
|
|
background: #f9f0ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-device {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-time {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.snapshot-detail-panel {
|
|
|
|
|
flex: 1;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
border: 1px solid #e8e8e8;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-header {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-title {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-meta {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 24px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-meta b {
|
|
|
|
|
color: #999;
|
|
|
|
|
margin-right: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.param-value {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.no-selection, .no-data {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
height: 100%;
|
|
|
|
|
min-height: 200px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.no-selection i, .no-data i {
|
|
|
|
|
font-size: 48px;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.compare-content {
|
|
|
|
|
min-height: 200px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.compare-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.compare-summary {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 32px;
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|