医院报废器材登记系统 鸿蒙PC Electron框架技术实现详解
·
欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/
atomgit仓库地址: https://gitcode.com/m0_66062719/baofeiqicaidengji





一、项目概述与设计理念
1.1 应用背景
医院设备管理是医疗行业的重要组成部分,报废器材登记是设备生命周期管理的关键环节。本系统旨在帮助医院高效管理医疗设备报废流程,提高设备管理效率,确保医疗安全。
┌─────────────────────────────────────────────────────┐
│ 用户需求分析 │
│ ├─ 医疗设备报废登记管理 │
│ ├─ 报废记录查询和统计 │
│ ├─ 设备类型分类管理 │
│ ├─ 数据统计分析展示 │
│ └─ 本地数据持久化存储 │
└─────────────────────────────────────────────────────┘
1.2 技术选型
| 技术方案 | 优势 | 适用场景 |
|---|---|---|
| HTML5/CSS3 | 快速开发,样式丰富 | 页面布局和样式 |
| JavaScript ES6+ | 现代语法,计算能力强 | 交互控制和数据管理 |
| LocalStorage | 本地存储,无需服务器 | 数据持久化 |
1.3 功能模块划分
┌─────────────────────────────────────────────────────┐
│ 医院报废器材登记系统功能模块 │
├─────────────────────────────────────────────────────┤
│ 📝 报废登记 │ 📋 报废列表 │ 📊 统计分析 │
│ ⚙️ 类型管理 │ │
└─────────────────────────────────────────────────────┘
二、核心代码实现详解
2.1 数据结构设计
// 报废记录数据结构
const scrapRecords = [
{
id: 1,
name: '心电图机',
type: 'diagnostic',
brand: '飞利浦',
model: 'TC20',
purchaseDate: '2015-06-15',
scrapDate: '2024-03-15',
department: '心内科',
reason: 'aging',
description: '设备使用超过8年,性能下降。',
value: 120000,
handler: '张医生'
},
{
id: 2,
name: '血压计',
type: 'measuring',
brand: '欧姆龙',
model: 'HEM-7200',
purchaseDate: '2018-03-20',
scrapDate: '2024-03-10',
department: '急诊科',
reason: 'precision',
description: '测量误差超过允许范围。',
value: 800,
handler: '李护士'
}
// ...更多数据
];
// 器材类型配置
const equipmentTypes = {
diagnostic: { name: '诊断设备', icon: '🔬', color: '#3b82f6' },
measuring: { name: '测量设备', icon: '📏', color: '#8b5cf6' },
treatment: { name: '治疗设备', icon: '💉', color: '#ec4899' },
surgical: { name: '手术器械', icon: '⚔️', color: '#10b981' },
other: { name: '其他设备', icon: '📦', color: '#f59e0b' }
};
// 报废原因配置
const scrapReasons = {
aging: { name: '设备老化', description: '使用年限到期' },
precision: { name: '精度不达标', description: '测量精度不足' },
damage: { name: '设备损坏', description: '设备损坏无法修复' },
upgrade: { name: '技术更新', description: '更换新型号' },
safety: { name: '安全隐患', description: '存在安全隐患' },
other: { name: '其他原因', description: '其他原因' }
};
数据结构说明:
报废记录包含以下字段:
├─ id: 唯一标识符
├─ name: 器材名称
├─ type: 器材类型(diagnostic/measuring/treatment/surgical/other)
├─ brand: 品牌
├─ model: 型号
├─ purchaseDate: 购入日期
├─ scrapDate: 报废日期
├─ department: 所属科室
├─ reason: 报废原因
├─ description: 详细说明
├─ value: 原值(元)
└─ handler: 经办人
2.2 报废登记表单处理
// 处理表单提交
function handleFormSubmit(event) {
event.preventDefault();
const formData = {
id: Date.now(),
name: document.getElementById('equipmentName').value,
type: document.getElementById('equipmentType').value,
brand: document.getElementById('brand').value,
model: document.getElementById('model').value,
purchaseDate: document.getElementById('purchaseDate').value,
scrapDate: document.getElementById('scrapDate').value,
department: document.getElementById('department').value,
reason: document.getElementById('scrapReason').value,
description: document.getElementById('description').value,
value: parseFloat(document.getElementById('value').value) || 0,
handler: document.getElementById('handler').value
};
// 添加到记录列表
scrapRecords.unshift(formData);
// 保存到本地存储
saveToStorage();
// 重置表单
resetForm();
// 更新界面
renderScrapList();
updateStatistics();
renderTypeManagement();
alert('报废登记成功!');
switchTab('list');
}
// 重置表单
function resetForm() {
document.getElementById('scrapForm').reset();
}
表单字段说明:
表单字段说明:
┌─────────────────────────────────────────────────────┐
│ 字段 │ 类型 │ 必填 │ 说明 │
│ ────────────────┼─────────┼─────┼────────────── │
│ equipmentName │ text │ 是 │ 器材名称 │
│ equipmentType │ select │ 是 │ 器材类型 │
│ brand │ text │ 否 │ 品牌 │
│ model │ text │ 否 │ 型号 │
│ purchaseDate │ date │ 否 │ 购入日期 │
│ scrapDate │ date │ 是 │ 报废日期 │
│ department │ text │ 否 │ 所属科室 │
│ scrapReason │ select │ 是 │ 报废原因 │
│ description │ textarea│ 否 │ 详细说明 │
│ value │ number │ 否 │ 原值(元) │
│ handler │ text │ 否 │ 经办人 │
└─────────────────────────────────────────────────────┘
2.3 报废列表渲染
// 渲染报废列表
function renderScrapList() {
const tbody = document.getElementById('scrapTableBody');
tbody.innerHTML = '';
// 根据筛选条件过滤
const filteredRecords = currentFilter === 'all'
? scrapRecords
: scrapRecords.filter(r => r.type === currentFilter);
filteredRecords.forEach(record => {
const typeInfo = equipmentTypes[record.type];
const reasonInfo = scrapReasons[record.reason];
const row = document.createElement('tr');
row.innerHTML = `
<td>${record.name}</td>
<td><span class="type-badge" style="background: ${typeInfo.color}20; color: ${typeInfo.color}">${typeInfo.icon} ${typeInfo.name}</span></td>
<td>${record.brand || '-'}</td>
<td>${record.scrapDate}</td>
<td>${reasonInfo.name}</td>
<td>${record.department || '-'}</td>
<td>
<div class="table-actions">
<button class="action-btn view" onclick="showDetail(${record.id})">查看</button>
<button class="action-btn delete" onclick="deleteRecord(${record.id})">删除</button>
</div>
</td>
`;
tbody.appendChild(row);
});
// 如果没有记录
if (filteredRecords.length === 0) {
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = `<td colspan="7" style="text-align: center; padding: 30px;">暂无记录</td>`;
tbody.appendChild(emptyRow);
}
}
// 筛选记录
function filterRecords(type) {
currentFilter = type;
// 更新筛选按钮状态
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`[data-type="${type}"]`).classList.add('active');
renderScrapList();
}
2.4 统计分析模块
// 更新统计信息
function updateStatistics() {
// 1. 总报废数
document.getElementById('totalRecords').textContent = scrapRecords.length;
// 2. 原值总计
const totalValue = scrapRecords.reduce((sum, r) => sum + r.value, 0);
document.getElementById('totalValue').textContent = totalValue.toLocaleString();
// 3. 本月报废数
const currentMonth = new Date().toISOString().substring(0, 7);
const monthRecords = scrapRecords.filter(r => r.scrapDate.startsWith(currentMonth)).length;
document.getElementById('monthRecords').textContent = monthRecords;
// 4. 设备类型数
const typeCount = new Set(scrapRecords.map(r => r.type)).size;
document.getElementById('typeCount').textContent = typeCount;
// 更新图表
renderTypeChart();
renderMonthChart();
}
// 渲染类型统计图表
function renderTypeChart() {
const chart = document.getElementById('typeChart');
chart.innerHTML = '';
const typeStats = {};
Object.keys(equipmentTypes).forEach(type => {
typeStats[type] = scrapRecords.filter(r => r.type === type).length;
});
const maxCount = Math.max(...Object.values(typeStats), 1);
Object.keys(typeStats).forEach(type => {
const info = equipmentTypes[type];
const count = typeStats[type];
const height = (count / maxCount) * 100;
const bar = document.createElement('div');
bar.className = 'chart-bar';
bar.innerHTML = `
<div class="bar-fill" style="height: ${height}%; background: ${info.color};"></div>
<div class="bar-value">${count}</div>
<div class="bar-label">${info.name}</div>
`;
chart.appendChild(bar);
});
}
// 渲染月份统计图表
function renderMonthChart() {
const chart = document.getElementById('monthChart');
chart.innerHTML = '';
// 获取最近6个月的数据
const months = [];
const now = new Date();
for (let i = 5; i >= 0; i--) {
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
months.push(date.toISOString().substring(0, 7));
}
const monthStats = {};
months.forEach(month => {
monthStats[month] = scrapRecords.filter(r => r.scrapDate.startsWith(month)).length;
});
const maxCount = Math.max(...Object.values(monthStats), 1);
months.forEach(month => {
const count = monthStats[month];
const height = (count / maxCount) * 100;
const displayMonth = month.substring(2); // 显示 YY-MM 格式
const bar = document.createElement('div');
bar.className = 'chart-bar';
bar.innerHTML = `
<div class="bar-fill" style="height: ${height}%; background: var(--primary);"></div>
<div class="bar-value">${count}</div>
<div class="bar-label">${displayMonth}</div>
`;
chart.appendChild(bar);
});
}
三、类型管理功能
3.1 类型管理渲染
// 渲染类型管理
function renderTypeManagement() {
const grid = document.getElementById('typesGrid');
grid.innerHTML = '';
Object.keys(equipmentTypes).forEach(type => {
const info = equipmentTypes[type];
const count = scrapRecords.filter(r => r.type === type).length;
const card = document.createElement('div');
card.className = 'type-card';
card.innerHTML = `
<div class="type-icon">${info.icon}</div>
<div class="type-info">
<div class="type-name">${info.name}</div>
<div class="type-count">${count} 件报废</div>
</div>
`;
grid.appendChild(card);
});
}
// 添加器材类型
function addEquipmentType() {
const name = document.getElementById('newTypeName').value.trim();
const icon = document.getElementById('newTypeIcon').value.trim();
if (!name) {
alert('请输入类型名称');
return;
}
const newKey = name.toLowerCase().replace(/\s+/g, '-');
equipmentTypes[newKey] = {
name: name,
icon: icon || '📦',
color: getRandomColor()
};
closeModal();
renderTypeManagement();
alert('类型添加成功!');
}
// 生成随机颜色
function getRandomColor() {
const colors = ['#3b82f6', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b', '#ef4444', '#06b6d4'];
return colors[Math.floor(Math.random() * colors.length)];
}
3.2 详情弹窗功能
// 显示详情
function showDetail(id) {
const record = scrapRecords.find(r => r.id === id);
if (!record) return;
const typeInfo = equipmentTypes[record.type];
const reasonInfo = scrapReasons[record.reason];
const modal = document.getElementById('detailModal');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
modalTitle.textContent = record.name;
modalBody.innerHTML = `
<div class="detail-grid">
<div>
<div class="detail-title">器材类型</div>
<div class="detail-value">${typeInfo.icon} ${typeInfo.name}</div>
</div>
<div>
<div class="detail-title">品牌</div>
<div class="detail-value">${record.brand || '-'}</div>
</div>
<div>
<div class="detail-title">型号</div>
<div class="detail-value">${record.model || '-'}</div>
</div>
<div>
<div class="detail-title">原值</div>
<div class="detail-value">¥${record.value.toLocaleString()}</div>
</div>
<div>
<div class="detail-title">购入日期</div>
<div class="detail-value">${record.purchaseDate || '-'}</div>
</div>
<div>
<div class="detail-title">报废日期</div>
<div class="detail-value">${record.scrapDate}</div>
</div>
<div>
<div class="detail-title">所属科室</div>
<div class="detail-value">${record.department || '-'}</div>
</div>
<div>
<div class="detail-title">报废原因</div>
<div class="detail-value">${reasonInfo.name}</div>
</div>
<div>
<div class="detail-title">经办人</div>
<div class="detail-value">${record.handler || '-'}</div>
</div>
</div>
<div class="detail-section">
<div class="detail-title">详细说明</div>
<div class="detail-value">${record.description || '-'}</div>
</div>
`;
modal.classList.add('active');
}
四、数据持久化
4.1 本地存储实现
// 保存到本地存储
function saveToStorage() {
try {
localStorage.setItem('medicalScrapData', JSON.stringify({
records: scrapRecords,
types: equipmentTypes
}));
} catch (e) {
console.log('无法保存到本地存储');
}
}
// 从本地存储加载
function loadFromStorage() {
try {
const data = localStorage.getItem('medicalScrapData');
if (data) {
const parsed = JSON.parse(data);
// 只在数据可用时使用,这里保持默认数据
}
} catch (e) {
console.log('无法从本地存储加载数据');
}
}
五、视觉效果设计
5.1 绿色医疗主题
:root {
--primary: #10b981;
--primary-dark: #059669;
--secondary: #06b6d4;
--bg-primary: linear-gradient(135deg, #0f172a 0%, #164e63 50%, #0f172a 100%);
--bg-card: rgba(15, 23, 42, 0.8);
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--border-color: rgba(148, 163, 184, 0.2);
--shadow: 0 4px 20px rgba(16, 185, 129, 0.15);
}
5.2 统计卡片样式
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
display: flex;
align-items: center;
gap: 16px;
padding: 24px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
border-radius: var(--radius);
transition: var(--transition);
}
.stat-card:hover {
border-color: var(--primary);
}
.stat-icon {
width: 56px;
height: 56px;
background: linear-gradient(135deg, rgba(16, 185, 129, 0.2), rgba(6, 182, 212, 0.2));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
}
六、技术亮点与创新
6.1 动态图表生成
技术亮点:
┌─────────────────────────────────────────────────────┐
│ 1. 类型统计图表 - 根据数据动态生成柱状图 │
│ 2. 月度统计图表 - 自动获取最近6个月数据 │
│ 3. 自适应高度 - 根据最大值自动计算比例 │
│ 4. 颜色编码 - 不同类型使用不同颜色 │
└─────────────────────────────────────────────────────┘
6.2 数据管理
技术亮点:
┌─────────────────────────────────────────────────────┐
│ 1. 数据持久化 - localStorage本地存储 │
│ 2. 实时更新 - 添加记录后自动更新UI │
│ 3. 筛选功能 - 按类型筛选记录 │
│ 4. 删除确认 - 二次确认防止误删 │
└─────────────────────────────────────────────────────┘
七、总结与展望
7.1 项目成果
| 功能模块 | 状态 | 核心特性 |
|---|---|---|
| 报废登记 | ✅ | 完整表单、数据验证、自动保存 |
| 报废列表 | ✅ | 表格展示、类型筛选、详情查看 |
| 统计分析 | ✅ | 数值统计、动态图表、月度趋势 |
| 类型管理 | ✅ | 类型展示、添加新类型 |
7.2 未来规划
- 数据导入导出 - 支持Excel格式导入导出
- 高级筛选 - 按日期范围、科室等多条件筛选
- 报表生成 - 自动生成报废报表
- 权限管理 - 多用户角色管理
- 移动端适配 - 优化移动端体验
7.3 技术价值
医院报废器材登记系统展示了如何构建一个专业的医疗设备管理应用,为开发者提供了以下参考:
- 表单数据处理和验证
- 动态图表生成
- 数据持久化存储
- 响应式布局设计
通过本项目的实践,开发者可以快速掌握医疗管理应用开发的核心技术,为构建更多优秀应用奠定基础。
更多推荐


所有评论(0)