新增批量删除用户

This commit is contained in:
eoao
2026-01-22 22:22:00 +08:00
parent ef002f9863
commit aaf4a29013
10 changed files with 125 additions and 109 deletions
@@ -494,7 +494,7 @@ function addItem(email) {
const existIndex = emailList.findIndex(item => item.emailId === email.emailId)
if (existIndex > -1) {
return
return false;
}
email.formatText = htmlToText(email);
@@ -511,7 +511,7 @@ function addItem(email) {
}
total.value++
return;
return true;
}
@@ -532,6 +532,7 @@ function addItem(email) {
}
total.value++
return true;
}
function handleCheckAllChange(val) {
@@ -639,7 +640,10 @@ function handleList(list) {
5: { icon: 'bi:send-arrow-up-fill', color: '#FBBD08', content: t('delayed') },
7: { icon: 'ic:round-mark-email-read', color: '#FBBD08', content: t('noRecipient') },
};
if (email.isDel) email.isdelContent = t('selectDeleted');
if (email.isDel) {
email.isDelContent = t('selectDeleted');
}
email.statusIcon = statusIconMap[email.status];
})
}
+5 -2
View File
@@ -292,7 +292,8 @@ const en = {
to: 'To',
clear: 'Clear',
include: 'Include',
delAllEmailConfirm: 'Do you really want to delete it?',
delAllConfirm: 'Do you really want to delete it?',
pin: 'Pin',
s3Configuration: 'S3 Configuration',
confirmDeletionOfContacts: 'Confirm clearing contacts?',
recentContacts: 'Recent contacts',
@@ -309,7 +310,9 @@ const en = {
character: '',
mustNotContain: 'Must Not Contain',
mustNotContainDesc: 'Separate with commas',
setSuccess: 'Settings saved successfully'
setSuccess: 'Settings saved successfully',
details: 'Details',
userDetails: 'User Details'
}
export default en
+5 -2
View File
@@ -292,7 +292,8 @@ const zh = {
to: '至',
clear: '清除',
include: '包含',
delAllEmailConfirm: '确定要删除吗?',
delAllConfirm: '确定要删除吗?',
pin: '置顶',
s3Configuration: 'S3 配置',
confirmDeletionOfContacts: '确认清除这些联系人吗?',
recentContacts: '最近联系人',
@@ -309,6 +310,8 @@ const zh = {
character: '位',
mustNotContain: '禁止包含',
mustNotContainDesc: '输入多个值用,分开',
setSuccess: '设置成功'
setSuccess: '设置成功',
details: '详情',
userDetails: '用户详情'
}
export default zh
+1 -1
View File
@@ -25,7 +25,7 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="hasPerm('email:send')" @click="openSetName(item)">{{ $t('rename') }}</el-dropdown-item>
<el-dropdown-item v-if="item.accountId !== userStore.user.account.accountId" @click="setAsTop(item, index)">{{ $t('置顶') }}</el-dropdown-item>
<el-dropdown-item v-if="item.accountId !== userStore.user.account.accountId" @click="setAsTop(item, index)">{{ $t('pin') }}</el-dropdown-item>
<el-dropdown-item v-if="item.accountId !== userStore.user.account.accountId && hasPerm('account:delete')"
@click="remove(item)">{{ $t('delete') }}
</el-dropdown-item>
+2 -2
View File
@@ -18,8 +18,8 @@ export function userSetType(params) {
}
export function userDelete(userId) {
return http.delete('/user/delete', {params:{userId}})
export function userDelete(userIds) {
return http.delete('/user/delete', {params:{userIds: userIds + ''}})
}
export function userAdd(form) {
+1 -1
View File
@@ -204,7 +204,7 @@ function batchDelete() {
}
ElMessageBox.confirm(
t('delAllEmailConfirm'),
t('delAllConfirm'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
+2 -2
View File
@@ -110,9 +110,9 @@ async function latest() {
if (!existIds.has(email.emailId)) {
existIds.add(email.emailId)
scroll.value.addItem(email)
const flag = scroll.value.addItem(email)
if (innerWidth > 1367) {
if (innerWidth > 1367 && flag) {
ElNotification({
type: 'primary',
message: `<div style="cursor: pointer;"><div style="overflow: hidden;white-space: nowrap;text-overflow: ellipsis; font-weight: bold;font-size: 16px;margin-bottom: 5px;">${email.name}</div><div style="color: teal;">${email.subject}</div></div>`,
+88 -88
View File
@@ -23,7 +23,7 @@
<Icon class="icon" @click="changeTimeSort" icon="material-symbols-light:timer-arrow-up-outline" v-else width="28"
height="28"/>
<Icon class="icon" icon="ion:reload" width="18" height="18" @click="refresh"/>
<Icon class="icon" icon="pepicons-pencil:expand" width="26" height="26" @click="changeExpand"/>
<Icon class="icon" icon="uiw:delete" width="16" height="16" @click="delUser"/>
</div>
<el-scrollbar ref="scrollbarRef" class="scrollbar">
<div>
@@ -34,78 +34,12 @@
<el-table
@filter-change="tableFilter"
:empty-text="first ? '' : null"
:default-expand-all="expandStatus"
:data="users"
:preserve-expanded-content="preserveExpanded"
style="width: 100%;"
:key="key"
ref="tableRef"
>
<el-table-column :width="expandWidth" type="expand">
<template #default="props">
<div class="details">
<div v-if="props.row.username"><span class="details-item-title">LinuxDo:</span>
<el-avatar :src="props.row.avatar" :size="30" class="linuxdo-avatar" />
<span style="margin: 0 10px">用户名{{props.row.username}}</span>
<span>
等级<el-tag type="success">{{props.row.trustLevel}}</el-tag>
</span>
</div>
<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>{{
props.row.accountCount
}}
</div>
<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>
<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>
</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>{{
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('sendEmail') }}:</span>
<span>{{ formatSendCount(props.row) }}</span>
<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') }}
</el-button>
</div>
</div>
</template>
</el-table-column>
<el-table-column :width="expandWidth" type="selection" :selectable="row => row.type !== 0" />
<el-table-column show-overflow-tooltip :tooltip-formatter="tableRowFormatter" :label="$t('tabEmailAddress')"
:min-width="emailWidth">
<template #default="props">
@@ -147,11 +81,11 @@
</el-table-column>
<el-table-column :label="$t('tabSetting')" :width="settingWidth">
<template #default="props">
<el-dropdown trigger="click">
<el-dropdown>
<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="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>
<template v-if="props.row.type !== 0">
<el-dropdown-item v-if="props.row.isDel !== 1" @click="setStatus(props.row)">
@@ -159,8 +93,8 @@
</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-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-menu>
</template>
</el-dropdown>
@@ -288,6 +222,70 @@
/>
</div>
</el-dialog>
<el-dialog class="account-dialog" v-model="detailsShow" :title="t('userDetails')" >
<div class="details">
<div v-if="userDetails.username"><span class="details-item-title">LinuxDo:</span>
<el-avatar :src="userDetails.avatar" :size="30" class="linuxdo-avatar" />
<span style="margin: 0 10px">用户名{{userDetails.username}}</span>
<span>
等级<el-tag type="success">{{userDetails.trustLevel}}</el-tag>
</span>
</div>
<div v-if="!sendNumShow"><span
class="details-item-title">{{ $t('tabSent') }}:</span>{{ userDetails.sendEmailCount }}
</div>
<div v-if="!accountNumShow"><span class="details-item-title">{{ $t('tabMailboxes') }}:</span>{{
userDetails.accountCount
}}
</div>
<div v-if="!createTimeShow"><span class="details-item-title">{{ $t('tabRegisteredAt') }}:</span>{{
tzDayjs(userDetails.createTime).format('YYYY-MM-DD HH:mm')
}}
</div>
<div v-if="!typeShow"><span class="details-item-title">{{ $t('perm') }}:</span>
{{ toRoleName(userDetails.type) }}
</div>
<div v-if="!statusShow">
<span class="details-item-title">{{ $t('tabStatus') }}:</span>
<el-tag disable-transitions v-if="userDetails.isDel === 1" type="info">{{ $t('deleted') }}</el-tag>
<el-tag disable-transitions v-else-if="userDetails.status === 0" type="primary">{{ $t('active') }}
</el-tag>
<el-tag disable-transitions v-else-if="userDetails.status === 1" type="danger">{{ $t('banned') }}
</el-tag>
</div>
<div><span class="details-item-title">{{ $t('registrationIp') }}:</span>{{
userDetails.createIp || $t('unknown')
}}
</div>
<div><span class="details-item-title">{{ $t('recentIP') }}:</span>{{
userDetails.activeIp || $t('unknown')
}}
</div>
<div><span class="details-item-title">{{ $t('recentActivity') }}:</span>{{
userDetails.activeTime ? tzDayjs(userDetails.activeTime).format('YYYY-MM-DD') : $t('unknown')
}}
</div>
<div><span
class="details-item-title">{{ $t('loginDevice') }}:</span>{{ userDetails.device || $t('unknown') }}
</div>
<div><span class="details-item-title">{{ $t('loginSystem') }}:</span>{{ userDetails.os || $t('unknown') }}
</div>
<div><span
class="details-item-title">{{ $t('browserLogin') }}:</span>{{ userDetails.browser || $t('unknown') }}
</div>
<div>
<span class="details-item-title">{{ $t('sendEmail') }}:</span>
<span>{{ formatSendCount(userDetails) }}</span>
<el-tag style="margin-left: 10px" v-if="userDetails.sendAction.hasPerm">
{{ formatSendType(userDetails) }}
</el-tag>
<el-button size="small" style="margin-left: 10px"
v-if="userDetails.sendAction.hasPerm && userDetails.sendAction.sendCount"
@click="resetSendCount(userDetails)" type="primary">{{ $t('reset') }}
</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
@@ -336,10 +334,12 @@ const statusShow = ref(true)
const typeShow = ref(true)
const receiveWidth = ref(null)
const phonePageShow = ref(false)
const detailsShow = ref(false);
const layout = ref('prev, pager, next, sizes, total')
const pageSize = ref('')
const expandStatus = ref(false)
const users = ref([])
const tableRef = ref({})
const userDetails = ref({})
const total = ref(0)
const first = ref(true)
const scrollbarRef = ref(null)
@@ -378,7 +378,6 @@ const settingLoading = ref(false)
const tableLoading = ref(true)
const roleList = reactive([])
const mySelect = ref({})
const key = ref(0)
const accountList = reactive([])
const accountParams = reactive({
size: 10,
@@ -460,6 +459,11 @@ function openAccountList(userId) {
accountShow.value = true
}
function openDetails(user) {
userDetails.value = user;
detailsShow.value = true;
}
function getAccountList(loading = false) {
accountLoading.value = loading
userAllAccount(accountParams.userId,accountParams.num, accountParams.size).then(({list,total}) => {
@@ -540,11 +544,6 @@ const tableRowFormatter = (data) => {
return data.row.email
}
function changeExpand() {
expandStatus.value = !expandStatus.value
key.value++
}
const openSelect = () => {
mySelect.value.toggleMenu()
}
@@ -679,18 +678,23 @@ function resetSendCount(user) {
}
function delUser(user) {
const rows = tableRef.value.getSelectionRows();
const userIds = rows.map(row => row.userId);
if (userIds.length === 0) {
return;
}
ElMessageBox.confirm(t('delConfirm', {msg: user.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
userDelete(user.userId).then(() => {
userDelete(userIds).then(() => {
ElMessage({
message: t('delSuccessMsg'),
type: "success",
plain: true
})
getUserList(false)
getUserList(true)
})
});
}
@@ -888,7 +892,7 @@ function adjustWidth() {
typeShow.value = width > 767
emailWidth.value = width > 480 ? 230 : null
settingWidth.value = width < 480 ? (locale.value === 'en' ? 85 : 75) : null
expandWidth.value = width < 480 ? 25 : 40
expandWidth.value = width < 480 ? 30 : 35
pagerCount.value = width < 768 ? 7 : 11
receiveWidth.value = width < 480 ? 90 : null
layout.value = width < 768 ? 'pager' : 'prev, pager, next,sizes, total'
@@ -990,13 +994,9 @@ function adjustWidth() {
}
.details {
padding: 15px 15px 15px 52px;
padding: 10px 10px 10px 10px;
display: grid;
gap: 10px;
@media (max-width: 767px) {
padding-left: 35px;
}
.details-item-title {
white-space: pre;
color: #909399;
+6 -2
View File
@@ -1,7 +1,7 @@
import BizError from "../error/biz-error";
import orm from "../entity/orm";
import {oauth} from "../entity/oauth";
import { eq } from 'drizzle-orm';
import { eq, inArray } from 'drizzle-orm';
import userService from "./user-service";
import loginService from "./login-service";
import cryptoUtils from "../utils/crypto-utils";
@@ -102,7 +102,11 @@ const oauthService = {
},
async deleteByUserId(c, userId) {
await orm(c).delete(oauth).where(eq(oauth.userId, userId)).run();
await this.deleteByUserIds(c, [userId]);
},
async deleteByUserIds(c, userIds) {
await orm(c).delete(oauth).where(inArray(oauth.userId, userIds)).run();
},
//定时任务凌晨清除未绑定邮箱的oauth用户
+8 -6
View File
@@ -42,10 +42,12 @@ const userService = {
user.account = account;
user.name = account.name;
user.permKeys = permKeys;
user.role = roleRow
user.role = roleRow;
user.type = userRow.type;
if (c.env.admin === userRow.email) {
user.role = constant.ADMIN_ROLE
user.type = 0;
}
return user;
@@ -98,11 +100,11 @@ const userService = {
},
async physicsDelete(c, params) {
const { userId } = params
await accountService.physicsDeleteByUserIds(c, [userId])
await oauthService.deleteByUserId(c, userId);
await orm(c).delete(user).where(eq(user.userId, userId)).run();
await c.env.kv.delete(kvConst.AUTH_INFO + userId);
let { userIds } = params;
userIds = userIds.split(',').map(Number);
await accountService.physicsDeleteByUserIds(c, userIds);
await oauthService.deleteByUserIds(c, userIds);
await orm(c).delete(user).where(inArray(user.userId, userIds)).run();
},
async list(c, params) {