You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1339 lines
42 KiB
Vue
1339 lines
42 KiB
Vue
<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">
|
|
<i class="el-icon-monitor"></i>
|
|
设备台账管理
|
|
</h1>
|
|
<p class="page-subtitle">设备全生命周期管理与履历追踪</p>
|
|
</div>
|
|
<div class="panel-right">
|
|
<div class="current-time">
|
|
<i class="el-icon-time"></i>
|
|
{{ 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'">
|
|
<i class="el-icon-grid"></i>
|
|
网格视图
|
|
</el-button>
|
|
<el-button :type="viewMode === 'list' ? 'primary' : 'default'" @click="viewMode = 'list'">
|
|
<i class="el-icon-menu"></i>
|
|
列表视图
|
|
</el-button>
|
|
</el-button-group>
|
|
</div>
|
|
<div class="toolbar-right">
|
|
<!-- <el-button type="primary" icon="el-icon-plus" @click="handleAdd" v-hasPermi="['dms:dmsBaseMachineInfo:add']">
|
|
新增设备
|
|
</el-button>
|
|
<el-button type="success" icon="el-icon-edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['dms:dmsBaseMachineInfo:edit']">
|
|
修改设备
|
|
</el-button>
|
|
<el-button type="danger" icon="el-icon-delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['dms:dmsBaseMachineInfo:remove']">
|
|
删除设备
|
|
</el-button>-->
|
|
<el-button text @click="showSearch = !showSearch">
|
|
<i class="el-icon-filter"></i>
|
|
{{ showSearch ? '隐藏' : '显示' }}筛选
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 查询过滤器 -->
|
|
<el-card class="search-filter-card" v-show="showSearch">
|
|
<div slot="header" class="filter-header">
|
|
<div class="filter-title">
|
|
<i class="el-icon-filter"></i>
|
|
筛选条件
|
|
</div>
|
|
<el-button text @click="showSearch = false">
|
|
<i class="el-icon-arrow-up"></i>
|
|
</el-button>
|
|
</div>
|
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
|
|
<el-form-item label="设备编号" prop="deviceCode">
|
|
<el-input v-model="queryParams.deviceCode" placeholder="请输入设备编号" clearable />
|
|
</el-form-item>
|
|
<el-form-item label="设备名称" prop="deviceName">
|
|
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable />
|
|
</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-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.objId" class="machine-card-wrapper" @click="selectMachine(machine)">
|
|
<div :class="['machine-card', { selected: selectedMachine && selectedMachine.objId === machine.objId }]">
|
|
<div :class="['status-indicator', `status-${machine.deviceStatus}`]">
|
|
<span class="status-dot"></span>
|
|
</div>
|
|
<div class="machine-visual">
|
|
<div class="machine-icon-bg">
|
|
<i class="el-icon-monitor" style="font-size: 40px;"></i>
|
|
</div>
|
|
<div class="machine-number">{{ machine.deviceCode }}</div>
|
|
</div>
|
|
<div class="machine-details">
|
|
<h3 class="machine-name">{{ machine.deviceName }}</h3>
|
|
<div class="machine-meta">
|
|
<div class="meta-item">
|
|
<i class="el-icon-location"></i>
|
|
<span>{{ machine.productLineName || '-' }}</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<i class="el-icon-document"></i>
|
|
<span>{{ getDeviceTypeName(machine.deviceType) }}</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<i class="el-icon-time"></i>
|
|
<span>建立 {{ calculateRunDays(machine.createdTime) }} 天</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" :page.sync="queryParams.pageNum" :limit.sync="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" style="width: 100%">
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
<el-table-column label="设备编号" align="center" prop="deviceCode" min-width="120" />
|
|
<el-table-column label="设备名称" align="center" prop="deviceName" min-width="150" />
|
|
<el-table-column label="设备类型" align="center" prop="deviceTypeName" min-width="120" />
|
|
<el-table-column label="所属产线" align="center" prop="productLineName" min-width="130" />
|
|
<el-table-column label="设备状态" align="center" prop="deviceStatus" width="120">
|
|
<template slot-scope="scope">
|
|
<dict-tag :options="machine_status" :value="String(scope.row.deviceStatus)" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="创建时间" align="center" prop="createdTime" min-width="160">
|
|
<template slot-scope="scope">
|
|
<span>{{ formatDate(scope.row.createdTime) }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" align="center" width="320" fixed="right">
|
|
<template slot-scope="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" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 添加或修改设备信息对话框 -->
|
|
<el-dialog :title="dialog.title" :visible.sync="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="deviceCode">
|
|
<el-input v-model="form.deviceCode" placeholder="请输入设备编号" />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="设备名称" prop="deviceName">
|
|
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item label="设备类型" prop="deviceType">
|
|
<el-select v-model="form.deviceType" placeholder="请选择设备类型" style="width: 100%;">
|
|
<el-option v-for="item in deviceTypes" :key="item.deviceTypeId" :label="item.deviceTypeName" :value="String(item.deviceTypeId)" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="设备型号" prop="deviceModel">
|
|
<el-input v-model="form.deviceModel" placeholder="请输入设备型号" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item label="供应商" prop="supplierId">
|
|
<el-select v-model="form.supplierId" placeholder="请选择供应商" style="width: 100%;">
|
|
<el-option v-for="item in supplierInfoList" :key="item.supplierId" :label="item.supplierName" :value="String(item.supplierId)" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="设备状态" prop="deviceStatus">
|
|
<el-select v-model="form.deviceStatus" 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="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>
|
|
<span slot="footer" class="dialog-footer">
|
|
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
|
<el-button @click="cancel">取 消</el-button>
|
|
</span>
|
|
</el-dialog>
|
|
|
|
<!-- 生命周期抽屉 -->
|
|
<el-drawer :visible.sync="lifecycleDrawer" :title="`设备生命周期 - ${selectedMachine ? selectedMachine.deviceName : ''}`" 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">
|
|
<i class="el-icon-monitor" style="font-size: 40px;"></i>
|
|
</div>
|
|
<div class="device-basic">
|
|
<h2>{{ selectedMachine.deviceName }}</h2>
|
|
<p>编号:{{ selectedMachine.deviceCode }} | 产线:{{ selectedMachine.productLineName || '-' }}</p>
|
|
</div>
|
|
</div>
|
|
<el-divider />
|
|
<div class="overview-stats">
|
|
<div class="stat">
|
|
<span class="stat-label">建立天数</span>
|
|
<span class="stat-value">{{ calculateRunDays(selectedMachine.createdTime) }}</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.indexOf(type.value) > -1 }]"
|
|
@click="toggleLifecycleType(type.value)">
|
|
<div class="type-icon" :style="{ background: type.gradient }">
|
|
<i :class="type.iconClass" style="font-size: 24px;"></i>
|
|
</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 }">
|
|
<i :class="event.iconClass" style="font-size: 20px; color: #fff;"></i>
|
|
</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>
|
|
<div class="timeline-line"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 空状态 -->
|
|
<el-empty v-else-if="selectedLifecycleTypes.length > 0" description="暂无相关生命周期数据" />
|
|
</div>
|
|
</el-drawer>
|
|
|
|
<!-- 设备详情对话框 -->
|
|
<el-dialog :visible.sync="detailDialog" :title="`设备详情 - ${selectedMachine ? selectedMachine.deviceName : ''}`" 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>
|
|
<div slot="header" class="card-header-title">
|
|
<span>设备基础信息</span>
|
|
</div>
|
|
<el-descriptions :column="1" border>
|
|
<el-descriptions-item label="设备编号">{{ selectedMachine ? selectedMachine.deviceCode : '' }}</el-descriptions-item>
|
|
<el-descriptions-item label="设备名称">{{ selectedMachine ? selectedMachine.deviceName : '' }}</el-descriptions-item>
|
|
<el-descriptions-item label="设备类型">{{ selectedMachine ? selectedMachine.deviceTypeName : '' }}</el-descriptions-item>
|
|
<el-descriptions-item label="所属产线">{{ selectedMachine ? (selectedMachine.productLineName || '-') : '' }}</el-descriptions-item>
|
|
<el-descriptions-item label="设备状态">
|
|
<dict-tag :options="machine_status" :value="String(selectedMachine ? (selectedMachine.deviceStatus || '') : '')" />
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="创建时间">{{ selectedMachine ? formatDateTime(selectedMachine.createdTime) : '' }}</el-descriptions-item>
|
|
</el-descriptions>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { listDeviceLedger } from '@/api/base/deviceLedger';
|
|
import { listAllDmsBillsMaintInstance } from '@/api/dms/dmsBillsMaintInstance';
|
|
import { listAllDmsRecordInspect } from '@/api/dms/dmsRecordInspect';
|
|
import { listAllShutDown } from '@/api/dms/shutDown';
|
|
import { listAllDmsBillsFaultInstance } from '@/api/dms/dmsBillsFaultInstance';
|
|
import { listAllRepairRecord } from '@/api/dms/repairRecord';
|
|
import pagination from '@/components/Pagination';
|
|
|
|
export default {
|
|
name: 'ProdBaseMachineInfo',
|
|
components: {
|
|
pagination
|
|
},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
buttonLoading: false,
|
|
showSearch: true,
|
|
machineList: [],
|
|
total: 0,
|
|
viewMode: 'grid',
|
|
selectedMachine: null,
|
|
detailDialog: false,
|
|
activeTab: 'basic',
|
|
currentTime: '',
|
|
timer: null,
|
|
deviceTypes: [],
|
|
supplierInfoList: [],
|
|
ids: [],
|
|
single: true,
|
|
multiple: true,
|
|
// 生命周期相关
|
|
lifecycleDrawer: false,
|
|
lifecycleLoading: false,
|
|
selectedLifecycleTypes: [],
|
|
lifecycleEvents: [],
|
|
lifecycleRequestId: 0,
|
|
lifecycleTypes: [
|
|
{ label: '保养', value: 'maintenance', iconClass: 'el-icon-setting', gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', count: 0 },
|
|
{ label: '点检', value: 'inspection', iconClass: 'el-icon-view', gradient: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)', count: 0 },
|
|
{ label: '停机', value: 'downtime', iconClass: 'el-icon-warning-outline', gradient: 'linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%)', count: 0 },
|
|
{ label: '故障', value: 'fault', iconClass: 'el-icon-circle-close', gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', count: 0 },
|
|
{ label: '维修', value: 'repair', iconClass: 'el-icon-tools', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', count: 0 }
|
|
],
|
|
technicalParams: [],
|
|
dialog: {
|
|
visible: false,
|
|
title: ''
|
|
},
|
|
queryParams: {
|
|
pageNum: 1,
|
|
pageSize: 10,
|
|
deviceCode: undefined,
|
|
deviceName: undefined
|
|
},
|
|
form: {
|
|
objId: undefined,
|
|
deviceCode: undefined,
|
|
deviceName: undefined,
|
|
deviceType: undefined,
|
|
deviceModel: undefined,
|
|
supplierId: undefined,
|
|
deviceStatus: '1',
|
|
remark: undefined
|
|
},
|
|
rules: {
|
|
deviceCode: [
|
|
{ required: true, message: '设备编号不能为空', trigger: 'blur' }
|
|
],
|
|
deviceName: [
|
|
{ required: true, message: '设备名称不能为空', trigger: 'blur' }
|
|
],
|
|
deviceStatus: [
|
|
{ required: true, message: '设备状态不能为空', trigger: 'change' }
|
|
]
|
|
},
|
|
machine_status: [
|
|
{ label: '正常', value: '1' },
|
|
{ label: '维修中', value: '2' },
|
|
{ label: '停用', value: '0' }
|
|
]
|
|
};
|
|
},
|
|
watch: {
|
|
selectedLifecycleTypes: {
|
|
handler(newTypes) {
|
|
if (newTypes.length > 0 && this.selectedMachine) {
|
|
this.loadLifecycleData();
|
|
} else {
|
|
this.lifecycleRequestId++;
|
|
this.lifecycleEvents = [];
|
|
this.lifecycleLoading = false;
|
|
}
|
|
},
|
|
deep: true
|
|
},
|
|
selectedMachine(newMachine) {
|
|
if (newMachine) {
|
|
this.selectedLifecycleTypes = [];
|
|
this.lifecycleEvents = [];
|
|
this.loadLifecycleTypes();
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
this.getList();
|
|
this.getDeviceTypes();
|
|
this.getSupplierInfoList();
|
|
this.updateCurrentTime();
|
|
this.timer = setInterval(this.updateCurrentTime, 1000);
|
|
},
|
|
beforeDestroy() {
|
|
if (this.timer) {
|
|
clearInterval(this.timer);
|
|
}
|
|
},
|
|
methods: {
|
|
updateCurrentTime() {
|
|
const now = new Date();
|
|
this.currentTime = now.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false
|
|
});
|
|
},
|
|
async getList() {
|
|
this.loading = true;
|
|
try {
|
|
const res = await listDeviceLedger(this.queryParams);
|
|
this.machineList = res.rows || [];
|
|
this.total = res.total || 0;
|
|
} catch (error) {
|
|
console.error('获取设备列表失败:', error);
|
|
this.$modal.msgError('获取设备列表失败');
|
|
this.machineList = [];
|
|
this.total = 0;
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
async getDeviceTypes() {
|
|
try {
|
|
// 使用假数据
|
|
this.deviceTypes = [
|
|
{ deviceTypeId: '1', deviceTypeName: '数控机床' },
|
|
{ deviceTypeId: '2', deviceTypeName: '冲床' },
|
|
{ deviceTypeId: '3', deviceTypeName: '焊接机' },
|
|
{ deviceTypeId: '4', deviceTypeName: '其他' }
|
|
];
|
|
} catch (error) {
|
|
console.error('获取设备类型失败:', error);
|
|
}
|
|
},
|
|
async getSupplierInfoList() {
|
|
try {
|
|
// 使用假数据
|
|
this.supplierInfoList = [
|
|
{ supplierId: '1', supplierName: '供应商A' },
|
|
{ supplierId: '2', supplierName: '供应商B' },
|
|
{ supplierId: '3', supplierName: '供应商C' }
|
|
];
|
|
} catch (error) {
|
|
console.error('获取供应商列表失败:', error);
|
|
}
|
|
},
|
|
getDeviceTypeName(typeId) {
|
|
const type = this.deviceTypes.find(t => String(t.deviceTypeId) === String(typeId));
|
|
return type ? type.deviceTypeName : '-';
|
|
},
|
|
tableRowClassName({ row }) {
|
|
if (row.deviceStatus === '0') return 'danger-row';
|
|
if (row.deviceStatus === '2') return 'warning-row';
|
|
return '';
|
|
},
|
|
formatDate(date) {
|
|
if (!date) return null;
|
|
return this.parseTime(date, '{y}-{m}-{d}');
|
|
},
|
|
formatDateTime(date) {
|
|
if (!date) return null;
|
|
return this.parseTime(date, '{y}-{m}-{d} {h}:{i}');
|
|
},
|
|
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));
|
|
},
|
|
handleQuery() {
|
|
this.queryParams.pageNum = 1;
|
|
this.getList();
|
|
},
|
|
resetQuery() {
|
|
this.$refs.queryFormRef.resetFields();
|
|
this.handleQuery();
|
|
},
|
|
selectMachine(machine) {
|
|
this.selectedMachine = machine;
|
|
},
|
|
handleSelectionChange(selection) {
|
|
this.ids = selection.map(item => item.objId);
|
|
this.single = selection.length !== 1;
|
|
this.multiple = !selection.length;
|
|
},
|
|
handleAdd() {
|
|
this.reset();
|
|
this.dialog.visible = true;
|
|
this.dialog.title = '添加设备信息';
|
|
},
|
|
async handleUpdate(row) {
|
|
this.reset();
|
|
const objId = row ? row.objId : this.ids[0];
|
|
try {
|
|
const form = row || this.machineList.find(m => m.objId === objId);
|
|
if (form) {
|
|
Object.assign(this.form, form);
|
|
this.form.deviceType = form.deviceType ? String(form.deviceType) : undefined;
|
|
this.form.supplierId = form.supplierId ? String(form.supplierId) : undefined;
|
|
}
|
|
this.dialog.visible = true;
|
|
this.dialog.title = '修改设备信息';
|
|
} catch (error) {
|
|
console.error('获取设备信息失败:', error);
|
|
this.$modal.msgError('获取设备信息失败');
|
|
}
|
|
},
|
|
submitForm() {
|
|
this.$refs.machineFormRef.validate(async (valid) => {
|
|
if (valid) {
|
|
this.buttonLoading = true;
|
|
try {
|
|
this.$modal.msgSuccess('操作成功');
|
|
this.dialog.visible = false;
|
|
await this.getList();
|
|
} catch (error) {
|
|
console.error('保存设备信息失败:', error);
|
|
this.$modal.msgError('保存设备信息失败');
|
|
} finally {
|
|
this.buttonLoading = false;
|
|
}
|
|
}
|
|
});
|
|
},
|
|
async handleDelete(row) {
|
|
const objIds = row ? row.objId : this.ids;
|
|
try {
|
|
await this.$modal.confirm('是否确认删除设备信息编号为"' + objIds + '"的数据项?');
|
|
this.$modal.msgSuccess('删除成功');
|
|
await this.getList();
|
|
} catch (error) {
|
|
console.error('删除设备信息失败:', error);
|
|
if (error !== 'cancel') {
|
|
this.$modal.msgError('删除设备信息失败');
|
|
}
|
|
}
|
|
},
|
|
cancel() {
|
|
this.reset();
|
|
this.dialog.visible = false;
|
|
},
|
|
reset() {
|
|
this.form = {
|
|
objId: undefined,
|
|
deviceCode: undefined,
|
|
deviceName: undefined,
|
|
deviceType: undefined,
|
|
deviceModel: undefined,
|
|
supplierId: undefined,
|
|
deviceStatus: '1',
|
|
remark: undefined
|
|
};
|
|
this.$refs.machineFormRef && this.$refs.machineFormRef.resetFields();
|
|
},
|
|
viewDetails(machine) {
|
|
this.selectedMachine = machine;
|
|
this.detailDialog = true;
|
|
this.activeTab = 'basic';
|
|
},
|
|
// 生命周期相关方法
|
|
viewLifecycle(machine) {
|
|
this.selectedMachine = machine;
|
|
this.lifecycleDrawer = true;
|
|
this.selectedLifecycleTypes = [];
|
|
this.lifecycleEvents = [];
|
|
this.loadLifecycleTypes();
|
|
},
|
|
toggleLifecycleType(type) {
|
|
const index = this.selectedLifecycleTypes.indexOf(type);
|
|
if (index > -1) {
|
|
this.selectedLifecycleTypes.splice(index, 1);
|
|
} else {
|
|
this.selectedLifecycleTypes.push(type);
|
|
}
|
|
},
|
|
async loadLifecycleTypes() {
|
|
if (!this.selectedMachine) return;
|
|
const deviceCode = this.selectedMachine.deviceCode;
|
|
try {
|
|
// 并行获取所有类型的数据数量
|
|
const [maintRes, inspectRes, shutdownRes, faultRes, repairRes] = await Promise.all([
|
|
listAllDmsBillsMaintInstance({ deviceCode }).catch(() => ({ data: [] })),
|
|
listAllDmsRecordInspect({ deviceCode }).catch(() => ({ data: [] })),
|
|
listAllShutDown({ deviceCode }).catch(() => ({ data: [] })),
|
|
listAllDmsBillsFaultInstance({ deviceCode }).catch(() => ({ data: [] })),
|
|
listAllRepairRecord({ deviceCode }).catch(() => ({ data: [] }))
|
|
]);
|
|
// 更新各类型数量
|
|
this.lifecycleTypes.forEach(type => {
|
|
switch (type.value) {
|
|
case 'maintenance':
|
|
type.count = (maintRes.data || []).length;
|
|
break;
|
|
case 'inspection':
|
|
type.count = (inspectRes.data || []).length;
|
|
break;
|
|
case 'downtime':
|
|
type.count = (shutdownRes.data || []).length;
|
|
break;
|
|
case 'fault':
|
|
type.count = (faultRes.data || []).length;
|
|
break;
|
|
case 'repair':
|
|
type.count = (repairRes.data || []).length;
|
|
break;
|
|
default:
|
|
type.count = 0;
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('加载生命周期类型统计失败:', error);
|
|
}
|
|
},
|
|
async loadLifecycleData() {
|
|
if (!this.selectedMachine || this.selectedLifecycleTypes.length === 0) {
|
|
this.lifecycleEvents = [];
|
|
return;
|
|
}
|
|
const reqId = ++this.lifecycleRequestId;
|
|
this.lifecycleLoading = true;
|
|
const events = [];
|
|
const deviceCode = this.selectedMachine.deviceCode;
|
|
try {
|
|
// 根据选中的类型加载对应数据
|
|
const promises = [];
|
|
this.selectedLifecycleTypes.forEach(type => {
|
|
switch (type) {
|
|
case 'maintenance':
|
|
promises.push(
|
|
listAllDmsBillsMaintInstance({ deviceCode }).then(res => {
|
|
(res.data || []).forEach((item, i) => {
|
|
const statusMap = { 1: '待保养', 2: '保养中', 3: '已完成' };
|
|
const statusText = statusMap[item.maintStatus] || '未知';
|
|
events.push({
|
|
id: `maint-${item.maintInstanceId || i}`,
|
|
type: 'maintenance',
|
|
title: item.maintLevelName || '设备保养',
|
|
description: `保养单号: ${item.billsMaintCode || '-'} | 保养人员: ${item.maintSupervisor || '-'} | 状态: ${statusText}`,
|
|
time: item.realEndTime || item.realBeginTime || item.planBeginTime || item.createTime,
|
|
iconClass: 'el-icon-setting',
|
|
color: '#4facfe',
|
|
tags: ['保养', statusText]
|
|
});
|
|
});
|
|
}).catch(() => {})
|
|
);
|
|
break;
|
|
case 'inspection':
|
|
promises.push(
|
|
listAllDmsRecordInspect({ deviceCode }).then(res => {
|
|
(res.data || []).forEach((item, i) => {
|
|
const statusMap = { 1: '待巡检', 2: '巡检中', 3: '完成' };
|
|
const statusText = statusMap[item.inspectStatus] || '未知';
|
|
const inspectTypeText = item.inspectType === '1' ? '巡检' : '点检';
|
|
events.push({
|
|
id: `inspect-${item.recordInspectId || i}`,
|
|
type: 'inspection',
|
|
title: inspectTypeText,
|
|
description: `巡检单号: ${item.billsInspectCode || '-'} | 执行人: ${item.performer || '-'} | 状态: ${statusText}`,
|
|
time: item.realEndTime || item.realBeginTime || item.planBeginTime || item.createTime,
|
|
iconClass: 'el-icon-view',
|
|
color: statusText === '完成' ? '#30cfd0' : '#ff7675',
|
|
tags: [inspectTypeText, statusText]
|
|
});
|
|
});
|
|
}).catch(() => {})
|
|
);
|
|
break;
|
|
case 'downtime':
|
|
promises.push(
|
|
listAllShutDown({ deviceCode }).then(res => {
|
|
(res.data || []).forEach((item, i) => {
|
|
const shutTimeText = item.shutTime ? `${item.shutTime}小时` : '-';
|
|
events.push({
|
|
id: `downtime-${item.recordShutDownId || i}`,
|
|
type: 'downtime',
|
|
title: '设备停机',
|
|
description: `原因: ${item.shutReason || '-'} | 时长: ${shutTimeText}`,
|
|
time: item.shutBeginTime || item.createTime,
|
|
iconClass: 'el-icon-warning-outline',
|
|
color: '#ffeaa7',
|
|
tags: ['停机', item.shutReason ? (item.shutReason.includes('计划') ? '计划' : '非计划') : '非计划']
|
|
});
|
|
});
|
|
}).catch(() => {})
|
|
);
|
|
break;
|
|
case 'fault':
|
|
promises.push(
|
|
listAllDmsBillsFaultInstance({ deviceCode }).then(res => {
|
|
(res.data || []).forEach((item, i) => {
|
|
const statusMap = { '0': '待维修', '1': '维修中', '2': '维修完成', '3': '待检修', '4': '检修中', '5': '检修完成' };
|
|
const statusText = statusMap[item.billsStatus] || '未知';
|
|
const faultLevelMap = { '1': '一般', '2': '紧急', '3': '严重' };
|
|
const levelText = faultLevelMap[item.faultLevel] || '一般';
|
|
events.push({
|
|
id: `fault-${item.repairInstanceId || i}`,
|
|
type: 'fault',
|
|
title: item.faultType || '设备故障',
|
|
description: `故障单号: ${item.billsFaultCode || '-'} | 等级: ${levelText} | 报修人: ${item.applyUser || '-'} | 状态: ${statusText}`,
|
|
time: item.applyTime || item.createTime,
|
|
iconClass: 'el-icon-circle-close',
|
|
color: '#fa709a',
|
|
tags: ['故障', levelText]
|
|
});
|
|
});
|
|
}).catch(() => {})
|
|
);
|
|
break;
|
|
case 'repair':
|
|
promises.push(
|
|
listAllRepairRecord({ deviceCode }).then(res => {
|
|
(res.data || []).forEach((item, i) => {
|
|
const resultMap = { '1': '成功', '2': '失败', '3': '部分完成' };
|
|
const resultText = resultMap[item.repairResult] || '未知';
|
|
events.push({
|
|
id: `repair-${item.recordId || i}`,
|
|
type: 'repair',
|
|
title: item.faultPhenomenon || '设备维修',
|
|
description: `工单编号: ${item.workOrderCode || '-'} | 维修人: ${item.repairerName || '-'} | 结果: ${resultText}`,
|
|
time: item.endTime || item.startTime || item.createTime,
|
|
iconClass: 'el-icon-tools',
|
|
color: '#667eea',
|
|
tags: ['维修', resultText]
|
|
});
|
|
});
|
|
}).catch(() => {})
|
|
);
|
|
break;
|
|
}
|
|
});
|
|
await Promise.all(promises);
|
|
if (reqId === this.lifecycleRequestId) {
|
|
this.lifecycleEvents = events.sort((a, b) => {
|
|
const timeA = a.time ? new Date(a.time).getTime() : 0;
|
|
const timeB = b.time ? new Date(b.time).getTime() : 0;
|
|
return timeB - timeA;
|
|
});
|
|
this.lifecycleLoading = false;
|
|
}
|
|
} catch (e) {
|
|
if (reqId === this.lifecycleRequestId) {
|
|
this.lifecycleLoading = false;
|
|
}
|
|
console.error('加载生命周期数据失败:', e);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</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;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.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: 6px;
|
|
}
|
|
}
|
|
|
|
.main-content {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
min-height: 500px;
|
|
}
|
|
|
|
.grid-view {
|
|
.machine-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
}
|
|
}
|
|
|
|
.machine-card-wrapper {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.machine-card {
|
|
background: #fff;
|
|
border: 2px solid #e0e0e0;
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
&:hover {
|
|
border-color: #409eff;
|
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
|
}
|
|
&.selected {
|
|
border-color: #409eff;
|
|
background: #f0f7ff;
|
|
}
|
|
}
|
|
|
|
.status-indicator {
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 8px;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
&.status-1 {
|
|
background: #67c23a;
|
|
}
|
|
&.status-2 {
|
|
background: #e6a23c;
|
|
}
|
|
&.status-0 {
|
|
background: #f56c6c;
|
|
}
|
|
}
|
|
|
|
.machine-visual {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
.machine-icon-bg {
|
|
width: 60px;
|
|
height: 60px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #fff;
|
|
margin-bottom: 6px;
|
|
}
|
|
.machine-number {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
|
|
.machine-details {
|
|
margin-bottom: 12px;
|
|
.machine-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
margin: 0 0 8px 0;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.machine-meta {
|
|
.meta-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 12px;
|
|
color: #909399;
|
|
margin-bottom: 4px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
}
|
|
|
|
.quick-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.list-view {
|
|
.el-table {
|
|
background: transparent;
|
|
}
|
|
}
|
|
|
|
.detail-section {
|
|
padding: 12px 0;
|
|
}
|
|
|
|
.card-header-title {
|
|
display: flex;
|
|
align-items: center;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.dialog-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
.fade-transform-enter-active, .fade-transform-leave-active {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.fade-transform-enter, .fade-transform-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
|
|
// 生命周期相关样式
|
|
.lifecycle-drawer {
|
|
::v-deep .el-drawer__body {
|
|
padding: 16px;
|
|
background: #f5f7fa;
|
|
}
|
|
}
|
|
|
|
.lifecycle-container {
|
|
min-height: 100%;
|
|
}
|
|
|
|
.device-overview-card {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
.overview-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
|
|
.device-avatar {
|
|
width: 64px;
|
|
height: 64px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #fff;
|
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|
}
|
|
|
|
.device-basic {
|
|
flex: 1;
|
|
|
|
h2 {
|
|
margin: 0 0 6px 0;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
p {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
color: #909399;
|
|
line-height: 1.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
.el-divider {
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.overview-stats {
|
|
display: flex;
|
|
gap: 32px;
|
|
justify-content: space-around;
|
|
|
|
.stat {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
|
|
.stat-label {
|
|
font-size: 13px;
|
|
color: #909399;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: #409eff;
|
|
font-family: 'Arial', sans-serif;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.lifecycle-type-selector {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
h3 {
|
|
margin: 0 0 16px 0;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.type-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
}
|
|
|
|
.type-card {
|
|
background: #f8f9fa;
|
|
border: 2px solid transparent;
|
|
border-radius: 10px;
|
|
padding: 14px 10px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
position: relative;
|
|
|
|
&:hover {
|
|
border-color: #409eff;
|
|
background: #fff;
|
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
&.active {
|
|
border-color: #409eff;
|
|
background: linear-gradient(135deg, #f0f7ff 0%, #e6f4ff 100%);
|
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.25);
|
|
|
|
.type-name {
|
|
color: #409eff;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.type-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 0 auto 10px;
|
|
color: #fff;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.type-name {
|
|
display: block;
|
|
font-size: 13px;
|
|
color: #2c3e50;
|
|
margin-bottom: 0;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.type-count {
|
|
display: inline-block;
|
|
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
|
|
color: #fff;
|
|
border-radius: 12px;
|
|
min-width: 22px;
|
|
height: 22px;
|
|
line-height: 22px;
|
|
padding: 0 6px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
position: absolute;
|
|
top: -6px;
|
|
right: -6px;
|
|
box-shadow: 0 2px 8px rgba(255, 107, 107, 0.4);
|
|
}
|
|
}
|
|
|
|
.lifecycle-timeline-container {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
h3 {
|
|
margin: 0 0 20px 0;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
padding-bottom: 12px;
|
|
border-bottom: 2px solid #f0f0f0;
|
|
}
|
|
}
|
|
|
|
.timeline-wrapper {
|
|
position: relative;
|
|
padding-left: 32px;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 9px;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background: linear-gradient(to bottom, #409eff 0%, #e0e0e0 100%);
|
|
border-radius: 2px;
|
|
}
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
margin-bottom: 24px;
|
|
padding-bottom: 8px;
|
|
|
|
&:last-child {
|
|
.timeline-line {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.timeline-marker {
|
|
position: absolute;
|
|
left: -32px;
|
|
top: 0;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 3px solid #fff;
|
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
|
z-index: 2;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: scale(1.1);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
}
|
|
}
|
|
|
|
.timeline-content {
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.event-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
|
|
h4 {
|
|
margin: 0;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.event-time {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.event-description {
|
|
margin: 0 0 10px 0;
|
|
font-size: 13px;
|
|
color: #606266;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.event-tags {
|
|
display: flex;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 8px;
|
|
|
|
.el-tag {
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.event-details {
|
|
margin-top: 12px;
|
|
|
|
.el-collapse {
|
|
border: none;
|
|
}
|
|
|
|
.el-descriptions {
|
|
margin-top: 8px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.timeline-line {
|
|
position: absolute;
|
|
left: -17px;
|
|
top: 28px;
|
|
width: 2px;
|
|
height: calc(100% - 28px);
|
|
background: #e0e0e0;
|
|
z-index: 1;
|
|
}
|
|
}
|
|
</style>
|