mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 14:06:28 +00:00
feat: improve error handling and code quality
后端改进: - 添加统一异常处理系统 (exceptions.py, response.py) - 实现自定义异常类 (ValidationError, AuthorizationError, ResourceNotFoundError, BusinessLogicError) - 配置全局异常处理器,统一 API 错误响应格式 - 迁移业务逻辑错误到自定义异常 (users.py, auth.py) - 添加 SQL LIKE 通配符转义,防止通配符滥用 - 使用 EmailStr 进行邮箱格式验证 - 移除敏感字段暴露 (jwt_sub) 前端改进: - 配置 ESLint 9 (flat config) 和 Prettier - 修复所有 ESLint 错误和警告 - 移除未使用的变量和导入 - 为组件添加 PropTypes 默认值 - 统一代码格式和风格
This commit is contained in:
@@ -22,13 +22,13 @@
|
||||
</template>
|
||||
|
||||
<!-- Tab 切换 -->
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<a-tabs v-model:active-key="activeTab" @change="handleTabChange">
|
||||
<!-- 待审批用户 Tab -->
|
||||
<a-tab-pane key="pending" tab="待审批用户">
|
||||
<!-- 桌面端表格 -->
|
||||
<a-table
|
||||
v-if="!isMobile"
|
||||
:dataSource="pendingUsers"
|
||||
:data-source="pendingUsers"
|
||||
:columns="pendingColumns"
|
||||
:loading="loading"
|
||||
:row-key="record => record.id"
|
||||
@@ -44,9 +44,7 @@
|
||||
<a-button type="primary" size="small" @click="handleApprove(record)">
|
||||
通过
|
||||
</a-button>
|
||||
<a-button danger size="small" @click="handleReject(record)">
|
||||
拒绝
|
||||
</a-button>
|
||||
<a-button danger size="small" @click="handleReject(record)"> 拒绝 </a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
@@ -59,10 +57,14 @@
|
||||
<a-descriptions-item label="ID">{{ user.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="用户名">{{ user.alias }}</a-descriptions-item>
|
||||
<a-descriptions-item label="邮箱">{{ user.email || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="注册时间">{{ formatDateTime(user.created_at) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="注册时间">{{
|
||||
formatDateTime(user.created_at)
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<a-space class="mt-3" style="width: 100%">
|
||||
<a-button type="primary" size="small" block @click="handleApprove(user)">通过</a-button>
|
||||
<a-button type="primary" size="small" block @click="handleApprove(user)"
|
||||
>通过</a-button
|
||||
>
|
||||
<a-button danger size="small" block @click="handleReject(user)">拒绝</a-button>
|
||||
</a-space>
|
||||
</a-card>
|
||||
@@ -75,7 +77,7 @@
|
||||
<!-- 桌面端表格 -->
|
||||
<a-table
|
||||
v-if="!isMobile"
|
||||
:dataSource="userStore.users"
|
||||
:data-source="userStore.users"
|
||||
:columns="allColumns"
|
||||
:loading="loading"
|
||||
:row-key="record => record.id"
|
||||
@@ -95,7 +97,11 @@
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'jwt_exp'">
|
||||
{{ record.jwt_exp && record.jwt_exp !== '0' ? formatDateTime(parseInt(record.jwt_exp) * 1000) : '-' }}
|
||||
{{
|
||||
record.jwt_exp && record.jwt_exp !== '0'
|
||||
? formatDateTime(parseInt(record.jwt_exp) * 1000)
|
||||
: '-'
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'created_at'">
|
||||
{{ formatDateTime(record.created_at) }}
|
||||
@@ -105,9 +111,7 @@
|
||||
<a-button type="primary" size="small" @click="handleEdit(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button danger size="small" @click="handleDelete(record)">
|
||||
删除
|
||||
</a-button>
|
||||
<a-button danger size="small" @click="handleDelete(record)"> 删除 </a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
@@ -115,7 +119,12 @@
|
||||
|
||||
<!-- 移动端卡片视图 -->
|
||||
<a-space v-else direction="vertical" :size="16" style="width: 100%">
|
||||
<a-card v-for="user in userStore.users" :key="user.id" size="small" :loading="loading">
|
||||
<a-card
|
||||
v-for="user in userStore.users"
|
||||
:key="user.id"
|
||||
size="small"
|
||||
:loading="loading"
|
||||
>
|
||||
<a-descriptions :column="1" size="small" bordered>
|
||||
<a-descriptions-item label="ID">{{ user.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="用户名">{{ user.alias }}</a-descriptions-item>
|
||||
@@ -131,32 +140,38 @@
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="Token过期">
|
||||
{{ user.jwt_exp && user.jwt_exp !== '0' ? formatDateTime(parseInt(user.jwt_exp) * 1000) : '-' }}
|
||||
{{
|
||||
user.jwt_exp && user.jwt_exp !== '0'
|
||||
? formatDateTime(parseInt(user.jwt_exp) * 1000)
|
||||
: '-'
|
||||
}}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ formatDateTime(user.created_at) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{
|
||||
formatDateTime(user.created_at)
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<a-space class="mt-3" style="width: 100%">
|
||||
<a-button type="primary" size="small" block @click="handleEdit(user)">编辑</a-button>
|
||||
<a-button type="primary" size="small" block @click="handleEdit(user)"
|
||||
>编辑</a-button
|
||||
>
|
||||
<a-button danger size="small" block @click="handleDelete(user)">删除</a-button>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-space>
|
||||
|
||||
<!-- 批量操作 -->
|
||||
<div class="batch-actions" v-if="selectedUsers.length > 0">
|
||||
<div v-if="selectedUsers.length > 0" class="batch-actions">
|
||||
<a-alert
|
||||
:message="`已选择 ${selectedUsers.length} 个用户`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
>
|
||||
<template #description>
|
||||
<a-space style="margin-top: 10px;">
|
||||
<a-space style="margin-top: 10px">
|
||||
<a-button type="primary" size="small" @click="handleBatchApprove">
|
||||
批量审批
|
||||
</a-button>
|
||||
<a-button danger size="small" @click="handleBatchDelete">
|
||||
批量删除
|
||||
</a-button>
|
||||
<a-button danger size="small" @click="handleBatchDelete"> 批量删除 </a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-alert>
|
||||
@@ -167,17 +182,12 @@
|
||||
|
||||
<!-- 创建/编辑用户对话框 -->
|
||||
<a-modal
|
||||
:title="dialogMode === 'create' ? '创建用户' : '编辑用户'"
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogMode === 'create' ? '创建用户' : '编辑用户'"
|
||||
:width="isMobile ? '100%' : 600"
|
||||
:style="isMobile ? { top: 0, maxWidth: '100vw' } : {}"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
|
||||
<a-form-item label="用户名" name="alias">
|
||||
<a-input v-model:value="formData.alias" placeholder="请输入用户名" />
|
||||
</a-form-item>
|
||||
@@ -203,14 +213,12 @@
|
||||
v-model:value="formData.password"
|
||||
:placeholder="dialogMode === 'create' ? '请输入密码' : '留空则不修改密码'"
|
||||
/>
|
||||
<span class="form-hint" v-if="dialogMode === 'edit'">
|
||||
留空则不修改密码
|
||||
</span>
|
||||
<span v-if="dialogMode === 'edit'" class="form-hint"> 留空则不修改密码 </span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="重置密码" v-if="dialogMode === 'edit'">
|
||||
<a-form-item v-if="dialogMode === 'edit'" label="重置密码">
|
||||
<a-switch v-model:checked="formData.reset_password" />
|
||||
<span class="form-hint-danger" v-if="formData.reset_password">
|
||||
<span v-if="formData.reset_password" class="form-hint-danger">
|
||||
⚠️ 将重置为默认密码
|
||||
</span>
|
||||
</a-form-item>
|
||||
@@ -218,9 +226,7 @@
|
||||
|
||||
<template #footer>
|
||||
<a-button @click="dialogVisible = false">取消</a-button>
|
||||
<a-button type="primary" @click="handleSubmit" :loading="submitting">
|
||||
确定
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="submitting" @click="handleSubmit"> 确定 </a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
@@ -228,31 +234,29 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { UserOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons-vue'
|
||||
import Layout from '@/components/Layout.vue'
|
||||
import { useBreakpoint } from '@/composables/useBreakpoint'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAdminStore } from '@/stores/admin'
|
||||
import { adminAPI } from '@/api/index'
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { UserOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons-vue';
|
||||
import Layout from '@/components/Layout.vue';
|
||||
import { useBreakpoint } from '@/composables/useBreakpoint';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { adminAPI } from '@/api/index';
|
||||
|
||||
const userStore = useUserStore()
|
||||
const adminStore = useAdminStore()
|
||||
const { isMobile } = useBreakpoint()
|
||||
const userStore = useUserStore();
|
||||
const { isMobile } = useBreakpoint();
|
||||
|
||||
// 状态
|
||||
const loading = ref(false)
|
||||
const activeTab = ref('all') // 默认展示所有用户
|
||||
const pendingUsers = ref([])
|
||||
const selectedUsers = ref([])
|
||||
const selectedRowKeys = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const dialogMode = ref('create')
|
||||
const submitting = ref(false)
|
||||
const loading = ref(false);
|
||||
const activeTab = ref('all'); // 默认展示所有用户
|
||||
const pendingUsers = ref([]);
|
||||
const selectedUsers = ref([]);
|
||||
const selectedRowKeys = ref([]);
|
||||
const dialogVisible = ref(false);
|
||||
const dialogMode = ref('create');
|
||||
const submitting = ref(false);
|
||||
|
||||
// 表单
|
||||
const formRef = ref(null)
|
||||
const formRef = ref(null);
|
||||
const formData = ref({
|
||||
alias: '',
|
||||
role: 'user',
|
||||
@@ -260,7 +264,7 @@ const formData = ref({
|
||||
email: '',
|
||||
password: '',
|
||||
reset_password: false,
|
||||
})
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
@@ -269,15 +273,13 @@ const formRules = {
|
||||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' },
|
||||
],
|
||||
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
||||
email: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
// 时间格式化
|
||||
const formatDateTime = (timestamp) => {
|
||||
if (!timestamp) return '-'
|
||||
const date = new Date(timestamp)
|
||||
const formatDateTime = timestamp => {
|
||||
if (!timestamp) return '-';
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
@@ -285,8 +287,8 @@ const formatDateTime = (timestamp) => {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 待审批用户表格列
|
||||
const pendingColumns = [
|
||||
@@ -295,7 +297,7 @@ const pendingColumns = [
|
||||
{ title: '邮箱', dataIndex: 'email', key: 'email', ellipsis: true },
|
||||
{ title: '注册时间', dataIndex: 'created_at', key: 'created_at', width: 180 },
|
||||
{ title: '操作', key: 'actions', width: 200, fixed: 'right' },
|
||||
]
|
||||
];
|
||||
|
||||
// 所有用户表格列
|
||||
const allColumns = [
|
||||
@@ -307,40 +309,40 @@ const allColumns = [
|
||||
{ title: 'Token 过期时间', dataIndex: 'jwt_exp', key: 'jwt_exp', width: 180 },
|
||||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 180 },
|
||||
{ title: '操作', key: 'actions', width: 200, fixed: 'right' },
|
||||
]
|
||||
];
|
||||
|
||||
// 行选择配置
|
||||
const rowSelection = {
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: (keys, rows) => {
|
||||
selectedRowKeys.value = keys
|
||||
selectedUsers.value = rows
|
||||
selectedRowKeys.value = keys;
|
||||
selectedUsers.value = rows;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// 获取待审批用户
|
||||
const fetchPendingUsers = async () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
pendingUsers.value = await adminAPI.getPendingUsers()
|
||||
pendingUsers.value = await adminAPI.getPendingUsers();
|
||||
} catch (error) {
|
||||
message.error(error.message || '获取待审批用户失败')
|
||||
message.error(error.message || '获取待审批用户失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Tab 切换
|
||||
const handleTabChange = (tab) => {
|
||||
const handleTabChange = tab => {
|
||||
if (tab === 'pending') {
|
||||
fetchPendingUsers()
|
||||
fetchPendingUsers();
|
||||
} else {
|
||||
handleRefresh()
|
||||
handleRefresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 审批通过用户
|
||||
const handleApprove = async (user) => {
|
||||
const handleApprove = async user => {
|
||||
Modal.confirm({
|
||||
title: '审批确认',
|
||||
content: `确认通过用户 "${user.alias}" 的审批吗?`,
|
||||
@@ -348,18 +350,18 @@ const handleApprove = async (user) => {
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await adminAPI.approveUser(user.id)
|
||||
message.success('审批成功')
|
||||
fetchPendingUsers()
|
||||
await adminAPI.approveUser(user.id);
|
||||
message.success('审批成功');
|
||||
fetchPendingUsers();
|
||||
} catch (error) {
|
||||
message.error(error.message || '审批失败')
|
||||
message.error(error.message || '审批失败');
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 拒绝用户
|
||||
const handleReject = async (user) => {
|
||||
const handleReject = async user => {
|
||||
Modal.confirm({
|
||||
title: '拒绝确认',
|
||||
content: `确认拒绝用户 "${user.alias}" 的申请吗?拒绝后将删除该用户。`,
|
||||
@@ -368,36 +370,36 @@ const handleReject = async (user) => {
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await adminAPI.rejectUser(user.id)
|
||||
message.success('已拒绝并删除用户')
|
||||
fetchPendingUsers()
|
||||
await adminAPI.rejectUser(user.id);
|
||||
message.success('已拒绝并删除用户');
|
||||
fetchPendingUsers();
|
||||
} catch (error) {
|
||||
message.error(error.message || '操作失败')
|
||||
message.error(error.message || '操作失败');
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 刷新数据
|
||||
const handleRefresh = async () => {
|
||||
if (activeTab.value === 'pending') {
|
||||
await fetchPendingUsers()
|
||||
await fetchPendingUsers();
|
||||
} else {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
await userStore.fetchUsers()
|
||||
message.success('刷新成功')
|
||||
await userStore.fetchUsers();
|
||||
message.success('刷新成功');
|
||||
} catch (error) {
|
||||
message.error(error.message || '刷新失败')
|
||||
message.error(error.message || '刷新失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 创建用户
|
||||
const handleCreate = () => {
|
||||
dialogMode.value = 'create'
|
||||
dialogMode.value = 'create';
|
||||
formData.value = {
|
||||
alias: '',
|
||||
role: 'user',
|
||||
@@ -405,13 +407,13 @@ const handleCreate = () => {
|
||||
email: '',
|
||||
password: '',
|
||||
reset_password: false,
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
};
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 编辑用户
|
||||
const handleEdit = (user) => {
|
||||
dialogMode.value = 'edit'
|
||||
const handleEdit = user => {
|
||||
dialogMode.value = 'edit';
|
||||
formData.value = {
|
||||
id: user.id,
|
||||
alias: user.alias,
|
||||
@@ -420,44 +422,44 @@ const handleEdit = (user) => {
|
||||
email: user.email || '',
|
||||
password: '',
|
||||
reset_password: false,
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
};
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
if (!formRef.value) return;
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
submitting.value = true
|
||||
await formRef.value.validate();
|
||||
submitting.value = true;
|
||||
|
||||
// 检查密码设置冲突
|
||||
if (dialogMode.value === 'edit' && formData.value.password && formData.value.reset_password) {
|
||||
message.warning('不能同时设置新密码和重置密码,请选择其一')
|
||||
submitting.value = false
|
||||
return
|
||||
message.warning('不能同时设置新密码和重置密码,请选择其一');
|
||||
submitting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogMode.value === 'create') {
|
||||
await userStore.createUser(formData.value)
|
||||
message.success('创建成功')
|
||||
await userStore.createUser(formData.value);
|
||||
message.success('创建成功');
|
||||
} else {
|
||||
await userStore.updateUser(formData.value.id, formData.value)
|
||||
message.success('更新成功')
|
||||
await userStore.updateUser(formData.value.id, formData.value);
|
||||
message.success('更新成功');
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
await handleRefresh()
|
||||
dialogVisible.value = false;
|
||||
await handleRefresh();
|
||||
} catch (error) {
|
||||
message.error(error.message || '操作失败')
|
||||
message.error(error.message || '操作失败');
|
||||
} finally {
|
||||
submitting.value = false
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const handleDelete = (user) => {
|
||||
const handleDelete = user => {
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: `确定要删除用户 "${user.alias}" 吗?`,
|
||||
@@ -466,15 +468,15 @@ const handleDelete = (user) => {
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await userStore.deleteUser(user.id)
|
||||
message.success('删除成功')
|
||||
await handleRefresh()
|
||||
await userStore.deleteUser(user.id);
|
||||
message.success('删除成功');
|
||||
await handleRefresh();
|
||||
} catch (error) {
|
||||
message.error(error.message || '删除失败')
|
||||
message.error(error.message || '删除失败');
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 批量审批
|
||||
const handleBatchApprove = () => {
|
||||
@@ -484,24 +486,24 @@ const handleBatchApprove = () => {
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
const userIds = selectedUsers.value.map((u) => u.id)
|
||||
let successCount = 0
|
||||
let failureCount = 0
|
||||
const userIds = selectedUsers.value.map(u => u.id);
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
for (const userId of userIds) {
|
||||
try {
|
||||
await adminAPI.approveUser(userId)
|
||||
successCount++
|
||||
} catch (error) {
|
||||
failureCount++
|
||||
await adminAPI.approveUser(userId);
|
||||
successCount++;
|
||||
} catch {
|
||||
failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
message.success(`批量审批完成:成功 ${successCount},失败 ${failureCount}`)
|
||||
await handleRefresh()
|
||||
message.success(`批量审批完成:成功 ${successCount},失败 ${failureCount}`);
|
||||
await handleRefresh();
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = () => {
|
||||
@@ -512,29 +514,29 @@ const handleBatchDelete = () => {
|
||||
cancelText: '取消',
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
const userIds = selectedUsers.value.map((u) => u.id)
|
||||
let successCount = 0
|
||||
let failureCount = 0
|
||||
const userIds = selectedUsers.value.map(u => u.id);
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
for (const userId of userIds) {
|
||||
try {
|
||||
await userStore.deleteUser(userId)
|
||||
successCount++
|
||||
} catch (error) {
|
||||
failureCount++
|
||||
await userStore.deleteUser(userId);
|
||||
successCount++;
|
||||
} catch {
|
||||
failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
message.success(`批量删除完成:成功 ${successCount},失败 ${failureCount}`)
|
||||
await handleRefresh()
|
||||
message.success(`批量删除完成:成功 ${successCount},失败 ${failureCount}`);
|
||||
await handleRefresh();
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 默认加载所有用户
|
||||
handleRefresh()
|
||||
})
|
||||
handleRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user