feat: fix backend some error;fix frontend display error
This commit is contained in:
@@ -20,7 +20,7 @@ app.get('/api/messages', async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const [rows] = await db.execute(
|
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]
|
[recipient]
|
||||||
);
|
);
|
||||||
res.json(rows);
|
res.json(rows);
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
const { simpleParser } = require('mailparser');
|
const { simpleParser } = require('mailparser');
|
||||||
const db = require('./db');
|
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) {
|
async function saveEmail(stream) {
|
||||||
try {
|
try {
|
||||||
const parsed = await simpleParser(stream);
|
// First, buffer the entire stream
|
||||||
const rawEmail = stream.toString(); // Or handle stream to buffer conversion appropriately
|
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 recipient = parsed.to ? parsed.to.text : 'undisclosed-recipients';
|
||||||
const sender = parsed.from ? parsed.from.text : 'unknown-sender';
|
const sender = parsed.from ? parsed.from.text : 'unknown-sender';
|
||||||
|
|||||||
@@ -1,257 +1,158 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app-container">
|
<header class="app-header">
|
||||||
<h1>临时邮箱</h1>
|
<div class="logo">Email Unlimit</div>
|
||||||
<div class="email-generator">
|
<nav class="nav-links">
|
||||||
<input v-model="randomEmail" type="text" readonly @click="copyToClipboard" title="点击复制"/>
|
<a href="https://gitea.shenjianl.cn/shenjianZ/email-unlimit" target="_blank">Gitee</a>
|
||||||
<button @click="generateRandomEmail">生成新地址</button>
|
<a href="#" @click.prevent="showHowItWorks">How it Works</a>
|
||||||
</div>
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div class="inbox">
|
<main id="app-main">
|
||||||
<h2>收件箱 ({{ currentEmail }})</h2>
|
<section class="hero-section">
|
||||||
<button @click="fetchMessages" :disabled="loading">
|
<h1>您的专属临时邮箱,无限且私密</h1>
|
||||||
{{ loading ? '刷新中...' : '刷新邮件' }}
|
<p>输入任何<code>@shenjianl.cn</code>地址,立即在此查看收件箱。</p>
|
||||||
|
<form @submit.prevent="fetchMessages" class="input-group">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
v-model="recipient"
|
||||||
|
class="email-input"
|
||||||
|
placeholder="输入您的临时邮箱地址..."
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="submit" class="btn btn-primary">查看收件箱</button>
|
||||||
|
<button @click="generateRandomEmail" type="button" class="btn btn-secondary">随机生成</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="inbox-section">
|
||||||
|
<div class="inbox-container">
|
||||||
|
<div class="message-list">
|
||||||
|
<div class="message-list-header">
|
||||||
|
<h2>收件箱</h2>
|
||||||
|
<button @click="fetchMessages" class="refresh-btn" title="刷新">
|
||||||
|
↻
|
||||||
</button>
|
</button>
|
||||||
<div v-if="error" class="error">{{ error }}</div>
|
</div>
|
||||||
<ul v-if="messages.length > 0" class="message-list">
|
<div v-if="loading" class="loading-state">正在加载...</div>
|
||||||
<li v-for="msg in messages" :key="msg.id" @click="selectMessage(msg)" :class="{ selected: selectedMessage && selectedMessage.id === msg.id }">
|
<div v-else-if="messages.length === 0" class="empty-state">暂无邮件</div>
|
||||||
<div class="sender"><strong>发件人:</strong> {{ msg.sender }}</div>
|
<div v-else>
|
||||||
<div class="subject"><strong>主题:</strong> {{ msg.subject }}</div>
|
<div
|
||||||
<div class="time">{{ new Date(msg.received_at).toLocaleString() }}</div>
|
v-for="message in messages"
|
||||||
</li>
|
:key="message.id"
|
||||||
</ul>
|
class="message-item"
|
||||||
<div v-else class="no-messages">
|
:class="{ selected: selectedMessage && selectedMessage.id === message.id }"
|
||||||
<p>{{ loading ? '正在加载...' : '收件箱是空的。' }}</p>
|
@click="selectMessage(message)"
|
||||||
|
>
|
||||||
|
<div class="from">{{ message.sender }}</div>
|
||||||
|
<div class="subject">{{ message.subject }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="selectedMessage" class="message-view">
|
<div class="message-detail">
|
||||||
|
<div v-if="!selectedMessage" class="empty-state">
|
||||||
|
<p>请从左侧选择一封邮件查看</p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="message-content-header">
|
||||||
<h3>{{ selectedMessage.subject }}</h3>
|
<h3>{{ selectedMessage.subject }}</h3>
|
||||||
<p><strong>发件人:</strong> {{ selectedMessage.sender }}</p>
|
<p><strong>发件人:</strong> {{ selectedMessage.sender }}</p>
|
||||||
<p><strong>收件人:</strong> {{ currentEmail }}</p>
|
<p><strong>收件人:</strong> {{ selectedMessage.recipient }}</p>
|
||||||
<p><strong>时间:</strong> {{ new Date(selectedMessage.received_at).toLocaleString() }}</p>
|
</div>
|
||||||
<hr>
|
<div class="message-body">
|
||||||
<div v-html="selectedMessage.body" class="email-body"></div>
|
{{ selectedMessage.body }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- How it Works Modal -->
|
||||||
|
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<button @click="closeModal" class="close-btn">×</button>
|
||||||
|
<h3>工作原理</h3>
|
||||||
|
<ol>
|
||||||
|
<li>在上面的输入框中,随意编造一个以<code>@{{ domain }}</code>结尾的邮箱地址。</li>
|
||||||
|
<li>使用这个地址去注册任何网站或接收邮件。</li>
|
||||||
|
<li>在这里输入您刚刚使用的地址,点击“查看收件箱”,即可看到邮件。</li>
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import { ref, onMounted } from 'vue';
|
||||||
|
|
||||||
const API_URL = '/api';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
data() {
|
setup() {
|
||||||
return {
|
const recipient = ref('');
|
||||||
randomEmail: '',
|
const messages = ref([]);
|
||||||
currentEmail: '',
|
const selectedMessage = ref(null);
|
||||||
messages: [],
|
const loading = ref(false);
|
||||||
selectedMessage: null,
|
const showModal = ref(false);
|
||||||
loading: false,
|
// !!! 生产环境<E78EAF><E5A283>要提示 !!!
|
||||||
error: null,
|
// 请务必将下面的 'yourdomain.com' 替换为您的真实域名
|
||||||
domain: 'shenjianl.cn', // 从 info.md 获取
|
const domain = 'shenjianl.cn';
|
||||||
refreshInterval: null
|
|
||||||
};
|
const fetchMessages = async () => {
|
||||||
},
|
if (!recipient.value) {
|
||||||
methods: {
|
alert('请输入一个邮箱地址');
|
||||||
generateRandomString(length) {
|
|
||||||
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
let result = '';
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
generateRandomEmail() {
|
|
||||||
const name = this.generateRandomString(8);
|
|
||||||
this.randomEmail = `${name}@${this.domain}`;
|
|
||||||
this.currentEmail = this.randomEmail;
|
|
||||||
this.messages = [];
|
|
||||||
this.selectedMessage = null;
|
|
||||||
this.fetchMessages();
|
|
||||||
},
|
|
||||||
async fetchMessages() {
|
|
||||||
if (!this.currentEmail) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
this.error = null;
|
selectedMessage.value = null; // Clear selected message on new fetch
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_URL}/messages`, {
|
// API URL 已修改为相对路径,以适配 Nginx 反向代理
|
||||||
params: { recipient: this.currentEmail }
|
const response = await fetch(`/api/messages?recipient=${recipient.value}`);
|
||||||
});
|
if (!response.ok) {
|
||||||
this.messages = response.data;
|
throw new Error('Network response was not ok');
|
||||||
// 如果有新邮件,且当前选中了邮件,则更新选中的邮件内容
|
|
||||||
if (this.selectedMessage) {
|
|
||||||
const updatedSelected = this.messages.find(m => m.id === this.selectedMessage.id);
|
|
||||||
if (updatedSelected) {
|
|
||||||
this.selectedMessage = updatedSelected;
|
|
||||||
} else {
|
|
||||||
this.selectedMessage = null; // 如果邮件被删除,则取消选中
|
|
||||||
}
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
messages.value = data;
|
||||||
|
// Automatically select the first message if available
|
||||||
|
if (messages.value.length > 0) {
|
||||||
|
selectedMessage.value = messages.value[0];
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
this.error = '无法加载邮件。请检查后端服务是否运行。';
|
console.error('Failed to fetch messages:', error);
|
||||||
console.error(err);
|
alert('无法获取邮件,请检查后端服务和 Nginx 配置是否正常。');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
}
|
|
||||||
},
|
|
||||||
selectMessage(message) {
|
|
||||||
this.selectedMessage = message;
|
|
||||||
},
|
|
||||||
copyToClipboard() {
|
|
||||||
navigator.clipboard.writeText(this.randomEmail).then(() => {
|
|
||||||
alert('邮箱地址已复制到剪贴板');
|
|
||||||
}, (err) => {
|
|
||||||
console.error('Could not copy text: ', err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
startAutoRefresh() {
|
|
||||||
this.refreshInterval = setInterval(this.fetchMessages, 15000); // 每15秒刷新一次
|
|
||||||
},
|
|
||||||
stopAutoRefresh() {
|
|
||||||
if (this.refreshInterval) {
|
|
||||||
clearInterval(this.refreshInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.generateRandomEmail();
|
|
||||||
this.startAutoRefresh();
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.stopAutoRefresh();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectMessage = (message) => {
|
||||||
|
selectedMessage.value = message;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateRandomEmail = () => {
|
||||||
|
const randomPart = Math.random().toString(36).substring(2, 10);
|
||||||
|
recipient.value = `${randomPart}@${domain}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showHowItWorks = () => {
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipient,
|
||||||
|
messages,
|
||||||
|
selectedMessage,
|
||||||
|
loading,
|
||||||
|
showModal,
|
||||||
|
domain,
|
||||||
|
fetchMessages,
|
||||||
|
selectMessage,
|
||||||
|
generateRandomEmail,
|
||||||
|
showHowItWorks,
|
||||||
|
closeModal,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
background-color: #f4f7f9;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app-container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3 {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-generator {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-generator input {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 16px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
border: none;
|
|
||||||
background-color: #42b983;
|
|
||||||
color: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #36a374;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background-color: #ccc;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inbox {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 4px;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-list li {
|
|
||||||
padding: 15px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-list li:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-list li:hover {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-list li.selected {
|
|
||||||
background-color: #e8f5e9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sender, .subject, .time {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-messages {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-view {
|
|
||||||
border: 1px solid #eee;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #fdfdfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-body {
|
|
||||||
margin-top: 15px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 1em;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #e74c3c;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
313
frontend/src/assets/main.css
Normal file
313
frontend/src/assets/main.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import './assets/main.css' // 引入新的全局样式文件
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
createApp(App).mount('#app')
|
||||||
Reference in New Issue
Block a user