完成运维记录前端

master
lynn 1 year ago
parent f16bdd2339
commit 65334c48a4

@ -0,0 +1,928 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>维护管理</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6;
color: #333;
background-color: #f0f2f5;
display: flex;
height: 100vh;
}
.app-layout {
display: flex;
width: 100%;
height: 100%;
}
.sidebar {
background-color: #ffffff;
padding: 20px 0;
display: flex;
flex-direction: column;
flex-shrink: 0;
box-shadow: 2px 0 6px rgba(0,21,41,.15);
z-index: 10;
}
.sidebar-main {
width: 220px;
}
.sidebar-secondary {
width: 150px;
background-color: #fafafa;
border-left: 1px solid #f0f0f0;
border-right: 1px solid #f0f0f0;
}
.sidebar-header {
padding: 0 20px 20px 20px;
margin-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.sidebar-header h2 {
color: #1890ff;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
margin-bottom: 5px;
}
.sidebar-nav {
flex-grow: 1;
overflow-y: auto;
}
.sidebar ul {
list-style: none;
}
.sidebar li a {
display: block;
padding: 10px 20px;
color: #555;
text-decoration: none;
transition: background-color 0.2s, color 0.2s;
font-size: 0.9rem;
}
.sidebar li a:hover,
.sidebar li a.active {
background-color: #e6f7ff;
color: #1890ff;
}
.sidebar .submenu {
padding-left: 20px;
}
.sidebar .submenu li a {
padding-left: 30px;
font-size: 0.85rem;
}
.sidebar-footer {
padding: 15px 20px;
border-top: 1px solid #f0f0f0;
color: #888;
cursor: pointer;
font-size: 0.9rem;
text-align: center;
}
.main-content {
flex-grow: 1;
padding: 20px;
overflow-y: auto;
background-color: #f0f2f5;
}
/* 内容区域容器 */
.content-wrapper {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* 页面头部 */
.page-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e8e8e8;
}
.page-title {
font-size: 1.5rem; /* 调整标题大小 */
font-weight: 600;
color: #333;
}
/* 搜索过滤器 */
.search-filters {
background-color: #fff; /* 背景 */
padding: 15px 20px;
border-radius: 6px;
margin-bottom: 20px;
display: flex;
flex-wrap: wrap; /* 允许换行 */
align-items: center;
gap: 15px 20px; /* 行间距和列间距 */
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.filter-item {
display: flex;
align-items: center;
gap: 8px; /* 标签和输入框间距 */
}
.filter-item label {
font-size: 0.9rem;
color: #555;
white-space: nowrap; /* 防止标签换行 */
}
.filter-item input[type="text"],
.filter-item input[type="date"],
.filter-item select {
padding: 6px 10px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 0.9rem;
min-width: 120px; /* 最小宽度 */
}
.filter-item input[type="text"] {
min-width: 200px; /* 关键词输入框更宽 */
}
/* 统计卡片 */
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); /* 响应式列 */
gap: 20px;
margin-bottom: 20px;
}
.card-item {
background-color: #fff;
padding: 15px 20px;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
text-align: center;
border-left: 4px solid grey; /* 默认边框 */
}
.card-item.total { border-left-color: #1890ff; } /* 蓝色 */
.card-item.pending { border-left-color: #faad14; } /* 黄色 */
.card-item.completed { border-left-color: #52c41a; } /* 绿色 */
.card-label {
font-size: 0.9rem;
color: #888;
margin-bottom: 8px;
}
.card-value {
font-size: 1.8rem;
font-weight: 600;
color: #333;
}
/* 工具栏/操作栏 */
.toolbar {
margin-bottom: 20px;
display: flex;
justify-content: flex-end; /* 按钮靠右 */
gap: 10px;
}
.btn {
padding: 8px 15px;
border: 1px solid #d9d9d9;
background-color: #fff;
color: #555;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9rem;
}
.btn:hover {
border-color: #1890ff;
color: #1890ff;
}
.btn-primary {
background-color: #1890ff;
border-color: #1890ff;
color: #fff;
}
.btn-primary:hover {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
/* 表格内的链接式按钮 */
.btn-link {
background: none;
border: none;
color: #1890ff;
padding: 0 5px; /* 微调内边距 */
cursor: pointer;
font-size: 0.9rem;
}
.btn-link:hover {
text-decoration: underline;
color: #40a9ff;
border: none; /* 覆盖 .btn 的边框 */
}
/* 数据表格样式 */
.data-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.data-table th,
.data-table td {
border: 1px solid #f0f0f0; /* 更细的边框 */
padding: 12px 15px; /* 调整内边距 */
text-align: left;
vertical-align: middle;
font-size: 0.9rem;
}
.data-table th {
background-color: #fafafa; /* 表头背景 */
font-weight: 600;
color: #4a5568;
}
.data-table tbody tr:hover {
background-color: #f5faff; /* 悬停颜色 */
}
.data-table .action-links a {
color: #1890ff;
text-decoration: none;
margin-right: 10px;
}
.data-table .action-links a:last-child {
margin-right: 0;
}
.data-table .action-links a:hover {
text-decoration: underline;
}
/* 状态徽章样式 */
.status-badge {
display: inline-block;
padding: 3px 8px;
font-size: 0.8rem;
font-weight: 500;
border-radius: 10px; /* 胶囊形状 */
color: #fff;
white-space: nowrap;
}
.badge-blue {
background-color: #1890ff; /* 蓝色 */
}
.badge-red {
background-color: #f5222d; /* 红色 */
}
/* 分页控件样式 */
.pagination {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 5px;
}
.pagination button,
.pagination span {
padding: 6px 12px;
border: 1px solid #d9d9d9;
background-color: #fff;
border-radius: 4px;
font-size: 0.9rem;
cursor: pointer;
min-width: 32px; /* 保持最小宽度 */
text-align: center;
}
.pagination button:disabled {
color: #bfbfbf;
cursor: not-allowed;
background-color: #f5f5f5;
}
.pagination span.current-page {
background-color: #1890ff;
border-color: #1890ff;
color: #fff;
cursor: default;
}
/* 调整第二列的列表和链接样式 */
.sidebar-secondary ul {
padding-top: 15px;
}
.sidebar-secondary li a {
padding: 12px 20px;
font-size: 0.95rem;
}
.sidebar-secondary li a.active,
.sidebar-secondary li a:hover {
background-color: #fff;
color: #1890ff;
font-weight: 500;
border-right: none;
border-left: 3px solid #1890ff;
margin-left: -1px;
}
/* 统计数据覆盖层样式 */
.stats-overlay {
position: absolute;
top: 20px;
left: 20px;
background-color: rgba(255, 255, 255, 0.85);
padding: 15px 20px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 5;
display: flex;
flex-direction: row;
gap: 15px;
}
.stat-item {
background-color: #f8f9fa;
padding: 10px 15px;
border-radius: 4px;
text-align: center;
border: 1px solid #e9ecef;
min-width: 100px;
}
.stat-label {
font-size: 0.85rem;
color: #6c757d;
display: block;
margin-bottom: 3px;
}
.stat-value {
font-size: 1.2rem;
font-weight: 600;
color: #2d3748;
display: block;
}
/* Modal 样式 (通用) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1050; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgba(0,0,0,0.5); /* Black w/ opacity */
align-items: center;
justify-content: center;
}
.modal.show {
display: flex; /* Show when needed */
}
.modal-dialog {
position: relative;
margin: auto;
pointer-events: none;
max-width: 600px; /* Modal max width */
width: 90%;
}
.modal-content {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
pointer-events: auto;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 0.3rem;
outline: 0;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,.5);
}
.modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 1rem 1rem;
border-bottom: 1px solid #dee2e6;
border-top-left-radius: calc(0.3rem - 1px);
border-top-right-radius: calc(0.3rem - 1px);
}
.modal-title {
margin-bottom: 0;
line-height: 1.5;
font-size: 1.25rem;
}
.close-button {
padding: 1rem 1rem;
margin: -1rem -1rem -1rem auto;
background-color: transparent;
border: 0;
font-size: 1.5rem;
font-weight: 700;
line-height: 1;
color: #000;
text-shadow: 0 1px 0 #fff;
opacity: .5;
cursor: pointer;
}
.close-button:hover {
opacity: .75;
}
.modal-body {
position: relative;
flex: 1 1 auto;
padding: 1rem;
}
.modal-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
padding: 0.75rem;
border-top: 1px solid #dee2e6;
border-bottom-right-radius: calc(0.3rem - 1px);
border-bottom-left-radius: calc(0.3rem - 1px);
gap: 0.5rem; /* Spacing between buttons */
}
/* 表单组样式 */
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.9rem;
}
.form-group input[type="date"],
.form-group textarea,
.form-group input[type="file"] {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.form-group textarea {
resize: vertical;
}
.form-group input[type="file"] {
padding: 0.3rem 0.75rem; /* Adjust padding for file input */
}
.form-text {
font-size: 0.8rem;
color: #6c757d;
}
.signature-area .signature-display {
background-color: #f8f9fa;
border: 1px dashed #ced4da;
padding: 1rem;
text-align: center;
color: #6c757d;
border-radius: 0.25rem;
min-height: 80px; /* Placeholder height */
display: flex;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div class="app-layout">
<div class="sidebar sidebar-main">
<div class="sidebar-header">
<h2>系统管理</h2> <!-- 或其他系统名称 -->
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="#" >系统首页</a></li>
<li><a href="#">信息管理</a></li>
<li>
<a href="#" class="active">库存管理</a>
</li>
<li><a href="#">统计报表</a></li>
<li><a href="#">应急防御</a></li>
<li><a href="#">工单管理</a></li>
<li><a href="#">抢险泵车行程定位管理</a></li>
<li><a href="#">入库管理</a></li>
<li><a href="#">出库管理</a></li>
<li><a href="#">胥口仓库管理</a></li>
<li><a href="#">系统管理</a></li>
</ul>
</nav>
<div class="sidebar-footer">
收起左边
</div>
</div>
<div class="main-content">
<div class="page-header">
<h1 class="page-title">维护记录</h1>
</div>
<div class="content-wrapper">
<!-- 搜索区域 -->
<div class="search-filters">
<div class="filter-item">
<label for="search-warehouse">仓库:</label>
<select id="search-warehouse" name="warehouse">
<option value="">全部</option>
<option value="wt">外塘河仓库</option>
<option value="xk">胥口仓库</option>
</select>
</div>
<div class="filter-item">
<label for="search-status">状态:</label>
<select id="search-status" name="status">
<option value="">全部</option>
<option value="pending">待处理</option>
<option value="completed">已完成</option>
</select>
</div>
<div class="filter-item">
<label for="search-date-start">计划日期:</label>
<input type="date" id="search-date-start" name="date-start">
<span>-</span>
<input type="date" id="search-date-end" name="date-end">
</div>
<div class="filter-item">
<label for="search-keyword">关键词:</label>
<input type="text" id="search-keyword" name="keyword" placeholder="维护项目/编号/负责人">
</div>
<div class="filter-item">
<button class="btn">查询</button>
<button class="btn">重置</button>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-cards">
<div class="card-item total">
<div class="card-label">全部记录</div>
<div class="card-value" id="stat-total">-</div>
</div>
<div class="card-item pending">
<div class="card-label">未完成</div>
<div class="card-value" id="stat-pending">-</div>
</div>
<div class="card-item completed">
<div class="card-label">已完成</div>
<div class="card-value" id="stat-completed">-</div>
</div>
</div>
<div class="toolbar">
<!-- <button class="btn">搜索</button> --> <!-- Search moved above -->
<button class="btn btn-primary">创建计划</button>
</div>
<table class="data-table">
<thead>
<tr>
<th>记录编号</th>
<th>维护物资</th>
<th>计划维护日期</th>
<th>实际维护日期</th>
<th>负责人</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- Sample Row 1 (Completed) -->
<tr data-status="completed" data-plandate="2023-08-14">
<td>WX20230815001</td>
<td>3号水泵</td>
<td>2023-08-14</td>
<td>2023-08-15</td>
<td>张三</td>
<td>已完成</td>
<td class="action-links">
<a href="#">查看</a>
<!-- Completed: No 'Complete' button -->
</td>
</tr>
<!-- Sample Row 2 (Pending - Overdue) -->
<tr data-status="pending" data-plandate="2025-08-15">
<td>WX20230816002</td>
<td>柴油发电机</td>
<td>2025-08-15</td>
<td></td> <!-- Placeholder for badge -->
<td>李四</td>
<td>待处理</td>
<td class="action-links">
<a href="#">查看</a>
<button class="btn btn-link complete-btn">完成维护</button>
</td>
</tr>
<!-- Sample Row 3 (Completed) -->
<tr data-status="completed" data-plandate="2023-08-17">
<td>WX20230818003</td>
<td>防汛挡板</td>
<td>2023-08-17</td>
<td>2023-08-18</td>
<td>王五</td>
<td>已完成</td>
<td class="action-links">
<a href="#">查看</a>
</td>
</tr>
<!-- Sample Row 4 (Ongoing - Future) -->
<tr data-status="ongoing" data-plandate="2024-12-25"> <!-- Future Date -->
<td>WX20241225004</td>
<td>冲锋舟</td>
<td>2024-12-25</td>
<td></td> <!-- Placeholder for badge -->
<td>赵六</td>
<td>进行中</td>
<td class="action-links">
<a href="#">查看</a>
<button class="btn btn-link complete-btn">完成维护</button>
</td>
</tr>
<!-- Sample Row 5 (Pending - Very Overdue) -->
<tr data-status="pending" data-plandate="2023-07-01"> <!-- Past Date -->
<td>WX20230701005</td>
<td>救生衣检查</td>
<td>2023-07-01</td>
<td></td> <!-- Placeholder for badge -->
<td>孙七</td>
<td>待处理</td>
<td class="action-links">
<a href="#">查看</a>
<button class="btn btn-link complete-btn">完成维护</button>
</td>
</tr>
<!-- Sample Row 6 (Ongoing - Near Future) -->
<tr data-status="ongoing" data-plandate="2024-08-10"> <!-- Future Date -->
<td>WX20240810006</td>
<td>应急照明灯</td>
<td>2024-08-10</td>
<td></td> <!-- Placeholder for badge -->
<td>周八</td>
<td>进行中</td>
<td class="action-links">
<a href="#">查看</a>
<button class="btn btn-link complete-btn">完成维护</button>
</td>
</tr>
</tbody>
</table>
<div class="pagination">
<button disabled>&laquo; 上一页</button>
<span class="current-page">1</span>
<span>2</span>
<span>3</span>
<span>...</span>
<span>10</span>
<button>下一页 &raquo;</button>
</div>
</div>
<!-- 未来维护规则的内容可以放在这里,通过 JS 切换显示 -->
<!--
<div class="page-header" style="display: none;">
<h1 class="page-title">维护规则</h1>
</div>
<div class="placeholder-content" style="display: none;">
维护规则内容区域
</div>
-->
</div>
</div>
<script>
// --- Helper function to calculate date difference in days ---
function getDateDifferenceInDays(dateString) {
const plannedDate = new Date(dateString);
const today = new Date();
// Reset time part to compare dates only
today.setHours(0, 0, 0, 0);
plannedDate.setHours(0, 0, 0, 0);
const diffTime = plannedDate - today;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); // Use Math.ceil for correct day counting
return diffDays;
}
// --- Update Stats Cards and Status Badges ---
function updateTableAndStats() {
const tableRows = document.querySelectorAll('.data-table tbody tr');
let totalCount = 0;
let pendingCount = 0;
let completedCount = 0;
tableRows.forEach(row => {
totalCount++;
const status = row.dataset.status;
const plannedDateStr = row.dataset.plandate;
const actualDateCell = row.cells[3]; // 4th cell is 'Actual Maintenance Date'
if (status === 'completed') {
completedCount++;
} else {
pendingCount++; // Count both 'pending' and 'ongoing' as uncompleted
// Update badge for uncompleted items
if (plannedDateStr && actualDateCell) {
const diffDays = getDateDifferenceInDays(plannedDateStr);
let badgeHTML = '';
if (diffDays >= 0) {
// Future or today
badgeHTML = `<span class="status-badge badge-blue">还有 ${diffDays} 天</span>`;
} else {
// Overdue
badgeHTML = `<span class="status-badge badge-red">已超期 ${Math.abs(diffDays)} 天</span>`;
}
actualDateCell.innerHTML = badgeHTML;
}
}
});
// Update Stats Cards
document.getElementById('stat-total').textContent = totalCount;
document.getElementById('stat-pending').textContent = pendingCount;
document.getElementById('stat-completed').textContent = completedCount;
}
// --- Initial Load ---
document.addEventListener('DOMContentLoaded', () => {
updateTableAndStats();
initializeModalInteractions(); // Initialize modal listeners
});
// --- Modal Interactions ---
function initializeModalInteractions() {
const completeModal = document.getElementById('completeMaintenanceModal');
if (!completeModal) return;
const actualDateInput = completeModal.querySelector('#actual-date');
const form = completeModal.querySelector('#completeMaintenanceForm');
// Function to open the modal
function openModal() {
// Set default date to today
if (actualDateInput) {
actualDateInput.valueAsDate = new Date();
}
// Clear previous form data (optional)
if (form) form.reset();
completeModal.classList.add('show');
}
// Function to close the modal
function closeModal() {
completeModal.classList.remove('show');
}
// Add event listeners to "完成维护" buttons in the table
document.querySelectorAll('.complete-btn').forEach(button => {
button.addEventListener('click', openModal);
});
// Add event listeners to close buttons and backdrop
completeModal.querySelectorAll('[data-dismiss="modal"]').forEach(button => {
button.addEventListener('click', closeModal);
});
// Close modal if clicking outside the modal content
completeModal.addEventListener('click', (event) => {
if (event.target === completeModal) {
closeModal();
}
});
// Handle form submission (simulation)
if(form) {
form.addEventListener('submit', (event) => {
event.preventDefault(); // Prevent actual submission
alert('维护记录已提交(模拟)');
// Add logic here to potentially update the table row
closeModal();
});
}
}
// --- Existing Submenu Logic (Keep it if needed) ---
/*
document.querySelectorAll('.sidebar .has-submenu > .nav-link').forEach(link => {
link.addEventListener('click', function(event) {
event.preventDefault(); // 阻止链接跳转
const parentLi = this.closest('.has-submenu');
parentLi.classList.toggle('open');
});
});
*/
// --- Placeholder for future Modal logic ---
</script>
<!-- 完成维护 Modal -->
<div id="completeMaintenanceModal" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">完成维护</h3>
<button class="close-button" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<form id="completeMaintenanceForm">
<div class="form-group">
<label for="actual-date">实际维护日期:</label>
<input type="date" id="actual-date" name="actual-date" required>
</div>
<div class="form-group">
<label for="maintenance-notes">备注:</label>
<textarea id="maintenance-notes" name="notes" rows="4" required></textarea>
</div>
<div class="form-group">
<label for="maintenance-photos">上传图片:</label>
<input type="file" id="maintenance-photos" name="photos" multiple accept="image/*">
<small class="form-text">(可选)</small>
</div>
<div class="form-group signature-area">
<label>签名:</label>
<div class="signature-display">当前用户: 张三</div> <!-- Simulate user name -->
<!-- Add canvas or similar for actual signature if needed -->
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn" data-dismiss="modal">取消</button>
<button type="submit" form="completeMaintenanceForm" class="btn btn-primary">确认完成</button>
</div>
</div>
</div>
</div>
</body>
</html>

@ -0,0 +1,806 @@
<template>
<div class="table-page-container">
<div ref="lxHeader">
<lx-header icon="md-apps" text="维护记录" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<slot>
<div style="display: flex;justify-content: flex-start;flex-wrap: wrap;">
<div class="search-item">
<span>仓库:</span>
<Select v-model="select.warehouse" style="width: 120px">
<Option value="">全部</Option>
<Option value="wt">外塘河仓库</Option>
<Option value="xk">胥口仓库</Option>
</Select>
</div>
<div class="search-item">
<span>状态:</span>
<Select v-model="select.status" style="width: 120px">
<Option value="">全部</Option>
<Option value="pending">待处理</Option>
<Option value="completed">已完成</Option>
</Select>
</div>
<div class="search-item">
<span>计划日期:</span>
<DatePicker v-model="select.date_start" type="date" placeholder="开始日期" style="width: 120px"></DatePicker>
<span>-</span>
<DatePicker v-model="select.date_end" type="date" placeholder="结束日期" style="width: 120px"></DatePicker>
</div>
<div class="search-item">
<span>关键词:</span>
<Input v-model="select.keyword" placeholder="维护项目/编号/负责人" style="width: 200px"></Input>
</div>
<Button type="primary" style="margin-left: 8px;" @click="getList"></Button>
<Button style="margin-left: 8px;" @click="resetSearch"></Button>
</div>
</slot>
</lx-header>
</div>
<div class="content-wrapper">
<!-- 统计卡片 -->
<div class="stats-cards">
<div class="card-item total">
<div class="card-label">全部记录</div>
<div class="card-value">{{ stats.total }}</div>
</div>
<div class="card-item pending">
<div class="card-label">未完成</div>
<div class="card-value">{{ stats.pending }}</div>
</div>
<div class="card-item completed">
<div class="card-label">已完成</div>
<div class="card-value">{{ stats.completed }}</div>
</div>
</div>
<div class="table-flex-content">
<el-table
:data="list"
style="width: 100%"
:height="tableHeight"
border
size="medium"
>
<el-table-column
prop="id"
label="记录编号"
min-width="120"
align="center"
/>
<el-table-column
prop="material"
label="维护物资"
min-width="120"
align="center"
/>
<el-table-column
prop="plan_date"
label="计划维护日期"
min-width="120"
align="center"
/>
<el-table-column
label="实际维护日期/状态"
min-width="150"
align="center"
>
<template slot-scope="scope">
<template v-if="scope.row.status === 'completed'">
{{ scope.row.actual_date }}
</template>
<template v-else>
<div :class="['status-badge', getBadgeClass(scope.row.plan_date)]" style="display: inline-block;">
{{ getBadgeText(scope.row.plan_date) }}
</div>
</template>
</template>
</el-table-column>
<el-table-column
prop="operator"
label="负责人"
min-width="100"
align="center"
/>
<el-table-column
label="状态"
min-width="100"
align="center"
>
<template slot-scope="scope">
{{ getStatusText(scope.row.status) }}
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
min-width="200"
header-align="center"
class-name="table-col-action"
>
<template slot-scope="scope">
<div style="display: flex; gap: 8px; justify-content: center;">
<Button type="primary" size="small" style="border-radius: 6px;" @click="viewDetail(scope.row)"></Button>
<Button v-if="scope.row.status !== 'completed'" type="primary" size="small" style="border-radius: 6px;" ghost @click="completeMaintenance(scope.row)"></Button>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
@size-change="pageSizeChange"
@current-change="pageChange"
:current-page="select.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
/>
</div>
</div>
</div>
<!-- 完成维护 Modal -->
<div class="modal" :class="{ show: showModal }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">完成维护</h3>
<button class="close-button" @click="closeModal">&times;</button>
</div>
<div class="modal-body">
<form @submit.prevent="submitMaintenance">
<div class="form-group">
<label for="actual-date">实际维护日期:</label>
<input type="date" id="actual-date" v-model="form.actual_date" required>
</div>
<div class="form-group">
<label for="maintenance-notes">备注:</label>
<textarea id="maintenance-notes" v-model="form.notes" rows="4" required></textarea>
</div>
<div class="form-group">
<label for="maintenance-photos">上传图片:</label>
<input type="file" id="maintenance-photos" multiple accept="image/*" @change="handleFileUpload">
<small class="form-text">可选</small>
</div>
<div class="form-group signature-area">
<label>签名:</label>
<div class="signature-display">当前用户: {{ currentUser }}</div>
</div>
</form>
</div>
<div class="modal-footer">
<Button @click="closeModal"></Button>
<Button type="primary" @click="submitMaintenance"></Button>
</div>
</div>
</div>
</div>
<!-- 查看详情 Modal -->
<div class="modal" :class="{ show: showViewModal }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">维护记录详情</h3>
<button class="close-button" @click="closeViewModal">&times;</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>记录编号:</label>
<div class="form-value">{{ currentRecord.id }}</div>
</div>
<div class="form-group">
<label>维护物资:</label>
<div class="form-value">{{ currentRecord.material }}</div>
</div>
<div class="form-group">
<label>计划维护日期:</label>
<div class="form-value">{{ currentRecord.plan_date }}</div>
</div>
<div class="form-group">
<label>实际维护日期:</label>
<div class="form-value">{{ currentRecord.actual_date || '-' }}</div>
</div>
<div class="form-group">
<label>负责人:</label>
<div class="form-value">{{ currentRecord.operator }}</div>
</div>
<div class="form-group">
<label>状态:</label>
<div class="form-value">{{ getStatusText(currentRecord.status) }}</div>
</div>
<div class="form-group">
<label>维护备注:</label>
<div class="form-value">{{ currentRecord.maintenance_notes || '-' }}</div>
</div>
<div class="form-group">
<label>维护图片:</label>
<div class="photo-gallery" v-if="currentRecord.maintenance_photos && currentRecord.maintenance_photos.length">
<div v-for="(photo, index) in currentRecord.maintenance_photos" :key="index" class="photo-item">
<img :src="photo" alt="维护图片" class="photo-preview">
</div>
</div>
<div class="form-value" v-else>-</div>
</div>
<div class="form-group">
<label>维护人签名:</label>
<div class="form-value">{{ currentRecord.maintenance_signature || '-' }}</div>
</div>
</div>
<div class="modal-footer">
<Button @click="closeViewModal"></Button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { Button, Select, Option, DatePicker, Input } from 'view-design';
export default {
components: {
Button,
Select,
Option,
DatePicker,
Input
},
data() {
return {
select: {
page: 1,
page_size: 10,
warehouse: '',
status: '',
date_start: '',
date_end: '',
keyword: ''
},
stats: {
total: 0,
pending: 0,
completed: 0
},
list: [
{
id: 'WX20230815001',
material: '3号水泵',
plan_date: '2023-08-14',
actual_date: '2023-08-15',
operator: '张三',
status: 'completed',
maintenance_notes: '水泵运行正常,已更换密封圈',
maintenance_photos: ['/path/to/photo1.jpg', '/path/to/photo2.jpg'],
maintenance_signature: '张三'
},
{
id: 'WX20230816002',
material: '柴油发电机',
plan_date: '2025-05-15', //
actual_date: '',
operator: '李四',
status: 'pending'
},
{
id: 'WX20230818003',
material: '防汛挡板',
plan_date: '2023-08-17',
actual_date: '2023-08-18',
operator: '王五',
status: 'completed',
maintenance_notes: '挡板完好,已进行防腐处理',
maintenance_photos: ['/path/to/photo3.jpg'],
maintenance_signature: '王五'
},
{
id: 'WX20241225004',
material: '冲锋舟',
plan_date: '2024-12-25', //
actual_date: '',
operator: '赵六',
status: 'pending'
},
{
id: 'WX20230701005',
material: '救生衣检查',
plan_date: '2023-07-01', //
actual_date: '',
operator: '孙七',
status: 'pending'
},
{
id: 'WX20240810006',
material: '应急照明灯',
plan_date: '2024-08-10', //
actual_date: '',
operator: '周八',
status: 'pending'
}
],
showModal: false,
showViewModal: false,
currentRecord: {},
form: {
actual_date: '',
notes: '',
photos: []
},
currentUser: '张三',
total: 0,
tableHeight: 550
}
},
created() {
this.getList()
},
mounted() {
this.calcTableHeight();
window.addEventListener('resize', this.calcTableHeight);
},
beforeDestroy() {
window.removeEventListener('resize', this.calcTableHeight);
},
methods: {
async getList() {
try {
// TODO: API
this.list = [
{
id: 'WX20230815001',
material: '3号水泵',
plan_date: '2023-08-14',
actual_date: '2023-08-15',
operator: '张三',
status: 'completed',
maintenance_notes: '水泵运行正常,已更换密封圈',
maintenance_photos: ['/path/to/photo1.jpg', '/path/to/photo2.jpg'],
maintenance_signature: '张三'
},
{
id: 'WX20230816002',
material: '柴油发电机',
plan_date: '2025-08-15', //
actual_date: '',
operator: '李四',
status: 'pending'
},
{
id: 'WX20230818003',
material: '防汛挡板',
plan_date: '2023-08-17',
actual_date: '2023-08-18',
operator: '王五',
status: 'completed'
},
{
id: 'WX20241225004',
material: '冲锋舟',
plan_date: '2024-12-25', //
actual_date: '',
operator: '赵六',
status: 'pending'
},
{
id: 'WX20230701005',
material: '救生衣检查',
plan_date: '2023-07-01', //
actual_date: '',
operator: '孙七',
status: 'pending'
},
{
id: 'WX20240810006',
material: '应急照明灯',
plan_date: '2024-08-10', //
actual_date: '',
operator: '周八',
status: 'pending'
}
];
this.total = this.list.length;
this.stats = {
total: this.list.length,
pending: this.list.filter(item => item.status === 'pending').length,
completed: this.list.filter(item => item.status === 'completed').length
};
} catch (e) {
this.$message.error('获取维护记录失败');
}
},
resetSearch() {
this.select = {
page: 1,
page_size: 10,
warehouse: '',
status: '',
date_start: '',
date_end: '',
keyword: ''
}
this.getList()
},
pageChange(e) {
this.select.page = e
this.getList()
},
pageSizeChange(e) {
this.select.page_size = e
this.getList()
},
calcTableHeight() {
const header = this.$refs.lxHeader ? this.$refs.lxHeader.offsetHeight : 0;
const windowHeight = window.innerHeight;
const minTableHeight = 600;
this.tableHeight = Math.max(windowHeight - header - 100, minTableHeight);
},
getDateDifferenceInDays(dateString) {
const plannedDate = new Date(dateString);
const today = new Date();
// 00:00:00
today.setHours(0, 0, 0, 0);
plannedDate.setHours(0, 0, 0, 0);
//
const diffTime = plannedDate - today;
//
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
},
viewDetail(item) {
this.currentRecord = { ...item };
this.showViewModal = true;
},
completeMaintenance(item) {
this.showModal = true
this.form = {
actual_date: new Date().toISOString().split('T')[0],
notes: '',
photos: []
}
},
closeModal() {
this.showModal = false
this.form = {
actual_date: '',
notes: '',
photos: []
}
},
handleFileUpload(event) {
this.form.photos = Array.from(event.target.files)
},
async submitMaintenance() {
try {
// TODO:
this.closeModal()
this.getList()
} catch (e) {
this.$message.error('提交维护记录失败')
}
},
getBadgeClass(planDate) {
const diffDays = this.getDateDifferenceInDays(planDate);
return diffDays >= 0 ? 'badge-blue' : 'badge-red';
},
getBadgeText(planDate) {
const diffDays = this.getDateDifferenceInDays(planDate);
return diffDays >= 0 ? `还有 ${diffDays}` : `已超期 ${Math.abs(diffDays)}`;
},
getStatusText(status) {
const statusMap = {
'pending': '待处理',
'completed': '已完成',
'ongoing': '进行中'
};
return statusMap[status] || status;
},
closeViewModal() {
this.showViewModal = false;
this.currentRecord = {};
}
}
}
</script>
<style>
.table-page-container {
padding: 20px;
background-color: #f0f2f5;
min-height: 100vh;
}
.page-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e8e8e8;
}
.page-title {
font-size: 1.5rem;
font-weight: 600;
color: #333;
}
.content-wrapper {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-top: 10px;
}
.search-item {
display: flex;
align-items: center;
margin-right: 16px;
}
.search-item span {
margin-right: 8px;
white-space: nowrap;
}
.search-item .ivu-select,
.search-item .ivu-date-picker,
.search-item .ivu-input {
margin-right: 8px;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card-item {
background-color: #fff;
padding: 15px 20px;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
text-align: center;
border-left: 4px solid grey;
}
.card-item.total { border-left-color: #1890ff; }
.card-item.pending { border-left-color: #faad14; }
.card-item.completed { border-left-color: #52c41a; }
.card-label {
font-size: 0.9rem;
color: #888;
margin-bottom: 8px;
}
.card-value {
font-size: 1.8rem;
font-weight: 600;
color: #333;
}
.table-flex-content {
flex: 1 1 0;
display: flex;
flex-direction: column;
min-height: 0;
}
.el-table th {
background: #f5f7fa !important;
color: #333;
font-weight: bold;
font-size: 15px;
}
.el-table td {
font-size: 14px;
height: 48px;
}
.el-table {
border-radius: 10px;
overflow: hidden;
width: 100%;
height: 100%;
}
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
color: #fff;
}
.badge-blue {
background-color: #2d8cf0;
}
.badge-red {
background-color: #ed4014;
}
.modal {
display: none;
position: fixed;
z-index: 1050;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.5);
align-items: center;
justify-content: center;
}
.modal.show {
display: flex;
}
.modal-dialog {
position: relative;
margin: auto;
pointer-events: none;
max-width: 600px;
width: 90%;
}
.modal-content {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
pointer-events: auto;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 0.3rem;
outline: 0;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,.5);
}
.modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 1rem 1rem;
border-bottom: 1px solid #dee2e6;
border-top-left-radius: calc(0.3rem - 1px);
border-top-right-radius: calc(0.3rem - 1px);
}
.modal-title {
margin-bottom: 0;
line-height: 1.5;
font-size: 1.25rem;
}
.close-button {
padding: 1rem 1rem;
margin: -1rem -1rem -1rem auto;
background-color: transparent;
border: 0;
font-size: 1.5rem;
font-weight: 700;
line-height: 1;
color: #000;
text-shadow: 0 1px 0 #fff;
opacity: .5;
cursor: pointer;
}
.close-button:hover {
opacity: .75;
}
.modal-body {
position: relative;
flex: 1 1 auto;
padding: 1rem;
}
.modal-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
padding: 0.75rem;
border-top: 1px solid #dee2e6;
border-bottom-right-radius: calc(0.3rem - 1px);
border-bottom-left-radius: calc(0.3rem - 1px);
gap: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.9rem;
}
.form-group input[type="date"],
.form-group textarea,
.form-group input[type="file"] {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.form-group textarea {
resize: vertical;
}
.form-group input[type="file"] {
padding: 0.3rem 0.75rem;
}
.form-text {
font-size: 0.8rem;
color: #6c757d;
}
.signature-area .signature-display {
background-color: #f8f9fa;
border: 1px dashed #ced4da;
padding: 1rem;
text-align: center;
color: #6c757d;
border-radius: 0.25rem;
min-height: 80px;
display: flex;
align-items: center;
justify-content: center;
}
.form-value {
padding: 8px 12px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
min-height: 38px;
}
.photo-gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
margin-top: 10px;
}
.photo-item {
position: relative;
padding-bottom: 100%;
overflow: hidden;
border-radius: 4px;
border: 1px solid #e9ecef;
}
.photo-preview {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
</style>

@ -0,0 +1,620 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>维护规则</title>
<style>
/* --- Basic Reset & Global Styles (Copied from maintain.html) --- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6;
color: #333;
background-color: #f0f2f5;
display: flex;
height: 100vh;
overflow: hidden;
}
.app-layout {
display: flex;
width: 100%;
height: 100%;
}
/* --- Sidebar Styles (Copied and potentially slightly adjusted) --- */
.sidebar {
width: 220px;
background-color: #ffffff;
padding: 0;
display: flex;
flex-direction: column;
flex-shrink: 0;
box-shadow: 2px 0 6px rgba(0,21,41,.15);
z-index: 10;
}
.sidebar-header {
padding: 20px;
margin-bottom: 0;
border-bottom: 1px solid #f0f0f0;
text-align: center;
}
.sidebar-header h2 {
color: #333;
font-size: 1.1rem;
font-weight: 600;
}
.sidebar-nav {
flex-grow: 1;
overflow-y: auto;
padding: 15px 0;
}
.sidebar ul {
list-style: none;
}
.sidebar li a.nav-link {
display: flex;
align-items: center;
padding: 10px 20px;
color: #555;
text-decoration: none;
transition: background-color 0.2s, color 0.2s;
font-size: 0.9rem;
position: relative;
}
.nav-arrow {
margin-left: auto;
font-size: 0.7rem;
transition: transform 0.2s;
}
.has-submenu.open > .nav-link .nav-arrow {
transform: rotate(90deg);
}
.sidebar li a.nav-link:hover,
.sidebar li.active > a.nav-link {
background-color: #e6f7ff;
color: #1890ff;
}
/* 子菜单 */
.submenu {
list-style: none;
padding-left: 0;
max-height: 0; /* 默认隐藏 */
overflow: hidden;
transition: max-height 0.3s ease-out;
background-color: #fafafa;
}
.has-submenu.open > .submenu {
max-height: 500px;
}
.submenu li a {
display: block;
padding: 8px 20px 8px 46px;
color: #555;
text-decoration: none;
font-size: 0.85rem;
transition: background-color 0.2s, color 0.2s;
}
.submenu li a:hover,
.submenu li.active a {
background-color: #dcf4ff;
color: #1890ff;
}
.sidebar-footer {
padding: 15px 20px;
border-top: 1px solid #f0f0f0;
color: #888;
cursor: pointer;
font-size: 0.9rem;
text-align: center;
flex-shrink: 0;
}
/* --- Main Content & Table Styles (Copied from maintain.html) --- */
.main-content {
flex-grow: 1;
padding: 20px;
overflow-y: auto;
background-color: #f0f2f5;
}
.content-wrapper {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.page-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e8e8e8;
}
.page-title {
font-size: 1.5rem;
font-weight: 600;
color: #333;
}
.toolbar {
margin-bottom: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn {
padding: 8px 15px;
border: 1px solid #d9d9d9;
background-color: #fff;
color: #555;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9rem;
}
.btn:hover {
border-color: #1890ff;
color: #1890ff;
}
.btn-primary {
background-color: #1890ff;
border-color: #1890ff;
color: #fff;
}
.btn-primary:hover {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
.data-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.data-table th,
.data-table td {
border: 1px solid #f0f0f0;
padding: 12px 15px;
text-align: left;
vertical-align: middle;
font-size: 0.9rem;
}
.data-table th {
background-color: #fafafa;
font-weight: 600;
color: #4a5568;
}
.data-table tbody tr:hover {
background-color: #f5faff;
}
.data-table .action-links a {
color: #1890ff;
text-decoration: none;
margin-right: 10px;
}
.data-table .action-links a:last-child {
margin-right: 0;
}
.data-table .action-links a:hover {
text-decoration: underline;
}
/* --- Modal Styles (Copied from maintain.html) --- */
.modal {
display: none;
position: fixed;
z-index: 1050;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.5);
align-items: center;
justify-content: center;
}
.modal.show {
display: flex;
}
.modal-dialog {
position: relative;
margin: auto;
pointer-events: none;
max-width: 600px;
width: 90%;
}
.modal-content {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
pointer-events: auto;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 0.3rem;
outline: 0;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,.5);
}
.modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 1rem 1rem;
border-bottom: 1px solid #dee2e6;
border-top-left-radius: calc(0.3rem - 1px);
border-top-right-radius: calc(0.3rem - 1px);
}
.modal-title {
margin-bottom: 0;
line-height: 1.5;
font-size: 1.25rem;
}
.close-button {
padding: 1rem 1rem;
margin: -1rem -1rem -1rem auto;
background-color: transparent;
border: 0;
font-size: 1.5rem;
font-weight: 700;
line-height: 1;
color: #000;
text-shadow: 0 1px 0 #fff;
opacity: .5;
cursor: pointer;
}
.close-button:hover {
opacity: .75;
}
.modal-body {
position: relative;
flex: 1 1 auto;
padding: 1rem;
}
.modal-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
padding: 0.75rem;
border-top: 1px solid #dee2e6;
border-bottom-right-radius: calc(0.3rem - 1px);
border-bottom-left-radius: calc(0.3rem - 1px);
gap: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.9rem;
}
.form-group input[type="text"],
.form-group input[type="date"],
.form-group select,
.form-group textarea {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.form-group textarea {
resize: vertical;
}
.form-text {
font-size: 0.8rem;
color: #6c757d;
}
/* "生成计划时机"控件容器 */
.timing-controls {
display: flex;
align-items: center;
gap: 5px; /* 控件之间的间距 */
}
/* 调整 timing-controls 内部控件宽度 */
.timing-controls select,
.timing-controls input {
width: auto; /* 允许控件根据内容调整宽度 */
flex-shrink: 0; /* 防止控件被过度压缩 */
}
.timing-controls input[type="number"] {
width: 80px; /* 保持数字输入框的固定宽度 */
flex-shrink: 1; /* 允许数字输入框被压缩 */
}
</style>
</head>
<body>
<div class="app-layout">
<div class="sidebar">
<div class="sidebar-header">
<h2>系统管理</h2>
</div>
<nav class="sidebar-nav">
<ul>
<li>
<a href="#" class="nav-link">系统首页</a>
</li>
<li>
<a href="#" class="nav-link">信息管理</a>
</li>
<li>
<a href="3d.html" class="nav-link">库存管理</a> <!-- Link to the map page -->
</li>
<!-- Updated Maintenance Menu -->
<li class="has-submenu open active"> <!-- Keep parent active -->
<a href="#" class="nav-link">
维护管理 <span class="nav-arrow"></span>
</a>
<ul class="submenu">
<li><a href="maintain.html">维护记录</a></li> <!-- Link to records page -->
<li class="active"><a href="#">维护规则</a></li> <!-- Activate this sub-item -->
</ul>
</li>
<li><a href="#" class="nav-link">统计报表</a></li>
<li><a href="#" class="nav-link">应急防御</a></li>
<!-- Add other menu items if needed -->
</ul>
</nav>
<div class="sidebar-footer">
收起左边
</div>
</div>
<div class="main-content">
<div class="page-header">
<h1 class="page-title">维护规则</h1> <!-- Page Title -->
</div>
<div class="content-wrapper">
<div class="toolbar">
<button class="btn btn-primary" id="addRuleBtn">新增规则</button> <!-- Add Rule Button -->
</div>
<table class="data-table">
<thead>
<tr>
<th>规则名称</th>
<th>重复周期</th>
<th>起始日期设定</th>
<th>生成计划时机</th>
<th>已用于物资数</th>
<th>未完成时下次计划</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- Sample Rule 1 -->
<tr>
<td>水泵月度检查</td>
<td>每月</td>
<td>每年第一天开始运算</td>
<td>提前 7 天</td>
<td>5</td>
<td>暂停生成</td>
<td class="action-links">
<a href="#">编辑</a>
<a href="#">删除</a>
<a href="#">关联物资</a>
</td>
</tr>
<!-- Sample Rule 2 -->
<tr>
<td>发电机季度保养</td>
<td>每季度</td>
<td>首次关联物资时</td>
<td>提前 14 天</td>
<td>2</td>
<td>强制生成</td>
<td class="action-links">
<a href="#">编辑</a>
<a href="#">删除</a>
<a href="#">关联物资</a>
</td>
</tr>
<!-- Sample Rule 3 -->
<tr>
<td>挡板年度检修</td>
<td>每年</td>
<td>每年第一天开始运算</td>
<td>提前 30 天</td>
<td>12</td>
<td>暂停生成</td>
<td class="action-links">
<a href="#">编辑</a>
<a href="#">删除</a>
<a href="#">关联物资</a>
</td>
</tr>
<!-- Add more sample rules -->
</tbody>
</table>
<!-- Pagination can be added here if needed -->
</div>
</div>
</div>
<!-- Add Rule Modal -->
<div id="addRuleModal" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">新增维护规则</h3>
<button class="close-button" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<form id="addRuleForm">
<div class="form-group">
<label for="rule-name">规则名称:</label>
<input type="text" id="rule-name" name="rule-name" required>
</div>
<div class="form-group">
<label for="repeat-cycle">重复周期:</label>
<select id="repeat-cycle" name="repeat-cycle" required>
<option value="">请选择</option>
<option value="monthly">每月</option>
<option value="quarterly">每季度</option>
<option value="half">每半年</option>
<option value="yearly">每年</option>
</select>
</div>
<div class="form-group">
<label for="start-date-setting">起始日期设定:</label>
<select id="start-date-setting" name="start-date-setting" required>
<option value="">请选择</option>
<option value="specific_date">从每年第一天开始运算</option>
<option value="first_association">首次关联物资时</option>
</select>
</div>
<div class="form-group">
<label for="generation-timing-value">生成计划时机:</label>
<div class="timing-controls">
<select id="generation-timing-unit" name="generation-timing-unit" required>
<option value="days">提前</option>
<!-- Add other units if needed -->
</select>
<input type="number" id="generation-timing-value" name="generation-timing-value" required min="1" style="width: 80px;">
<select id="generation-timing-period" name="generation-timing-period" required>
<option value="days"></option>
<!-- Add other periods if needed -->
</select>
</div>
</div>
<div class="form-group">
<label for="next-plan-generation">存在未完成计划时生成下一次计划的方式:</label>
<select id="next-plan-generation" name="next-plan-generation" required>
<option value="">请选择</option>
<option value="skip">暂停生成(直到上次计划已经执行完成)</option>
<option value="force">强制生成(忽略未完成状态)</option>
</select>
</div>
<div class="form-group">
<label for="rule-notes">备注:</label>
<textarea id="rule-notes" name="notes" rows="3"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn" data-dismiss="modal">取消</button>
<button type="submit" form="addRuleForm" class="btn btn-primary">保存规则</button>
</div>
</div>
</div>
</div>
<script>
// --- Sidebar Submenu Logic ---
document.querySelectorAll('.sidebar .has-submenu > .nav-link').forEach(link => {
link.addEventListener('click', function(event) {
// Allow navigation if it's not just a toggle link (e.g., has real href)
if (!this.getAttribute('href') || this.getAttribute('href') === '#') {
event.preventDefault();
const parentLi = this.closest('.has-submenu');
parentLi.classList.toggle('open');
}
});
});
// --- Add Rule Modal Logic ---
const addRuleModal = document.getElementById('addRuleModal');
const addRuleBtn = document.getElementById('addRuleBtn');
const startDateSettingSelect = document.getElementById('start-date-setting');
function openAddRuleModal() {
// Reset form (optional)
const form = document.getElementById('addRuleForm');
if (form) form.reset();
if (addRuleModal) addRuleModal.classList.add('show');
}
function closeAddRuleModal() {
if (addRuleModal) addRuleModal.classList.remove('show');
}
if (addRuleBtn) {
addRuleBtn.addEventListener('click', openAddRuleModal);
}
if (addRuleModal) {
// Close buttons
addRuleModal.querySelectorAll('[data-dismiss="modal"]').forEach(button => {
button.addEventListener('click', closeAddRuleModal);
});
// Backdrop click
addRuleModal.addEventListener('click', (event) => {
if (event.target === addRuleModal) {
closeAddRuleModal();
}
});
}
// Handle Add Rule Form Submission (Simulation)
const addRuleForm = document.getElementById('addRuleForm');
if(addRuleForm) {
addRuleForm.addEventListener('submit', (event) => {
event.preventDefault();
alert('维护规则已保存(模拟)');
// Add logic here to actually save data and potentially update the table
closeAddRuleModal();
});
}
</script>
</body>
</html>

@ -0,0 +1,410 @@
<template>
<div class="table-page-container">
<div ref="lxHeader">
<lx-header icon="md-apps" text="维护规则" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<slot>
<div style="display: flex;justify-content: flex-end;flex-wrap: wrap;">
<Button type="primary" @click="showAddRuleModal"></Button>
</div>
</slot>
</lx-header>
</div>
<div class="table-flex-content">
<xy-table :list="list" :total="total" @pageSizeChange="pageSizeChange" @pageIndexChange="pageChange"
:table-item="table" size="medium" :height="tableHeight" style="border-radius: 10px; overflow: hidden; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.08); width: 100%;">
<template v-slot:btns>
<el-table-column label="操作" align="center" min-width="200" header-align="center" class-name="table-col-action">
<template slot-scope="scope">
<div style="display: flex; gap: 8px; justify-content: center;">
<Button type="primary" size="small" style="border-radius: 6px;" @click="handleEdit(scope.row)"></Button>
<Button type="primary" size="small" style="border-radius: 6px;" ghost @click="handleDelete(scope.row)"></Button>
<Button type="primary" size="small" style="border-radius: 6px;" ghost @click="handleAssociate(scope.row)"></Button>
</div>
</template>
</el-table-column>
</template>
</xy-table>
</div>
<!-- 新增规则 Modal -->
<Modal v-model="showModal" title="新增维护规则" @on-ok="handleSubmit" @on-cancel="handleCancel">
<Form ref="form" :model="form" :rules="rules" :label-width="120">
<FormItem label="规则名称" prop="name">
<Input v-model="form.name" placeholder="请输入规则名称"></Input>
</FormItem>
<FormItem label="重复周期" prop="cycle">
<Select v-model="form.cycle">
<Option value="monthly">每月</Option>
<Option value="quarterly">每季度</Option>
<Option value="half">每半年</Option>
<Option value="yearly">每年</Option>
</Select>
</FormItem>
<FormItem label="起始日期设定" prop="startSetting">
<Select v-model="form.startSetting">
<Option value="specific_date">从每年第一天开始运算</Option>
<Option value="first_association">首次关联物资时</Option>
</Select>
</FormItem>
<FormItem label="生成计划时机" prop="timing">
<div class="timing-controls">
<Select v-model="form.timingUnit" style="width: 80px">
<Option value="days">提前</Option>
</Select>
<InputNumber v-model="form.timingValue" :min="1" style="width: 80px"></InputNumber>
<Select v-model="form.timingPeriod" style="width: 80px">
<Option value="days"></Option>
</Select>
</div>
</FormItem>
<FormItem label="未完成时下次计划" prop="nextPlan">
<Select v-model="form.nextPlan">
<Option value="skip">暂停生成直到上次计划已经执行完成</Option>
<Option value="force">强制生成忽略未完成状态</Option>
</Select>
</FormItem>
<FormItem label="备注" prop="notes">
<Input v-model="form.notes" type="textarea" :rows="3" placeholder="请输入备注"></Input>
</FormItem>
</Form>
</Modal>
<!-- 编辑规则 Modal -->
<Modal v-model="showEditModal" title="编辑维护规则" @on-ok="handleEditSubmit" @on-cancel="handleEditCancel">
<Form ref="editForm" :model="editForm" :rules="rules" :label-width="120">
<FormItem label="规则名称" prop="name">
<Input v-model="editForm.name" placeholder="请输入规则名称"></Input>
</FormItem>
<FormItem label="重复周期" prop="cycle">
<Select v-model="editForm.cycle">
<Option value="monthly">每月</Option>
<Option value="quarterly">每季度</Option>
<Option value="half">每半年</Option>
<Option value="yearly">每年</Option>
</Select>
</FormItem>
<FormItem label="起始日期设定" prop="startSetting">
<Select v-model="editForm.startSetting">
<Option value="specific_date">从每年第一天开始运算</Option>
<Option value="first_association">首次关联物资时</Option>
</Select>
</FormItem>
<FormItem label="生成计划时机" prop="timing">
<div class="timing-controls">
<Select v-model="editForm.timingUnit" style="width: 80px">
<Option value="days">提前</Option>
</Select>
<InputNumber v-model="editForm.timingValue" :min="1" style="width: 80px"></InputNumber>
<Select v-model="editForm.timingPeriod" style="width: 80px">
<Option value="days"></Option>
</Select>
</div>
</FormItem>
<FormItem label="未完成时下次计划" prop="nextPlan">
<Select v-model="editForm.nextPlan">
<Option value="skip">暂停生成直到上次计划已经执行完成</Option>
<Option value="force">强制生成忽略未完成状态</Option>
</Select>
</FormItem>
<FormItem label="备注" prop="notes">
<Input v-model="editForm.notes" type="textarea" :rows="3" placeholder="请输入备注"></Input>
</FormItem>
</Form>
</Modal>
<!-- 删除确认 Modal -->
<Modal v-model="showDeleteModal" title="删除确认" @on-ok="handleDeleteConfirm" @on-cancel="handleDeleteCancel">
<p>确定要删除该维护规则吗此操作不可恢复</p>
</Modal>
<!-- 关联物资 Modal -->
<Modal v-model="showAssociateModal" title="关联物资" @on-ok="handleAssociateSubmit" @on-cancel="handleAssociateCancel">
<div class="associate-content">
<div class="search-box">
<Input v-model="associateSearch" placeholder="搜索物资" style="width: 200px; margin-bottom: 16px;"></Input>
</div>
<Table :columns="associateColumns" :data="associateList" :height="400">
<template slot-scope="{ row }" slot="selection">
<Checkbox v-model="row.selected"></Checkbox>
</template>
</Table>
</div>
</Modal>
</div>
</template>
<script>
import { Button, Modal, Form, FormItem, Input, Select, Option, InputNumber, Table, Checkbox } from 'view-design';
export default {
components: {
Button,
Modal,
Form,
FormItem,
Input,
Select,
Option,
InputNumber,
Table,
Checkbox
},
data() {
return {
select: {
page: 1,
page_size: 10,
},
total: 0,
list: [],
table: [
{ label: '序号', type: 'index', width: 60 },
{ label: '规则名称', prop: 'name', minWidth: 160 },
{ label: '重复周期', prop: 'cycle', minWidth: 120 },
{ label: '起始日期设定', prop: 'startSetting', minWidth: 160 },
{ label: '生成计划时机', prop: 'timing', minWidth: 120 },
{ label: '已用于物资数', prop: 'materialCount', minWidth: 120 },
{ label: '未完成时下次计划', prop: 'nextPlan', minWidth: 160 }
],
tableHeight: 550,
showModal: false,
showEditModal: false,
showDeleteModal: false,
showAssociateModal: false,
form: {
name: '',
cycle: '',
startSetting: '',
timingUnit: 'days',
timingValue: 7,
timingPeriod: 'days',
nextPlan: '',
notes: ''
},
editForm: {
name: '',
cycle: '',
startSetting: '',
timingUnit: 'days',
timingValue: 7,
timingPeriod: 'days',
nextPlan: '',
notes: ''
},
rules: {
name: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
cycle: [{ required: true, message: '请选择重复周期', trigger: 'change' }],
startSetting: [{ required: true, message: '请选择起始日期设定', trigger: 'change' }],
nextPlan: [{ required: true, message: '请选择未完成时下次计划', trigger: 'change' }]
},
associateSearch: '',
associateColumns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '物资名称',
key: 'name',
minWidth: 120
},
{
title: '物资编号',
key: 'code',
minWidth: 120
},
{
title: '仓库',
key: 'warehouse',
minWidth: 120
}
],
associateList: [
{
name: '3号水泵',
code: 'SB001',
warehouse: '外塘河仓库',
selected: false
},
{
name: '柴油发电机',
code: 'FD001',
warehouse: '胥口仓库',
selected: false
}
],
currentRule: null
}
},
created() {
this.getList()
},
mounted() {
this.calcTableHeight();
window.addEventListener('resize', this.calcTableHeight);
},
beforeDestroy() {
window.removeEventListener('resize', this.calcTableHeight);
},
methods: {
async getList() {
try {
// TODO: API
this.list = [
{
name: '水泵月度检查',
cycle: '每月',
startSetting: '每年第一天开始运算',
timing: '提前 7 天',
materialCount: 5,
nextPlan: '暂停生成'
},
{
name: '发电机季度保养',
cycle: '每季度',
startSetting: '首次关联物资时',
timing: '提前 14 天',
materialCount: 2,
nextPlan: '强制生成'
},
{
name: '挡板年度检修',
cycle: '每年',
startSetting: '每年第一天开始运算',
timing: '提前 30 天',
materialCount: 12,
nextPlan: '暂停生成'
}
];
this.total = this.list.length;
} catch (e) {
this.$message.error('获取维护规则失败');
}
},
pageChange(e) {
this.select.page = e
this.getList()
},
pageSizeChange(e) {
this.select.page_size = e
this.getList()
},
calcTableHeight() {
const header = this.$refs.lxHeader ? this.$refs.lxHeader.offsetHeight : 0;
const windowHeight = window.innerHeight;
const minTableHeight = 600;
this.tableHeight = Math.max(windowHeight - header - 100, minTableHeight);
},
showAddRuleModal() {
this.showModal = true;
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
// TODO:
this.$message.success('保存成功');
this.showModal = false;
this.getList();
}
});
},
handleCancel() {
this.showModal = false;
this.$refs.form.resetFields();
},
handleEdit(row) {
this.currentRule = row;
this.editForm = {
name: row.name,
cycle: row.cycle,
startSetting: row.startSetting,
timingUnit: 'days',
timingValue: 7,
timingPeriod: 'days',
nextPlan: row.nextPlan,
notes: row.notes || ''
};
this.showEditModal = true;
},
handleEditSubmit() {
this.$refs.editForm.validate((valid) => {
if (valid) {
// TODO:
this.$message.success('编辑成功');
this.showEditModal = false;
this.getList();
}
});
},
handleEditCancel() {
this.showEditModal = false;
this.$refs.editForm.resetFields();
},
handleDelete(row) {
this.currentRule = row;
this.showDeleteModal = true;
},
handleDeleteConfirm() {
// TODO:
this.$message.success('删除成功');
this.showDeleteModal = false;
this.getList();
},
handleDeleteCancel() {
this.showDeleteModal = false;
},
handleAssociate(row) {
this.currentRule = row;
this.showAssociateModal = true;
},
handleAssociateSubmit() {
// TODO:
this.$message.success('关联成功');
this.showAssociateModal = false;
this.getList();
},
handleAssociateCancel() {
this.showAssociateModal = false;
}
}
}
</script>
<style>
.table-page-container {
display: flex;
flex-direction: column;
height: 100vh;
box-sizing: border-box;
background: #f5f7fa;
}
.table-flex-content {
flex: 1 1 0;
display: flex;
flex-direction: column;
min-height: 0;
}
.el-table th {
background: #f5f7fa !important;
color: #333;
font-weight: bold;
font-size: 15px;
}
.el-table td {
font-size: 14px;
height: 48px;
}
.el-table {
border-radius: 10px;
overflow: hidden;
width: 100%;
height: 100%;
}
.timing-controls {
display: flex;
align-items: center;
gap: 8px;
}
</style>

@ -49,8 +49,8 @@
<div class="xy-table-item type-dialog-item">
<div class="xy-table-item-label">原值</div>
<div class="xy-table-item-content"><el-input-number v-model="form.original_value" style="width:100%;" :min="0" :step="0.01" :precision="2" placeholder="请输入原值" /></div>
</div>
</div>
</div>
</div>
<div class="form-row">
<div class="xy-table-item type-dialog-item">
<div class="xy-table-item-label">使用科室</div>
@ -59,8 +59,8 @@
<div class="xy-table-item type-dialog-item">
<div class="xy-table-item-label">保管人</div>
<div class="xy-table-item-content"><Input v-model="form.keeper" style="width:100%;" placeholder="请输入保管人" /></div>
</div>
</div>
</div>
</div>
<div class="form-row">
<div class="xy-table-item type-dialog-item">
<div class="xy-table-item-label">存放地点</div>
@ -70,12 +70,12 @@
<div class="xy-table-item-label">所属库</div>
<div class="xy-table-item-content"><Input v-model="form.warehouse" style="width:100%;" placeholder="请输入所属库" /></div>
</div>
</div>
</div>
<div class="form-row">
<div class="xy-table-item type-dialog-item">
<div class="xy-table-item-label">使用方向</div>
<div class="xy-table-item-content"><Input v-model="form.usage_direction" style="width:100%;" placeholder="请输入使用方向" /></div>
</div>
</div>
<div class="xy-table-item type-dialog-item">
<div class="xy-table-item-label">备注</div>
<div class="xy-table-item-content"><Input v-model="form.remark" style="width:100%;" placeholder="请输入备注" /></div>

Loading…
Cancel
Save