|
|
|
|
@ -0,0 +1,389 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="collect-log-page" style="padding: 16px 0">
|
|
|
|
|
<el-tabs v-model:active-name="activeTab" lazy>
|
|
|
|
|
<!-- 1) Analyses -->
|
|
|
|
|
<el-tab-pane label="分析记录" name="analysis">
|
|
|
|
|
<el-form :inline="true" class="mb-4" label-width="100px">
|
|
|
|
|
<el-form-item label="时间范围">
|
|
|
|
|
<el-date-picker v-model="query.dateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss" range-separator="~" start-placeholder="开始日期" end-placeholder="结束日期" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="采集地址">
|
|
|
|
|
<el-select v-model="query.addressId" placeholder="全部" clearable style="min-width: 180px">
|
|
|
|
|
<el-option v-for="a in addressList" :key="a.id" :label="a.name" :value="a.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="机床">
|
|
|
|
|
<el-select v-model="query.machineId" placeholder="全部" clearable style="min-width: 140px">
|
|
|
|
|
<el-option v-for="m in machineList" :key="m.id" :label="m.name" :value="m.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="分析类型">
|
|
|
|
|
<el-select v-model="query.analysisType" placeholder="全部" clearable style="min-width: 180px">
|
|
|
|
|
<el-option v-for="(label, key) in analysisTypeOptions" :key="key" :label="label" :value="key" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="程序名">
|
|
|
|
|
<el-input v-model="query.programName" placeholder="请输入程序名" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" @click="loadAnalysis">查询</el-button>
|
|
|
|
|
<el-button @click="resetAnalysis">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<el-table :data="analysisList" border stripe v-loading="analysisLoading" style="width: 100%">
|
|
|
|
|
<el-table-column prop="analysisTime" label="分析时间" sortable width="170" />
|
|
|
|
|
<el-table-column label="采集地址" width="180">
|
|
|
|
|
<template #default="{ row }">{{ row.addressName || row.collectAddressId }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="machineName" label="机床" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="分析类型" align="center" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="analysisTypeTag(row.analysisType)" size="small">{{ analysisTypeLabel(row.analysisType) }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="previousProgram" label="前程序" width="120" />
|
|
|
|
|
<el-table-column prop="currentProgram" label="当前程序" width="120" />
|
|
|
|
|
<el-table-column prop="partCountDelta" label="产量变化" width="110" />
|
|
|
|
|
<el-table-column prop="analysisSummary" label="摘要" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="操作" width="120" fixed="right" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button type="text" @click="viewAnalysis(row)">查看详情</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="page.page"
|
|
|
|
|
v-model:page-size="page.pageSize"
|
|
|
|
|
:page-sizes="[20, 50, 100]"
|
|
|
|
|
:total="page.total"
|
|
|
|
|
background
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<el-dialog v-model="analysisDetailVisible" title="分析详情" width="640px" destroy-on-close>
|
|
|
|
|
<el-descriptions :column="1" border>
|
|
|
|
|
<el-descriptions-item label="分析时间">{{ detailRow?.analysisTime }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="采集地址">{{ detailRow?.addressName || detailRow?.collectAddressId }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="机床">{{ detailRow?.machineName }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="分析类型">{{ analysisTypeLabel(detailRow?.analysisType) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="前程序">{{ detailRow?.previousProgram }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="当前程序">{{ detailRow?.currentProgram }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="产量变化">{{ detailRow?.partCountDelta }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="摘要">{{ detailRow?.analysisSummary }}</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="analysisDetailVisible = false">关闭</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
|
|
<!-- 2) Cycle -->
|
|
|
|
|
<el-tab-pane label="采集周期" name="cycle">
|
|
|
|
|
<el-form :inline="true" class="mb-4" label-width="100px">
|
|
|
|
|
<el-form-item label="时间范围">
|
|
|
|
|
<el-date-picker v-model="cycleQuery.dateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss" range-separator="~" start-placeholder="开始日期" end-placeholder="结束日期" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="采集地址">
|
|
|
|
|
<el-select v-model="cycleQuery.addressId" placeholder="全部" clearable style="min-width: 180px">
|
|
|
|
|
<el-option v-for="a in addressList" :key="a.id" :label="a.name" :value="a.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="是否异常">
|
|
|
|
|
<el-select v-model="cycleQuery.hasAnomaly" placeholder="全部" clearable style="min-width: 120px">
|
|
|
|
|
<el-option label="全部" value="" />
|
|
|
|
|
<el-option label="有异常" value="1" />
|
|
|
|
|
<el-option label="无异常" value="0" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" @click="loadCycles">查询</el-button>
|
|
|
|
|
<el-button @click="resetCycle">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<el-table :data="cycleList" border stripe v-loading="cycleLoading" style="width: 100%">
|
|
|
|
|
<el-table-column prop="cycleTime" label="周期时间" width="170" />
|
|
|
|
|
<el-table-column label="采集地址" width="180">
|
|
|
|
|
<template #default="{ row }">{{ row.addressName || row.collectAddressId }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="totalMachines" label="总机床" width="120" />
|
|
|
|
|
<el-table-column prop="successCount" label="成功" width="80" />
|
|
|
|
|
<el-table-column prop="failCount" label="失败" width="80" />
|
|
|
|
|
<el-table-column prop="hasAnomaly" label="异常" width="80">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.hasAnomaly ? 'danger' : 'success'" size="small">{{ row.hasAnomaly ? '有' : '无' }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="cycleSummary" label="摘要" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="操作" width="120" fixed="right" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button type="text" @click="viewCycle(row)">查看</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="cyclePage.page"
|
|
|
|
|
v-model:page-size="cyclePage.pageSize"
|
|
|
|
|
:page-sizes="[20, 50, 100]"
|
|
|
|
|
:total="cyclePage.total"
|
|
|
|
|
background
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
/>
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
|
|
<!-- 3) Raw -->
|
|
|
|
|
<el-tab-pane label="原始数据" name="raw">
|
|
|
|
|
<el-form :inline="true" class="mb-4" label-width="100px">
|
|
|
|
|
<el-form-item label="采集地址">
|
|
|
|
|
<el-select v-model="rawQuery.addressId" placeholder="全部" clearable style="min-width: 180px">
|
|
|
|
|
<el-option v-for="a in addressList" :key="a.id" :label="a.name" :value="a.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="时间范围">
|
|
|
|
|
<el-date-picker v-model="rawQuery.dateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss" range-separator="~" start-placeholder="开始日期" end-placeholder="结束日期" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" @click="loadRaw">查询</el-button>
|
|
|
|
|
<el-button @click="resetRaw">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<el-table :data="rawList" border stripe v-loading="rawLoading" style="width: 100%">
|
|
|
|
|
<el-table-column prop="id" label="ID" width="80" />
|
|
|
|
|
<el-table-column prop="logTime" label="时间" width="170" />
|
|
|
|
|
<el-table-column prop="sourceAddress" label="地址" />
|
|
|
|
|
<el-table-column prop="contentPreview" label="内容摘要" show-overflow-tooltip />
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="rawPage.page"
|
|
|
|
|
v-model:page-size="rawPage.pageSize"
|
|
|
|
|
:page-sizes="[20, 50, 100]"
|
|
|
|
|
:total="rawPage.total"
|
|
|
|
|
background
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
/>
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
</el-tabs>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, reactive, onMounted, watch } from 'vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import request from '@/utils/request'
|
|
|
|
|
import type { CollectAnalysis, CollectCycle, CollectRaw } from '@/api/collect-log'
|
|
|
|
|
import type { CollectAddress, Machine } from '@/types'
|
|
|
|
|
|
|
|
|
|
// Tab identification
|
|
|
|
|
const activeTab = ref<'analysis' | 'cycle' | 'raw'>('analysis')
|
|
|
|
|
|
|
|
|
|
// 地址与机床列表
|
|
|
|
|
const addressList = ref<CollectAddress[]>([])
|
|
|
|
|
const machineList = ref<Machine[]>([])
|
|
|
|
|
|
|
|
|
|
// --------------- Tab 1: Analyses ---------------
|
|
|
|
|
const analysisList = ref<CollectAnalysis[]>([])
|
|
|
|
|
const analysisLoading = ref(false)
|
|
|
|
|
const detailRow = ref<CollectAnalysis | null>(null)
|
|
|
|
|
const analysisDetailVisible = ref(false)
|
|
|
|
|
const page = reactive({ page: 1, pageSize: 20, total: 0 })
|
|
|
|
|
const query = reactive({ dateRange: null as string[] | null, addressId: undefined as number | undefined, machineId: undefined as number | undefined, analysisType: '', programName: '', keyword: '' })
|
|
|
|
|
|
|
|
|
|
const analysisTypeOptions: Record<string, string> = {
|
|
|
|
|
NORMAL_UNCHANGED: 'NORMAL_UNCHANGED',
|
|
|
|
|
PART_COUNT_INCREASE: 'PART_COUNT_INCREASE',
|
|
|
|
|
PROGRAM_SWITCH: 'PROGRAM_SWITCH',
|
|
|
|
|
MANUAL_RESET: 'MANUAL_RESET',
|
|
|
|
|
DEVICE_ONLINE: 'DEVICE_ONLINE',
|
|
|
|
|
DEVICE_OFFLINE: 'DEVICE_OFFLINE',
|
|
|
|
|
NEW_DEVICE_FOUND: 'NEW_DEVICE_FOUND',
|
|
|
|
|
DATA_ANOMALY: 'DATA_ANOMALY',
|
|
|
|
|
COLLECTION_FAILED: 'COLLECTION_FAILED'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function analysisTypeTag(type: string) {
|
|
|
|
|
const map: Record<string, string> = {
|
|
|
|
|
NORMAL_UNCHANGED: 'info',
|
|
|
|
|
PART_COUNT_INCREASE: 'success',
|
|
|
|
|
PROGRAM_SWITCH: 'warning',
|
|
|
|
|
MANUAL_RESET: 'warning',
|
|
|
|
|
DEVICE_ONLINE: 'success',
|
|
|
|
|
DEVICE_OFFLINE: 'danger',
|
|
|
|
|
NEW_DEVICE_FOUND: 'danger',
|
|
|
|
|
DATA_ANOMALY: 'danger',
|
|
|
|
|
COLLECTION_FAILED: 'danger',
|
|
|
|
|
}
|
|
|
|
|
return map[type] || 'info'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function analysisTypeLabel(type: string | undefined) {
|
|
|
|
|
const map: Record<string, string> = {
|
|
|
|
|
NORMAL_UNCHANGED: '正常未变',
|
|
|
|
|
PART_COUNT_INCREASE: '产量增减',
|
|
|
|
|
PROGRAM_SWITCH: '程序切换',
|
|
|
|
|
MANUAL_RESET: '手动重置',
|
|
|
|
|
DEVICE_ONLINE: '设备在线',
|
|
|
|
|
DEVICE_OFFLINE: '设备离线',
|
|
|
|
|
NEW_DEVICE_FOUND: '新设备发现',
|
|
|
|
|
DATA_ANOMALY: '数据异常',
|
|
|
|
|
COLLECTION_FAILED: '采集失败',
|
|
|
|
|
}
|
|
|
|
|
return type ? (map[type] ?? type) : '未知'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function viewAnalysis(row: CollectAnalysis) {
|
|
|
|
|
detailRow.value = row
|
|
|
|
|
analysisDetailVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadAnalysis() {
|
|
|
|
|
analysisLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await request.get<{ items: CollectAnalysis[]; total: number }>("/admin/collect-log/analysis", {
|
|
|
|
|
params: {
|
|
|
|
|
page: page.page,
|
|
|
|
|
pageSize: page.pageSize,
|
|
|
|
|
dateRange: query.dateRange,
|
|
|
|
|
addressId: query.addressId,
|
|
|
|
|
machineId: query.machineId,
|
|
|
|
|
analysisType: query.analysisType,
|
|
|
|
|
programName: query.programName,
|
|
|
|
|
keyword: query.keyword,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
analysisList.value = res.data?.items ?? []
|
|
|
|
|
page.total = res.data?.total ?? 0
|
|
|
|
|
} finally {
|
|
|
|
|
analysisLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetAnalysis() {
|
|
|
|
|
Object.assign(query, { dateRange: null, addressId: undefined, machineId: undefined, analysisType: '', programName: '', keyword: '' })
|
|
|
|
|
loadAnalysis()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 监听分页变化
|
|
|
|
|
watch(() => [page.page, page.pageSize], () => loadAnalysis())
|
|
|
|
|
|
|
|
|
|
// --------------- Tab 2: Cycles ---------------
|
|
|
|
|
const cycleList = ref<CollectCycle[]>([])
|
|
|
|
|
const cycleLoading = ref(false)
|
|
|
|
|
const cyclePage = reactive({ page: 1, pageSize: 20, total: 0 })
|
|
|
|
|
const cycleQuery = reactive({ dateRange: null as string[] | null, addressId: undefined as number | undefined, hasAnomaly: '' })
|
|
|
|
|
|
|
|
|
|
async function loadCycles() {
|
|
|
|
|
cycleLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await request.get<{ items: CollectCycle[]; total: number }>("/admin/collect-log/cycle", {
|
|
|
|
|
params: {
|
|
|
|
|
page: cyclePage.page,
|
|
|
|
|
pageSize: cyclePage.pageSize,
|
|
|
|
|
dateRange: cycleQuery.dateRange,
|
|
|
|
|
addressId: cycleQuery.addressId,
|
|
|
|
|
hasAnomaly: cycleQuery.hasAnomaly || undefined,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
cycleList.value = res.data?.items ?? []
|
|
|
|
|
cyclePage.total = res.data?.total ?? 0
|
|
|
|
|
} finally {
|
|
|
|
|
cycleLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetCycle() {
|
|
|
|
|
Object.assign(cycleQuery, { dateRange: null, addressId: undefined, hasAnomaly: '' })
|
|
|
|
|
loadCycles()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function viewCycle(row: CollectCycle) {
|
|
|
|
|
// 仅演示用:打开一个简单的提示信息
|
|
|
|
|
ElMessage.info(`周期 ${row.cycleTime} 区间分析完成,共 ${row.totalMachines} 台机床`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(() => cyclePage.page + cyclePage.pageSize, loadCycles)
|
|
|
|
|
// 初始加载周期数据
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadAnalysis()
|
|
|
|
|
loadCycles()
|
|
|
|
|
// 地址与机床数据加载
|
|
|
|
|
loadAddresses()
|
|
|
|
|
loadMachines()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// --------------- Tab 3: Raw ---------------
|
|
|
|
|
const rawList = ref<CollectRaw[]>([])
|
|
|
|
|
const rawLoading = ref(false)
|
|
|
|
|
const rawPage = reactive({ page: 1, pageSize: 20, total: 0 })
|
|
|
|
|
const rawQuery = reactive({ dateRange: null as string[] | null, addressId: undefined as number | undefined })
|
|
|
|
|
const rawURLList = ref<string[]>([])
|
|
|
|
|
|
|
|
|
|
async function loadRaw() {
|
|
|
|
|
rawLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await request.get<{ items: CollectRaw[]; total: number }>("/admin/collect-log/raw", {
|
|
|
|
|
params: {
|
|
|
|
|
page: rawPage.page,
|
|
|
|
|
pageSize: rawPage.pageSize,
|
|
|
|
|
dateRange: rawQuery.dateRange,
|
|
|
|
|
addressId: rawQuery.addressId,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
rawList.value = res.data?.items ?? []
|
|
|
|
|
rawPage.total = res.data?.total ?? 0
|
|
|
|
|
} finally {
|
|
|
|
|
rawLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetRaw() {
|
|
|
|
|
Object.assign(rawQuery, { dateRange: null, addressId: undefined })
|
|
|
|
|
loadRaw()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadRaw()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 仓库:地址/机床数据加载
|
|
|
|
|
async function loadAddresses() {
|
|
|
|
|
try {
|
|
|
|
|
const r = await request.get<{ items: CollectAddress[] }>("/admin/collect-address/list")
|
|
|
|
|
if (r.data?.items && r.data.items.length > 0) {
|
|
|
|
|
addressList.value = r.data.items as CollectAddress[]
|
|
|
|
|
} else {
|
|
|
|
|
// 兜底:提供一个可用的地址列表,确保界面可用
|
|
|
|
|
addressList.value = [
|
|
|
|
|
{ id: 1, name: 'FANUC-A栋', url: '', brandId: 0, brandName: '', interval: 60, isEnabled: true, lastCollectTime: '', machineCount: 8, failCount: 0 },
|
|
|
|
|
{ id: 2, name: 'FANUC-B栋', url: '', brandId: 0, brandName: '', interval: 60, isEnabled: true, lastCollectTime: '', machineCount: 6, failCount: 0 },
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
addressList.value = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async function loadMachines() {
|
|
|
|
|
try {
|
|
|
|
|
const r = await request.get<{ items: Machine[] }>('/admin/machine/list')
|
|
|
|
|
machineList.value = (r.data?.items as Machine[]) ?? []
|
|
|
|
|
} catch {
|
|
|
|
|
machineList.value = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------- Helpers --------------
|
|
|
|
|
// 为模板暴露 tag/label 映射
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.collect-log-page {
|
|
|
|
|
.mb-4 {
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|