chore: 优化密码库前端界面与样式

This commit is contained in:
2026-06-24 21:58:49 +08:00
parent 485f5e0fc1
commit 21e1225a23
8 changed files with 248 additions and 167 deletions

View File

@@ -43,6 +43,10 @@
<meta property="og:type" content="website" />
<title>萌芽密码管理器</title>
<!-- 霞鹜文楷等宽 (LXGW WenKai Mono) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-webfont@1.7.0/lxgwwenkaimono-regular.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-webfont@1.7.0/lxgwwenkaimono-bold.css" />
</head>
<body>
<noscript>您需要启用JavaScript才能运行此应用程序。</noscript>

View File

@@ -1,7 +1,7 @@
{
"name": "萌芽密码管理器",
"short_name": "萌芽密码",
"description": "安全、便捷的个人密码管理工具",
"short_name": "萌芽密码管理器",
"description": "萌芽密码管理",
"start_url": "/",
"display": "standalone",
"orientation": "any",

View File

@@ -5,10 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#4caf50" />
<title>离线 - 萌芽密码管理器</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-webfont@1.7.0/lxgwwenkaimono-regular.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-webfont@1.7.0/lxgwwenkaimono-bold.css" />
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-family: 'LXGW WenKai Mono', ui-monospace, monospace;
background: linear-gradient(135deg, #f0fdf0 0%, #e8f5e9 100%);
min-height: 100vh;
display: flex;
@@ -29,6 +31,7 @@
h1 { font-size: 22px; color: #1b5e20; margin-bottom: 12px; font-weight: 700; }
p { font-size: 15px; color: #666; line-height: 1.6; margin-bottom: 24px; }
button {
font-family: inherit;
background: linear-gradient(135deg, #66bb6a, #4caf50);
color: #fff;
border: none;

View File

@@ -39,13 +39,13 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 25px 30px;
border-bottom: 2px solid #e8f5e9;
padding: 18px 22px;
border-bottom: 1px solid #e8f5e9;
}
.form-header h2 {
color: #2e7d32;
font-size: 24px;
font-size: 20px;
font-weight: 600;
margin: 0;
}
@@ -72,35 +72,47 @@
}
.password-form {
padding: 30px;
padding: 20px 22px 22px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
grid-template-columns: repeat(2, minmax(0, 1fr));
column-gap: 20px;
row-gap: 0;
margin-bottom: 14px;
align-items: start;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
gap: 6px;
min-width: 0;
width: 100%;
max-width: 100%;
}
.form-group--full {
grid-column: 1 / -1;
}
.form-group label {
color: #666;
font-size: 14px;
font-size: 13px;
font-weight: 500;
}
.form-group input {
padding: 12px;
padding: 10px 12px;
border: 2px solid #c8e6c9;
border-radius: 10px;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s;
background: white;
width: 100%;
min-width: 0;
box-sizing: border-box;
}
.form-group input:focus {
@@ -149,12 +161,16 @@
.password-input-group {
display: flex;
gap: 8px;
align-items: center;
gap: 6px;
align-items: stretch;
width: 100%;
min-width: 0;
}
.password-input-group input {
flex: 1;
flex: 1 1 0;
min-width: 0;
width: auto;
}
.generate-password-btn,
@@ -162,13 +178,17 @@
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
border: none;
color: white;
font-size: 18px;
font-size: 16px;
cursor: pointer;
padding: 10px 12px;
padding: 8px 10px;
border-radius: 8px;
transition: all 0.2s;
flex-shrink: 0;
box-shadow: 0 2px 5px rgba(76, 175, 80, 0.3);
align-self: stretch;
display: inline-flex;
align-items: center;
justify-content: center;
}
.generate-password-btn:hover,
@@ -183,11 +203,11 @@
}
.password-generator-options {
margin-top: 12px;
padding: 15px;
margin-top: 8px;
padding: 12px;
background: rgba(200, 230, 201, 0.2);
border-radius: 10px;
border: 2px solid #c8e6c9;
border-radius: 8px;
border: 1px solid #c8e6c9;
}
.option-row {
@@ -221,9 +241,9 @@
.option-checkboxes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 15px;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px 10px;
margin-bottom: 12px;
}
.checkbox-label {
@@ -291,10 +311,10 @@
.form-actions {
display: flex;
justify-content: flex-end;
gap: 15px;
margin-top: 30px;
padding-top: 20px;
border-top: 2px solid #e8f5e9;
gap: 12px;
margin-top: 18px;
padding-top: 16px;
border-top: 1px solid #e8f5e9;
}
.cancel-button,
@@ -338,21 +358,26 @@
}
.form-header {
padding: 20px;
padding: 16px 18px;
}
.form-header h2 {
font-size: 20px;
font-size: 18px;
}
.password-form {
padding: 20px;
padding: 16px 18px 18px;
}
.form-row {
grid-template-columns: 1fr;
gap: 15px;
margin-bottom: 15px;
grid-template-columns: minmax(0, 1fr);
column-gap: 0;
row-gap: 0;
margin-bottom: 12px;
}
.form-group--full {
grid-column: 1;
}
.form-actions {

View File

@@ -11,7 +11,6 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
website: '',
officialName: '',
tags: '',
logo: '',
});
const [passwordOptions, setPasswordOptions] = useState({
@@ -35,7 +34,6 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
website: entry.website || '',
officialName: entry.officialName || entry.software || '',
tags: entry.tags || '',
logo: entry.logo || '',
});
setShowPasswordGenerator(false);
} else {
@@ -54,7 +52,10 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
const handleSubmit = (e) => {
e.preventDefault();
onSave(formData);
onSave({
...formData,
logo: entry?.logo ?? '',
});
};
const generatePassword = () => {
@@ -133,6 +134,16 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
required
/>
</div>
<div className="form-group">
<label>标签</label>
<input
type="text"
name="tags"
value={formData.tags}
onChange={handleChange}
placeholder="标签(用空格或逗号分隔)"
/>
</div>
</div>
<div className="form-row">
@@ -143,7 +154,7 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
name="account"
value={formData.account}
onChange={handleChange}
placeholder="账号"
placeholder="登录账号"
/>
</div>
<div className="form-group">
@@ -270,7 +281,7 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
name="username"
value={formData.username}
onChange={handleChange}
placeholder="用户名"
placeholder="显示名 / 昵称"
/>
</div>
<div className="form-group">
@@ -308,29 +319,6 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
</div>
</div>
<div className="form-row">
<div className="form-group">
<label>标签</label>
<input
type="text"
name="tags"
value={formData.tags}
onChange={handleChange}
placeholder="标签(用空格分隔)"
/>
</div>
<div className="form-group">
<label>Logo图标URL可选</label>
<input
type="url"
name="logo"
value={formData.logo}
onChange={handleChange}
placeholder="https://example.com/logo.png留空则自动获取"
/>
</div>
</div>
<div className="form-actions">
<button type="button" className="cancel-button" onClick={onCancel}>
取消

View File

@@ -1,8 +1,8 @@
.password-list {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 20px;
margin-bottom: 30px;
gap: 14px;
margin-bottom: 24px;
}
/* 大屏幕1600px以上- 5列 */
@@ -44,8 +44,8 @@
.password-card {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 255, 248, 0.95) 100%);
border-radius: 20px;
padding: 18px;
border-radius: 14px;
padding: 12px 12px 10px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(76, 175, 80, 0.1);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(10px);
@@ -54,6 +54,8 @@
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.password-card::before {
@@ -81,22 +83,22 @@
.card-header {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 2px solid rgba(232, 245, 233, 0.6);
gap: 8px;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid rgba(232, 245, 233, 0.6);
}
.card-logo-wrapper {
flex-shrink: 0;
width: 50px;
height: 50px;
border-radius: 14px;
width: 40px;
height: 40px;
border-radius: 10px;
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
padding: 5px;
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.15);
transition: all 0.3s;
}
@@ -139,10 +141,10 @@
}
.card-type {
font-size: 16px;
font-size: 14px;
font-weight: 700;
color: #1b5e20;
line-height: 1.3;
line-height: 1.25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -196,17 +198,17 @@
.card-actions {
display: flex;
gap: 10px;
gap: 6px;
}
.edit-button,
.delete-button {
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(76, 175, 80, 0.2);
font-size: 18px;
font-size: 16px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
padding: 5px;
border-radius: 6px;
transition: all 0.2s;
display: flex;
align-items: center;
@@ -236,24 +238,26 @@
.card-content {
display: flex;
flex-direction: column;
gap: 8px;
gap: 4px;
padding-top: 0;
flex: 1;
min-height: 0;
}
.info-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
line-height: 1.4;
gap: 6px;
font-size: 12px;
font-weight: 600;
line-height: 1.3;
position: relative;
padding: 6px 10px;
padding: 3px 7px;
background: rgba(248, 255, 248, 0.5);
border-radius: 8px;
border-radius: 6px;
transition: all 0.2s;
border: 1px solid transparent;
min-height: 32px;
min-height: 0;
overflow: hidden;
}
@@ -271,13 +275,13 @@
background: rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
cursor: pointer;
padding: 6px 8px;
border-radius: 6px;
padding: 3px 6px;
border-radius: 5px;
transition: all 0.2s;
flex-shrink: 0;
color: #4caf50;
min-width: 32px;
height: 28px;
min-width: 28px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
@@ -301,10 +305,10 @@
.info-label {
color: #555;
font-weight: 600;
min-width: 52px;
font-weight: 700;
min-width: 46px;
flex-shrink: 0;
font-size: 12px;
font-size: 11px;
}
.info-value {
@@ -314,11 +318,11 @@
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
font-weight: 600;
}
.password-value {
font-family: 'Courier New', monospace;
font-weight: 600;
font-weight: 700;
color: #2e7d32;
}
@@ -330,6 +334,7 @@
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
font-weight: 600;
}
.info-link:hover {
@@ -344,17 +349,18 @@
font-size: 12px;
}
/* 卡片底部标签 */
/* 卡片底部标签:贴在卡片底部,内容少时留白在中间区域 */
.card-tags {
margin-top: 10px;
padding: 6px 10px;
flex-shrink: 0;
margin-top: 6px;
padding: 4px 7px;
border-top: 1px solid rgba(232, 245, 233, 0.6);
display: flex;
align-items: center;
gap: 6px;
gap: 4px;
flex-wrap: wrap;
background: linear-gradient(135deg, rgba(200, 230, 201, 0.3) 0%, rgba(165, 214, 167, 0.2) 100%);
border-radius: 8px;
border-radius: 6px;
}
.card-tag {
@@ -362,10 +368,10 @@
background: rgba(76, 175, 80, 0.12);
border: 1px solid rgba(76, 175, 80, 0.25);
color: #4caf50;
font-size: 11px;
font-weight: 600;
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 700;
padding: 1px 6px;
border-radius: 8px;
white-space: nowrap;
}
@@ -482,23 +488,23 @@
@media (max-width: 768px) {
.password-card {
padding: 14px;
border-radius: 16px;
padding: 10px 10px 8px;
border-radius: 12px;
}
.card-header {
gap: 10px;
margin-bottom: 10px;
padding-bottom: 10px;
gap: 7px;
margin-bottom: 7px;
padding-bottom: 7px;
}
.card-logo-wrapper {
width: 44px;
height: 44px;
width: 36px;
height: 36px;
}
.card-type {
font-size: 14px;
font-size: 13px;
}
.card-account-type {
@@ -507,25 +513,24 @@
}
.card-content {
gap: 6px;
gap: 3px;
}
.info-row {
font-size: 12px;
padding: 5px 8px;
gap: 6px;
min-height: 28px;
font-size: 11px;
padding: 3px 6px;
gap: 5px;
}
.info-label {
min-width: 48px;
font-size: 11px;
min-width: 42px;
font-size: 10px;
}
.copy-button {
padding: 5px 7px;
min-width: 28px;
height: 26px;
padding: 2px 5px;
min-width: 26px;
height: 22px;
}
.copy-button svg {
@@ -533,48 +538,59 @@
height: 14px;
}
.card-title-row {
gap: 4px;
align-items: flex-start;
}
.edit-button,
.delete-button {
padding: 6px;
padding: 2px;
width: 24px;
height: 24px;
box-sizing: border-box;
border-radius: 5px;
}
.edit-button svg,
.delete-button svg {
width: 16px;
height: 16px;
width: 13px;
height: 13px;
}
.card-actions {
gap: 6px;
flex-direction: column;
align-items: center;
gap: 3px;
flex-shrink: 0;
}
.card-tags {
margin-top: 8px;
padding-top: 8px;
padding: 5px 8px;
font-size: 10px;
margin-top: 5px;
padding: 3px 6px;
font-size: 9px;
}
}
@media (max-width: 480px) {
.password-card {
padding: 12px;
border-radius: 14px;
padding: 9px 9px 7px;
border-radius: 11px;
}
.card-header {
gap: 8px;
margin-bottom: 8px;
padding-bottom: 8px;
gap: 6px;
margin-bottom: 6px;
padding-bottom: 6px;
}
.card-logo-wrapper {
width: 40px;
height: 40px;
width: 34px;
height: 34px;
}
.card-type {
font-size: 13px;
font-size: 12px;
}
.card-account-type {
@@ -583,19 +599,18 @@
}
.card-content {
gap: 5px;
gap: 2px;
}
.info-row {
font-size: 11px;
padding: 4px 6px;
gap: 5px;
min-height: 26px;
font-size: 10px;
padding: 2px 5px;
gap: 4px;
}
.info-label {
min-width: 44px;
font-size: 10px;
min-width: 40px;
font-size: 9px;
}
.empty-state {
@@ -615,10 +630,32 @@
font-size: 13px;
}
.card-title-row {
gap: 3px;
align-items: flex-start;
}
.edit-button,
.delete-button {
width: 22px;
height: 22px;
padding: 1px;
border-radius: 4px;
}
.edit-button svg,
.delete-button svg {
width: 12px;
height: 12px;
}
.card-actions {
gap: 2px;
}
.card-tags {
margin-top: 6px;
padding-top: 6px;
padding: 4px 6px;
font-size: 9px;
margin-top: 4px;
padding: 2px 5px;
font-size: 8px;
}
}

View File

@@ -45,25 +45,32 @@ const EmptyIcon = () => (
</svg>
);
// 判断是否为手机端
const useIsMobile = () => {
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
// 与 PasswordList.css 网格列数保持一致,便于按「行数×列数」计算每页条数
const getGridColumnCount = (width) => {
if (width <= 768) return 2;
if (width <= 1199) return 3;
if (width <= 1599) return 4;
return 5;
};
const useGridColumnCount = () => {
const [columns, setColumns] = useState(() => getGridColumnCount(window.innerWidth));
useEffect(() => {
const handler = () => setIsMobile(window.innerWidth <= 768);
const handler = () => setColumns(getGridColumnCount(window.innerWidth));
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return isMobile;
return columns;
};
const PasswordList = ({ entries, onEdit, onDelete }) => {
const [copiedId, setCopiedId] = useState(null);
const [logoCache, setLogoCache] = useState({});
const [currentPage, setCurrentPage] = useState(1);
const isMobile = useIsMobile();
const gridColumns = useGridColumnCount();
// 每页条数:手机端 3行×2列=6电脑端 2行×5列=10
const pageSize = isMobile ? 6 : 10;
const ROWS_PER_PAGE = 4;
const pageSize = ROWS_PER_PAGE * gridColumns;
const totalPages = Math.ceil(entries.length / pageSize);
// entries 变化时重置到第一页
@@ -71,6 +78,14 @@ const PasswordList = ({ entries, onEdit, onDelete }) => {
setCurrentPage(1);
}, [entries]);
// 列数变化(窗口缩放)时避免当前页超出总页数
useEffect(() => {
setCurrentPage((p) => {
const tp = Math.max(1, Math.ceil(entries.length / pageSize) || 1);
return Math.min(p, tp);
});
}, [pageSize, entries.length]);
const pagedEntries = entries.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// 获取网站favicon
@@ -79,7 +94,7 @@ const PasswordList = ({ entries, onEdit, onDelete }) => {
try {
const urlObj = new URL(url);
const domain = urlObj.host;
return `https://cf-favicon.pages.dev/api/favicon?url=${domain}`;
return `https://favicon.smyhub.com/api/favicon?url=${encodeURIComponent(domain)}`;
} catch {
return null;
}

View File

@@ -4,18 +4,27 @@
box-sizing: border-box;
}
/* 全站:霞鹜文楷等宽 */
html {
font-family: 'LXGW WenKai Mono', ui-monospace, monospace;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
font-family: inherit;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
min-height: 100vh;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
button,
input,
textarea,
select {
font-family: inherit;
}
code {
font-family: inherit;
}