1062 lines
37 KiB
HTML
1062 lines
37 KiB
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>
|
||
/* 基本样式 - 更简洁的设计 */
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background-color: #f5f7fa;
|
||
color: #333;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1000px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
h1 {
|
||
text-align: center;
|
||
color: #2d3748;
|
||
margin-bottom: 20px;
|
||
font-size: 24px;
|
||
}
|
||
|
||
h2 {
|
||
font-size: 16px;
|
||
color: #4a5568;
|
||
margin-bottom: 15px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 数据管理区域 - 缩小版 */
|
||
.import-export {
|
||
background-color: white;
|
||
border-radius: 6px;
|
||
padding: 10px 15px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
margin-bottom: 15px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.import-export h2 {
|
||
margin-bottom: 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.import-export > div {
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 主内容区域 */
|
||
.main-content {
|
||
display: grid;
|
||
grid-template-columns: 220px 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* 分类侧边栏 */
|
||
.categories {
|
||
background-color: white;
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.category-list {
|
||
list-style: none;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.category-item {
|
||
padding: 8px 10px;
|
||
margin-bottom: 6px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.category-item:hover {
|
||
background-color: #f7fafc;
|
||
}
|
||
|
||
.category-item.active {
|
||
background-color: #3182ce;
|
||
color: white;
|
||
}
|
||
|
||
.category-actions {
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
.add-category {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 统一按钮样式 */
|
||
.btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.2s;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 小按钮样式 - 统一所有小按钮 */
|
||
.btn-small, .btn-copy {
|
||
padding: 3px 6px;
|
||
font-size: 11px;
|
||
border: none;
|
||
border-radius: 3px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: #3182ce;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: #2c5282;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: #718096;
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background-color: #4a5568;
|
||
}
|
||
|
||
.btn-danger {
|
||
background-color: #e53e3e;
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background-color: #c53030;
|
||
}
|
||
|
||
.btn-edit {
|
||
background-color: #ed8936;
|
||
color: white;
|
||
}
|
||
|
||
.btn-edit:hover {
|
||
background-color: #dd6b20;
|
||
}
|
||
|
||
.btn-delete {
|
||
background-color: #e53e3e;
|
||
color: white;
|
||
}
|
||
|
||
.btn-delete:hover {
|
||
background-color: #c53030;
|
||
}
|
||
|
||
.btn-copy {
|
||
background-color: #38a169;
|
||
color: white;
|
||
}
|
||
|
||
.btn-copy:hover {
|
||
background-color: #2f855a;
|
||
}
|
||
|
||
/* 密码列表 */
|
||
.passwords {
|
||
background-color: white;
|
||
border-radius: 6px;
|
||
padding: 12px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.passwords-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.password-list {
|
||
list-style: none;
|
||
}
|
||
|
||
/* 密码项 - 更紧凑的设计 */
|
||
.password-item {
|
||
padding: 8px 10px;
|
||
margin-bottom: 8px;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 5px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.password-item:hover {
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||
border-color: #cbd5e0;
|
||
}
|
||
|
||
.password-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.password-title {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
color: #2d3748;
|
||
}
|
||
|
||
.password-details {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.password-row {
|
||
display: flex;
|
||
margin-bottom: 4px;
|
||
align-items: center;
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.password-row:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.password-label {
|
||
width: 60px;
|
||
font-weight: 500;
|
||
color: #718096;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.password-value {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
color: #2d3748;
|
||
}
|
||
|
||
/* 密码输入框样式优化 */
|
||
.password-value input[type="password"] {
|
||
font-size: 13px;
|
||
padding: 0;
|
||
}
|
||
|
||
/* 表单样式 */
|
||
.form-section {
|
||
background-color: white;
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
font-weight: 500;
|
||
color: #4a5568;
|
||
font-size: 14px;
|
||
}
|
||
|
||
input, select, textarea {
|
||
width: 100%;
|
||
padding: 8px 10px;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
input:focus, select:focus, textarea:focus {
|
||
outline: none;
|
||
border-color: #3182ce;
|
||
box-shadow: 0 0 0 1px rgba(49, 130, 206, 0.2);
|
||
}
|
||
|
||
input[type="password"] {
|
||
font-family: monospace;
|
||
}
|
||
|
||
/* 显示/隐藏密码按钮 */
|
||
.toggle-password {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #3182ce;
|
||
font-size: 13px;
|
||
padding: 0;
|
||
}
|
||
|
||
/* 搜索框 */
|
||
.search-box {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
/* 新的布局样式 */
|
||
.layout-grid {
|
||
display: grid;
|
||
grid-template-columns: 300px 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.left-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
.right-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 更大的密码列表 - 带滚动功能 */
|
||
.passwords.large-list {
|
||
flex: 1;
|
||
min-height: 400px;
|
||
max-height: 600px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 自定义滚动条样式 */
|
||
.passwords.large-list::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
.passwords.large-list::-webkit-scrollbar-track {
|
||
background: #f1f1f1;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.passwords.large-list::-webkit-scrollbar-thumb {
|
||
background: #cbd5e0;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.passwords.large-list::-webkit-scrollbar-thumb:hover {
|
||
background: #a0aec0;
|
||
}
|
||
|
||
/* 更小的表单 */
|
||
.form-section.small-form {
|
||
padding: 12px;
|
||
}
|
||
|
||
.small-form .form-row {
|
||
grid-template-columns: 1fr;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.small-form .form-group {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.small-form input,
|
||
.small-form select,
|
||
.small-form textarea {
|
||
padding: 6px 8px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* 复制提示样式 */
|
||
.copy-toast {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
background-color: #38a169;
|
||
color: white;
|
||
padding: 12px 20px;
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
z-index: 1000;
|
||
opacity: 0;
|
||
transform: translateY(-20px);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.copy-toast.show {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 768px) {
|
||
.layout-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.form-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.passwords-header {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 10px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>密码管理器</h1>
|
||
|
||
<!-- 复制提示 -->
|
||
<div class="copy-toast" id="copyToast">已复制到剪贴板</div>
|
||
|
||
<!-- 导入/导出功能 -->
|
||
<div class="import-export" style="background-color: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); margin-bottom: 20px;">
|
||
<h2>数据管理</h2>
|
||
<div style="display: flex; gap: 10px;">
|
||
<button class="btn btn-primary" onclick="exportData()">导出数据</button>
|
||
<label class="btn btn-secondary" style="cursor: pointer;">
|
||
导入数据
|
||
<input type="file" id="importFile" accept=".json" onchange="importData()" style="display: none;" />
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分类管理区域 -->
|
||
<div class="category-manager" style="background-color: white; border-radius: 6px; padding: 10px 15px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">
|
||
<h2>分类管理</h2>
|
||
<div style="display: flex; gap: 10px; align-items: center;">
|
||
<!-- 分类选择下拉框 -->
|
||
<select id="categorySelect" onchange="switchCategory(this.value)" style="padding: 6px 8px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 14px;">
|
||
<!-- 分类选项将通过JavaScript动态生成 -->
|
||
</select>
|
||
|
||
<!-- 分类管理操作 -->
|
||
<div style="display: flex; gap: 8px; align-items: center;">
|
||
<input type="text" id="newCategoryName" placeholder="新分类" style="padding: 6px 8px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 14px; width: 120px;" />
|
||
<button class="btn btn-small btn-primary" onclick="addCategory()">添加</button>
|
||
<button class="btn btn-small btn-edit" onclick="editSelectedCategory()">编辑</button>
|
||
<button class="btn btn-small btn-delete" onclick="deleteSelectedCategory()">删除</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主内容区域 - 调整为更大的密码列表 -->
|
||
<div class="layout-grid">
|
||
<!-- 左侧:仅表单 -->
|
||
<div class="left-panel">
|
||
<!-- 添加/编辑密码表单 - 更小的尺寸 -->
|
||
<div class="form-section small-form">
|
||
<h2 id="formTitle">添加密码</h2>
|
||
<form id="passwordForm" onsubmit="savePassword(event)">
|
||
<input type="hidden" id="passwordId" />
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="title">标题</label>
|
||
<input type="text" id="title" required />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="category">分类</label>
|
||
<select id="category" required>
|
||
<!-- 分类选项将通过JavaScript动态生成 -->
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="username">用户名</label>
|
||
<input type="text" id="username" required />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="password">密码</label>
|
||
<div style="position: relative;">
|
||
<input type="password" id="password" required />
|
||
<button type="button" class="toggle-password" onclick="togglePassword()" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%);">
|
||
显示
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="url">网址</label>
|
||
<input type="url" id="url" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="notes">备注</label>
|
||
<textarea id="notes" rows="2"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 8px; margin-top: 10px;">
|
||
<button type="submit" class="btn btn-primary btn-small">保存</button>
|
||
<button type="button" class="btn btn-secondary btn-small" onclick="resetForm()">重置</button>
|
||
<button type="button" class="btn btn-danger btn-small" id="deletePasswordBtn" onclick="deletePassword()" style="display: none;">删除</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:密码列表 - 更大的尺寸 -->
|
||
<div class="right-panel">
|
||
<div class="passwords large-list">
|
||
<div class="passwords-header">
|
||
<h2>密码列表</h2>
|
||
<div class="search-box">
|
||
<input type="text" id="searchInput" placeholder="搜索密码..." oninput="searchPasswords()" />
|
||
</div>
|
||
</div>
|
||
<ul class="password-list" id="passwordList">
|
||
<!-- 密码项将通过JavaScript动态生成 -->
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 数据存储
|
||
let categories = [
|
||
{ id: 'default', name: '默认分类' }
|
||
];
|
||
|
||
let passwords = [];
|
||
let currentCategory = 'default';
|
||
let editingPasswordId = null;
|
||
const DATA_FILE_NAME = 'passwords.json';
|
||
|
||
// DOM加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadData();
|
||
renderCategories();
|
||
renderPasswords();
|
||
});
|
||
|
||
// 加载数据
|
||
function loadData() {
|
||
try {
|
||
// 尝试从localStorage加载数据(作为备选方案)
|
||
const savedData = localStorage.getItem('passwordManagerData');
|
||
if (savedData) {
|
||
const parsedData = JSON.parse(savedData);
|
||
categories = parsedData.categories || categories;
|
||
passwords = parsedData.passwords || passwords;
|
||
console.log('从localStorage加载数据成功');
|
||
} else {
|
||
console.log('没有找到保存的数据,使用默认数据');
|
||
}
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存数据到localStorage(作为备选方案)
|
||
function saveData() {
|
||
try {
|
||
const data = {
|
||
categories: categories,
|
||
passwords: passwords
|
||
};
|
||
localStorage.setItem('passwordManagerData', JSON.stringify(data));
|
||
console.log('数据已保存到localStorage');
|
||
} catch (error) {
|
||
console.error('保存数据失败:', error);
|
||
}
|
||
}
|
||
|
||
// 导出数据到JSON文件
|
||
function exportData() {
|
||
try {
|
||
const data = {
|
||
categories: categories,
|
||
passwords: passwords
|
||
};
|
||
const jsonString = JSON.stringify(data, null, 2);
|
||
const blob = new Blob([jsonString], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = DATA_FILE_NAME;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
console.log('数据已导出到文件:', DATA_FILE_NAME);
|
||
} catch (error) {
|
||
console.error('导出数据失败:', error);
|
||
alert('导出数据失败,请检查浏览器权限');
|
||
}
|
||
}
|
||
|
||
// 从JSON文件导入数据
|
||
function importData() {
|
||
try {
|
||
const fileInput = document.getElementById('importFile');
|
||
const file = fileInput.files[0];
|
||
if (!file) return;
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const jsonString = e.target.result;
|
||
const data = JSON.parse(jsonString);
|
||
|
||
// 验证数据格式
|
||
if (Array.isArray(data.categories) && Array.isArray(data.passwords)) {
|
||
categories = data.categories;
|
||
passwords = data.passwords;
|
||
|
||
// 确保默认分类存在
|
||
if (!categories.some(c => c.id === 'default')) {
|
||
categories.push({ id: 'default', name: '默认分类' });
|
||
}
|
||
|
||
// 确保当前分类有效
|
||
if (!categories.some(c => c.id === currentCategory)) {
|
||
currentCategory = 'default';
|
||
}
|
||
|
||
renderCategories();
|
||
renderPasswords();
|
||
saveData(); // 同时保存到localStorage
|
||
console.log('数据已从文件导入');
|
||
alert('数据导入成功');
|
||
} else {
|
||
alert('无效的数据格式');
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
|
||
// 重置文件输入
|
||
fileInput.value = '';
|
||
} catch (error) {
|
||
console.error('导入数据失败:', error);
|
||
alert('导入数据失败,请检查文件格式');
|
||
}
|
||
}
|
||
|
||
// 渲染分类列表和下拉框
|
||
function renderCategories() {
|
||
const categorySelect = document.getElementById('category');
|
||
const mainCategorySelect = document.getElementById('categorySelect');
|
||
|
||
// 清空现有内容
|
||
categorySelect.innerHTML = '';
|
||
mainCategorySelect.innerHTML = '';
|
||
|
||
// 渲染分类选项
|
||
categories.forEach(category => {
|
||
// 密码表单中的分类下拉选项
|
||
const option = document.createElement('option');
|
||
option.value = category.id;
|
||
option.textContent = category.name;
|
||
categorySelect.appendChild(option);
|
||
|
||
// 主分类选择下拉选项
|
||
const mainOption = document.createElement('option');
|
||
mainOption.value = category.id;
|
||
mainOption.textContent = category.name;
|
||
if (category.id === currentCategory) {
|
||
mainOption.selected = true;
|
||
}
|
||
mainCategorySelect.appendChild(mainOption);
|
||
});
|
||
}
|
||
|
||
// 渲染密码列表
|
||
function renderPasswords() {
|
||
const passwordList = document.getElementById('passwordList');
|
||
|
||
// 筛选当前分类的密码
|
||
let filteredPasswords = passwords.filter(password => password.category === currentCategory);
|
||
|
||
// 清空现有内容
|
||
passwordList.innerHTML = '';
|
||
|
||
// 渲染密码项
|
||
filteredPasswords.forEach(password => {
|
||
const li = document.createElement('li');
|
||
li.className = 'password-item';
|
||
li.innerHTML = `
|
||
<div class="password-header">
|
||
<div class="password-title">${password.title}</div>
|
||
<div class="password-actions">
|
||
<button class="btn-small btn-edit" onclick="editPassword('${password.id}')">编辑</button>
|
||
<button class="btn-small btn-delete" onclick="deletePassword('${password.id}')">删除</button>
|
||
</div>
|
||
</div>
|
||
<div class="password-details">
|
||
<div class="password-row">
|
||
<div class="password-label">用户名:</div>
|
||
<div class="password-value">
|
||
<span>${password.username}</span>
|
||
<button class="btn-copy" onclick="copyToClipboard('${password.username}', '用户名')">复制</button>
|
||
</div>
|
||
</div>
|
||
<div class="password-row">
|
||
<div class="password-label">密码:</div>
|
||
<div class="password-value">
|
||
<input type="password" value="${password.password}" readonly style="border: none; background: transparent;" />
|
||
<button class="toggle-password" onclick="togglePasswordVisibility(this)">显示</button>
|
||
<button class="btn-copy" onclick="copyToClipboard('${password.password}', '密码')">复制</button>
|
||
</div>
|
||
</div>
|
||
${password.url ? `
|
||
<div class="password-row">
|
||
<div class="password-label">网址:</div>
|
||
<div class="password-value">
|
||
<a href="${password.url}" target="_blank">${password.url}</a>
|
||
<button class="btn-copy" onclick="copyToClipboard('${password.url}', '网址')">复制</button>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
${password.notes ? `
|
||
<div class="password-row">
|
||
<div class="password-label">备注:</div>
|
||
<div class="password-value">
|
||
<span>${password.notes}</span>
|
||
<button class="btn-copy" onclick="copyToClipboard('${password.notes}', '备注')">复制</button>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
passwordList.appendChild(li);
|
||
});
|
||
}
|
||
|
||
// 添加分类
|
||
function addCategory() {
|
||
const categoryName = document.getElementById('newCategoryName').value.trim();
|
||
if (categoryName) {
|
||
const newCategory = {
|
||
id: Date.now().toString(),
|
||
name: categoryName
|
||
};
|
||
categories.push(newCategory);
|
||
renderCategories();
|
||
saveData();
|
||
document.getElementById('newCategoryName').value = '';
|
||
}
|
||
}
|
||
|
||
// 编辑分类
|
||
function editCategory(categoryId) {
|
||
const category = categories.find(c => c.id === categoryId);
|
||
if (category) {
|
||
const newName = prompt('请输入新的分类名称:', category.name);
|
||
if (newName && newName.trim()) {
|
||
category.name = newName.trim();
|
||
renderCategories();
|
||
saveData();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 删除分类
|
||
function deleteCategory(categoryId) {
|
||
if (categoryId === 'default') {
|
||
alert('默认分类不能删除!');
|
||
return;
|
||
}
|
||
|
||
if (confirm('确定要删除这个分类吗? 该分类下的所有密码也会被删除。')) {
|
||
categories = categories.filter(c => c.id !== categoryId);
|
||
passwords = passwords.filter(p => p.category !== categoryId);
|
||
|
||
// 如果当前分类被删除,切换到默认分类
|
||
if (currentCategory === categoryId) {
|
||
currentCategory = 'default';
|
||
}
|
||
|
||
renderCategories();
|
||
renderPasswords();
|
||
saveData();
|
||
}
|
||
}
|
||
|
||
// 编辑选中的分类
|
||
function editSelectedCategory() {
|
||
const select = document.getElementById('categorySelect');
|
||
const categoryId = select.value;
|
||
editCategory(categoryId);
|
||
}
|
||
|
||
// 删除选中的分类
|
||
function deleteSelectedCategory() {
|
||
const select = document.getElementById('categorySelect');
|
||
const categoryId = select.value;
|
||
deleteCategory(categoryId);
|
||
}
|
||
|
||
// 切换分类
|
||
function switchCategory(categoryId) {
|
||
currentCategory = categoryId;
|
||
renderCategories();
|
||
renderPasswords();
|
||
}
|
||
|
||
// 保存密码
|
||
function savePassword(event) {
|
||
event.preventDefault();
|
||
|
||
const id = document.getElementById('passwordId').value;
|
||
const title = document.getElementById('title').value;
|
||
const category = document.getElementById('category').value;
|
||
const username = document.getElementById('username').value;
|
||
const password = document.getElementById('password').value;
|
||
const url = document.getElementById('url').value;
|
||
const notes = document.getElementById('notes').value;
|
||
|
||
if (id) {
|
||
// 编辑现有密码
|
||
const passwordIndex = passwords.findIndex(p => p.id === id);
|
||
if (passwordIndex !== -1) {
|
||
passwords[passwordIndex] = {
|
||
...passwords[passwordIndex],
|
||
title,
|
||
category,
|
||
username,
|
||
password,
|
||
url,
|
||
notes
|
||
};
|
||
}
|
||
} else {
|
||
// 添加新密码
|
||
const newPassword = {
|
||
id: Date.now().toString(),
|
||
title,
|
||
category,
|
||
username,
|
||
password,
|
||
url,
|
||
notes,
|
||
createdAt: new Date().toISOString()
|
||
};
|
||
passwords.push(newPassword);
|
||
}
|
||
|
||
// 如果添加的密码不属于当前显示的分类,切换到对应分类
|
||
const categorySelect = document.getElementById('category');
|
||
const selectedCategory = categorySelect.value;
|
||
if (currentCategory !== selectedCategory) {
|
||
switchCategory(selectedCategory);
|
||
} else {
|
||
renderPasswords();
|
||
}
|
||
|
||
saveData();
|
||
resetForm();
|
||
}
|
||
|
||
// 编辑密码
|
||
function editPassword(passwordId) {
|
||
const password = passwords.find(p => p.id === passwordId);
|
||
if (password) {
|
||
document.getElementById('passwordId').value = password.id;
|
||
document.getElementById('title').value = password.title;
|
||
document.getElementById('category').value = password.category;
|
||
document.getElementById('username').value = password.username;
|
||
document.getElementById('password').value = password.password;
|
||
document.getElementById('url').value = password.url;
|
||
document.getElementById('notes').value = password.notes;
|
||
|
||
document.getElementById('formTitle').textContent = '编辑密码';
|
||
document.getElementById('deletePasswordBtn').style.display = 'inline-block';
|
||
}
|
||
}
|
||
|
||
// 删除密码
|
||
function deletePassword(passwordId) {
|
||
const id = passwordId || document.getElementById('passwordId').value;
|
||
if (id) {
|
||
if (confirm('确定要删除这个密码吗?')) {
|
||
passwords = passwords.filter(p => p.id !== id);
|
||
renderPasswords();
|
||
saveData();
|
||
resetForm();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 重置表单
|
||
function resetForm() {
|
||
document.getElementById('passwordForm').reset();
|
||
document.getElementById('passwordId').value = '';
|
||
document.getElementById('formTitle').textContent = '添加密码';
|
||
document.getElementById('deletePasswordBtn').style.display = 'none';
|
||
}
|
||
|
||
// 切换密码可见性
|
||
function togglePassword() {
|
||
const passwordInput = document.getElementById('password');
|
||
const toggleBtn = passwordInput.nextElementSibling;
|
||
|
||
if (passwordInput.type === 'password') {
|
||
passwordInput.type = 'text';
|
||
toggleBtn.textContent = '隐藏';
|
||
} else {
|
||
passwordInput.type = 'password';
|
||
toggleBtn.textContent = '显示';
|
||
}
|
||
}
|
||
|
||
// 切换密码列表中的密码可见性
|
||
function togglePasswordVisibility(btn) {
|
||
const passwordInput = btn.previousElementSibling;
|
||
|
||
if (passwordInput.type === 'password') {
|
||
passwordInput.type = 'text';
|
||
btn.textContent = '隐藏';
|
||
} else {
|
||
passwordInput.type = 'password';
|
||
btn.textContent = '显示';
|
||
}
|
||
}
|
||
|
||
// 搜索密码
|
||
function searchPasswords() {
|
||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||
const passwordItems = document.querySelectorAll('.password-item');
|
||
|
||
passwordItems.forEach(item => {
|
||
const title = item.querySelector('.password-title').textContent.toLowerCase();
|
||
const username = item.querySelectorAll('.password-value')[0].textContent.toLowerCase();
|
||
const url = item.querySelectorAll('.password-value')[2]?.textContent.toLowerCase() || '';
|
||
|
||
if (title.includes(searchTerm) || username.includes(searchTerm) || url.includes(searchTerm)) {
|
||
item.style.display = 'block';
|
||
} else {
|
||
item.style.display = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
// 复制到剪贴板 - 支持多种浏览器环境
|
||
function copyToClipboard(text, type) {
|
||
// 尝试使用现代Clipboard API
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
navigator.clipboard.writeText(text)
|
||
.then(() => {
|
||
showCopySuccess(event.target);
|
||
})
|
||
.catch(err => {
|
||
console.error('Clipboard API失败:', err);
|
||
// 失败时回退到传统方法
|
||
fallbackCopyTextToClipboard(text, event.target);
|
||
});
|
||
} else {
|
||
// 使用传统方法
|
||
fallbackCopyTextToClipboard(text, event.target);
|
||
}
|
||
}
|
||
|
||
// 传统复制方法(备选方案)
|
||
function fallbackCopyTextToClipboard(text, btn) {
|
||
try {
|
||
// 创建临时输入元素
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = text;
|
||
|
||
// 确保元素不在可视区域
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.left = '-999999px';
|
||
textArea.style.top = '-999999px';
|
||
|
||
document.body.appendChild(textArea);
|
||
textArea.focus();
|
||
textArea.select();
|
||
|
||
// 执行复制命令
|
||
const successful = document.execCommand('copy');
|
||
|
||
if (successful) {
|
||
showCopySuccess(btn);
|
||
} else {
|
||
throw new Error('execCommand失败');
|
||
}
|
||
} catch (err) {
|
||
console.error('传统复制方法失败:', err);
|
||
alert('复制失败,请手动复制');
|
||
} finally {
|
||
// 清理临时元素
|
||
const textArea = document.querySelector('textarea[style*="-999999px"]');
|
||
if (textArea) {
|
||
document.body.removeChild(textArea);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 显示复制成功提示
|
||
function showCopySuccess(btn) {
|
||
// 更新按钮状态
|
||
const originalText = btn.textContent;
|
||
btn.textContent = '已复制';
|
||
btn.style.backgroundColor = '#2f855a';
|
||
|
||
setTimeout(() => {
|
||
btn.textContent = originalText;
|
||
btn.style.backgroundColor = '#38a169';
|
||
}, 1000);
|
||
|
||
// 显示Toast提示
|
||
const toast = document.getElementById('copyToast');
|
||
toast.classList.add('show');
|
||
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
}, 2000);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |