|
|
<!--
|
|
|
TODO: 字典和统计接口待完善
|
|
|
|
|
|
需要引入的字典:
|
|
|
1. active_flag - 激活标识字典
|
|
|
2. maint_type - 保养类型字典
|
|
|
3. alarm_level - 报警级别字典
|
|
|
4. handle_status - 处理状态字典
|
|
|
5. bills_status - 工单状态字典
|
|
|
6. device_status - 设备状态字典
|
|
|
|
|
|
需要创建的统计接口:
|
|
|
1. getDmsDeviceStatistics() - 设备统计接口
|
|
|
返回:{ totalCount, runningCount, maintenanceCount, alarmCount, faultCount }
|
|
|
2. getDmsDeviceMaintenanceStats(machineId) - 设备维护统计接口
|
|
|
返回:{ plannedCount, temporaryCount, avgInterval, nextMaintenanceDate }
|
|
|
-->
|
|
|
<template>
|
|
|
<div class="dms-machine-ledger">
|
|
|
<!-- 工厂背景装饰 -->
|
|
|
<div class="factory-bg-decoration">
|
|
|
<svg viewBox="0 0 1200 120" preserveAspectRatio="none">
|
|
|
<path d="M0,0 L0,60 L50,60 L50,30 L100,30 L100,50 L150,50 L150,20 L200,20 L200,60 L1200,60 L1200,0 Z"
|
|
|
fill="url(#factory-gradient)" opacity="0.1"/>
|
|
|
<defs>
|
|
|
<linearGradient id="factory-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
|
<stop offset="0%" style="stop-color:#409EFF;stop-opacity:1" />
|
|
|
<stop offset="100%" style="stop-color:#667eea;stop-opacity:1" />
|
|
|
</linearGradient>
|
|
|
</defs>
|
|
|
</svg>
|
|
|
</div>
|
|
|
|
|
|
<!-- 顶部信息面板 -->
|
|
|
<div class="top-info-panel">
|
|
|
<div class="panel-left">
|
|
|
<h1 class="page-title">
|
|
|
<el-icon><Monitor /></el-icon>
|
|
|
设备台账管理
|
|
|
</h1>
|
|
|
<p class="page-subtitle">设备全生命周期管理与履历追踪</p>
|
|
|
</div>
|
|
|
<div class="panel-right">
|
|
|
<div class="current-time">
|
|
|
<el-icon><Clock /></el-icon>
|
|
|
{{ currentTime }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 功能操作区 -->
|
|
|
<div class="action-toolbar">
|
|
|
<div class="toolbar-left">
|
|
|
<el-button-group>
|
|
|
<el-button :type="viewMode === 'grid' ? 'primary' : 'default'"
|
|
|
@click="viewMode = 'grid'">
|
|
|
<el-icon><Grid /></el-icon>
|
|
|
网格视图
|
|
|
</el-button>
|
|
|
<el-button :type="viewMode === 'list' ? 'primary' : 'default'"
|
|
|
@click="viewMode = 'list'">
|
|
|
<el-icon><List /></el-icon>
|
|
|
列表视图
|
|
|
</el-button>
|
|
|
</el-button-group>
|
|
|
</div>
|
|
|
<div class="toolbar-right">
|
|
|
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['dms:dmsBaseMachineInfo:add']">
|
|
|
新增设备
|
|
|
</el-button>
|
|
|
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate()"
|
|
|
v-hasPermi="['dms:dmsBaseMachineInfo:edit']">
|
|
|
修改设备
|
|
|
</el-button>
|
|
|
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()"
|
|
|
v-hasPermi="['dms:dmsBaseMachineInfo:remove']">
|
|
|
删除设备
|
|
|
</el-button>
|
|
|
<el-button type="warning" icon="Download" @click="handleExport"
|
|
|
v-hasPermi="['dms:dmsBaseMachineInfo:export']">
|
|
|
导出数据
|
|
|
</el-button>
|
|
|
<el-button text @click="showSearch = !showSearch">
|
|
|
<el-icon><Filter /></el-icon>
|
|
|
{{ showSearch ? '隐藏' : '显示' }}筛选
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 查询过滤器 -->
|
|
|
<el-card class="search-filter-card" v-show="showSearch">
|
|
|
<template #header>
|
|
|
<div class="filter-header">
|
|
|
<div class="filter-title">
|
|
|
<el-icon><Filter /></el-icon>
|
|
|
筛选条件
|
|
|
</div>
|
|
|
<el-button text @click="showSearch = false">
|
|
|
<el-icon><ArrowUp /></el-icon>
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
|
|
|
<el-form-item label="设备编号" prop="machineCode">
|
|
|
<el-input v-model="queryParams.machineCode" placeholder="请输入设备编号" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="设备名称" prop="machineName">
|
|
|
<el-input v-model="queryParams.machineName" placeholder="请输入设备名称" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="设备位置" prop="machineLocation">
|
|
|
<el-input v-model="queryParams.machineLocation" placeholder="请输入设备位置" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="设备状态" prop="machineStatus">
|
|
|
<el-select v-model="queryParams.machineStatus" placeholder="请选择设备状态" clearable>
|
|
|
<el-option v-for="dict in machine_status" :key="dict.value"
|
|
|
:label="dict.label" :value="dict.value" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="设备类型" prop="machineType">
|
|
|
<el-select v-model="queryParams.machineType" placeholder="请选择设备类型" clearable>
|
|
|
<el-option v-for="type in deviceTypes" :key="type.deviceTypeId"
|
|
|
:label="type.deviceTypeName" :value="type.deviceTypeId" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
|
<el-button type="primary" icon="Search" @click="handleQuery" class="filter-btn-primary">搜索</el-button>
|
|
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 主内容区 -->
|
|
|
<div class="main-content">
|
|
|
<!-- 网格视图 -->
|
|
|
<div v-if="viewMode === 'grid'" class="grid-view" v-loading="loading">
|
|
|
<transition-group name="fade-transform" tag="div" class="machine-grid">
|
|
|
<div v-for="machine in machineList" :key="machine.machineId"
|
|
|
class="machine-card-wrapper" @click="selectMachine(machine)">
|
|
|
<div :class="['machine-card', { selected: selectedMachine?.machineId === machine.machineId }]">
|
|
|
<!-- 状态指示灯 -->
|
|
|
<div :class="['status-indicator', `status-${machine.machineStatus}`]">
|
|
|
<span class="status-dot"></span>
|
|
|
</div>
|
|
|
|
|
|
<!-- 设备图标区 -->
|
|
|
<div class="machine-visual">
|
|
|
<div class="machine-icon-bg" v-if="!machine.photoAddress">
|
|
|
<el-icon :size="40">
|
|
|
<component :is="getMachineIcon(machine.machineType)" />
|
|
|
</el-icon>
|
|
|
</div>
|
|
|
<div class="machine-photo" v-else>
|
|
|
<ImagePreview
|
|
|
:width="40"
|
|
|
:height="40"
|
|
|
:src="machine.photoAddress"
|
|
|
:preview-src-list="[machine.photoAddress]"
|
|
|
/>
|
|
|
</div>
|
|
|
<div class="machine-number">{{ machine.machineCode }}</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 设备信息 -->
|
|
|
<div class="machine-details">
|
|
|
<h3 class="machine-name">{{ machine.machineName }}</h3>
|
|
|
<div class="machine-meta">
|
|
|
<div class="meta-item">
|
|
|
<el-icon><Location /></el-icon>
|
|
|
<span>{{ machine.machineLocation || '-' }}</span>
|
|
|
</div>
|
|
|
<div class="meta-item">
|
|
|
<el-icon><Memo /></el-icon>
|
|
|
<span>{{ getDeviceTypeName(machine.machineType) }}</span>
|
|
|
</div>
|
|
|
<div class="meta-item">
|
|
|
<el-icon><Timer /></el-icon>
|
|
|
<span>运行 {{ calculateRunDays(machine.createTime) }} 天</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 快捷操作 -->
|
|
|
<div class="quick-actions">
|
|
|
<el-button size="small" @click.stop="handleUpdate(machine)" v-hasPermi="['dms:dmsBaseMachineInfo:edit']">编辑</el-button>
|
|
|
<el-button size="small" @click.stop="viewLifecycle(machine)">生命周期</el-button>
|
|
|
<el-button size="small" @click.stop="viewDetails(machine)">详情</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</transition-group>
|
|
|
|
|
|
<!-- 网格视图分页 -->
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
|
|
|
v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
</div>
|
|
|
|
|
|
<!-- 列表视图 -->
|
|
|
<div v-else-if="viewMode === 'list'" class="list-view">
|
|
|
<el-table v-loading="loading" :data="machineList" :row-class-name="tableRowClassName"
|
|
|
@selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
|
<el-table-column label="设备编号" align="center" prop="machineCode" />
|
|
|
<el-table-column label="设备名称" align="center" prop="machineName" />
|
|
|
<el-table-column label="设备类型" align="center" prop="machineType">
|
|
|
<template #default="scope">
|
|
|
{{ getDeviceTypeName(scope.row.machineType) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="设备位置" align="center" prop="machineLocation" />
|
|
|
<el-table-column label="设备状态" align="center" prop="machineStatus">
|
|
|
<template #default="scope">
|
|
|
<div class="status-cell">
|
|
|
<span :class="['status-badge', `status-${scope.row.machineStatus}`]"></span>
|
|
|
<dict-tag :options="machine_status" :value="String(scope.row.machineStatus)" />
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="设备图片" align="center" prop="photoAddress" width="100">
|
|
|
<template #default="scope">
|
|
|
<ImagePreview
|
|
|
v-if="scope.row.photoAddress && checkFileSuffix(scope.row.photoAddress)"
|
|
|
:width="50"
|
|
|
:height="50"
|
|
|
:src="scope.row.photoAddress"
|
|
|
:preview-src-list="[scope.row.photoAddress]"
|
|
|
/>
|
|
|
<span v-else>无图片</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
|
|
<template #default="scope">
|
|
|
<span class="date-text">{{ formatDate(scope.row.createTime) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" align="center" width="240">
|
|
|
<template #default="scope">
|
|
|
<el-button type="primary" size="small" @click="handleUpdate(scope.row)" v-hasPermi="['dms:dmsBaseMachineInfo:edit']">编辑</el-button>
|
|
|
<el-button size="small" @click="viewLifecycle(scope.row)">生命周期</el-button>
|
|
|
<el-button size="small" @click="viewDetails(scope.row)">详情</el-button>
|
|
|
<el-button type="danger" size="small" @click="handleDelete(scope.row)" v-hasPermi="['dms:dmsBaseMachineInfo:remove']">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
|
|
|
v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 添加或修改设备信息对话框 -->
|
|
|
<el-dialog :title="dialog.title" v-model="dialog.visible" width="900px" append-to-body>
|
|
|
<el-form ref="machineFormRef" :model="form" :rules="rules" label-width="120px">
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备编号" prop="machineCode">
|
|
|
<el-input v-model="form.machineCode" placeholder="请输入设备编号" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备名称" prop="machineName">
|
|
|
<el-input v-model="form.machineName" placeholder="请输入设备名称" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<!-- <el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="资产编号" prop="assetNumber">
|
|
|
<el-input v-model="form.assetNumber" placeholder="请输入资产编号" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备位置" prop="machineLocation">
|
|
|
<el-input v-model="form.machineLocation" placeholder="请输入设备位置" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row> -->
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备类型" prop="machineType">
|
|
|
<el-select v-model="form.machineType" placeholder="请选择设备类型" style="width: 100%;">
|
|
|
<el-option
|
|
|
v-for="item in deviceTypes"
|
|
|
:key="item.deviceTypeId"
|
|
|
:label="item.deviceTypeName"
|
|
|
:value="item.deviceTypeId"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备规格" prop="machineSpec">
|
|
|
<el-input v-model="form.machineSpec" placeholder="请输入设备规格" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="供应商" prop="supplierId">
|
|
|
<el-input v-model="form.supplierId" placeholder="请输入供应商" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备状态" prop="machineStatus">
|
|
|
<el-select v-model="form.machineStatus" placeholder="请选择设备状态" style="width: 100%;">
|
|
|
<el-option
|
|
|
v-for="dict in machine_status"
|
|
|
:key="dict.value"
|
|
|
:label="dict.label"
|
|
|
:value="dict.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<!-- <el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="所属车间" prop="workshopId">
|
|
|
<el-select v-model="form.workshopId" placeholder="请选择所属车间" style="width: 100%;">
|
|
|
<el-option
|
|
|
v-for="item in workshopInfoList"
|
|
|
:key="item.workshopId"
|
|
|
:label="item.workshopName"
|
|
|
:value="item.workshopId"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备模型" prop="deviceModeId">
|
|
|
<el-select v-model="form.deviceModeId" placeholder="请选择设备模型" style="width: 100%;">
|
|
|
<el-option
|
|
|
v-for="item in deviceModeList"
|
|
|
:key="item.deviceModeId"
|
|
|
:label="item.deviceModeName"
|
|
|
:value="item.deviceModeId"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row> -->
|
|
|
<!-- <el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备IP地址" prop="machineIp">
|
|
|
<el-input v-model="form.machineIp" placeholder="请输入设备IP地址" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备端口" prop="machinePort">
|
|
|
<el-input v-model="form.machinePort" placeholder="请输入设备端口" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>-->
|
|
|
<!-- <el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="设备协议" prop="accessProtocol">
|
|
|
<el-input v-model="form.accessProtocol" placeholder="请输入设备协议" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="寄存器地址" prop="registerAddress">
|
|
|
<el-input v-model="form.registerAddress" placeholder="请输入寄存器地址" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>-->
|
|
|
<!-- <el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="数据类型" prop="dataType">
|
|
|
<el-select v-model="form.dataType" placeholder="请选择数据类型" style="width: 100%;">
|
|
|
<el-option
|
|
|
v-for="dict in machine_data_type"
|
|
|
:key="dict.value"
|
|
|
:label="dict.label"
|
|
|
:value="dict.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="数据长度" prop="dataLength">
|
|
|
<el-input-number v-model="form.dataLength" placeholder="请输入数据长度" style="width: 100%;" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>-->
|
|
|
<!-- <el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="数据编码格式" prop="dataEncoding">
|
|
|
<el-select v-model="form.dataEncoding" placeholder="请选择数据编码格式" style="width: 100%;">
|
|
|
<el-option
|
|
|
v-for="dict in machine_data_encoding"
|
|
|
:key="dict.value"
|
|
|
:label="dict.label"
|
|
|
:value="dict.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="请求间隔(毫秒)" prop="requestInterval">
|
|
|
<el-input-number v-model="form.requestInterval" placeholder="请输入请求间隔" style="width: 100%;" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>-->
|
|
|
<!-- <el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="班制类型" prop="classType">
|
|
|
<el-radio-group v-model="form.classType">
|
|
|
<el-radio
|
|
|
v-for="dict in mes_class_type"
|
|
|
:key="dict.value"
|
|
|
:value="dict.value"
|
|
|
>{{ dict.label }}</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="入库类型" prop="instockType">
|
|
|
<el-radio-group v-model="form.instockType">
|
|
|
<el-radio
|
|
|
v-for="dict in mes_instock_type"
|
|
|
:key="dict.value"
|
|
|
:value="dict.value"
|
|
|
>{{ dict.label }}</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row> -->
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="24">
|
|
|
<el-form-item label="设备图片" prop="photoAddress">
|
|
|
<imageUpload v-model="form.file" :limit="1" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="24">
|
|
|
<el-form-item label="备注" prop="remark">
|
|
|
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
|
|
<el-button @click="cancel">取 消</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 生命周期抽屉 -->
|
|
|
<el-drawer v-model="lifecycleDrawer" :title="`设备生命周期 - ${selectedMachine?.machineName || ''}`"
|
|
|
size="60%" class="lifecycle-drawer">
|
|
|
<div class="lifecycle-container" v-loading="lifecycleLoading">
|
|
|
<!-- 设备概览卡片 -->
|
|
|
<div class="device-overview-card" v-if="selectedMachine">
|
|
|
<div class="overview-header">
|
|
|
<div class="device-avatar">
|
|
|
<el-icon :size="40">
|
|
|
<component :is="getMachineIcon(selectedMachine.machineType)" />
|
|
|
</el-icon>
|
|
|
</div>
|
|
|
<div class="device-basic">
|
|
|
<h2>{{ selectedMachine.machineName }}</h2>
|
|
|
<p>编号:{{ selectedMachine.machineCode }} | 位置:{{ selectedMachine.machineLocation || '-' }}</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-divider />
|
|
|
<div class="overview-stats">
|
|
|
<div class="stat">
|
|
|
<span class="stat-label">运行天数</span>
|
|
|
<span class="stat-value">{{ calculateRunDays(selectedMachine.createTime) }}</span>
|
|
|
</div>
|
|
|
<div class="stat">
|
|
|
<span class="stat-label">故障次数</span>
|
|
|
<span class="stat-value">{{ lifecycleStats.faultCount }}</span>
|
|
|
</div>
|
|
|
<div class="stat">
|
|
|
<span class="stat-label">保养次数</span>
|
|
|
<span class="stat-value">{{ lifecycleStats.maintCount }}</span>
|
|
|
</div>
|
|
|
<div class="stat">
|
|
|
<span class="stat-label">报警次数</span>
|
|
|
<span class="stat-value">{{ lifecycleStats.alarmCount }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 生命周期类型选择 -->
|
|
|
<div class="lifecycle-type-selector">
|
|
|
<h3>选择查看的生命周期类型</h3>
|
|
|
<div class="type-cards">
|
|
|
<div v-for="type in lifecycleTypes" :key="type.value"
|
|
|
:class="['type-card', { active: selectedLifecycleTypes.includes(type.value) }]"
|
|
|
@click="toggleLifecycleType(type.value)">
|
|
|
<div class="type-icon" :style="{ background: type.gradient }">
|
|
|
<el-icon :size="24">
|
|
|
<component :is="type.icon" />
|
|
|
</el-icon>
|
|
|
</div>
|
|
|
<span class="type-name">{{ type.label }}</span>
|
|
|
<span v-if="type.count > 0" class="type-count">{{ type.count }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 生命周期时间轴 -->
|
|
|
<div class="lifecycle-timeline-container" v-if="lifecycleEvents.length > 0">
|
|
|
<h3>设备履历时间轴</h3>
|
|
|
<div class="timeline-wrapper">
|
|
|
<div v-for="event in lifecycleEvents" :key="event.id" class="timeline-item">
|
|
|
<div class="timeline-marker" :style="{ backgroundColor: event.color }">
|
|
|
<el-icon :size="20">
|
|
|
<component :is="event.icon" />
|
|
|
</el-icon>
|
|
|
</div>
|
|
|
<div class="timeline-content">
|
|
|
<div class="event-header">
|
|
|
<h4>{{ event.title }}</h4>
|
|
|
<span class="event-time">{{ formatDateTime(event.time) }}</span>
|
|
|
</div>
|
|
|
<p class="event-description">{{ event.description }}</p>
|
|
|
<div class="event-tags">
|
|
|
<el-tag v-for="tag in event.tags" :key="tag" size="small">{{ tag }}</el-tag>
|
|
|
</div>
|
|
|
<div class="event-details" v-if="event.details && event.details.length">
|
|
|
<el-collapse>
|
|
|
<el-collapse-item title="查看详情">
|
|
|
<el-descriptions :column="2" border>
|
|
|
<el-descriptions-item v-for="item in event.details"
|
|
|
:key="item.label" :label="item.label">
|
|
|
<template v-if="item.dictOptions">
|
|
|
<dict-tag :options="item.dictOptions" :value="String(item.value)" />
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
{{ item.value ?? '-' }}
|
|
|
</template>
|
|
|
</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
</el-collapse-item>
|
|
|
</el-collapse>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="timeline-line"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
<el-empty v-else-if="selectedLifecycleTypes.length > 0"
|
|
|
description="暂无相关生命周期数据" />
|
|
|
</div>
|
|
|
</el-drawer>
|
|
|
|
|
|
<!-- 设备详情对话框 -->
|
|
|
<el-dialog v-model="detailDialog" :title="`设备详情 - ${selectedMachine?.machineName || ''}`"
|
|
|
width="80%" top="5vh">
|
|
|
<el-tabs v-model="activeTab">
|
|
|
<el-tab-pane label="基本信息" name="basic">
|
|
|
<div class="detail-section">
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-card>
|
|
|
<template #header>
|
|
|
<span class="card-header-title">设备基础信息</span>
|
|
|
</template>
|
|
|
<el-descriptions :column="1" border>
|
|
|
<el-descriptions-item label="设备编号">{{ selectedMachine?.machineCode }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="设备名称">{{ selectedMachine?.machineName }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="设备类型">{{ getDeviceTypeName(selectedMachine?.machineType) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="设备位置">{{ selectedMachine?.machineLocation || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="设备状态">
|
|
|
<dict-tag :options="machine_status" :value="String(selectedMachine?.machineStatus ?? '')" />
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="创建时间">{{ formatDateTime(selectedMachine?.createTime) }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-card>
|
|
|
<template #header>
|
|
|
<span class="card-header-title">技术参数</span>
|
|
|
</template>
|
|
|
<el-table :data="technicalParams" max-height="300">
|
|
|
<el-table-column prop="paramName" label="参数名称" />
|
|
|
<el-table-column prop="paramValue" label="参数值" />
|
|
|
<el-table-column prop="unit" label="单位" />
|
|
|
<el-table-column prop="remark" label="备注" />
|
|
|
</el-table>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</div>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane label="维护信息" name="maintenance">
|
|
|
<div class="detail-section maintenance-section">
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="24">
|
|
|
<el-card class="maintenance-summary">
|
|
|
<template #header>
|
|
|
<span class="card-header-title">维护统计</span>
|
|
|
</template>
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="6">
|
|
|
<el-statistic title="计划保养次数" :value="maintenanceStats.planned" />
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-statistic title="临时保养次数" :value="maintenanceStats.temporary" />
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-statistic title="平均间隔(天)" :value="maintenanceStats.avgInterval" />
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-statistic title="下次保养日期" :value="maintenanceStats.nextDate || '-'" />
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :span="24">
|
|
|
<el-card>
|
|
|
<template #header>
|
|
|
<span class="card-header-title">维护记录</span>
|
|
|
</template>
|
|
|
<el-table :data="maintenanceRecords" max-height="400">
|
|
|
<el-table-column prop="maintCode" label="保养单号" />
|
|
|
<el-table-column prop="maintType" label="保养类型">
|
|
|
<template #default="scope">
|
|
|
<dict-tag :options="maint_type" :value="String(scope.row.maintType)" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="maintDate" label="保养日期" />
|
|
|
<el-table-column prop="maintPerson" label="保养人员" />
|
|
|
<el-table-column prop="maintContent" label="保养内容" show-overflow-tooltip />
|
|
|
<el-table-column prop="maintStatus" label="保养结果">
|
|
|
<template #default="scope">
|
|
|
<dict-tag :options="maint_status" :value="String(scope.row.maintStatus)" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</div>
|
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="ProdBaseMachineInfo" lang="ts">
|
|
|
import { ref, reactive, toRefs, getCurrentInstance, onMounted, onUnmounted, shallowRef, markRaw, watch } from 'vue';
|
|
|
import type { ComponentInternalInstance } from 'vue';
|
|
|
import {
|
|
|
listDmsBaseMachineInfo,
|
|
|
getDmsBaseMachineInfo,
|
|
|
addProdBaseMachineInfo,
|
|
|
updateProdBaseMachineInfo,
|
|
|
delDmsBaseMachineInfo
|
|
|
} from '@/api/dms/dmsBaseMachineInfo';
|
|
|
import { getBaseDeviceTypeList } from '@/api/dms/baseDeviceType';
|
|
|
import { getDmsBaseDevicePurchaseList, getPurchaseCount } from '@/api/dms/dmsBaseDevicePurchase';
|
|
|
import { getDmsBaseDeviceInstallList, getInstallCount } from '@/api/dms/dmsBaseDeviceInstall';
|
|
|
import { getDmsBaseDeviceDebuggingList, getDebuggingCount } from '@/api/dms/dmsBaseDeviceDebugging';
|
|
|
import { getDmsBillsFaultInstanceList, getFaultInstanceCount } from '@/api/dms/dmsBillsFaultInstance';
|
|
|
import { getDmsBillsMaintDetailList, getMaintInstancesByMachineId, countMaintInstancesByMachineId } from '@/api/dms/dmsBillsMaintDetail';
|
|
|
import { getDmsInspectInstanceDetailList, getInspectInstancesByMachineId, countInspectInstancesByMachineId } from '@/api/dms/dmsInspectInstanceDetail';
|
|
|
import { getBaseAlarmInfoList, getAlarmInfoCount } from '@/api/dms/baseAlarmInfo';
|
|
|
import { getDmsBaseSpecialdeviceParamList } from '@/api/dms/dmsBaseSpecialdeviceParam';
|
|
|
import { getWorkshopList } from '@/api/mes/baseWorkshopInfo';
|
|
|
import { getDmsDeviceModeList } from '@/api/dms/deviceMode';
|
|
|
import { ProdBaseMachineInfoVO, ProdBaseMachineInfoForm } from '@/api/dms/dmsBaseMachineInfo/types';
|
|
|
import imageUpload from '@/components/ImageUpload/index.vue';
|
|
|
import ImagePreview from '@/components/ImagePreview/index.vue';
|
|
|
import request from '@/utils/request';
|
|
|
|
|
|
// 图标导入
|
|
|
import {
|
|
|
Monitor, Clock, Grid, List, Connection, Filter, ArrowUp, ArrowDown,
|
|
|
Location, Memo, Timer, InfoFilled, Calendar, DataAnalysis, TrendCharts,
|
|
|
Download, Search, Refresh, Document, Files, Box, Setting,
|
|
|
Tools, Notification, TurnOff, Warning, Money, Switch, View,
|
|
|
ShoppingCart, CircleCheck, Position, MagicStick, Plus, Edit, Delete
|
|
|
} from '@element-plus/icons-vue';
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
// 设备台账页面实际使用到的核心字典集合
|
|
|
const {
|
|
|
machine_status,
|
|
|
debug_status,
|
|
|
fault_status,
|
|
|
maint_level,
|
|
|
maint_status,
|
|
|
handle_status,
|
|
|
bills_status,
|
|
|
active_flag,
|
|
|
machine_data_type,
|
|
|
machine_data_encoding,
|
|
|
mes_class_type,
|
|
|
mes_instock_type
|
|
|
} = toRefs<any>(proxy?.useDict(
|
|
|
'machine_status',
|
|
|
'debug_status',
|
|
|
'fault_status',
|
|
|
'maint_level',
|
|
|
'maint_status',
|
|
|
'handle_status',
|
|
|
'bills_status',
|
|
|
'active_flag',
|
|
|
'machine_data_type',
|
|
|
'machine_data_encoding',
|
|
|
'mes_class_type',
|
|
|
'mes_instock_type'
|
|
|
));
|
|
|
// 本页还需要的其它字典(用于详情/统计处标签显示)
|
|
|
const { maint_type, alarm_level } = toRefs<any>(proxy?.useDict('maint_type', 'alarm_level'));
|
|
|
|
|
|
// 响应式数据
|
|
|
const loading = ref(false);
|
|
|
const buttonLoading = ref(false);
|
|
|
const showSearch = ref(true);
|
|
|
const machineList = ref([]);
|
|
|
const total = ref(0);
|
|
|
const viewMode = ref('grid');
|
|
|
const selectedMachine = ref(null);
|
|
|
const lifecycleDrawer = ref(false);
|
|
|
const detailDialog = ref(false);
|
|
|
const activeTab = ref('basic');
|
|
|
const lifecycleLoading = ref(false);
|
|
|
const selectedLifecycleTypes = ref([]);
|
|
|
const lifecycleEvents = ref([]);
|
|
|
// 防抖/竞态控制:仅应用最近一次生命周期数据请求的结果
|
|
|
const lifecycleRequestId = ref(0);
|
|
|
const currentTime = ref('');
|
|
|
const deviceTypes = ref([]);
|
|
|
const workshopInfoList = ref([]);
|
|
|
const deviceModeList = ref([]);
|
|
|
|
|
|
// 表单相关
|
|
|
const queryFormRef = ref<ElFormInstance>();
|
|
|
const machineFormRef = ref<ElFormInstance>();
|
|
|
const ids = ref<Array<string | number>>([]);
|
|
|
const single = ref(true);
|
|
|
const multiple = ref(true);
|
|
|
|
|
|
const dialog = reactive<DialogOption>({
|
|
|
visible: false,
|
|
|
title: ''
|
|
|
});
|
|
|
|
|
|
// 查询参数
|
|
|
const queryParams = ref({
|
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
|
machineCode: undefined,
|
|
|
machineName: undefined,
|
|
|
machineLocation: undefined,
|
|
|
machineStatus: undefined,
|
|
|
machineType: undefined
|
|
|
});
|
|
|
|
|
|
// 表单数据
|
|
|
const initFormData: ProdBaseMachineInfoForm = {
|
|
|
machineId: undefined,
|
|
|
machineCode: undefined,
|
|
|
machineName: undefined,
|
|
|
assetNumber: undefined,
|
|
|
machineLocation: undefined,
|
|
|
machineType: undefined,
|
|
|
machineSpec: undefined,
|
|
|
supplierId: undefined,
|
|
|
machineStatus: '1',
|
|
|
remark: undefined,
|
|
|
photoAddress: undefined,
|
|
|
ossId: undefined,
|
|
|
// workshopId: undefined,
|
|
|
// deviceModeId: undefined,
|
|
|
// machineIp: undefined,
|
|
|
// machinePort: undefined,
|
|
|
// accessProtocol: undefined,
|
|
|
// registerAddress: undefined,
|
|
|
// dataType: undefined,
|
|
|
// dataLength: undefined,
|
|
|
// dataEncoding: undefined,
|
|
|
// requestInterval: undefined,
|
|
|
// classType: undefined,
|
|
|
// instockType: undefined,
|
|
|
file: undefined
|
|
|
};
|
|
|
|
|
|
const form = ref<ProdBaseMachineInfoForm>({ ...initFormData });
|
|
|
|
|
|
// 表单验证规则
|
|
|
const rules = reactive({
|
|
|
machineCode: [
|
|
|
{ required: true, message: '设备编号不能为空', trigger: 'blur' }
|
|
|
],
|
|
|
machineName: [
|
|
|
{ required: true, message: '设备名称不能为空', trigger: 'blur' }
|
|
|
],
|
|
|
machineStatus: [
|
|
|
{ required: true, message: '设备状态不能为空', trigger: 'change' }
|
|
|
]
|
|
|
});
|
|
|
|
|
|
// 生命周期类型配置
|
|
|
const lifecycleTypes = ref([
|
|
|
{
|
|
|
label: '采购信息',
|
|
|
value: 'purchase',
|
|
|
icon: shallowRef(ShoppingCart),
|
|
|
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
count: 0
|
|
|
},
|
|
|
{
|
|
|
label: '安装调试',
|
|
|
value: 'install',
|
|
|
icon: shallowRef(Setting),
|
|
|
gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
|
|
|
count: 0
|
|
|
},
|
|
|
{
|
|
|
label: '维保记录',
|
|
|
value: 'maintenance',
|
|
|
icon: shallowRef(Tools),
|
|
|
gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
|
|
|
count: 0
|
|
|
},
|
|
|
{
|
|
|
label: '故障维修',
|
|
|
value: 'fault',
|
|
|
icon: shallowRef(Warning),
|
|
|
gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
|
|
|
count: 0
|
|
|
},
|
|
|
{
|
|
|
label: '巡检记录',
|
|
|
value: 'inspection',
|
|
|
icon: shallowRef(View),
|
|
|
gradient: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)',
|
|
|
count: 0
|
|
|
},
|
|
|
{
|
|
|
label: '报警信息',
|
|
|
value: 'alarm',
|
|
|
icon: shallowRef(Notification),
|
|
|
gradient: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)',
|
|
|
count: 0
|
|
|
}
|
|
|
]);
|
|
|
|
|
|
// 生命周期统计
|
|
|
const lifecycleStats = ref({
|
|
|
faultCount: 0,
|
|
|
maintCount: 0,
|
|
|
alarmCount: 0
|
|
|
});
|
|
|
|
|
|
// 技术参数
|
|
|
const technicalParams = ref([]);
|
|
|
|
|
|
// 维护统计
|
|
|
const maintenanceStats = ref({
|
|
|
planned: 0,
|
|
|
temporary: 0,
|
|
|
avgInterval: 0,
|
|
|
nextDate: null
|
|
|
});
|
|
|
|
|
|
// 维护记录
|
|
|
const maintenanceRecords = ref([]);
|
|
|
|
|
|
// 检查文件后缀是否为图片
|
|
|
const checkFileSuffix = (fileSuffix: string | string[]) => {
|
|
|
if (!fileSuffix) return false;
|
|
|
const arr = ['.png', '.jpg', '.jpeg', '.gif'];
|
|
|
const suffix = typeof fileSuffix === 'string' ? fileSuffix.toLowerCase() : '';
|
|
|
return arr.some(item => suffix.endsWith(item));
|
|
|
};
|
|
|
|
|
|
// 更新时间
|
|
|
const updateCurrentTime = () => {
|
|
|
const now = new Date();
|
|
|
currentTime.value = now.toLocaleString('zh-CN', {
|
|
|
year: 'numeric',
|
|
|
month: '2-digit',
|
|
|
day: '2-digit',
|
|
|
hour: '2-digit',
|
|
|
minute: '2-digit',
|
|
|
second: '2-digit',
|
|
|
hour12: false
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 获取设备列表
|
|
|
const getList = async () => {
|
|
|
loading.value = true;
|
|
|
try {
|
|
|
const res = await listDmsBaseMachineInfo(queryParams.value);
|
|
|
machineList.value = res.rows || [];
|
|
|
total.value = res.total || 0;
|
|
|
} catch (error) {
|
|
|
console.error('获取设备列表失败:', error);
|
|
|
proxy?.$modal.msgError('获取设备列表失败');
|
|
|
machineList.value = [];
|
|
|
total.value = 0;
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 获取设备类型列表
|
|
|
const getDeviceTypes = async () => {
|
|
|
try {
|
|
|
const res = await getBaseDeviceTypeList(null);
|
|
|
deviceTypes.value = res.data;
|
|
|
} catch (error) {
|
|
|
console.error('获取设备类型失败:', error);
|
|
|
proxy?.$modal.msgError('获取设备类型失败');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 获取车间列表
|
|
|
// const getWorkshopListSelect = async () => {
|
|
|
// try {
|
|
|
// const res = await getWorkshopList(null);
|
|
|
// workshopInfoList.value = res.data;
|
|
|
// } catch (error) {
|
|
|
// console.error('获取车间列表失败:', error);
|
|
|
// proxy?.$modal.msgError('获取车间列表失败');
|
|
|
// }
|
|
|
// };
|
|
|
|
|
|
// 获取设备模型列表
|
|
|
const getDmsDeviceModeListSelect = async () => {
|
|
|
try {
|
|
|
const res = await getDmsDeviceModeList(null);
|
|
|
deviceModeList.value = res.data;
|
|
|
} catch (error) {
|
|
|
console.error('获取设备模型列表失败:', error);
|
|
|
proxy?.$modal.msgError('获取设备模型列表失败');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 获取设备图标
|
|
|
const getMachineIcon = (type) => {
|
|
|
const iconMap = {
|
|
|
'1': markRaw(Monitor),
|
|
|
'2': markRaw(Setting),
|
|
|
'3': markRaw(Tools),
|
|
|
'4': markRaw(Tools)
|
|
|
};
|
|
|
return iconMap[type] || markRaw(Monitor);
|
|
|
};
|
|
|
|
|
|
// 获取设备类型名称
|
|
|
const getDeviceTypeName = (typeId) => {
|
|
|
const type = deviceTypes.value.find(t => t.deviceTypeId === typeId);
|
|
|
return type?.deviceTypeName || '-';
|
|
|
};
|
|
|
|
|
|
// 获取保养级别标签
|
|
|
const getMaintLevelLabel = (level) => {
|
|
|
const dict = maint_level?.value?.find(d => d.value === level);
|
|
|
return dict?.label || level;
|
|
|
};
|
|
|
|
|
|
// 获取故障状态标签
|
|
|
const getFaultStatusLabel = (status) => {
|
|
|
const dict = bills_status?.value?.find(d => d.value === status);
|
|
|
return dict?.label || status;
|
|
|
};
|
|
|
|
|
|
// 表格行样式
|
|
|
const tableRowClassName = ({ row }) => {
|
|
|
if (row.machineStatus === '0') return 'danger-row';
|
|
|
if (row.machineStatus === '2') return 'warning-row';
|
|
|
return '';
|
|
|
};
|
|
|
|
|
|
// 格式化日期
|
|
|
const formatDate = (date) => {
|
|
|
if (!date) return null;
|
|
|
return proxy.parseTime(date, '{y}-{m}-{d}');
|
|
|
};
|
|
|
|
|
|
// 格式化日期时间
|
|
|
const formatDateTime = (date) => {
|
|
|
if (!date) return null;
|
|
|
return proxy.parseTime(date, '{y}-{m}-{d} {h}:{i}');
|
|
|
};
|
|
|
|
|
|
// 计算运行天数
|
|
|
const calculateRunDays = (startDate) => {
|
|
|
if (!startDate) return 0;
|
|
|
const start = new Date(startDate);
|
|
|
const now = new Date();
|
|
|
const diff = now.getTime() - start.getTime();
|
|
|
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
|
};
|
|
|
|
|
|
// 搜索
|
|
|
const handleQuery = () => {
|
|
|
queryParams.value.pageNum = 1;
|
|
|
getList();
|
|
|
};
|
|
|
|
|
|
// 重置
|
|
|
const resetQuery = () => {
|
|
|
(proxy as any)?.resetForm?.("queryFormRef");
|
|
|
handleQuery();
|
|
|
};
|
|
|
|
|
|
// 选择设备
|
|
|
const selectMachine = (machine) => {
|
|
|
selectedMachine.value = machine;
|
|
|
};
|
|
|
|
|
|
// 多选框选中数据
|
|
|
const handleSelectionChange = (selection: ProdBaseMachineInfoVO[]) => {
|
|
|
ids.value = selection.map(item => item.machineId);
|
|
|
single.value = selection.length != 1;
|
|
|
multiple.value = !selection.length;
|
|
|
};
|
|
|
|
|
|
// 新增按钮操作
|
|
|
const handleAdd = () => {
|
|
|
reset();
|
|
|
dialog.visible = true;
|
|
|
dialog.title = '添加设备信息';
|
|
|
};
|
|
|
|
|
|
// 修改按钮操作
|
|
|
const handleUpdate = async (row?: ProdBaseMachineInfoVO) => {
|
|
|
reset();
|
|
|
const _machineId = row?.machineId || ids.value[0];
|
|
|
try {
|
|
|
const res = await getDmsBaseMachineInfo(_machineId);
|
|
|
Object.assign(form.value, res.data);
|
|
|
|
|
|
// 处理图片数据 - 只取第一张图片
|
|
|
if (res.data.ossId && res.data.photoAddress) {
|
|
|
const ossIdArray = res.data.ossId.split(',');
|
|
|
const urlArray = res.data.photoAddress.split(',');
|
|
|
if (ossIdArray.length > 0 && urlArray.length > 0) {
|
|
|
form.value.file = [{
|
|
|
ossId: ossIdArray[0].trim(),
|
|
|
url: urlArray[0].trim()
|
|
|
}];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
dialog.visible = true;
|
|
|
dialog.title = '修改设备信息';
|
|
|
} catch (error) {
|
|
|
console.error('获取设备信息失败:', error);
|
|
|
proxy?.$modal.msgError('获取设备信息失败');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 提交按钮
|
|
|
const submitForm = () => {
|
|
|
machineFormRef.value?.validate(async (valid: boolean) => {
|
|
|
if (valid) {
|
|
|
buttonLoading.value = true;
|
|
|
try {
|
|
|
// 处理图片上传 - 只处理第一张图片
|
|
|
if (form.value.file && form.value.file.length > 0) {
|
|
|
form.value.ossId = form.value.file[0].ossId;
|
|
|
form.value.photoAddress = form.value.file[0].url;
|
|
|
} else {
|
|
|
form.value.ossId = undefined;
|
|
|
form.value.photoAddress = undefined;
|
|
|
}
|
|
|
|
|
|
if (form.value.machineId) {
|
|
|
await updateProdBaseMachineInfo(form.value);
|
|
|
} else {
|
|
|
await addProdBaseMachineInfo(form.value);
|
|
|
}
|
|
|
proxy?.$modal.msgSuccess('操作成功');
|
|
|
dialog.visible = false;
|
|
|
await getList();
|
|
|
} catch (error) {
|
|
|
console.error('保存设备信息失败:', error);
|
|
|
proxy?.$modal.msgError('保存设备信息失败');
|
|
|
} finally {
|
|
|
buttonLoading.value = false;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 删除按钮操作
|
|
|
const handleDelete = async (row?: ProdBaseMachineInfoVO) => {
|
|
|
const _machineIds = row?.machineId || ids.value;
|
|
|
try {
|
|
|
await proxy?.$modal.confirm('是否确认删除设备信息编号为"' + _machineIds + '"的数据项?');
|
|
|
await delDmsBaseMachineInfo(_machineIds);
|
|
|
proxy?.$modal.msgSuccess('删除成功');
|
|
|
await getList();
|
|
|
} catch (error) {
|
|
|
console.error('删除设备信息失败:', error);
|
|
|
if (error !== 'cancel') {
|
|
|
proxy?.$modal.msgError('删除设备信息失败');
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 导出按钮操作
|
|
|
const handleExport = () => {
|
|
|
proxy?.download('dms/dmsBaseMachineInfo/export', {
|
|
|
...queryParams.value
|
|
|
}, `machineInfo_${new Date().getTime()}.xlsx`);
|
|
|
};
|
|
|
|
|
|
// 取消按钮
|
|
|
const cancel = () => {
|
|
|
reset();
|
|
|
dialog.visible = false;
|
|
|
};
|
|
|
|
|
|
// 表单重置
|
|
|
const reset = () => {
|
|
|
form.value = { ...initFormData };
|
|
|
machineFormRef.value?.resetFields();
|
|
|
};
|
|
|
|
|
|
// 切换生命周期类型
|
|
|
const toggleLifecycleType = (type) => {
|
|
|
const index = selectedLifecycleTypes.value.indexOf(type);
|
|
|
if (index > -1) {
|
|
|
selectedLifecycleTypes.value.splice(index, 1);
|
|
|
} else {
|
|
|
selectedLifecycleTypes.value.push(type);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 查看生命周期
|
|
|
const viewLifecycle = async (machine) => {
|
|
|
selectedMachine.value = machine;
|
|
|
lifecycleDrawer.value = true;
|
|
|
selectedLifecycleTypes.value = [];
|
|
|
lifecycleEvents.value = [];
|
|
|
|
|
|
// 预加载统计数据
|
|
|
await loadLifecycleTypes();
|
|
|
};
|
|
|
|
|
|
// 加载生命周期数据
|
|
|
const loadLifecycleData = async () => {
|
|
|
if (!selectedMachine.value || selectedLifecycleTypes.value.length === 0) {
|
|
|
lifecycleEvents.value = [];
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 标记本次请求序列,后续仅应用最后一次请求的结果
|
|
|
const reqId = ++lifecycleRequestId.value;
|
|
|
lifecycleLoading.value = true;
|
|
|
const events = [];
|
|
|
const machineId = selectedMachine.value.machineId;
|
|
|
|
|
|
// 兼容不同接口返回结构(data 或 rows)
|
|
|
const getListData = (res) => (res && (res.data ?? res.rows)) || [];
|
|
|
|
|
|
try {
|
|
|
const promises = selectedLifecycleTypes.value.map(async (type) => {
|
|
|
switch (type) {
|
|
|
case 'purchase':
|
|
|
try {
|
|
|
const purchaseRes = await getDmsBaseDevicePurchaseList({ machineId:machineId });
|
|
|
const purchases = getListData(purchaseRes);
|
|
|
if (purchases.length > 0) {
|
|
|
purchases.forEach(purchase => {
|
|
|
events.push({
|
|
|
id: `purchase-${purchase.devicePurchaseId}`,
|
|
|
type: 'purchase',
|
|
|
title: '设备采购',
|
|
|
description: `采购人: ${purchase.purchasePerson || '-'}`,
|
|
|
time: purchase.purchaseTime,
|
|
|
icon: markRaw(ShoppingCart),
|
|
|
color: '#667eea',
|
|
|
tags: ['采购'],
|
|
|
details: [
|
|
|
{ label: '计划编号', value: purchase.workOrder },
|
|
|
{ label: '采购地点', value: purchase.purchasePosition },
|
|
|
{ label: '采购价格', value: purchase.purchasePrice || '-' },
|
|
|
{ label: '备注', value: purchase.remark || '-' }
|
|
|
]
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('获取采购信息失败:', error);
|
|
|
proxy?.$modal.msgError('获取采购信息失败');
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 'install':
|
|
|
try {
|
|
|
const installRes = await getDmsBaseDeviceInstallList({ machineId:machineId });
|
|
|
const installs = getListData(installRes);
|
|
|
if (installs.length > 0) {
|
|
|
installs.forEach(install => {
|
|
|
events.push({
|
|
|
id: `install-${install.deviceInstallId}`,
|
|
|
type: 'install',
|
|
|
title: '设备安装',
|
|
|
description: `安装人员: ${install.installPerson || '-'}`,
|
|
|
time: install.installTime,
|
|
|
icon: markRaw(Setting),
|
|
|
color: '#f093fb',
|
|
|
tags: ['安装'],
|
|
|
details: [
|
|
|
{ label: '安装地点', value: install.installPosition },
|
|
|
{ label: '状态', value: install.activeFlag, dictOptions: active_flag?.value }
|
|
|
]
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const debugRes = await getDmsBaseDeviceDebuggingList({ machineId:machineId });
|
|
|
const debugs = getListData(debugRes);
|
|
|
if (debugs.length > 0) {
|
|
|
debugs.forEach(debug => {
|
|
|
events.push({
|
|
|
id: `debug-${debug.deviceDebuggingId}`,
|
|
|
type: 'debugging',
|
|
|
title: '设备调试',
|
|
|
description: `调试人员: ${debug.debugPerson || '-'}`,
|
|
|
time: debug.debugTime,
|
|
|
icon: markRaw(Tools),
|
|
|
color: '#f5576c',
|
|
|
tags: ['调试'],
|
|
|
details: [
|
|
|
{ label: '调试单号', value: debug.workOrder },
|
|
|
{ label: '调试状态', value: debug.status, dictOptions: debug_status?.value },
|
|
|
{ label: '备注', value: debug.remark || '-' }
|
|
|
]
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('获取安装调试信息失败:', error);
|
|
|
proxy?.$modal.msgError('获取安装调试信息失败');
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 'maintenance':
|
|
|
try {
|
|
|
// 使用新的后端接口,直接获取维保工单列表
|
|
|
const res = await getMaintInstancesByMachineId(machineId);
|
|
|
const maintInstances = getListData(res);
|
|
|
|
|
|
maintInstances.forEach(maint => {
|
|
|
events.push({
|
|
|
id: `maint-${maint.maintInstanceId}`,
|
|
|
type: 'maintenance',
|
|
|
title: '保养记录',
|
|
|
description: `保养单号: ${maint.billsMaintCode}`,
|
|
|
time: maint.planBeginTime || maint.createTime,
|
|
|
icon: markRaw(Tools),
|
|
|
color: '#4facfe',
|
|
|
tags: ['保养', getMaintLevelLabel(maint.maintLevel)],
|
|
|
details: [
|
|
|
{ label: '保养单号', value: maint.billsMaintCode },
|
|
|
{ label: '保养级别', value: maint.maintLevel, dictOptions: maint_level?.value },
|
|
|
{ label: '保养状态', value: maint.maintStatus, dictOptions: maint_status?.value },
|
|
|
{ label: '计划开始时间', value: formatDateTime(maint.planBeginTime) },
|
|
|
// { label: '计划结束时间', value: formatDateTime(maint.planEndTime) }
|
|
|
]
|
|
|
});
|
|
|
});
|
|
|
} catch (error) {
|
|
|
console.error('获取保养记录失败:', error);
|
|
|
proxy?.$modal.msgError('获取保养记录失败');
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 'fault':
|
|
|
try {
|
|
|
const res = await getDmsBillsFaultInstanceList({ machineId:machineId });
|
|
|
const faults = getListData(res);
|
|
|
if (faults.length > 0) {
|
|
|
faults.forEach(fault => {
|
|
|
events.push({
|
|
|
id: `fault-${fault.repairInstanceId}`,
|
|
|
type: 'fault',
|
|
|
title: '故障报修',
|
|
|
description: `故障单号: ${fault.billsFaultCode}`,
|
|
|
time: fault.applyTime,
|
|
|
icon: markRaw(Warning),
|
|
|
color: '#fa709a',
|
|
|
tags: ['故障', getFaultStatusLabel(fault.billsStatus)],
|
|
|
details: [
|
|
|
{ label: '申请人', value: fault.applyUser },
|
|
|
{ label: '工单状态', value: fault.billsStatus, dictOptions: bills_status?.value },
|
|
|
{ label: '故障描述', value: fault.faultDescription || '-' },
|
|
|
{ label: '备注', value: fault.remark || '-' }
|
|
|
]
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('获取故障记录失败:', error);
|
|
|
proxy?.$modal.msgError('获取故障记录失败');
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 'inspection':
|
|
|
try {
|
|
|
// 使用新的后端接口,直接获取巡检工单列表
|
|
|
const res = await getInspectInstancesByMachineId(machineId);
|
|
|
const inspectInstances = getListData(res);
|
|
|
|
|
|
inspectInstances.forEach(inspect => {
|
|
|
events.push({
|
|
|
id: `inspect-${inspect.inspectInstanceId}`,
|
|
|
type: 'inspection',
|
|
|
title: '巡检记录',
|
|
|
description: `巡检单号: ${inspect.billsInspectCode}`,
|
|
|
time: inspect.planBeginTime || inspect.createTime,
|
|
|
icon: markRaw(View),
|
|
|
color: '#30cfd0',
|
|
|
tags: ['巡检'],
|
|
|
details: [
|
|
|
{ label: '巡检单号', value: inspect.billsInspectCode },
|
|
|
{ label: '巡检类型', value: inspect.inspectType === '1' ? '巡检' : '点检' },
|
|
|
{ label: '巡检状态', value: inspect.inspectStatus || '-' },
|
|
|
{ label: '计划开始时间', value: formatDateTime(inspect.planBeginTime) },
|
|
|
// { label: '计划结束时间', value: formatDateTime(inspect.planEndTime) }
|
|
|
]
|
|
|
});
|
|
|
});
|
|
|
} catch (error) {
|
|
|
console.error('获取巡检记录失败:', error);
|
|
|
proxy?.$modal.msgError('获取巡检记录失败');
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 'alarm':
|
|
|
try {
|
|
|
const res = await getBaseAlarmInfoList({ machineId:machineId });
|
|
|
const alarms = getListData(res);
|
|
|
if (alarms.length > 0) {
|
|
|
alarms.slice(0, 10).forEach(alarm => {
|
|
|
events.push({
|
|
|
id: `alarm-${alarm.alarmId}`,
|
|
|
type: 'alarm',
|
|
|
title: '设备报警',
|
|
|
description: `报警类型: ${alarm.alarmTypeName || '-'}`,
|
|
|
time: alarm.alarmTime,
|
|
|
icon: markRaw(Notification),
|
|
|
color: '#a8edea',
|
|
|
tags: ['报警', alarm.alarmLevelName],
|
|
|
details: [
|
|
|
{ label: '报警级别', value: alarm.alarmLevel ?? alarm.alarmLevelName, dictOptions: alarm_level?.value },
|
|
|
{ label: '报警内容', value: alarm.alarmContent },
|
|
|
{ label: '处理状态', value: alarm.handleStatus, dictOptions: handle_status?.value }
|
|
|
]
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('获取报警信息失败:', error);
|
|
|
proxy?.$modal.msgError('获取报警信息失败');
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
await Promise.all(promises);
|
|
|
|
|
|
// 仅当本次请求仍是最新请求时,应用结果
|
|
|
if (reqId === lifecycleRequestId.value) {
|
|
|
lifecycleEvents.value = events.sort((a, b) => {
|
|
|
const timeA = new Date(a.time).getTime();
|
|
|
const timeB = new Date(b.time).getTime();
|
|
|
return timeB - timeA;
|
|
|
});
|
|
|
lifecycleLoading.value = false;
|
|
|
}
|
|
|
} catch (e) {
|
|
|
if (reqId === lifecycleRequestId.value) {
|
|
|
lifecycleLoading.value = false;
|
|
|
}
|
|
|
proxy?.$modal.msgError('加载生命周期数据失败');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 查看详情
|
|
|
const viewDetails = async (machine) => {
|
|
|
selectedMachine.value = machine;
|
|
|
detailDialog.value = true;
|
|
|
activeTab.value = 'basic';
|
|
|
|
|
|
// 加载详情数据
|
|
|
await Promise.all([
|
|
|
loadTechnicalParams(machine.machineId),
|
|
|
loadMaintenanceStats(machine.machineId)
|
|
|
]);
|
|
|
};
|
|
|
|
|
|
// 加载技术参数
|
|
|
const loadTechnicalParams = async (machineId) => {
|
|
|
try {
|
|
|
const paramRes = await getDmsBaseSpecialdeviceParamList({ machineId });
|
|
|
|
|
|
technicalParams.value = paramRes.data?.map(param => ({
|
|
|
paramName: param.paramName,
|
|
|
paramValue: param.paramValue,
|
|
|
unit: param.paramUnit || '-',
|
|
|
remark: param.remark || '-'
|
|
|
})) || [];
|
|
|
} catch (error) {
|
|
|
console.error('获取技术参数失败:', error);
|
|
|
technicalParams.value = [];
|
|
|
proxy?.$modal.msgError('获取技术参数失败');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 加载维护统计
|
|
|
const loadMaintenanceStats = async (machineId) => {
|
|
|
try {
|
|
|
const statsRes = await getDmsDeviceMaintenanceStats({ machineId });
|
|
|
const statsData = statsRes.data || {};
|
|
|
|
|
|
maintenanceStats.value = {
|
|
|
planned: statsData.plannedCount || 0,
|
|
|
temporary: statsData.temporaryCount || 0,
|
|
|
avgInterval: statsData.avgInterval || 0,
|
|
|
nextDate: statsData.nextMaintenanceDate ? formatDate(statsData.nextMaintenanceDate) : null
|
|
|
};
|
|
|
|
|
|
// 修复:通过维保明细表查询维护记录
|
|
|
const maintDetailRes = await getDmsBillsMaintDetailList({ machineId, pageNum: 1, pageSize: 10 });
|
|
|
const maintDetails = (maintDetailRes.data || maintDetailRes.rows || []);
|
|
|
|
|
|
// 从维保明细中提取维护记录,按工单去重处理
|
|
|
const maintInstanceMap = new Map();
|
|
|
maintDetails.forEach(detail => {
|
|
|
const maintInstanceId = detail.maintInstanceId;
|
|
|
if (!maintInstanceMap.has(maintInstanceId)) {
|
|
|
maintInstanceMap.set(maintInstanceId, {
|
|
|
maintCode: detail.billsMaintCode || `MAINT-${detail.maintInstanceId}`,
|
|
|
maintType: detail.maintLevel || 1, // 保养级别对应保养类型
|
|
|
maintDate: formatDate(detail.beginTime || detail.createTime),
|
|
|
maintPerson: detail.maintSupervisor || '-',
|
|
|
maintContent: detail.operationDescription || '-',
|
|
|
maintStatus: detail.maintStatus || 1
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
|
|
|
maintenanceRecords.value = Array.from(maintInstanceMap.values());
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('获取维护统计失败:', error);
|
|
|
maintenanceStats.value = { planned: 0, temporary: 0, avgInterval: 0, nextDate: null };
|
|
|
maintenanceRecords.value = [];
|
|
|
proxy?.$modal.msgError('获取维护统计失败');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 加载生命周期类型统计
|
|
|
const loadLifecycleTypes = async () => {
|
|
|
if (!selectedMachine.value) return;
|
|
|
|
|
|
const machineId = selectedMachine.value.machineId;
|
|
|
|
|
|
try {
|
|
|
const [
|
|
|
purchaseCount,
|
|
|
installCount,
|
|
|
debuggingCount,
|
|
|
maintCount, // 使用新的后端接口
|
|
|
faultCount,
|
|
|
inspectCount, // 使用新的后端接口
|
|
|
alarmCount
|
|
|
] = await Promise.all([
|
|
|
getPurchaseCount({ machineId }).catch(() => ({ data: 0 })),
|
|
|
getInstallCount({ machineId }).catch(() => ({ data: 0 })),
|
|
|
getDebuggingCount({ machineId }).catch(() => ({ data: 0 })),
|
|
|
countMaintInstancesByMachineId(machineId).catch(() => ({ data: 0 })),
|
|
|
getFaultInstanceCount({ machineId }).catch(() => ({ data: 0 })),
|
|
|
countInspectInstancesByMachineId(machineId).catch(() => ({ data: 0 })),
|
|
|
getAlarmInfoCount({ machineId }).catch(() => ({ data: 0 }))
|
|
|
]);
|
|
|
|
|
|
// 更新类型计数
|
|
|
lifecycleTypes.value.forEach(type => {
|
|
|
switch (type.value) {
|
|
|
case 'purchase':
|
|
|
type.count = purchaseCount.data || 0;
|
|
|
break;
|
|
|
case 'install':
|
|
|
type.count = (installCount.data || 0) + (debuggingCount.data || 0);
|
|
|
break;
|
|
|
case 'maintenance':
|
|
|
type.count = maintCount.data || 0;
|
|
|
break;
|
|
|
case 'fault':
|
|
|
type.count = faultCount.data || 0;
|
|
|
break;
|
|
|
case 'inspection':
|
|
|
type.count = inspectCount.data || 0;
|
|
|
break;
|
|
|
case 'alarm':
|
|
|
type.count = alarmCount.data || 0;
|
|
|
break;
|
|
|
default:
|
|
|
type.count = 0;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('加载生命周期类型统计失败:', error);
|
|
|
proxy?.$modal.msgError('加载生命周期类型统计失败');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 定时更新
|
|
|
let timer = null;
|
|
|
onMounted(() => {
|
|
|
getList();
|
|
|
getDeviceTypes();
|
|
|
// getWorkshopListSelect();
|
|
|
getDmsDeviceModeListSelect();
|
|
|
updateCurrentTime();
|
|
|
timer = setInterval(updateCurrentTime, 1000);
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
if (timer) {
|
|
|
clearInterval(timer);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 监听生命周期类型变化,自动加载数据
|
|
|
watch(selectedLifecycleTypes, (newTypes) => {
|
|
|
if (newTypes.length > 0 && selectedMachine.value) {
|
|
|
loadLifecycleData();
|
|
|
} else {
|
|
|
// 作废在途请求,防止已发起的请求在清空选择后回填数据
|
|
|
lifecycleRequestId.value++;
|
|
|
lifecycleEvents.value = [];
|
|
|
lifecycleLoading.value = false;
|
|
|
}
|
|
|
}, { deep: true });
|
|
|
|
|
|
// 监听选中设备变化,重置生命周期选择和数据
|
|
|
watch(selectedMachine, (newMachine) => {
|
|
|
if (newMachine) {
|
|
|
selectedLifecycleTypes.value = [];
|
|
|
lifecycleEvents.value = [];
|
|
|
loadLifecycleTypes();
|
|
|
}
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.dms-machine-ledger {
|
|
|
min-height: 100vh;
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
padding: 6px;
|
|
|
}
|
|
|
|
|
|
.factory-bg-decoration {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 60px;
|
|
|
z-index: 0;
|
|
|
|
|
|
svg {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.top-info-panel {
|
|
|
position: relative;
|
|
|
z-index: 1;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 8px 16px;
|
|
|
background: rgba(255, 255, 255, 0.95);
|
|
|
border-radius: 6px;
|
|
|
margin-bottom: 8px;
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
.panel-left {
|
|
|
.page-title {
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
color: #2c3e50;
|
|
|
margin: 0;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
}
|
|
|
|
|
|
.page-subtitle {
|
|
|
font-size: 11px;
|
|
|
color: #7f8c8d;
|
|
|
margin: 1px 0 0 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.panel-right {
|
|
|
.current-time {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 3px;
|
|
|
font-size: 12px;
|
|
|
color: #34495e;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.action-toolbar {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
.toolbar-left, .toolbar-right {
|
|
|
display: flex;
|
|
|
gap: 6px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.search-filter-card {
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
.filter-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
|
|
|
.filter-title {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 4px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
:deep(.el-card__body) {
|
|
|
padding: 12px;
|
|
|
}
|
|
|
|
|
|
.el-form {
|
|
|
.el-form-item {
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.main-content {
|
|
|
.grid-view {
|
|
|
.machine-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(5, 1fr); // 修改为一行5个
|
|
|
gap: 8px;
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.machine-card-wrapper {
|
|
|
cursor: pointer;
|
|
|
|
|
|
.machine-card {
|
|
|
background: #fff;
|
|
|
border-radius: 4px;
|
|
|
padding: 8px;
|
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
|
|
transition: all 0.3s ease;
|
|
|
position: relative;
|
|
|
|
|
|
&:hover {
|
|
|
transform: translateY(-1px);
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
}
|
|
|
|
|
|
&.selected {
|
|
|
border: 2px solid #409EFF;
|
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
|
|
|
}
|
|
|
|
|
|
.status-indicator {
|
|
|
position: absolute;
|
|
|
top: 6px;
|
|
|
right: 6px;
|
|
|
|
|
|
.status-dot {
|
|
|
display: inline-block;
|
|
|
width: 5px;
|
|
|
height: 5px;
|
|
|
border-radius: 50%;
|
|
|
animation: pulse 2s infinite;
|
|
|
}
|
|
|
|
|
|
&.status-0 .status-dot { background: #f56c6c; }
|
|
|
&.status-1 .status-dot { background: #67c23a; }
|
|
|
&.status-2 .status-dot { background: #e6a23c; }
|
|
|
&.status-3 .status-dot { background: #909399; }
|
|
|
}
|
|
|
|
|
|
.machine-visual {
|
|
|
text-align: center;
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
.machine-icon-bg {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
width: 40px;
|
|
|
height: 40px;
|
|
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
|
|
border-radius: 6px;
|
|
|
color: #fff;
|
|
|
margin-bottom: 4px;
|
|
|
}
|
|
|
|
|
|
.machine-photo {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
width: 40px;
|
|
|
height: 40px;
|
|
|
margin-bottom: 4px;
|
|
|
border-radius: 6px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.machine-number {
|
|
|
font-size: 11px;
|
|
|
font-weight: 600;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.machine-details {
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
.machine-name {
|
|
|
font-size: 13px;
|
|
|
font-weight: 600;
|
|
|
color: #2c3e50;
|
|
|
margin: 0 0 4px 0;
|
|
|
text-align: center;
|
|
|
line-height: 1.2;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.machine-meta {
|
|
|
.meta-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 2px;
|
|
|
font-size: 10px;
|
|
|
color: #7f8c8d;
|
|
|
margin-bottom: 2px;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
|
|
|
&:last-child {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.quick-actions {
|
|
|
display: flex;
|
|
|
gap: 4px;
|
|
|
justify-content: center;
|
|
|
|
|
|
.el-button {
|
|
|
padding: 2px 6px;
|
|
|
font-size: 10px;
|
|
|
min-height: 20px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.list-view {
|
|
|
.status-cell {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 4px;
|
|
|
|
|
|
.status-badge {
|
|
|
display: inline-block;
|
|
|
width: 5px;
|
|
|
height: 5px;
|
|
|
border-radius: 50%;
|
|
|
|
|
|
&.status-0 { background: #f56c6c; }
|
|
|
&.status-1 { background: #67c23a; }
|
|
|
&.status-2 { background: #e6a23c; }
|
|
|
&.status-3 { background: #909399; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.date-text {
|
|
|
font-size: 12px;
|
|
|
color: #606266;
|
|
|
}
|
|
|
|
|
|
:deep(.danger-row) {
|
|
|
background-color: rgba(245, 108, 108, 0.05);
|
|
|
}
|
|
|
|
|
|
:deep(.warning-row) {
|
|
|
background-color: rgba(230, 162, 60, 0.05);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.lifecycle-drawer {
|
|
|
:deep(.el-drawer__body) {
|
|
|
padding: 12px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.lifecycle-container {
|
|
|
.device-overview-card {
|
|
|
background: #fff;
|
|
|
border-radius: 6px;
|
|
|
padding: 12px;
|
|
|
margin-bottom: 12px;
|
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
.overview-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
|
|
|
.device-avatar {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
width: 50px;
|
|
|
height: 50px;
|
|
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
|
|
border-radius: 10px;
|
|
|
color: #fff;
|
|
|
}
|
|
|
|
|
|
.device-basic {
|
|
|
h2 {
|
|
|
margin: 0 0 3px 0;
|
|
|
font-size: 16px;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
|
|
|
p {
|
|
|
margin: 0;
|
|
|
font-size: 12px;
|
|
|
color: #7f8c8d;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.overview-stats {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
|
|
|
gap: 10px;
|
|
|
|
|
|
.stat {
|
|
|
text-align: center;
|
|
|
|
|
|
.stat-label {
|
|
|
display: block;
|
|
|
font-size: 11px;
|
|
|
color: #7f8c8d;
|
|
|
margin-bottom: 3px;
|
|
|
}
|
|
|
|
|
|
.stat-value {
|
|
|
display: block;
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.lifecycle-type-selector {
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
h3 {
|
|
|
margin: 0 0 10px 0;
|
|
|
font-size: 15px;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
|
|
|
.type-cards {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
|
gap: 6px;
|
|
|
|
|
|
.type-card {
|
|
|
background: #fff;
|
|
|
border: 2px solid #e1e6f0;
|
|
|
border-radius: 6px;
|
|
|
padding: 10px;
|
|
|
text-align: center;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s ease;
|
|
|
position: relative;
|
|
|
|
|
|
&:hover {
|
|
|
border-color: #409EFF;
|
|
|
}
|
|
|
|
|
|
&.active {
|
|
|
border-color: #409EFF;
|
|
|
background: #f0f9ff;
|
|
|
}
|
|
|
|
|
|
.type-icon {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
border-radius: 6px;
|
|
|
margin-bottom: 5px;
|
|
|
color: #fff;
|
|
|
}
|
|
|
|
|
|
.type-name {
|
|
|
display: block;
|
|
|
font-size: 11px;
|
|
|
color: #2c3e50;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.type-count {
|
|
|
position: absolute;
|
|
|
top: -3px;
|
|
|
right: -3px;
|
|
|
background: #409EFF;
|
|
|
color: #fff;
|
|
|
border-radius: 8px;
|
|
|
padding: 1px 5px;
|
|
|
font-size: 9px;
|
|
|
min-width: 14px;
|
|
|
text-align: center;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.lifecycle-timeline-container {
|
|
|
h3 {
|
|
|
margin: 0 0 10px 0;
|
|
|
font-size: 15px;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
|
|
|
.timeline-wrapper {
|
|
|
position: relative;
|
|
|
|
|
|
.timeline-item {
|
|
|
position: relative;
|
|
|
padding-left: 35px;
|
|
|
padding-bottom: 16px;
|
|
|
|
|
|
&:last-child {
|
|
|
.timeline-line {
|
|
|
display: none;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.timeline-marker {
|
|
|
position: absolute;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
width: 28px;
|
|
|
height: 28px;
|
|
|
border-radius: 50%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
color: #fff;
|
|
|
z-index: 2;
|
|
|
}
|
|
|
|
|
|
.timeline-content {
|
|
|
background: #fff;
|
|
|
border-radius: 6px;
|
|
|
padding: 10px;
|
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
.event-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 5px;
|
|
|
|
|
|
h4 {
|
|
|
margin: 0;
|
|
|
font-size: 13px;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
|
|
|
.event-time {
|
|
|
font-size: 11px;
|
|
|
color: #7f8c8d;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.event-description {
|
|
|
font-size: 12px;
|
|
|
color: #606266;
|
|
|
margin: 0 0 6px 0;
|
|
|
}
|
|
|
|
|
|
.event-tags {
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
.el-tag {
|
|
|
margin-right: 3px;
|
|
|
margin-bottom: 3px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.timeline-line {
|
|
|
position: absolute;
|
|
|
left: 13px;
|
|
|
top: 28px;
|
|
|
width: 2px;
|
|
|
height: 100%;
|
|
|
background: #e1e6f0;
|
|
|
z-index: 1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.detail-section {
|
|
|
.card-header-title {
|
|
|
font-weight: 600;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
|
|
|
.maintenance-section {
|
|
|
.maintenance-summary {
|
|
|
margin-bottom: 12px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.filter-btn-primary {
|
|
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
|
|
border: none;
|
|
|
|
|
|
&:hover {
|
|
|
background: linear-gradient(45deg, #5a6fd8, #6a4190);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@keyframes pulse {
|
|
|
0% {
|
|
|
transform: scale(0.95);
|
|
|
box-shadow: 0 0 0 0 currentColor;
|
|
|
}
|
|
|
|
|
|
70% {
|
|
|
transform: scale(1);
|
|
|
box-shadow: 0 0 0 10px transparent;
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
transform: scale(0.95);
|
|
|
box-shadow: 0 0 0 0 transparent;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.fade-transform-enter-active,
|
|
|
.fade-transform-leave-active {
|
|
|
transition: all 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.fade-transform-enter-from {
|
|
|
opacity: 0;
|
|
|
transform: translateY(20px);
|
|
|
}
|
|
|
|
|
|
.fade-transform-leave-to {
|
|
|
opacity: 0;
|
|
|
transform: translateY(-20px);
|
|
|
}
|
|
|
</style>
|