mailu branch failed
This commit is contained in:
19
frontend/Dockerfile
Normal file
19
frontend/Dockerfile
Normal 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
16
frontend/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
17
frontend/public/index.html
Normal file
17
frontend/public/index.html
Normal 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
257
frontend/src/App.vue
Normal 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
4
frontend/src/main.js
Normal 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
6
frontend/vue.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true
|
||||
// devServer 代理配置已移除,所有代理均由外部 Nginx 处理。
|
||||
})
|
||||
Reference in New Issue
Block a user