feat: 支持系统账号批量操作
- 新增账号批量删除和批量重置密码接口及结果返回 - 用户列表增加批量操作工具栏与结果提示 - 账号删除切换为逻辑删除语义
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from 'element-plus';
|
||||
|
||||
import { markRaw, onMounted, ref } from 'vue';
|
||||
import { computed, markRaw, nextTick, onMounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
Delete,
|
||||
@@ -38,9 +38,13 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const pageDataRef = ref();
|
||||
const tableRef = ref();
|
||||
const importDialog = ref();
|
||||
const saveDialog = ref();
|
||||
const selectedRows = ref<any[]>([]);
|
||||
const batchActionLoading = ref(false);
|
||||
const dictStore = useDictStore();
|
||||
const selectedCount = computed(() => selectedRows.value.length);
|
||||
const headerButtons = [
|
||||
{
|
||||
key: 'create',
|
||||
@@ -65,8 +69,18 @@ function initDict() {
|
||||
const handleSearch = (params: string) => {
|
||||
pageDataRef.value.setQuery({ loginName: params, isQueryOr: true });
|
||||
};
|
||||
function clearSelection() {
|
||||
selectedRows.value = [];
|
||||
tableRef.value?.clearSelection?.();
|
||||
}
|
||||
async function reloadPage() {
|
||||
await pageDataRef.value?.reload?.();
|
||||
await nextTick();
|
||||
clearSelection();
|
||||
}
|
||||
function reset(formEl?: FormInstance) {
|
||||
formEl?.resetFields();
|
||||
clearSelection();
|
||||
pageDataRef.value.setQuery({});
|
||||
}
|
||||
function showDialog(row: any) {
|
||||
@@ -96,7 +110,7 @@ function remove(row: any) {
|
||||
instance.confirmButtonLoading = false;
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
reset();
|
||||
void reloadPage();
|
||||
done();
|
||||
}
|
||||
})
|
||||
@@ -126,6 +140,7 @@ function resetPassword(row: any) {
|
||||
instance.confirmButtonLoading = false;
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success($t('sysAccount.resetPasswordSuccess'));
|
||||
clearSelection();
|
||||
done();
|
||||
}
|
||||
})
|
||||
@@ -139,6 +154,103 @@ function resetPassword(row: any) {
|
||||
},
|
||||
).catch(() => {});
|
||||
}
|
||||
function handleSelectionChange(rows: any[]) {
|
||||
selectedRows.value = rows;
|
||||
}
|
||||
function getSelectedIds() {
|
||||
return selectedRows.value
|
||||
.map((row) => row?.id)
|
||||
.filter((id) => id !== null && id !== undefined);
|
||||
}
|
||||
function getFirstBatchErrorReason(result: any) {
|
||||
return result?.errorItems?.[0]?.reason;
|
||||
}
|
||||
function notifyBatchActionResult(action: 'delete' | 'reset', result: any) {
|
||||
const successCount = result?.successCount || 0;
|
||||
const errorCount = result?.errorCount || 0;
|
||||
const firstReason = getFirstBatchErrorReason(result);
|
||||
if (errorCount === 0) {
|
||||
ElMessage.success(
|
||||
action === 'delete'
|
||||
? $t('sysAccount.batchDeleteSuccess', { count: successCount })
|
||||
: $t('sysAccount.batchResetPasswordSuccess', { count: successCount }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (successCount === 0) {
|
||||
ElMessage.error(
|
||||
firstReason ||
|
||||
(action === 'delete'
|
||||
? $t('sysAccount.batchDeleteAllFailed')
|
||||
: $t('sysAccount.batchResetPasswordAllFailed')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
ElMessage.warning(
|
||||
action === 'delete'
|
||||
? $t('sysAccount.batchDeletePartialSuccess', {
|
||||
successCount,
|
||||
errorCount,
|
||||
})
|
||||
: $t('sysAccount.batchResetPasswordPartialSuccess', {
|
||||
successCount,
|
||||
errorCount,
|
||||
}),
|
||||
);
|
||||
}
|
||||
async function submitBatchAction(
|
||||
url: string,
|
||||
action: 'delete' | 'reset',
|
||||
confirmMessage: string,
|
||||
) {
|
||||
const ids = getSelectedIds();
|
||||
if (ids.length === 0) {
|
||||
ElMessage.warning($t('sysAccount.batchActionSelectRequired'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(confirmMessage, $t('message.noticeTitle'), {
|
||||
confirmButtonText: $t('message.ok'),
|
||||
cancelButtonText: $t('message.cancel'),
|
||||
type: 'warning',
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
batchActionLoading.value = true;
|
||||
try {
|
||||
const res = await api.post(url, { ids });
|
||||
if (res.errorCode !== 0) {
|
||||
ElMessage.error(res.message || $t('sysAccount.batchActionFailed'));
|
||||
return;
|
||||
}
|
||||
notifyBatchActionResult(action, res.data || {});
|
||||
if (action === 'delete' && (res.data?.successCount || 0) > 0) {
|
||||
await reloadPage();
|
||||
return;
|
||||
}
|
||||
clearSelection();
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || $t('sysAccount.batchActionFailed'));
|
||||
} finally {
|
||||
batchActionLoading.value = false;
|
||||
}
|
||||
}
|
||||
function batchDelete() {
|
||||
void submitBatchAction(
|
||||
'/api/v1/sysAccount/removeBatchWithResult',
|
||||
'delete',
|
||||
$t('sysAccount.batchDeleteConfirm', { count: selectedCount.value }),
|
||||
);
|
||||
}
|
||||
function batchResetPassword() {
|
||||
void submitBatchAction(
|
||||
'/api/v1/sysAccount/resetPasswordBatch',
|
||||
'reset',
|
||||
$t('sysAccount.batchResetPasswordConfirm', { count: selectedCount.value }),
|
||||
);
|
||||
}
|
||||
function isAdmin(data: any) {
|
||||
return data?.accountType === 1 || data?.accountType === 99;
|
||||
}
|
||||
@@ -154,7 +266,37 @@ function isAdmin(data: any) {
|
||||
:buttons="headerButtons"
|
||||
@search="handleSearch"
|
||||
@button-click="handleHeaderButtonClick"
|
||||
/>
|
||||
>
|
||||
<template #middle>
|
||||
<div v-if="selectedCount > 0" class="sys-account-batch-inline">
|
||||
<span class="sys-account-batch-inline__count">
|
||||
{{ $t('sysAccount.batchSelectedCount', { count: selectedCount }) }}
|
||||
</span>
|
||||
<div class="sys-account-batch-inline__actions">
|
||||
<div v-access:code="'/api/v1/sysAccount/save'">
|
||||
<ElButton
|
||||
class="sys-account-batch-inline__button"
|
||||
:icon="Lock"
|
||||
:loading="batchActionLoading"
|
||||
@click="batchResetPassword"
|
||||
>
|
||||
{{ $t('sysAccount.batchResetPassword') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
<div v-access:code="'/api/v1/sysAccount/remove'">
|
||||
<ElButton
|
||||
class="sys-account-batch-inline__button is-danger"
|
||||
:icon="Delete"
|
||||
:loading="batchActionLoading"
|
||||
@click="batchDelete"
|
||||
>
|
||||
{{ $t('sysAccount.batchDelete') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HeaderSearch>
|
||||
</template>
|
||||
<PageData
|
||||
ref="pageDataRef"
|
||||
@@ -162,7 +304,13 @@ function isAdmin(data: any) {
|
||||
:page-size="10"
|
||||
>
|
||||
<template #default="{ pageList }">
|
||||
<ElTable :data="pageList" border>
|
||||
<ElTable
|
||||
ref="tableRef"
|
||||
:data="pageList"
|
||||
border
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<ElTableColumn type="selection" width="48" />
|
||||
<ElTableColumn
|
||||
prop="avatar"
|
||||
align="center"
|
||||
@@ -276,4 +424,68 @@ function isAdmin(data: any) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.sys-account-batch-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sys-account-batch-inline__count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--text-muted));
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sys-account-batch-inline__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sys-account-batch-inline__actions :deep(.el-button) {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
padding-inline: 14px;
|
||||
border-radius: 10px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.sys-account-batch-inline__button) {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:deep(.sys-account-batch-inline__button:not(.is-disabled):hover),
|
||||
:deep(.sys-account-batch-inline__button:not(.is-disabled):focus-visible) {
|
||||
color: hsl(var(--primary));
|
||||
background: hsl(var(--primary) / 0.08);
|
||||
border-color: hsl(var(--primary) / 0.16);
|
||||
}
|
||||
|
||||
:deep(.sys-account-batch-inline__button.is-danger) {
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
:deep(.sys-account-batch-inline__button.is-danger:not(.is-disabled):hover),
|
||||
:deep(.sys-account-batch-inline__button.is-danger:not(.is-disabled):focus-visible) {
|
||||
color: hsl(var(--destructive));
|
||||
background: hsl(var(--destructive) / 0.08);
|
||||
border-color: hsl(var(--destructive) / 0.16);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.sys-account-batch-inline {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sys-account-batch-inline__actions {
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user