项目概述
这是一个基于Web的专利管理系统,专门为鸿蒙系统环境优化,支持专利信息管理、年费提醒、数据导入导出等功能。
技术架构
- 前端: HTML5, CSS3, JavaScript ES6
- 存储: 浏览器localStorage
- 数据格式: CSV
- 兼容性: 鸿蒙系统及主流浏览器
核心功能
1. 专利信息管理
- 添加专利: 通过表单录入专利基本信息
- 编辑专利: 修改现有专利信息
- 删除专利: 移除不需要的专利记录
- 搜索专利: 按名称、专利号、申请人等字段搜索
2. 年费管理
- 缴费提醒: 自动计算年费缴纳时间
- 颜色标识 :
- 黄色背景:60天内到期(即将缴费)
- 红色背景:已逾期未缴费
- 状态显示: 显示距离缴费日的天数
3. 数据导入导出
- CSV导出: 将所有专利数据导出为CSV文件
- CSV导入: 从CSV文件批量导入专利数据
- 数据备份: 支持数据备份和恢复
数据字段说明
表格
| 字段名 | 类型 | 说明 |
|---|---|---|
| name | String | 专利名称(必填) |
| number | String | 专利号(必填,唯一) |
| inventor | String | 发明人 |
| applicant | String | 申请人 |
| contact | String | 联系电话 |
| feeAmount | Number | 年费金额(元) |
| filingDate | Date | 申请日期 |
| grantDate | Date | 授权日期 |
| dueDate | Date | 年费缴纳日(必填) |
| type | String | 专利类型(发明/实用新型/外观设计) |
| status | String | 状态(申请中/已授权/失效) |
| notes | String | 备注信息 |
用户界面
主界面布局
text
编辑
[统计卡片区] - 总专利数、即将缴费、已逾期数量
[添加表单区] - 新增/编辑专利信息
[操作工具栏] - 搜索框、导出导入、清空数据按钮
[数据表格区] - 展示所有专利信息
统计面板
- 总专利数:当前系统中专利总数
- 即将缴费:未来60天内需要缴费的专利数
- 已逾期:已经超过缴费期限的专利数
操作指南
添加新专利
- 在表单中填写专利信息
- 确保专利号不重复
- 点击"保存专利"按钮
编辑专利
- 在表格中点击对应专利的"编辑"按钮
- 修改所需字段
- 提交保存
导入CSV数据
- 准备符合格式的CSV文件
- 点击"导入数据(CSV)"按钮
- 选择文件并确认导入
- 数据将覆盖现有记录
导出CSV数据
- 点击"导出数据(CSV)"按钮
- 自动下载包含所有专利信息的CSV文件
数据验证规则
- 专利名称和专利号为必填项
- 专利号需保持唯一性
- 日期格式为YYYY-MM-DD
- 年费金额为数字格式
鸿蒙系统特性
- 鸿蒙系统环境自动检测
- 离线缓存支持(Service Worker)
- 鸿蒙字体优化显示
- 系统级兼容性适配
文件结构
- 单一HTML文件部署
- 内嵌CSS和JavaScript
- 无需服务器环境
- 支持本地运行
错误处理
- 重复专利号提示
- CSV格式错误处理
- 数据导入验证
- 空数据保护
维护说明
- 数据自动保存至localStorage
- 支持数据清空重置
- 支持批量数据操作
- 提供数据完整性校验
系统要求
- 支持localStorage的现代浏览器
- 鸿蒙系统或主流操作系统
- 无特殊硬件要求
版本信息
- 基于HTML5标准构建
- 零依赖,轻量级应用
- 持续更新维护
该系统适用于个人和小型机构的专利管理工作,提供了完整的数据生命周期管理功能。
详细代码说明
html预览
<!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>
:root {
--primary-color: #007BFF;
--warning-color: #FFC107;
--danger-color: #DC3545;
--success-color: #28A745;
--border-color: #dee2e6;
}
定义CSS变量,包括主色调、警告色、危险色、成功色和边框颜色。
css
编辑
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
重置所有元素的默认边距和内边距,并设置盒模型为border-box。
css
编辑
body {
font-family: 'HarmonyOS Sans', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
padding: 20px;
}
设置页面字体(优先使用鸿蒙系统字体)、行高、文字颜色和背景色。
css
编辑
.container {
max-width: 1400px;
margin: 0 auto;
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
}
定义主容器样式,居中显示,白色背景,圆角和阴影效果。
css
编辑
h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
border-bottom: 3px solid var(--primary-color);
padding-bottom: 10px;
}
标题样式,居中对齐,底部添加蓝色边框。
css
编辑
.form-container {
background: #f1f3f5;
padding: 20px;
border-radius: 6px;
margin-bottom: 30px;
border: 1px solid var(--border-color);
}
表单容器样式,浅灰色背景,圆角和边框。
css
编辑
.form-row {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 15px;
}
.form-group {
flex: 1 1 300px;
}
表单行使用弹性布局,允许换行,每项最小宽度300px。
css
编辑
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #495057;
}
标签样式,块级显示,加粗,深灰色文字。
css
编辑
input[type="text"],
input[type="number"],
input[type="date"],
input[type="tel"],
select,
textarea {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
}
统一设置各种输入控件的样式,占满父容器宽度。
css
编辑
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s ease;
}
按钮基础样式,无边框,圆角,悬停过渡效果。
css
编辑
.btn-primary { background-color: var(--primary-color); color: white; }
.btn-success { background-color: var(--success-color); color: white; }
.btn-danger { background-color: var(--danger-color); color: white; }
.btn-warning { background-color: var(--warning-color); color: #000; }
.btn-secondary { background-color: #6c757d; color: white; }
.btn:disabled { background-color: #6c757d; cursor: not-allowed; }
定义不同类型的按钮颜色主题。
css
编辑
.actions-bar {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
操作栏使用弹性布局,两端对齐,支持换行。
css
编辑
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
box-shadow: 0 0 5px rgba(0,0,0,0.05);
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
th {
background-color: #f8f9fa;
font-weight: bold;
color: #495057;
position: sticky;
top: 0;
}
tr:hover {
background-color: #f1f3f5;
transition: background-color 0.2s;
}
表格样式,合并边框,悬停效果,表头固定。
css
编辑
.status-soon {
background-color: #fff3cd !important; /* 黄色背景:即将到期 */
font-weight: bold;
}
.status-overdue {
background-color: #f8d7da !important; /* 红色背景:已逾期 */
color: #721c24;
font-weight: bold;
}
定义状态样式,即将到期用黄色背景,已逾期用红色背景。
css
编辑
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
模态框样式,半透明遮罩层,居中弹窗。
css
编辑
.stats {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 200px;
padding: 15px;
background: #f8f9fa;
border-left: 4px solid var(--primary-color);
border-radius: 4px;
}
.stat-card.warning { border-left-color: var(--warning-color); }
.stat-card.danger { border-left-color: var(--danger-color); }
.stat-number {
font-size: 24px;
font-weight: bold;
margin: 5px 0;
}
统计卡片样式,左侧不同颜色的装饰条表示不同状态。
css
编辑
@media (max-width: 768px) {
.container { padding: 15px; }
.form-row { flex-direction: column; }
.form-group { flex: 1 1 100%; }
.stats { flex-direction: column; }
.actions-bar { flex-direction: column; }
.action-buttons { flex-direction: column; }
table { font-size: 14px; }
th, td { padding: 8px 10px; }
}
响应式设计,在小屏幕设备上调整布局。
html
预览
</head>
<body>
<div class="container">
<div class="harmony-logo">
<h1>📝 专利管理系统</h1>
<span class="harmony-badge">鸿蒙系统优化版</span>
</div>
<!-- 统计卡片 -->
<div class="stats">
<div class="stat-card">
<div>总专利数</div>
<div id="total-count" class="stat-number">0</div>
</div>
<div class="stat-card warning">
<div>即将缴费 (60天内)</div>
<div id="soon-count" class="stat-number">0</div>
</div>
<div class="stat-card danger">
<div>已逾期</div>
<div id="overdue-count" class="stat-number">0</div>
</div>
</div>
页面头部和统计卡片区域,显示总专利数、即将缴费和已逾期的数量。
html
预览
<!-- 添加专利表单 -->
<div class="form-container">
<h2>添加新专利</h2>
<form id="patentForm">
<div class="form-row">
<div class="form-group">
<label>专利名称 *</label>
<input type="text" id="name" required>
</div>
<div class="form-group">
<label>专利号 *</label>
<input type="text" id="number" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>发明人</label>
<input type="text" id="inventor">
</div>
<div class="form-group">
<label>申请人</label>
<input type="text" id="applicant">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>联系电话</label>
<input type="tel" id="contact">
</div>
<div class="form-group">
<label>年费金额 (元)</label>
<input type="number" id="feeAmount" min="0" step="0.01">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>申请日期</label>
<input type="date" id="filingDate">
</div>
<div class="form-group">
<label>授权日期</label>
<input type="date" id="grantDate">
</div>
<div class="form-group">
<label>年费缴纳日 *</label>
<input type="date" id="dueDate" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>专利类型</label>
<select id="type">
<option value="发明专利">发明专利</option>
<option value="实用新型">实用新型</option>
<option value="外观设计">外观设计</option>
</select>
</div>
<div class="form-group">
<label>状态</label>
<select id="status">
<option value="申请中">申请中</option>
<option value="已授权">已授权</option>
<option value="失效">失效</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>备注</label>
<textarea id="notes"></textarea>
</div>
</div>
<button type="submit" class="btn btn-primary">保存专利</button>
<button type="button" id="cancelEdit" class="btn btn-danger" style="display:none;">取消编辑</button>
</form>
</div>
添加专利的表单,包含专利的各种属性字段。
html
预览
<!-- 操作与表格 -->
<div class="actions-bar">
<input type="text" id="searchInput" class="search-box" placeholder="搜索专利名称或专利号...">
<div class="action-buttons">
<button id="exportBtn" class="btn btn-success">导出数据 (CSV)</button>
<button id="importBtn" class="btn btn-secondary">导入数据 (CSV)</button>
<button id="clearBtn" class="btn btn-danger">清空所有数据</button>
</div>
</div>
<table id="patentTable">
<thead>
<tr>
<th>专利名称</th>
<th>专利号</th>
<th>申请人</th>
<th>联系方式</th>
<th>年费金额</th>
<th>类型</th>
<th>年费缴纳日</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 数据将通过 JS 动态插入 -->
</tbody>
</table>
</div>
操作栏包含搜索框和数据导入导出按钮,表格用于显示专利列表。
html
预览
<!-- 编辑模态框 -->
<div id="editModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>编辑专利信息</h2>
<form id="editForm">
<input type="hidden" id="editIndex">
<div class="form-row">
<div class="form-group"><label>专利名称</label><input type="text" id="editName" required></div>
<div class="form-group"><label>专利号</label><input type="text" id="editNumber" required></div>
</div>
<div class="form-row">
<div class="form-group"><label>发明人</label><input type="text" id="editInventor"></div>
<div class="form-group"><label>申请人</label><input type="text" id="editApplicant"></div>
</div>
<div class="form-row">
<div class="form-group"><label>联系电话</label><input type="tel" id="editContact"></div>
<div class="form-group"><label>年费金额 (元)</label><input type="number" id="editFeeAmount" min="0" step="0.01"></div>
</div>
<div class="form-row">
<div class="form-group"><label>申请日期</label><input type="date" id="editFilingDate"></div>
<div class="form-group"><label>授权日期</label><input type="date" id="editGrantDate"></div>
<div class="form-group"><label>年费缴纳日</label><input type="date" id="editDueDate" required></div>
</div>
<div class="form-row">
<div class="form-group">
<label>类型</label>
<select id="editType">
<option value="发明专利">发明专利</option>
<option value="实用新型">实用新型</option>
<option value="外观设计">外观设计</option>
</select>
</div>
<div class="form-group">
<label>状态</label>
<select id="editStatus">
<option value="申请中">申请中</option>
<option value="已授权">已授权</option>
<option value="失效">失效</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group"><label>备注</label><textarea id="editNotes"></textarea></div>
</div>
<button type="submit" class="btn btn-primary">更新专利</button>
</form>
</div>
</div>
编辑专利的模态框,包含与添加表单相同的字段。
javascript
编辑
<script>
// 模拟数据库 (使用 localStorage)
let patents = JSON.parse(localStorage.getItem('patents')) || [];
// DOM 元素
const patentForm = document.getElementById('patentForm');
const patentTableBody = document.querySelector('#patentTable tbody');
const searchInput = document.getElementById('searchInput');
const cancelEditBtn = document.getElementById('cancelEdit');
const exportBtn = document.getElementById('exportBtn');
const importBtn = document.getElementById('importBtn');
const clearBtn = document.getElementById('clearBtn');
// 统计元素
const totalCountEl = document.getElementById('total-count');
const soonCountEl = document.getElementById('soon-count');
const overdueCountEl = document.getElementById('overdue-count');
// 模态框元素
const editModal = document.getElementById('editModal');
const closeModal = document.querySelector('.close');
const editForm = document.getElementById('editForm');
// 编辑状态
let isEditing = null;
JavaScript初始化,获取DOM元素,定义全局变量。
javascript
编辑
// ================= 工具函数 =================
// 保存到 LocalStorage
function saveToStorage() {
localStorage.setItem('patents', JSON.stringify(patents));
}
// 格式化日期 (YYYY-MM-DD)
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN');
}
// 计算日期差 (天数)
function getDateDiffInDays(date1, date2) {
const diffTime = Math.abs(date2 - date1);
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
// 获取专利状态类名 (用于表格高亮)
function getStatusClass(dueDateStr) {
if (!dueDateStr) return '';
const dueDate = new Date(dueDateStr);
const today = new Date();
today.setHours(0,0,0,0);
dueDate.setHours(0,0,0,0);
// 如果年费日小于今天,说明已过期
if (dueDate < today) {
return 'status-overdue';
}
// 如果在60天内
const diffDays = getDateDiffInDays(today, dueDate);
if (diffDays <= 60) {
return 'status-soon';
}
return '';
}
定义工具函数,包括数据存储、日期格式化、计算日期差和状态判断。
javascript
编辑
// 解析CSV数据
function parseCSV(csvText) {
const lines = csvText.split(/\r?\n/);
const headers = lines[0].split(',').map(header => header.trim().replace(/^"|"$/g, ''));
const result = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '') continue;
const values = [];
let current = '';
let inQuotes = false;
for (let j = 0; j < lines[i].length; j++) {
const char = lines[i][j];
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === ',' && !inQuotes) {
values.push(current.trim());
current = '';
} else {
current += char;
}
}
values.push(current.trim());
// 构建对象
const obj = {};
headers.forEach((header, index) => {
if (values[index]) {
obj[header] = values[index].replace(/^"|"$/g, '');
} else {
obj[header] = '';
}
});
result.push(obj);
}
return result;
}
CSV解析函数,处理带引号的数据。
javascript
编辑
// ================= 渲染与统计 =================
// 渲染专利列表
function renderPatents(filteredPatents = patents) {
patentTableBody.innerHTML = '';
let soonCount = 0;
let overdueCount = 0;
filteredPatents.forEach((patent, index) => {
const rowClass = getStatusClass(patent.dueDate);
// 统计逻辑
if (rowClass === 'status-overdue') overdueCount++;
if (rowClass === 'status-soon') soonCount++;
const row = document.createElement('tr');
row.className = rowClass;
row.innerHTML = `
<td>${patent.name}</td>
<td><strong>"${patent.number}"</strong></td>
<td>${patent.applicant || '-'}</td>
<td>${patent.contact || '-'}</td>
<td>${patent.feeAmount ? '¥' + parseFloat(patent.feeAmount).toFixed(2) : '-'}</td>
<td>${patent.type}</td>
<td>${formatDate(patent.dueDate)}
<br><small>(${getRelativeDateText(patent.dueDate)})</small>
</td>
<td>${patent.status}</td>
<td class="action-btns">
<button class="btn btn-warning btn-edit" data-index="${index}">编辑</button>
<button class="btn btn-danger btn-delete" data-index="${index}">删除</button>
</td>
`;
patentTableBody.appendChild(row);
});
// 更新统计
totalCountEl.textContent = filteredPatents.length;
soonCountEl.textContent = soonCount;
overdueCountEl.textContent = overdueCount;
}
// 获取相对日期文本
function getRelativeDateText(dateStr) {
if (!dateStr) return '';
const due = new Date(dateStr);
const today = new Date();
const diffDays = getDateDiffInDays(today, due);
if (due < today) {
return `已逾期 ${diffDays} 天`;
} else if (diffDays === 0) {
return `今天到期`;
} else {
return `还剩 ${diffDays} 天`;
}
}
渲染专利列表函数,动态创建表格行并更新统计数据。
javascript
编辑
// ================= 事件处理 =================
// 1. 添加/更新专利
patentForm.addEventListener('submit', function(e) {
e.preventDefault();
const patentData = {
name: document.getElementById('name').value,
number: document.getElementById('number').value,
inventor: document.getElementById('inventor').value,
applicant: document.getElementById('applicant').value,
contact: document.getElementById('contact').value,
feeAmount: document.getElementById('feeAmount').value,
filingDate: document.getElementById('filingDate').value,
grantDate: document.getElementById('grantDate').value,
dueDate: document.getElementById('dueDate').value,
type: document.getElementById('type').value,
status: document.getElementById('status').value,
notes: document.getElementById('notes').value
};
// 检查专利号是否重复 (添加时)
if (isEditing === null && patents.some(p => p.number === patentData.number)) {
alert('错误:该专利号已存在!');
return;
}
if (isEditing === null) {
// 添加模式
patents.push(patentData);
alert('专利添加成功!');
} else {
// 编辑模式
patents[isEditing] = patentData;
alert('专利信息已更新!');
// 重置表单状态
isEditing = null;
document.getElementById('cancelEdit').style.display = 'none';
patentForm.querySelector('button[type="submit"]').textContent = '更新专利信息';
}
saveToStorage();
renderPatents();
patentForm.reset();
});
处理表单提交事件,区分添加和编辑模式,检查重复专利号。
javascript
编辑
// 2. 搜索功能
searchInput.addEventListener('input', function() {
const query = this.value.toLowerCase();
const filtered = patents.filter(patent =>
patent.name.toLowerCase().includes(query) ||
patent.number.toLowerCase().includes(query) ||
(patent.applicant && patent.applicant.toLowerCase().includes(query)) ||
(patent.contact && patent.contact.toLowerCase().includes(query))
);
renderPatents(filtered);
});
实时搜索功能,根据关键词过滤专利数据。
javascript
编辑
// 3. 删除与编辑 (事件委托)
patentTableBody.addEventListener('click', function(e) {
const index = e.target.dataset.index;
if (e.target.classList.contains('btn-delete')) {
if (confirm('确定要删除这条专利记录吗?')) {
patents.splice(index, 1);
saveToStorage();
renderPatents();
}
}
if (e.target.classList.contains('btn-edit')) {
// 填充表单
const patent = patents[index];
document.getElementById('name').value = patent.name;
document.getElementById('number').value = patent.number;
document.getElementById('inventor').value = patent.inventor;
document.getElementById('applicant').value = patent.applicant;
document.getElementById('contact').value = patent.contact;
document.getElementById('feeAmount').value = patent.feeAmount || '';
document.getElementById('filingDate').value = patent.filingDate;
document.getElementById('grantDate').value = patent.grantDate;
document.getElementById('dueDate').value = patent.dueDate;
document.getElementById('type').value = patent.type;
document.getElementById('status').value = patent.status;
document.getElementById('notes').value = patent.notes;
// 切换到编辑模式
isEditing = index;
document.getElementById('cancelEdit').style.display = 'inline-block';
patentForm.querySelector('button[type="submit"]').textContent = '更新专利信息';
}
});
使用事件委托处理删除和编辑按钮点击事件。
javascript
编辑
// 4. 取消编辑
cancelEditBtn.addEventListener('click', function() {
patentForm.reset();
isEditing = null;
this.style.display = 'none';
patentForm.querySelector('button[type="submit"]').textContent = '保存专利';
});
// 5. 导出 CSV
exportBtn.addEventListener('click', function() {
if (patents.length === 0) {
alert('当前没有数据可导出!');
return;
}
// 构建 CSV 内容
let csvContent = "序号,专利名称,专利号,发明人,申请人,联系电话,年费金额,申请日期,授权日期,年费日,类型,状态,备注\n";
patents.forEach((item, index) => {
csvContent += `"${index+1}","${item.name}","${item.number}","${item.inventor}","${item.applicant}","${item.contact || ''}","${item.feeAmount || ''}","${item.filingDate}","${item.grantDate}","${item.dueDate}","${item.type}","${item.status}","${item.notes.replace(/"/g, '""')}"\n`;
});
// 创建下载链接
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `专利管理系统_数据导出_${new Date().toISOString().slice(0, 10)}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
取消编辑功能和CSV导出功能,创建Blob对象并触发下载。
javascript
编辑
// 6. 导入 CSV
importBtn.addEventListener('click', function() {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.csv';
fileInput.onchange = function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const csvText = e.target.result;
const parsedData = parseCSV(csvText);
if (parsedData.length > 0) {
if (confirm(`确定要导入 ${parsedData.length} 条专利数据吗?这将覆盖现有数据!`)) {
// 转换CSV数据为系统格式
const convertedData = parsedData.map(item => ({
name: item['专利名称'] || item['name'] || '',
number: item['专利号'] || item['number'] || '',
inventor: item['发明人'] || item['inventor'] || '',
applicant: item['申请人'] || item['applicant'] || '',
contact: item['联系电话'] || item['contact'] || '',
feeAmount: item['年费金额'] || item['feeAmount'] || '',
filingDate: item['申请日期'] || item['filingDate'] || '',
grantDate: item['授权日期'] || item['grantDate'] || '',
dueDate: item['年费日'] || item['dueDate'] || '',
type: item['类型'] || item['type'] || '发明专利',
status: item['状态'] || item['status'] || '申请中',
notes: item['备注'] || item['notes'] || ''
}));
patents = convertedData;
saveToStorage();
renderPatents();
alert('数据导入成功!');
}
} else {
alert('导入失败:文件中没有找到有效的专利数据!');
}
} catch (error) {
alert('导入失败:文件格式错误或损坏!\n错误详情:' + error.message);
}
};
reader.readAsText(file);
};
fileInput.click();
});
CSV导入功能,读取文件并解析数据。
javascript
编辑
// 7. 清空所有数据
clearBtn.addEventListener('click', function() {
if (patents.length === 0) {
alert('当前没有数据可清空!');
return;
}
if (confirm('确定要清空所有专利数据吗?此操作不可恢复!')) {
patents = [];
saveToStorage();
renderPatents();
alert('所有数据已清空!');
}
});
// 8. 模态框关闭事件
closeModal.onclick = function() {
editModal.style.display = 'none';
}
window.onclick = function(event) {
if (event.target === editModal) {
editModal.style.display = 'none';
}
}
// 初始化渲染
renderPatents();
// 鸿蒙系统兼容性检查
if ('serviceWorker' in navigator) {
// 注册Service Worker用于离线缓存
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => console.log('SW registered'))
.catch(error => console.log('SW registration failed'));
}
});
}
// 添加鸿蒙系统检测
function detectHarmonyOS() {
const userAgent = navigator.userAgent;
if (userAgent.includes('HarmonyOS')) {
document.title = '鸿蒙系统专利管理系统';
console.log('检测到鸿蒙系统环境');
}
}
// 页面加载完成后检测系统
window.addEventListener('load', detectHarmonyOS);
</script>
</body>
</html>
清空数据功能、模态框关闭事件、初始化渲染以及鸿蒙系统检测代码。整个系统使用localStorage作为本地数据存储,实现了专利的增删改查功能。
完整代码
html
<!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>
:root {
--primary-color: #007BFF;
--warning-color: #FFC107;
--danger-color: #DC3545;
--success-color: #28A745;
--border-color: #dee2e6;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'HarmonyOS Sans', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
border-bottom: 3px solid var(--primary-color);
padding-bottom: 10px;
}
/* 表单样式 */
.form-container {
background: #f1f3f5;
padding: 20px;
border-radius: 6px;
margin-bottom: 30px;
border: 1px solid var(--border-color);
}
.form-row {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 15px;
}
.form-group {
flex: 1 1 300px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #495057;
}
input[type="text"],
input[type="number"],
input[type="date"],
input[type="tel"],
select,
textarea {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
}
textarea {
resize: vertical;
height: 60px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s ease;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-success {
background-color: var(--success-color);
color: white;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
}
.btn-warning {
background-color: var(--warning-color);
color: #000;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
/* 操作栏 */
.actions-bar {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
.search-box {
padding: 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
flex: 1;
min-width: 200px;
}
.action-buttons {
display: flex;
gap: 10px;
}
/* 表格样式 */
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
box-shadow: 0 0 5px rgba(0,0,0,0.05);
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
th {
background-color: #f8f9fa;
font-weight: bold;
color: #495057;
position: sticky;
top: 0;
}
tr:hover {
background-color: #f1f3f5;
transition: background-color 0.2s;
}
.status-soon {
background-color: #fff3cd !important; /* 黄色背景:即将到期 */
font-weight: bold;
}
.status-overdue {
background-color: #f8d7da !important; /* 红色背景:已逾期 */
color: #721c24;
font-weight: bold;
}
.action-btns .btn {
padding: 5px 10px;
margin-right: 5px;
font-size: 12px;
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #000;
}
/* 统计信息 */
.stats {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 200px;
padding: 15px;
background: #f8f9fa;
border-left: 4px solid var(--primary-color);
border-radius: 4px;
}
.stat-card.warning {
border-left-color: var(--warning-color);
}
.stat-card.danger {
border-left-color: var(--danger-color);
}
.stat-number {
font-size: 24px;
font-weight: bold;
margin: 5px 0;
}
/* 鸿蒙系统特定样式 */
.harmony-logo {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.harmony-logo img {
width: 60px;
height: 60px;
margin-right: 15px;
}
.harmony-badge {
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
color: white;
padding: 5px 10px;
border-radius: 15px;
font-size: 12px;
margin-left: 10px;
}
/* 响应式设计优化 */
@media (max-width: 768px) {
.container {
padding: 15px;
}
.form-row {
flex-direction: column;
}
.form-group {
flex: 1 1 100%;
}
.stats {
flex-direction: column;
}
.actions-bar {
flex-direction: column;
}
.action-buttons {
flex-direction: column;
}
table {
font-size: 14px;
}
th, td {
padding: 8px 10px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="harmony-logo">
<h1>📝 专利管理系统</h1>
<span class="harmony-badge">鸿蒙系统优化版</span>
</div>
<!-- 统计卡片 -->
<div class="stats">
<div class="stat-card">
<div>总专利数</div>
<div id="total-count" class="stat-number">0</div>
</div>
<div class="stat-card warning">
<div>即将缴费 (60天内)</div>
<div id="soon-count" class="stat-number">0</div>
</div>
<div class="stat-card danger">
<div>已逾期</div>
<div id="overdue-count" class="stat-number">0</div>
</div>
</div>
<!-- 添加专利表单 -->
<div class="form-container">
<h2>添加新专利</h2>
<form id="patentForm">
<div class="form-row">
<div class="form-group">
<label>专利名称 *</label>
<input type="text" id="name" required>
</div>
<div class="form-group">
<label>专利号 *</label>
<input type="text" id="number" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>发明人</label>
<input type="text" id="inventor">
</div>
<div class="form-group">
<label>申请人</label>
<input type="text" id="applicant">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>联系电话</label>
<input type="tel" id="contact">
</div>
<div class="form-group">
<label>年费金额 (元)</label>
<input type="number" id="feeAmount" min="0" step="0.01">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>申请日期</label>
<input type="date" id="filingDate">
</div>
<div class="form-group">
<label>授权日期</label>
<input type="date" id="grantDate">
</div>
<div class="form-group">
<label>年费缴纳日 *</label>
<input type="date" id="dueDate" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>专利类型</label>
<select id="type">
<option value="发明专利">发明专利</option>
<option value="实用新型">实用新型</option>
<option value="外观设计">外观设计</option>
</select>
</div>
<div class="form-group">
<label>状态</label>
<select id="status">
<option value="申请中">申请中</option>
<option value="已授权">已授权</option>
<option value="失效">失效</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>备注</label>
<textarea id="notes"></textarea>
</div>
</div>
<button type="submit" class="btn btn-primary">保存专利</button>
<button type="button" id="cancelEdit" class="btn btn-danger" style="display:none;">取消编辑</button>
</form>
</div>
<!-- 操作与表格 -->
<div class="actions-bar">
<input type="text" id="searchInput" class="search-box" placeholder="搜索专利名称或专利号...">
<div class="action-buttons">
<button id="exportBtn" class="btn btn-success">导出数据 (CSV)</button>
<button id="importBtn" class="btn btn-secondary">导入数据 (CSV)</button>
<button id="clearBtn" class="btn btn-danger">清空所有数据</button>
</div>
</div>
<table id="patentTable">
<thead>
<tr>
<th>专利名称</th>
<th>专利号</th>
<th>申请人</th>
<th>联系方式</th>
<th>年费金额</th>
<th>类型</th>
<th>年费缴纳日</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 数据将通过 JS 动态插入 -->
</tbody>
</table>
</div>
<!-- 编辑模态框 -->
<div id="editModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>编辑专利信息</h2>
<form id="editForm">
<input type="hidden" id="editIndex">
<div class="form-row">
<div class="form-group"><label>专利名称</label><input type="text" id="editName" required></div>
<div class="form-group"><label>专利号</label><input type="text" id="editNumber" required></div>
</div>
<div class="form-row">
<div class="form-group"><label>发明人</label><input type="text" id="editInventor"></div>
<div class="form-group"><label>申请人</label><input type="text" id="editApplicant"></div>
</div>
<div class="form-row">
<div class="form-group"><label>联系电话</label><input type="tel" id="editContact"></div>
<div class="form-group"><label>年费金额 (元)</label><input type="number" id="editFeeAmount" min="0" step="0.01"></div>
</div>
<div class="form-row">
<div class="form-group"><label>申请日期</label><input type="date" id="editFilingDate"></div>
<div class="form-group"><label>授权日期</label><input type="date" id="editGrantDate"></div>
<div class="form-group"><label>年费缴纳日</label><input type="date" id="editDueDate" required></div>
</div>
<div class="form-row">
<div class="form-group">
<label>类型</label>
<select id="editType">
<option value="发明专利">发明专利</option>
<option value="实用新型">实用新型</option>
<option value="外观设计">外观设计</option>
</select>
</div>
<div class="form-group">
<label>状态</label>
<select id="editStatus">
<option value="申请中">申请中</option>
<option value="已授权">已授权</option>
<option value="失效">失效</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group"><label>备注</label><textarea id="editNotes"></textarea></div>
</div>
<button type="submit" class="btn btn-primary">更新专利</button>
</form>
</div>
</div>
<script>
// 模拟数据库 (使用 localStorage)
let patents = JSON.parse(localStorage.getItem('patents')) || [];
// DOM 元素
const patentForm = document.getElementById('patentForm');
const patentTableBody = document.querySelector('#patentTable tbody');
const searchInput = document.getElementById('searchInput');
const cancelEditBtn = document.getElementById('cancelEdit');
const exportBtn = document.getElementById('exportBtn');
const importBtn = document.getElementById('importBtn');
const clearBtn = document.getElementById('clearBtn');
// 统计元素
const totalCountEl = document.getElementById('total-count');
const soonCountEl = document.getElementById('soon-count');
const overdueCountEl = document.getElementById('overdue-count');
// 模态框元素
const editModal = document.getElementById('editModal');
const closeModal = document.querySelector('.close');
const editForm = document.getElementById('editForm');
// 编辑状态
let isEditing = null;
// ================= 工具函数 =================
// 保存到 LocalStorage
function saveToStorage() {
localStorage.setItem('patents', JSON.stringify(patents));
}
// 格式化日期 (YYYY-MM-DD)
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN');
}
// 计算日期差 (天数)
function getDateDiffInDays(date1, date2) {
const diffTime = Math.abs(date2 - date1);
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
// 获取专利状态类名 (用于表格高亮)
function getStatusClass(dueDateStr) {
if (!dueDateStr) return '';
const dueDate = new Date(dueDateStr);
const today = new Date();
today.setHours(0,0,0,0);
dueDate.setHours(0,0,0,0);
// 如果年费日小于今天,说明已过期
if (dueDate < today) {
return 'status-overdue';
}
// 如果在60天内
const diffDays = getDateDiffInDays(today, dueDate);
if (diffDays <= 60) {
return 'status-soon';
}
return '';
}
// 解析CSV数据
function parseCSV(csvText) {
const lines = csvText.split(/\r?\n/);
const headers = lines[0].split(',').map(header => header.trim().replace(/^"|"$/g, ''));
const result = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '') continue;
const values = [];
let current = '';
let inQuotes = false;
for (let j = 0; j < lines[i].length; j++) {
const char = lines[i][j];
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === ',' && !inQuotes) {
values.push(current.trim());
current = '';
} else {
current += char;
}
}
values.push(current.trim());
// 构建对象
const obj = {};
headers.forEach((header, index) => {
if (values[index]) {
obj[header] = values[index].replace(/^"|"$/g, '');
} else {
obj[header] = '';
}
});
result.push(obj);
}
return result;
}
// ================= 渲染与统计 =================
// 渲染专利列表
function renderPatents(filteredPatents = patents) {
patentTableBody.innerHTML = '';
let soonCount = 0;
let overdueCount = 0;
filteredPatents.forEach((patent, index) => {
const rowClass = getStatusClass(patent.dueDate);
// 统计逻辑
if (rowClass === 'status-overdue') overdueCount++;
if (rowClass === 'status-soon') soonCount++;
const row = document.createElement('tr');
row.className = rowClass;
row.innerHTML = `
<td>${patent.name}</td>
<td><strong>"${patent.number}"</strong></td>
<td>${patent.applicant || '-'}</td>
<td>${patent.contact || '-'}</td>
<td>${patent.feeAmount ? '¥' + parseFloat(patent.feeAmount).toFixed(2) : '-'}</td>
<td>${patent.type}</td>
<td>${formatDate(patent.dueDate)}
<br><small>(${getRelativeDateText(patent.dueDate)})</small>
</td>
<td>${patent.status}</td>
<td class="action-btns">
<button class="btn btn-warning btn-edit" data-index="${index}">编辑</button>
<button class="btn btn-danger btn-delete" data-index="${index}">删除</button>
</td>
`;
patentTableBody.appendChild(row);
});
// 更新统计
totalCountEl.textContent = filteredPatents.length;
soonCountEl.textContent = soonCount;
overdueCountEl.textContent = overdueCount;
}
// 获取相对日期文本
function getRelativeDateText(dateStr) {
if (!dateStr) return '';
const due = new Date(dateStr);
const today = new Date();
const diffDays = getDateDiffInDays(today, due);
if (due < today) {
return `已逾期 ${diffDays} 天`;
} else if (diffDays === 0) {
return `今天到期`;
} else {
return `还剩 ${diffDays} 天`;
}
}
// ================= 事件处理 =================
// 1. 添加/更新专利
patentForm.addEventListener('submit', function(e) {
e.preventDefault();
const patentData = {
name: document.getElementById('name').value,
number: document.getElementById('number').value,
inventor: document.getElementById('inventor').value,
applicant: document.getElementById('applicant').value,
contact: document.getElementById('contact').value,
feeAmount: document.getElementById('feeAmount').value,
filingDate: document.getElementById('filingDate').value,
grantDate: document.getElementById('grantDate').value,
dueDate: document.getElementById('dueDate').value,
type: document.getElementById('type').value,
status: document.getElementById('status').value,
notes: document.getElementById('notes').value
};
// 检查专利号是否重复 (添加时)
if (isEditing === null && patents.some(p => p.number === patentData.number)) {
alert('错误:该专利号已存在!');
return;
}
if (isEditing === null) {
// 添加模式
patents.push(patentData);
alert('专利添加成功!');
} else {
// 编辑模式
patents[isEditing] = patentData;
alert('专利信息已更新!');
// 重置表单状态
isEditing = null;
document.getElementById('cancelEdit').style.display = 'none';
patentForm.querySelector('button[type="submit"]').textContent = '更新专利信息';
}
saveToStorage();
renderPatents();
patentForm.reset();
});
// 2. 搜索功能
searchInput.addEventListener('input', function() {
const query = this.value.toLowerCase();
const filtered = patents.filter(patent =>
patent.name.toLowerCase().includes(query) ||
patent.number.toLowerCase().includes(query) ||
(patent.applicant && patent.applicant.toLowerCase().includes(query)) ||
(patent.contact && patent.contact.toLowerCase().includes(query))
);
renderPatents(filtered);
});
// 3. 删除与编辑 (事件委托)
patentTableBody.addEventListener('click', function(e) {
const index = e.target.dataset.index;
if (e.target.classList.contains('btn-delete')) {
if (confirm('确定要删除这条专利记录吗?')) {
patents.splice(index, 1);
saveToStorage();
renderPatents();
}
}
if (e.target.classList.contains('btn-edit')) {
// 填充表单
const patent = patents[index];
document.getElementById('name').value = patent.name;
document.getElementById('number').value = patent.number;
document.getElementById('inventor').value = patent.inventor;
document.getElementById('applicant').value = patent.applicant;
document.getElementById('contact').value = patent.contact;
document.getElementById('feeAmount').value = patent.feeAmount || '';
document.getElementById('filingDate').value = patent.filingDate;
document.getElementById('grantDate').value = patent.grantDate;
document.getElementById('dueDate').value = patent.dueDate;
document.getElementById('type').value = patent.type;
document.getElementById('status').value = patent.status;
document.getElementById('notes').value = patent.notes;
// 切换到编辑模式
isEditing = index;
document.getElementById('cancelEdit').style.display = 'inline-block';
patentForm.querySelector('button[type="submit"]').textContent = '更新专利信息';
}
});
// 4. 取消编辑
cancelEditBtn.addEventListener('click', function() {
patentForm.reset();
isEditing = null;
this.style.display = 'none';
patentForm.querySelector('button[type="submit"]').textContent = '保存专利';
});
// 5. 导出 CSV
exportBtn.addEventListener('click', function() {
if (patents.length === 0) {
alert('当前没有数据可导出!');
return;
}
// 构建 CSV 内容
let csvContent = "序号,专利名称,专利号,发明人,申请人,联系电话,年费金额,申请日期,授权日期,年费日,类型,状态,备注\n";
patents.forEach((item, index) => {
csvContent += `"${index+1}","${item.name}","${item.number}","${item.inventor}","${item.applicant}","${item.contact || ''}","${item.feeAmount || ''}","${item.filingDate}","${item.grantDate}","${item.dueDate}","${item.type}","${item.status}","${item.notes.replace(/"/g, '""')}"\n`;
});
// 创建下载链接
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `专利管理系统_数据导出_${new Date().toISOString().slice(0, 10)}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
// 6. 导入 CSV
importBtn.addEventListener('click', function() {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.csv';
fileInput.onchange = function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const csvText = e.target.result;
const parsedData = parseCSV(csvText);
if (parsedData.length > 0) {
if (confirm(`确定要导入 ${parsedData.length} 条专利数据吗?这将覆盖现有数据!`)) {
// 转换CSV数据为系统格式
const convertedData = parsedData.map(item => ({
name: item['专利名称'] || item['name'] || '',
number: item['专利号'] || item['number'] || '',
inventor: item['发明人'] || item['inventor'] || '',
applicant: item['申请人'] || item['applicant'] || '',
contact: item['联系电话'] || item['contact'] || '',
feeAmount: item['年费金额'] || item['feeAmount'] || '',
filingDate: item['申请日期'] || item['filingDate'] || '',
grantDate: item['授权日期'] || item['grantDate'] || '',
dueDate: item['年费日'] || item['dueDate'] || '',
type: item['类型'] || item['type'] || '发明专利',
status: item['状态'] || item['status'] || '申请中',
notes: item['备注'] || item['notes'] || ''
}));
patents = convertedData;
saveToStorage();
renderPatents();
alert('数据导入成功!');
}
} else {
alert('导入失败:文件中没有找到有效的专利数据!');
}
} catch (error) {
alert('导入失败:文件格式错误或损坏!\n错误详情:' + error.message);
}
};
reader.readAsText(file);
};
fileInput.click();
});
// 7. 清空所有数据
clearBtn.addEventListener('click', function() {
if (patents.length === 0) {
alert('当前没有数据可清空!');
return;
}
if (confirm('确定要清空所有专利数据吗?此操作不可恢复!')) {
patents = [];
saveToStorage();
renderPatents();
alert('所有数据已清空!');
}
});
// 8. 模态框关闭事件
closeModal.onclick = function() {
editModal.style.display = 'none';
}
window.onclick = function(event) {
if (event.target === editModal) {
editModal.style.display = 'none';
}
}
// 初始化渲染
renderPatents();
// 鸿蒙系统兼容性检查
if ('serviceWorker' in navigator) {
// 注册Service Worker用于离线缓存
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => console.log('SW registered'))
.catch(error => console.log('SW registration failed'));
}
});
}
// 添加鸿蒙系统检测
function detectHarmonyOS() {
const userAgent = navigator.userAgent;
if (userAgent.includes('HarmonyOS')) {
document.title = '鸿蒙系统专利管理系统';
console.log('检测到鸿蒙系统环境');
}
}
// 页面加载完成后检测系统
window.addEventListener('load', detectHarmonyOS);
</script>
</body>
</html>