mailu branch failed

This commit is contained in:
2025-07-28 12:18:54 +08:00
commit 2999e562c7
19 changed files with 1178 additions and 0 deletions

19
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy all other files
COPY . .
# Expose port
EXPOSE 8080
# Run the app
CMD ["npm", "run", "serve"]

16
frontend/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"vue": "^3.5.18",
"axios": "^1.11.0"
},
"devDependencies": {
"@vue/cli-service": "~5.0.8"
}
}

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>临时邮件</title>
</head>
<body>
<noscript>
<strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

257
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,257 @@
<template>
<div id="app-container">
<h1>临时邮箱</h1>
<div class="email-generator">
<input v-model="randomEmail" type="text" readonly @click="copyToClipboard" title="点击复制"/>
<button @click="generateRandomEmail">生成新地址</button>
</div>
<div class="inbox">
<h2>收件箱 ({{ currentEmail }})</h2>
<button @click="fetchMessages" :disabled="loading">
{{ loading ? '刷新中...' : '刷新邮件' }}
</button>
<div v-if="error" class="error">{{ error }}</div>
<ul v-if="messages.length > 0" class="message-list">
<li v-for="msg in messages" :key="msg.id" @click="selectMessage(msg)" :class="{ selected: selectedMessage && selectedMessage.id === msg.id }">
<div class="sender"><strong>发件人:</strong> {{ msg.sender }}</div>
<div class="subject"><strong>主题:</strong> {{ msg.subject }}</div>
<div class="time">{{ new Date(msg.received_at).toLocaleString() }}</div>
</li>
</ul>
<div v-else class="no-messages">
<p>{{ loading ? '正在加载...' : '收件箱是空的。' }}</p>
</div>
</div>
<div v-if="selectedMessage" class="message-view">
<h3>{{ selectedMessage.subject }}</h3>
<p><strong>发件人:</strong> {{ selectedMessage.sender }}</p>
<p><strong>收件人:</strong> {{ currentEmail }}</p>
<p><strong>时间:</strong> {{ new Date(selectedMessage.received_at).toLocaleString() }}</p>
<hr>
<div v-html="selectedMessage.body" class="email-body"></div>
</div>
</div>
</template>
<script>
import axios from 'axios';
const API_URL = '/api';
export default {
name: 'App',
data() {
return {
randomEmail: '',
currentEmail: '',
messages: [],
selectedMessage: null,
loading: false,
error: null,
domain: 'shenjianl.cn', // 从 info.md 获取
refreshInterval: null
};
},
methods: {
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;
}
this.loading = true;
this.error = null;
try {
const response = await axios.get(`${API_URL}/messages`, {
params: { recipient: this.currentEmail }
});
this.messages = response.data;
// 如果有新邮件,且当前选中了邮件,则更新选中的邮件内容
if (this.selectedMessage) {
const updatedSelected = this.messages.find(m => m.id === this.selectedMessage.id);
if (updatedSelected) {
this.selectedMessage = updatedSelected;
} else {
this.selectedMessage = null; // 如果邮件被删除,则取消选中
}
}
} catch (err) {
this.error = '无法加载邮件。请检查后端服务是否运行。';
console.error(err);
} finally {
this.loading = 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();
}
};
</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>

4
frontend/src/main.js Normal file
View File

@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

6
frontend/vue.config.js Normal file
View File

@@ -0,0 +1,6 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
// devServer 代理配置已移除,所有代理均由外部 Nginx 处理。
})