mirror of
https://github.com/schroinerxy/cloud-mail.git
synced 2026-06-21 19:35:50 +08:00
修改文档和版本号
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2025 LaziestRen
|
Copyright (c) 2025 eoao
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="doc/demo/logo.png" width="80px" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<h1>Cloud Mail</h1>
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
<h4>A responsive email service built with Vue 3 that supports email sending and can be deployed on Cloudflare. 🎉</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Project Showcase
|
||||||
|
|
||||||
|
[**👉 Online Demo**](https://skymail.ink)
|
||||||
|
|
||||||
|
[**👉 Beginner’s Guide – UI Deployment**](https://doc.skymail.ink)
|
||||||
|
|
||||||
|
|  |  |
|
||||||
|
|--------------------------|---------------------|
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **💻 Responsive Design**: Automatically adapts to both desktop and most mobile browsers.
|
||||||
|
|
||||||
|
- **📧 Email Sending**: Integrated with Resend for bulk email sending, embedded images, attachments, and status tracking.
|
||||||
|
|
||||||
|
- **🛡️ Admin Features**: Admins can manage users and emails, with RBAC permission control to limit access to features and resources.
|
||||||
|
|
||||||
|
- **🔀 Multiple Accounts**: Users can add multiple email accounts.
|
||||||
|
|
||||||
|
- **📦 Attachment Support**: Send and receive attachments, stored and downloaded via R2 object storage.
|
||||||
|
|
||||||
|
- **🔔 Email Push**: Forward received emails to Telegram bots or other email providers.
|
||||||
|
|
||||||
|
- **📈 Data Visualization**: Use Echarts to visualize system data, including user email growth.
|
||||||
|
|
||||||
|
- **⭐ Starred Emails**: Mark important emails for quick access.
|
||||||
|
|
||||||
|
- **🎨 Personalization**: Customize website title, login background, and transparency.
|
||||||
|
|
||||||
|
- **⚙️ Feature Settings**: Toggle on or off features like registration, email sending, and more, with the option to make the site private.
|
||||||
|
|
||||||
|
- **🤖 CAPTCHA**: Integrated with Turnstile CAPTCHA to prevent automated registration.
|
||||||
|
|
||||||
|
- **📜 More Features**: Under development...
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Framework**: [Vue3](https://vuejs.org/) + [Element Plus](https://element-plus.org/)
|
||||||
|
|
||||||
|
- **Web Framework**: [Hono](https://hono.dev/)
|
||||||
|
|
||||||
|
- **ORM**: [Drizzle](https://orm.drizzle.team/)
|
||||||
|
|
||||||
|
- **Platform**: [Cloudflare Workers](https://developers.cloudflare.com/workers/)
|
||||||
|
|
||||||
|
- **Email Service**: [Resend](https://resend.com/)
|
||||||
|
|
||||||
|
- **Caching**: [Cloudflare KV](https://developers.cloudflare.com/kv/)
|
||||||
|
|
||||||
|
- **Database**: [Cloudflare D1](https://developers.cloudflare.com/d1/)
|
||||||
|
|
||||||
|
- **File Storage**: [Cloudflare R2](https://developers.cloudflare.com/r2/)
|
||||||
|
|
||||||
|
## Setup Guide
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
Nodejs v18.20 +
|
||||||
|
|
||||||
|
Cloudflare account (with a bound domain)
|
||||||
|
|
||||||
|
**Clone the project to your local machine:**
|
||||||
|
``` shell
|
||||||
|
git clone https://github.com/eoao/cloud-mail
|
||||||
|
cd cloud-mail/mail-worker
|
||||||
|
```
|
||||||
|
|
||||||
|
**Install Dependencies:**
|
||||||
|
```shell
|
||||||
|
npm i
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configure the Project**
|
||||||
|
|
||||||
|
mail-worker/wrangler.toml
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[d1_databases]]
|
||||||
|
binding = "db" # Default binding name for D1 database, cannot be changed
|
||||||
|
database_name = "" # Database name
|
||||||
|
database_id = "" # Database ID
|
||||||
|
|
||||||
|
[[kv_namespaces]]
|
||||||
|
binding = "kv" # Default binding name for KV storage, cannot be changed
|
||||||
|
id = "" # KV namespace ID
|
||||||
|
|
||||||
|
|
||||||
|
[[r2_buckets]]
|
||||||
|
binding = "r2" # Default binding name for R2 storage, cannot be changed
|
||||||
|
bucket_name = "" # R2 bucket name
|
||||||
|
|
||||||
|
[assets]
|
||||||
|
binding = "assets" # Static asset binding name, cannot be changed
|
||||||
|
directory = "./dist" # Directory for frontend Vue project build, default: dist
|
||||||
|
|
||||||
|
[vars]
|
||||||
|
orm_log = false
|
||||||
|
domain = [] # Configure email domains, example: ["example1.com", "example2.com"]
|
||||||
|
admin = "" # Admin email, example: "admin@example.com"
|
||||||
|
jwt_secret = "" # JWT secret for login tokens, choose a random string
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deploy Remotely**
|
||||||
|
|
||||||
|
1. Create KV, D1 database, and R2 object storage in Cloudflare Console.
|
||||||
|
2. In the project directory `mail-worker/wrangler.toml`, configure the environment variables and database IDs/names.
|
||||||
|
3. Run the deployment command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
4. In Cloudflare → Account Home → Your Domain → Email → Email Routing → Route Rules → Catch-all Address, edit and route to the worker.
|
||||||
|
|
||||||
|
5. In your browser, visit `https://your-project-domain/api/init/your-jwt-secret` to initialize or update the D1 and KV databases.
|
||||||
|
|
||||||
|
6. After deployment, log in to the site with the admin account to configure R2 domains, Turnstile keys, and more.
|
||||||
|
|
||||||
|
|
||||||
|
**Run Locally**
|
||||||
|
|
||||||
|
1. Run locally. Databases and object storage will automatically be set up, no manual creation needed. Data is stored in the `mail-worker/.wrangler` folder.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
2. In your browser, visit `http://127.0.0.1:8787/api/init/your-jwt-secret` to initialize D1 and KV databases.
|
||||||
|
|
||||||
|
3. For local testing, you can set the R2 domain to `http://127.0.0.1:8787/api/file`.
|
||||||
|
|
||||||
|
**Email Sending**
|
||||||
|
|
||||||
|
1. Register on Resend, then click on “Domains” to add and verify your domain. Wait for verification.
|
||||||
|
|
||||||
|
2. Go to "API Keys" to create an API key, then copy the token and paste it in the project website settings.
|
||||||
|
|
||||||
|
3. Go to "Webhooks" and add a callback URL `https://your-project-domain/api/webhooks`.
|
||||||
|
Select the following events: ✅ (email.bounced, email.complained, email.delivered, email.delivery_delayed).
|
||||||
|
|
||||||
|
|
||||||
|
**Project Update**
|
||||||
|
|
||||||
|
After the update, run `https://your-project-domain/api/init/your-jwt-secret` to synchronize the database schema.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
**Buy me a cup of milk tea**
|
||||||
|
|
||||||
|
<a href="https://afdian.com/a/eoao_"><img width="150" src="https://pic1.afdiancdn.com/static/img/welcome/button-sponsorme.png" alt=""></a>
|
||||||
|
|
||||||
|
|
||||||
|
**Special Sponsors**
|
||||||
|
|
||||||
|
[DartNode](https://dartnode.com):Providing cloud computing service resource support
|
||||||
|
|
||||||
|
[](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT](LICENSE) license.
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
[Telegram](https://t.me/cloud_mail_tg)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="doc/demo/logo.png" width="10%" />
|
<img src="doc/demo/logo.png" width="80px" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<h4>使用Vue3开发的响应式简约邮箱服务,支持邮件发送附件收发,可以部署到Cloudflare云平台实现免费白嫖🎉</h4>
|
<h4>使用Vue3开发的响应式简约邮箱服务,支持邮件发送附件收发,可以部署到Cloudflare云平台实现免费白嫖🎉</h4>
|
||||||
</div>
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
<span>简体中文 | <a href="/README-en.md" style="margin-left: 5px">English </a></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
|
|
||||||
只需要一个域名,就可以创建多个不同的邮箱,类似各大邮箱平台 QQ邮箱,谷歌邮箱等,本项目使用Cloud flare部署,Rsend推送邮件,无需服务器费用,搭建属于自己的邮箱服务
|
只需要一个域名,就可以创建多个不同的邮箱,类似各大邮箱平台 QQ邮箱,谷歌邮箱等,本项目使用Cloud flare部署,Resend推送邮件,无需服务器费用,搭建属于自己的邮箱服务
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 项目展示
|
## 项目展示
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ Cloudflare 账号 (需要绑定域名)
|
|||||||
|
|
||||||
**克隆项目到本地**
|
**克隆项目到本地**
|
||||||
``` shell
|
``` shell
|
||||||
git clone https://github.com/LaziestRen/cloud-mail #拉取代码
|
git clone https://github.com/eoao/cloud-mail #拉取代码
|
||||||
cd cloud-mail/mail-worker #进入worker目录
|
cd cloud-mail/mail-worker #进入worker目录
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ directory = "./dist" #前端vue项目打包的静态资源存放位置,
|
|||||||
[vars]
|
[vars]
|
||||||
orm_log = false
|
orm_log = false
|
||||||
domain = [] #邮件域名可以配置多个示例: ["example1.com","example2.com"]
|
domain = [] #邮件域名可以配置多个示例: ["example1.com","example2.com"]
|
||||||
admin = "" #管理员的邮箱 示例: admin@example.com
|
admin = "" #管理员的邮箱 示例: "admin@example.com"
|
||||||
jwt_secret = "" #登录身份令牌的密钥,随便填一串字符串
|
jwt_secret = "" #登录身份令牌的密钥,随便填一串字符串
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -140,7 +140,7 @@ jwt_secret = "" #登录身份令牌的密钥,随便填一串字符串
|
|||||||
**远程部署**
|
**远程部署**
|
||||||
|
|
||||||
1. 在 Cloudflare 控制台创建KV,D1数据库,R2对象存储
|
1. 在 Cloudflare 控制台创建KV,D1数据库,R2对象存储
|
||||||
2. 在项目目录 mail-worker/wrangler.toml 配置文件中配置对应环境变量,以及创建的数据库id和名称
|
2. 在项目目录 `mail-worker/wrangler.toml` 配置文件中配置对应环境变量,以及创建的数据库id和名称
|
||||||
3. 执行远程部署命令
|
3. 执行远程部署命令
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -149,7 +149,7 @@ jwt_secret = "" #登录身份令牌的密钥,随便填一串字符串
|
|||||||
|
|
||||||
4. 在Cloudflare→账户主页→你的域名→电子邮件→电子邮件路由→路由规则→Catch-all地址,编辑发送到worker
|
4. 在Cloudflare→账户主页→你的域名→电子邮件→电子邮件路由→路由规则→Catch-all地址,编辑发送到worker
|
||||||
|
|
||||||
5. 浏览器输入 https://你的项目域名/api/init/你的jwt_secret 初始化或更新 d1和kv数据库
|
5. 浏览器输入 `https://你的项目域名/api/init/你的jwt_secret` 初始化或更新 d1和kv数据库
|
||||||
|
|
||||||
6. 部署完成登录网站,使用管理员账号可以在设置页面添加配置 R2域名 Turnstile密钥 等
|
6. 部署完成登录网站,使用管理员账号可以在设置页面添加配置 R2域名 Turnstile密钥 等
|
||||||
|
|
||||||
@@ -158,63 +158,40 @@ jwt_secret = "" #登录身份令牌的密钥,随便填一串字符串
|
|||||||
|
|
||||||
**本地运行**
|
**本地运行**
|
||||||
|
|
||||||
1. 本地运行,数据库,对象存储会自动安装,无需创建,数据库数据保存在 mail-worker/.wrangler文件夹
|
1. 本地运行,数据库,对象存储会自动安装,无需创建,数据库数据保存在 `mail-worker/.wrangler` 文件夹
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
2. 浏览器输入 http://127.0.0.1:8787/api/init/你的jwt_secret 初始化d1和kv数据库
|
2. 浏览器输入 `http://127.0.0.1:8787/api/init/你的jwt_secret` 初始化d1和kv数据库
|
||||||
|
|
||||||
3. 本地运行项目设置页面r2域名可设置为 http://127.0.0.1:8787/api/file
|
3. 本地运行项目设置页面r2域名可设置为 `http://127.0.0.1:8787/api/file`
|
||||||
|
|
||||||
**邮件发送**
|
**邮件发送**
|
||||||
|
|
||||||
1. 在 resend 官网注册后,点击左侧 Domains 添加并验证你的域名,等待验证完成
|
1. 在 resend 官网注册后,点击左侧 Domains 添加并验证你的域名,等待验证完成
|
||||||
2. 点击左侧 Api Keys 创建立api key, 复制token回到项目网站设置页面添加 resend token
|
2. 点击左侧 Api Keys 创建立api key, 复制token回到项目网站设置页面添加 resend token
|
||||||
|
|
||||||
3. 点击左侧 Webhooks 添加回调地址 https://你的项目域名/api/webhooks
|
3. 点击左侧 Webhooks 添加回调地址 `https://你的项目域名/api/webhooks`
|
||||||
|
|
||||||
勾选✅ (email.bounced email.complained email.delivered email.delivery_delayed)
|
勾选✅ (email.bounced email.complained email.delivered email.delivery_delayed)
|
||||||
|
|
||||||
## 目录结构
|
**项目更新**
|
||||||
|
|
||||||
```
|
更新后需要执行 `https://你的项目域名/api/init/你的jwt_secret` 来同步数据库结构
|
||||||
cloud-mail
|
|
||||||
├── mail-worker #worker后端项目
|
## 赞助
|
||||||
│ ├── src
|
|
||||||
│ │ ├── api #接口层
|
**请我喝一杯奶茶**
|
||||||
│ │ ├── const #常量
|
|
||||||
│ │ ├── email #邮件接收
|
<a href="https://afdian.com/a/eoao_"><img width="150" src="https://pic1.afdiancdn.com/static/img/welcome/button-sponsorme.png" alt=""></a>
|
||||||
│ │ ├── entity #数据库实体层
|
|
||||||
│ │ ├── error #自定义异常
|
|
||||||
│ │ ├── hono #web框架配置 拦截器等
|
|
||||||
│ │ ├── init #数据库缓存初始化
|
|
||||||
│ │ ├── model #响应体数据封装
|
|
||||||
│ │ ├── security #身份认证层
|
|
||||||
│ │ ├── service #服务层
|
|
||||||
│ │ ├── utils #工具类
|
|
||||||
│ │ └── index.js #入口文件
|
|
||||||
│ ├── pageckge.json #项目依赖
|
|
||||||
│ └── wrangler.toml #项目配置
|
|
||||||
└── mail-vue #vue前端项目
|
|
||||||
├── src
|
|
||||||
│ ├── assets #静态资源字体等
|
|
||||||
│ ├── axios #axios配置
|
|
||||||
│ ├── components #自定义组件
|
|
||||||
│ ├── layout #主体布局组件
|
|
||||||
│ ├── request #api接口
|
|
||||||
│ ├── router #路由配置
|
|
||||||
│ ├── store #全局状态管理
|
|
||||||
│ ├── utils #工具类
|
|
||||||
│ ├── views #页面组件
|
|
||||||
│ ├── app.vue #根组件
|
|
||||||
│ ├── main.js #入口js
|
|
||||||
│ └── style.css #全局css
|
|
||||||
├── package.json #项目依赖
|
|
||||||
└── env.dev #项目配置
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
**特别赞助商**
|
||||||
|
|
||||||
|
[DartNode](https://dartnode.com):提供云计算服务资源支持
|
||||||
|
|
||||||
|
[](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,8 @@ const en = {
|
|||||||
language: 'Language',
|
language: 'Language',
|
||||||
totalUserAccount: '{msg}',
|
totalUserAccount: '{msg}',
|
||||||
sendBanned: 'Banned',
|
sendBanned: 'Banned',
|
||||||
wrote: 'wrote'
|
wrote: 'wrote',
|
||||||
|
support: 'Support'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default en
|
export default en
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ const zh = {
|
|||||||
language: '网站语言',
|
language: '网站语言',
|
||||||
totalUserAccount: '{msg} 个',
|
totalUserAccount: '{msg} 个',
|
||||||
sendBanned: '已禁用',
|
sendBanned: '已禁用',
|
||||||
wrote: '来信'
|
wrote: '来信',
|
||||||
|
support: '赞助'
|
||||||
}
|
}
|
||||||
export default zh
|
export default zh
|
||||||
@@ -39,7 +39,7 @@ export function fromNow(date) {
|
|||||||
if (isToday) {
|
if (isToday) {
|
||||||
if (diffSeconds < 60) return `Just now`;
|
if (diffSeconds < 60) return `Just now`;
|
||||||
if (diffMinutes < 60) return `${diffMinutes} min ago`;
|
if (diffMinutes < 60) return `${diffMinutes} min ago`;
|
||||||
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
if (diffHours < 2) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
||||||
return d.format('HH:mm');
|
return d.format('HH:mm');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,20 +159,18 @@ function openHistory(regKey) {
|
|||||||
regKeyHistory(regKey.regKeyId).then(list => {
|
regKeyHistory(regKey.regKeyId).then(list => {
|
||||||
|
|
||||||
historyList.push(...list)
|
historyList.push(...list)
|
||||||
|
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
|
|
||||||
const email = list.reduce((a, b) =>
|
const email = list.reduce((a, b) =>
|
||||||
a.email.length > b.email.length ? a : b
|
compareByLengthAndUpperCase(a, b, 'email')
|
||||||
).email;
|
).email;
|
||||||
|
|
||||||
emailColumnWidth.value = getTextWidth(email) + 30
|
emailColumnWidth.value = getTextWidth(email) + 30
|
||||||
emailColumnWidth.value = emailColumnWidth.value < 300 ? emailColumnWidth.value : 300
|
emailColumnWidth.value = emailColumnWidth.value < 300 ? emailColumnWidth.value : 300
|
||||||
|
|
||||||
const createTime = list.reduce((a, b) =>
|
const createTime = list.reduce((a, b) =>
|
||||||
a.createTime.length > b.email.createTime ? a : b
|
compareByLengthAndUpperCase(a, b, 'createTime')
|
||||||
);
|
).createTime;
|
||||||
createTimeColumnWidth.value = getTextWidth(createTime) + 30
|
createTimeColumnWidth.value = getTextWidth(createTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
@@ -182,6 +180,14 @@ function openHistory(regKey) {
|
|||||||
showRegKeyHistory.value = true
|
showRegKeyHistory.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const compareByLengthAndUpperCase = (a, b, key) => {
|
||||||
|
const getUpperCaseCount = (str) => (str.match(/[A-Z]/g) || []).length;
|
||||||
|
if (a[key].length === b[key].length) {
|
||||||
|
return getUpperCaseCount(a[key]) > getUpperCaseCount(b[key]) ? a : b;
|
||||||
|
}
|
||||||
|
return a[key].length > b[key].length ? a : b;
|
||||||
|
};
|
||||||
|
|
||||||
function formatUserCreateTime(regKey) {
|
function formatUserCreateTime(regKey) {
|
||||||
const createTime = tzDayjs(regKey.createTime);
|
const createTime = tzDayjs(regKey.createTime);
|
||||||
const currentYear = dayjs().year();
|
const currentYear = dayjs().year();
|
||||||
@@ -197,7 +203,7 @@ function formatUserCreateTime(regKey) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (createYear === currentYear) {
|
if (expireYear === currentYear) {
|
||||||
return createTime.format('MMM D, HH:mm');
|
return createTime.format('MMM D, HH:mm');
|
||||||
} else {
|
} else {
|
||||||
return createTime.format('MMM D, YYYY HH:mm');
|
return createTime.format('MMM D, YYYY HH:mm');
|
||||||
|
|||||||
@@ -267,20 +267,29 @@
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="concerning-item">
|
<div class="concerning-item">
|
||||||
<span>{{$t('version')}} :</span>
|
<span>{{$t('version')}} :</span>
|
||||||
<span>v1.3.1</span>
|
<span>v1.5.0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="concerning-item">
|
<div class="concerning-item">
|
||||||
<span>{{$t('community')}} : </span>
|
<span>{{$t('community')}} : </span>
|
||||||
|
<el-button @click="jump('https://github.com/eoao/cloud-mail')">
|
||||||
|
Github
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="codicon:github-inverted" width="22" height="22" />
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
<el-button @click="jump('https://t.me/cloud_mail_tg')">
|
<el-button @click="jump('https://t.me/cloud_mail_tg')">
|
||||||
telegram
|
Telegram
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Icon icon="logos:telegram" width="30" height="30"/>
|
<Icon icon="logos:telegram" width="30" height="30"/>
|
||||||
</template>
|
</template>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="jump('https://github.com/eoao/cloud-mail')">
|
</div>
|
||||||
github
|
<div class="concerning-item">
|
||||||
|
<span>{{$t('support')}} : </span>
|
||||||
|
<el-button @click="jump('https://afdian.com/a/eoao_')" >
|
||||||
|
Afdian
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Icon icon="codicon:github-inverted" width="22" height="22" />
|
<Icon color="#8261DB" icon="simple-icons:afdian" width="24" height="24" />
|
||||||
</template>
|
</template>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,9 +514,9 @@ const forwardEmail = ref([])
|
|||||||
const forwardStatus = ref(0)
|
const forwardStatus = ref(0)
|
||||||
const emailColumnWidth = ref(0)
|
const emailColumnWidth = ref(0)
|
||||||
const tokenColumnWidth = ref(0)
|
const tokenColumnWidth = ref(0)
|
||||||
|
|
||||||
const ruleType = ref(0)
|
const ruleType = ref(0)
|
||||||
const ruleEmail = ref([])
|
const ruleEmail = ref([])
|
||||||
|
|
||||||
const resendList = computed(() => {
|
const resendList = computed(() => {
|
||||||
|
|
||||||
let list = Object.keys(setting.value.resendTokens).map(key => {
|
let list = Object.keys(setting.value.resendTokens).map(key => {
|
||||||
@@ -519,23 +528,27 @@ const resendList = computed(() => {
|
|||||||
|
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
|
|
||||||
const key = list.reduce((a, b) =>
|
const key = list.reduce((a, b) => compareByLengthAndUpperCase(a, b, 'key')).key;
|
||||||
a.key.length > b.key.length ? a : b
|
emailColumnWidth.value = getTextWidth(key) + 30;
|
||||||
).key;
|
|
||||||
|
|
||||||
emailColumnWidth.value = getTextWidth(key) + 30
|
const value = list.reduce((a, b) => compareByLengthAndUpperCase(a, b, 'value')).value;
|
||||||
|
tokenColumnWidth.value = getTextWidth(value) + 30;
|
||||||
const value = list.reduce((a, b) =>
|
|
||||||
a.value.length > b.value.length ? a : b
|
|
||||||
).value;
|
|
||||||
|
|
||||||
tokenColumnWidth.value = getTextWidth(value) + 30
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const compareByLengthAndUpperCase = (a, b, key) => {
|
||||||
|
const getUpperCaseCount = (str) => (str.match(/[A-Z]/g) || []).length;
|
||||||
|
if (a[key].length === b[key].length) {
|
||||||
|
return getUpperCaseCount(a[key]) > getUpperCaseCount(b[key]) ? a : b;
|
||||||
|
}
|
||||||
|
return a[key].length > b[key].length ? a : b;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
settingQuery().then(settingData => {
|
settingQuery().then(settingData => {
|
||||||
setting.value = settingData
|
setting.value = settingData
|
||||||
resendTokenForm.domain = setting.value.domainList[0]
|
resendTokenForm.domain = setting.value.domainList[0]
|
||||||
|
|||||||
+1
File diff suppressed because one or more lines are too long
+199
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
<title></title>
|
<title></title>
|
||||||
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
|
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
|
||||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||||
<script type="module" crossorigin src="/assets/index-Cjzn1ZdJ.js"></script>
|
<script type="module" crossorigin src="/assets/index-mj4MUxg3.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Dx0iKe9o.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-C5vjKzw7.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loading-first">
|
<div id="loading-first">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const en = {
|
|||||||
notEmail: 'Invalid email',
|
notEmail: 'Invalid email',
|
||||||
notExistDomain: 'Email domain does not exist',
|
notExistDomain: 'Email domain does not exist',
|
||||||
isDelAccount: 'This Email has been deleted',
|
isDelAccount: 'This Email has been deleted',
|
||||||
isRegAccount: 'This Email is already signed up',
|
isRegAccount: 'This Email is already registered',
|
||||||
accountLimit: 'Account limit reached',
|
accountLimit: 'Account limit reached',
|
||||||
delMyAccount: 'Cannot delete your own account',
|
delMyAccount: 'Cannot delete your own account',
|
||||||
noUserAccount: 'This email does not belong to the current user',
|
noUserAccount: 'This email does not belong to the current user',
|
||||||
@@ -50,9 +50,10 @@ const en = {
|
|||||||
starNotExistEmail: 'Starred email does not exist',
|
starNotExistEmail: 'Starred email does not exist',
|
||||||
emptyBotToken: 'Verification token cannot be empty',
|
emptyBotToken: 'Verification token cannot be empty',
|
||||||
botVerifyFail: 'Bot verification failed, please try again',
|
botVerifyFail: 'Bot verification failed, please try again',
|
||||||
authExpired: 'Authentication expired, please log in again',
|
authExpired: 'Authentication has expired. Please sign in again',
|
||||||
unauthorized: 'Unauthorized',
|
unauthorized: 'Unauthorized',
|
||||||
bannedSend: 'You are banned from sending emails',
|
bannedSend: 'You are banned from sending emails',
|
||||||
|
initSuccess: 'Successfully initialized',
|
||||||
perms: {
|
perms: {
|
||||||
"邮件": "Email",
|
"邮件": "Email",
|
||||||
"邮件发送": "Send email",
|
"邮件发送": "Send email",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const zh = {
|
|||||||
authExpired: '身份认证失效,请重新登录',
|
authExpired: '身份认证失效,请重新登录',
|
||||||
unauthorized: '权限不足',
|
unauthorized: '权限不足',
|
||||||
bannedSend: '你已被禁止发送邮件',
|
bannedSend: '你已被禁止发送邮件',
|
||||||
|
initSuccess: 'Successfully initialized',
|
||||||
perms: {
|
perms: {
|
||||||
"邮件": "邮件",
|
"邮件": "邮件",
|
||||||
"邮件发送": "邮件发送",
|
"邮件发送": "邮件发送",
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import settingService from '../service/setting-service';
|
import settingService from '../service/setting-service';
|
||||||
import emailUtils from '../utils/email-utils';
|
import emailUtils from '../utils/email-utils';
|
||||||
import {emailConst} from "../const/entity-const";
|
import {emailConst} from "../const/entity-const";
|
||||||
|
import { t } from '../i18n/i18n'
|
||||||
const init = {
|
const init = {
|
||||||
async init(c) {
|
async init(c) {
|
||||||
|
|
||||||
const secret = c.req.param('secret');
|
const secret = c.req.param('secret');
|
||||||
|
|
||||||
if (secret !== c.env.jwt_secret) {
|
if (secret !== c.env.jwt_secret) {
|
||||||
return c.text('secret不匹配');
|
return c.text(t('initSuccess'));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.intDB(c);
|
await this.intDB(c);
|
||||||
|
|||||||
Reference in New Issue
Block a user