feat: enhance import/export page with new layout and features

This commit is contained in:
shuaiplus
2026-03-04 23:07:03 +08:00
parent c99a558b5e
commit 35dc239c25
3 changed files with 135 additions and 4 deletions
+40 -4
View File
@@ -2,7 +2,7 @@
import { argon2idAsync } from '@noble/hashes/argon2.js'; import { argon2idAsync } from '@noble/hashes/argon2.js';
import { strFromU8, unzipSync } from 'fflate'; import { strFromU8, unzipSync } from 'fflate';
import { BlobReader, Uint8ArrayWriter, ZipReader, configure as configureZipJs } from '@zip.js/zip.js'; import { BlobReader, Uint8ArrayWriter, ZipReader, configure as configureZipJs } from '@zip.js/zip.js';
import { Download, FileUp } from 'lucide-preact'; import { Archive, ArrowLeftRight, Download, FileJson, FileUp } from 'lucide-preact';
import ConfirmDialog from '@/components/ConfirmDialog'; import ConfirmDialog from '@/components/ConfirmDialog';
import type { CiphersImportPayload } from '@/lib/api'; import type { CiphersImportPayload } from '@/lib/api';
import { import {
@@ -581,8 +581,43 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
} }
return ( return (
<div className="stack"> <div className="import-export-page">
<section className="card"> <section className="card import-export-hero">
<h3>{t('txt_import_export_title')}</h3>
<p className="import-export-hero-sub">{t('txt_import_export_feature_intro')}</p>
<div className="import-export-feature-grid">
<article className="import-export-feature-item">
<span className="import-export-feature-icon">
<Archive size={16} />
</span>
<div>
<strong>{t('txt_import_export_feature_bw_zip_title')}</strong>
<p>{t('txt_import_export_feature_bw_zip_desc')}</p>
</div>
</article>
<article className="import-export-feature-item">
<span className="import-export-feature-icon">
<FileJson size={16} />
</span>
<div>
<strong>{t('txt_import_export_feature_nodewarden_json_title')}</strong>
<p>{t('txt_import_export_feature_nodewarden_json_desc')}</p>
</div>
</article>
<article className="import-export-feature-item">
<span className="import-export-feature-icon">
<ArrowLeftRight size={16} />
</span>
<div>
<strong>{t('txt_import_export_feature_compat_title')}</strong>
<p>{t('txt_import_export_feature_compat_desc')}</p>
</div>
</article>
</div>
</section>
<div className="import-export-panels">
<section className="card import-export-panel">
<h3>{t('txt_import')}</h3> <h3>{t('txt_import')}</h3>
<p className="muted" style={{ textAlign: 'left', marginBottom: 12 }}> <p className="muted" style={{ textAlign: 'left', marginBottom: 12 }}>
{t('txt_import_vault_data_hint')} {t('txt_import_vault_data_hint')}
@@ -665,7 +700,7 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
</div> </div>
</section> </section>
<section className="card"> <section className="card import-export-panel">
<h3>{t('txt_export')}</h3> <h3>{t('txt_export')}</h3>
<p className="muted" style={{ textAlign: 'left', marginBottom: 12 }}> <p className="muted" style={{ textAlign: 'left', marginBottom: 12 }}>
{t('txt_export_vault_data_hint')} {t('txt_export_vault_data_hint')}
@@ -735,6 +770,7 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
</button> </button>
</div> </div>
</section> </section>
</div>
<ConfirmDialog <ConfirmDialog
open={exportAuthDialogOpen} open={exportAuthDialogOpen}
+17
View File
@@ -752,6 +752,14 @@ messages.en.txt_target_folder = 'Target folder';
messages.en.txt_select_folder_placeholder = '-- Select folder --'; messages.en.txt_select_folder_placeholder = '-- Select folder --';
messages.en.txt_import_vault_data_hint = 'Import vault data into your current account.'; messages.en.txt_import_vault_data_hint = 'Import vault data into your current account.';
messages.en.txt_export_vault_data_hint = 'Export vault data from your current account.'; messages.en.txt_export_vault_data_hint = 'Export vault data from your current account.';
messages.en.txt_import_export_title = 'Import & Export';
messages.en.txt_import_export_feature_intro = 'Move your vault across clients with full compatibility, including attachments and encrypted formats.';
messages.en.txt_import_export_feature_bw_zip_title = 'Bitwarden vault + attachments ZIP';
messages.en.txt_import_export_feature_bw_zip_desc = 'Supports importing and exporting Bitwarden ZIP archives that include vault data and attachments.';
messages.en.txt_import_export_feature_nodewarden_json_title = 'NodeWarden vault + attachments JSON';
messages.en.txt_import_export_feature_nodewarden_json_desc = 'Supports NodeWarden JSON import/export with vault data and attachments bundled in one file.';
messages.en.txt_import_export_feature_compat_title = 'Cross-client compatibility';
messages.en.txt_import_export_feature_compat_desc = 'Supports Bitwarden JSON/CSV import and mainstream migration formats for a smooth move-in.';
messages.en.txt_encrypted_mode = 'Encrypted mode'; messages.en.txt_encrypted_mode = 'Encrypted mode';
messages.en.txt_account_verification = 'Account verification'; messages.en.txt_account_verification = 'Account verification';
messages.en.txt_password_verification = 'Password verification'; messages.en.txt_password_verification = 'Password verification';
@@ -818,6 +826,15 @@ zhCNOverrides.txt_import_encrypted_file_message = '该 Bitwarden 导出文件已
zhCNOverrides.txt_import_encrypted_zip_title = '导入加密 ZIP'; zhCNOverrides.txt_import_encrypted_zip_title = '导入加密 ZIP';
zhCNOverrides.txt_import_encrypted_zip_message = '该 ZIP 压缩包已加密,请输入 ZIP 密码继续。'; zhCNOverrides.txt_import_encrypted_zip_message = '该 ZIP 压缩包已加密,请输入 ZIP 密码继续。';
zhCNOverrides.txt_import_export_title = '导入导出';
zhCNOverrides.txt_import_export_feature_intro = '兼容主流客户端的数据迁移流程,支持附件与加密格式,导入导出一步完成。';
zhCNOverrides.txt_import_export_feature_bw_zip_title = 'Bitwarden 密码库 + 附件 ZIP';
zhCNOverrides.txt_import_export_feature_bw_zip_desc = '支持导入和导出包含密码库与附件的 Bitwarden ZIP 压缩包。';
zhCNOverrides.txt_import_export_feature_nodewarden_json_title = 'NodeWarden 密码库 + 附件 JSON';
zhCNOverrides.txt_import_export_feature_nodewarden_json_desc = '支持 NodeWarden 自研 JSON 导入导出,一个文件同时包含密码库和附件。';
zhCNOverrides.txt_import_export_feature_compat_title = '跨客户端兼容';
zhCNOverrides.txt_import_export_feature_compat_desc = '支持 Bitwarden JSON/CSV 导入及主流迁移格式,降低迁移门槛。';
messages['zh-CN'] = { ...messages.en, ...zhCNOverrides }; messages['zh-CN'] = { ...messages.en, ...zhCNOverrides };
function resolveInitialLocale(): Locale { function resolveInitialLocale(): Locale {
+78
View File
@@ -1032,6 +1032,79 @@ input[type='file'].input::file-selector-button:hover {
gap: 12px; gap: 12px;
} }
.import-export-page {
display: grid;
gap: 12px;
}
.import-export-hero {
margin-bottom: 0;
}
.import-export-hero h3 {
margin: 0 0 8px 0;
}
.import-export-hero-sub {
margin: 0;
color: #5f6f85;
line-height: 1.5;
}
.import-export-feature-grid {
margin-top: 12px;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.import-export-feature-item {
border: 1px solid #d9e4f2;
border-radius: 10px;
background: #f7faff;
padding: 10px;
display: flex;
align-items: flex-start;
gap: 10px;
min-width: 0;
}
.import-export-feature-icon {
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid #cbdcf7;
background: #e9f1ff;
color: #1d4ed8;
display: inline-grid;
place-items: center;
flex-shrink: 0;
}
.import-export-feature-item strong {
display: block;
font-size: 14px;
line-height: 1.35;
}
.import-export-feature-item p {
margin: 4px 0 0 0;
color: #64748b;
font-size: 13px;
line-height: 1.45;
}
.import-export-panels {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
align-items: start;
}
.import-export-panel h3 {
margin: 0 0 6px 0;
}
.field-grid { .field-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1510,6 +1583,11 @@ input[type='file'].input::file-selector-button:hover {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.import-export-feature-grid,
.import-export-panels {
grid-template-columns: 1fr;
}
.uri-row { .uri-row {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }