2025-09-17 15:53:38 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 选项卡和按钮组合 -->
|
|
|
|
|
|
<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>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="action-buttons">
|
2025-09-23 20:36:47 +08:00
|
|
|
|
<el-button type="primary" icon="Search" class="search-btn"> 搜索 </el-button>
|
|
|
|
|
|
<el-button type="primary" icon="Plus" class="create-btn" @click="openRecordDialog"> <i class="fas fa-plus"></i> 新增实验记录 </el-button>
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 5. 主内容区(根据选中选项卡切换内容) -->
|
|
|
|
|
|
<div class="content-container">
|
|
|
|
|
|
<!-- 5.1 巡检计划表格(与试验系统表格结构一致) -->
|
|
|
|
|
|
<div v-if="activeTab === 'plan'" class="table-container">
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-table :data="planTableData" border v-loading="loading">
|
2025-09-18 19:56:24 +08:00
|
|
|
|
<el-table-column align="center" prop="name" label="计划名称" width="220">
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<div class="plan-name">{{ scope.row.name }}</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
2025-09-18 19:56:24 +08:00
|
|
|
|
<el-table-column align="center" prop="type" label="巡检类型" width="120"></el-table-column>
|
|
|
|
|
|
<el-table-column align="center" prop="cycle" label="巡检周期" width="120"></el-table-column>
|
|
|
|
|
|
<el-table-column align="center" prop="dateRange" label="执行时间范围"></el-table-column>
|
|
|
|
|
|
<el-table-column align="center" prop="progress" label="完成进度" width="120">
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<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>
|
2025-09-18 19:56:24 +08:00
|
|
|
|
<el-table-column align="center" prop="status" label="状态" width="100">
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<span :class="['status-tag', `status-${scope.row.status}`]">
|
|
|
|
|
|
{{ getStatusText(scope.row.status) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
2025-09-18 19:56:24 +08:00
|
|
|
|
<el-table-column align="center" prop="responsible" label="负责人" width="120"></el-table-column>
|
|
|
|
|
|
<el-table-column align="center" label="操作" width="220">
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<div class="operation-buttons">
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<!-- 草稿状态 -->
|
|
|
|
|
|
<template v-if="scope.row.status === 'drafted'">
|
|
|
|
|
|
<el-button type="text" class="operate-btn edit-btn" @click="handleEditRecord(scope.row)">编辑</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn submit-btn">提交审批</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn delete-btn">删除</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 已批准状态 -->
|
|
|
|
|
|
<template v-else-if="scope.row.status === 'approved'">
|
|
|
|
|
|
<el-button type="text" class="operate-btn edit-btn" @click="handleEditRecord(scope.row)">编辑</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn view-btn" @click="handleViewDetail(scope.row)">详情</el-button>
|
|
|
|
|
|
<!-- 当testPlanType为1时显示停用按钮 -->
|
|
|
|
|
|
<el-button v-if="scope.row.testPlanType === '1'" type="text" class="operate-btn stop-btn" @click="handleStart(scope.row, '2')">
|
|
|
|
|
|
停用
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<!-- 当testPlanType不存在或为2时显示启用按钮 -->
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-else-if="!scope.row.testPlanType || scope.row.testPlanType === '2'"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="operate-btn start-btn"
|
|
|
|
|
|
@click="handleStart(scope.row, '1')"
|
|
|
|
|
|
>
|
|
|
|
|
|
启用
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 进行中状态 -->
|
|
|
|
|
|
<template v-else-if="scope.row.status === 'in-progress'">
|
|
|
|
|
|
<el-button type="text" class="operate-btn edit-btn" @click="handleEditRecord(scope.row)">编辑</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn view-btn" @click="handleViewDetail(scope.row)">详情</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn track-btn">跟踪</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 已完成状态 -->
|
|
|
|
|
|
<template v-else-if="scope.row.status === 'completed'">
|
|
|
|
|
|
<el-button type="text" class="operate-btn edit-btn" @click="handleEditRecord(scope.row)">编辑</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn view-btn" @click="handleViewDetail(scope.row)">详情</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn report-btn">查看报告</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 默认显示详情 -->
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-button type="text" class="operate-btn view-btn" @click="handleViewDetail(scope.row)">详情</el-button>
|
|
|
|
|
|
</template>
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</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">
|
2025-09-18 19:56:24 +08:00
|
|
|
|
<el-button type="text" class="operate-btn accept-btn" v-if="scope.row.status === 'pending'">接受</el-button>
|
|
|
|
|
|
<el-button type="text" class="operate-btn complete-btn" v-if="scope.row.status === 'accepted'">完成</el-button>
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-button type="text" class="operate-btn view-btn" @click="handleViewDetail(scope.row)">查看详情</el-button>
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</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>
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<!-- 分页 -->
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<div class="pagination" v-if="activeTab !== 'record'">
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<p class="total-records">
|
|
|
|
|
|
显示第{{ (currentPage - 1) * pageSize + 1 }}到{{ Math.min(currentPage * pageSize, totalRecords) }}条,共{{ totalRecords }}条记录
|
|
|
|
|
|
</p>
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<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">
|
|
|
|
|
|
<!-- 材料名称 -->
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-form-item label="计划名称" class="form-item">
|
|
|
|
|
|
<el-input v-model="formData.planName" placeholder="请输入计划名称" class="form-input" />
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</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">
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-form-item label="计划编号" class="form-item">
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<el-input v-model="formData.testNumber" placeholder="EX-20240601" class="form-input" />
|
|
|
|
|
|
</el-form-item>
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-form-item label="实验对象类型" class="form-item">
|
|
|
|
|
|
<el-select v-model="formData.testObject" placeholder="请选择实验对象类型" class="form-input">
|
|
|
|
|
|
<el-option label="1安全试验" value="1" />
|
|
|
|
|
|
<el-option label="2网络实验" value="2" />
|
|
|
|
|
|
<el-option label="3性能试验" value="3" />
|
|
|
|
|
|
<el-option label="4" value="4" />
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 试验目的 -->
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-form-item label="试验目的与预期" class="form-item">
|
2025-09-17 15:53:38 +08:00
|
|
|
|
<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">
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-option v-for="user in userList" :key="user.value" :label="user.label" :value="user.value" />
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 参与人员 -->
|
|
|
|
|
|
<el-form-item label="参与人员" class="form-item">
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-select v-model="formData.participants" placeholder="请选择参与人员" class="form-input" multiple collapse-tags>
|
|
|
|
|
|
<el-option v-for="user in userList" :key="user.value" :label="user.label" :value="user.value" />
|
|
|
|
|
|
</el-select>
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</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>
|
2025-09-22 15:42:13 +08:00
|
|
|
|
<el-button type="primary" @click="handleSave" :loading="saveLoading">保存</el-button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 详情弹窗 -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="showDetailDialog"
|
|
|
|
|
|
title="实验记录详情"
|
|
|
|
|
|
width="800px"
|
|
|
|
|
|
:loading="detailLoading"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
:close-on-press-escape="false"
|
|
|
|
|
|
class="custom-experiment-dialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="detail-content">
|
|
|
|
|
|
<!-- 基础信息 -->
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
<h3 class="section-title">基础信息</h3>
|
|
|
|
|
|
<div class="detail-grid">
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<label class="detail-label">计划名称:</label>
|
|
|
|
|
|
<span class="detail-value">{{ detailData.planName || '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<label class="detail-label">计划编号:</label>
|
|
|
|
|
|
<span class="detail-value">{{ detailData.planCode || '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<label class="detail-label">实验对象:</label>
|
|
|
|
|
|
<span class="detail-value">{{ getTestObjectText(detailData.testObject) || '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<label class="detail-label">负责人:</label>
|
|
|
|
|
|
<span class="detail-value">{{ detailData.person?.userName || '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<label class="detail-label">开始时间:</label>
|
|
|
|
|
|
<span class="detail-value">{{ detailData.beginTime ? formatDate(detailData.beginTime) : '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-item">
|
|
|
|
|
|
<label class="detail-label">结束时间:</label>
|
|
|
|
|
|
<span class="detail-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 实验设备 -->
|
|
|
|
|
|
<div v-if="detailData.testDevice" class="detail-section">
|
|
|
|
|
|
<h3 class="section-title">实验设备</h3>
|
|
|
|
|
|
<div class="device-list">
|
|
|
|
|
|
<span v-for="(device, index) in detailData.testDevice.split(',')" :key="index" class="device-tag">
|
|
|
|
|
|
{{ device.trim() }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 实验步骤 -->
|
|
|
|
|
|
<div v-if="detailData.testStep" class="detail-section">
|
|
|
|
|
|
<h3 class="section-title">实验步骤</h3>
|
|
|
|
|
|
<div class="steps-container">
|
|
|
|
|
|
<div v-for="(step, index) in detailData.testStep.split(',')" :key="index" class="step-item">
|
|
|
|
|
|
<div class="step-number">{{ index + 1 }}</div>
|
|
|
|
|
|
<div class="step-content">{{ step.trim() }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 实验信息 -->
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
<h3 class="section-title">实验信息</h3>
|
|
|
|
|
|
<div class="detail-textarea">
|
|
|
|
|
|
<label class="detail-label">实验说明:</label>
|
|
|
|
|
|
<div class="detail-text">{{ detailData.testInfo || '-' }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-textarea">
|
|
|
|
|
|
<label class="detail-label">实验设置:</label>
|
|
|
|
|
|
<div class="detail-text">{{ detailData.testSetting || '-' }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-textarea">
|
|
|
|
|
|
<label class="detail-label">解决方案:</label>
|
|
|
|
|
|
<div class="detail-text">{{ detailData.testSolutions || '-' }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 参与人员 -->
|
|
|
|
|
|
<div v-if="detailData.persons && detailData.persons.length > 0" class="detail-section">
|
|
|
|
|
|
<h3 class="section-title">参与人员</h3>
|
|
|
|
|
|
<div class="participant-list">
|
|
|
|
|
|
<div v-for="(person, index) in detailData.persons" :key="person.id" class="participant-item">
|
|
|
|
|
|
<span class="participant-name">{{ person.userName }}</span>
|
|
|
|
|
|
<span class="participant-team">{{ person.teamName }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 巡检项目 -->
|
|
|
|
|
|
<div v-if="detailData.inspectionItemList && detailData.inspectionItemList.length > 0" class="detail-section">
|
|
|
|
|
|
<h3 class="section-title">巡检项目</h3>
|
|
|
|
|
|
<div class="inspection-list">
|
|
|
|
|
|
<div v-for="(item, index) in detailData.inspectionItemList" :key="item.id" class="inspection-item">
|
|
|
|
|
|
<span class="inspection-name">{{ item.name }}</span>
|
|
|
|
|
|
<span class="inspection-type">{{ item.type }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
|
<el-button @click="showDetailDialog = false">关闭</el-button>
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-09-22 15:42:13 +08:00
|
|
|
|
import { ref, onMounted } from 'vue';
|
2025-09-17 15:53:38 +08:00
|
|
|
|
import router from '@/router';
|
2025-09-22 15:42:13 +08:00
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
|
|
import { shiyanDetail, shiyanlist, addshiyan, updateshiyan } from '@/api/zhinengxunjian/shiyan/index';
|
|
|
|
|
|
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian/index';
|
2025-09-17 15:53:38 +08:00
|
|
|
|
// 1. 选项卡状态管理
|
|
|
|
|
|
const activeTab = ref('plan'); // 默认为"巡检计划"
|
|
|
|
|
|
const timeRange = ref('month'); // 统计时间范围:月/周/日
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 筛选条件
|
|
|
|
|
|
const filterStatus = ref('all');
|
|
|
|
|
|
const filterType = ref('all');
|
|
|
|
|
|
const dateRange = ref([]);
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 分页参数
|
2025-09-17 15:53:38 +08:00
|
|
|
|
const currentPage = ref(1);
|
|
|
|
|
|
const pageSize = ref(20);
|
2025-09-22 15:42:13 +08:00
|
|
|
|
const totalRecords = ref(0);
|
2025-09-17 15:53:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 4. 巡检计划表格数据
|
2025-09-22 15:42:13 +08:00
|
|
|
|
const planTableData = ref([]);
|
|
|
|
|
|
const loading = ref(false); // 加载状态
|
|
|
|
|
|
|
|
|
|
|
|
const fetchExperimentData = async () => {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const queryParams = {
|
|
|
|
|
|
projectId: 1,
|
|
|
|
|
|
pageSize: pageSize.value,
|
|
|
|
|
|
pageNum: currentPage.value
|
|
|
|
|
|
// 其他参数...
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const response = await shiyanlist(queryParams);
|
|
|
|
|
|
if (response && response.code === 200) {
|
|
|
|
|
|
planTableData.value = (response.rows || []).map((item) => ({
|
|
|
|
|
|
// 关键:将ID转为字符串存储,避免大整数精度丢失
|
|
|
|
|
|
id: String(item.id), // 强制转为字符串
|
|
|
|
|
|
name: `${item.planName}\n编号: ${item.planCode}`,
|
|
|
|
|
|
type: getTestObjectText(item.testObject),
|
|
|
|
|
|
cycle: item.cycle || '每月',
|
|
|
|
|
|
dateRange: `${item.beginTime || ''} 至 ${item.endTime || ''}`,
|
|
|
|
|
|
progress: calculateProgress(item.testStatus),
|
|
|
|
|
|
status: mapStatus(item.testStatus),
|
|
|
|
|
|
responsible: item.person?.userName || '未知负责人',
|
|
|
|
|
|
// 保留testPlanType字段用于显示启用/停用按钮
|
|
|
|
|
|
testPlanType: item.testPlanType
|
|
|
|
|
|
}));
|
|
|
|
|
|
totalRecords.value = response.total || 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取实验数据失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
2025-09-22 15:42:13 +08:00
|
|
|
|
};
|
2025-09-17 15:53:38 +08:00
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 辅助方法
|
|
|
|
|
|
const getTestObjectText = (type) => {
|
|
|
|
|
|
const typeMap = {
|
|
|
|
|
|
'1': '安全试验',
|
|
|
|
|
|
'2': '网络实验',
|
|
|
|
|
|
'3': '性能试验',
|
|
|
|
|
|
'4': '其他试验'
|
|
|
|
|
|
};
|
|
|
|
|
|
return typeMap[type] || '未知类型';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const calculateProgress = (status) => {
|
|
|
|
|
|
const progressMap = {
|
|
|
|
|
|
'1': 30, // 已批准
|
|
|
|
|
|
'2': 60, // 进行中
|
|
|
|
|
|
'3': 100, // 已完成
|
|
|
|
|
|
'4': 0 // 未通过
|
|
|
|
|
|
};
|
|
|
|
|
|
return progressMap[status] || 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const mapStatus = (status) => {
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
'1': 'approved', // 已批准
|
|
|
|
|
|
'2': 'in-progress', // 进行中
|
|
|
|
|
|
'3': 'completed', // 已完成
|
|
|
|
|
|
'4': 'rejected', // 未通过
|
|
|
|
|
|
'5': 'drafted' // 草稿
|
|
|
|
|
|
};
|
|
|
|
|
|
return statusMap[status] || 'drafted';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载时获取数据
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
fetchExperimentData();
|
|
|
|
|
|
getUsersList(); // 加载用户列表
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 巡检任务表格数据
|
|
|
|
|
|
const taskTableData = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 统计数据
|
2025-09-17 15:53:38 +08:00
|
|
|
|
const statData = ref({
|
2025-09-22 15:42:13 +08:00
|
|
|
|
completed: 0,
|
|
|
|
|
|
problems: 0,
|
|
|
|
|
|
resolved: 0,
|
|
|
|
|
|
avgTime: '0分钟',
|
|
|
|
|
|
completeRate: 0,
|
|
|
|
|
|
resolveRate: 0,
|
|
|
|
|
|
timelyRate: 0
|
2025-09-17 15:53:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 问题类型分布
|
|
|
|
|
|
const problemCategories = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 最近巡检记录
|
|
|
|
|
|
const recentRecords = ref([]);
|
2025-09-17 15:53:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 切换时间范围
|
2025-09-17 15:53:38 +08:00
|
|
|
|
const handleTimeRangeChange = (range) => {
|
|
|
|
|
|
timeRange.value = range;
|
|
|
|
|
|
updateStatData(range);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 分页变化处理
|
2025-09-17 15:53:38 +08:00
|
|
|
|
const handlePageChange = (page) => {
|
|
|
|
|
|
currentPage.value = page;
|
2025-09-22 15:42:13 +08:00
|
|
|
|
fetchExperimentData();
|
2025-09-17 15:53:38 +08:00
|
|
|
|
};
|
2025-09-22 15:42:13 +08:00
|
|
|
|
|
2025-09-17 15:53:38 +08:00
|
|
|
|
const handleSizeChange = (size) => {
|
|
|
|
|
|
pageSize.value = size;
|
|
|
|
|
|
currentPage.value = 1;
|
2025-09-22 15:42:13 +08:00
|
|
|
|
fetchExperimentData();
|
2025-09-17 15:53:38 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 状态文本映射
|
2025-09-17 15:53:38 +08:00
|
|
|
|
const getStatusText = (status) => {
|
|
|
|
|
|
const statusMap = {
|
2025-09-22 15:42:13 +08:00
|
|
|
|
'drafted': '草稿',
|
|
|
|
|
|
'approved': '已批准',
|
2025-09-17 15:53:38 +08:00
|
|
|
|
'in-progress': '进行中',
|
|
|
|
|
|
'completed': '已完成',
|
2025-09-22 15:42:13 +08:00
|
|
|
|
'rejected': '未通过'
|
2025-09-17 15:53:38 +08:00
|
|
|
|
};
|
|
|
|
|
|
return statusMap[status] || '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getTaskStatusText = (status) => {
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
'pending': '待接受',
|
|
|
|
|
|
'accepted': '处理中',
|
|
|
|
|
|
'completed': '已完成',
|
|
|
|
|
|
'rejected': '已拒绝'
|
|
|
|
|
|
};
|
|
|
|
|
|
return statusMap[status] || '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getRecordStatusText = (status) => {
|
|
|
|
|
|
const statusMap = {
|
|
|
|
|
|
'normal': '正常',
|
|
|
|
|
|
'attention': '需关注',
|
|
|
|
|
|
'problem': '有问题'
|
|
|
|
|
|
};
|
|
|
|
|
|
return statusMap[status] || '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 进度条颜色
|
2025-09-17 15:53:38 +08:00
|
|
|
|
const getProgressColor = (status) => {
|
|
|
|
|
|
const colorMap = { 'drafted': '#ccc', 'in-progress': '#3b82f6', 'completed': '#10b981', 'paused': '#9e9e9e' };
|
|
|
|
|
|
return colorMap[status] || '#ccc';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 18. 新增实验记录弹窗相关
|
|
|
|
|
|
const showRecordDialog = ref(false);
|
2025-09-22 15:42:13 +08:00
|
|
|
|
const saveLoading = ref(false); // 保存加载状态
|
2025-09-17 15:53:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
const formData = ref({
|
2025-09-22 15:42:13 +08:00
|
|
|
|
planName: '',
|
2025-09-17 15:53:38 +08:00
|
|
|
|
testNumber: 'EX-20240601',
|
|
|
|
|
|
refNumber: '',
|
|
|
|
|
|
startDate: '',
|
|
|
|
|
|
endDate: '',
|
2025-09-22 15:42:13 +08:00
|
|
|
|
|
2025-09-17 15:53:38 +08:00
|
|
|
|
testPurpose: '',
|
|
|
|
|
|
envRequirements: '',
|
|
|
|
|
|
manager: '',
|
2025-09-22 15:42:13 +08:00
|
|
|
|
participants: [], // 改为数组存储多选的用户ID
|
2025-09-17 15:53:38 +08:00
|
|
|
|
steps: [{ content: '' }, { content: '' }, { content: '' }],
|
|
|
|
|
|
equipments: [
|
|
|
|
|
|
{ name: '服务器(型号:XYZ-9000)', selected: false },
|
|
|
|
|
|
{ name: '网络测试仪(型号:NT-5000)', selected: false },
|
|
|
|
|
|
{ name: '温度控制系统', selected: false },
|
|
|
|
|
|
{ name: '负载生成工具', selected: false }
|
|
|
|
|
|
],
|
|
|
|
|
|
riskMitigation: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 当前编辑的记录ID
|
|
|
|
|
|
const editRecordId = ref(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 选中的用户列表(用于显示为多选框)
|
|
|
|
|
|
const selectedUsers = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 用户选择弹窗
|
|
|
|
|
|
const showUserSelectDialog = ref(false);
|
|
|
|
|
|
const availableUsers = ref([]);
|
|
|
|
|
|
const selectedUserIds = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 用户列表(用于负责人和参与人员选择)
|
|
|
|
|
|
const userList = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户列表
|
|
|
|
|
|
const getUsersList = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await xunjianUserlist();
|
|
|
|
|
|
const userRows =
|
|
|
|
|
|
response?.data?.rows && Array.isArray(response.data.rows)
|
|
|
|
|
|
? response.data.rows
|
|
|
|
|
|
: response?.rows && Array.isArray(response.rows)
|
|
|
|
|
|
? response.rows
|
|
|
|
|
|
: Array.isArray(response)
|
|
|
|
|
|
? response
|
|
|
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
|
|
userList.value = userRows
|
|
|
|
|
|
.filter((item) => item && typeof item === 'object')
|
|
|
|
|
|
.map((item, index) => ({
|
|
|
|
|
|
label: item.userName || `用户${index + 1}`,
|
|
|
|
|
|
value: item.id || `id_${index}`
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
if (userList.value.length === 0) {
|
|
|
|
|
|
userList.value = [{ label: '默认用户', value: 'default' }];
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户失败:', error);
|
|
|
|
|
|
userList.value = [{ label: '默认用户', value: 'default' }];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-17 15:53:38 +08:00
|
|
|
|
// 新设备输入
|
|
|
|
|
|
const newEquipment = ref('');
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭弹窗
|
|
|
|
|
|
const handleClose = () => {
|
|
|
|
|
|
showRecordDialog.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 3. 优化保存函数,修复testNumber生成逻辑(仅在提交时生成,不预先填充)
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
saveLoading.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 编辑模式:校验ID有效性(保持字符串ID,避免大整数问题)
|
|
|
|
|
|
if (editRecordId.value) {
|
|
|
|
|
|
// 仅校验ID存在性,不转换为数字
|
|
|
|
|
|
if (!editRecordId.value || typeof editRecordId.value !== 'string' || editRecordId.value.trim() === '') {
|
|
|
|
|
|
ElMessage.error('编辑记录ID无效,无法保存');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 表单基础校验(增强必填项校验)
|
|
|
|
|
|
const { planName } = formData.value;
|
|
|
|
|
|
if (!planName.trim()) {
|
|
|
|
|
|
ElMessage.warning('请填写材料名称');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 新增时自动生成试验编号(提交时才生成,不在输入框预先显示)
|
|
|
|
|
|
const finalTestNumber = editRecordId.value
|
|
|
|
|
|
? formData.value.testNumber // 编辑时保留原编号
|
|
|
|
|
|
: `EX-${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(
|
|
|
|
|
|
2,
|
|
|
|
|
|
'0'
|
|
|
|
|
|
)}-${Date.now().toString().slice(-4)}`;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建请求数据
|
|
|
|
|
|
const requestData = {
|
|
|
|
|
|
projectId: 1,
|
|
|
|
|
|
planName: finalTestNumber, // 使用最终生成的编号
|
|
|
|
|
|
planCode: finalTestNumber,
|
|
|
|
|
|
testObject: '3',
|
|
|
|
|
|
beginTime: formData.value.startDate ? new Date(formData.value.startDate).toISOString() : '',
|
|
|
|
|
|
endTime: formData.value.endDate ? new Date(formData.value.endDate).toISOString() : '',
|
|
|
|
|
|
testInfo: formData.value.testPurpose,
|
|
|
|
|
|
testSetting: formData.value.envRequirements,
|
|
|
|
|
|
personCharge: formData.value.manager,
|
|
|
|
|
|
personIds: formData.value.participants.join(','),
|
|
|
|
|
|
inspectionItems: '',
|
|
|
|
|
|
testSolutions: formData.value.riskMitigation,
|
|
|
|
|
|
testStep: formData.value.steps
|
|
|
|
|
|
.filter((step) => step.content.trim())
|
|
|
|
|
|
.map((step) => step.content)
|
|
|
|
|
|
.join(','),
|
|
|
|
|
|
testDevice: formData.value.equipments
|
|
|
|
|
|
.filter((equip) => equip.selected)
|
|
|
|
|
|
.map((equip) => equip.name)
|
|
|
|
|
|
.join(','),
|
|
|
|
|
|
testStatus: '1',
|
|
|
|
|
|
// 关键修复:编辑时添加主键ID(与后端更新接口参数名一致)
|
|
|
|
|
|
id: editRecordId.value // 若后端用planId等,需改为对应字段名
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 调用接口
|
|
|
|
|
|
let response;
|
|
|
|
|
|
if (editRecordId.value) {
|
|
|
|
|
|
// 编辑模式:调用更新接口
|
|
|
|
|
|
response = await updateshiyan(requestData);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 新增模式:调用添加接口(删除请求参数中的id,避免后端报错)
|
|
|
|
|
|
const { id, ...addData } = requestData;
|
|
|
|
|
|
response = await addshiyan(addData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response && response.code === 200) {
|
|
|
|
|
|
ElMessage.success(editRecordId.value ? '更新成功' : '新增成功');
|
|
|
|
|
|
showRecordDialog.value = false;
|
|
|
|
|
|
fetchExperimentData(); // 重新加载列表,刷新数据
|
|
|
|
|
|
resetForm(); // 重置表单和编辑状态
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(response?.msg || (editRecordId.value ? '更新失败' : '新增失败'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(editRecordId.value ? '更新异常:' : '新增异常:', error);
|
|
|
|
|
|
ElMessage.error('系统异常,请稍后重试');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saveLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
|
formData.value = {
|
|
|
|
|
|
planName: '', // 材料名称为空
|
|
|
|
|
|
testNumber: '', // 试验编号为空(原逻辑自动生成,改为新增时为空)
|
|
|
|
|
|
refNumber: '', // 参考号为空
|
|
|
|
|
|
startDate: '', // 开始日期为空
|
|
|
|
|
|
endDate: '', // 结束日期为空
|
|
|
|
|
|
|
|
|
|
|
|
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: '' // 风险识别为空
|
|
|
|
|
|
};
|
|
|
|
|
|
// 关键修复:重置编辑状态标识(清空编辑ID)
|
|
|
|
|
|
editRecordId.value = null;
|
|
|
|
|
|
selectedUsers.value = [];
|
|
|
|
|
|
showUserSelectDialog.value = false;
|
|
|
|
|
|
availableUsers.value = [];
|
|
|
|
|
|
selectedUserIds.value = [];
|
|
|
|
|
|
newEquipment.value = '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 打开新增弹窗时强制重置表单
|
|
|
|
|
|
const openRecordDialog = () => {
|
|
|
|
|
|
getUsersList();
|
|
|
|
|
|
resetForm(); // 确保打开时表单为空
|
|
|
|
|
|
showRecordDialog.value = true;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
// 详情弹窗相关
|
|
|
|
|
|
const showDetailDialog = ref(false);
|
|
|
|
|
|
const detailLoading = ref(false);
|
|
|
|
|
|
const detailData = ref({});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 修复查看详情:以字符串形式传递ID
|
|
|
|
|
|
const handleViewDetail = async (row) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('原始row数据:', row);
|
|
|
|
|
|
if (!row || !row.id) {
|
|
|
|
|
|
ElMessage.error('记录ID不存在,无法查看详情');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接使用字符串ID,不转为数字
|
|
|
|
|
|
const recordId = row.id;
|
|
|
|
|
|
console.log('请求详情的ID(字符串):', recordId, '类型:', typeof recordId);
|
|
|
|
|
|
|
|
|
|
|
|
// 调用接口时直接传递字符串ID
|
|
|
|
|
|
const response = await shiyanDetail(recordId);
|
|
|
|
|
|
|
|
|
|
|
|
if (response.code === 200 && response.data) {
|
|
|
|
|
|
Object.assign(detailData.value, response.data);
|
|
|
|
|
|
showDetailDialog.value = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(response.msg || '获取详情失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取详情失败:', error);
|
|
|
|
|
|
ElMessage.error(`获取记录详情失败: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleEditRecord = async (row) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 先校验row和id的有效性(保留之前的校验逻辑)
|
|
|
|
|
|
if (!row || !row.id) {
|
|
|
|
|
|
throw new Error('选中的记录不存在ID,无法编辑');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 关键修复:设置编辑记录ID(字符串格式,避免精度丢失)
|
|
|
|
|
|
editRecordId.value = row.id;
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 调用接口获取详情(使用字符串ID避免精度丢失)
|
|
|
|
|
|
const detailResponse = await shiyanDetail(row.id);
|
|
|
|
|
|
if (detailResponse.code !== 200) {
|
|
|
|
|
|
throw new Error(detailResponse.msg || '获取记录详情失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
const recordDetail = detailResponse.data.rows?.[0] || detailResponse.data;
|
|
|
|
|
|
// 兼容两种数据结构:可能在rows数组中,也可能直接在data中
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 处理testStep:将逗号分隔的字符串转换为步骤数组
|
|
|
|
|
|
const steps = [];
|
|
|
|
|
|
if (recordDetail.testStep) {
|
|
|
|
|
|
// 拆分字符串(例如 "1. 213,2. 321" → ["1. 213", "2. 321"])
|
|
|
|
|
|
const stepItems = recordDetail.testStep.split(',');
|
|
|
|
|
|
stepItems.forEach((stepText) => {
|
|
|
|
|
|
// 移除序号前缀(如"1. "),只保留内容
|
|
|
|
|
|
const content = stepText.replace(/^\d+\.\s*/, '').trim();
|
|
|
|
|
|
if (content) {
|
|
|
|
|
|
steps.push({ content });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 确保至少有3个步骤(如果解析后为空)
|
|
|
|
|
|
while (steps.length < 3) {
|
|
|
|
|
|
steps.push({ content: '' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 处理testDevice:将逗号分隔的字符串转换为设备数组
|
|
|
|
|
|
const equipments = [];
|
|
|
|
|
|
if (recordDetail.testDevice) {
|
|
|
|
|
|
const deviceNames = recordDetail.testDevice.split(',');
|
|
|
|
|
|
deviceNames.forEach((name) => {
|
|
|
|
|
|
const trimmedName = name.trim();
|
|
|
|
|
|
if (trimmedName) {
|
|
|
|
|
|
equipments.push({ name: trimmedName, selected: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 补充默认设备(未在记录中的设备默认不选中)
|
|
|
|
|
|
const defaultEquipments = [
|
|
|
|
|
|
{ name: '服务器(型号:XYZ-9000)', selected: false },
|
|
|
|
|
|
{ name: '网络测试仪(型号:NT-5000)', selected: false },
|
|
|
|
|
|
{ name: '温度控制系统', selected: false },
|
|
|
|
|
|
{ name: '负载生成工具', selected: false }
|
|
|
|
|
|
];
|
|
|
|
|
|
// 合并记录中的设备和默认设备(去重)
|
|
|
|
|
|
defaultEquipments.forEach((equip) => {
|
|
|
|
|
|
if (!equipments.some((e) => e.name === equip.name)) {
|
|
|
|
|
|
equipments.push(equip);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 处理参与人员(从personIds解析)
|
|
|
|
|
|
const participants = [];
|
|
|
|
|
|
if (recordDetail.personIds) {
|
|
|
|
|
|
// personIds是逗号分隔的用户ID字符串(如"1968950664352530437,123")
|
|
|
|
|
|
participants.push(...recordDetail.personIds.split(',').filter((id) => id.trim()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 填充表单数据(完整映射所有字段)
|
|
|
|
|
|
formData.value = {
|
|
|
|
|
|
planName: recordDetail.planName || '',
|
|
|
|
|
|
testNumber: recordDetail.testNumber || recordDetail.planCode || '',
|
|
|
|
|
|
refNumber: recordDetail.refNumber || '',
|
|
|
|
|
|
// 处理日期格式(确保与date-picker兼容)
|
|
|
|
|
|
startDate: recordDetail.startDate || recordDetail.beginTime?.split(' ')[0] || '',
|
|
|
|
|
|
endDate: recordDetail.endDate || recordDetail.endTime?.split(' ')[0] || '',
|
|
|
|
|
|
|
|
|
|
|
|
testPurpose: recordDetail.testPurpose || recordDetail.testInfo || '',
|
|
|
|
|
|
envRequirements: recordDetail.envRequirements || recordDetail.testSetting || '',
|
|
|
|
|
|
manager: recordDetail.manager || recordDetail.personCharge || '',
|
|
|
|
|
|
participants: participants, // 从personIds解析的数组
|
|
|
|
|
|
steps: steps, // 解析后的步骤数组
|
|
|
|
|
|
equipments: equipments, // 解析并合并后的设备数组
|
|
|
|
|
|
riskMitigation: recordDetail.riskMitigation || recordDetail.testSolutions || ''
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 处理参与人员显示(匹配用户列表)
|
|
|
|
|
|
if (formData.value.participants.length > 0) {
|
|
|
|
|
|
selectedUsers.value = formData.value.participants.map((userId) => {
|
|
|
|
|
|
const user = userList.value.find((u) => u.value === userId);
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: userId,
|
|
|
|
|
|
userName: user ? user.label : `未知用户(${userId})`,
|
|
|
|
|
|
selected: true
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
selectedUsers.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 打开编辑弹窗
|
|
|
|
|
|
showRecordDialog.value = true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('编辑记录异常:', error);
|
|
|
|
|
|
ElMessage.error(`获取记录详情失败: ${error.message || '请稍后重试'}`);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-09-17 15:53:38 +08:00
|
|
|
|
// 添加新步骤
|
|
|
|
|
|
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 = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-09-22 15:42:13 +08:00
|
|
|
|
|
|
|
|
|
|
// 启动计划函数
|
|
|
|
|
|
// 处理启用/停用操作
|
|
|
|
|
|
const handleStart = async (row, actionType) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 检查记录ID的有效性
|
|
|
|
|
|
if (!row || !row.id) {
|
|
|
|
|
|
ElMessage.error('记录不存在或ID无效,无法操作');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 确定操作类型(1=启用,2=停用)
|
|
|
|
|
|
const isEnable = actionType === '1';
|
|
|
|
|
|
const operationText = isEnable ? '启用' : '停用';
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 显示确认对话框
|
|
|
|
|
|
await ElMessageBox.confirm(`确定要${operationText}该试验计划吗?`, '操作确认', {
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 获取完整的记录详情数据
|
|
|
|
|
|
const detailResponse = await shiyanDetail(row.id);
|
|
|
|
|
|
if (detailResponse.code !== 200 || !detailResponse.data) {
|
|
|
|
|
|
throw new Error(`获取记录详情失败,无法${operationText}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const recordDetail = detailResponse.data.rows?.[0] || detailResponse.data;
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 构建完整的请求数据,包含所有必要字段
|
|
|
|
|
|
const requestData = {
|
|
|
|
|
|
id: recordDetail.id, // 确保包含ID
|
|
|
|
|
|
projectId: recordDetail.projectId || 1,
|
|
|
|
|
|
planName: recordDetail.planName || '',
|
|
|
|
|
|
planCode: recordDetail.planCode || '',
|
|
|
|
|
|
testObject: recordDetail.testObject || '3',
|
|
|
|
|
|
beginTime: recordDetail.beginTime || '',
|
|
|
|
|
|
endTime: recordDetail.endTime || '',
|
|
|
|
|
|
testInfo: recordDetail.testInfo || '',
|
|
|
|
|
|
testSetting: recordDetail.testSetting || '',
|
|
|
|
|
|
personCharge: recordDetail.personCharge || '',
|
|
|
|
|
|
personIds: recordDetail.personIds || '',
|
|
|
|
|
|
inspectionItems: recordDetail.inspectionItems || '',
|
|
|
|
|
|
testSolutions: recordDetail.testSolutions || '',
|
|
|
|
|
|
testStep: recordDetail.testStep || '',
|
|
|
|
|
|
testDevice: recordDetail.testDevice || '',
|
|
|
|
|
|
testStatus: recordDetail.testStatus || '1',
|
|
|
|
|
|
// 根据操作类型设置testPlanType的值(1=启用,2=停用)
|
|
|
|
|
|
testPlanType: actionType
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 调用接口,传递完整数据但只修改testPlanType
|
|
|
|
|
|
const response = await updateshiyan(requestData);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 处理接口响应
|
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
|
// 6.1 提示操作成功
|
|
|
|
|
|
ElMessage.success(`已成功${operationText}该试验计划`);
|
|
|
|
|
|
|
|
|
|
|
|
// 6.2 刷新数据列表
|
|
|
|
|
|
fetchExperimentData();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 6.3 接口返回失败时提示
|
|
|
|
|
|
ElMessage.error(`${operationText}失败:${response.msg || '服务器异常'}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 7. 捕获网络或代码错误,用户取消操作时也会进入这里
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
// 排除用户主动取消的情况
|
|
|
|
|
|
console.error('启动操作失败:', error);
|
|
|
|
|
|
ElMessage.error(`操作失败:${error.message || '请检查网络或重试'}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 日期格式化函数
|
|
|
|
|
|
const formatDate = (dateString) => {
|
|
|
|
|
|
if (!dateString) return '';
|
|
|
|
|
|
const date = new Date(dateString);
|
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
|
|
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
|
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
|
|
|
};
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
/* 1. 基础容器样式(继承试验系统) */
|
|
|
|
|
|
.operation-inspection {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background-color: #f9fbfd;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
}
|
|
|
|
|
|
.navigation-tabs {
|
|
|
|
|
|
display: flex;
|
2025-09-19 20:28:31 +08:00
|
|
|
|
margin-bottom: 20px;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
2025-09-19 20:28:31 +08:00
|
|
|
|
padding: 2px;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
2025-09-19 20:28:31 +08:00
|
|
|
|
|
2025-09-17 15:53:38 +08:00
|
|
|
|
.nav-tab {
|
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
|
cursor: pointer;
|
2025-09-19 20:28:31 +08:00
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
border-radius: 4px;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
font-size: 14px;
|
2025-09-19 20:28:31 +08:00
|
|
|
|
color: #606266;
|
|
|
|
|
|
border-right: 1px solid #f0f0f0;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
flex: 1;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
2025-09-19 20:28:31 +08:00
|
|
|
|
|
2025-09-17 15:53:38 +08:00
|
|
|
|
.nav-tab:last-child {
|
|
|
|
|
|
border-right: none;
|
|
|
|
|
|
}
|
2025-09-19 20:28:31 +08:00
|
|
|
|
|
|
|
|
|
|
.nav-tab:hover {
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
background-color: #ecf5ff;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
2025-09-19 20:28:31 +08:00
|
|
|
|
|
2025-09-17 15:53:38 +08:00
|
|
|
|
.nav-tab.active {
|
2025-09-19 20:28:31 +08:00
|
|
|
|
background-color: #409eff;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
color: #fff;
|
2025-09-19 20:28:31 +08:00
|
|
|
|
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 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 {
|
2025-09-22 15:42:13 +08:00
|
|
|
|
height: 36px;
|
|
|
|
|
|
border-radius: 4px;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-22 15:42:13 +08:00
|
|
|
|
/* 8. 状态标签样式(与其他页面保持一致) */
|
2025-09-17 15:53:38 +08:00
|
|
|
|
.status-tag {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.status-drafted {
|
2025-09-22 15:42:13 +08:00
|
|
|
|
background-color: #f2f3f5;
|
|
|
|
|
|
color: #86909c;
|
|
|
|
|
|
}
|
|
|
|
|
|
.status-approved {
|
|
|
|
|
|
background-color: #eff6ff;
|
|
|
|
|
|
color: #2563eb;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
.status-in-progress {
|
2025-09-22 15:42:13 +08:00
|
|
|
|
background-color: #fff7e0;
|
|
|
|
|
|
color: #ff7d00;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
.status-completed {
|
|
|
|
|
|
background-color: #e6ffed;
|
|
|
|
|
|
color: #00b42a;
|
|
|
|
|
|
}
|
2025-09-22 15:42:13 +08:00
|
|
|
|
.status-rejected {
|
|
|
|
|
|
background-color: #fff2f0;
|
|
|
|
|
|
color: #f5222d;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
.status-pending {
|
|
|
|
|
|
background-color: #f9fafb;
|
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
|
}
|
|
|
|
|
|
.status-accepted {
|
2025-09-22 15:42:13 +08:00
|
|
|
|
background-color: #e0f2fe;
|
|
|
|
|
|
color: #0284c7;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
.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;
|
2025-09-18 19:56:24 +08:00
|
|
|
|
justify-content: center;
|
2025-09-17 15:53:38 +08:00
|
|
|
|
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%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-22 15:42:13 +08:00
|
|
|
|
|
|
|
|
|
|
/* 详情弹窗样式 */
|
|
|
|
|
|
.custom-experiment-dialog .el-dialog__body {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-content {
|
|
|
|
|
|
max-height: 600px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 详情区块 */
|
|
|
|
|
|
.detail-section {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border: 1px solid #e4e7ed;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding-bottom: 8px;
|
|
|
|
|
|
border-bottom: 1px solid #e8f4ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 基础信息网格 */
|
|
|
|
|
|
.detail-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-label {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-value {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
|
padding: 4px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 文本区域 */
|
|
|
|
|
|
.detail-textarea {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-text {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #495057;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
min-height: 60px;
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 设备列表样式 */
|
|
|
|
|
|
.device-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-tag {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
|
background-color: #f0f9ff;
|
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
|
border: 1px solid #bae7ff;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 步骤条样式 */
|
|
|
|
|
|
.steps-container {
|
|
|
|
|
|
padding-left: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-item:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-item:not(:last-child)::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 17px;
|
|
|
|
|
|
top: 36px;
|
|
|
|
|
|
bottom: -16px;
|
|
|
|
|
|
width: 2px;
|
|
|
|
|
|
background-color: #e4e7ed;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-number {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 36px;
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background-color: #1890ff;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
background-color: #fafafa;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 列表样式 */
|
|
|
|
|
|
.participant-list,
|
|
|
|
|
|
.inspection-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-item,
|
|
|
|
|
|
.inspection-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 24px;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-name,
|
|
|
|
|
|
.inspection-name {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
|
min-width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-team,
|
|
|
|
|
|
.participant-role,
|
|
|
|
|
|
.inspection-type {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-item,
|
|
|
|
|
|
.inspection-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 24px;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-name,
|
|
|
|
|
|
.inspection-name {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
|
min-width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-team,
|
|
|
|
|
|
.participant-role,
|
|
|
|
|
|
.inspection-type {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 详情弹窗响应式设计 */
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.detail-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-item,
|
|
|
|
|
|
.inspection-item {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.participant-name,
|
|
|
|
|
|
.inspection-name {
|
|
|
|
|
|
min-width: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-17 15:53:38 +08:00
|
|
|
|
</style>
|