chore: sync project updates

This commit is contained in:
root
2026-03-11 21:15:06 +08:00
parent 5a56af2ce8
commit f1b4dfc44e
35 changed files with 20688 additions and 20031 deletions

View File

@@ -1,155 +1,188 @@
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);
useEffect(() => {
loadEntries();
}, []);
useEffect(() => {
filterEntries();
}, [searchKeyword, entries]);
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.accountType?.toLowerCase().includes(keyword) ||
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">
<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;
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;