新增全部用户查看删除邮箱

This commit is contained in:
eoao
2025-09-21 19:20:00 +08:00
parent 457304eafb
commit 82bd308d56
21 changed files with 244 additions and 77 deletions

8
mail-vue/jsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

View File

@@ -36,7 +36,7 @@
"terser": "^5.39.0",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.7.0",
"vite": "6.3.4",
"vite": "7.1.5",
"vite-plugin-pwa": "^1.0.3"
}
}

View File

@@ -62,7 +62,7 @@ importers:
devDependencies:
'@vitejs/plugin-vue':
specifier: ^5.2.1
version: 5.2.4(vite@6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(vue@3.5.20)
version: 5.2.4(vite@7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(vue@3.5.20)
less:
specifier: ^4.2.2
version: 4.4.1
@@ -79,11 +79,11 @@ importers:
specifier: ^28.7.0
version: 28.8.0(@babel/parser@7.28.3)(vue@3.5.20)
vite:
specifier: 6.3.4
version: 6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1)
specifier: 7.1.5
version: 7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1)
vite-plugin-pwa:
specifier: ^1.0.3
version: 1.0.3(vite@6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(workbox-build@7.3.0)(workbox-window@7.3.0)
version: 1.0.3(vite@7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(workbox-build@7.3.0)(workbox-window@7.3.0)
packages:
@@ -2217,6 +2217,10 @@ packages:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -2343,19 +2347,19 @@ packages:
'@vite-pwa/assets-generator':
optional: true
vite@6.3.4:
resolution: {integrity: sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
vite@7.1.5:
resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
'@types/node': ^20.19.0 || >=22.12.0
jiti: '>=1.21.0'
less: '*'
less: ^4.0.0
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
@@ -3501,9 +3505,9 @@ snapshots:
'@types/web-bluetooth@0.0.21': {}
'@vitejs/plugin-vue@5.2.4(vite@6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(vue@3.5.20)':
'@vitejs/plugin-vue@5.2.4(vite@7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(vue@3.5.20)':
dependencies:
vite: 6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1)
vite: 7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1)
vue: 3.5.20
'@vue/compiler-core@3.5.20':
@@ -4821,6 +4825,11 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -4962,25 +4971,25 @@ snapshots:
dependencies:
inherits: 2.0.3
vite-plugin-pwa@1.0.3(vite@6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(workbox-build@7.3.0)(workbox-window@7.3.0):
vite-plugin-pwa@1.0.3(vite@7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1))(workbox-build@7.3.0)(workbox-window@7.3.0):
dependencies:
debug: 4.4.1
pretty-bytes: 6.1.1
tinyglobby: 0.2.14
vite: 6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1)
vite: 7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1)
workbox-build: 7.3.0
workbox-window: 7.3.0
transitivePeerDependencies:
- supports-color
vite@6.3.4(less@4.4.1)(sass@1.91.0)(terser@5.43.1):
vite@7.1.5(less@4.4.1)(sass@1.91.0)(terser@5.43.1):
dependencies:
esbuild: 0.25.9
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.50.0
tinyglobby: 0.2.14
tinyglobby: 0.2.15
optionalDependencies:
fsevents: 2.3.3
less: 4.4.1

View File

@@ -66,7 +66,9 @@ http.interceptors.response.use((res) => {
})
reject(data)
}
resolve(data.data)
setTimeout(() => {
resolve(data.data)
},0)
})
},
(error) => {

View File

@@ -1,5 +1,5 @@
const en = {
inbox: 'Inbox',
'收件箱': 'Inbox',
drafts: 'Drafts',
sent: 'Sent',
starred: 'Starred',
@@ -14,6 +14,8 @@ const en = {
noMessagesFound: 'No messages found',
addAccount: 'Add Account',
emailAccount: 'Email',
account: 'Account',
userAccount: 'User Accounts',
deleteUser: 'Delete Account',
deleteUserBtn: 'Delete',
changePassword: 'Change Password',

View File

@@ -1,5 +1,5 @@
const zh = {
inbox: '收件箱',
'收件箱': '收件箱',
drafts: '草稿箱',
sent: '已发送',
starred: '星标邮件',
@@ -14,6 +14,8 @@ const zh = {
noMessagesFound: '没有任何邮件',
addAccount: '添加邮箱',
emailAccount: '邮箱',
account: '邮箱',
userAccount: '用户邮箱',
deleteUser: '删除账户',
deleteUserBtn: '删除账户',
changePassword: '修改密码',

View File

@@ -9,7 +9,7 @@
<el-menu-item @click="router.push({name: 'email'})" index="email"
:class="route.meta.name === 'email' ? 'choose-item' : ''">
<Icon icon="hugeicons:mailbox-01" width="20" height="20" />
<span class="menu-name" style="margin-left: 21px">{{$t('inbox')}}</span>
<span class="menu-name" style="margin-left: 21px">{{$t('收件箱')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'send'})" index="send" v-perm="'email:send'"
:class="route.meta.name === 'send' ? 'choose-item' : ''">

View File

@@ -10,17 +10,6 @@
</div>
</div>
<div class="toolbar">
<el-dropdown>
<div class="translate icon-item">
<Icon icon="carbon:ibm-watson-language-translator"/>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="changeLang('zh')">简体中文</el-dropdown-item>
<el-dropdown-item @click="changeLang('en')">English</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div v-if="uiStore.dark" class="sun-icon icon-item" @click="openDark($event)">
<Icon icon="mingcute:sun-fill"/>
</div>
@@ -449,11 +438,6 @@ function formatName(email) {
font-size: 24px;
}
.translate {
padding-top: 2px;
font-size: 21px;
}
.avatar {
display: flex;
align-items: center;

View File

@@ -32,4 +32,12 @@ export function userRestSendCount(userId) {
export function userRestore(userId,type) {
return http.put('/user/restore', {userId,type})
}
}
export function userAllAccount(userId, num, size) {
return http.get('/user/allAccount', {params:{userId,num,size}})
}
export function userDeleteAccount(accountId) {
return http.delete('/user/deleteAccount', {params:{accountId}})
}

View File

@@ -16,7 +16,7 @@ const routes = [
name: 'email',
component: () => import('@/views/email/index.vue'),
meta: {
title: 'inbox',
title: '收件箱',
name: 'email',
menu: true
}

View File

@@ -11,8 +11,5 @@ export const useSettingStore = defineStore('setting', {
}),
actions: {
},
persist: {
pick: ['lang'],
},
}
})

View File

@@ -143,11 +143,14 @@
<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 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>
<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)">{{ $t('account') }}</el-dropdown-item>
<el-dropdown-item @click="delUser(props.row)" v-if="props.row.type !== 0">{{ $t('delete') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -237,6 +240,44 @@
</el-button>
</div>
</el-dialog>
<el-dialog class="account-dialog" v-model="accountShow" :title="t('userAccount')" @closed="resetAccountList" >
<el-table :data="accountList" style="height: 480px" v-loading="accountLoading" element-loading-background="transparent" :empty-text="accountLoading ? '' : null">
<el-table-column property="email" :label="t('emailAccount')" >
<template #default="props">
<div class="email-row">{{ props.row.email }}</div>
</template>
</el-table-column>
<el-table-column property="address" :label="t('tabStatus')" :width="locale === 'en' ? 75 : 65" >
<template #default="props">
<el-tag type="primary" disable-transitions v-if="props.row.isDel === 0">{{$t('active')}}</el-tag>
<el-tag type="info" disable-transitions v-if="props.row.isDel === 1">{{$t('deleted')}}</el-tag>
</template>
</el-table-column>
<el-table-column :label="t('action')" :width="locale === 'en' ? 75 : 65" >
<template #default="props">
<el-dropdown trigger="click">
<el-button type="primary" size="small">{{t('action')}}</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="deleteAccount(props.row)">{{ $t('delete') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="account-pagination">
<el-pagination
:disabled="accountLoading"
background
layout="prev, pager, next"
:pager-count="3"
:total="accountParams.total"
@current-change="accountCurChange"
/>
</div>
</el-dialog>
</div>
</template>
@@ -249,7 +290,10 @@ import {
userSetStatus,
userSetType,
userAdd,
userRestSendCount, userRestore
userRestSendCount,
userRestore,
userDeleteAccount,
userAllAccount
} from '@/request/user.js'
import {roleSelectUse} from "@/request/role.js";
import {Icon} from "@iconify/vue";
@@ -289,6 +333,7 @@ const users = ref([])
const total = ref(0)
const first = ref(true)
const scrollbarRef = ref(null)
const accountLoading = ref(false)
const domainList = settingStore.domainList
@@ -314,6 +359,7 @@ const userForm = reactive({
})
const showAdd = ref(false)
const accountShow = ref(false)
const addLoading = ref(false);
const setTypeShow = ref(false)
const setPwdShow = ref(false)
@@ -323,6 +369,13 @@ const tableLoading = ref(true)
const roleList = reactive([])
const mySelect = ref({})
const key = ref(0)
const accountList = reactive([])
const accountParams = reactive({
size: 10,
num: 0,
total: 0,
userId: 0,
})
roleSelectUse().then(list => {
roleList.length = 0
@@ -331,11 +384,11 @@ roleSelectUse().then(list => {
const paramsStar = localStorage.getItem('user-params')
if (paramsStar) {
const locaParams = JSON.parse(paramsStar)
params.num = locaParams.num
params.size = locaParams.size
params.timeSort = locaParams.timeSort
params.status = locaParams.status
const localParams = JSON.parse(paramsStar)
params.num = localParams.num
params.size = localParams.size
params.timeSort = localParams.timeSort
params.status = localParams.status
}
watch(() => params, () => {
@@ -363,6 +416,50 @@ const filterItem = reactive({
receive: ['normal', 'del']
})
function deleteAccount(account) {
ElMessageBox.confirm(t('delConfirm', {msg: account.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
userDeleteAccount(account.accountId).then(() => {
getAccountList()
ElMessage({
message: t('删除成功'),
type: "success",
plain: true
})
})
});
}
function accountCurChange(e) {
accountParams.num = e
getAccountList()
}
function resetAccountList() {
accountList.length = 0
accountParams.num = 0
accountParams.size = 10
accountParams.total = 0
}
function openAccountList(userId) {
accountParams.userId = userId
getAccountList(true)
accountShow.value = true
}
function getAccountList(loading = false) {
accountLoading.value = loading
userAllAccount(accountParams.userId,accountParams.num, accountParams.size).then(({list,total}) => {
accountList.length = 0
accountList.push(...list)
accountParams.total = total
accountLoading.value = false
})
}
function tableFilter(e) {
if (e.send) filterItem.send = e.send
@@ -820,6 +917,15 @@ function adjustWidth() {
}
}
:deep(.account-dialog) {
width: 500px !important;
@media (max-width: 540px) {
width: calc(100% - 40px) !important;
margin-right: 20px !important;
margin-left: 20px !important;
}
}
.header-actions {
padding: 9px 15px;
display: flex;
@@ -889,6 +995,12 @@ function adjustWidth() {
}
}
.account-pagination {
display: flex;
justify-content: end;
width: 100%;
}
.pagination {
margin-top: 15px;
margin-bottom: 20px;
@@ -982,15 +1094,6 @@ function adjustWidth() {
background: var(--el-bg-color);
}
:deep(.el-dialog) {
width: 400px !important;
@media (max-width: 440px) {
width: calc(100% - 40px) !important;
margin-right: 20px !important;
margin-left: 20px !important;
}
}
:deep(.cell) {
white-space: normal;
overflow: visible;
@@ -1020,4 +1123,4 @@ function adjustWidth() {
:deep(.el-message-box__container) {
align-items: start;
}
</style>
</style>