更新用户权限模块功能
This commit is contained in:
@@ -324,4 +324,40 @@ class DepartmentService
|
||||
}
|
||||
return $this->isDescendant($id, $child->parent_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门及所有子部门的ID列表
|
||||
*
|
||||
* @param int $departmentId 部门ID
|
||||
* @param array $departments 所有部门数据
|
||||
* @return array
|
||||
*/
|
||||
public function getDepartmentAndChildrenIds(int $departmentId, array $departments = null): array
|
||||
{
|
||||
if ($departments === null) {
|
||||
$departments = Department::where('status', 1)->get()->keyBy('id')->toArray();
|
||||
}
|
||||
|
||||
$ids = [$departmentId];
|
||||
$this->collectChildrenIds($departmentId, $departments, $ids);
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集子部门ID
|
||||
*
|
||||
* @param int $parentId 父部门ID
|
||||
* @param array $departments 所有部门数据
|
||||
* @param array &$ids ID收集数组
|
||||
*/
|
||||
private function collectChildrenIds(int $parentId, array $departments, array &$ids): void
|
||||
{
|
||||
foreach ($departments as $department) {
|
||||
if ($department['parent_id'] == $parentId) {
|
||||
$ids[] = $department['id'];
|
||||
$this->collectChildrenIds($department['id'], $departments, $ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -14,6 +15,20 @@ use App\Jobs\Auth\UserExportJob;
|
||||
|
||||
class UserService
|
||||
{
|
||||
protected $departmentService;
|
||||
|
||||
public function __construct(DepartmentService $departmentService)
|
||||
{
|
||||
$this->departmentService = $departmentService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户ID
|
||||
*/
|
||||
protected function getCurrentUserId(): int
|
||||
{
|
||||
return Auth::guard('admin')->id();
|
||||
}
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
@@ -32,7 +47,9 @@ class UserService
|
||||
}
|
||||
|
||||
if (!empty($params['department_id'])) {
|
||||
$query->where('department_id', $params['department_id']);
|
||||
// 获取部门及所有子部门的ID
|
||||
$departmentIds = $this->departmentService->getDepartmentAndChildrenIds($params['department_id']);
|
||||
$query->whereIn('department_id', $departmentIds);
|
||||
}
|
||||
|
||||
if (isset($params['status']) && $params['status'] !== '') {
|
||||
@@ -226,6 +243,15 @@ class UserService
|
||||
*/
|
||||
public function batchDelete(array $ids): int
|
||||
{
|
||||
$currentUserId = $this->getCurrentUserId();
|
||||
|
||||
// 检查是否包含当前用户
|
||||
if (in_array($currentUserId, $ids)) {
|
||||
throw ValidationException::withMessages([
|
||||
'ids' => ['不能删除当前登录用户'],
|
||||
]);
|
||||
}
|
||||
|
||||
return User::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
@@ -234,6 +260,15 @@ class UserService
|
||||
*/
|
||||
public function batchUpdateStatus(array $ids, int $status): int
|
||||
{
|
||||
$currentUserId = $this->getCurrentUserId();
|
||||
|
||||
// 如果是禁用操作,检查是否包含当前用户
|
||||
if ($status === 0 && in_array($currentUserId, $ids)) {
|
||||
throw ValidationException::withMessages([
|
||||
'ids' => ['不能禁用当前登录用户'],
|
||||
]);
|
||||
}
|
||||
|
||||
return User::whereIn('id', $ids)->update(['status' => $status]);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<a-tree ref="menuTreeRef" v-model:checkedKeys="checkedPermissionIds" :tree-data="permissionTree"
|
||||
:field-names="fieldNames" :checkable="true" :default-expand-all="true"
|
||||
:check-strictly="false" :selectable="false">
|
||||
<template #title="{ name }">
|
||||
{{ name }}
|
||||
<template #title="{ title }">
|
||||
{{ title }}
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@ const checkedPermissionIds = ref([])
|
||||
|
||||
// 树字段映射
|
||||
const fieldNames = {
|
||||
title: 'name',
|
||||
title: 'title',
|
||||
key: 'id',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<a-modal v-model:open="visible" title="批量分配角色" :confirm-loading="loading" @ok="handleOk" @cancel="handleCancel">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<a-form-item label="角色">
|
||||
<a-select
|
||||
v-model:value="selectedRoleIds"
|
||||
mode="multiple"
|
||||
placeholder="请选择角色"
|
||||
allow-clear
|
||||
:options="roleOptions"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
:loading="roleLoading"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const roleLoading = ref(false)
|
||||
const roleOptions = ref([])
|
||||
const userIds = ref([])
|
||||
const selectedRoleIds = ref([])
|
||||
|
||||
// 打开弹窗
|
||||
const open = async (ids) => {
|
||||
visible.value = true
|
||||
userIds.value = ids
|
||||
selectedRoleIds.value = []
|
||||
await loadRoles()
|
||||
}
|
||||
|
||||
// 加载角色列表
|
||||
const loadRoles = async () => {
|
||||
try {
|
||||
roleLoading.value = true
|
||||
const res = await authApi.roles.list.get({ page_size: 1000 })
|
||||
if (res.code === 200) {
|
||||
roleOptions.value = (res.data.list || []).map(role => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
code: role.code
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载角色列表失败:', error)
|
||||
} finally {
|
||||
roleLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 角色过滤
|
||||
const filterOption = (input, option) => {
|
||||
const name = option?.name?.toLowerCase() || ''
|
||||
const code = option?.code?.toLowerCase() || ''
|
||||
const keyword = input?.toLowerCase() || ''
|
||||
return name.includes(keyword) || code.includes(keyword)
|
||||
}
|
||||
|
||||
// 确认
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await authApi.users.batchRoles.post({
|
||||
ids: userIds.value,
|
||||
role_ids: selectedRoleIds.value
|
||||
})
|
||||
|
||||
if (res.code === 200) {
|
||||
message.success('分配成功')
|
||||
emit('success')
|
||||
handleCancel()
|
||||
} else {
|
||||
message.error(res.message || '分配失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量分配角色失败:', error)
|
||||
message.error(error.message || '分配失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
loading.value = false
|
||||
selectedRoleIds.value = []
|
||||
}
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<a-modal v-model:open="visible" title="批量分配部门" :confirm-loading="loading" @ok="handleOk" @cancel="handleCancel">
|
||||
<a-form :model="formState" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<a-form-item label="部门">
|
||||
<a-tree-select
|
||||
v-model:value="formState.department_id"
|
||||
:tree-data="departmentTree"
|
||||
placeholder="请选择部门"
|
||||
allow-clear
|
||||
:field-names="{ label: 'name', value: 'id', children: 'children' }"
|
||||
tree-default-expand-all
|
||||
show-search
|
||||
:filter-tree-node="filterTreeNode"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const departmentTree = ref([])
|
||||
const userIds = ref([])
|
||||
|
||||
const formState = reactive({
|
||||
department_id: undefined
|
||||
})
|
||||
|
||||
// 打开弹窗
|
||||
const open = (ids) => {
|
||||
visible.value = true
|
||||
userIds.value = ids
|
||||
formState.department_id = undefined
|
||||
loadDepartmentTree()
|
||||
}
|
||||
|
||||
// 加载部门树
|
||||
const loadDepartmentTree = async () => {
|
||||
try {
|
||||
const res = await authApi.departments.tree.get()
|
||||
if (res.code === 200) {
|
||||
departmentTree.value = res.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载部门树失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 树节点过滤
|
||||
const filterTreeNode = (inputValue, treeNode) => {
|
||||
const name = treeNode.dataRef.name
|
||||
return name ? name.toLowerCase().includes(inputValue.toLowerCase()) : false
|
||||
}
|
||||
|
||||
// 确认
|
||||
const handleOk = async () => {
|
||||
if (!formState.department_id) {
|
||||
message.warning('请选择部门')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await authApi.users.batchDepartment.post({
|
||||
ids: userIds.value,
|
||||
department_id: formState.department_id
|
||||
})
|
||||
|
||||
if (res.code === 200) {
|
||||
message.success('分配成功')
|
||||
emit('success')
|
||||
handleCancel()
|
||||
} else {
|
||||
message.error(res.message || '分配失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量分配部门失败:', error)
|
||||
message.error(error.message || '分配失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
loading.value = false
|
||||
formState.department_id = undefined
|
||||
}
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
@@ -1,18 +1,23 @@
|
||||
<template>
|
||||
<a-modal title="角色设置" :open="visible" :width="500" :destroy-on-close="true" :footer="null" @cancel="handleCancel">
|
||||
<div class="role-content">
|
||||
<a-checkbox-group v-model:value="checkedRoles" style="width: 100%">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-checkbox v-for="role in roleList" :key="role.id" :value="role.id" style="width: 100%">
|
||||
{{ role.name }}
|
||||
</a-checkbox>
|
||||
</a-space>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button @click="handleCancel">取 消</a-button>
|
||||
<a-button type="primary" :loading="isSaveing" @click="submit">保 存</a-button>
|
||||
</template>
|
||||
<a-modal v-model:open="visible" title="设置角色" :confirm-loading="loading" @ok="handleOk" @cancel="handleCancel">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<a-form-item label="用户">
|
||||
<a-input :value="userForm.username" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="角色">
|
||||
<a-select
|
||||
v-model:value="selectedRoleIds"
|
||||
mode="multiple"
|
||||
placeholder="请选择角色"
|
||||
allow-clear
|
||||
:options="roleOptions"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
:loading="roleLoading"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
@@ -21,104 +26,100 @@ import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
const emit = defineEmits(['success', 'closed'])
|
||||
|
||||
const visible = ref(false)
|
||||
const isSaveing = ref(false)
|
||||
const loading = ref(false)
|
||||
const roleLoading = ref(false)
|
||||
const roleOptions = ref([])
|
||||
const selectedRoleIds = ref([])
|
||||
const userId = ref(null)
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
role_ids: []
|
||||
const userForm = reactive({
|
||||
username: ''
|
||||
})
|
||||
|
||||
// 选中的角色
|
||||
const checkedRoles = ref([])
|
||||
|
||||
// 角色列表
|
||||
const roleList = ref([])
|
||||
|
||||
// 打开对话框
|
||||
// 打开弹窗
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
return {
|
||||
open,
|
||||
setData,
|
||||
close
|
||||
loadRoles()
|
||||
}
|
||||
|
||||
// 设置用户数据
|
||||
const setData = (user) => {
|
||||
userId.value = user.id
|
||||
userForm.username = user.username
|
||||
// 设置已选择的角色
|
||||
selectedRoleIds.value = (user.roles || []).map(role => role.id)
|
||||
}
|
||||
|
||||
// 加载角色列表
|
||||
const loadRoles = async () => {
|
||||
try {
|
||||
roleLoading.value = true
|
||||
const res = await authApi.roles.list.get({ page_size: 1000 })
|
||||
if (res.code === 200) {
|
||||
roleOptions.value = (res.data.list || []).map(role => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
code: role.code
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载角色列表失败:', error)
|
||||
} finally {
|
||||
roleLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
const close = () => {
|
||||
visible.value = false
|
||||
// 角色过滤
|
||||
const filterOption = (input, option) => {
|
||||
const name = option?.name?.toLowerCase() || ''
|
||||
const code = option?.code?.toLowerCase() || ''
|
||||
const keyword = input?.toLowerCase() || ''
|
||||
return name.includes(keyword) || code.includes(keyword)
|
||||
}
|
||||
|
||||
// 处理取消
|
||||
const handleCancel = () => {
|
||||
emit('closed')
|
||||
visible.value = false
|
||||
}
|
||||
// 确认
|
||||
const handleOk = async () => {
|
||||
if (!userId.value) {
|
||||
message.warning('用户ID不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
// 提交保存
|
||||
const submit = async () => {
|
||||
try {
|
||||
isSaveing.value = true
|
||||
|
||||
// 获取选中的角色 ID
|
||||
form.role_ids = checkedRoles.value || []
|
||||
|
||||
loading.value = true
|
||||
const res = await authApi.users.batchRoles.post({
|
||||
ids: [form.id],
|
||||
role_ids: form.role_ids
|
||||
ids: [userId.value],
|
||||
role_ids: selectedRoleIds.value
|
||||
})
|
||||
|
||||
isSaveing.value = false
|
||||
if (res.code === 200) {
|
||||
emit('success', form)
|
||||
visible.value = false
|
||||
message.success('操作成功')
|
||||
message.success('设置成功')
|
||||
emit('success')
|
||||
handleCancel()
|
||||
} else {
|
||||
message.error(res.message || '操作失败')
|
||||
message.error(res.message || '设置失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存角色失败:', error)
|
||||
isSaveing.value = false
|
||||
message.error('操作失败')
|
||||
console.error('设置角色失败:', error)
|
||||
message.error(error.message || '设置失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色列表
|
||||
const getRoles = async () => {
|
||||
try {
|
||||
const res = await authApi.roles.all.get()
|
||||
roleList.value = res.data || []
|
||||
} catch (error) {
|
||||
console.error('获取角色列表失败:', error)
|
||||
message.error('获取角色列表失败')
|
||||
}
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
loading.value = false
|
||||
userId.value = null
|
||||
userForm.username = ''
|
||||
selectedRoleIds.value = []
|
||||
}
|
||||
|
||||
// 设置数据
|
||||
const setData = (data) => {
|
||||
form.id = data.id
|
||||
checkedRoles.value = data.roles ? data.roles.map(item => item.id) : []
|
||||
}
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
// 组件挂载时加载数据
|
||||
getRoles()
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
open,
|
||||
setData,
|
||||
close
|
||||
setData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.role-content {
|
||||
padding: 20px 0;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="body">
|
||||
<a-tree v-model:selectedKeys="selectedDeptKeys" :tree-data="filteredDepartmentTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }" show-line @select="onDeptSelect">
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }" show-line default-expand-all @select="onDeptSelect">
|
||||
<template #icon="{ dataRef }">
|
||||
<ApartmentOutlined v-if="dataRef.children && dataRef.children.length > 0" />
|
||||
<UserOutlined v-else />
|
||||
@@ -127,6 +127,12 @@
|
||||
<!-- 角色设置弹窗 -->
|
||||
<role-dialog v-if="dialog.role" ref="roleDialogRef" @success="handleRoleSuccess" @closed="dialog.role = false" />
|
||||
|
||||
<!-- 批量分配部门弹窗 -->
|
||||
<department-dialog v-if="dialog.department" ref="departmentDialogRef" @success="handleDepartmentSuccess" @closed="dialog.department = false" />
|
||||
|
||||
<!-- 批量分配角色弹窗 -->
|
||||
<batch-role-dialog v-if="dialog.batchRole" ref="batchRoleDialogRef" @success="handleBatchRoleSuccess" @closed="dialog.batchRole = false" />
|
||||
|
||||
<!-- 导入用户弹窗 -->
|
||||
<sc-import v-model:open="dialog.import" title="导入用户" :api="authApi.users.import.post"
|
||||
:template-api="authApi.users.downloadTemplate.get" filename="用户" @success="handleImportSuccess" />
|
||||
@@ -159,6 +165,8 @@ import scImport from '@/components/scImport/index.vue'
|
||||
import scExport from '@/components/scExport/index.vue'
|
||||
import saveDialog from './components/SaveDialog.vue'
|
||||
import roleDialog from './components/RoleDialog.vue'
|
||||
import departmentDialog from './components/DepartmentDialog.vue'
|
||||
import batchRoleDialog from './components/BatchRoleDialog.vue'
|
||||
import authApi from '@/api/auth'
|
||||
import { useTable } from '@/hooks/useTable'
|
||||
|
||||
@@ -200,6 +208,8 @@ const {
|
||||
const dialog = reactive({
|
||||
save: false,
|
||||
role: false,
|
||||
department: false,
|
||||
batchRole: false,
|
||||
import: false,
|
||||
export: false
|
||||
})
|
||||
@@ -207,6 +217,8 @@ const dialog = reactive({
|
||||
// 弹窗引用
|
||||
const saveDialogRef = ref(null)
|
||||
const roleDialogRef = ref(null)
|
||||
const departmentDialogRef = ref(null)
|
||||
const batchRoleDialogRef = ref(null)
|
||||
|
||||
// 部门树数据
|
||||
const departmentTree = ref([])
|
||||
@@ -366,8 +378,10 @@ const handleBatchDepartment = () => {
|
||||
message.warning('请选择要分配部门的用户')
|
||||
return
|
||||
}
|
||||
// TODO: 实现批量分配部门弹窗
|
||||
message.info('批量分配部门功能开发中...')
|
||||
dialog.department = true
|
||||
setTimeout(() => {
|
||||
departmentDialogRef.value?.open(selectedRows.value.map(item => item.id))
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 批量分配角色
|
||||
@@ -376,8 +390,10 @@ const handleBatchRoles = () => {
|
||||
message.warning('请选择要分配角色的用户')
|
||||
return
|
||||
}
|
||||
// TODO: 实现批量分配角色弹窗
|
||||
message.info('批量分配角色功能开发中...')
|
||||
dialog.batchRole = true
|
||||
setTimeout(() => {
|
||||
batchRoleDialogRef.value?.open(selectedRows.value.map(item => item.id))
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
@@ -472,7 +488,10 @@ const handleEdit = (record) => {
|
||||
const handleRole = (record) => {
|
||||
dialog.role = true
|
||||
setTimeout(() => {
|
||||
roleDialogRef.value?.open().setData(record)
|
||||
if (roleDialogRef.value) {
|
||||
roleDialogRef.value.open()
|
||||
roleDialogRef.value.setData(record)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
@@ -502,6 +521,18 @@ const handleRoleSuccess = () => {
|
||||
refreshTable()
|
||||
}
|
||||
|
||||
// 批量分配部门成功回调
|
||||
const handleDepartmentSuccess = () => {
|
||||
selectedRows.value = []
|
||||
refreshTable()
|
||||
}
|
||||
|
||||
// 批量分配角色成功回调
|
||||
const handleBatchRoleSuccess = () => {
|
||||
selectedRows.value = []
|
||||
refreshTable()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadDepartmentTree()
|
||||
|
||||
Reference in New Issue
Block a user