Files
td_official/src/views/dhr_demo/shiyanguanli.vue
2025-09-13 18:43:26 +08:00

1588 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div class="operation-inspection">
<!-- 1. 顶部导航选项卡对应原试验系统的外层导航 -->
<div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab active" @click="handleInspection3">试验管理</div>
<div class="nav-tab" @click="handleInspection4">报修管理</div>
<div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div>
</div>
<TitleComponent title="实验管理系统" subtitle="制定巡检计划,安排巡检任务,跟进巡检记录"></TitleComponent>
<!-- 选项卡和按钮组合 -->
<div class="tabs-wrapper">
<div style="display: flex; align-items: center; gap: 10px">
<el-button type="primary" @click="handleInspectionManagement1">实验计划</el-button>
<el-button type="primary" @click="handleInspectionManagement2">实验任务</el-button>
<el-button type="primary" @click="handleInspectionManagement3">实验记录</el-button>
</div>
</div>
<!-- 4. 筛选和操作区域与试验系统filter-and-actions结构一致 -->
<div class="filter-and-actions">
<div class="filters">
<el-select v-model="filterStatus" placeholder="巡检状态" clearable>
<el-option label="全部状态" value="all"></el-option>
<el-option label="正常" value="normal"></el-option>
<el-option label="需关注" value="attention"></el-option>
<el-option label="有问题" value="problem"></el-option>
</el-select>
<el-select v-model="filterType" placeholder="巡检类型" clearable>
<el-option label="全部类型" value="all"></el-option>
<el-option label="数据库" value="database"></el-option>
<el-option label="服务器" value="server"></el-option>
<el-option label="网络设备" value="network"></el-option>
</el-select>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
class="date-picker"
></el-date-picker>
<el-button type="primary" class="search-btn"> 搜索 </el-button>
</div>
<div class="action-buttons">
<el-button type="primary" class="create-btn" @click="showRecordDialog = true"> <i class="fas fa-plus"></i> 新增实验记录 </el-button>
</div>
</div>
<!-- 5. 主内容区根据选中选项卡切换内容 -->
<div class="content-container">
<!-- 5.1 巡检计划表格与试验系统表格结构一致 -->
<div v-if="activeTab === 'plan'" class="table-container">
<el-table :data="planTableData" border>
<el-table-column prop="name" label="计划名称" width="220">
<template #default="scope">
<div class="plan-name">{{ scope.row.name }}</div>
</template>
</el-table-column>
<el-table-column prop="type" label="巡检类型" width="120"></el-table-column>
<el-table-column prop="cycle" label="巡检周期" width="120"></el-table-column>
<el-table-column prop="dateRange" label="执行时间范围"></el-table-column>
<el-table-column prop="progress" label="完成进度" width="120">
<template #default="scope">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: scope.row.progress + '%', backgroundColor: getProgressColor(scope.row.status) }"></div>
</div>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<span :class="['status-tag', `status-${scope.row.status}`]">
{{ getStatusText(scope.row.status) }}
</span>
</template>
</el-table-column>
<el-table-column prop="responsible" label="负责人" width="120"></el-table-column>
<el-table-column label="操作" width="220">
<template #default="scope">
<div class="operation-buttons">
<button class="operate-btn edit-btn" v-if="['drafted', 'paused'].includes(scope.row.status)">编辑</button>
<button class="operate-btn execute-btn" v-if="scope.row.status === 'drafted'">执行</button>
<button class="operate-btn pause-btn" v-if="scope.row.status === 'in-progress'">暂停</button>
<button class="operate-btn resume-btn" v-if="scope.row.status === 'paused'">恢复</button>
<button class="operate-btn view-btn">查看详情</button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 5.2 巡检任务表格结构与计划表格一致数据不同 -->
<div v-if="activeTab === 'task'" class="table-container">
<el-table :data="taskTableData" border>
<el-table-column prop="name" label="任务名称" width="220"></el-table-column>
<el-table-column prop="planName" label="所属计划" width="180"></el-table-column>
<el-table-column prop="type" label="巡检类型" width="120"></el-table-column>
<el-table-column prop="target" label="巡检对象" width="150"></el-table-column>
<el-table-column prop="deadline" label="截止时间" width="160"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<span :class="['status-tag', `status-${scope.row.status}`]">
{{ getTaskStatusText(scope.row.status) }}
</span>
</template>
</el-table-column>
<el-table-column prop="executor" label="执行人" width="120"></el-table-column>
<el-table-column label="操作" width="180">
<template #default="scope">
<div class="operation-buttons">
<button class="operate-btn accept-btn" v-if="scope.row.status === 'pending'">接受</button>
<button class="operate-btn complete-btn" v-if="scope.row.status === 'accepted'">完成</button>
<button class="operate-btn view-btn">查看详情</button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 5.3 巡检记录整合原右侧卡片和统计图表 -->
<div v-if="activeTab === 'record'" class="record-container">
<div class="main-content-container">
<!-- 左侧统计与图表区 -->
<div class="left-content">
<div class="content-card">
<div class="card-header">
<h2 class="card-title">巡检记录统计</h2>
<div class="time-range-buttons">
<button class="time-btn" :class="{ active: timeRange === 'month' }" @click="handleTimeRangeChange('month')"></button>
<button class="time-btn" :class="{ active: timeRange === 'week' }" @click="handleTimeRangeChange('week')"></button>
<button class="time-btn" :class="{ active: timeRange === 'day' }" @click="handleTimeRangeChange('day')"></button>
</div>
</div>
<div class="card-body">
<!-- 统计卡片 -->
<div class="stat-grid">
<div class="stat-card">
<p class="stat-label">已完成巡检</p>
<p class="stat-value">{{ statData.completed }}</p>
</div>
<div class="stat-card">
<p class="stat-label">发现问题数</p>
<p class="stat-value">{{ statData.problems }}</p>
</div>
<div class="stat-card">
<p class="stat-label">已解决问题</p>
<p class="stat-value">{{ statData.resolved }}</p>
</div>
<div class="stat-card">
<p class="stat-label">平均完成时间</p>
<p class="stat-value">{{ statData.avgTime }}</p>
</div>
</div>
<div class="divider"></div>
<!-- 进度与图表区 -->
<div class="chart-container">
<!-- 左侧饼图 -->
<div class="pie-chart">
<p class="chart-title">问题解决率</p>
<div class="pie-wrapper">
<svg class="w-full h-full" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="none" stroke="#f3f4f6" stroke-width="10" />
<circle
cx="50"
cy="50"
r="45"
fill="none"
stroke="#10b981"
stroke-width="10"
:stroke-dasharray="282.74"
:stroke-dashoffset="282.74 * (1 - statData.resolveRate / 100)"
transform="rotate(-90 50 50)"
/>
<circle
cx="50"
cy="50"
r="45"
fill="none"
stroke="#f97316"
stroke-width="10"
:stroke-dasharray="282.74 * (1 - statData.resolveRate / 100)"
stroke-dashoffset="0"
transform="rotate(-90 50 50)"
/>
</svg>
<div class="pie-center">
<p class="text-sm text-gray-500">解决率</p>
<p class="text-lg font-bold text-gray-800">{{ statData.resolveRate }}%</p>
</div>
</div>
<div class="pie-legend">
<div class="legend-item">
<div class="legend-color resolved"></div>
<span class="legend-text">已解决</span>
</div>
<div class="legend-item">
<div class="legend-color unresolved"></div>
<span class="legend-text">未解决</span>
</div>
</div>
</div>
<!-- 右侧进度条 -->
<div class="progress-bars">
<div class="progress-item">
<div class="progress-header">
<span class="progress-label">巡检完成率</span>
<span class="progress-value">{{ statData.completeRate }}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: statData.completeRate + '%', backgroundColor: '#3b82f6' }"></div>
</div>
</div>
<div class="progress-item">
<div class="progress-header">
<span class="progress-label">问题解决率</span>
<span class="progress-value">{{ statData.resolveRate }}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: statData.resolveRate + '%', backgroundColor: '#10b981' }"></div>
</div>
</div>
<div class="progress-item">
<div class="progress-header">
<span class="progress-label">任务及时率</span>
<span class="progress-value">{{ statData.timelyRate }}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: statData.timelyRate + '%', backgroundColor: '#8b5cf6' }"></div>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<!-- 问题分类统计 -->
<div class="problem-category">
<h3 class="section-title">问题类型分布</h3>
<div class="category-list">
<div class="category-item" v-for="(item, index) in problemCategories" :key="index">
<div class="category-header">
<span class="category-label">{{ item.name }}</span>
<span class="category-value">{{ item.rate }}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: item.rate + '%', backgroundColor: item.color }"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧最近巡检记录 -->
<div class="right-content">
<div class="content-card">
<div class="card-header">
<h2 class="card-title">最近巡检记录</h2>
</div>
<div class="card-body record-list">
<div class="inspection-record" v-for="(record, index) in recentRecords" :key="index">
<div class="record-header">
<h3 class="record-title">{{ record.name }}</h3>
<span :class="['status-tag', `status-${record.status}`]">
{{ getRecordStatusText(record.status) }}
</span>
</div>
<p class="record-meta">{{ record.time }} · {{ record.executor }}</p>
<div class="record-summary">{{ record.summary }}</div>
<div class="record-actions">
<button class="operate-btn view-btn">查看详情</button>
<button class="operate-btn report-btn">生成报告</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 6. 分页与试验系统结构一致 -->
<div class="pagination" v-if="activeTab !== 'record'">
<p class="total-records">显示1到{{ pageSize }}{{ totalRecords }}条记录</p>
<el-pagination
layout="prev, pager, next, jumper, sizes"
:total="totalRecords"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[20, 50, 100]"
@current-change="handlePageChange"
@size-change="handleSizeChange"
></el-pagination>
</div>
</div>
<!-- 新增实验记录弹窗 -->
<el-dialog
v-model="showRecordDialog"
title="新增实验记录"
width="1100px"
:before-close="handleClose"
class="custom-experiment-dialog"
:close-on-click-modal="false"
>
<el-form :model="createForm" :rules="createFormRules" ref="createFormRef" label-width="120px" class="custom-form">
<div class="form-container">
<!-- 材料名称 -->
<el-form-item label="材料名称" class="form-item">
<el-input v-model="formData.materialName" placeholder="请输入材料名称" class="form-input" />
</el-form-item>
<!-- 试验时间 -->
<div class="form-row">
<el-form-item label="预计开始时间" class="form-item">
<el-date-picker v-model="formData.startDate" type="date" placeholder="请选择日期" value-format="YYYY-MM-DD" class="form-input" />
</el-form-item>
<el-form-item label="预计结束时间" class="form-item">
<el-date-picker v-model="formData.endDate" type="date" placeholder="请选择日期" value-format="YYYY-MM-DD" class="form-input" />
</el-form-item>
</div>
<!-- 试验编号 -->
<div class="form-row">
<el-form-item label="试验编号" class="form-item">
<el-input v-model="formData.testNumber" placeholder="EX-20240601" class="form-input" />
</el-form-item>
<el-form-item label="试验参考号" class="form-item">
<el-input v-model="formData.refNumber" placeholder="请输入参考号" class="form-input" />
</el-form-item>
</div>
<!-- 应用编号 -->
<div class="form-row">
<el-form-item label="应用编号" class="form-item">
<el-select v-model="formData.appNumber" placeholder="请选择" class="form-input">
<el-option label="APP-2024-001" value="APP-2024-001" />
<el-option label="APP-2024-002" value="APP-2024-002" />
</el-select>
</el-form-item>
<el-form-item label="应用项目" class="form-item">
<el-input v-model="formData.appProject" placeholder="请输入应用项目" class="form-input" />
</el-form-item>
</div>
<!-- 试验目的 -->
<el-form-item label="试验目的" class="form-item">
<el-input
v-model="formData.testPurpose"
type="textarea"
placeholder="请简要描述本次试验的目的,检验标准及试验预期达到的效果"
class="form-input"
rows="2"
/>
</el-form-item>
<!-- 试验环境要求 -->
<el-form-item label="试验环境要求" class="form-item">
<el-input
v-model="formData.envRequirements"
type="textarea"
placeholder="请描述试验所需的硬件、软件、网络环境等要求"
class="form-input"
rows="2"
/>
</el-form-item>
<!-- 负责人 -->
<el-form-item label="负责人" class="form-item">
<el-select v-model="formData.manager" placeholder="请选择试验负责人" class="form-input">
<el-option label="张明" value="张明" />
<el-option label="李华" value="李华" />
<el-option label="王强" value="王强" />
</el-select>
</el-form-item>
<!-- 参与人员 -->
<el-form-item label="参与人员" class="form-item">
<el-input v-model="formData.participants" placeholder="张明、李华、王强、刘洋、赵云" class="form-input" />
</el-form-item>
<!-- 试验步骤 -->
<el-form-item label="试验步骤" class="form-item" style="width: 100%">
<div class="steps-container">
<div class="step-item" v-for="(step, index) in formData.steps" :key="index">
<div class="step-number">{{ index + 1 }}</div>
<el-input v-model="step.content" placeholder="输入试验步骤" />
</div>
<el-button type="text" size="small" class="add-step-btn" @click="addStep">添加步骤</el-button>
</div>
</el-form-item>
<!-- 所需设备与准备 -->
<el-form-item label="所需资源与设备" class="form-item" style="width: 100%">
<div class="equipment-list">
<div class="equipment-item" v-for="(equip, index) in formData.equipments" :key="index">
<el-checkbox v-model="equip.selected">{{ equip.name }}</el-checkbox>
</div>
<div class="add-equipment">
<el-input v-model="newEquipment" placeholder="添加其他资源" />
<el-button type="primary" size="small" @click="addEquipment">添加</el-button>
</div>
</div>
</el-form-item>
<!-- 风险识别与应对措施 -->
<el-form-item label="风险识别" class="form-item">
<el-input
v-model="formData.riskMitigation"
type="textarea"
placeholder="请描述试验中存在的风险及相应的应对措施"
class="form-input"
rows="2"
/>
</el-form-item>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import router from '@/router';
import TitleComponent from '@/views/demo/components/TitleComponent.vue';
// 1. 选项卡状态管理
const activeTab = ref('plan'); // 默认为"巡检计划"
const timeRange = ref('month'); // 统计时间范围:月/周/日
// 2. 筛选条件
const filterStatus = ref('all');
const filterType = ref('all');
const dateRange = ref([]);
// 3. 分页参数
const currentPage = ref(1);
const pageSize = ref(20);
const totalRecords = ref(86); // 总记录数(模拟)
// 4. 巡检计划表格数据
const planTableData = ref([
{
name: '数据库月度性能巡检\n编号: INS-20240601',
type: '数据库',
cycle: '每月',
dateRange: '2024-06-01 至 2024-06-30',
progress: 75,
status: 'in-progress',
responsible: '张明'
},
{
name: '服务器日常巡检\n编号: INS-20240602',
type: '服务器',
cycle: '每日',
dateRange: '2024-06-01 至 2024-06-30',
progress: 92,
status: 'in-progress',
responsible: '李华'
},
{
name: '网络设备安全巡检\n编号: INS-20240603',
type: '网络设备',
cycle: '每周',
dateRange: '2024-06-01 至 2024-06-30',
progress: 50,
status: 'in-progress',
responsible: '王强'
},
{
name: '存储设备健康检查\n编号: INS-20240604',
type: '存储设备',
cycle: '每季度',
dateRange: '2024-06-01 至 2024-06-30',
progress: 0,
status: 'drafted',
responsible: '赵伟'
},
{
name: '安全设备策略巡检\n编号: INS-20240501',
type: '安全设备',
cycle: '每月',
dateRange: '2024-05-01 至 2024-05-31',
progress: 100,
status: 'completed',
responsible: '刘芳'
},
{
name: '中间件运行状态巡检\n编号: INS-20240502',
type: '中间件',
cycle: '每周',
dateRange: '2024-05-01 至 2024-05-31',
progress: 100,
status: 'completed',
responsible: '陈明'
},
{
name: '虚拟化平台巡检\n编号: INS-20240605',
type: '虚拟化',
cycle: '每月',
dateRange: '2024-06-01 至 2024-06-30',
progress: 30,
status: 'paused',
responsible: '张丽'
},
{
name: '备份系统有效性检查\n编号: INS-20240606',
type: '备份系统',
cycle: '每两周',
dateRange: '2024-06-01 至 2024-06-30',
progress: 0,
status: 'drafted',
responsible: '李强'
}
]);
// 5. 巡检任务表格数据
const taskTableData = ref([
{
name: 'DB服务器CPU监控',
planName: '数据库月度性能巡检',
type: '数据库',
target: '主数据库服务器',
deadline: '2024-06-15 18:00',
status: 'accepted',
executor: '张明'
},
{
name: '应用服务器内存检查',
planName: '服务器日常巡检',
type: '服务器',
target: '应用集群节点1-3',
deadline: '2024-06-10 24:00',
status: 'completed',
executor: '李华'
},
{
name: '防火墙规则审计',
planName: '网络设备安全巡检',
type: '网络设备',
target: '主防火墙',
deadline: '2024-06-12 18:00',
status: 'pending',
executor: '王强'
},
{
name: '存储阵列容量检查',
planName: '存储设备健康检查',
type: '存储设备',
target: '主存储阵列',
deadline: '2024-06-20 18:00',
status: 'pending',
executor: '赵伟'
},
{
name: 'WAF策略更新检查',
planName: '安全设备策略巡检',
type: '安全设备',
target: 'Web应用防火墙',
deadline: '2024-05-25 18:00',
status: 'completed',
executor: '刘芳'
}
]);
// 6. 统计数据(根据时间范围变化)
const statData = ref({
completed: 42,
problems: 7,
resolved: 5,
avgTime: '45分钟',
completeRate: 68,
resolveRate: 72,
timelyRate: 60
});
// 7. 问题类型分布
const problemCategories = ref([
{ name: '温度异常', rate: 85, color: '#3b82f6' },
{ name: '内存使用率', rate: 62, color: '#10b981' },
{ name: 'CPU负载', rate: 45, color: '#f97316' },
{ name: '响应时间', rate: 30, color: '#8b5cf6' },
{ name: '磁盘空间', rate: 15, color: '#ec4899' }
]);
// 8. 最近巡检记录
const recentRecords = ref([
{
name: '数据库性能巡检',
status: 'normal',
time: '2024-06-09 14:00-16:45',
executor: '张明',
summary: '数据库连接量128阈值500查询响应时间平均0.3S表空间使用率75%,整体状态正常'
},
{
name: '生产服务器日常巡检',
status: 'attention',
time: '2024-06-09 10:00-11:30',
executor: '李华',
summary: 'CPU平均使用率35%峰值59%内存使用率85%需关注磁盘空间62%,服务运行正常'
},
{
name: '网络设备安全巡检',
status: 'problem',
time: '2024-06-08 09:00-10:45',
executor: '王强',
summary: '防火墙部分规则异常,安全补丁需更新,访问控制和流量监控正常,已创建问题单#PRB-2024060801'
}
]);
// 9. 方法:切换顶部导航
const handleInspection1 = () => {
router.push('/rili/rili');
};
const handleInspection2 = () => {
router.push('/rili/InspectionManagement');
};
const handleInspection3 = () => {
router.push('/rili/shiyanguanli');
};
const handleInspection4 = () => {
router.push('/rili/baoxiuguanli');
};
const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli');
};
const handleInspection6 = () => {
router.push('/rili/gongdanliebiao');
};
const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai');
};
const handleInspectionManagement1 = () => {
router.push('/rili/shiyanguanli');
};
const handleInspectionManagement2 = () => {
router.push('/rili/shiyanrenwu');
};
const handleInspectionManagement3 = () => {
router.push('/rili/shiyanjilu');
};
// 10. 方法:切换功能选项卡
const switchTab = (tab) => {
activeTab.value = tab;
// 实际应用中需根据选项卡加载对应数据
if (tab === 'record') {
// 加载统计数据
updateStatData(timeRange.value);
}
};
// 11. 方法:更新统计数据(根据时间范围)
const updateStatData = (range) => {
const mockData = {
month: { completed: 42, problems: 7, resolved: 5, avgTime: '45分钟', completeRate: 68, resolveRate: 72, timelyRate: 60 },
week: { completed: 12, problems: 2, resolved: 1, avgTime: '40分钟', completeRate: 75, resolveRate: 50, timelyRate: 65 },
day: { completed: 2, problems: 0, resolved: 0, avgTime: '35分钟', completeRate: 100, resolveRate: 100, timelyRate: 100 }
};
statData.value = mockData[range];
};
// 12. 方法:切换时间范围
const handleTimeRangeChange = (range) => {
timeRange.value = range;
updateStatData(range);
};
// 13. 方法:分页变化
const handlePageChange = (page) => {
currentPage.value = page;
// 实际应用中加载对应页数据
};
const handleSizeChange = (size) => {
pageSize.value = size;
currentPage.value = 1;
// 实际应用中重新加载数据
};
// 14. 方法:获取计划状态文本
const getStatusText = (status) => {
const statusMap = {
'drafted': '已起草',
'in-progress': '进行中',
'completed': '已完成',
'paused': '暂停'
};
return statusMap[status] || '';
};
// 15. 方法:获取任务状态文本
const getTaskStatusText = (status) => {
const statusMap = {
'pending': '待接受',
'accepted': '处理中',
'completed': '已完成',
'rejected': '已拒绝'
};
return statusMap[status] || '';
};
// 16. 方法:获取记录状态文本
const getRecordStatusText = (status) => {
const statusMap = {
'normal': '正常',
'attention': '需关注',
'problem': '有问题'
};
return statusMap[status] || '';
};
// 17. 方法:获取进度条颜色
const getProgressColor = (status) => {
const colorMap = { 'drafted': '#ccc', 'in-progress': '#3b82f6', 'completed': '#10b981', 'paused': '#9e9e9e' };
return colorMap[status] || '#ccc';
};
// 18. 新增实验记录弹窗相关
const showRecordDialog = ref(false);
// 表单数据
const formData = ref({
materialName: '',
testNumber: 'EX-20240601',
refNumber: '',
startDate: '',
endDate: '',
appNumber: '',
appProject: '',
testPurpose: '',
envRequirements: '',
manager: '',
participants: '张明、李华、王强、刘洋、赵云',
steps: [{ content: '' }, { content: '' }, { content: '' }],
equipments: [
{ name: '服务器(型号:XYZ-9000)', selected: false },
{ name: '网络测试仪(型号:NT-5000)', selected: false },
{ name: '温度控制系统', selected: false },
{ name: '负载生成工具', selected: false }
],
riskMitigation: ''
});
// 新设备输入
const newEquipment = ref('');
// 关闭弹窗
const handleClose = () => {
showRecordDialog.value = false;
};
// 保存实验记录
const handleSave = () => {
// 实际应用中这里应该有表单验证和提交逻辑
console.log('保存实验记录:', formData.value);
showRecordDialog.value = false;
// 保存成功后可以显示提示信息
};
// 添加新步骤
const addStep = () => {
formData.value.steps.push({ content: '' });
};
// 添加新设备
const addEquipment = () => {
if (newEquipment.value.trim()) {
formData.value.equipments.push({ name: newEquipment.value.trim(), selected: false });
newEquipment.value = '';
}
};
</script>
<style scoped>
/* 1. 基础容器样式(继承试验系统) */
.operation-inspection {
padding: 20px;
background-color: #f9fbfd;
min-height: 100vh;
}
/* 2. 顶部导航选项卡 */
.navigation-tabs {
display: flex;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
margin-bottom: 20px;
overflow: hidden;
}
.nav-tab {
padding: 12px 24px;
cursor: pointer;
transition: all 0.2s;
font-size: 14px;
color: #6b7280;
flex: 1;
text-align: center;
border-right: 1px solid #f0f0f0;
}
.nav-tab:last-child {
border-right: none;
}
.nav-tab:hover:not(.active) {
background-color: #f3f4f6;
}
.nav-tab.active {
background-color: #165dff;
color: #fff;
font-weight: 500;
}
/* 3. 页面标题(与试验系统一致) */
.page-header {
margin-bottom: 20px;
}
.page-title {
font-size: 20px;
font-weight: 600;
color: #1f2329;
margin: 0 0 5px 0;
}
.page-description {
font-size: 14px;
color: #6b7280;
margin: 0;
}
/* 4. 功能选项卡导航(与试验系统一致) */
.tabs-nav {
display: flex;
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 4px 4px 0 0;
overflow: hidden;
margin-bottom: -1px;
}
.tab-btn {
padding: 12px 24px;
background: none;
border: none;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.tab-btn.active {
background-color: #fff;
color: #165dff;
border-top: 2px solid #165dff;
font-weight: 500;
}
.tab-btn:not(.active) {
color: #6b7280;
background-color: #f9fafb;
}
.tab-btn:not(.active):hover {
background-color: #f3f4f6;
}
/* 5. 筛选和操作区域(与试验系统一致) */
.filter-and-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 0 0 4px 4px;
margin-bottom: 16px;
flex-wrap: wrap;
gap: 12px;
}
.filters {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.action-buttons {
display: flex;
gap: 12px;
}
.el-select,
.date-picker {
width: 160px;
}
.search-btn,
.export-btn,
.create-btn {
background-color: #165dff;
border-color: #165dff;
}
/* 6. 表格容器(与试验系统一致) */
.table-container {
background-color: #fff;
border-radius: 4px;
border: 1px solid #e5e7eb;
margin-bottom: 16px;
}
.el-table {
width: 100%;
}
.el-table th {
background-color: #f9fafb;
font-weight: 500;
color: #4b5563;
}
.plan-name {
white-space: pre-line;
}
/* 7. 进度条样式(与试验系统一致) */
.progress-bar {
height: 8px;
background-color: #f3f4f6;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
transition: width 0.3s ease;
}
/* 8. 状态标签样式(扩展试验系统) */
.status-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-drafted {
background-color: #e0efff;
color: #165dff;
}
.status-in-progress {
background-color: #e0f2fe;
color: #0284c7;
}
.status-completed {
background-color: #e6ffed;
color: #00b42a;
}
.status-paused {
background-color: #f2f3f5;
color: #86909c;
}
.status-pending {
background-color: #f9fafb;
color: #6b7280;
}
.status-accepted {
background-color: #eff6ff;
color: #2563eb;
}
.status-rejected {
background-color: #fee2e2;
color: #dc2626;
}
.status-normal {
background-color: #e6ffed;
color: #00b42a;
}
.status-attention {
background-color: #fff7e0;
color: #ff7d00;
}
.status-problem {
background-color: #fff2f0;
color: #f5222d;
}
/* 9. 操作按钮样式(扩展试验系统) */
.operation-buttons {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.operate-btn {
padding: 2px 8px;
font-size: 12px;
border-radius: 4px;
cursor: pointer;
border: none;
background: none;
transition: all 0.2s;
}
.edit-btn {
color: #165dff;
}
.edit-btn:hover {
background-color: #e8f3ff;
}
.execute-btn {
color: #00b42a;
}
.execute-btn:hover {
background-color: #e6ffed;
}
.pause-btn {
color: #ff7d00;
}
.pause-btn:hover {
background-color: #fff7e0;
}
.resume-btn {
color: #722ed1;
}
.resume-btn:hover {
background-color: #f3e8ff;
}
.view-btn {
color: #165dff;
}
.view-btn:hover {
background-color: #e8f3ff;
}
.complete-btn {
color: #00b42a;
}
.complete-btn:hover {
background-color: #e6ffed;
}
.accept-btn {
color: #2563eb;
}
.accept-btn:hover {
background-color: #eff6ff;
}
.report-btn {
color: #ff7d00;
}
.report-btn:hover {
background-color: #fff7e0;
}
/* 10. 分页样式(与试验系统一致) */
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #e5e7eb;
}
.total-records {
font-size: 14px;
color: #6b7280;
margin: 0;
}
.el-pagination {
--el-pagination-item-active-bg-color: #165dff;
}
/* 11. 记录页面容器 */
.record-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.main-content-container {
display: flex;
gap: 16px;
height: calc(100% - 20px);
}
.left-content {
flex: 2;
display: flex;
flex-direction: column;
}
.right-content {
flex: 1;
display: flex;
flex-direction: column;
}
/* 12. 内容卡片样式 */
.content-card {
background-color: #fff;
border-radius: 4px;
border: 1px solid #e5e7eb;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
}
.card-header {
padding: 16px;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
font-size: 16px;
font-weight: 500;
color: #1f2329;
margin: 0;
}
.card-body {
padding: 16px;
flex: 1;
overflow-y: auto;
}
/* 13. 时间范围按钮 */
.time-range-buttons {
display: flex;
gap: 8px;
}
.time-btn {
padding: 4px 8px;
font-size: 12px;
border-radius: 4px;
border: 1px solid #e5e7eb;
background-color: #f9fafb;
color: #6b7280;
cursor: pointer;
transition: all 0.2s;
}
.time-btn.active {
background-color: #165dff;
color: #fff;
border-color: #165dff;
}
/* 14. 统计卡片样式 */
.stat-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 16px;
}
.stat-card {
background-color: #f9fafb;
border-radius: 4px;
padding: 12px;
}
.stat-label {
font-size: 14px;
color: #6b7280;
margin: 0 0 8px 0;
}
.stat-value {
font-size: 20px;
font-weight: 600;
color: #1f2329;
margin: 0;
}
/* 15. 分隔线 */
.divider {
height: 1px;
background-color: #e5e7eb;
margin: 16px 0;
}
/* 16. 图表容器 */
.chart-container {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
flex-wrap: wrap;
}
.pie-chart {
flex: 1;
min-width: 200px;
display: flex;
flex-direction: column;
align-items: center;
}
.chart-title {
font-size: 14px;
color: #6b7280;
margin: 0 0 8px 0;
align-self: flex-start;
}
.pie-wrapper {
position: relative;
width: 180px;
height: 180px;
}
.pie-center {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.pie-legend {
display: flex;
gap: 16px;
margin-top: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #6b7280;
}
.legend-color {
width: 8px;
height: 8px;
border-radius: 50%;
}
.legend-color.resolved {
background-color: #10b981;
}
.legend-color.unresolved {
background-color: #f97316;
}
/* 17. 进度条组 */
.progress-bars {
flex: 2;
min-width: 250px;
display: flex;
flex-direction: column;
gap: 12px;
}
.progress-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.progress-header {
display: flex;
justify-content: space-between;
font-size: 12px;
}
.progress-label {
color: #6b7280;
}
.progress-value {
font-weight: 500;
color: #1f2329;
}
/* 18. 问题分类 */
.problem-category {
margin-bottom: 8px;
}
.section-title {
font-size: 14px;
font-weight: 500;
color: #1f2329;
margin: 0 0 12px 0;
}
.category-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.category-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.category-header {
display: flex;
justify-content: space-between;
font-size: 12px;
}
.category-label {
color: #6b7280;
}
.category-value {
color: #1f2329;
}
/* 19. 最近巡检记录列表 */
.record-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.inspection-record {
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 12px;
background-color: #f9fafb;
}
.record-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.record-title {
font-size: 14px;
font-weight: 500;
color: #1f2329;
margin: 0;
}
.record-meta {
font-size: 12px;
color: #6b7280;
margin: 0 0 8px 0;
}
.record-summary {
font-size: 12px;
color: #1f2329;
margin: 0 0 8px 0;
line-height: 1.5;
}
.record-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
/* 20. 响应式适配 */
@media (max-width: 1200px) {
.main-content-container {
flex-direction: column;
}
.stat-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.filters {
flex-direction: column;
align-items: stretch;
}
.el-select,
.date-picker {
width: 100%;
}
.action-buttons {
width: 100%;
justify-content: space-between;
}
.navigation-tabs {
flex-wrap: wrap;
}
.nav-tab {
flex: 1 1 auto;
min-width: 100px;
}
}
/* 选项卡样式 */
.tabs-wrapper {
background-color: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
/* 筛选栏样式 */
.filter-bar {
background-color: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.filter-item {
flex-shrink: 0;
}
.filter-bar .el-select,
.filter-bar .el-date-picker {
width: 150px;
height: 36px;
}
.filter-actions {
margin-left: auto;
display: flex;
gap: 10px;
}
/* 新增实验记录弹窗样式 */
.custom-experiment-dialog {
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
overflow: hidden;
}
.custom-experiment-dialog .el-dialog__header {
background-color: #f8f9fa;
border-bottom: 1px solid #e9ecef;
padding: 20px 24px;
}
.custom-experiment-dialog .el-dialog__title {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
}
.custom-experiment-dialog .el-dialog__body {
padding: 24px;
overflow: visible;
}
.form-container {
padding: 0;
}
.form-row {
display: flex;
gap: 24px;
margin-bottom: 20px;
}
.form-item {
flex: 1;
margin-bottom: 20px !important;
}
.el-form-item__label {
font-size: 14px;
font-weight: 500;
color: #495057;
padding-right: 12px;
}
.form-input {
width: 100%;
border-radius: 6px;
transition: all 0.3s ease;
}
.form-input:focus {
border-color: #165dff;
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
}
/* 试验步骤样式 */
.steps-container {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
width: 100%;
}
.step-item {
display: flex;
align-items: center;
margin-bottom: 16px;
width: 100%;
}
.step-item:last-child {
margin-bottom: 0;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #165dff;
color: white;
font-size: 16px;
font-weight: 600;
margin-right: 16px;
flex-shrink: 0;
}
.step-input:focus {
border-color: #165dff;
outline: none;
}
.add-step-btn {
color: #165dff;
margin-top: 12px;
width: 100%;
text-align: center;
font-size: 14px;
}
/* 设备列表样式 */
.equipment-list {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
width: 100%;
}
.equipment-item {
margin-bottom: 12px;
display: flex;
align-items: center;
padding: 6px 0;
}
.equipment-item:last-child {
margin-bottom: 0;
}
.el-checkbox__label {
font-size: 14px;
color: #495057;
margin-left: 8px;
}
.add-equipment {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
width: 100%;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 16px 24px;
background-color: #f8f9fa;
border-top: 1px solid #e9ecef;
}
.dialog-footer .el-button {
padding: 8px 20px;
border-radius: 6px;
font-size: 14px;
}
.dialog-footer .el-button--primary {
background-color: #165dff;
border-color: #165dff;
}
.dialog-footer .el-button--primary:hover {
background-color: #0d47a1;
border-color: #0d47a1;
}
/* 响应式设计 */
@media (max-width: 768px) {
.custom-experiment-dialog {
width: 90% !important;
margin: 0 auto;
}
.form-row {
flex-direction: column;
gap: 0;
}
.add-equipment {
flex-direction: column;
align-items: stretch;
}
.new-equipment-input {
width: 100%;
}
}
</style>