chore: 优化密码库前端界面与样式
This commit is contained in:
@@ -43,6 +43,10 @@
|
|||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
<title>萌芽密码管理器</title>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>您需要启用JavaScript才能运行此应用程序。</noscript>
|
<noscript>您需要启用JavaScript才能运行此应用程序。</noscript>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "萌芽密码管理器",
|
"name": "萌芽密码管理器",
|
||||||
"short_name": "萌芽密码",
|
"short_name": "萌芽密码管理器",
|
||||||
"description": "安全、便捷的个人密码管理工具",
|
"description": "萌芽密码管理器",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"orientation": "any",
|
"orientation": "any",
|
||||||
|
|||||||
@@ -5,10 +5,12 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#4caf50" />
|
<meta name="theme-color" content="#4caf50" />
|
||||||
<title>离线 - 萌芽密码管理器</title>
|
<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>
|
<style>
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
body {
|
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%);
|
background: linear-gradient(135deg, #f0fdf0 0%, #e8f5e9 100%);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -29,6 +31,7 @@
|
|||||||
h1 { font-size: 22px; color: #1b5e20; margin-bottom: 12px; font-weight: 700; }
|
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; }
|
p { font-size: 15px; color: #666; line-height: 1.6; margin-bottom: 24px; }
|
||||||
button {
|
button {
|
||||||
|
font-family: inherit;
|
||||||
background: linear-gradient(135deg, #66bb6a, #4caf50);
|
background: linear-gradient(135deg, #66bb6a, #4caf50);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -39,13 +39,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 25px 30px;
|
padding: 18px 22px;
|
||||||
border-bottom: 2px solid #e8f5e9;
|
border-bottom: 1px solid #e8f5e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-header h2 {
|
.form-header h2 {
|
||||||
color: #2e7d32;
|
color: #2e7d32;
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -72,35 +72,47 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.password-form {
|
.password-form {
|
||||||
padding: 30px;
|
padding: 20px 22px 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 20px;
|
column-gap: 20px;
|
||||||
margin-bottom: 20px;
|
row-gap: 0;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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 {
|
.form-group label {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input {
|
.form-group input {
|
||||||
padding: 12px;
|
padding: 10px 12px;
|
||||||
border: 2px solid #c8e6c9;
|
border: 2px solid #c8e6c9;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
background: white;
|
background: white;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input:focus {
|
.form-group input:focus {
|
||||||
@@ -149,12 +161,16 @@
|
|||||||
|
|
||||||
.password-input-group {
|
.password-input-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-input-group input {
|
.password-input-group input {
|
||||||
flex: 1;
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.generate-password-btn,
|
.generate-password-btn,
|
||||||
@@ -162,13 +178,17 @@
|
|||||||
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
|
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 10px 12px;
|
padding: 8px 10px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-shadow: 0 2px 5px rgba(76, 175, 80, 0.3);
|
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,
|
.generate-password-btn:hover,
|
||||||
@@ -183,11 +203,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.password-generator-options {
|
.password-generator-options {
|
||||||
margin-top: 12px;
|
margin-top: 8px;
|
||||||
padding: 15px;
|
padding: 12px;
|
||||||
background: rgba(200, 230, 201, 0.2);
|
background: rgba(200, 230, 201, 0.2);
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
border: 2px solid #c8e6c9;
|
border: 1px solid #c8e6c9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-row {
|
.option-row {
|
||||||
@@ -221,9 +241,9 @@
|
|||||||
|
|
||||||
.option-checkboxes {
|
.option-checkboxes {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 12px;
|
gap: 8px 10px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-label {
|
.checkbox-label {
|
||||||
@@ -291,10 +311,10 @@
|
|||||||
.form-actions {
|
.form-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 15px;
|
gap: 12px;
|
||||||
margin-top: 30px;
|
margin-top: 18px;
|
||||||
padding-top: 20px;
|
padding-top: 16px;
|
||||||
border-top: 2px solid #e8f5e9;
|
border-top: 1px solid #e8f5e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-button,
|
.cancel-button,
|
||||||
@@ -338,21 +358,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-header {
|
.form-header {
|
||||||
padding: 20px;
|
padding: 16px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-header h2 {
|
.form-header h2 {
|
||||||
font-size: 20px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-form {
|
.password-form {
|
||||||
padding: 20px;
|
padding: 16px 18px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 15px;
|
column-gap: 0;
|
||||||
margin-bottom: 15px;
|
row-gap: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--full {
|
||||||
|
grid-column: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
|
|||||||
website: '',
|
website: '',
|
||||||
officialName: '',
|
officialName: '',
|
||||||
tags: '',
|
tags: '',
|
||||||
logo: '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [passwordOptions, setPasswordOptions] = useState({
|
const [passwordOptions, setPasswordOptions] = useState({
|
||||||
@@ -35,7 +34,6 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
|
|||||||
website: entry.website || '',
|
website: entry.website || '',
|
||||||
officialName: entry.officialName || entry.software || '',
|
officialName: entry.officialName || entry.software || '',
|
||||||
tags: entry.tags || '',
|
tags: entry.tags || '',
|
||||||
logo: entry.logo || '',
|
|
||||||
});
|
});
|
||||||
setShowPasswordGenerator(false);
|
setShowPasswordGenerator(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -54,7 +52,10 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
|
|||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSave(formData);
|
onSave({
|
||||||
|
...formData,
|
||||||
|
logo: entry?.logo ?? '',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
@@ -133,6 +134,16 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>标签</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="tags"
|
||||||
|
value={formData.tags}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="标签(用空格或逗号分隔)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
@@ -143,7 +154,7 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
|
|||||||
name="account"
|
name="account"
|
||||||
value={formData.account}
|
value={formData.account}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="账号"
|
placeholder="登录账号"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@@ -270,7 +281,7 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
|
|||||||
name="username"
|
name="username"
|
||||||
value={formData.username}
|
value={formData.username}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="用户名"
|
placeholder="显示名 / 昵称"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@@ -308,29 +319,6 @@ const PasswordForm = ({ entry, onSave, onCancel }) => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="form-actions">
|
||||||
<button type="button" className="cancel-button" onClick={onCancel}>
|
<button type="button" className="cancel-button" onClick={onCancel}>
|
||||||
取消
|
取消
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
.password-list {
|
.password-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(5, 1fr);
|
||||||
gap: 20px;
|
gap: 14px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 大屏幕(1600px以上)- 5列 */
|
/* 大屏幕(1600px以上)- 5列 */
|
||||||
@@ -44,8 +44,8 @@
|
|||||||
|
|
||||||
.password-card {
|
.password-card {
|
||||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 255, 248, 0.95) 100%);
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 255, 248, 0.95) 100%);
|
||||||
border-radius: 20px;
|
border-radius: 14px;
|
||||||
padding: 18px;
|
padding: 12px 12px 10px;
|
||||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(76, 175, 80, 0.1);
|
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);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
@@ -54,6 +54,8 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-card::before {
|
.password-card::before {
|
||||||
@@ -81,22 +83,22 @@
|
|||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 8px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 8px;
|
||||||
border-bottom: 2px solid rgba(232, 245, 233, 0.6);
|
border-bottom: 1px solid rgba(232, 245, 233, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-logo-wrapper {
|
.card-logo-wrapper {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 50px;
|
width: 40px;
|
||||||
height: 50px;
|
height: 40px;
|
||||||
border-radius: 14px;
|
border-radius: 10px;
|
||||||
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 8px;
|
padding: 5px;
|
||||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.15);
|
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.15);
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
@@ -139,10 +141,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-type {
|
.card-type {
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1b5e20;
|
color: #1b5e20;
|
||||||
line-height: 1.3;
|
line-height: 1.25;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -196,17 +198,17 @@
|
|||||||
|
|
||||||
.card-actions {
|
.card-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-button,
|
.edit-button,
|
||||||
.delete-button {
|
.delete-button {
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: rgba(255, 255, 255, 0.8);
|
||||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
border: 1px solid rgba(76, 175, 80, 0.2);
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 8px;
|
padding: 5px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -236,24 +238,26 @@
|
|||||||
.card-content {
|
.card-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-row {
|
.info-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
line-height: 1.4;
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 6px 10px;
|
padding: 3px 7px;
|
||||||
background: rgba(248, 255, 248, 0.5);
|
background: rgba(248, 255, 248, 0.5);
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
min-height: 32px;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,13 +275,13 @@
|
|||||||
background: rgba(76, 175, 80, 0.1);
|
background: rgba(76, 175, 80, 0.1);
|
||||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
border: 1px solid rgba(76, 175, 80, 0.2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 6px 8px;
|
padding: 3px 6px;
|
||||||
border-radius: 6px;
|
border-radius: 5px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: #4caf50;
|
color: #4caf50;
|
||||||
min-width: 32px;
|
min-width: 28px;
|
||||||
height: 28px;
|
height: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -301,10 +305,10 @@
|
|||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
color: #555;
|
color: #555;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
min-width: 52px;
|
min-width: 46px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
@@ -314,11 +318,11 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-value {
|
.password-value {
|
||||||
font-family: 'Courier New', monospace;
|
font-weight: 700;
|
||||||
font-weight: 600;
|
|
||||||
color: #2e7d32;
|
color: #2e7d32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,6 +334,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-link:hover {
|
.info-link:hover {
|
||||||
@@ -344,17 +349,18 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 卡片底部标签 */
|
/* 卡片底部标签:贴在卡片底部,内容少时留白在中间区域 */
|
||||||
.card-tags {
|
.card-tags {
|
||||||
margin-top: 10px;
|
flex-shrink: 0;
|
||||||
padding: 6px 10px;
|
margin-top: 6px;
|
||||||
|
padding: 4px 7px;
|
||||||
border-top: 1px solid rgba(232, 245, 233, 0.6);
|
border-top: 1px solid rgba(232, 245, 233, 0.6);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
background: linear-gradient(135deg, rgba(200, 230, 201, 0.3) 0%, rgba(165, 214, 167, 0.2) 100%);
|
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 {
|
.card-tag {
|
||||||
@@ -362,10 +368,10 @@
|
|||||||
background: rgba(76, 175, 80, 0.12);
|
background: rgba(76, 175, 80, 0.12);
|
||||||
border: 1px solid rgba(76, 175, 80, 0.25);
|
border: 1px solid rgba(76, 175, 80, 0.25);
|
||||||
color: #4caf50;
|
color: #4caf50;
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
padding: 2px 8px;
|
padding: 1px 6px;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,23 +488,23 @@
|
|||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.password-card {
|
.password-card {
|
||||||
padding: 14px;
|
padding: 10px 10px 8px;
|
||||||
border-radius: 16px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
gap: 10px;
|
gap: 7px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 7px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-logo-wrapper {
|
.card-logo-wrapper {
|
||||||
width: 44px;
|
width: 36px;
|
||||||
height: 44px;
|
height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-type {
|
.card-type {
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-account-type {
|
.card-account-type {
|
||||||
@@ -507,25 +513,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-content {
|
||||||
gap: 6px;
|
gap: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-row {
|
.info-row {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
padding: 5px 8px;
|
padding: 3px 6px;
|
||||||
gap: 6px;
|
gap: 5px;
|
||||||
min-height: 28px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
min-width: 48px;
|
min-width: 42px;
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-button {
|
.copy-button {
|
||||||
padding: 5px 7px;
|
padding: 2px 5px;
|
||||||
min-width: 28px;
|
min-width: 26px;
|
||||||
height: 26px;
|
height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-button svg {
|
.copy-button svg {
|
||||||
@@ -533,48 +538,59 @@
|
|||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-title-row {
|
||||||
|
gap: 4px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-button,
|
.edit-button,
|
||||||
.delete-button {
|
.delete-button {
|
||||||
padding: 6px;
|
padding: 2px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-button svg,
|
.edit-button svg,
|
||||||
.delete-button svg {
|
.delete-button svg {
|
||||||
width: 16px;
|
width: 13px;
|
||||||
height: 16px;
|
height: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-actions {
|
.card-actions {
|
||||||
gap: 6px;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-tags {
|
.card-tags {
|
||||||
margin-top: 8px;
|
margin-top: 5px;
|
||||||
padding-top: 8px;
|
padding: 3px 6px;
|
||||||
padding: 5px 8px;
|
font-size: 9px;
|
||||||
font-size: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.password-card {
|
.password-card {
|
||||||
padding: 12px;
|
padding: 9px 9px 7px;
|
||||||
border-radius: 14px;
|
border-radius: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 6px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-logo-wrapper {
|
.card-logo-wrapper {
|
||||||
width: 40px;
|
width: 34px;
|
||||||
height: 40px;
|
height: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-type {
|
.card-type {
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-account-type {
|
.card-account-type {
|
||||||
@@ -583,19 +599,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-content {
|
||||||
gap: 5px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-row {
|
.info-row {
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
padding: 4px 6px;
|
padding: 2px 5px;
|
||||||
gap: 5px;
|
gap: 4px;
|
||||||
min-height: 26px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
min-width: 44px;
|
min-width: 40px;
|
||||||
font-size: 10px;
|
font-size: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
@@ -615,10 +630,32 @@
|
|||||||
font-size: 13px;
|
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 {
|
.card-tags {
|
||||||
margin-top: 6px;
|
margin-top: 4px;
|
||||||
padding-top: 6px;
|
padding: 2px 5px;
|
||||||
padding: 4px 6px;
|
font-size: 8px;
|
||||||
font-size: 9px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,25 +45,32 @@ const EmptyIcon = () => (
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 判断是否为手机端
|
// 与 PasswordList.css 网格列数保持一致,便于按「行数×列数」计算每页条数
|
||||||
const useIsMobile = () => {
|
const getGridColumnCount = (width) => {
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
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(() => {
|
useEffect(() => {
|
||||||
const handler = () => setIsMobile(window.innerWidth <= 768);
|
const handler = () => setColumns(getGridColumnCount(window.innerWidth));
|
||||||
window.addEventListener('resize', handler);
|
window.addEventListener('resize', handler);
|
||||||
return () => window.removeEventListener('resize', handler);
|
return () => window.removeEventListener('resize', handler);
|
||||||
}, []);
|
}, []);
|
||||||
return isMobile;
|
return columns;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PasswordList = ({ entries, onEdit, onDelete }) => {
|
const PasswordList = ({ entries, onEdit, onDelete }) => {
|
||||||
const [copiedId, setCopiedId] = useState(null);
|
const [copiedId, setCopiedId] = useState(null);
|
||||||
const [logoCache, setLogoCache] = useState({});
|
const [logoCache, setLogoCache] = useState({});
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const isMobile = useIsMobile();
|
const gridColumns = useGridColumnCount();
|
||||||
|
|
||||||
// 每页条数:手机端 3行×2列=6,电脑端 2行×5列=10
|
const ROWS_PER_PAGE = 4;
|
||||||
const pageSize = isMobile ? 6 : 10;
|
const pageSize = ROWS_PER_PAGE * gridColumns;
|
||||||
const totalPages = Math.ceil(entries.length / pageSize);
|
const totalPages = Math.ceil(entries.length / pageSize);
|
||||||
|
|
||||||
// entries 变化时重置到第一页
|
// entries 变化时重置到第一页
|
||||||
@@ -71,6 +78,14 @@ const PasswordList = ({ entries, onEdit, onDelete }) => {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}, [entries]);
|
}, [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);
|
const pagedEntries = entries.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
||||||
|
|
||||||
// 获取网站favicon
|
// 获取网站favicon
|
||||||
@@ -79,7 +94,7 @@ const PasswordList = ({ entries, onEdit, onDelete }) => {
|
|||||||
try {
|
try {
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
const domain = urlObj.host;
|
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 {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,27 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全站:霞鹜文楷等宽 */
|
||||||
|
html {
|
||||||
|
font-family: 'LXGW WenKai Mono', ui-monospace, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: inherit;
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
|
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc1 50%, #ffd3a5 100%);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
button,
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
input,
|
||||||
monospace;
|
textarea,
|
||||||
|
select {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user