diff --git a/backend/app.js b/backend/app.js index 5d67785..5a2d392 100644 --- a/backend/app.js +++ b/backend/app.js @@ -20,7 +20,7 @@ app.get('/api/messages', async (req, res) => { try { const [rows] = await db.execute( - 'SELECT id, sender, subject, body, received_at FROM emails WHERE recipient = ? ORDER BY received_at DESC', + 'SELECT id, sender, recipient, subject, body, received_at FROM emails WHERE recipient = ? ORDER BY received_at DESC', [recipient] ); res.json(rows); diff --git a/backend/saveEmail.js b/backend/saveEmail.js index 15d376c..0d63f49 100644 --- a/backend/saveEmail.js +++ b/backend/saveEmail.js @@ -1,10 +1,24 @@ const { simpleParser } = require('mailparser'); const db = require('./db'); +// Helper function to convert stream to buffer +function streamToBuffer(stream) { + return new Promise((resolve, reject) => { + const chunks = []; + stream.on('data', (chunk) => chunks.push(chunk)); + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(chunks))); + }); +} + async function saveEmail(stream) { try { - const parsed = await simpleParser(stream); - const rawEmail = stream.toString(); // Or handle stream to buffer conversion appropriately + // First, buffer the entire stream + const emailBuffer = await streamToBuffer(stream); + + // Now, parse the buffered email content + const parsed = await simpleParser(emailBuffer); + const rawEmail = emailBuffer.toString(); const recipient = parsed.to ? parsed.to.text : 'undisclosed-recipients'; const sender = parsed.from ? parsed.from.text : 'unknown-sender'; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 25e45ff..177d231 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,257 +1,158 @@ - - + \ No newline at end of file diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css new file mode 100644 index 0000000..f286a77 --- /dev/null +++ b/frontend/src/assets/main.css @@ -0,0 +1,313 @@ +/* Global Styles & Resets */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap'); + +:root { + --primary-purple: #6d28d9; + --light-purple: #8b5cf6; + --dark-purple: #5b21b6; + --light-grey: #f3f4f6; + --medium-grey: #e5e7eb; + --dark-grey: #4b5563; + --text-light: #ffffff; + --text-dark: #1f2937; + --border-radius: 12px; + --card-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); +} + +body { + margin: 0; + font-family: 'Inter', sans-serif; + background-color: #ffffff; + color: var(--text-dark); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* App Container */ +#app { + display: flex; + flex-direction: column; + align-items: center; + padding: 0 2rem; + min-height: 100vh; +} + +/* Header */ +.app-header { + width: 100%; + max-width: 1400px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem 0; +} + +.logo { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-dark); +} + +.nav-links a { + margin-left: 2rem; + text-decoration: none; + color: var(--dark-grey); + font-weight: 500; + transition: color 0.3s ease; +} + +.nav-links a:hover { + color: var(--primary-purple); +} + +/* Hero Section */ +.hero-section { + width: 100%; + max-width: 1400px; + background-color: var(--primary-purple); + color: var(--text-light); + padding: 4rem 2rem; + border-radius: var(--border-radius); + text-align: center; + margin-bottom: 2rem; +} + +.hero-section h1 { + font-size: 2.5rem; + margin-top: 0; + margin-bottom: 1rem; +} + +.hero-section p { + font-size: 1.125rem; + opacity: 0.9; + margin-bottom: 2rem; +} + +.input-group { + display: flex; + justify-content: center; + gap: 0.5rem; +} + +.email-input { + width: 100%; + max-width: 400px; + padding: 0.75rem 1rem; + border: 1px solid transparent; + border-radius: 8px; + font-size: 1rem; +} + +.email-input:focus { + outline: none; + border-color: var(--light-purple); +} + +.btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.btn-primary { + background-color: var(--light-purple); + color: var(--text-light); +} + +.btn-primary:hover { + background-color: var(--dark-purple); +} + +.btn-secondary { + background-color: rgba(255, 255, 255, 0.2); + color: var(--text-light); +} + +.btn-secondary:hover { + background-color: rgba(255, 255, 255, 0.3); +} + +/* Inbox Section */ +.inbox-section { + width: 100%; + max-width: 1400px; + min-width: 1200px; + background-color: var(--light-grey); + border-radius: var(--border-radius); + padding: 1.5rem; + box-shadow: var(--card-shadow); +} + +.inbox-container { + display: flex; + gap: 1.5rem; + min-height: 400px; +} + +.message-list { + width: 35%; + border-right: 1px solid var(--medium-grey); + padding-right: 1.5rem; +} + +.message-list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.message-list-header h2 { + margin: 0; + font-size: 1.25rem; +} + +.refresh-btn { + background: none; + border: none; + cursor: pointer; + font-size: 1.5rem; + color: var(--dark-grey); +} + +.message-item { + padding: 1rem; + border-radius: 8px; + cursor: pointer; + border: 1px solid transparent; + border-left: 4px solid transparent; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.message-item:hover { + background-color: var(--medium-grey); +} + +.message-item.selected { + background-color: #ffffff; + border-left-color: var(--primary-purple); +} + +.message-item .from { + font-weight: 700; +} + +.message-item .subject { + color: var(--dark-grey); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.message-detail { + width: 65%; +} + +.empty-state, .loading-state { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + color: var(--dark-grey); +} + +.message-content-header { + margin-bottom: 1rem; +} + +.message-content-header h3 { + margin: 0 0 0.5rem 0; +} + +.message-content-header p { + margin: 0; + color: var(--dark-grey); +} + +.message-body { + background-color: #ffffff; + padding: 1rem; + border-radius: 8px; + white-space: pre-wrap; + word-wrap: break-word; + height: 300px; + overflow-y: auto; +} + +/* Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal-content { + background-color: #ffffff; + padding: 2rem; + border-radius: var(--border-radius); + width: 90%; + max-width: 500px; + box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + position: relative; +} + +.modal-content h3 { + margin-top: 0; + color: var(--primary-purple); +} + +.modal-content ol { + padding-left: 1.5rem; + line-height: 1.8; + font-size: 1.05rem; + color: var(--dark-grey); +} + +.modal-content code { + background-color: var(--light-purple); + color: var(--text-light); + padding: 0.3em 0.6em; + border-radius: 6px; + font-family: monospace; + font-weight: 500; +} + +.close-btn { + position: absolute; + top: 1rem; + right: 1rem; + background: none; + border: none; + font-size: 1.75rem; + cursor: pointer; + color: var(--dark-grey); +} + + +/* Responsive */ +@media (max-width: 768px) { + .inbox-container { + flex-direction: column; + } + .message-list, .message-detail { + width: 100%; + border-right: none; + padding-right: 0; + } + .input-group { + flex-direction: column; + } +} \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js index 01433bc..e4be44b 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,4 +1,5 @@ import { createApp } from 'vue' import App from './App.vue' +import './assets/main.css' // 引入新的全局样式文件 -createApp(App).mount('#app') +createApp(App).mount('#app') \ No newline at end of file