Files
mengyakeyvault/mengyakeyvault-frontend/src/components/PasswordManager.js
2026-03-11 21:15:06 +08:00

189 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './PasswordManager.css';
import PasswordList from './PasswordList';
import PasswordForm from './PasswordForm';
import SearchBar from './SearchBar';
// API 地址配置:优先使用环境变量,否则根据构建模式自动选择
const API_BASE = process.env.REACT_APP_API_BASE ||
(process.env.NODE_ENV === 'production'
? 'https://keyvault.api.shumengya.top/api'
: 'http://localhost:8080/api');
const PasswordManager = () => {
const [entries, setEntries] = useState([]);
const [filteredEntries, setFilteredEntries] = useState([]);
const [searchKeyword, setSearchKeyword] = useState('');
const [editingEntry, setEditingEntry] = useState(null);
const [showForm, setShowForm] = useState(false);
const [loading, setLoading] = useState(true);
// PWA 安装提示
const [installPrompt, setInstallPrompt] = useState(null);
const [showInstallBanner, setShowInstallBanner] = useState(false);
useEffect(() => {
loadEntries();
}, []);
useEffect(() => {
filterEntries();
}, [searchKeyword, entries]);
// 监听 PWA 安装事件
useEffect(() => {
const handler = (e) => {
e.preventDefault();
setInstallPrompt(e);
setShowInstallBanner(true);
};
window.addEventListener('beforeinstallprompt', handler);
return () => window.removeEventListener('beforeinstallprompt', handler);
}, []);
const handleInstall = async () => {
if (!installPrompt) return;
installPrompt.prompt();
const { outcome } = await installPrompt.userChoice;
if (outcome === 'accepted') {
setShowInstallBanner(false);
setInstallPrompt(null);
}
};
const loadEntries = async () => {
try {
setLoading(true);
const response = await axios.get(`${API_BASE}/entries`);
setEntries(response.data.entries || []);
} catch (error) {
console.error('加载条目失败:', error);
} finally {
setLoading(false);
}
};
const filterEntries = () => {
if (!searchKeyword.trim()) {
setFilteredEntries(entries);
return;
}
const keyword = searchKeyword.toLowerCase();
const filtered = entries.filter(entry =>
entry.account?.toLowerCase().includes(keyword) ||
entry.username?.toLowerCase().includes(keyword) ||
entry.email?.toLowerCase().includes(keyword) ||
entry.website?.toLowerCase().includes(keyword) ||
entry.officialName?.toLowerCase().includes(keyword) ||
(entry.software && entry.software.toLowerCase().includes(keyword)) ||
entry.tags?.toLowerCase().includes(keyword)
);
setFilteredEntries(filtered);
};
const handleAdd = () => {
setEditingEntry(null);
setShowForm(true);
};
const handleEdit = (entry) => {
setEditingEntry(entry);
setShowForm(true);
};
const handleDelete = async (id) => {
if (!window.confirm('确定要删除这条记录吗?')) {
return;
}
try {
await axios.delete(`${API_BASE}/entries/${id}`);
loadEntries();
} catch (error) {
console.error('删除失败:', error);
alert('删除失败,请重试');
}
};
const handleSave = async (entryData) => {
try {
if (editingEntry) {
await axios.put(`${API_BASE}/entries`, { ...entryData, id: editingEntry.id });
} else {
await axios.post(`${API_BASE}/entries`, entryData);
}
setShowForm(false);
setEditingEntry(null);
loadEntries();
} catch (error) {
console.error('保存失败:', error);
alert('保存失败,请重试');
}
};
const handleCancel = () => {
setShowForm(false);
setEditingEntry(null);
};
if (loading) {
return (
<div className="manager-loading">
<div className="loading-spinner"></div>
</div>
);
}
return (
<div className="password-manager">
{/* PWA 安装横幅 */}
{showInstallBanner && (
<div className="pwa-install-banner">
<span className="pwa-banner-icon">🌱</span>
<span className="pwa-banner-text">将萌芽密码添加到桌面随时快速访问</span>
<button className="pwa-install-btn" onClick={handleInstall}>添加到桌面</button>
<button className="pwa-dismiss-btn" onClick={() => setShowInstallBanner(false)}></button>
</div>
)}
<nav className="manager-nav">
<div className="nav-content">
<img
src={`${process.env.PUBLIC_URL}/logo.png`}
alt="Logo"
className="nav-logo"
/>
<h1 className="nav-title">萌芽密码管理器</h1>
</div>
</nav>
<div className="manager-header">
<button className="add-button" onClick={handleAdd}>
+ 添加密码
</button>
</div>
<SearchBar
keyword={searchKeyword}
onKeywordChange={setSearchKeyword}
/>
<PasswordList
entries={filteredEntries}
onEdit={handleEdit}
onDelete={handleDelete}
/>
{showForm && (
<PasswordForm
entry={editingEntry}
onSave={handleSave}
onCancel={handleCancel}
/>
)}
</div>
);
};
export default PasswordManager;