新增支持s3协议对象存储
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="theme-color" content="#D3E3FD" id="theme-color-meta">
|
||||
<title></title>
|
||||
<link rel="icon" href="./src/assets/favicon.svg" type="image/svg+xml">
|
||||
<link rel="icon" href="./src/assets/favicon.png" type="image/png">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||
<script>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 7.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 14 KiB |
BIN
mail-vue/src/assets/favicon.png
Normal file
BIN
mail-vue/src/assets/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><g fill="none"><path fill="#367af2" d="M4 15.42v18.33A6.25 6.25 0 0 0 10.25 40h27.5A6.25 6.25 0 0 0 44 33.75V15.5L24.582 25.724a1.25 1.25 0 0 1-1.168-.002z"/><path fill="url(#SVG9uDrcZUx)" d="M4 15.42v18.33A6.25 6.25 0 0 0 10.25 40h27.5A6.25 6.25 0 0 0 44 33.75V15.5L24.582 25.724a1.25 1.25 0 0 1-1.168-.002z"/><path fill="url(#SVGkPPLtdEu)" d="M4 15.42v18.33A6.25 6.25 0 0 0 10.25 40h27.5A6.25 6.25 0 0 0 44 33.75V15.5L24.582 25.724a1.25 1.25 0 0 1-1.168-.002z"/><path fill="url(#SVG1EOghF8v)" fill-opacity="0.75" d="M4 15.42v18.33A6.25 6.25 0 0 0 10.25 40h27.5A6.25 6.25 0 0 0 44 33.75V15.5L24.582 25.724a1.25 1.25 0 0 1-1.168-.002z"/><path fill="url(#SVG72PJueUR)" fill-opacity="0.7" d="M4 15.42v18.33A6.25 6.25 0 0 0 10.25 40h27.5A6.25 6.25 0 0 0 44 33.75V15.5L24.582 25.724a1.25 1.25 0 0 1-1.168-.002z"/><path fill="url(#SVGCdktzmgW)" d="M4.02 13.747A6.25 6.25 0 0 1 10.25 8h27.5A6.25 6.25 0 0 1 44 14.25v1.38L24.582 25.854a1.25 1.25 0 0 1-1.168-.002L4 15.551V14.25q0-.254.02-.503"/><defs><linearGradient id="SVG9uDrcZUx" x1="31" x2="39.662" y1="19.5" y2="40.944" gradientUnits="userSpaceOnUse"><stop offset=".199" stop-color="#0094f0" stop-opacity="0"/><stop offset=".431" stop-color="#0094f0"/></linearGradient><linearGradient id="SVGkPPLtdEu" x1="18.286" x2="7.955" y1="18.008" y2="41.831" gradientUnits="userSpaceOnUse"><stop offset=".191" stop-color="#0094f0" stop-opacity="0"/><stop offset=".431" stop-color="#0094f0"/></linearGradient><linearGradient id="SVG1EOghF8v" x1="34.547" x2="36.228" y1="30.084" y2="42.272" gradientUnits="userSpaceOnUse"><stop stop-color="#2764e7" stop-opacity="0"/><stop offset="1" stop-color="#2764e7"/></linearGradient><linearGradient id="SVG72PJueUR" x1="30.191" x2="33.258" y1="18.439" y2="43.244" gradientUnits="userSpaceOnUse"><stop offset=".533" stop-color="#0094f0" stop-opacity="0"/><stop offset="1" stop-color="#0094f0"/></linearGradient><linearGradient id="SVGCdktzmgW" x1="15.883" x2="28.057" y1="2.275" y2="34.261" gradientUnits="userSpaceOnUse"><stop stop-color="#6ce0ff"/><stop offset=".462" stop-color="#29c3ff"/><stop offset="1" stop-color="#4894fe"/></linearGradient></defs></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -55,7 +55,9 @@ http.interceptors.response.use((res) => {
|
||||
})
|
||||
reject(data)
|
||||
}
|
||||
resolve(data.data)
|
||||
setTimeout(() => {
|
||||
resolve(data.data)
|
||||
},300)
|
||||
})
|
||||
},
|
||||
(error) => {
|
||||
|
||||
@@ -136,8 +136,6 @@ const en = {
|
||||
loginDomain: 'Sign-In Box Domain',
|
||||
multipleEmail: 'Multiple Accounts',
|
||||
multipleEmailDesc: 'Enable this feature to allow users to add multiple accounts.',
|
||||
physicallyWipeData: 'Physically Wipe Data',
|
||||
physicallyWipeDataDesc: 'This action will permanently erase all deleted data.',
|
||||
customization: 'Customization',
|
||||
websiteTitle: 'Title',
|
||||
loginBoxOpacity: 'Login Box Opacity',
|
||||
@@ -148,7 +146,7 @@ const en = {
|
||||
autoRefreshDesc: 'Automatically fetch the latest emails from the server.',
|
||||
sendEmail: 'Send Email',
|
||||
resendToken: 'Resend Token',
|
||||
R2OS: 'R2 Object Storage',
|
||||
oss: 'Object Storage',
|
||||
osDomain: 'Domain',
|
||||
emailPush: 'Email Push',
|
||||
tgBot: 'Telegram Bot',
|
||||
@@ -290,7 +288,8 @@ const en = {
|
||||
to: 'To',
|
||||
clear: 'Clear',
|
||||
include: 'Include',
|
||||
delAllEmailConfirm: 'Do you really want to delete it?'
|
||||
delAllEmailConfirm: 'Do you really want to delete it?',
|
||||
s3Configuration: 'S3 Configuration'
|
||||
}
|
||||
|
||||
export default en
|
||||
|
||||
@@ -136,8 +136,6 @@ const zh = {
|
||||
loginDomain: '登录框域名',
|
||||
multipleEmail: '多号模式',
|
||||
multipleEmailDesc: '开启后账号栏出现一个用户可以添加多个邮箱',
|
||||
physicallyWipeData: '物理清空数据',
|
||||
physicallyWipeDataDesc: '该操作会物理清空所有已被删除的数据',
|
||||
customization: '个性化设置',
|
||||
websiteTitle: '网站标题',
|
||||
loginBoxOpacity: '登录透明',
|
||||
@@ -147,8 +145,8 @@ const zh = {
|
||||
autoRefresh: '自动刷新',
|
||||
autoRefreshDesc: '轮询请求服务器获取最新邮件',
|
||||
sendEmail: '邮件发送',
|
||||
resendToken: '添加 Resend Token',
|
||||
R2OS: 'R2 对象存储',
|
||||
resendToken: 'Resend Token',
|
||||
oss: '对象存储',
|
||||
osDomain: '访问域名',
|
||||
emailPush: '邮件推送',
|
||||
tgBot: 'Telegram 机器人',
|
||||
@@ -211,7 +209,7 @@ const zh = {
|
||||
addRoleTitle: '添加身份',
|
||||
emptyUserNameMsg: '用户名不能为空',
|
||||
delAccountConfirm: '确认删除当前账号及所有数据吗?',
|
||||
clearAllDelConfirm: '此操作不可逆转, 输入 <b style="font-weight: bold">确认删除</b> 继续操作',
|
||||
clearAllDelConfirm: '此操作不可逆转, 输入 <b style="font-weight: bold;">确认删除</b> 继续操作',
|
||||
warning: '警告',
|
||||
delInputPattern: '确认删除',
|
||||
inputErrorMessage: '请输入确认删除',
|
||||
@@ -290,7 +288,8 @@ const zh = {
|
||||
to: '至',
|
||||
clear: '清除',
|
||||
include: '包含',
|
||||
delAllEmailConfirm: '确定要删除吗?'
|
||||
delAllEmailConfirm: '确定要删除吗?',
|
||||
s3Configuration: 'S3 配置'
|
||||
|
||||
}
|
||||
export default zh
|
||||
|
||||
@@ -61,20 +61,7 @@
|
||||
v-model="setting.manyEmail"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div>
|
||||
<span>{{ $t('physicallyWipeData') }}</span>
|
||||
<el-tooltip effect="dark" :content="$t('physicallyWipeDataDesc')">
|
||||
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<el-button class="opt-button" style="margin-top: 0" @click="physicsDeleteAllData" size="small"
|
||||
type="primary">
|
||||
<Icon icon="material-symbols:delete-outline-rounded" width="16" height="16"/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -196,9 +183,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- R2 Object Storage Card -->
|
||||
<!-- Object Storage Card -->
|
||||
<div class="settings-card">
|
||||
<div class="card-title">{{ $t('R2OS') }}</div>
|
||||
<div class="card-title">{{ $t('oss') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="setting-item">
|
||||
<div><span>{{ $t('osDomain') }}</span></div>
|
||||
@@ -210,10 +197,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div><span>{{ $t('S3配置') }}</span></div>
|
||||
<div><span>{{ $t('s3Configuration') }}</span></div>
|
||||
<div class="r2domain">
|
||||
<el-button class="opt-button" size="small" type="primary" @click="ossConfigShow = true">
|
||||
<Icon icon="fluent:settings-48-regular" width="18" height="18"/>
|
||||
<el-button @mouseenter="s3IsDisabled = true"
|
||||
@mouseleave="s3IsDisabled = false"
|
||||
:disabled="setting.hasR2 && s3IsDisabled" class="opt-button" size="small" type="primary" @click="addS3Show = true">
|
||||
<Icon icon="fluent:settings-48-regular" width="16" height="16"/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -642,6 +631,20 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="addS3Show" :title="t('s3Configuration')" width="340" @closed="resetAddS3Form">
|
||||
<form>
|
||||
<el-input class="dialog-input" type="text" placeholder="Bucket" v-model="s3.bucket"/>
|
||||
<el-input class="dialog-input" type="text" placeholder="Endpoint" v-model="s3.endpoint"/>
|
||||
<el-input class="dialog-input" type="text" placeholder="Region" v-model="s3.region"/>
|
||||
<el-input class="dialog-input" type="text" :placeholder="setting.s3AccessKey || 'Access Key'"
|
||||
v-model="s3.s3AccessKey"/>
|
||||
<el-input type="text" :placeholder="setting.s3SecretKey || 'Secret Key'" v-model="s3.s3SecretKey"/>
|
||||
<div class="s3-button">
|
||||
<el-button :loading="clearS3Loading" @click="clearS3">{{ t('clear') }}</el-button>
|
||||
<el-button type="primary" :loading="settingLoading && !clearS3Loading" @click="saveS3">{{ t('save') }}</el-button>
|
||||
</div>
|
||||
</form>
|
||||
</el-dialog>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
@@ -680,7 +683,6 @@ const userStore = useUserStore();
|
||||
const editTitleShow = ref(false)
|
||||
const resendTokenFormShow = ref(false)
|
||||
const r2DomainShow = ref(false)
|
||||
const ossConfigShow = ref(false)
|
||||
const turnstileShow = ref(false)
|
||||
const tgSettingShow = ref(false)
|
||||
const noticePopupShow = ref(false)
|
||||
@@ -692,14 +694,17 @@ const uiStore = useUiStore();
|
||||
const {settings: setting} = storeToRefs(settingStore);
|
||||
const editTitle = ref('')
|
||||
const settingLoading = ref(false)
|
||||
const clearS3Loading = ref(false)
|
||||
const r2DomainInput = ref('')
|
||||
const loginOpacity = ref(0)
|
||||
const backgroundUrl = ref('')
|
||||
let backgroundFile = {}
|
||||
const s3IsDisabled = ref(false)
|
||||
const showSetBackground = ref(false)
|
||||
let regVerifyCount = ref(1)
|
||||
let addVerifyCount = ref(1)
|
||||
let backup = '{}'
|
||||
const addS3Show = ref(false)
|
||||
const addVerifyCountShow = ref(false)
|
||||
const regVerifyCountShow = ref(false)
|
||||
const resendTokenForm = reactive({
|
||||
@@ -711,6 +716,14 @@ const turnstileForm = reactive({
|
||||
secretKey: ''
|
||||
})
|
||||
|
||||
const s3 = reactive({
|
||||
bucket: '',
|
||||
endpoint: '',
|
||||
region: '',
|
||||
s3AccessKey: '',
|
||||
s3SecretKey: ''
|
||||
})
|
||||
|
||||
const noticeForm = reactive({
|
||||
noticeTitle: '',
|
||||
noticeContent: '',
|
||||
@@ -763,14 +776,8 @@ function getSettings() {
|
||||
r2DomainInput.value = setting.value.r2Domain
|
||||
addVerifyCount.value = setting.value.addVerifyCount
|
||||
regVerifyCount.value = setting.value.regVerifyCount
|
||||
noticeForm.notice = setting.value.notice
|
||||
noticeForm.noticeContent = setting.value.noticeContent
|
||||
noticeForm.noticeDuration = setting.value.noticeDuration
|
||||
noticeForm.noticeTitle = setting.value.noticeTitle
|
||||
noticeForm.noticePosition = setting.value.noticePosition
|
||||
noticeForm.noticeType = setting.value.noticeType
|
||||
noticeForm.noticeOffset = setting.value.noticeOffset
|
||||
noticeForm.noticeWidth = setting.value.noticeWidth
|
||||
resetNoticeForm()
|
||||
resetAddS3Form()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -789,6 +796,14 @@ function openRegVerifyCount() {
|
||||
regVerifyCountShow.value = true
|
||||
}
|
||||
|
||||
function resetAddS3Form() {
|
||||
s3.bucket = setting.value.bucket
|
||||
s3.endpoint = setting.value.endpoint
|
||||
s3.region = setting.value.region
|
||||
s3.s3AccessKey = ''
|
||||
s3.s3SecretKey = ''
|
||||
}
|
||||
|
||||
const resendList = computed(() => {
|
||||
|
||||
let list = Object.keys(setting.value.resendTokens).map(key => {
|
||||
@@ -958,6 +973,33 @@ function addChatTag(val) {
|
||||
})
|
||||
}
|
||||
|
||||
function clearS3() {
|
||||
|
||||
const form = {
|
||||
bucket: '',
|
||||
endpoint: '',
|
||||
region: '',
|
||||
s3AccessKey: '',
|
||||
s3SecretKey: ''
|
||||
}
|
||||
clearS3Loading.value = true
|
||||
editSetting(form)
|
||||
}
|
||||
|
||||
function saveS3() {
|
||||
|
||||
const form = {
|
||||
bucket: s3.bucket,
|
||||
endpoint: s3.endpoint,
|
||||
region: s3.region
|
||||
}
|
||||
|
||||
if (s3.s3AccessKey) form.s3AccessKey = s3.s3AccessKey
|
||||
if (s3.s3SecretKey) form.s3SecretKey = s3.s3SecretKey
|
||||
|
||||
editSetting(form)
|
||||
}
|
||||
|
||||
function tgBotSave() {
|
||||
const form = {
|
||||
tgBotToken: tgBotToken.value,
|
||||
@@ -1125,6 +1167,8 @@ function change(e) {
|
||||
const settingForm = {...setting.value}
|
||||
delete settingForm.siteKey
|
||||
delete settingForm.secretKey
|
||||
delete settingForm.s3AccessKey
|
||||
delete settingForm.s3SecretKey
|
||||
delete settingForm.resendTokens
|
||||
editSetting(settingForm, false)
|
||||
}
|
||||
@@ -1167,11 +1211,13 @@ function editSetting(settingForm, refreshStatus = true) {
|
||||
addVerifyCountShow.value = false
|
||||
regVerifyCountShow.value = false
|
||||
noticePopupShow.value = false
|
||||
addS3Show.value = false
|
||||
}).catch((e) => {
|
||||
loginOpacity.value = setting.value.loginOpacity
|
||||
setting.value = {...setting.value, ...JSON.parse(backup)}
|
||||
}).finally(() => {
|
||||
settingLoading.value = false
|
||||
clearS3Loading.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1492,6 +1538,16 @@ function editSetting(settingForm, refreshStatus = true) {
|
||||
width: fit-content !important;
|
||||
}
|
||||
|
||||
.s3-button {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
gap: 15px;
|
||||
|
||||
.el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.r2domain {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
@@ -1523,6 +1579,10 @@ function editSetting(settingForm, refreshStatus = true) {
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-input {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.concerning-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -17,8 +17,9 @@ export default defineConfig(({mode}) => {
|
||||
base: env.VITE_STATIC_URL || '/',
|
||||
plugins: [vue(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate', // 配置 service worker 的注册方式
|
||||
includeAssets: ['favicon.svg', 'robots.txt'], // 指定需要包含的静态资源
|
||||
registerType: 'autoUpdate',
|
||||
|
||||
includeAssets: ['favicon.svg', 'robots.txt'],
|
||||
manifest: {
|
||||
name: 'Cloud Mail',
|
||||
short_name: 'Cloud Mail',
|
||||
@@ -26,14 +27,26 @@ export default defineConfig(({mode}) => {
|
||||
theme_color: '#FFFFFF',
|
||||
icons: [
|
||||
{
|
||||
src: 'mail-192.png',//像素尺寸一定要对应
|
||||
src: 'mail-192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: 'mail-512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png'
|
||||
},
|
||||
{
|
||||
src: 'mail-512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any'
|
||||
},
|
||||
{
|
||||
src: 'mail-512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable'
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user