新增深色模式

This commit is contained in:
eoao
2025-08-22 00:13:56 +08:00
parent d3325f0fe3
commit ff8ba81661
180 changed files with 1867 additions and 3346 deletions

View File

@@ -34,7 +34,7 @@
<el-option key="1" :label="$t('user')" :value="'user'"/>
<el-option key="2" :label="$t('selectEmail')" :value="'account'"/>
</el-select>
<div style="color: #333;display: flex;">
<div class="search-type">
<span>{{ selectTitle }}</span>
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="20" height="20"/>
</div>
@@ -194,6 +194,11 @@ function getEmailList(emailId, size) {
pointer-events: none;
}
.search-type {
display: flex;
color: var(--el-text-color-regular);
}
:deep(.header-actions) {
padding-top: 8px;
padding-bottom: 8px;

View File

@@ -121,7 +121,7 @@
<script setup>
import {Icon} from "@iconify/vue";
import {useTransition} from "@vueuse/core";
import {defineOptions, onActivated, onDeactivated, onMounted, reactive, ref, watch} from "vue";
import {defineOptions, onActivated, onDeactivated, onMounted, reactive, ref, watch, computed} from "vue";
import echarts from "@/echarts/index.js";
import dayjs from "dayjs";
import {analysisEcharts} from "@/request/analysis.js";
@@ -185,6 +185,17 @@ const emailColumnData = {
daysData: []
}
const topic = computed(() => ({
color: uiStore.dark ? '#E5EAF3' : '#303133',
background: uiStore.dark ? '#141414' : '#FFFFFF',
borderColor: uiStore.dark ? '#141414' : '#FFFFFF',
scaleLineColor: uiStore.dark ? '#636466' : '#CDD0D6',
crossColor: uiStore.dark ? '#8D9095' : '#A8ABB2',
axisColor: uiStore.dark ? '#A3A6AD' : '#909399',
splitLineColor: uiStore.dark ? '#58585B' : '#D4D7DE',
gaugeSplitLine: uiStore.dark ? '#CFD3DC' : '#606266',
containerBackground: uiStore.dark ? '#6C6E72' : '#E6EBF8'
}))
let daySendTotal = 0
let leaveWidth = 0
let senderPie = null
@@ -194,6 +205,7 @@ let sendGauge = null
let first = true
let boxKey = ref(0)
let senderPieLeft = window.innerWidth < 500 ? `${window.innerWidth - 110}` : '72%'
let analysisDark = uiStore.dark
onMounted(() => {
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -232,17 +244,6 @@ onMounted(() => {
})
function initPicture() {
if (route.name !== 'analysis') return
boxKey.value++
setTimeout(() => {
createSenderPie()
createIncreaseLine()
createEmailColumnChart();
createSendGauge();
})
}
const widthChange = debounce(initPicture, 500, {
leading: false,
trailing: true
@@ -261,6 +262,9 @@ onActivated(() => {
widthChange()
} else if (!senderPie) {
widthChange()
} else if (analysisDark !== uiStore.dark) {
initPicture()
analysisDark = uiStore.dark
}
})
@@ -273,6 +277,23 @@ window.onresize = () => {
widthChange()
}
watch(() => uiStore.dark, () => {
if (route.name !== 'analysis') return
analysisDark = uiStore.dark
initPicture()
})
function initPicture() {
if (route.name !== 'analysis') return
boxKey.value++
setTimeout(() => {
createSenderPie()
createIncreaseLine()
createEmailColumnChart();
createSendGauge();
})
}
function setStyle() {
senderPieLeft = window.innerWidth < 500 ? `${window.innerWidth - 110}` : '72%'
emailColumnData.barWidth = window.innerWidth > 767 ? '40%' : '60%'
@@ -301,11 +322,14 @@ function createSenderPie() {
if (senderPie) {
senderPie.dispose()
}
senderPie = echarts.init(document.querySelector(".sender-pie"))
let option = {
tooltip: {
trigger: 'item',
textStyle: {
color: topic.value.color
},
backgroundColor: topic.value.background,
formatter: params => {
return `${params.marker} ${params.name} ${params.value} (${params.percent}%)`;
}
@@ -315,6 +339,9 @@ function createSenderPie() {
orient: 'vertical',
left: '10',
top: '20',
textStyle: {
color: topic.value.color
},
formatter: function (name) {
return truncateTextByWidth(name)
}
@@ -329,7 +356,7 @@ function createSenderPie() {
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 4,
borderColor: '#fff',
borderColor: topic.value.borderColor,
borderWidth: 2
},
label: {
@@ -370,7 +397,10 @@ function createIncreaseLine() {
axisPointer: {
type: 'cross', // 指示器的类型为交叉型,适用于折线图等
crossStyle: {
color: '#999' // 设置指示器线的颜色
color: topic.value.crossColor// 设置指示器线的颜色
},
lineStyle: {
color: topic.value.crossColor // ← 竖线颜色
},
axis: 'x',
},
@@ -381,12 +411,12 @@ function createIncreaseLine() {
});
return result;
},
backgroundColor: '#fff', // 设置背景颜色
borderColor: '#ccc', // 设置边框颜色
backgroundColor: topic.value.background, // 设置背景颜色
borderColor: topic.value.splitLineColor, // 设置边框颜色
borderWidth: 1, // 设置边框宽度
padding: 10, // 设置内边距
textStyle: {
color: '#333', // 设置文字颜色
color: topic.value.color, // 设置文字颜色
}
},
grid: {
@@ -402,7 +432,7 @@ function createIncreaseLine() {
show: false,
alignWithLabel: false, // 刻度线与标签对齐,
lineStyle: {
color: 'red',
color: topic.value.axisColor,
}
},
axisPointer: {
@@ -412,7 +442,7 @@ function createIncreaseLine() {
},
axisLine: {
lineStyle: {
color: '#909399',
color: topic.value.axisColor,
width: 1,
type: 'solid'
}
@@ -427,6 +457,7 @@ function createIncreaseLine() {
}
return value;
},
},
boundaryGap: false,
},
@@ -444,7 +475,7 @@ function createIncreaseLine() {
axisLine: {
show: true,
lineStyle: {
color: '#909399',
color: topic.value.axisColor,
width: 1,
}
},
@@ -460,7 +491,7 @@ function createIncreaseLine() {
show: true, // 显示网格线
lineStyle: {
type: 'dashed', // 设置网格线为虚线
color: '#ccc' // 设置虚线的颜色
color: topic.value.scaleLineColor // 设置虚线的颜色
}
}
},
@@ -519,6 +550,10 @@ function createEmailColumnChart() {
const option = {
tooltip: {
textStyle: {
color: topic.value.color
},
backgroundColor: topic.value.background,
formatter: function (params) {
params.marker
return `${params.marker} ${params.seriesName}: ${params.value}`
@@ -526,7 +561,10 @@ function createEmailColumnChart() {
},
legend: {
data: [t('emailReceived'), t('emailSent')],
top: '0'
top: '0',
textStyle: {
color: topic.value.color, // 图例文字颜色
}
},
grid: {
left: '18',
@@ -539,12 +577,12 @@ function createEmailColumnChart() {
type: 'category',
data: emailColumnData.daysData,
axisTick: {
show: false
show: false,
},
axisLine: {
show: true,
lineStyle: {
color: '#909399',
color: topic.value.axisColor,
width: 1,
}
},
@@ -555,6 +593,21 @@ function createEmailColumnChart() {
return 10
}
},
splitLine: {
show: true,
lineStyle: {
color: topic.value.splitLineColor, // ← 横线颜色
type: 'solid', // dashed=虚线solid=实线
width: 1
}
},
axisLine: {
show: true,
lineStyle: {
color: topic.value.axisColor,
width: 0,
}
},
type: 'value',
boundaryGap: [0, 0.1],
},
@@ -603,7 +656,12 @@ function createSendGauge() {
}
sendGauge = echarts.init(document.querySelector(".send-count"));
let option = {
tooltip: {},
tooltip: {
textStyle: {
color: topic.value.color
},
backgroundColor: topic.value.background
},
series: [{
name: t('sentToday'),
type: 'gauge',
@@ -622,31 +680,39 @@ function createSendGauge() {
color: '#3CB2FF'
}
},
axisLabel: {
color: topic.value.gaugeSplitLine,
},
// 轴线背景色(新增)
axisLine: {
roundCap: true,
lineStyle: {
color: [[1, '#E6EBF8']]
color: [[1, topic.value.containerBackground]]
}
},
splitLine: {
lineStyle: {
color: topic.value.gaugeSplitLine, // 大刻度线颜色
}
},
// 刻度颜色(新增)
axisTick: {
lineStyle: {
color: '#999'
color: topic.value.axisColor
}
},
// 中心文字颜色(新增)
detail: {
valueAnimation: true,
formatter: '{value}',
color: '#333' // 黑色文字
color: topic.value.color // 黑色文字
},
data: [{
value: daySendTotal,
name: t('total'),
// 名称标签颜色(新增)
title: {
color: '#333' // 灰色标签
color: topic.value.color // 灰色标签
}
}]
}],
@@ -683,7 +749,7 @@ function createSendGauge() {
height: 100%;
padding: 20px 20px 30px;
gap: 20px;
background: #FAFCFF;
background: var(--extra-light-fill);
display: grid;
grid-auto-rows: min-content;
@media (max-width: 1024px) {
@@ -727,6 +793,10 @@ function createSendGauge() {
gap: 5px;
grid-auto-rows: min-content;
> div:first-child {
font-size: 15px;
}
> div:last-child {
font-size: 13px;
}
@@ -761,18 +831,19 @@ function createSendGauge() {
justify-content: start;
gap: 20px;
padding-top: 5px;
font-size: 14px;
.normal {
width: fit-content;
color: var(--el-color-success);
font-weight: bold;
font-weight: bold;;
margin-left: 3px;
}
.deleted {
width: fit-content;
color: var(--el-color-danger);
font-weight: bold;
font-weight: bold;;
margin-left: 3px;
}
}
@@ -792,7 +863,7 @@ function createSendGauge() {
}
.picture-item {
background: #fff;
background: var(--el-bg-color);
border-radius: 8px;
border: 1px solid var(--el-border-color);
@@ -832,7 +903,7 @@ function createSendGauge() {
}
.picture-cs-item {
background: #fff;
background: var(--el-bg-color);
border-radius: 8px;
border: 1px solid var(--el-border-color);

View File

@@ -34,7 +34,7 @@
<el-alert v-if="email.status === 5" :closable="false" :title="$t('delayed')" class="email-msg" type="warning" show-icon />
</div>
<el-scrollbar class="htm-scrollbar" :class="email.attList.length === 0 ? 'bottom-distance' : ''">
<ShadowHtml :html="formatImage(email.content)" v-if="email.content" />
<ShadowHtml class="shadow-html" :html="formatImage(email.content)" v-if="email.content" />
<pre v-else class="email-text" >{{email.text}}</pre>
</el-scrollbar>
<div class="att" v-if="email.attList.length > 0">
@@ -51,7 +51,7 @@
<div class="att-name" @click="showImage(att.key)">
{{ att.filename }}
</div>
<div style="color: rgba(24, 36, 48, 0.6);">{{ formatBytes(att.size) }}</div>
<div class="att-size">{{ formatBytes(att.size) }}</div>
<div class="opt-icon att-icon">
<Icon v-if="isImage(att.filename)" icon="hugeicons:view" width="22" height="22" @click="showImage(att.key)"/>
<a :href="cvtR2Url(att.key)" download>
@@ -208,7 +208,7 @@ const handleDelete = () => {
display: flex;
align-items: center;
gap: 20px;
box-shadow: inset 0 -1px 0 0 rgba(100, 121, 143, 0.12);
box-shadow: var(--header-actions-border);
font-size: 18px;
.star {
display: flex;
@@ -281,7 +281,7 @@ const handleDelete = () => {
padding: 5px 8px;
border-radius: 4px;
align-self: start;
border: 1px solid #e7e9ec;
border: 1px solid var(--base-border-color);
display: grid;
grid-template-columns: auto 1fr auto auto;
gap: 10px;
@@ -290,6 +290,10 @@ const handleDelete = () => {
display: grid;
}
.att-size {
color: var(--secondary-text-color);
}
.att-name {
margin-right: 10px;
white-space: nowrap;
@@ -305,13 +309,13 @@ const handleDelete = () => {
}
.opt-icon {
color: rgba(24, 36, 48, 0.6);
color: var(--secondary-text-color);
align-items: center;
display: flex;
gap: 8px;
cursor: pointer;
a {
color: rgba(24, 36, 48, 0.6);
color: var(--secondary-text-color);
align-items: center;
display: flex;
}
@@ -321,14 +325,14 @@ const handleDelete = () => {
.email-info {
border-bottom: 1px solid #e7e9ec;
border-bottom: 1px solid var(--light-border-color);
margin-bottom: 20px;
padding-bottom: 8px;
@media (max-width: 1024px) {
margin-bottom: 15px;
}
.date {
color: #585d69;
color: var(--regular-text-color);
margin-bottom: 6px;
}
@@ -343,7 +347,7 @@ const handleDelete = () => {
margin-bottom: 6px;
.send-name {
color: #585d69;
color: var(--regular-text-color);
display: flex;
flex-wrap: wrap;
}
@@ -361,7 +365,7 @@ const handleDelete = () => {
word-break: break-word;
}
span:nth-child(2) {
color: #585d69;
color: var(--regular-text-color);
}
}
@@ -380,6 +384,17 @@ const handleDelete = () => {
}
}
.shadow-html::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--message-block-color); /* 半透明黑色蒙层 */
pointer-events: none; /* 不影响点击 */
}
.email-text {
font-family: inherit;
white-space: pre-wrap;

View File

@@ -32,7 +32,7 @@
:value="item"
/>
</el-select>
<div style="color: #333">
<div style="color: var(--el-text-color-primary)">
<span>{{ suffix }}</span>
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="20" height="20"/>
</div>
@@ -64,7 +64,7 @@
:value="item"
/>
</el-select>
<div style="color: #333">
<div>
<span>{{ suffix }}</span>
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="20" height="20"/>
</div>
@@ -178,7 +178,8 @@ window.loadBefore = (e) => {
}
const loginOpacity = computed(() => {
return `rgba(255, 255, 255, ${settingStore.settings.loginOpacity})`
const opacity = settingStore.settings.loginOpacity
return uiStore.dark ? `rgba(0, 0, 0, ${opacity})` : `rgba(255, 255, 255, ${opacity})`
})
const background = computed(() => {
@@ -415,7 +416,7 @@ function submitRegister() {
justify-content: center;
width: 450px;
height: 100%;
border: 1px solid #e4e7ed;
border-left: 1px solid var(--login-border);
box-shadow: var(--el-box-shadow-light);
@media (max-width: 1024px) {
padding: 20px 18px;
@@ -423,6 +424,7 @@ function submitRegister() {
margin-left: 18px;
}
@media (max-width: 767px) {
border: 1px solid var(--login-border);
padding: 20px 18px;
border-radius: 6px;
height: fit-content;
@@ -440,7 +442,7 @@ function submitRegister() {
.form-desc {
margin-top: 5px;
margin-bottom: 18px;
color: #71717a;
color: var(--form-desc-color);
}
.form-title {
@@ -453,17 +455,19 @@ function submitRegister() {
text-align: center;
span {
color: #006be6;
color: var(--login-switch-color);
cursor: pointer;
}
}
:deep(.el-input__wrapper) {
border-radius: 6px;
background: var(--el-bg-color);
}
.email-input :deep(.el-input__wrapper) {
border-radius: 6px 0 0 6px;
background: var(--el-bg-color);
}
.el-input {
@@ -490,7 +494,7 @@ function submitRegister() {
padding: 0 !important;
padding-left: 8px !important;
padding-right: 4px !important;
background: #FFFFFF;
background: var(--el-bg-color);
border-radius: 0 8px 8px 0;
}
@@ -518,7 +522,6 @@ function submitRegister() {
#login-box {
background: linear-gradient(to bottom, #2980b9, #6dd5fa, #fff);
color: #333;
font: 100% Arial, sans-serif;
height: 100%;
margin: 0;

View File

@@ -15,40 +15,40 @@
<Icon class="icon" icon="fluent:broom-sparkle-16-regular" width="22" height="22" @click="clearNotUse"/>
</div>
<el-scrollbar class="scrollbar" :style="`background: ${regKeyData.length > 0 ? '#FAFCFF;' : '#FFF'}`">
<div class="loading" :class="regKeyLoading ? 'loading-show' : 'loading-hide'">
<loading />
<el-scrollbar class="scrollbar">
<div class="loading" :class="regKeyLoading ? 'loading-show' : 'loading-hide'" :style="regKeyFirst ? 'background: transparent' : ''">
<loading/>
</div>
<div class="code-box">
<div class="code-item" v-for="item in regKeyData">
<div class="code-info">
<div class="info-left">
<div class="info-left-item">
<span class="code" @click="copyCode(item.code)">{{item.code}}</span>
<span class="code" @click="copyCode(item.code)">{{ item.code }}</span>
</div>
<div class="info-left-item">
<div>{{$t('remainingUses')}}</div>
<div v-if="item.count">{{item.count}}</div>
<el-tag v-else type="danger">{{$t('exhausted')}}</el-tag>
<div>{{ $t('remainingUses') }}</div>
<div v-if="item.count">{{ item.count }}</div>
<el-tag v-else type="danger">{{ $t('exhausted') }}</el-tag>
</div>
<div class="info-left-item">
<div>{{$t('roleDesc')}}</div>
<el-tag>{{item.roleName}}</el-tag>
<div>{{ $t('roleDesc') }}</div>
<el-tag>{{ item.roleName }}</el-tag>
</div>
<div class="info-left-item">
<div>{{$t('validUntil')}}</div>
<div v-if="item.expireTime">{{ formatExpireTime(item.expireTime)}}</div>
<el-tag v-else type="danger">{{$t('expired')}}</el-tag>
<div>{{ $t('validUntil') }}</div>
<div v-if="item.expireTime">{{ formatExpireTime(item.expireTime) }}</div>
<el-tag v-else type="danger">{{ $t('expired') }}</el-tag>
</div>
</div>
<div class="info-right">
<el-dropdown class="setting">
<Icon icon="fluent:settings-24-filled" width="21" height="21" color="#909399" />
<template #dropdown >
<Icon icon="fluent:settings-24-filled" width="21" height="21" color="#909399"/>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="copyCode(item.code)">{{$t('copy')}}</el-dropdown-item>
<el-dropdown-item @click="openHistory(item)">{{$t('history')}}</el-dropdown-item>
<el-dropdown-item @click="deleteRegKey(item)">{{$t('delete')}}</el-dropdown-item>
<el-dropdown-item @click="copyCode(item.code)">{{ $t('copy') }}</el-dropdown-item>
<el-dropdown-item @click="openHistory(item)">{{ $t('history') }}</el-dropdown-item>
<el-dropdown-item @click="deleteRegKey(item)">{{ $t('delete') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -64,7 +64,7 @@
<div class="container">
<el-input v-model="addForm.code" :placeholder="$t('regKey')">
<template #suffix>
<Icon @click.stop="genCode" class="gen-code" icon="bitcoin-icons:refresh-filled" width="24" height="24" />
<Icon @click.stop="genCode" class="gen-code" icon="bitcoin-icons:refresh-filled" width="24" height="24"/>
</template>
</el-input>
<el-select v-model="addForm.roleId" :placeholder="$t('roleDesc')">
@@ -77,31 +77,33 @@
/>
<el-input-number v-model="addForm.count" :min="1" :max="99999"/>
<el-button class="btn" type="primary" @click="submit" :loading="addLoading"
>{{$t('add')}}
>{{ $t('add') }}
</el-button>
</div>
</el-dialog>
<el-dialog class="history-list" v-model="showRegKeyHistory" :title="$t('useHistory')">
<div class="loading" :class="historyLoading ? 'loading-show' : 'loading-hide'">
<loading />
<loading/>
</div>
<el-table v-if="!historyLoading" :data="historyList" :fit="true" style="height: 100%" >
<el-table-column :min-width="emailColumnWidth" property="email" :label="$t('user')" :show-overflow-tooltip="true" />
<el-table-column :width="createTimeColumnWidth" :formatter="formatUserCreateTime" property="createTime" :label="$t('date')" fixed="right" :show-overflow-tooltip="true" />
<el-table v-if="!historyLoading" :data="historyList" :fit="true" style="height: 100%">
<el-table-column :min-width="emailColumnWidth" property="email" :label="$t('user')"
:show-overflow-tooltip="true"/>
<el-table-column :width="createTimeColumnWidth" :formatter="formatUserCreateTime" property="createTime"
:label="$t('date')" fixed="right" :show-overflow-tooltip="true"/>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import {defineOptions, reactive, ref, watch} from "vue"
import {defineOptions, nextTick, reactive, ref, watch} from "vue"
import {Icon} from "@iconify/vue";
import loading from "@/components/loading/index.vue";
import {useSettingStore} from "@/store/setting.js";
import {roleSelectUse} from "@/request/role.js";
import {useRoleStore} from "@/store/role.js";
import {regKeyAdd, regKeyList, regKeyClearNotUse, regKeyDelete, regKeyHistory} from "@/request/reg-key.js";
import { getTextWidth } from "@/utils/text.js";
import {getTextWidth} from "@/utils/text.js";
import dayjs from "dayjs";
import {tzDayjs} from "@/utils/day.js";
import {useI18n} from "vue-i18n";
@@ -116,7 +118,7 @@ const params = reactive({
code: '',
})
const { t } = useI18n()
const {t} = useI18n()
const roleList = reactive([])
const addLoading = ref(false)
const showAdd = ref(false)
@@ -193,15 +195,7 @@ function formatUserCreateTime(regKey) {
const currentYear = dayjs().year();
const expireYear = createTime.year();
if(settingStore.lang === 'zh') {
if (expireYear === currentYear) {
return createTime.format('M月D日 HH:mm');
} else {
return createTime.format('YYYY年M月D日 HH:mm');
}
} else {
if (settingStore.lang === 'en') {
if (expireYear === currentYear) {
return createTime.format('MMM D, HH:mm');
@@ -209,6 +203,14 @@ function formatUserCreateTime(regKey) {
return createTime.format('MMM D, YYYY HH:mm');
}
} else {
if (expireYear === currentYear) {
return createTime.format('M月D日 HH:mm');
} else {
return createTime.format('YYYY年M月D日 HH:mm');
}
}
}
@@ -218,16 +220,18 @@ function formatExpireTime(expireTime) {
const currentYear = dayjs().year();
const expireYear = expireDate.year();
if (settingStore.lang === 'zh') {
return expireYear === currentYear
? expireDate.format('M月D日')
: expireDate.format('YYYY年M月D日');
} else {
if (settingStore.lang === 'en') {
return expireYear === currentYear
? expireDate.format('MMM D')
: expireDate.format('MMM D, YYYY');
} else {
return expireYear === currentYear
? expireDate.format('M月D日')
: expireDate.format('YYYY年M月D日');
}
}
@@ -248,7 +252,9 @@ function getList(showLoading = false) {
regKeyData.length = 0
regKeyData.push(...list)
regKeyLoading.value = false
regKeyFirst.value = false
setTimeout(() => {
regKeyFirst.value = false
},200)
})
}
@@ -353,8 +359,8 @@ function submit() {
})
}
function deleteRegKey(regKey){
ElMessageBox.confirm(t('delConfirm',{msg: regKey.code}), {
function deleteRegKey(regKey) {
ElMessageBox.confirm(t('delConfirm', {msg: regKey.code}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
@@ -370,7 +376,7 @@ function deleteRegKey(regKey){
});
}
function resetForm(){
function resetForm() {
addForm.code = ''
}
@@ -390,30 +396,37 @@ function openAdd() {
.scrollbar {
height: calc(100% - 48px);
position: relative;
background: var(--extra-light-fill);
@media (max-width: 372px) {
height: calc(100% - 85px);
}
.code-box {
padding: 15px 15px 25px 15px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 15px;
.code-item {
background-color: #fff;
background: var(--el-bg-color);
border-radius: 8px;
border: 1px solid var(--el-border-color);
transition: all 300ms;
transition: all 200ms;
padding: 15px;
.code-info {
display: flex;
.info-left {
flex: 1;
min-width: 0;
.info-left-item {
display: flex;
padding-top: 5px;
.code {
font-weight: bold;
font-weight: bold;;
font-size: 16px;
white-space: nowrap;
overflow: hidden;
@@ -479,7 +492,7 @@ function openAdd() {
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(255, 255, 255, 0.8);
background: var(--loadding-background);
z-index: 2;
}
@@ -529,6 +542,7 @@ function openAdd() {
@media (max-width: 767px) {
gap: 15px;
}
.search-input {
width: min(200px, calc(100vw - 140px));
}
@@ -549,7 +563,7 @@ function openAdd() {
}
:deep(.el-table__inner-wrapper:before) {
background: #fff;
background: var(--el-bg-color);
}
</style>
</style>

View File

@@ -1,78 +1,82 @@
<template>
<div class="perm-box">
<div class="header-actions">
<Icon class="icon" icon="ion:add-outline" width="23" height="23" @click="openAddRole" />
<Icon class="icon" icon="ion:reload" width="18" height="18" @click="refresh" />
<Icon class="icon" icon="ion:add-outline" width="23" height="23" @click="openAddRole"/>
<Icon class="icon" icon="ion:reload" width="18" height="18" @click="refresh"/>
</div>
<div class="loading" v-if="tableLoading">
<loading/>
</div>
<el-scrollbar v-else class="perm-scrollbar">
<div>
<el-table
:data="roles"
style="width: 100%;"
>
<el-table-column width="10" />
<el-table-column :label="$t('role')" prop="name" :min-width="roleWidth">
<template #default="props">
<div class="role-name">
<span >{{props.row.name}}</span>
<span v-if="props.row.isDefault"><el-tag class="def-tag" >{{$t('default')}}</el-tag></span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('order')" :width="sortWidth" prop="sort"/>
<el-table-column v-if="desShow" :label="$t('description')" min-width="200" prop="description" >
<template #default="props">
<div class="description">
<span >{{props.row.description}}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('tabSetting')" :width="settingWidth">
<template #default="props">
<el-dropdown trigger="click">
<el-button size="small" type="primary">{{$t('action')}}</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openRoleSet(props.row)">{{$t('change')}}</el-dropdown-item>
<el-dropdown-item @click="setDef(props.row)">{{$t('default')}}</el-dropdown-item>
<el-dropdown-item @click="delRole(props.row)">{{$t('delete')}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<el-scrollbar class="perm-scrollbar">
<div class="loading" :class="tableLoading ? 'loading-show' : 'loading-hide'"
:style="first ? 'background: transparent' : ''">
<loading/>
</div>
<el-table
:data="roles"
style="height: 100%;"
:empty-text="''"
>
<el-table-column width="10"/>
<el-table-column :label="$t('role')" prop="name" :min-width="roleWidth">
<template #default="props">
<div class="role-name">
<span>{{ props.row.name }}</span>
<span v-if="props.row.isDefault"><el-tag class="def-tag">{{ $t('default') }}</el-tag></span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('order')" :width="sortWidth" prop="sort"/>
<el-table-column v-if="desShow" :label="$t('description')" min-width="200" prop="description">
<template #default="props">
<div class="description">
<span>{{ props.row.description }}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('tabSetting')" :width="settingWidth">
<template #default="props">
<el-dropdown trigger="click">
<el-button size="small" type="primary">{{ $t('action') }}</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openRoleSet(props.row)">{{ $t('change') }}</el-dropdown-item>
<el-dropdown-item @click="setDef(props.row)">{{ $t('default') }}</el-dropdown-item>
<el-dropdown-item @click="delRole(props.row)">{{ $t('delete') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
</el-scrollbar>
<el-dialog top="5vh" class="dialog" v-model="roleFormShow" @closed="resetForm">
<template #header>
<span style="font-size: 18px">{{dialogType.title}}</span>
<span style="font-size: 18px">{{ dialogType.title }}</span>
<el-popover
width ="340"
width="340"
:title="t('featDesc')"
placement="bottom"
>
<template #reference>
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</template>
<div style="font-weight: bold;margin-bottom: 2px;">{{t('emailInterception')}}</div>
<div>{{t('emailInterceptionDesc')}}</div>
<div style="font-weight: bold;margin-top: 10px;margin-bottom: 2px;">{{t('availableDomains')}}</div>
<div style="font-weight: bold;;margin-bottom: 2px;">{{ t('emailInterception') }}</div>
<div>{{ t('emailInterceptionDesc') }}</div>
<div style="font-weight: bold;;margin-top: 10px;margin-bottom: 2px;">{{ t('availableDomains') }}</div>
<div>
{{t('availableDomainsDesc')}}
{{ t('availableDomainsDesc') }}
</div>
</el-popover>
</template>
<div class="dialog-box">
<el-input class="dialog-input" v-model="form.name" type="text" :maxlength="12" :placeholder="$t('roleName')" autocomplete="off" />
<el-input class="dialog-input" v-model="form.description" :maxlength="30" type="text" :placeholder="$t('description')" autocomplete="off" />
<el-input-tag class="dialog-input-tag" tag-type="warning" :class="form.banEmail.length === 0 ? 'dialog-input' : '' " v-model="form.banEmail" @add-tag="banEmailAddTag" type="text" :placeholder="$t('emailInterception')" autocomplete="off" />
<el-input class="dialog-input" v-model="form.name" type="text" :maxlength="12" :placeholder="$t('roleName')"
autocomplete="off"/>
<el-input class="dialog-input" v-model="form.description" :maxlength="30" type="text"
:placeholder="$t('description')" autocomplete="off"/>
<el-input-tag class="dialog-input-tag" tag-type="warning"
:class="form.banEmail.length === 0 ? 'dialog-input' : '' " v-model="form.banEmail"
@add-tag="banEmailAddTag" type="text" :placeholder="$t('emailInterception')" autocomplete="off"/>
<el-radio-group class="dialog-radio" v-model="form.banEmailType" v-if="form.banEmail.length > 0">
<el-radio :label="$t('removeAll')" :value="0" />
<el-radio :label="$t('removeContent')" :value="1" />
<el-radio :label="$t('removeAll')" :value="0"/>
<el-radio :label="$t('removeContent')" :value="1"/>
</el-radio-group>
<el-select
class="dialog-input"
@@ -94,11 +98,12 @@
/>
</el-select>
<div class="dialog-input">
<el-input-number :placeholder="$t('order')" :min="0" :max="9999" v-model.number="form.sort" controls-position="right" autocomplete="off" />
<el-input-number :placeholder="$t('order')" :min="0" :max="9999" v-model.number="form.sort"
controls-position="right" autocomplete="off"/>
</div>
<el-radio-group v-model="expand" size="small" @change="expandChange" class="perm-expand">
<el-radio-button :label="$t('expand')" :value="true" />
<el-radio-button :label="$t('collapse')" :value="false" />
<el-radio-button :label="$t('expand')" :value="true"/>
<el-radio-button :label="$t('collapse')" :value="false"/>
</el-radio-group>
<el-tree
:expand-on-click-node="false"
@@ -114,25 +119,28 @@
>
<template #default="{ node, data }">
<div>
<span>{{node.label}}</span>
<span>{{ node.label }}</span>
<span class="send-num" v-if="data.permKey === 'email:send'" @click.stop>
<el-input-number v-model="form.sendCount" controls-position="right" :min="0" :max="99999" size="small" :placeholder="$t('total')" >
<el-input-number v-model="form.sendCount" controls-position="right" :min="0" :max="99999" size="small"
:placeholder="$t('total')">
</el-input-number>
<el-select v-model="form.sendType" placeholder="Select" size="small" style="width: 65px;margin-left: 5px;">
<el-option :label="$t('total')" value="count" />
<el-option :label="$t('daily')" value="day" />
<el-option :label="$t('btnBan')" value="ban" />
<el-select v-model="form.sendType" placeholder="Select" size="small"
style="width: 65px;margin-left: 5px;">
<el-option :label="$t('total')" value="count"/>
<el-option :label="$t('daily')" value="day"/>
<el-option :label="$t('btnBan')" value="ban"/>
</el-select>
</span>
<span class="send-num" v-if="data.permKey === 'account:add'" @click.stop>
<el-input-number v-model="form.accountCount" controls-position="right" :min="0" :max="99999" size="small" :placeholder="$t('total')" >
<el-input-number v-model="form.accountCount" controls-position="right" :min="0" :max="99999"
size="small" :placeholder="$t('total')">
</el-input-number>
</span>
</div>
</template>
</el-tree>
<el-button class="btn" type="primary" :loading="permLoading" @click="roleFormClick"
>{{$t('save')}}
>{{ $t('save') }}
</el-button>
</div>
</el-dialog>
@@ -153,8 +161,8 @@ defineOptions({
name: 'role'
})
const { domainList } = useSettingStore();
const { t, locale } = useI18n();
const {domainList} = useSettingStore();
const {t, locale} = useI18n();
const userStore = useUserStore();
const roleStore = useRoleStore();
const roleFormShow = ref(false)
@@ -167,6 +175,7 @@ const desShow = ref(true)
const settingWidth = ref(null)
const sortWidth = ref(null)
const roleWidth = ref(200)
const first = ref(true)
const dialogType = reactive({
title: '',
@@ -200,7 +209,7 @@ rolePermTree().then(tree => {
domainOptions = domainList.map(domain => {
const cleanDomain = domain.replace(/^@/, '');
return { label: cleanDomain, value: cleanDomain };
return {label: cleanDomain, value: cleanDomain};
});
@@ -209,7 +218,7 @@ function availDomainChange() {
return !domainOptions.map(option => option.value).includes(domain)
})
if (index > -1) {
form.availDomain.splice(index,1)
form.availDomain.splice(index, 1)
}
}
@@ -292,7 +301,7 @@ function setRole() {
return
}
const params = {...form,roleId: chooseRole.roleId}
const params = {...form, roleId: chooseRole.roleId}
const checkedId = tree.value.getCheckedKeys()
const halfId = tree.value.getHalfCheckedKeys()
params.permIds = [...checkedId, ...halfId]
@@ -390,6 +399,9 @@ function getRoleList() {
roles.value = list
}).finally(() => {
tableLoading.value = false
setTimeout(() => {
first.value = false
}, 200)
})
}
@@ -414,6 +426,7 @@ window.onresize = () => {
height: 100%;
overflow: hidden;
width: 100%;
.perm-scrollbar {
height: 100%;
}
@@ -421,6 +434,7 @@ window.onresize = () => {
.send-num {
margin-left: 10px;
.el-input-number {
width: 95px;
}
@@ -436,16 +450,19 @@ window.onresize = () => {
display: flex;
align-items: center;
gap: 18px;
box-shadow: inset 0 -1px 0 0 rgba(100, 121, 143, 0.12);
box-shadow: var(--header-actions-border);
font-size: 18px;
.search {
:deep(.el-input-group) {
height: 28px;
}
:deep(.el-input__inner) {
height: 28px;
}
}
.icon {
cursor: pointer;
}
@@ -466,10 +483,25 @@ window.onresize = () => {
}
.loading {
height: 100%;
height: calc(100% - 41px);
width: 100%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
background: var(--loadding-background);
z-index: 2;
}
.loading-show {
transition: all 200ms ease 200ms;
opacity: 1;
}
.loading-hide {
pointer-events: none;
transition: all 200ms;
opacity: 0;
}
.role-name {
@@ -494,15 +526,17 @@ window.onresize = () => {
.dialog-input {
margin-bottom: 15px !important;
}
.dialog-radio {
margin-top: 5px;
margin-bottom: 5px;
}
.dialog-input-tag {
}
}
.perm-expand {
.perm-expand {
margin-bottom: 5px;
--el-border-radius-base: 4px;
position: relative;
@@ -520,8 +554,13 @@ window.onresize = () => {
}
}
:deep(.el-scrollbar__view) {
height: 100%;
}
.btn {
width: 100%;
margin-top: 15px;
}
</style>
</style>

View File

@@ -32,7 +32,7 @@
</div>
<div class="del-email" v-perm="'my:delete'">
<div class="title">{{$t('deleteUser')}}</div>
<div style="color: #585d69;">
<div style="color: var(--regular-text-color);">
{{$t('delAccountMsg')}}
</div>
<div>

View File

@@ -1,31 +1,31 @@
<template>
<div class="settings-container">
<div v-if="firstLoading" class="loading">
<loading />
<loading/>
</div>
<el-scrollbar class="scroll" v-else >
<el-scrollbar class="scroll" v-else>
<div class="scroll-body">
<div class="card-grid">
<!-- Website Settings Card -->
<div class="settings-card">
<div class="card-title">{{$t('websiteSetting')}}</div>
<div class="card-title">{{ $t('websiteSetting') }}</div>
<div class="card-content">
<div class="setting-item">
<div><span>{{$t('websiteReg')}}</span></div>
<div><span>{{ $t('websiteReg') }}</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.register"/>
</div>
</div>
<div class="setting-item">
<div><span>{{$t('loginDomain')}}</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.loginDomain"/>
</div>
</div>
<div class="setting-item">
<div><span>{{$t('regKey')}}</span></div>
<div><span>{{ $t('loginDomain') }}</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.loginDomain"/>
</div>
</div>
<div class="setting-item">
<div><span>{{ $t('regKey') }}</span></div>
<div>
<el-select
@change="change"
@@ -43,7 +43,7 @@
</div>
</div>
<div class="setting-item">
<div><span>{{$t('addAccount')}}</span></div>
<div><span>{{ $t('addAccount') }}</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.addEmail"/>
@@ -51,7 +51,7 @@
</div>
<div class="setting-item">
<div>
<span>{{$t('multipleEmail')}}</span>
<span>{{ $t('multipleEmail') }}</span>
<el-tooltip effect="dark" :content="$t('multipleEmailDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
@@ -63,7 +63,7 @@
</div>
<div class="setting-item">
<div>
<span>{{$t('physicallyWipeData')}}</span>
<span>{{ $t('physicallyWipeData') }}</span>
<el-tooltip effect="dark" :content="$t('physicallyWipeDataDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
@@ -80,10 +80,10 @@
<!-- Personalization Settings Card -->
<div class="settings-card">
<div class="card-title">{{$t('customization')}}</div>
<div class="card-title">{{ $t('customization') }}</div>
<div class="card-content">
<div class="setting-item">
<div class="title-item"><span>{{$t('websiteTitle')}}</span></div>
<div class="title-item"><span>{{ $t('websiteTitle') }}</span></div>
<div class="email-title">
<span>{{ setting.title }}</span>
<el-button class="opt-button" size="small" type="primary" @click="editTitleShow = true">
@@ -92,13 +92,14 @@
</div>
</div>
<div class="setting-item">
<div class="title-item"><span>{{$t('loginBoxOpacity')}}</span></div>
<div class="title-item"><span>{{ $t('loginBoxOpacity') }}</span></div>
<div>
<el-input-number size="small" v-model="loginOpacity" @change="opacityChange" :precision="2" :step="0.01" :max="1" :min="0" />
<el-input-number size="small" v-model="loginOpacity" @change="opacityChange" :precision="2"
:step="0.01" :max="1" :min="0"/>
</div>
</div>
<div class="setting-item personalized">
<div><span>{{$t('loginBackground')}}</span></div>
<div><span>{{ $t('loginBackground') }}</span></div>
<div>
<el-image
class="background"
@@ -128,10 +129,10 @@
<!-- Email Sending Settings Card -->
<div class="settings-card">
<div class="card-title">{{$t('emailSetting')}}</div>
<div class="card-title">{{ $t('emailSetting') }}</div>
<div class="card-content">
<div class="setting-item">
<div><span>{{$t('receiveEmail')}}</span></div>
<div><span>{{ $t('receiveEmail') }}</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.receive"/>
@@ -139,7 +140,7 @@
</div>
<div class="setting-item">
<div>
<span>{{$t('autoRefresh')}}</span>
<span>{{ $t('autoRefresh') }}</span>
<el-tooltip effect="dark" :content="$t('autoRefreshDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
@@ -161,7 +162,7 @@
</div>
</div>
<div class="setting-item">
<div><span>{{$t('sendEmail')}}</span></div>
<div><span>{{ $t('sendEmail') }}</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.send"/>
@@ -169,7 +170,7 @@
</div>
<div class="setting-item">
<div>
<span>{{$t('noRecipientTitle')}}</span>
<span>{{ $t('noRecipientTitle') }}</span>
<el-tooltip effect="dark" :content="$t('noRecipientDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
@@ -180,12 +181,14 @@
</div>
</div>
<div class="setting-item">
<div><span>{{$t('resendToken')}}</span></div>
<div><span>{{ $t('resendToken') }}</span></div>
<div>
<el-button class="opt-button" style="margin-top: 0" @click="openResendList" size="small" type="primary">
<el-button class="opt-button" style="margin-top: 0" @click="openResendList" size="small"
type="primary">
<Icon icon="ic:round-list" width="18" height="18"/>
</el-button>
<el-button class="opt-button" style="margin-top: 0" @click="openResendForm" size="small" type="primary">
<el-button class="opt-button" style="margin-top: 0" @click="openResendForm" size="small"
type="primary">
<Icon icon="material-symbols:add-rounded" width="16" height="16"/>
</el-button>
</div>
@@ -195,10 +198,10 @@
<!-- R2 Object Storage Card -->
<div class="settings-card">
<div class="card-title">{{$t('R2OS')}}</div>
<div class="card-title">{{ $t('R2OS') }}</div>
<div class="card-content">
<div class="setting-item">
<div><span>{{$t('osDomain')}}</span></div>
<div><span>{{ $t('osDomain') }}</span></div>
<div class="r2domain">
<span>{{ setting.r2Domain || '' }}</span>
<el-button class="opt-button" size="small" type="primary" @click="r2DomainShow = true">
@@ -210,10 +213,10 @@
</div>
<div class="settings-card">
<div class="card-title">{{$t('emailPush')}}</div>
<div class="card-title">{{ $t('emailPush') }}</div>
<div class="card-content">
<div class="setting-item">
<div><span>{{$t('tgBot')}}</span></div>
<div><span>{{ $t('tgBot') }}</span></div>
<div class="forward">
<span>{{ setting.tgBotStatus === 0 ? $t('enabled') : $t('disabled') }}</span>
<el-button class="opt-button" size="small" type="primary" @click="openTgSetting">
@@ -222,7 +225,7 @@
</div>
</div>
<div class="setting-item">
<div><span>{{$t('otherEmail')}}</span></div>
<div><span>{{ $t('otherEmail') }}</span></div>
<div class="forward">
<span>{{ setting.forwardStatus === 0 ? $t('enabled') : $t('disabled') }}</span>
<el-button class="opt-button" size="small" type="primary" @click="openThirdEmailSetting">
@@ -231,7 +234,7 @@
</div>
</div>
<div class="setting-item">
<div><span>{{$t('forwardingRules')}}</span></div>
<div><span>{{ $t('forwardingRules') }}</span></div>
<div class="forward">
<span>{{ setting.ruleType === 0 ? $t('forwardAll') : $t('rules') }}</span>
<el-button class="opt-button" size="small" type="primary" @click="openForwardRules">
@@ -244,10 +247,10 @@
<!-- Turnstile Verification Card -->
<div class="settings-card">
<div class="card-title">{{$t('turnstileSetting')}}</div>
<div class="card-title">{{ $t('turnstileSetting') }}</div>
<div class="card-content">
<div class="setting-item">
<div><span>{{$t('signUpVerification')}}</span></div>
<div><span>{{ $t('signUpVerification') }}</span></div>
<div>
<el-button class="opt-button" size="small" type="primary" @click="openRegVerifyCount">
<Icon icon="fluent:settings-48-regular" width="18" height="18"/>
@@ -259,14 +262,14 @@
placeholder="Select"
class="bot-verify-select"
>
<el-option key="1" :value="0" :label="$t('enable')" />
<el-option key="1" :value="1" :label="$t('disable')" />
<el-option key="1" :value="2" :label="$t('rulesVerify')" />
<el-option key="1" :value="0" :label="$t('enable')"/>
<el-option key="1" :value="1" :label="$t('disable')"/>
<el-option key="1" :value="2" :label="$t('rulesVerify')"/>
</el-select>
</div>
</div>
<div class="setting-item">
<div><span>{{$t('addEmailVerification')}}</span></div>
<div><span>{{ $t('addEmailVerification') }}</span></div>
<div>
<el-button class="opt-button" size="small" type="primary" @click="openAddVerifyCount">
<Icon icon="fluent:settings-48-regular" width="18" height="18"/>
@@ -278,9 +281,9 @@
placeholder="Select"
class="bot-verify-select"
>
<el-option key="1" :value="0" :label="$t('enable')" />
<el-option key="1" :value="1" :label="$t('disable')" />
<el-option key="1" :value="2" :label="$t('rulesVerify')" />
<el-option key="1" :value="0" :label="$t('enable')"/>
<el-option key="1" :value="1" :label="$t('disable')"/>
<el-option key="1" :value="2" :label="$t('rulesVerify')"/>
</el-select>
</div>
</div>
@@ -306,10 +309,10 @@
</div>
<div class="settings-card">
<div class="card-title">{{$t('noticeTitle')}}</div>
<div class="card-title">{{ $t('noticeTitle') }}</div>
<div class="card-content">
<div class="setting-item">
<div><span>{{$t('noticePopup')}}</span></div>
<div><span>{{ $t('noticePopup') }}</span></div>
<div class="forward">
<span>{{ setting.notice === 0 ? $t('enabled') : $t('disabled') }}</span>
<el-button class="opt-button" size="small" type="primary" @click="openNoticePopupSetting">
@@ -318,7 +321,7 @@
</div>
</div>
<div class="setting-item">
<div><span>{{$t('popUp')}}</span></div>
<div><span>{{ $t('popUp') }}</span></div>
<div class="forward">
<el-button class="opt-button" size="small" type="primary" @click="openNoticePopup">
<Icon icon="mynaui:click-solid" width="18" height="18"/>
@@ -329,18 +332,25 @@
</div>
<div class="settings-card about">
<div class="card-title">{{$t('about')}}</div>
<div class="card-title">{{ $t('about') }}</div>
<div class="card-content">
<div class="concerning-item">
<span>{{$t('version')}} :</span>
<span>v1.7.0</span>
<span>{{ $t('version') }} :</span>
<el-badge is-dot :hidden="!hasUpdate">
<el-button @click="jump('https://github.com/eoao/cloud-mail/releases')">
{{ currentVersion }}
<template #icon>
<Icon icon="qlementine-icons:version-control-16" style="font-size: 20px" color="#1890FF"/>
</template>
</el-button>
</el-badge>
</div>
<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" />
<Icon icon="codicon:github-inverted" width="22" height="22"/>
</template>
</el-button>
<el-button @click="jump('https://t.me/cloud_mail_tg')">
@@ -351,23 +361,23 @@
</el-button>
</div>
<div class="concerning-item">
<span>{{$t('support')}} : </span>
<el-button @click="jump('https://doc.skymail.ink/support.html')" >
{{t('supportDesc')}}
<span>{{ $t('support') }} : </span>
<el-button @click="jump('https://doc.skymail.ink/support.html')">
{{ t('supportDesc') }}
<template #icon>
<Icon color="#79D6B5" icon="simple-icons:buymeacoffee" width="20" height="20" />
<Icon color="#79D6B5" icon="simple-icons:buymeacoffee" width="20" height="20"/>
</template>
</el-button>
</div>
<div class="concerning-item">
<span>{{ $t('help') }} : </span>
<el-button @click="jump('https://doc.skymail.ink')">
{{ t('document') }}
<template #icon>
<Icon color="#79D6B5" icon="fluent-color:document-32" width="18" height="18"/>
</template>
</el-button>
</div>
<div class="concerning-item">
<span>{{$t('help')}} : </span>
<el-button @click="jump('https://doc.skymail.ink')" >
{{t('document')}}
<template #icon>
<Icon color="#79D6B5" icon="fluent-color:document-32" width="18" height="18" />
</template>
</el-button>
</div>
</div>
</div>
</div>
@@ -377,7 +387,7 @@
<el-dialog v-model="editTitleShow" :title="$t('changeTitle')" width="340" @closed="editTitle = setting.title">
<form>
<el-input type="text" :placeholder="$t('websiteTitle')" v-model="editTitle"/>
<el-button type="primary" :loading="settingLoading" @click="saveTitle">{{$t('save')}}</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveTitle">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog v-model="resendTokenFormShow" :title="$t('resendToken')" width="340" @closed="cleanResendTokenForm">
@@ -391,13 +401,14 @@
/>
</el-select>
<el-input type="text" :placeholder="$t('addResendTokenDesc')" v-model="resendTokenForm.token"/>
<el-button type="primary" :loading="settingLoading" @click="saveResendToken">{{$t('save')}}</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveResendToken">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog v-model="r2DomainShow" :title="$t('addOsDomain')" width="340" @closed="r2DomainInput = setting.r2Domain">
<el-dialog v-model="r2DomainShow" :title="$t('addOsDomain')" width="340"
@closed="r2DomainInput = setting.r2Domain">
<form>
<el-input type="text" :placeholder="$t('domainDesc')" v-model="r2DomainInput"/>
<el-button type="primary" :loading="settingLoading" @click="saveR2domain">{{$t('save')}}</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveR2domain">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog v-model="turnstileShow" :title="$t('addTurnstileSecret')" width="340"
@@ -405,7 +416,7 @@
<form>
<el-input type="text" placeholder="Site Key" v-model="turnstileForm.siteKey"/>
<el-input type="text" style="margin-top: 15px" placeholder="Secret Key" v-model="turnstileForm.secretKey"/>
<el-button type="primary" :loading="settingLoading" @click="saveTurnstileKey">{{$t('save')}}</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveTurnstileKey">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog
@@ -415,16 +426,17 @@
>
<template #header>
<span style="font-size: 18px">
{{$t('backgroundTitle')}}
{{ $t('backgroundTitle') }}
<el-tooltip>
<template #content>
<span>{{$t('backgroundWarning')}}</span>
<span>{{ $t('backgroundWarning') }}</span>
</template>
<Icon class="title-icon warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
</span>
</template>
<el-input :placeholder="$t('backgroundUrlDesc')" v-model="backgroundUrl" v-if="!localUpShow" class="background-url" />
<el-input :placeholder="$t('backgroundUrlDesc')" v-model="backgroundUrl" v-if="!localUpShow"
class="background-url"/>
<el-image
v-if="localUpShow"
:preview-src-list="[backgroundImage]"
@@ -435,12 +447,12 @@
></el-image>
<div class="cut-button">
<el-button type="primary" link @click="openCut" v-if="!localUpShow">
{{$t('localUpload')}}
{{ $t('localUpload') }}
</el-button>
<el-button type="primary" link @click="localUpShow = false" v-if="localUpShow">
{{$t('imageLink')}}
{{ $t('imageLink') }}
</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveBackground">{{$t('save')}}</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveBackground">{{ $t('save') }}</el-button>
</div>
</el-dialog>
<el-dialog
@@ -449,7 +461,7 @@
>
<template #header>
<div class="forward-head">
<span class="forward-set-title">{{$t('tgBot')}}</span>
<span class="forward-set-title">{{ $t('tgBot') }}</span>
<el-tooltip effect="dark" :content="$t('tgBotDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
@@ -457,13 +469,15 @@
</template>
<div class="forward-set-body">
<el-input :placeholder="$t('tgBotToken')" v-model="tgBotToken"></el-input>
<el-input-tag tag-type="warning" :placeholder="$t('toBotTokenDesc')" v-model="tgChatId" @add-tag="addChatTag" ></el-input-tag>
<el-input-tag tag-type="warning" :placeholder="$t('toBotTokenDesc')" v-model="tgChatId"
@add-tag="addChatTag"></el-input-tag>
</div>
<template #footer>
<div class="dialog-footer">
<el-switch v-model="tgBotStatus" :active-value="0" :inactive-value="1" :active-text="$t('enable')" :inactive-text="$t('disable')" />
<el-switch v-model="tgBotStatus" :active-value="0" :inactive-value="1" :active-text="$t('enable')"
:inactive-text="$t('disable')"/>
<el-button :loading="settingLoading" type="primary" @click="tgBotSave">
{{$t('save')}}
{{ $t('save') }}
</el-button>
</div>
</template>
@@ -474,20 +488,22 @@
>
<template #header>
<div class="forward-head">
<span class="forward-set-title">{{$t('otherEmail')}}</span>
<span class="forward-set-title">{{ $t('otherEmail') }}</span>
<el-tooltip effect="dark" :content="$t('otherEmailDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
</div>
</template>
<div class="forward-set-body">
<el-input-tag tag-type="warning" :placeholder="$t('otherEmailInputDesc')" v-model="forwardEmail" @add-tag="emailAddTag"></el-input-tag>
<el-input-tag tag-type="warning" :placeholder="$t('otherEmailInputDesc')" v-model="forwardEmail"
@add-tag="emailAddTag"></el-input-tag>
</div>
<template #footer>
<div class="dialog-footer">
<el-switch v-model="forwardStatus" :active-value="0" :inactive-value="1" :active-text="$t('enable')" :inactive-text="$t('disable')" />
<el-switch v-model="forwardStatus" :active-value="0" :inactive-value="1" :active-text="$t('enable')"
:inactive-text="$t('disable')"/>
<el-button :loading="settingLoading" type="primary" @click="forwardEmailSave">
{{$t('save')}}
{{ $t('save') }}
</el-button>
</div>
</template>
@@ -497,91 +513,97 @@
class="forward-dialog"
>
<template #header>
<div class="forward-head">
<span class="forward-set-title">{{$t('forwardingRules')}}</span>
<el-tooltip effect="dark" :content="$t('forwardingRulesDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
</div>
<div class="forward-head">
<span class="forward-set-title">{{ $t('forwardingRules') }}</span>
<el-tooltip effect="dark" :content="$t('forwardingRulesDesc')">
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
</el-tooltip>
</div>
</template>
<div class="forward-set-body">
<el-input-tag :placeholder="$t('ruleEmailsInputDesc')" tag-type="success" v-model="ruleEmail" @add-tag="ruleEmailAddTag" />
<el-input-tag :placeholder="$t('ruleEmailsInputDesc')" tag-type="success" v-model="ruleEmail"
@add-tag="ruleEmailAddTag"/>
</div>
<template #footer>
<div class="dialog-footer">
<el-radio-group v-model="ruleType">
<el-radio :value="0" >{{$t('forwardAll')}}</el-radio>
<el-radio :value="1" >{{$t('rules')}}</el-radio>
<el-radio :value="0">{{ $t('forwardAll') }}</el-radio>
<el-radio :value="1">{{ $t('rules') }}</el-radio>
</el-radio-group>
<el-button :loading="settingLoading" type="primary" @click="ruleEmailSave">
{{$t('save')}}
{{ $t('save') }}
</el-button>
</div>
</template>
</el-dialog>
<el-dialog class="resend-table" v-model="showResendList" :title="$t('resendTokenList')">
<el-table :data="resendList" >
<el-table-column :min-width="emailColumnWidth" property="key" :label="$t('domain')" :show-overflow-tooltip="true" />
<el-table-column :width="tokenColumnWidth" property="value" label="Token" fixed="right" :show-overflow-tooltip="true" />
<el-table :data="resendList">
<el-table-column :min-width="emailColumnWidth" property="key" :label="$t('domain')"
:show-overflow-tooltip="true"/>
<el-table-column :width="tokenColumnWidth" property="value" label="Token" fixed="right"
:show-overflow-tooltip="true"/>
</el-table>
</el-dialog>
<el-dialog v-model="regVerifyCountShow" :title="$t('rulesVerifyTitle',{count: regVerifyCount})" @closed="regVerifyCount = setting.regVerifyCount" >
<el-dialog v-model="regVerifyCountShow" :title="$t('rulesVerifyTitle',{count: regVerifyCount})"
@closed="regVerifyCount = setting.regVerifyCount">
<form>
<el-input-number type="text" v-model="regVerifyCount" :min="1" >
<el-input-number type="text" v-model="regVerifyCount" :min="1">
</el-input-number>
<el-button type="primary" :loading="settingLoading" @click="saveRegVerifyCount">{{$t('save')}}</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveRegVerifyCount">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog v-model="addVerifyCountShow" :title="$t('rulesVerifyTitle',{count: addVerifyCount})" @closed="addVerifyCount = setting.addVerifyCount">
<el-dialog v-model="addVerifyCountShow" :title="$t('rulesVerifyTitle',{count: addVerifyCount})"
@closed="addVerifyCount = setting.addVerifyCount">
<form>
<el-input-number type="text" v-model="addVerifyCount" :min="1"/>
<el-button type="primary" :loading="settingLoading" @click="saveAddVerifyCount">{{$t('save')}}</el-button>
<el-button type="primary" :loading="settingLoading" @click="saveAddVerifyCount">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog top="5vh" v-model="noticePopupShow" :title="$t('noticePopup')" class="notice-popup" @closed="resetNoticeForm">
<el-dialog top="5vh" v-model="noticePopupShow" :title="$t('noticePopup')" class="notice-popup"
@closed="resetNoticeForm">
<form>
<el-input v-model="noticeForm.noticeTitle" :placeholder="t('titleDesc')" />
<div class="notice-line-item" >
<el-select v-model="noticeForm.noticeType" >
<el-input v-model="noticeForm.noticeTitle" :placeholder="t('titleDesc')"/>
<div class="notice-line-item">
<el-select v-model="noticeForm.noticeType">
<template #prefix>
<span style="margin-right: 10px">{{$t('icon')}}</span>
<span style="margin-right: 10px">{{ $t('icon') }}</span>
</template>
<el-option key="none" label="None" value="none" />
<el-option key="primary" label="Primary" value="primary" />
<el-option key="success" label="Success" value="success" />
<el-option key="warning" label="Warning" value="warning" />
<el-option key="info" label="Info" value="info" />
<el-option key="none" label="None" value="none"/>
<el-option key="primary" label="Primary" value="primary"/>
<el-option key="success" label="Success" value="success"/>
<el-option key="warning" label="Warning" value="warning"/>
<el-option key="info" label="Info" value="info"/>
</el-select>
<el-select v-model="noticeForm.noticePosition" >
<el-select v-model="noticeForm.noticePosition">
<template #prefix>
<span style="margin-right: 10px">{{$t('position')}}</span>
<span style="margin-right: 10px">{{ $t('position') }}</span>
</template>
<el-option key="top-left" :label="t('topLeft')" value="top-left" />
<el-option key="top-right" :label="t('topRight')" value="top-right" />
<el-option key="bottom-left" :label="t('bottomLeft')" value="bottom-left" />
<el-option key="bottom-right" :label="t('bottomRight')" value="bottom-right" />
<el-option key="top-left" :label="t('topLeft')" value="top-left"/>
<el-option key="top-right" :label="t('topRight')" value="top-right"/>
<el-option key="bottom-left" :label="t('bottomLeft')" value="bottom-left"/>
<el-option key="bottom-right" :label="t('bottomRight')" value="bottom-right"/>
</el-select>
<el-input-number v-model="noticeForm.noticeWidth" >
<template #prefix >
{{$t('width')}}
<el-input-number v-model="noticeForm.noticeWidth">
<template #prefix>
{{ $t('width') }}
</template>
<template #suffix >
<template #suffix>
px
</template>
</el-input-number>
<el-input-number v-model="noticeForm.noticeOffset" >
<template #prefix >
{{$t('offset')}}
<el-input-number v-model="noticeForm.noticeOffset">
<template #prefix>
{{ $t('offset') }}
</template>
<template #suffix >
<template #suffix>
px
</template>
</el-input-number>
<el-input-number v-model="noticeForm.noticeDuration" >
<template #prefix >
{{$t('duration')}}
<el-input-number v-model="noticeForm.noticeDuration">
<template #prefix>
{{ $t('duration') }}
</template>
<template #suffix >
<template #suffix>
ms
</template>
</el-input-number>
@@ -597,13 +619,14 @@
</form>
<template #footer>
<div class="dialog-footer">
<el-switch v-model="noticeForm.notice" :active-value="0" :inactive-value="1" :active-text="$t('enable')" :inactive-text="$t('disable')" />
<el-switch v-model="noticeForm.notice" :active-value="0" :inactive-value="1" :active-text="$t('enable')"
:inactive-text="$t('disable')"/>
<div>
<el-button @click="previewNoticePopup">
{{$t('preview')}}
{{ $t('preview') }}
</el-button>
<el-button :loading="settingLoading" type="primary" @click="saveNoticePopup">
{{$t('save')}}
{{ $t('save') }}
</el-button>
</div>
</div>
@@ -627,14 +650,18 @@ import {debounce} from 'lodash-es'
import {isEmail} from "@/utils/verify-utils.js";
import loading from "@/components/loading/index.vue";
import {getTextWidth} from "@/utils/text.js";
import { fileToBase64 } from "@/utils/file-utils.js"
import { useI18n } from 'vue-i18n';
import {fileToBase64} from "@/utils/file-utils.js"
import {useI18n} from 'vue-i18n';
import axios from "axios";
defineOptions({
name: 'sys-setting'
})
const { t, locale } = useI18n();
const currentVersion = 'v1.7.0'
const hasUpdate = ref(false)
let getUpdateErrorCount = 1;
const {t, locale} = useI18n();
const firstLoading = ref(true)
const backgroundImage = ref('')
const localUpShow = ref(false)
@@ -711,6 +738,7 @@ const ruleType = ref(0)
const ruleEmail = ref([])
getSettings()
getUpdate()
function getSettings() {
settingQuery().then(settingData => {
@@ -772,8 +800,22 @@ const resendList = computed(() => {
return list;
});
function getUpdate() {
if (getUpdateErrorCount > 5 || !getUpdateErrorCount) return
axios.get('https://api.github.com/repos/eoao/cloud-mail/releases/latest').then(({data}) => {
hasUpdate.value = data.name !== currentVersion
getUpdateErrorCount = 0
}).catch(e => {
getUpdateErrorCount++
setTimeout(() => {
getUpdate()
}, 2000)
console.error('检查更新失败:', e)
})
}
function saveAddVerifyCount() {
if(!addVerifyCount.value) {
if (!addVerifyCount.value) {
addVerifyCount.value = 1
}
editSetting({addVerifyCount: addVerifyCount.value})
@@ -832,9 +874,9 @@ function resetNoticeForm() {
}
function saveNoticePopup() {
noticeForm.noticeOffset = noticeForm.noticeOffset || 0
noticeForm.noticeWidth = noticeForm.noticeWidth || 0
noticeForm.noticeDuration = noticeForm.noticeDuration || 0
noticeForm.noticeOffset = noticeForm.noticeOffset || 0
noticeForm.noticeWidth = noticeForm.noticeWidth || 0
noticeForm.noticeDuration = noticeForm.noticeDuration || 0
editSetting({...noticeForm})
}
@@ -899,9 +941,9 @@ function addChatTag(val) {
tgChatId.value.splice(tgChatId.value.length - 1, 1)
chatIds.forEach(id => {
if (!isNaN(Number(id))) {
tgChatId.value.push(id)
}
if (!isNaN(Number(id))) {
tgChatId.value.push(id)
}
})
}
@@ -934,7 +976,7 @@ function ruleEmailSave() {
function doOpacityChange() {
const form = {}
form.loginOpacity = loginOpacity.value
editSetting(form,true)
editSetting(form, true)
}
const opacityChange = debounce(doOpacityChange, 1000, {
@@ -986,7 +1028,7 @@ async function saveBackground() {
let image = ''
if (localUpShow.value) {
image = await fileToBase64(backgroundFile,true);
image = await fileToBase64(backgroundFile, true);
} else {
if (backgroundUrl.value && !backgroundUrl.value.startsWith('http')) {
ElMessage({
@@ -1127,7 +1169,8 @@ function editSetting(settingForm, refreshStatus = true) {
.settings-container {
height: 100%;
overflow: hidden;
background: #FAFCFF !important;
background: var(--extra-light-fill) !important;
.loading {
display: flex;
align-items: center;
@@ -1139,9 +1182,11 @@ function editSetting(settingForm, refreshStatus = true) {
.scroll {
width: 100%;
min-height: 100%;
:deep(.el-scrollbar__view) {
height: 100%;
}
.scroll-body {
min-height: 100%;
display: flex;
@@ -1169,7 +1214,7 @@ function editSetting(settingForm, refreshStatus = true) {
width: 250px;
height: 140px;
border-radius: 4px;
border: 1px solid #e4e7ed;
border: 1px solid var(--light-border);
@media (max-width: 500px) {
width: 150px;
height: 83px;
@@ -1187,7 +1232,7 @@ function editSetting(settingForm, refreshStatus = true) {
}
.settings-card {
background-color: #fff;
background-color: var(--el-bg-color);
border-radius: 8px;
border: 1px solid var(--el-border-color);
transition: all 300ms;
@@ -1195,7 +1240,6 @@ function editSetting(settingForm, refreshStatus = true) {
}
.card-title {
font-size: 15px;
font-weight: bold;
@@ -1215,6 +1259,7 @@ function editSetting(settingForm, refreshStatus = true) {
grid-template-columns: auto 1fr;
gap: 10px;
font-weight: normal;
> div:first-child {
display: flex;
align-items: center;
@@ -1232,14 +1277,13 @@ function editSetting(settingForm, refreshStatus = true) {
.title-icon.warning {
position: relative;
top: 2px;
color: gray;
cursor: pointer;
margin-left: 2px;
}
.warning {
margin-left: 4px;
color: gray;
color: grey;
cursor: pointer;
}
@@ -1268,9 +1312,11 @@ function editSetting(settingForm, refreshStatus = true) {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 15px;
> * {
width: 100%;
}
@media (max-width: 840px) {
grid-template-columns: 1fr 1fr;
}
@@ -1318,7 +1364,7 @@ function editSetting(settingForm, refreshStatus = true) {
}
:deep(.el-table__inner-wrapper:before) {
background: #fff;
background: var(--el-bg-color);
}
:deep(.cut-dialog.el-dialog) {
@@ -1340,17 +1386,18 @@ function editSetting(settingForm, refreshStatus = true) {
.forward-head {
display: flex;
align-items: center;
.forward-set-title {
top: 1px;
position: relative;
font-size: 16px;
font-weight: bold;
font-weight: bold;;
}
}
}
.error-image {
background: #f5f7fa;
background: var(--light-ill);
height: 100%;
display: flex;
align-items: center;
@@ -1363,6 +1410,7 @@ function editSetting(settingForm, refreshStatus = true) {
width: 100%;
display: flex;
justify-content: space-between;
.el-button {
width: fit-content;
}
@@ -1374,6 +1422,7 @@ function editSetting(settingForm, refreshStatus = true) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
span {
display: flex;
align-items: center;
@@ -1393,6 +1442,7 @@ function editSetting(settingForm, refreshStatus = true) {
display: flex;
flex-direction: column;
gap: 15px;
.el-switch {
align-self: end;
}
@@ -1451,6 +1501,7 @@ function editSetting(settingForm, refreshStatus = true) {
:deep(.el-button) {
padding: 0 10px;
font-weight: normal;
i {
font-size: 22px;
@@ -1469,6 +1520,7 @@ function editSetting(settingForm, refreshStatus = true) {
gap: 10px;
grid-template-columns: 1fr auto;
align-items: center;
span {
overflow: hidden;
white-space: nowrap;

View File

@@ -10,7 +10,8 @@
>
</el-input>
</div>
<el-select v-model="params.status" placeholder="Select" class="status-select" :style="`width: ${locale === 'en' ? 95 : 80 }px`">
<el-select v-model="params.status" placeholder="Select" class="status-select"
:style="`width: ${locale === 'en' ? 95 : 80 }px`">
<el-option :key="-1" :label="$t('all')" :value="-1"/>
<el-option :key="0" :label="$t('active')" :value="0"/>
<el-option :key="1" :label="$t('banned')" :value="1"/>
@@ -26,7 +27,8 @@
</div>
<el-scrollbar ref="scrollbarRef" class="scrollbar">
<div>
<div class="loading" :class="tableLoading ? 'loading-show' : 'loading-hide'">
<div class="loading" :class="tableLoading ? 'loading-show' : 'loading-hide'"
:style="first ? 'background: transparent' : ''">
<loading/>
</div>
<el-table
@@ -41,60 +43,78 @@
<el-table-column :width="expandWidth" type="expand">
<template #default="props">
<div class="details">
<div v-if="!sendNumShow"><span class="details-item-title">{{$t('tabSent')}}:</span>{{ props.row.sendEmailCount }}
<div v-if="!sendNumShow"><span
class="details-item-title">{{ $t('tabSent') }}:</span>{{ props.row.sendEmailCount }}
</div>
<div v-if="!accountNumShow"><span class="details-item-title">{{$t('tabMailboxes')}}:</span>{{
<div v-if="!accountNumShow"><span class="details-item-title">{{ $t('tabMailboxes') }}:</span>{{
props.row.accountCount
}}
</div>
<div v-if="!createTimeShow"><span class="details-item-title">{{$t('tabRegisteredAt')}}:</span>{{
<div v-if="!createTimeShow"><span class="details-item-title">{{ $t('tabRegisteredAt') }}:</span>{{
tzDayjs(props.row.createTime).format('YYYY-MM-DD HH:mm')
}}
</div>
<div v-if="!typeShow"><span class="details-item-title">{{$t('perm')}}:</span> {{ toRoleName(props.row.type) }}
<div v-if="!typeShow"><span class="details-item-title">{{ $t('perm') }}:</span>
{{ toRoleName(props.row.type) }}
</div>
<div v-if="!statusShow">
<span class="details-item-title">{{$t('tabStatus')}}:</span>
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">{{$t('deleted')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">{{$t('active')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">{{$t('banned')}}</el-tag>
<span class="details-item-title">{{ $t('tabStatus') }}:</span>
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">{{ $t('deleted') }}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">{{ $t('active') }}
</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">{{ $t('banned') }}
</el-tag>
</div>
<div><span class="details-item-title">{{$t('registrationIp')}}:</span>{{ props.row.createIp || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('recentIP')}}:</span>{{ props.row.activeIp || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('recentActivity')}}:</span>{{
<div><span class="details-item-title">{{ $t('registrationIp') }}:</span>{{
props.row.createIp || $t('unknown')
}}
</div>
<div><span class="details-item-title">{{ $t('recentIP') }}:</span>{{
props.row.activeIp || $t('unknown')
}}
</div>
<div><span class="details-item-title">{{ $t('recentActivity') }}:</span>{{
props.row.activeTime ? tzDayjs(props.row.activeTime).format('YYYY-MM-DD') : $t('unknown')
}}
</div>
<div><span class="details-item-title">{{$t('loginDevice')}}:</span>{{ props.row.device || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('loginSystem')}}:</span>{{ props.row.os || $t('unknown') }}</div>
<div><span class="details-item-title">{{$t('browserLogin')}}:</span>{{ props.row.browser || $t('unknown') }}</div>
<div><span
class="details-item-title">{{ $t('loginDevice') }}:</span>{{ props.row.device || $t('unknown') }}
</div>
<div><span class="details-item-title">{{ $t('loginSystem') }}:</span>{{ props.row.os || $t('unknown') }}
</div>
<div><span
class="details-item-title">{{ $t('browserLogin') }}:</span>{{ props.row.browser || $t('unknown') }}
</div>
<div>
<span class="details-item-title">{{$t('sendEmail')}}:</span>
<span class="details-item-title">{{ $t('sendEmail') }}:</span>
<span>{{ formatSendCount(props.row) }}</span>
<el-tag style="margin-left: 10px" v-if="props.row.sendAction.hasPerm" >
<el-tag style="margin-left: 10px" v-if="props.row.sendAction.hasPerm">
{{ formatSendType(props.row) }}
</el-tag>
<el-button size="small" style="margin-left: 10px"
v-if="props.row.sendAction.hasPerm && props.row.sendAction.sendCount"
@click="resetSendCount(props.row)" type="primary">{{$t('reset')}}
@click="resetSendCount(props.row)" type="primary">{{ $t('reset') }}
</el-button>
</div>
</div>
</template>
</el-table-column>
<el-table-column show-overflow-tooltip :tooltip-formatter="tableRowFormatter" :label="$t('tabEmailAddress')" :min-width="emailWidth">
<el-table-column show-overflow-tooltip :tooltip-formatter="tableRowFormatter" :label="$t('tabEmailAddress')"
:min-width="emailWidth">
<template #default="props">
<div class="email-row">{{ props.row.email }}</div>
</template>
</el-table-column>
<el-table-column :formatter="formatterReceive" label-class-name="receive" column-key="receive"
:filtered-value="filteredValue" :filters="filters" :width="receiveWidth" :label="$t('tabReceived')"
:filtered-value="filteredValue" :filters="filters" :width="receiveWidth"
:label="$t('tabReceived')"
prop="receiveEmailCount"/>
<el-table-column :formatter="formatterSend" label-class-name="send" column-key="send"
:filtered-value="filteredValue" :filters="filters" v-if="sendNumShow" :label="$t('tabSent')"
prop="sendEmailCount"/>
<el-table-column :formatter="formatterAccount" label-class-name="account" column-key="account"
:filtered-value="filteredValue" :filters="filters" v-if="accountNumShow" :label="$t('tabMailboxes')"
:filtered-value="filteredValue" :filters="filters" v-if="accountNumShow"
:label="$t('tabMailboxes')"
prop="accountCount"/>
<el-table-column v-if="createTimeShow" :label="$t('tabRegisteredAt')" min-width="160" prop="createTime">
<template #default="props">
@@ -103,9 +123,9 @@
</el-table-column>
<el-table-column v-if="statusShow" min-width="60px" :label="$t('tabStatus')" prop="status">
<template #default="props">
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">{{$t('deleted')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">{{$t('active')}}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">{{$t('banned')}}</el-tag>
<el-tag disable-transitions v-if="props.row.isDel === 1" type="info">{{ $t('deleted') }}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 0" type="primary">{{ $t('active') }}</el-tag>
<el-tag disable-transitions v-else-if="props.row.status === 1" type="danger">{{ $t('banned') }}</el-tag>
</template>
</el-table-column>
<el-table-column v-if="typeShow" :label="$t('tabRole')" min-width="140" prop="type">
@@ -118,16 +138,16 @@
<el-table-column :label="$t('tabSetting')" :width="settingWidth">
<template #default="props">
<el-dropdown trigger="click">
<el-button size="small" type="primary">{{$t('action')}}</el-button>
<el-button size="small" type="primary">{{ $t('action') }}</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openSetPwd(props.row)">{{$t('chgPwd')}}</el-dropdown-item>
<el-dropdown-item @click="openSetType(props.row)">{{$t('perm')}}</el-dropdown-item>
<el-dropdown-item @click="openSetPwd(props.row)">{{ $t('chgPwd') }}</el-dropdown-item>
<el-dropdown-item @click="openSetType(props.row)">{{ $t('perm') }}</el-dropdown-item>
<el-dropdown-item v-if="props.row.isDel !== 1" @click="setStatus(props.row)">
{{ setStatusName(props.row) }}
</el-dropdown-item>
<el-dropdown-item v-else @click="restore(props.row)">{{$t('restore')}}</el-dropdown-item>
<el-dropdown-item @click="delUser(props.row)">{{$t('delete')}}</el-dropdown-item>
<el-dropdown-item v-else @click="restore(props.row)">{{ $t('restore') }}</el-dropdown-item>
<el-dropdown-item @click="delUser(props.row)">{{ $t('delete') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -168,18 +188,18 @@
<el-input v-model="userForm.password" type="password" :placeholder="$t('newPassword')" autocomplete="off">
</el-input>
<el-button class="btn" type="primary" :loading="settingLoading" @click="updatePwd"
>{{$t('save')}}
>{{ $t('save') }}
</el-button>
</div>
</el-dialog>
<el-dialog class="dialog" v-model="setTypeShow" :title="$t('changePerm')" @closed="resetUserForm">
<div class="dialog-box">
<el-input disabled :model-value="$t('admin')" v-if="userForm.type === 0" />
<el-select v-else v-model="userForm.type" placeholder="Select" >
<el-input disabled :model-value="$t('admin')" v-if="userForm.type === 0"/>
<el-select v-else v-model="userForm.type" placeholder="Select">
<el-option v-for="item in roleList" :label="item.name" :value="item.roleId" :key="item.roleId"/>
</el-select>
<el-button :disabled="userForm.type === 0" class="btn" :loading="settingLoading" type="primary" @click="setType"
>{{$t('save')}}
>{{ $t('save') }}
</el-button>
</div>
</el-dialog>
@@ -201,7 +221,7 @@
:value="item"
/>
</el-select>
<div style="color: #333">
<div>
<span>{{ addForm.suffix }}</span>
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="20" height="20"/>
</div>
@@ -213,7 +233,7 @@
<el-option v-for="item in roleList" :label="item.name" :value="item.roleId" :key="item.roleId"/>
</el-select>
<el-button class="btn" type="primary" @click="submit" :loading="addLoading"
>{{$t('add')}}
>{{ $t('add') }}
</el-button>
</div>
</el-dialog>
@@ -239,13 +259,13 @@ import {useSettingStore} from "@/store/setting.js";
import {isEmail} from "@/utils/verify-utils.js";
import {useRoleStore} from "@/store/role.js";
import {useUserStore} from "@/store/user.js";
import { useI18n } from 'vue-i18n';
import {useI18n} from 'vue-i18n';
defineOptions({
name: 'user'
})
const { t, locale } = useI18n();
const {t, locale} = useI18n();
const roleStore = useRoleStore()
const userStore = useUserStore()
const settingStore = useSettingStore()
@@ -319,7 +339,7 @@ if (paramsStar) {
}
watch(() => params, () => {
localStorage.setItem('user-params',JSON.stringify(params))
localStorage.setItem('user-params', JSON.stringify(params))
}, {
deep: true
})
@@ -535,7 +555,7 @@ function toRoleName(type) {
function resetSendCount(user) {
ElMessageBox.confirm(t('reSendConfirm',{msg: user.email}), {
ElMessageBox.confirm(t('reSendConfirm', {msg: user.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
@@ -552,7 +572,7 @@ function resetSendCount(user) {
}
function delUser(user) {
ElMessageBox.confirm(t('delConfirm',{msg: user.email}), {
ElMessageBox.confirm(t('delConfirm', {msg: user.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
@@ -572,22 +592,22 @@ function restore(user) {
const type = ref(0)
ElMessageBox.confirm( null, {
ElMessageBox.confirm(null, {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
message: () => h('div', [
h('div', { class: 'mb-2' }, t('restoreConfirm', {msg: user.email})),
h('div', {class: 'mb-2'}, t('restoreConfirm', {msg: user.email})),
h(ElRadioGroup, {
modelValue: type.value,
'onUpdate:modelValue': (val) => (type.value = val),
}, [
h(ElRadio, { label: 'option1', value: 0 }, t('normalRestore')),
h(ElRadio, { label: 'option2', value: 1 }, t('allRestore')),
h(ElRadio, {label: 'option1', value: 0}, t('normalRestore')),
h(ElRadio, {label: 'option2', value: 1}, t('allRestore')),
])
]),
type: 'warning'
}).then(() => {
userRestore(user.userId,type.value).then(() => {
userRestore(user.userId, type.value).then(() => {
user.isDel = 0
ElMessage({
message: t('restoreSuccessMsg'),
@@ -601,7 +621,7 @@ function restore(user) {
function setStatus(user) {
if (user.status === 0) {
ElMessageBox.confirm(t('banRestore',{ msg: user.email }), {
ElMessageBox.confirm(t('banRestore', {msg: user.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
@@ -740,7 +760,9 @@ function getUserList(loading = true) {
scrollbarRef.value?.setScrollTop(0);
}).finally(() => {
tableLoading.value = false
first.value = false
setTimeout(() => {
first.value = false
}, 200)
})
}
@@ -778,12 +800,6 @@ function adjustWidth() {
word-break: break-all;
}
.el-table-filter__bottom {
button:last-child {
display: none;
}
}
.el-table-filter__content {
min-width: 0;
}
@@ -810,7 +826,7 @@ function adjustWidth() {
gap: 15px;
flex-wrap: wrap;
align-items: center;
box-shadow: inset 0 -1px 0 0 rgba(100, 121, 143, 0.12);
box-shadow: var(--header-actions-border);
font-size: 18px;
.search-input {
@@ -864,6 +880,7 @@ function adjustWidth() {
@media (max-width: 767px) {
padding-left: 35px;
}
.details-item-title {
white-space: pre;
color: #909399;
@@ -925,7 +942,7 @@ function adjustWidth() {
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.8);
background-color: var(--loadding-background);
left: 0;
z-index: 2;
top: 0;
@@ -956,13 +973,13 @@ function adjustWidth() {
:deep(.el-pagination .el-select) {
width: 100px;
background: #FFF;
background: var(--el-bg-color);
}
:deep(.el-input-group__append) {
padding: 0 !important;
padding-left: 8px !important;
background: #FFFFFF;
background: var(--el-bg-color);
}
:deep(.el-dialog) {
@@ -997,7 +1014,7 @@ function adjustWidth() {
}
:deep(.el-table__inner-wrapper:before) {
background: #fff;
background: var(--el-bg-color);
}
:deep(.el-message-box__container) {