html
复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>安全密码备忘录</title>
<script>
const builtinAccounts = [
// 格式: [账户名称, 用户名/邮箱, 密码, 网站, 备注]
['163邮箱', 'user11@163.com', 'Pass163!Example', 'https://mail.163.com', '倩女幽魂账号'],
['微信', 'wechat_user', 'WeChat@2023', 'https://weixin.qq.com', '微信社交账号'],
['淘宝', 'taobao_buyer', 'TaobaoShop456', 'https://www.taobao.com', '淘宝购物账号'],
['工商银行', 'icbc_user_001', 'ICBC@Bank2023', 'https://www.icbc.com.cn', '工商银行网上银行'],
['百度网盘', 'baidu_user', 'BaiduCloud789', 'https://pan.baidu.com', '百度云存储服务'],
['哔哩哔哩', 'bilibili_viewer', 'Bilibili@Video123', 'https://www.bilibili.com', 'B站视频账号'],
['Microsoft Office', 'office365_user', 'Office365#2023', 'https://www.office.com', 'Office 365订阅账号'],
['Gmail邮箱', 'user@gmail.com', 'GmailPass123!', 'https://mail.google.com', '个人Gmail邮箱账号']
];
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
line-height: 1.5;
padding: 10px;
min-height: 100vh;
max-height: 100vh;
overflow: hidden;
}
.container {
max-width: 100%;
height: calc(100vh - 20px);
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
overflow: hidden;
display: flex;
flex-direction: column;
}
header {
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
padding: 5px 10px;
display: flex;
align-items: baseline;
flex-shrink: 0;
/* 确保头部不会随着内容滚动而收缩,保持固定的高度。 */
}
h1 {
font-size: 1.5rem;
margin-bottom: 5px;
gap: 1px;
}
.subtitle {
opacity: 0.9;
font-weight: 300;
font-size: 0.85rem;
}
.main-content {
padding: 15px;
display: grid;
grid-template-columns: 1fr 1.5fr;
gap: 15px;
flex: 1;
overflow: hidden;
min-height: 0;
}
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
gap: 12px;
}
body {
padding: 8px;
}
header {
padding: 12px 15px;
}
h1 {
font-size: 1.3rem;
}
}
@media (max-width: 480px) {
.main-content {
padding: 10px;
gap: 10px;
}
h1 {
font-size: 1.2rem;
}
.section-title {
font-size: 1.1rem;
}
}
.auth-section,
.password-list-section {
background: #f9fafc;
border-radius: 10px;
padding: 15px;
border: 1px solid #eef1f7;
overflow: hidden;
display: flex;
flex-direction: column;
}
.auth-section {
min-height: 300px;
}
.password-list-section {
min-height: 300px;
}
.section-title {
font-size: 1.2rem;
margin-bottom: 1px;
color: #2c3e50;
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.section-title i {
color: #3498db;
}
.form-group {
margin-bottom: 12px;
}
label {
display: block;
margin-bottom: 1px;
font-weight: 600;
color: #4a5568;
font-size: 0.9rem;
}
input,
textarea,
select {
width: 100%;
padding: 5px 6px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 0.9rem;
transition: all 0.3s;
}
input:focus,
textarea:focus,
select:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
.btn {
display: inline-block;
padding: 7px 10px;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
text-align: center;
width: 100%;
}
.btn:hover {
background: #2980b9;
transform: translateY(-1px);
}
.btn-primary {
background: linear-gradient(to right, #3498db, #2ecc71);
}
.btn-primary:hover {
background: linear-gradient(to right, #2980b9, #27ae60);
}
.btn-danger {
background: #e74c3c;
}
.btn-danger:hover {
background: #c0392b;
}
.btn-success {
background: #2ecc71;
}
.btn-success:hover {
background: #27ae60;
}
.btn-small {
padding: 8px 12px;
font-size: 0.85rem;
}
.btn-info {
background: #9b59b6;
}
.btn-info:hover {
background: #8e44ad;
}
.btn-warning {
background: #f39c12;
}
.btn-warning:hover {
background: #d68910;
}
.passwords-container {
flex: 1;
overflow-y: auto;
padding-right: 5px;
}
.password-item {
background: white;
border-radius: 8px;
padding: 12px 15px;
margin-bottom: 10px;
border: 1px solid #eef1f7;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
transition: all 0.2s;
}
.password-item:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
}
.password-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.password-title {
font-weight: 700;
font-size: 1rem;
color: #2c3e50;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 70%;
}
.password-actions {
display: flex;
gap: 6px;
}
.password-actions button {
background: none;
border: none;
cursor: pointer;
font-size: 0.9rem;
color: #7f8c8d;
transition: color 0.3s;
width: 30px;
height: 30px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.password-actions button:hover {
color: #3498db;
background-color: #f0f7ff;
}
.password-field {
margin-bottom: 6px;
display: flex;
align-items: flex-start;
}
.password-field label {
min-width: 60px;
margin-bottom: 0;
color: #7f8c8d;
font-size: 0.85rem;
line-height: 1.2;
}
.password-value {
font-family: 'Courier New', monospace;
background: #f8f9fa;
padding: 6px 10px;
border-radius: 4px;
flex-grow: 1;
word-break: break-all;
font-size: 0.85rem;
line-height: 1.4;
}
.masked {
color: transparent;
text-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
}
.empty-state {
text-align: center;
padding: 30px 15px;
color: #7f8c8d;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.empty-state i {
font-size: 2.5rem;
margin-bottom: 10px;
color: #bdc3c7;
}
.empty-state h3 {
font-size: 1.1rem;
margin-bottom: 8px;
}
.empty-state p {
font-size: 0.9rem;
}
.security-notice {
background: #f8f9fa;
border-left: 3px solid #3498db;
padding: 10px 12px;
margin-top: 15px;
border-radius: 0 6px 6px 0;
font-size: 0.8rem;
flex-shrink: 0;
}
.hidden {
display: none;
}
.password-strength {
height: 4px;
border-radius: 2px;
margin-top: 6px;
background: #e0e0e0;
overflow: hidden;
}
.strength-bar {
height: 100%;
width: 0%;
transition: width 0.5s, background 0.5s;
}
.strength-weak {
background: #e74c3c;
width: 30%;
}
.strength-medium {
background: #f39c12;
width: 60%;
}
.strength-strong {
background: #2ecc71;
width: 100%;
}
footer {
text-align: center;
padding: 12px 15px;
color: #7f8c8d;
font-size: 0.8rem;
border-top: 1px solid #eef1f7;
flex-shrink: 0;
}
.form-section {
flex: 1;
overflow-y: auto;
padding-right: 5px;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.compact-controls {
display: flex;
gap: 8px;
}
.compact-controls .btn {
width: auto;
flex: 1;
}
.compact-view .password-field {
flex-direction: column;
align-items: flex-start;
}
.compact-view .password-field label {
margin-bottom: 3px;
min-width: auto;
}
.compact-view .password-value {
width: 100%;
}
.mobile-only {
display: none;
}
@media (max-width: 768px) {
.mobile-only {
display: block;
}
.desktop-only {
display: none;
}
.compact-controls {
flex-direction: column;
}
}
@media (max-height: 600px) {
.main-content {
padding: 10px;
gap: 10px;
}
.auth-section,
.password-list-section {
padding: 12px;
}
.password-item {
padding: 10px 12px;
margin-bottom: 8px;
}
.form-group {
margin-bottom: 8px;
}
.section-title {
margin-bottom: 12px;
}
.password-header {
margin-bottom: 6px;
}
.password-field {
margin-bottom: 4px;
}
}
.view-toggle {
display: flex;
border-radius: 6px;
overflow: hidden;
border: 1px solid #e2e8f0;
}
.view-toggle button {
flex: 1;
padding: 8px 10px;
background: white;
border: none;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.3s;
}
.view-toggle button.active {
background: #3498db;
color: white;
}
.view-toggle button:not(.active):hover {
background: #f0f7ff;
}
/* 响应式表格视图 */
.table-view {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.table-view th {
background-color: #f8f9fa;
padding: 8px 10px;
text-align: left;
font-weight: 600;
color: #4a5568;
border-bottom: 2px solid #e2e8f0;
}
.table-view td {
padding: 8px 10px;
border-bottom: 1px solid #eef1f7;
}
.table-view tr:hover {
background-color: #f8f9fa;
}
.tab-buttons {
display: flex;
border-bottom: 1px solid #e2e8f0;
margin-bottom: 15px;
}
.tab-button {
padding: 10px 15px;
background: none;
border: none;
border-bottom: 3px solid transparent;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s;
flex: 1;
text-align: center;
}
.tab-button.active {
border-bottom-color: #3498db;
color: #3498db;
font-weight: 600;
}
.tab-button:hover:not(.active) {
background-color: #f0f7ff;
}
.tab-content {
display: none;
flex: 1;
overflow-y: auto;
}
.tab-content.active {
display: block;
}
.builtin-account {
border-left: 4px solid #2ecc71;
}
.builtin-account .password-title {
color: #27ae60;
}
.account-badge {
display: inline-block;
padding: 2px 6px;
font-size: 0.7rem;
border-radius: 10px;
margin-left: 6px;
font-weight: 600;
}
.badge-builtin {
background-color: #2ecc71;
color: white;
}
.badge-custom {
background-color: #3498db;
color: white;
}
.filter-section {
margin-bottom: 1px;
padding: 2px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #eef1f7;
}
.filter-controls {
display: flex;
gap: 8px;
margin-top: 10px;
}
.filter-input-group {
position: relative;
flex: 1;
}
.filter-input-group input {
padding-left: 35px;
}
.filter-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #7f8c8d;
}
.filter-results {
font-size: 0.85rem;
color: #7f8c8d;
margin-top: 8px;
}
.filter-results span {
font-weight: 600;
color: #3498db;
}
.clear-filter-btn {
background: none;
border: none;
color: #e74c3c;
cursor: pointer;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 4px;
}
.clear-filter-btn:hover {
text-decoration: underline;
}
.no-results {
text-align: center;
padding: 40px 20px;
color: #7f8c8d;
}
.no-results i {
font-size: 2.5rem;
margin-bottom: 10px;
color: #bdc3c7;
}
.no-results h3 {
font-size: 1.1rem;
margin-bottom: 1px;
}
.no-results p {
font-size: 0.9rem;
}
.filter-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 1px;
}
.filter-tag {
background: #e3f2fd;
color: #1565c0;
padding: 0px 10px;
border-radius: 12px;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.filter-tag:hover {
background: #bbdefb;
}
.filter-tag.active {
background: #3498db;
color: white;
}
.controls-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
margin-bottom: 1px;
}
.password-length {
font-size: 0.75rem;
color: #7f8c8d;
text-align: right;
}
.password-length-label {
font-weight: 600;
}
.password-input-wrapper {
position: relative;
}
.char-count {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.9);
padding: 2px 8px;
border-radius: 10px;
font-size: 0.75rem;
color: #7f8c8d;
border: 1px solid #e2e8f0;
}
.lock-countdown {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: #e74c3c;
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 0.75rem;
font-weight: bold;
}
/* SVG图标样式 */
.icon {
display: inline-block;
width: 1em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
}
.icon-lock {
width: 1.2em;
height: 1.2em;
}
.icon-key {
width: 1.2em;
height: 1.2em;
}
.icon-list {
width: 1.2em;
height: 1.2em;
}
.icon-plus {
width: 1.2em;
height: 1.2em;
}
.icon-filter {
width: 1.2em;
height: 1.2em;
}
.icon-search {
width: 1.2em;
height: 1.2em;
}
.icon-times {
width: 1.2em;
height: 1.2em;
}
.icon-user {
width: 1.2em;
height: 1.2em;
}
.icon-copy {
width: 1.2em;
height: 1.2em;
}
.icon-eye {
width: 1.2em;
height: 1.2em;
}
.icon-eye-slash {
width: 1.2em;
height: 1.2em;
}
.icon-trash {
width: 1.2em;
height: 1.2em;
}
.icon-grip-vertical {
width: 1.2em;
height: 1.2em;
}
.icon-table {
width: 1.2em;
height: 1.2em;
}
.icon-info-circle {
width: 1.2em;
height: 1.2em;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>
<svg class="icon icon-lock" viewBox="0 0 24 24">
<path
d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" />
</svg>
安全密码备忘录
</h1>
<p class="subtitle">本地存储,安全可靠,管理您的所有账号和密码</p>
</header>
<div class="main-content">
<section class="auth-section">
<h2 class="section-title">
<svg class="icon icon-key" viewBox="0 0 24 24">
<path
d="M21 10h-8.35C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H13l2 2 2-2 2 2 4-4.04L21 10zM7 15c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z" />
</svg>
访问控制
</h2>
<div id="login-section">
<div class="form-group">
<label for="master-password">主密码</label>
<input type="password" id="master-password" placeholder="请输入主密码">
<div class="password-strength">
<div id="password-strength-bar" class="strength-bar"></div>
</div>
</div>
<button id="login-btn" class="btn btn-primary">解锁备忘录</button>
<p class="security-notice">
<svg class="icon icon-info-circle" viewBox="0 0 24 24"
style="width: 1em; height: 1em; margin-right: 5px;">
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" />
</svg>
数据存储在浏览器本地,请务必记住主密码,忘记密码将无法恢复数据。
</p>
</div>
<div id="add-password-section" class="hidden">
<h2 class="section-title">
<svg class="icon icon-plus" viewBox="0 0 24 24">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</svg>
添加新账号
</h2>
<div class="form-section">
<div class="form-group">
<label for="account-name">账户名称</label>
<input type="text" id="account-name" placeholder="例如:Gmail、微信">
</div>
<div class="form-group">
<label for="username">用户名/邮箱</label>
<input type="text" id="username" placeholder="输入用户名或邮箱">
</div>
<div class="form-group">
<label for="password">密码</label>
<div class="compact-controls">
<div class="password-input-wrapper" style="flex: 2;">
<input type="text" id="password" placeholder="输入密码">
<span class="char-count" id="password-char-count">0</span>
</div>
<button type="button" id="generate-password" class="btn btn-small"
style="flex: 1; background: #9b59b6;">生成</button>
</div>
<div class="password-length">
<span class="password-length-label">密码长度:</span> <span id="password-length">0</span> 个字符
</div>
</div>
<div class="form-group">
<label for="website">网站/应用地址</label>
<input type="text" id="website" placeholder="https://example.com">
</div>
<div class="form-group">
<label for="notes">备注</label>
<textarea id="notes" rows="2" placeholder="可选的备注信息"></textarea>
</div>
</div>
<div class="compact-controls">
<button id="save-password-btn" class="btn btn-success">保存</button>
<div class="password-input-wrapper" style="flex: 1; position: relative;">
<button id="logout-btn" class="btn btn-danger">锁定 (<span
id="lock-timer">300</span>s)</button>
</div>
<button id="show-builtin-btn" class="btn btn-info">示例账号</button>
</div>
</div>
</section>
<section class="password-list-section">
<div class="controls-row">
<div class="controls-row">
<h2 class="section-title">
<svg class="icon icon-list" viewBox="0 0 24 24">
<path
d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z" />
</svg>
账号列表 (<span id="password-count">0</span>)
</h2>
<div class="filter-section" id="filter-section" style="display: none;">
<div class="filter-controls">
<div class="filter-input-group">
<input type="text" id="account-filter" placeholder="按账户名称筛选..."
style="display: none;">
</div>
<button id="quick-filter-btn" class="btn btn-small btn-warning">快速筛选</button>
<button id="clear-filter-btn" class="clear-filter-btn" style="display: none;">
<svg class="icon icon-times" viewBox="0 0 24 24"
style="width: 0.9em; height: 0.9em;">
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
</svg>
清除筛选
</button>
</div>
</div>
</div>
<div class="view-toggle mobile-only">
<button id="card-view-btn" class="active" title="卡片视图">
<svg class="icon icon-grip-vertical" viewBox="0 0 24 24">
<path
d="M9 4h2v2H9zm4 0h2v2h-2zM9 8h2v2H9zm4 0h2v2h-2zm-4 4h2v2H9zm4 0h2v2h-2zm-4 4h2v2H9zm4 0h2v2h-2z" />
</svg>
</button>
<button id="table-view-btn" title="表格视图">
<svg class="icon icon-table" viewBox="0 0 24 24">
<path d="M3 9h18V7H3v2zm0 4h18v-2H3v2zm0 4h18v-2H3v2zM3 5v2h18V5H3z" />
</svg>
</button>
</div>
</div>
<div class="filter-tags" id="filter-tags"></div>
<div class="filter-results" id="filter-results">显示所有账号</div>
<div class="tab-buttons">
<button class="tab-button active" data-tab="custom">我的账号</button>
<button class="tab-button" data-tab="builtin">示例账号</button>
</div>
<div id="custom-passwords-container" class="tab-content active passwords-container">
<div class="empty-state">
<svg class="icon icon-lock" viewBox="0 0 24 24"
style="width: 3em; height: 3em; margin-bottom: 10px;">
<path
d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"
fill="#bdc3c7" />
</svg>
<h3>备忘录已锁定</h3>
<p>请输入主密码解锁以查看账号信息</p>
</div>
</div>
<div id="builtin-passwords-container" class="tab-content passwords-container">
<div class="empty-state">
<svg class="icon icon-lock" viewBox="0 0 24 24"
style="width: 3em; height: 3em; margin-bottom: 10px;">
<path
d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"
fill="#bdc3c7" />
</svg>
<h3>备忘录已锁定</h3>
<p>请输入主密码解锁以查看示例账号</p>
</div>
</div>
</section>
</div>
<footer>
<p>安全密码备忘录 © 2023 | 本地存储 | 请勿在公共电脑上使用</p>
</footer>
</div>
<script>
// 主密码和加密密钥
let masterPassword = '';
let isAuthenticated = false;
let isTableView = false;
let currentTab = 'custom';
let currentFilter = '';
let allCustomPasswords = [];
let allBuiltinPasswords = [];
// 自动锁定相关变量
let lockTimer = null;
let lockTimeout = 5 * 60; // 5分钟,单位:秒
let remainingTime = lockTimeout;
let lastActivityTime = Date.now();
// 账号字段名称数组
const accountFields = ['accountName', 'username', 'password', 'website', 'notes'];
// 从二维数组生成内置账号对象数组
function generateBuiltinAccounts() {
const accounts = [];
for (let i = 0; i < builtinAccounts.length; i++) {
const accountData = builtinAccounts[i];
const account = {};
for (let j = 0; j < accountFields.length; j++) {
account[accountFields[j]] = accountData[j];
}
accounts.push(account);
}
return accounts;
}
// 筛选标签 - 初始为空,从账号名称中自动提取
let commonFilters = [];
let builtinFilters = [];
// DOM 元素
const loginSection = document.getElementById('login-section');
const addPasswordSection = document.getElementById('add-password-section');
const customPasswordsContainer = document.getElementById('custom-passwords-container');
const builtinPasswordsContainer = document.getElementById('builtin-passwords-container');
const passwordCountElement = document.getElementById('password-count');
const masterPasswordInput = document.getElementById('master-password');
const loginBtn = document.getElementById('login-btn');
const logoutBtn = document.getElementById('logout-btn');
const savePasswordBtn = document.getElementById('save-password-btn');
const generatePasswordBtn = document.getElementById('generate-password');
const showBuiltinBtn = document.getElementById('show-builtin-btn');
const passwordStrengthBar = document.getElementById('password-strength-bar');
const cardViewBtn = document.getElementById('card-view-btn');
const tableViewBtn = document.getElementById('table-view-btn');
const tabButtons = document.querySelectorAll('.tab-button');
const accountFilterInput = document.getElementById('account-filter');
const clearFilterBtn = document.getElementById('clear-filter-btn');
const filterResults = document.getElementById('filter-results');
const filterTags = document.getElementById('filter-tags');
const quickFilterBtn = document.getElementById('quick-filter-btn');
const filterSection = document.getElementById('filter-section');
const passwordInput = document.getElementById('password');
const passwordCharCount = document.getElementById('password-char-count');
const passwordLengthElement = document.getElementById('password-length');
const lockTimerElement = document.getElementById('lock-timer');
// 初始化
document.addEventListener('DOMContentLoaded', function () {
// 检查是否已有主密码设置
checkExistingMasterPassword();
// 事件监听
loginBtn.addEventListener('click', handleLogin);
logoutBtn.addEventListener('click', handleLogout);
savePasswordBtn.addEventListener('click', savePassword);
generatePasswordBtn.addEventListener('click', generateRandomPassword);
showBuiltinBtn.addEventListener('click', showBuiltinAccounts);
masterPasswordInput.addEventListener('input', checkPasswordStrength);
cardViewBtn.addEventListener('click', () => switchView('card'));
tableViewBtn.addEventListener('click', () => switchView('table'));
accountFilterInput.addEventListener('input', applyFilter);
clearFilterBtn.addEventListener('click', clearFilter);
quickFilterBtn.addEventListener('click', showQuickFilter);
// 密码输入框字数统计
passwordInput.addEventListener('input', updatePasswordLength);
// 标签页切换
tabButtons.forEach(button => {
button.addEventListener('click', function () {
const tabId = this.getAttribute('data-tab');
switchTab(tabId);
});
});
// 密码输入框回车登录
masterPasswordInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
handleLogin();
}
});
// 筛选输入框回车筛选
accountFilterInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
applyFilter();
}
});
// 用户活动检测
document.addEventListener('click', updateLastActivityTime);
document.addEventListener('keypress', updateLastActivityTime);
document.addEventListener('scroll', updateLastActivityTime);
// 初始隐藏筛选区域
filterSection.style.display = 'none';
// 初始化内置账号
allBuiltinPasswords = generateBuiltinAccounts();
builtinFilters = extractAccountNames(allBuiltinPasswords);
// 初始更新密码长度显示
updatePasswordLength();
// 初始更新锁定计时器显示
updateLockTimerDisplay();
// 启动自动锁定检查
startAutoLockCheck();
});
// 更新最后活动时间
function updateLastActivityTime() {
if (isAuthenticated) {
lastActivityTime = Date.now();
resetLockTimer();
}
}
// 重置锁定计时器
function resetLockTimer() {
remainingTime = lockTimeout;
updateLockTimerDisplay();
}
// 开始自动锁定检查
function startAutoLockCheck() {
if (lockTimer) {
clearInterval(lockTimer);
}
lockTimer = setInterval(function () {
if (isAuthenticated) {
const currentTime = Date.now();
const inactiveTime = Math.floor((currentTime - lastActivityTime) / 1000);
if (inactiveTime >= lockTimeout) {
// 自动锁定
handleLogout();
alert('由于长时间无操作,备忘录已自动锁定');
} else {
// 更新剩余时间
remainingTime = lockTimeout - inactiveTime;
updateLockTimerDisplay();
}
}
}, 1000); // 每秒检查一次
}
// 更新锁定计时器显示
function updateLockTimerDisplay() {
lockTimerElement.textContent = remainingTime;
// 根据剩余时间改变颜色
if (remainingTime <= 30) {
lockTimerElement.style.color = '#e74c3c'; // 红色,剩余时间少
} else if (remainingTime <= 60) {
lockTimerElement.style.color = '#f39c12'; // 橙色
} else {
lockTimerElement.style.color = 'white'; // 白色
}
}
// 检查是否已有主密码
function checkExistingMasterPassword() {
const storedMasterHash = localStorage.getItem('masterPasswordHash');
if (!storedMasterHash) {
// 首次使用,需要设置主密码
loginBtn.textContent = '设置主密码';
document.querySelector('.security-notice').innerHTML = `
<svg class="icon icon-info-circle" viewBox="0 0 24 24" style="width: 1em; height: 1em; margin-right: 5px;">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
首次使用,请设置主密码。请务必记住此密码,忘记密码将无法恢复数据。
`;
}
}
// 处理登录/设置主密码
function handleLogin() {
const password = masterPasswordInput.value.trim();
if (!password) {
alert('请输入主密码');
return;
}
const storedMasterHash = localStorage.getItem('masterPasswordHash');
if (!storedMasterHash) {
// 首次使用,设置主密码
setMasterPassword(password);
} else {
// 验证主密码
if (simpleHash(password) === storedMasterHash) {
authenticateUser(password);
} else {
alert('密码错误,请重试');
masterPasswordInput.value = '';
}
}
}
// 设置主密码
function setMasterPassword(password) {
const hash = simpleHash(password);
localStorage.setItem('masterPasswordHash', hash);
authenticateUser(password);
alert('主密码设置成功!请务必记住此密码。');
}
// 验证用户
function authenticateUser(password) {
masterPassword = password;
isAuthenticated = true;
// 更新最后活动时间
lastActivityTime = Date.now();
resetLockTimer();
// 切换UI
loginSection.classList.add('hidden');
addPasswordSection.classList.remove('hidden');
masterPasswordInput.value = '';
// 显示筛选区域
filterSection.style.display = 'block';
// 加载保存的密码
loadPasswords();
// 加载示例账号
displayBuiltinAccounts();
}
// 处理登出
function handleLogout() {
isAuthenticated = false;
masterPassword = '';
// 切换UI
loginSection.classList.remove('hidden');
addPasswordSection.classList.add('hidden');
// 隐藏筛选区域
filterSection.style.display = 'none';
// 清空密码显示
customPasswordsContainer.innerHTML = `
<div class="empty-state">
<svg class="icon icon-lock" viewBox="0 0 24 24" style="width: 3em; height: 3em; margin-bottom: 10px;">
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" fill="#bdc3c7"/>
</svg>
<h3>备忘录已锁定</h3>
<p>请输入主密码解锁以查看保存的账号信息</p>
</div>
`;
builtinPasswordsContainer.innerHTML = `
<div class="empty-state">
<svg class="icon icon-lock" viewBox="0 0 24 24" style="width: 3em; height: 3em; margin-bottom: 10px;">
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" fill="#bdc3c7"/>
</svg>
<h3>备忘录已锁定</h3>
<p>请输入主密码解锁以查看示例账号</p>
</div>
`;
passwordCountElement.textContent = '0';
// 清除筛选
clearFilter();
// 重置锁定计时器
resetLockTimer();
}
// 切换标签页
function switchTab(tabId) {
currentTab = tabId;
// 更新标签按钮状态
tabButtons.forEach(button => {
if (button.getAttribute('data-tab') === tabId) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
// 显示对应的内容
document.querySelectorAll('.tab-content').forEach(content => {
if (content.id === `${tabId}-passwords-container`) {
content.classList.add('active');
} else {
content.classList.remove('active');
}
});
// 更新筛选标签
updateFilterTags();
// 重新应用筛选
applyFilter();
// 更新最后活动时间
updateLastActivityTime();
}
// 切换视图模式
function switchView(viewType) {
isTableView = viewType === 'table';
if (isTableView) {
cardViewBtn.classList.remove('active');
tableViewBtn.classList.add('active');
} else {
tableViewBtn.classList.remove('active');
cardViewBtn.classList.add('active');
}
// 重新加载密码以应用新视图
if (isAuthenticated) {
if (currentTab === 'custom') {
displayFilteredPasswords();
} else if (currentTab === 'builtin') {
displayBuiltinAccounts();
}
}
// 更新最后活动时间
updateLastActivityTime();
}
// 加载密码
function loadPasswords() {
const encryptedPasswords = localStorage.getItem('encryptedPasswords');
if (!encryptedPasswords) {
allCustomPasswords = [];
updateFilterTags();
displayFilteredPasswords();
updatePasswordCount();
return;
}
try {
// 简单解密(在实际应用中应使用更安全的加密方法)
const decryptedData = simpleDecrypt(encryptedPasswords, masterPassword);
allCustomPasswords = JSON.parse(decryptedData);
updateFilterTags();
displayFilteredPasswords();
updatePasswordCount();
} catch (error) {
console.error('解密失败:', error);
alert('加载数据时发生错误,请检查主密码是否正确');
handleLogout();
}
// 更新最后活动时间
updateLastActivityTime();
}
// 更新密码数量显示
function updatePasswordCount() {
let totalCount = 0;
if (currentTab === 'custom') {
totalCount = allCustomPasswords.length;
} else if (currentTab === 'builtin') {
totalCount = allBuiltinPasswords.length;
}
passwordCountElement.textContent = totalCount;
}
// 从账号名称中提取标签
function extractAccountNames(passwords) {
return [...new Set(passwords.map(p => p.accountName))];
}
// 应用筛选
function applyFilter() {
currentFilter = accountFilterInput.value.trim().toLowerCase();
// 显示/隐藏清除筛选按钮
if (currentFilter) {
clearFilterBtn.style.display = 'block';
} else {
clearFilterBtn.style.display = 'none';
}
// 重新显示密码
if (currentTab === 'custom') {
displayFilteredPasswords();
} else if (currentTab === 'builtin') {
displayBuiltinAccounts();
}
// 更新筛选结果信息
updateFilterResults();
// 更新最后活动时间
updateLastActivityTime();
}
// 更新筛选结果信息
function updateFilterResults() {
let filteredCount = 0;
if (currentTab === 'custom') {
if (currentFilter) {
filteredCount = allCustomPasswords.filter(password => {
return password.accountName.toLowerCase().includes(currentFilter) ||
password.username.toLowerCase().includes(currentFilter) ||
(password.notes && password.notes.toLowerCase().includes(currentFilter));
}).length;
} else {
filteredCount = allCustomPasswords.length;
}
} else if (currentTab === 'builtin') {
if (currentFilter) {
filteredCount = allBuiltinPasswords.filter(password => {
return password.accountName.toLowerCase().includes(currentFilter) ||
password.username.toLowerCase().includes(currentFilter) ||
(password.notes && password.notes.toLowerCase().includes(currentFilter));
}).length;
} else {
filteredCount = allBuiltinPasswords.length;
}
}
if (currentFilter) {
filterResults.innerHTML = `找到 <span>${filteredCount}</span> 个匹配"${currentFilter}"的账号`;
} else {
filterResults.innerHTML = `显示所有 <span>${filteredCount}</span> 个账号`;
}
}
// 清除筛选
function clearFilter() {
accountFilterInput.value = '';
currentFilter = '';
clearFilterBtn.style.display = 'none';
// 移除所有标签的活动状态
document.querySelectorAll('.filter-tag').forEach(tag => {
tag.classList.remove('active');
});
applyFilter();
// 更新最后活动时间
updateLastActivityTime();
}
// 显示快速筛选标签
function showQuickFilter() {
// 如果标签已显示,则隐藏;否则显示
if (filterTags.style.display === 'flex') {
filterTags.style.display = 'none';
quickFilterBtn.innerHTML = '快速筛选';
} else {
filterTags.style.display = 'flex';
quickFilterBtn.innerHTML = '隐藏标签';
}
// 更新最后活动时间
updateLastActivityTime();
}
// 更新筛选标签
function updateFilterTags() {
filterTags.innerHTML = '';
// 根据当前标签页选择筛选标签
let filters = [];
if (currentTab === 'custom') {
filters = extractAccountNames(allCustomPasswords);
} else if (currentTab === 'builtin') {
filters = builtinFilters;
}
// 如果没有账号,添加一些默认标签
if (filters.length === 0) {
filters = ['邮箱', '社交', '银行', '购物', '云', '办公', '视频', '学习'];
}
// 限制标签数量,避免太多
const maxTags = 15;
const tagsToShow = filters.slice(0, maxTags);
// 添加筛选标签
tagsToShow.forEach(filter => {
const tag = document.createElement('div');
tag.className = 'filter-tag';
tag.textContent = filter;
tag.addEventListener('click', function () {
// 如果已经是活动状态,则取消筛选
if (tag.classList.contains('active')) {
tag.classList.remove('active');
clearFilter();
} else {
// 移除其他标签的活动状态
document.querySelectorAll('.filter-tag').forEach(t => {
t.classList.remove('active');
});
tag.classList.add('active');
accountFilterInput.value = filter;
applyFilter();
}
// 更新最后活动时间
updateLastActivityTime();
});
filterTags.appendChild(tag);
});
// 如果标签数量超过限制,显示提示
if (filters.length > maxTags) {
const moreTag = document.createElement('div');
moreTag.className = 'filter-tag';
moreTag.textContent = `+${filters.length - maxTags}更多`;
moreTag.style.backgroundColor = '#f5f5f5';
moreTag.style.color = '#757575';
moreTag.style.cursor = 'default';
filterTags.appendChild(moreTag);
}
}
// 显示筛选后的密码
function displayFilteredPasswords() {
let filteredPasswords = [];
if (currentFilter) {
// 筛选密码
filteredPasswords = allCustomPasswords.filter(password => {
return password.accountName.toLowerCase().includes(currentFilter) ||
password.username.toLowerCase().includes(currentFilter) ||
(password.notes && password.notes.toLowerCase().includes(currentFilter));
});
} else {
// 显示所有密码
filteredPasswords = allCustomPasswords;
}
if (filteredPasswords.length === 0) {
if (allCustomPasswords.length === 0) {
customPasswordsContainer.innerHTML = `
<div class="empty-state">
<svg class="icon icon-key" viewBox="0 0 24 24" style="width: 3em; height: 3em; margin-bottom: 10px;">
<path d="M21 10h-8.35C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H13l2 2 2-2 2 2 4-4.04L21 10zM7 15c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z" fill="#bdc3c7"/>
</svg>
<h3>暂无保存的账号</h3>
<p>点击左侧"添加新账号"按钮开始保存您的账号信息</p>
</div>
`;
} else {
customPasswordsContainer.innerHTML = `
<div class="no-results">
<svg class="icon icon-search" viewBox="0 0 24 24" style="width: 3em; height: 3em; margin-bottom: 10px;">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="#bdc3c7"/>
</svg>
<h3>未找到匹配的账号</h3>
<p>没有找到包含"${currentFilter}"的账号,请尝试其他关键词</p>
${currentFilter ? `<button class="btn btn-small" onclick="clearFilter()" style="margin-top: 10px;">清除筛选</button>` : ''}
</div>
`;
}
return;
}
// 显示密码列表
if (isTableView) {
displayPasswordsTable(filteredPasswords, customPasswordsContainer, false);
} else {
displayPasswordsCards(filteredPasswords, customPasswordsContainer, false);
}
// 更新最后活动时间
updateLastActivityTime();
}
// 显示内置账号
function displayBuiltinAccounts() {
if (!isAuthenticated) {
builtinPasswordsContainer.innerHTML = `
<div class="empty-state">
<svg class="icon icon-lock" viewBox="0 0 24 24" style="width: 3em; height: 3em; margin-bottom: 10px;">
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" fill="#bdc3c7"/>
</svg>
<h3>备忘录已锁定</h3>
<p>请输入主密码解锁以查看示例账号</p>
</div>
`;
return;
}
let filteredBuiltinPasswords = allBuiltinPasswords;
if (currentFilter) {
// 筛选内置账号
filteredBuiltinPasswords = allBuiltinPasswords.filter(password => {
return password.accountName.toLowerCase().includes(currentFilter) ||
password.username.toLowerCase().includes(currentFilter) ||
(password.notes && password.notes.toLowerCase().includes(currentFilter));
});
}
if (filteredBuiltinPasswords.length === 0) {
builtinPasswordsContainer.innerHTML = `
<div class="no-results">
<svg class="icon icon-search" viewBox="0 0 24 24" style="width: 3em; height: 3em; margin-bottom: 10px;">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" fill="#bdc3c7"/>
</svg>
<h3>未找到匹配的示例账号</h3>
<p>没有找到包含"${currentFilter}"的示例账号,请尝试其他关键词</p>
${currentFilter ? `<button class="btn btn-small" onclick="clearFilter()" style="margin-top: 10px;">清除筛选</button>` : ''}
</div>
`;
return;
}
if (isTableView) {
displayPasswordsTable(filteredBuiltinPasswords, builtinPasswordsContainer, true);
} else {
displayPasswordsCards(filteredBuiltinPasswords, builtinPasswordsContainer, true);
}
// 更新最后活动时间
updateLastActivityTime();
}
// 显示卡片视图
function displayPasswordsCards(passwords, container, isBuiltin) {
container.innerHTML = '';
passwords.forEach((password, index) => {
const passwordElement = document.createElement('div');
passwordElement.className = `password-item ${isBuiltin ? 'builtin-account' : ''}`;
passwordElement.innerHTML = `
<div class="password-header">
<div class="password-title">
${escapeHtml(password.accountName)}
<span class="account-badge ${isBuiltin ? 'badge-builtin' : 'badge-custom'}">
${isBuiltin ? '示例' : '自定义'}
</span>
</div>
<div class="password-actions">
<button class="copy-username" data-index="${index}" title="复制用户名">
<svg class="icon icon-user" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>
</button>
<button class="copy-password" data-index="${index}" title="复制密码">
<svg class="icon icon-copy" viewBox="0 0 24 24">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
</button>
<button class="toggle-password" data-index="${index}" title="显示/隐藏密码">
<svg class="icon icon-eye" viewBox="0 0 24 24">
<path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
</svg>
</button>
${!isBuiltin ? `
<button class="delete-password" data-index="${index}" title="删除">
<svg class="icon icon-trash" viewBox="0 0 24 24">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
</svg>
</button>
` : ''}
</div>
</div>
<div class="password-field">
<label>用户名:</label>
<div class="password-value username-value" id="username-${isBuiltin ? 'builtin-' : ''}${index}">${escapeHtml(password.username)}</div>
</div>
<div class="password-field">
<label>密码:</label>
<div class="password-value masked password-value" id="password-${isBuiltin ? 'builtin-' : ''}${index}">
${password.password}
<span class="password-length-label" style="margin-top: 4px;"> ${password.password.length} 字符</span>
</div>
</div>
${password.website ? `
<div class="password-field">
<label>网址:</label>
<div class="password-value">
<a href="${password.website}" target="_blank" style="color: #3498db; text-decoration: none;">${escapeHtml(password.website.length > 30 ? password.website.substring(0, 30) + '...' : password.website)}</a>
</div>
</div>` : ''}
${password.notes ? `
<div class="password-field">
<label>备注:</label>
<div class="password-value">${escapeHtml(password.notes.length > 50 ? password.notes.substring(0, 50) + '...' : password.notes)}</div>
</div>` : ''}
`;
container.appendChild(passwordElement);
});
if (isBuiltin) {
attachBuiltinPasswordEvents(passwords);
} else {
attachCustomPasswordEvents(passwords);
}
}
// 显示表格视图
function displayPasswordsTable(passwords, container, isBuiltin) {
let tableHTML = `
<table class="table-view">
<thead>
<tr>
<th style="width: 25%;">账户名称</th>
<th style="width: 25%;">用户名</th>
<th style="width: 20%;">密码</th>
<th style="width: 30%;">操作</th>
</tr>
</thead>
<tbody>
`;
passwords.forEach((password, index) => {
tableHTML += `
<tr>
<td>
${escapeHtml(password.accountName)}
<span class="account-badge ${isBuiltin ? 'badge-builtin' : 'badge-custom'}">
${isBuiltin ? '示例' : '自定义'}
</span>
</td>
<td>${escapeHtml(password.username)}</td>
<td>
<span class="masked password-value" id="table-password-${isBuiltin ? 'builtin-' : ''}${index}">${password.password}</span>
<span class="password-length" style="font-size: 0.7rem; color: #7f8c8d;">
${password.password.length} 字符
</span>
</td>
<td>
<div style="display: flex; gap: 5px;">
<button class="copy-username btn-small" data-index="${index}" title="复制用户名" style="padding: 4px 8px; font-size: 0.8rem;">
<svg class="icon icon-user" viewBox="0 0 24 24" style="width: 0.9em; height: 0.9em;">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>
</button>
<button class="copy-password btn-small" data-index="${index}" title="复制密码" style="padding: 4px 8px; font-size: 0.8rem;">
<svg class="icon icon-copy" viewBox="0 0 24 24" style="width: 0.9em; height: 0.9em;">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
</button>
<button class="toggle-password btn-small" data-index="${index}" title="显示/隐藏密码" style="padding: 4px 8px; font-size: 0.8rem;">
<svg class="icon icon-eye" viewBox="0 0 24 24" style="width: 0.9em; height: 0.9em;">
<path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
</svg>
</button>
${!isBuiltin ? `
<button class="delete-password btn-small" data-index="${index}" title="删除" style="padding: 4px 8px; font-size: 0.8rem; background: #e74c3c;">
<svg class="icon icon-trash" viewBox="0 0 24 24" style="width: 0.9em; height: 0.9em;">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
</svg>
</button>
` : ''}
</div>
</td>
</tr>
`;
});
tableHTML += `
</tbody>
</table>
`;
container.innerHTML = tableHTML;
if (isBuiltin) {
attachBuiltinPasswordEvents(passwords);
} else {
attachCustomPasswordEvents(passwords);
}
}
// 附加自定义密码事件监听
function attachCustomPasswordEvents(passwords) {
// 复制用户名
document.querySelectorAll('.copy-username').forEach(button => {
button.addEventListener('click', function () {
const index = this.getAttribute('data-index');
copyToClipboard(passwords[index].username, '用户名');
updateLastActivityTime();
});
});
// 复制密码
document.querySelectorAll('.copy-password').forEach(button => {
button.addEventListener('click', function () {
const index = this.getAttribute('data-index');
copyToClipboard(passwords[index].password, '密码');
updateLastActivityTime();
});
});
// 显示/隐藏密码
document.querySelectorAll('.toggle-password').forEach(button => {
button.addEventListener('click', function () {
const index = this.getAttribute('data-index');
const prefix = isTableView ? 'table-password-' : 'password-';
const passwordElement = document.getElementById(`${prefix}${index}`);
const icon = this.querySelector('svg');
if (passwordElement.classList.contains('masked')) {
passwordElement.classList.remove('masked');
// 切换为隐藏眼睛图标
icon.innerHTML = '<path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/>';
} else {
passwordElement.classList.add('masked');
// 切换为显示眼睛图标
icon.innerHTML = '<path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>';
}
updateLastActivityTime();
});
});
// 删除密码
document.querySelectorAll('.delete-password').forEach(button => {
button.addEventListener('click', function () {
const index = this.getAttribute('data-index');
if (confirm('确定要删除这个账号信息吗?')) {
deletePassword(index);
}
updateLastActivityTime();
});
});
}
// 附加内置密码事件监听
function attachBuiltinPasswordEvents(passwords) {
// 复制用户名
document.querySelectorAll('.copy-username').forEach(button => {
button.addEventListener('click', function () {
const index = this.getAttribute('data-index');
copyToClipboard(passwords[index].username, '用户名');
updateLastActivityTime();
});
});
// 复制密码
document.querySelectorAll('.copy-password').forEach(button => {
button.addEventListener('click', function () {
const index = this.getAttribute('data-index');
copyToClipboard(passwords[index].password, '密码');
updateLastActivityTime();
});
});
// 显示/隐藏密码
document.querySelectorAll('.toggle-password').forEach(button => {
button.addEventListener('click', function () {
const index = this.getAttribute('data-index');
const prefix = isTableView ? 'table-password-builtin-' : 'password-builtin-';
const passwordElement = document.getElementById(`${prefix}${index}`);
const icon = this.querySelector('svg');
if (passwordElement.classList.contains('masked')) {
passwordElement.classList.remove('masked');
// 切换为隐藏眼睛图标
icon.innerHTML = '<path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/>';
} else {
passwordElement.classList.add('masked');
// 切换为显示眼睛图标
icon.innerHTML = '<path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>';
}
updateLastActivityTime();
});
});
}
// 显示内置账号
function showBuiltinAccounts() {
switchTab('builtin');
updateLastActivityTime();
}
// 保存新密码
function savePassword() {
const accountName = document.getElementById('account-name').value.trim();
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value.trim();
const website = document.getElementById('website').value.trim();
const notes = document.getElementById('notes').value.trim();
if (!accountName || !username || !password) {
alert('账户名称、用户名和密码为必填项');
return;
}
// 获取现有密码
let passwords = [];
const encryptedPasswords = localStorage.getItem('encryptedPasswords');
if (encryptedPasswords) {
try {
const decryptedData = simpleDecrypt(encryptedPasswords, masterPassword);
passwords = JSON.parse(decryptedData);
} catch (error) {
console.error('解密失败:', error);
alert('保存失败,请重新登录后重试');
return;
}
}
// 添加新密码
passwords.push({
accountName,
username,
password,
website,
notes
});
// 加密并保存
const encryptedData = simpleEncrypt(JSON.stringify(passwords), masterPassword);
localStorage.setItem('encryptedPasswords', encryptedData);
// 清空表单
document.getElementById('account-name').value = '';
document.getElementById('username').value = '';
document.getElementById('password').value = '';
document.getElementById('website').value = '';
document.getElementById('notes').value = '';
// 重新加载密码列表
loadPasswords();
// 切换到自定义账号标签
switchTab('custom');
alert('账号信息保存成功!');
updateLastActivityTime();
}
// 删除密码
function deletePassword(index) {
const encryptedPasswords = localStorage.getItem('encryptedPasswords');
if (!encryptedPasswords) return;
try {
const decryptedData = simpleDecrypt(encryptedPasswords, masterPassword);
let passwords = JSON.parse(decryptedData);
// 删除指定索引的密码
passwords.splice(index, 1);
// 重新加密保存
const newEncryptedData = simpleEncrypt(JSON.stringify(passwords), masterPassword);
localStorage.setItem('encryptedPasswords', newEncryptedData);
// 重新加载
loadPasswords();
} catch (error) {
console.error('删除失败:', error);
alert('删除失败,请重试');
}
}
// 生成随机密码
function generateRandomPassword() {
const length = 12;
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
let password = "";
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
document.getElementById('password').value = password;
updatePasswordLength();
checkPasswordStrength();
updateLastActivityTime();
}
// 更新密码长度显示
function updatePasswordLength() {
const password = passwordInput.value;
const length = password.length;
passwordCharCount.textContent = length;
passwordLengthElement.textContent = length;
// 根据长度改变颜色
if (length === 0) {
passwordCharCount.style.color = '#7f8c8d';
} else if (length < 8) {
passwordCharCount.style.color = '#e74c3c'; // 红色,太短
} else if (length < 12) {
passwordCharCount.style.color = '#f39c12'; // 橙色,中等
} else {
passwordCharCount.style.color = '#2ecc71'; // 绿色,足够长
}
}
// 检查密码强度
function checkPasswordStrength() {
const password = document.getElementById('password') ? document.getElementById('password').value : masterPasswordInput.value;
let strength = 0;
if (password.length >= 8) strength++;
if (/[a-z]/.test(password)) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/[0-9]/.test(password)) strength++;
if (/[^a-zA-Z0-9]/.test(password)) strength++;
// 更新强度条
passwordStrengthBar.className = 'strength-bar';
if (password.length === 0) {
passwordStrengthBar.style.width = '0%';
} else if (strength <= 2) {
passwordStrengthBar.classList.add('strength-weak');
} else if (strength <= 4) {
passwordStrengthBar.classList.add('strength-medium');
} else {
passwordStrengthBar.classList.add('strength-strong');
}
}
// 复制到剪贴板
function copyToClipboard(text, type) {
navigator.clipboard.writeText(text).then(() => {
alert(`${type} 已复制到剪贴板`);
}).catch(err => {
console.error('复制失败:', err);
// 备用复制方法
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
alert(`${type} 已复制到剪贴板`);
} catch (err) {
console.error('备用复制方法失败:', err);
alert('复制失败,请手动复制');
}
document.body.removeChild(textArea);
});
}
// 简单的哈希函数(仅用于演示,实际应用应使用更安全的哈希算法)
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0; // 转换为32位整数
}
return hash.toString(16);
}
// 简单加密(仅用于演示,实际应用应使用更安全的加密算法)
function simpleEncrypt(data, key) {
return btoa(escape(data + key));
}
// 简单解密
function simpleDecrypt(encryptedData, key) {
const decrypted = unescape(atob(encryptedData));
return decrypted.slice(0, -key.length);
}
// 转义HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>