mirror of
https://github.com/eoao/cloud-mail.git
synced 2026-05-06 13:41:43 +08:00
新增邮件和用户列表右键菜单
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"dexie": "^4.0.11",
|
||||
"echarts": "^5.6.0",
|
||||
"element-plus": "^2.9.11",
|
||||
"element-plus": "^2.13.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
|
||||
81
mail-vue/pnpm-lock.yaml
generated
81
mail-vue/pnpm-lock.yaml
generated
@@ -36,8 +36,8 @@ importers:
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0
|
||||
element-plus:
|
||||
specifier: ^2.9.11
|
||||
version: 2.11.1(vue@3.5.20)
|
||||
specifier: ^2.13.1
|
||||
version: 2.13.1(vue@3.5.20)
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
@@ -1085,8 +1085,8 @@ packages:
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@types/web-bluetooth@0.0.16':
|
||||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
@@ -1144,6 +1144,9 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/core@10.11.1':
|
||||
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
|
||||
|
||||
'@vueuse/core@12.8.2':
|
||||
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
|
||||
|
||||
@@ -1152,8 +1155,8 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/core@9.13.0':
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
|
||||
'@vueuse/metadata@10.11.1':
|
||||
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
|
||||
|
||||
'@vueuse/metadata@12.8.2':
|
||||
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
|
||||
@@ -1161,8 +1164,8 @@ packages:
|
||||
'@vueuse/metadata@14.1.0':
|
||||
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
|
||||
|
||||
'@vueuse/metadata@9.13.0':
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
|
||||
'@vueuse/shared@10.11.1':
|
||||
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
|
||||
|
||||
'@vueuse/shared@12.8.2':
|
||||
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
|
||||
@@ -1172,9 +1175,6 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/shared@9.13.0':
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||
|
||||
acorn@8.15.0:
|
||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -1349,6 +1349,9 @@ packages:
|
||||
dayjs@1.11.18:
|
||||
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
|
||||
|
||||
dayjs@1.11.19:
|
||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -1406,10 +1409,10 @@ packages:
|
||||
electron-to-chromium@1.5.211:
|
||||
resolution: {integrity: sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==}
|
||||
|
||||
element-plus@2.11.1:
|
||||
resolution: {integrity: sha512-weYFIniyNXTAe9vJZnmZpYzurh4TDbdKhBsJwhbzuo0SDZ8PLwHVll0qycJUxc6SLtH+7A9F7dvdDh5CnqeIVA==}
|
||||
element-plus@2.13.1:
|
||||
resolution: {integrity: sha512-eG4BDBGdAsUGN6URH1PixzZb0ngdapLivIk1meghS1uEueLvQ3aljSKrCt5x6sYb6mUk8eGtzTQFgsPmLavQcA==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
vue: ^3.3.0
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
@@ -1452,9 +1455,6 @@ packages:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
escape-string-regexp@5.0.0:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3522,7 +3522,7 @@ snapshots:
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.16': {}
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
@@ -3611,6 +3611,16 @@ snapshots:
|
||||
'@vueuse/shared': 14.1.0(vue@3.5.20)
|
||||
vue: 3.5.20
|
||||
|
||||
'@vueuse/core@10.11.1(vue@3.5.20)':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.20
|
||||
'@vueuse/metadata': 10.11.1
|
||||
'@vueuse/shared': 10.11.1(vue@3.5.20)
|
||||
vue-demi: 0.14.10(vue@3.5.20)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/core@12.8.2':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
@@ -3627,21 +3637,18 @@ snapshots:
|
||||
'@vueuse/shared': 14.1.0(vue@3.5.20)
|
||||
vue: 3.5.20
|
||||
|
||||
'@vueuse/core@9.13.0(vue@3.5.20)':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.16
|
||||
'@vueuse/metadata': 9.13.0
|
||||
'@vueuse/shared': 9.13.0(vue@3.5.20)
|
||||
vue-demi: 0.14.10(vue@3.5.20)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
'@vueuse/metadata@10.11.1': {}
|
||||
|
||||
'@vueuse/metadata@12.8.2': {}
|
||||
|
||||
'@vueuse/metadata@14.1.0': {}
|
||||
|
||||
'@vueuse/metadata@9.13.0': {}
|
||||
'@vueuse/shared@10.11.1(vue@3.5.20)':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.5.20)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@12.8.2':
|
||||
dependencies:
|
||||
@@ -3653,13 +3660,6 @@ snapshots:
|
||||
dependencies:
|
||||
vue: 3.5.20
|
||||
|
||||
'@vueuse/shared@9.13.0(vue@3.5.20)':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.5.20)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
ajv@8.17.1:
|
||||
@@ -3859,6 +3859,8 @@ snapshots:
|
||||
|
||||
dayjs@1.11.18: {}
|
||||
|
||||
dayjs@1.11.19: {}
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -3907,7 +3909,7 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.5.211: {}
|
||||
|
||||
element-plus@2.11.1(vue@3.5.20):
|
||||
element-plus@2.13.1(vue@3.5.20):
|
||||
dependencies:
|
||||
'@ctrl/tinycolor': 3.6.1
|
||||
'@element-plus/icons-vue': 2.3.2(vue@3.5.20)
|
||||
@@ -3915,10 +3917,9 @@ snapshots:
|
||||
'@popperjs/core': '@sxzz/popperjs-es@2.11.7'
|
||||
'@types/lodash': 4.17.20
|
||||
'@types/lodash-es': 4.17.12
|
||||
'@vueuse/core': 9.13.0(vue@3.5.20)
|
||||
'@vueuse/core': 10.11.1(vue@3.5.20)
|
||||
async-validator: 4.2.5
|
||||
dayjs: 1.11.18
|
||||
escape-html: 1.0.3
|
||||
dayjs: 1.11.19
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
|
||||
@@ -4044,8 +4045,6 @@ snapshots:
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
|
||||
estree-walker@1.0.1: {}
|
||||
|
||||
@@ -37,12 +37,14 @@
|
||||
v-if="!loading && emailList.length > 0"
|
||||
:key="keyCount"
|
||||
>
|
||||
<template #default="{ data: item, index }">
|
||||
<template #default="{ data: item, index }" >
|
||||
<div :class="'email-row ' + props.type"
|
||||
:data-checked="item.checked"
|
||||
@click="jumpDetails(item)"
|
||||
v-if="!item.expand"
|
||||
:key="item.emailId"
|
||||
@contextmenu="handleContextmenu($event, item)"
|
||||
:style="item.rightChecked ? 'background: #FDF6EC' : ''"
|
||||
>
|
||||
<el-checkbox :class=" props.type === 'all-email' ? 'all-email-checkbox' : 'checkbox'"
|
||||
v-model="item.checked" @click.stop></el-checkbox>
|
||||
@@ -133,16 +135,89 @@
|
||||
:showUserInfo="showUserInfo"
|
||||
:type="type"/>
|
||||
<div class="empty" v-if="noLoading && emailList.length === 0 && !loading">
|
||||
<el-empty :image-size="isMobile ? 120 : 0" :description="$t('noMessagesFound')"/>
|
||||
<el-empty :image-size="isMobile ? 120 : null" :description="$t('noMessagesFound')"/>
|
||||
</div>
|
||||
</div>
|
||||
<el-dropdown
|
||||
ref="dropdownRef"
|
||||
@visible-change="visibleChange"
|
||||
:virtual-ref="triggerRef"
|
||||
:show-arrow="false"
|
||||
:popper-options="{
|
||||
modifiers: [{ name: 'offset', options: { offset: [0, 0] } }],
|
||||
}"
|
||||
virtual-triggering
|
||||
trigger="contextmenu"
|
||||
placement="bottom-start"
|
||||
>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="props.type === 'email'" @click="emailRead()" >
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<Icon icon="fluent:mail-read-20-regular" width="20" height="20" />
|
||||
<span>{{t('markAsRead')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="props.type === 'email'" @click="openReply(rightClickEmail)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<Icon icon="la:reply" width="20" height="20" />
|
||||
<span>{{t('reply')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="['email','send', 'star'].includes(props.type)" @click="starChange(rightClickEmail)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<Icon icon="solar:star-line-duotone" width="19" height="19"/>
|
||||
<span>{{t('star')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="props.type === 'all-email'" @click="handleSearch('user', rightClickEmail.userEmail)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<Icon icon="iconoir:search" width="20" height="20" />
|
||||
<span>{{t('searchUser')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="props.type === 'all-email' " @click="handleSearch('account', rightClickEmail.toEmail)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<Icon icon="iconoir:search" width="20" height="20" />
|
||||
<span>{{t('searchEmail')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="props.type === 'all-email' " @click="handleSearch('name', rightClickEmail.name)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<Icon icon="iconoir:search" width="20" height="20" />
|
||||
<span>{{t('searchSender')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="rightDelete(rightClickEmail.emailId)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<Icon icon="uiw:delete" width="16" height="20" style="margin-left: 1px;margin-right: 3px" />
|
||||
<span>{{t('delete')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {Icon} from "@iconify/vue";
|
||||
import skeletonBlock from "@/components/email-scroll/skeleton/index.vue"
|
||||
import {computed, onActivated, reactive, ref, watch, nextTick} from "vue";
|
||||
import {computed, onActivated, reactive, ref, watch, nextTick, onMounted, onUnmounted } from "vue";
|
||||
import {useEmailStore} from "@/store/email.js";
|
||||
import {useUiStore} from "@/store/ui.js";
|
||||
import {useSettingStore} from "@/store/setting.js";
|
||||
@@ -191,7 +266,7 @@ const props = defineProps({
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: 'email'
|
||||
},
|
||||
showFirstLoading: {
|
||||
type: Boolean,
|
||||
@@ -203,7 +278,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['jump', 'refresh-before', 'delete-draft'])
|
||||
const emit = defineEmits(['jump', 'refresh-before', 'delete-draft', 'right-search'])
|
||||
const {t} = useI18n()
|
||||
const settingStore = useSettingStore()
|
||||
const uiStore = useUiStore();
|
||||
@@ -225,7 +300,26 @@ let reqLock = false
|
||||
let isMobile = ref(innerWidth < 1367)
|
||||
let skeletonRows = 0
|
||||
const timePaddingRight = ref('');
|
||||
const keyCount = ref(0)
|
||||
const keyCount = ref(0);
|
||||
const dropdownRef = ref(null);
|
||||
const dropdownCloseLock = ref(false);
|
||||
const dropdownShow = ref(false);
|
||||
const rightClickEmail = ref({});
|
||||
const checkedEmailCount = ref(0);
|
||||
let timer = null
|
||||
const position = ref(
|
||||
DOMRect.fromRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
)
|
||||
|
||||
const triggerRef = ref({
|
||||
getBoundingClientRect() {
|
||||
return position.value;
|
||||
}
|
||||
})
|
||||
|
||||
const queryParam = reactive({
|
||||
size: 50
|
||||
});
|
||||
@@ -234,6 +328,7 @@ defineExpose({
|
||||
refreshList,
|
||||
deleteEmail,
|
||||
addItem,
|
||||
handleList,
|
||||
emailList,
|
||||
firstLoad,
|
||||
latestEmail,
|
||||
@@ -248,6 +343,18 @@ onActivated(() => {
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
emailList.forEach(email => {
|
||||
email.formatCreateTime = fromNow(email.createTime);
|
||||
})
|
||||
}, 1000 * 60);
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
|
||||
getEmailList()
|
||||
|
||||
window.onresize = () => {
|
||||
@@ -323,6 +430,7 @@ watch(() => arrivedState.bottom, (isBottom) => {
|
||||
watch(
|
||||
() => emailList.map(item => item.checked),
|
||||
() => {
|
||||
checkedEmailCount.value = emailList.length
|
||||
if (emailList.length > 0) {
|
||||
updateCheckStatus();
|
||||
}
|
||||
@@ -353,6 +461,50 @@ watch(() => emailStore.addStarEmailId, () => {
|
||||
})
|
||||
})
|
||||
|
||||
window.addEventListener('wheel', (event) => {
|
||||
if (dropdownShow.value) {
|
||||
dropdownRef.value.handleClose();
|
||||
}
|
||||
})
|
||||
|
||||
function openReply(email) {
|
||||
uiStore.writerRef.openReply(email)
|
||||
}
|
||||
|
||||
function visibleChange(e) {
|
||||
dropdownShow.value = e;
|
||||
dropdownCloseLock.value = true;
|
||||
setTimeout(() => {
|
||||
dropdownCloseLock.value = false;
|
||||
},1500)
|
||||
|
||||
if (!e && rightClickEmail.value.rightChecked) {
|
||||
rightClickEmail.value.rightChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleContextmenu = (event, email) => {
|
||||
|
||||
if (props.type === 'draft') {
|
||||
return
|
||||
}
|
||||
|
||||
if (rightClickEmail.value.rightChecked) {
|
||||
rightClickEmail.value.rightChecked = false
|
||||
}
|
||||
|
||||
const { clientX, clientY } = event
|
||||
position.value = DOMRect.fromRect({
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
})
|
||||
event.preventDefault();
|
||||
dropdownRef.value?.handleOpen();
|
||||
|
||||
rightClickEmail.value = email;
|
||||
rightClickEmail.value.rightChecked = true
|
||||
}
|
||||
|
||||
function updateHasScrollbar() {
|
||||
nextTick(() => {
|
||||
const doc = document.querySelector('.virtual');
|
||||
@@ -407,7 +559,6 @@ function cleanSpace(text) {
|
||||
.trim();
|
||||
}
|
||||
|
||||
|
||||
function starChange(email) {
|
||||
|
||||
if (!email.isStar) {
|
||||
@@ -451,7 +602,45 @@ const handleRead = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
function emailRead() {
|
||||
const emailIds = getSelectedMailsIds();
|
||||
props.emailRead(emailIds)
|
||||
}
|
||||
|
||||
function rightDelete(emailId) {
|
||||
|
||||
if (props.type === 'all-email') {
|
||||
ElMessageBox.confirm(t('delOneEmailConfirm'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
props.emailDelete([emailId]).then(() => {
|
||||
ElMessage({
|
||||
message: t('delSuccessMsg'),
|
||||
type: 'success',
|
||||
plain: true
|
||||
})
|
||||
emailStore.deleteIds = [emailId];
|
||||
})
|
||||
})
|
||||
return;
|
||||
}
|
||||
props.emailDelete([emailId]).then(() => {
|
||||
ElMessage({
|
||||
message: t('delSuccessMsg'),
|
||||
type: 'success',
|
||||
plain: true
|
||||
})
|
||||
emailStore.deleteIds = [emailId];
|
||||
})
|
||||
}
|
||||
|
||||
function handleSearch(type, value) {
|
||||
emit('right-search', type, value);
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
ElMessageBox.confirm(t('delEmailsConfirm'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
@@ -551,13 +740,24 @@ function getSelectedDraftsIds() {
|
||||
|
||||
function updateCheckStatus() {
|
||||
const checkedCount = emailList.filter(item => item.checked).length;
|
||||
checkedEmailCount.value = checkedCount;
|
||||
checkAll.value = checkedCount === emailList.length;
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < emailList.length;
|
||||
}
|
||||
|
||||
function jumpDetails(email) {
|
||||
const sel = window.getSelection();
|
||||
if (sel && !sel.isCollapsed) return;
|
||||
|
||||
if (dropdownShow.value) {
|
||||
dropdownRef.value.handleClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dropdownCloseLock.value) {
|
||||
const sel = window.getSelection();
|
||||
if (sel.toString().trim()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
emit('jump', email)
|
||||
}
|
||||
|
||||
@@ -1054,6 +1254,26 @@ function loadData() {
|
||||
bottom: 1px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.right-dropdown-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item:last-child) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item:first-child) {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-dropdown-menu__item) {
|
||||
padding-right: 14px;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.unread {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
|
||||
@@ -12,10 +12,10 @@ const en = {
|
||||
SystemSettings: 'System Settings',
|
||||
noMoreData: 'No more data',
|
||||
noMessagesFound: 'No messages found',
|
||||
addAccount: 'Add Account',
|
||||
addAccount: 'Add Email Address',
|
||||
emailAccount: 'Email',
|
||||
account: 'Account',
|
||||
userAccount: 'User Accounts',
|
||||
account: 'Address',
|
||||
userAccount: 'User Email Address',
|
||||
deleteUser: 'Delete Account',
|
||||
deleteUserBtn: 'Delete',
|
||||
changePassword: 'Change Password',
|
||||
@@ -35,7 +35,7 @@ const en = {
|
||||
delAccountMsg: 'This will permanently delete your account and data. It cannot be reactivated',
|
||||
totalReceived: 'Total Received',
|
||||
totalSent: 'Total Sent',
|
||||
totalMailboxes: 'Total Accounts',
|
||||
totalMailboxes: 'Total Email Addresses',
|
||||
totalUsers: 'Total Users',
|
||||
deleted: 'Deleted',
|
||||
selectDeleted: 'Deleted',
|
||||
@@ -52,7 +52,7 @@ const en = {
|
||||
tabEmailAddress: 'Email',
|
||||
tabReceived: 'Received',
|
||||
tabSent: 'Sent',
|
||||
tabMailboxes: 'Accounts',
|
||||
tabMailboxes: 'Addresses',
|
||||
tabRegisteredAt: 'Registered at',
|
||||
tabStatus: 'Status',
|
||||
tabRole: 'Role',
|
||||
@@ -73,9 +73,9 @@ const en = {
|
||||
unauthorized: 'Unauthorized',
|
||||
unlimited: 'Unlimited',
|
||||
sendCount: 'Send email : ',
|
||||
accountCount: 'Add account : ',
|
||||
accountCount: 'Add Address : ',
|
||||
action: 'Action',
|
||||
chgPwd: 'Pwd',
|
||||
chgPwd: 'Password',
|
||||
perm: 'Role',
|
||||
btnBan: 'Ban',
|
||||
admin: 'Admin',
|
||||
@@ -136,8 +136,8 @@ const en = {
|
||||
websiteSetting: 'Website',
|
||||
websiteReg: 'Sign Up',
|
||||
loginDomain: 'Sign-In Box Domain',
|
||||
multipleEmail: 'Multiple Accounts',
|
||||
multipleEmailDesc: 'Enable this feature to allow users to add multiple accounts',
|
||||
multipleEmail: 'Multiple Email Address',
|
||||
multipleEmailDesc: 'Enable this feature to allow users to add multiple email',
|
||||
customization: 'Customization',
|
||||
websiteTitle: 'Title',
|
||||
loginBoxOpacity: 'Login Box Opacity',
|
||||
@@ -161,7 +161,7 @@ const en = {
|
||||
rules: 'Rules',
|
||||
turnstileSetting: 'Turnstile',
|
||||
signUpVerification: 'Sign Up Verification',
|
||||
addEmailVerification: 'Add Account Verification',
|
||||
addEmailVerification: 'Add Email Verification',
|
||||
about: 'About',
|
||||
version: 'Version',
|
||||
community: 'Community',
|
||||
@@ -207,6 +207,7 @@ const en = {
|
||||
emptyCountMsg: 'Available count cannot be empty',
|
||||
addSuccessMsg: 'Addition successful',
|
||||
delConfirm: 'Confirm deleting {msg}?',
|
||||
delUsersConfirm: 'Confirm deletion of selected users?',
|
||||
emptyRoleNameMsg: 'Role name cannot be empty',
|
||||
saveSuccessMsg: 'Saved successfully',
|
||||
changeRoleTitle: 'Change Role',
|
||||
@@ -237,7 +238,8 @@ const en = {
|
||||
sendSuccessMsg: 'Send successful',
|
||||
sendFailMsg: 'Send failed',
|
||||
saveDraftConfirm: 'Save draft?',
|
||||
delEmailsConfirm: 'Confirm batch delete these emails?',
|
||||
delEmailsConfirm: 'Confirm deletion of selected emails?',
|
||||
delOneEmailConfirm: 'Confirm deletion of this email?',
|
||||
sending: 'Sending email...',
|
||||
sendingErrorMsg: 'Sending in progress',
|
||||
networkErrorMsg: 'Network error. Check your internet',
|
||||
@@ -312,7 +314,18 @@ const en = {
|
||||
mustNotContainDesc: 'Separate with commas',
|
||||
setSuccess: 'Settings saved successfully',
|
||||
details: 'Details',
|
||||
userDetails: 'User Details'
|
||||
userDetails: 'User Details',
|
||||
markAsRead: 'Mark as Read',
|
||||
star: 'Star',
|
||||
setRole: 'Set Role',
|
||||
adminDeleteUser: 'Delete User',
|
||||
banUser: 'Ban User',
|
||||
enableUser: 'Enable User',
|
||||
restoreUser: 'Restore User',
|
||||
searchUser: 'Search by user',
|
||||
searchEmail: 'Search by Email',
|
||||
searchSender: 'Search by Sender',
|
||||
userEmail: 'Email Address'
|
||||
}
|
||||
|
||||
export default en
|
||||
|
||||
@@ -207,6 +207,7 @@ const zh = {
|
||||
emptyCountMsg: '可用次数不能为空',
|
||||
addSuccessMsg: '添加成功',
|
||||
delConfirm: '确认删除{msg}吗?',
|
||||
delUsersConfirm: '确定删除选中的用户吗?',
|
||||
emptyRoleNameMsg: '身份名不能为空',
|
||||
saveSuccessMsg: '保存成功',
|
||||
changeRoleTitle: '修改身份',
|
||||
@@ -237,7 +238,8 @@ const zh = {
|
||||
sendSuccessMsg: '发送成功',
|
||||
sendFailMsg: '发送失败',
|
||||
saveDraftConfirm: '是否保存草稿?',
|
||||
delEmailsConfirm: '确认批量删除这些邮件吗?',
|
||||
delEmailsConfirm: '确认删除选中的邮件吗?',
|
||||
delOneEmailConfirm: '确认删除这个邮件吗?',
|
||||
sending: '邮件正在发送中',
|
||||
sendingErrorMsg: '邮件正在发送中',
|
||||
networkErrorMsg: '网络错误,请检查网络连接',
|
||||
@@ -312,6 +314,17 @@ const zh = {
|
||||
mustNotContainDesc: '输入多个值用,分开',
|
||||
setSuccess: '设置成功',
|
||||
details: '详情',
|
||||
userDetails: '用户详情'
|
||||
userDetails: '用户详情',
|
||||
markAsRead: '标为已读',
|
||||
star: '星标',
|
||||
setRole: '设置权限',
|
||||
adminDeleteUser: '删除用户',
|
||||
banUser: '封禁用户',
|
||||
enableUser: '启动用户',
|
||||
restoreUser: '恢复用户',
|
||||
searchUser: '搜索用户',
|
||||
searchEmail: '搜索邮箱',
|
||||
searchSender: '搜索发件人',
|
||||
userEmail: '用户邮箱'
|
||||
}
|
||||
export default zh
|
||||
|
||||
@@ -13,7 +13,7 @@ export function emailLatest(emailId, accountId, allReceive) {
|
||||
}
|
||||
|
||||
export function emailRead(emailIds) {
|
||||
return http.put('/email/read', {emailIds}, {noMsg: true})
|
||||
return http.put('/email/read', {emailIds})
|
||||
}
|
||||
|
||||
export function emailSend(form,progress) {
|
||||
|
||||
@@ -56,6 +56,14 @@ export function fromNow(date) {
|
||||
|
||||
}
|
||||
|
||||
export function updateNow(date) {
|
||||
if (isToday) {
|
||||
if (diffSeconds < 60) return `Just now`;
|
||||
if (diffMinutes < 60) return `${diffMinutes} min ago`;
|
||||
if (diffHours < 2) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
||||
return d.format('hh:mm A');
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDetailDate(time) {
|
||||
const d = dayjs.utc(time).tz(timeZone);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
:item-height="65"
|
||||
@jump="jumpContent"
|
||||
@refresh-before="refreshBefore"
|
||||
@right-search="rightSearch"
|
||||
:type="'all-email'"
|
||||
|
||||
>
|
||||
@@ -227,6 +228,12 @@ function batchDelete() {
|
||||
})
|
||||
}
|
||||
|
||||
function rightSearch(type, value) {
|
||||
params.searchType = type;
|
||||
searchValue.value = value;
|
||||
search();
|
||||
}
|
||||
|
||||
function refreshBefore() {
|
||||
searchValue.value = null
|
||||
params.timeSort = 0
|
||||
@@ -337,7 +344,7 @@ async function latest() {
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (e.code === 401) {
|
||||
if (e.code === 401 || e.code === 403) {
|
||||
settingStore.settings.autoRefresh = AutoRefreshEnum.DISABLED;
|
||||
}
|
||||
console.error(e)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<emailScroll ref="scroll"
|
||||
:allow-star="false"
|
||||
:cancel-success="cancelStar"
|
||||
:getEmailList="getEmailList"
|
||||
:emailDelete="emailDelete"
|
||||
:star-add="starAdd"
|
||||
@@ -15,7 +14,7 @@
|
||||
:type="'draft'"
|
||||
>
|
||||
<template #name="props">
|
||||
<span class="send-email">{{ props.email.receiveEmail.join(',') || '(' + $t('noRecipient') + ')' }}</span>
|
||||
<span class="send-email">{{ props.email.receiveEmail?.join(',') || '(' + $t('noRecipient') + ')' }}</span>
|
||||
</template>
|
||||
<template #subject="props">
|
||||
{{ props.email.subject || '(' + $t('noSubject') + ')' }}
|
||||
@@ -27,8 +26,7 @@
|
||||
import emailScroll from "@/components/email-scroll/index.vue"
|
||||
import {emailDelete} from "@/request/email.js";
|
||||
import {starAdd, starCancel} from "@/request/star.js";
|
||||
import {useEmailStore} from "@/store/email.js";
|
||||
import {defineOptions, onMounted, ref, watch, toRaw} from "vue";
|
||||
import {defineOptions, ref, watch, toRaw} from "vue";
|
||||
import {useUiStore} from "@/store/ui.js";
|
||||
import {userDraftStore} from "@/store/draft.js";
|
||||
import db from "@/db/db.js"
|
||||
@@ -40,7 +38,6 @@ defineOptions({
|
||||
const draftStore = userDraftStore();
|
||||
const uiStore = useUiStore();
|
||||
const scroll = ref({})
|
||||
const emailStore = useEmailStore();
|
||||
|
||||
watch(() => draftStore.setDraft, async () => {
|
||||
|
||||
@@ -68,6 +65,7 @@ watch(() => draftStore.setDraft, async () => {
|
||||
watch(() => draftStore.refreshList, async () => {
|
||||
const {list} = await getEmailList();
|
||||
scroll.value.emailList.length = 0
|
||||
scroll.value.handleList(list);
|
||||
scroll.value.emailList.push(...list)
|
||||
})
|
||||
|
||||
@@ -90,15 +88,6 @@ async function jumpContent(email) {
|
||||
uiStore.writerRef.openDraft(email);
|
||||
}
|
||||
|
||||
function cancelStar(email) {
|
||||
emailStore.cancelStarEmailId = email.emailId
|
||||
scroll.value.deleteEmail([email.emailId])
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
emailStore.starScroll = scroll
|
||||
})
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.send-email {
|
||||
|
||||
@@ -133,7 +133,7 @@ async function latest() {
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code === 401) {
|
||||
if (e.code === 401 || e.code === 403) {
|
||||
settingStore.settings.autoRefresh = AutoRefreshEnum.DISABLED;
|
||||
}
|
||||
console.error(e)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
:star-cancel="starCancel"
|
||||
@jump="jumpContent"
|
||||
:time-sort="params.timeSort"
|
||||
:type="'send'"
|
||||
>
|
||||
<template #first>
|
||||
<Icon class="icon" @click="changeTimeSort" icon="material-symbols-light:timer-arrow-down-outline"
|
||||
@@ -29,7 +30,6 @@ import {starAdd, starCancel} from "@/request/star.js";
|
||||
import {defineOptions, onMounted, reactive, ref, watch} from "vue";
|
||||
import router from "@/router/index.js";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {AccountAllReceiveEnum} from "@/enums/account-enum.js";
|
||||
|
||||
defineOptions({
|
||||
name: 'send'
|
||||
|
||||
@@ -754,7 +754,7 @@ defineOptions({
|
||||
name: 'sys-setting'
|
||||
})
|
||||
|
||||
const currentVersion = 'v2.7.0'
|
||||
const currentVersion = 'v2.8.0'
|
||||
const hasUpdate = ref(false)
|
||||
let getUpdateErrorCount = 1;
|
||||
const {t, locale} = useI18n();
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
:preserve-expanded-content="preserveExpanded"
|
||||
style="width: 100%;"
|
||||
ref="tableRef"
|
||||
@cell-contextmenu="handleContextmenu"
|
||||
:cell-class-name="cellClassName"
|
||||
>
|
||||
<el-table-column :width="expandWidth" type="selection" :selectable="row => row.type !== 0" />
|
||||
<el-table-column show-overflow-tooltip :tooltip-formatter="tableRowFormatter" :label="$t('tabEmailAddress')"
|
||||
@@ -81,20 +83,21 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('tabSetting')" :width="settingWidth">
|
||||
<template #default="props">
|
||||
<el-dropdown>
|
||||
<el-button size="small" type="primary" v-if="(props.row.type === 0 && userStore.user.type !== 0)" >{{ $t('action') }}</el-button>
|
||||
<el-dropdown v-else >
|
||||
<el-button size="small" type="primary">{{ $t('action') }}</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openSetPwd(props.row)" v-if="(props.row.type !== 0 || userStore.user.type === 0)">{{ $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>
|
||||
<template v-if="props.row.type !== 0">
|
||||
<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>
|
||||
</template>
|
||||
<el-dropdown-item @click="openAccountList(props.row.userId)" v-if="(props.row.type !== 0 || userStore.user.type === 0)" >{{ $t('account') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click="openDetails(props.row)" v-if="(props.row.type !== 0 || userStore.user.type === 0)" >{{ $t('details') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click="openAccountList(props.row.userId)" >{{ $t('account') }}</el-dropdown-item>
|
||||
<el-dropdown-item @click="openDetails(props.row)" >{{ $t('details') }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -286,6 +289,78 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dropdown
|
||||
:show-timeout="0"
|
||||
:hide-timeout="0"
|
||||
ref="dropdownRef"
|
||||
@visible-change="visibleChange"
|
||||
:virtual-ref="triggerRef"
|
||||
:show-arrow="false"
|
||||
:popper-options="{
|
||||
modifiers: [{ name: 'offset', options: { offset: [0, 0] } }],
|
||||
}"
|
||||
virtual-triggering
|
||||
trigger="contextmenu"
|
||||
placement="bottom-start"
|
||||
>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openSetPwd(rightClickUser)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<icon icon="fluent:fingerprint-20-filled" width="22" height="22" />
|
||||
<span>{{t('changePassword')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openSetType(rightClickUser)">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item">
|
||||
<icon icon="fluent:lock-closed-16-regular" width="21" height="21" />
|
||||
<span>{{ t('setRole') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="rightClickUser.type !== 0">
|
||||
<template #default>
|
||||
<div class="right-dropdown-item" v-if="rightClickUser.isDel !== 1" @click="setStatus(rightClickUser)" >
|
||||
<Icon icon="ion:reload" v-if="rightClickUser.status" style="margin-left: 1px;margin-right: 1px" width="19" height="19" />
|
||||
<Icon icon="ion:ban-outline" v-else style="margin-left: 1px;margin-right: 1px" width="19" height="19" />
|
||||
<span>{{ setRightStatusName(rightClickUser) }}</span>
|
||||
</div>
|
||||
<div class="right-dropdown-item" v-else @click="restore(rightClickUser)">
|
||||
<Icon icon="ion:reload" style="margin-left: 1px;margin-right: 1px" width="19" height="19" />
|
||||
<span>{{ t('restoreUser') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openAccountList(rightClickUser.userId)" >
|
||||
<template #default>
|
||||
<div class="right-dropdown-item" >
|
||||
<Icon icon="hugeicons:mailbox-01" width="20" height="20" />
|
||||
<span>{{ t('userEmail') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openDetails(rightClickUser)" >
|
||||
<template #default>
|
||||
<div class="right-dropdown-item" >
|
||||
<Icon icon="si:user-alt-2-line" width="20" height="20" />
|
||||
<span>{{ t('userDetails') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="rightClickUser.type !== 0" @click="delOneUser(rightClickUser)" >
|
||||
<template #default>
|
||||
<div class="right-dropdown-item" >
|
||||
<Icon icon="uiw:delete" width="18" height="18" style="margin-left: 1px;margin-right: 1px" />
|
||||
<span>{{ t('adminDeleteUser') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -344,7 +419,21 @@ const total = ref(0)
|
||||
const first = ref(true)
|
||||
const scrollbarRef = ref(null)
|
||||
const accountLoading = ref(false)
|
||||
const dropdownRef = ref(null);
|
||||
const dropdownShow = ref(false);
|
||||
const rightClickUser = ref({});
|
||||
const position = ref(
|
||||
DOMRect.fromRect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
)
|
||||
|
||||
const triggerRef = ref({
|
||||
getBoundingClientRect() {
|
||||
return position.value;
|
||||
}
|
||||
})
|
||||
const domainList = settingStore.domainList
|
||||
|
||||
const addForm = reactive({
|
||||
@@ -425,6 +514,43 @@ const filterItem = reactive({
|
||||
receive: ['normal', 'del']
|
||||
})
|
||||
|
||||
window.addEventListener('wheel', (event) => {
|
||||
if (dropdownShow.value) {
|
||||
dropdownRef.value.handleClose();
|
||||
}
|
||||
})
|
||||
|
||||
function visibleChange(e) {
|
||||
dropdownShow.value = e;
|
||||
if (!e) {
|
||||
rightClickUser.value.checkedClass = '';
|
||||
}
|
||||
}
|
||||
|
||||
function cellClassName({ row }) {
|
||||
return row.checkedClass;
|
||||
}
|
||||
|
||||
const handleContextmenu = (row, column, cell, event) => {
|
||||
|
||||
if (row.type === 0 && userStore.user.type !== 0) {
|
||||
return
|
||||
}
|
||||
|
||||
rightClickUser.value.checkedClass = '';
|
||||
|
||||
const { clientX, clientY } = event
|
||||
position.value = DOMRect.fromRect({
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
})
|
||||
event.preventDefault()
|
||||
dropdownRef.value?.handleOpen()
|
||||
|
||||
row.checkedClass = 'checked-row';
|
||||
rightClickUser.value = row;
|
||||
}
|
||||
|
||||
function deleteAccount(account) {
|
||||
ElMessageBox.confirm(t('delConfirm', {msg: account.email}), {
|
||||
confirmButtonText: t('confirm'),
|
||||
@@ -540,6 +666,12 @@ function setStatusName(user) {
|
||||
if (user.status === 1) return t('enable')
|
||||
}
|
||||
|
||||
function setRightStatusName(user) {
|
||||
if (user.isDel === 1) return t('adminDeleteUser')
|
||||
if (user.status === 0) return t('banUser')
|
||||
if (user.status === 1) return t('enableUser')
|
||||
}
|
||||
|
||||
const tableRowFormatter = (data) => {
|
||||
return data.row.email
|
||||
}
|
||||
@@ -683,7 +815,7 @@ function delUser(user) {
|
||||
if (userIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
ElMessageBox.confirm(t('delConfirm', {msg: user.email}), {
|
||||
ElMessageBox.confirm(t('delUsersConfirm'), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
@@ -699,6 +831,23 @@ function delUser(user) {
|
||||
});
|
||||
}
|
||||
|
||||
function delOneUser(user) {
|
||||
ElMessageBox.confirm(t('delConfirm', {msg: user.email}), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
userDelete([user.userId]).then(() => {
|
||||
ElMessage({
|
||||
message: t('delSuccessMsg'),
|
||||
type: "success",
|
||||
plain: true
|
||||
})
|
||||
getUserList(true)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function restore(user) {
|
||||
|
||||
const type = ref(0)
|
||||
@@ -707,14 +856,14 @@ function restore(user) {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
message: () => h('div', [
|
||||
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('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')),
|
||||
// ])
|
||||
]),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
@@ -730,18 +879,7 @@ function restore(user) {
|
||||
}
|
||||
|
||||
function setStatus(user) {
|
||||
|
||||
if (user.status === 0) {
|
||||
ElMessageBox.confirm(t('banRestore', {msg: user.email}), {
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
httpSetStatus(user)
|
||||
});
|
||||
} else {
|
||||
httpSetStatus(user)
|
||||
}
|
||||
httpSetStatus(user);
|
||||
}
|
||||
|
||||
function httpSetStatus(user) {
|
||||
@@ -866,7 +1004,7 @@ function getUserList(loading = true) {
|
||||
newParams.isDel = 1
|
||||
}
|
||||
userList(newParams).then(data => {
|
||||
users.value = data.list
|
||||
users.value = data.list.map(item => ({...item, checkedClass: ''}))
|
||||
total.value = data.total
|
||||
scrollbarRef.value?.setScrollTop(0);
|
||||
}).finally(() => {
|
||||
@@ -917,6 +1055,10 @@ function adjustWidth() {
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
:deep(.el-table .checked-row) {
|
||||
background: var(--el-color-warning-light-9);
|
||||
}
|
||||
|
||||
.user-box {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
@@ -994,7 +1136,7 @@ function adjustWidth() {
|
||||
}
|
||||
|
||||
.details {
|
||||
padding: 10px 10px 10px 10px;
|
||||
padding: 0 10px 10px 10px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
.details-item-title {
|
||||
@@ -1093,6 +1235,10 @@ function adjustWidth() {
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.right-dropdown-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
const en = {
|
||||
IncorrectPwd: 'Incorrect password',
|
||||
addAccountDisabled: 'Add account feature is disabled',
|
||||
addAccountDisabled: 'Add Email Address feature is disabled',
|
||||
regDisabled: 'Sign up is disabled',
|
||||
emptyEmail: 'Email cannot be empty',
|
||||
notEmail: 'Invalid email',
|
||||
notExistDomain: 'Email domain does not exist',
|
||||
isDelAccount: 'This Email has been deleted',
|
||||
isRegAccount: 'This Email is already registered',
|
||||
accountLimit: 'Account limit reached',
|
||||
delMyAccount: 'Cannot delete your own account',
|
||||
accountLimit: 'Email address limit reached',
|
||||
delMyAccount: 'Cannot delete your own email',
|
||||
noUserAccount: 'This email does not belong to the current user',
|
||||
usernameLengthLimit: 'Username length exceeds the limit',
|
||||
noOsSendPic: 'Cannot send body images: object storage not configured',
|
||||
@@ -64,13 +64,13 @@ const en = {
|
||||
emailExistDatabase: 'Email already exists in the database',
|
||||
notConfigOss: 'Object storage not configured',
|
||||
perms: {
|
||||
"邮件": "Email",
|
||||
"邮件": "Emails",
|
||||
"邮件发送": "Send Email",
|
||||
"邮件删除": "Delete Email",
|
||||
"邮箱侧栏": "Account",
|
||||
"邮箱查看": "View Account",
|
||||
"邮箱添加": "Add Account",
|
||||
"邮箱删除": "Delete Account",
|
||||
"邮箱侧栏": "Email Address",
|
||||
"邮箱查看": "View Email",
|
||||
"邮箱添加": "Add Email",
|
||||
"邮箱删除": "Delete Email",
|
||||
"个人设置": "Settings",
|
||||
"用户注销": "Delete User",
|
||||
"分析页": "Analytics",
|
||||
|
||||
@@ -28,6 +28,7 @@ const requirePerms = [
|
||||
'/account/delete',
|
||||
'/account/add',
|
||||
'/my/delete',
|
||||
'/analysis/echarts',
|
||||
'/role/add',
|
||||
'/role/list',
|
||||
'/role/delete',
|
||||
@@ -36,6 +37,8 @@ const requirePerms = [
|
||||
'/role/setDefault',
|
||||
'/allEmail/list',
|
||||
'/allEmail/delete',
|
||||
'/allEmail/batchDelete',
|
||||
'/allEmail/latest',
|
||||
'/setting/setBackground',
|
||||
'/setting/deleteBackground',
|
||||
'/setting/set',
|
||||
|
||||
@@ -449,7 +449,7 @@ const emailService = {
|
||||
let count = 0
|
||||
let list = []
|
||||
|
||||
while ((count < 10) && list.length === 0) {
|
||||
while ((count < 6) && list.length === 0) {
|
||||
list = await orm(c).select({...email}).from(email)
|
||||
.leftJoin(
|
||||
account,
|
||||
@@ -467,7 +467,7 @@ const emailService = {
|
||||
.orderBy(desc(email.emailId))
|
||||
.limit(20);
|
||||
|
||||
await sleep(3000);
|
||||
await sleep(5000);
|
||||
count++
|
||||
}
|
||||
|
||||
@@ -556,24 +556,24 @@ const emailService = {
|
||||
}
|
||||
|
||||
if (userEmail) {
|
||||
conditions.push(sql`${user.email} COLLATE NOCASE LIKE ${userEmail + '%'}`);
|
||||
conditions.push(sql`${user.email} COLLATE NOCASE LIKE ${'%'+ userEmail + '%'}`);
|
||||
}
|
||||
|
||||
if (accountEmail) {
|
||||
conditions.push(
|
||||
or(
|
||||
sql`${email.toEmail} COLLATE NOCASE LIKE ${accountEmail + '%'}`,
|
||||
sql`${email.sendEmail} COLLATE NOCASE LIKE ${accountEmail + '%'}`,
|
||||
sql`${email.toEmail} COLLATE NOCASE LIKE ${'%'+ accountEmail + '%'}`,
|
||||
sql`${email.sendEmail} COLLATE NOCASE LIKE ${'%'+ accountEmail + '%'}`,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (name) {
|
||||
conditions.push(sql`${email.name} COLLATE NOCASE LIKE ${name + '%'}`);
|
||||
conditions.push(sql`${email.name} COLLATE NOCASE LIKE ${'%'+ name + '%'}`);
|
||||
}
|
||||
|
||||
if (subject) {
|
||||
conditions.push(sql`${email.subject} COLLATE NOCASE LIKE ${subject + '%'}`);
|
||||
conditions.push(sql`${email.subject} COLLATE NOCASE LIKE ${'%'+ subject + '%'}`);
|
||||
}
|
||||
|
||||
conditions.push(ne(email.status, emailConst.status.SAVING));
|
||||
@@ -633,7 +633,7 @@ const emailService = {
|
||||
let count = 0
|
||||
let list = []
|
||||
|
||||
while ((count < 10) && list.length === 0) {
|
||||
while ((count < 6) && list.length === 0) {
|
||||
list = await orm(c).select({...email, userEmail: user.email}).from(email)
|
||||
.leftJoin(user, eq(email.userId, user.userId))
|
||||
.where(
|
||||
@@ -645,7 +645,7 @@ const emailService = {
|
||||
.orderBy(desc(email.emailId))
|
||||
.limit(20);
|
||||
|
||||
await sleep(3000);
|
||||
await sleep(5000);
|
||||
count++
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ const userService = {
|
||||
|
||||
|
||||
if (email) {
|
||||
conditions.push(sql`${user.email} COLLATE NOCASE LIKE ${email + '%'}`);
|
||||
conditions.push(sql`${user.email} COLLATE NOCASE LIKE ${'%'+ email + '%'}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +250,7 @@ const userService = {
|
||||
|
||||
const { password, userId } = params;
|
||||
await this.resetPassword(c, { password }, userId);
|
||||
await c.env.kv.delete(KvConst.AUTH_INFO + userId);
|
||||
},
|
||||
|
||||
async setStatus(c, params) {
|
||||
|
||||
Reference in New Issue
Block a user