serv\web- 多租户修改

This commit is contained in:
2025-10-29 19:08:22 +08:00
parent c5c134fbb3
commit 82b6f14e64
86 changed files with 4446 additions and 2730 deletions

View File

@@ -6,7 +6,6 @@
<el-tabs v-model="activeTab">
<el-tab-pane label="学习记录" name="task-records">
<StudyRecords />
</el-tab-pane>
</el-tabs>
</div>
@@ -17,7 +16,6 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ElTabs, ElTabPane } from 'element-plus';
import StudyRecords from './components/StudyRecords.vue';
import { AdminLayout } from '@/views/admin';
defineOptions({
name: 'StudyManagementView'

View File

@@ -112,60 +112,19 @@
</el-dialog>
<!-- 绑定角色对话框 -->
<el-dialog v-model="bindRoleDialogVisible" title="绑定角色" width="800px" @close="resetBindList">
<div class="role-binding-container">
<!-- 部门信息显示 -->
<div class="dept-info" v-if="currentDept">
<h4>部门信息{{ currentDept.name }}</h4>
<p>部门ID{{ currentDept.deptID }}</p>
</div>
<!-- 角色绑定状态表格 -->
<el-table :data="roleList" style="width: 100%" border stripe>
<el-table-column width="80" label="绑定状态">
<template #default="{ row }">
<el-tag
:type="isRoleSelected(row.roleID) ? 'success' : 'info'"
size="small"
>
{{ isRoleSelected(row.roleID) ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="角色名称" min-width="150" />
<el-table-column prop="roleID" label="角色ID" min-width="120" />
<el-table-column prop="description" label="角色描述" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button
:type="isRoleSelected(row.roleID) ? 'danger' : 'primary'"
size="small"
@click="toggleRoleSelection(row)"
>
{{ isRoleSelected(row.roleID) ? '解绑' : '绑定' }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 统计信息 -->
<div class="binding-stats">
<el-alert
:title="`已绑定 ${selectedRoles.length} 个角色,未绑定 ${roleList.length - selectedRoles.length} 个角色`"
type="info"
:closable="false"
show-icon
/>
</div>
</div>
<template #footer>
<el-button @click="bindRoleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveRoleBinding" :loading="submitting">
保存
</el-button>
</template>
</el-dialog>
<GenericSelector
v-model:visible="bindRoleDialogVisible"
:title="`绑定角色 - ${currentDept?.name || ''}`"
left-title="可选角色"
right-title="已选角色"
:fetch-available-api="fetchAllRoles"
:fetch-selected-api="fetchDeptRoles"
:item-config="{ id: 'roleID', label: 'name', sublabel: 'description' }"
unit-name=""
search-placeholder="搜索角色名称或描述..."
@confirm="handleRoleConfirm"
@cancel="resetBindList"
/>
</div>
</AdminLayout>
</template>
@@ -175,6 +134,7 @@ import { deptApi } from '@/apis/system/dept';
import { roleApi } from '@/apis/system/role';
import { SysDept, SysRole } from '@/types';
import { AdminLayout } from '@/views/admin';
import { GenericSelector } from '@/components/base';
defineOptions({
name: 'DeptManageView'
@@ -190,12 +150,7 @@ const submitting = ref(false);
const treeRef = ref();
// 角色绑定相关数据
const roleList = ref<SysRole[]>([]);
const selectedRoles = ref<string[]>([]);
const currentDept = ref<SysDept | null>(null);
const bindList = ref<{ roles: SysRole[] }>({
roles: []
});
// 对话框状态
const dialogVisible = ref(false);
@@ -452,59 +407,39 @@ function resetForm() {
});
}
// 获取所有可选角色的接口
async function fetchAllRoles() {
return await roleApi.getAllRoles();
}
// 获取部门已绑定角色的接口
async function fetchDeptRoles() {
if (!currentDept.value) {
return {
success: true,
dataList: [],
code: 200,
message: '',
login: true,
auth: true
};
}
return await deptApi.getDeptByRole(currentDept.value);
}
// 查看绑定角色
async function handleBindRole(row: SysDept) {
currentDept.value = row;
try {
// 获取所有角色
const roleResult = await roleApi.getAllRoles();
roleList.value = roleResult.dataList || [];
// 获取已绑定的角色
const bindingResult = await deptApi.getDeptByRole(row);
bindList.value.roles = bindingResult.dataList || [];
// 设置已选中的角色
selectedRoles.value = bindList.value.roles.map(role => role.roleID).filter((id): id is string => !!id);
console.log('已绑定的角色:', bindList.value.roles);
console.log('所有角色:', roleList.value);
bindRoleDialogVisible.value = true;
} catch (error) {
console.error('获取角色绑定信息失败:', error);
ElMessage.error('获取角色绑定信息失败');
}
bindRoleDialogVisible.value = true;
}
// 重置绑定列表
function resetBindList() {
bindList.value = {
roles: []
};
selectedRoles.value = [];
currentDept.value = null;
}
// 检查角色是否已选中
function isRoleSelected(roleID: string | undefined): boolean {
return roleID ? selectedRoles.value.includes(roleID) : false;
}
// 切换角色选择状态
function toggleRoleSelection(role: SysRole) {
if (!role.roleID) return;
const index = selectedRoles.value.indexOf(role.roleID);
if (index > -1) {
selectedRoles.value.splice(index, 1);
} else {
selectedRoles.value.push(role.roleID);
}
}
// 保存角色绑定
async function saveRoleBinding() {
// 角色选择确认 - 在confirm时提交请求
async function handleRoleConfirm(items: SysRole[]) {
if (!currentDept.value || !currentDept.value.deptID) {
ElMessage.error('部门信息不完整');
return;
@@ -513,21 +448,22 @@ async function saveRoleBinding() {
try {
submitting.value = true;
// 获取当前已绑定的角色ID
const currentBoundRoles = (bindList.value.roles || []).map(role => role.roleID).filter((id): id is string => !!id);
// 获取当前已绑定的角色
const currentBoundResult = await deptApi.getDeptByRole(currentDept.value);
const currentBoundIds = (currentBoundResult.dataList || []).map(r => r.roleID).filter((id): id is string => !!id);
// 新选择的角色ID
const newSelectedIds = items.map(r => r.roleID).filter((id): id is string => !!id);
// 找出需要绑定的角色(新增的)
const rolesToBind = selectedRoles.value.filter(roleID => !currentBoundRoles.includes(roleID));
const rolesToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
// 找出需要解绑的角色(移除的)
const rolesToUnbind = currentBoundRoles.filter(roleID => !selectedRoles.value.includes(roleID));
const rolesToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
// 构建需要绑定的角色对象数组
if (rolesToBind.length > 0) {
const rolesToBindObjects = rolesToBind.map(roleID => {
const role = roleList.value.find(r => r.roleID === roleID);
return role || { roleID: roleID };
});
const rolesToBindObjects = items.filter(r => r.roleID && rolesToBind.includes(r.roleID));
const bindDept = {
dept: currentDept.value,
@@ -539,10 +475,7 @@ async function saveRoleBinding() {
// 构建需要解绑的角色对象数组
if (rolesToUnbind.length > 0) {
const rolesToUnbindObjects = rolesToUnbind.map(roleID => {
const role = roleList.value.find(r => r.roleID === roleID);
return role || { roleID: roleID };
});
const rolesToUnbindObjects = (currentBoundResult.dataList || []).filter(r => r.roleID && rolesToUnbind.includes(r.roleID));
const unbindDept = {
dept: currentDept.value,
@@ -553,7 +486,6 @@ async function saveRoleBinding() {
}
ElMessage.success('角色绑定保存成功');
bindRoleDialogVisible.value = false;
// 刷新部门列表
await loadDeptList();
@@ -799,30 +731,4 @@ async function handleNodeDrop(draggingNode: any, dropNode: any, dropType: string
}
}
}
// 角色绑定容器样式
.role-binding-container {
.dept-info {
background: #f5f7fa;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
h4 {
margin: 0 0 8px 0;
color: #303133;
font-size: 16px;
}
p {
margin: 0;
color: #606266;
font-size: 14px;
}
}
.binding-stats {
margin-top: 20px;
}
}
</style>

View File

@@ -30,9 +30,12 @@
<template #default="{ data }">
<div class="custom-tree-node">
<div class="node-label">
<el-icon v-if="data.icon" class="node-icon">
<component :is="data.icon" />
</el-icon>
<img
v-if="data.icon"
:src="PUBLIC_IMG_PATH + '/' + data.icon"
class="node-icon"
:alt="data.name"
/>
<span class="node-name">{{ data.name }}</span>
<el-tag
:type="getMenuTypeTagType(data.type)"
@@ -156,61 +159,19 @@
</el-dialog>
<!-- 绑定权限对话框 -->
<el-dialog v-model="bindPermissionDialogVisible" title="绑定权限" width="800px" @close="resetBindList">
<div class="permission-binding-container">
<!-- 菜单信息显示 -->
<div class="menu-info" v-if="currentMenu">
<h4>菜单信息{{ currentMenu.name }}</h4>
<p>菜单ID{{ currentMenu.menuID }}</p>
</div>
<!-- 权限绑定状态表格 -->
<el-table :data="permissionList" style="width: 100%" border stripe>
<el-table-column width="80" label="绑定状态">
<template #default="{ row }">
<el-tag
:type="isPermissionSelected(row.permissionID) ? 'success' : 'info'"
size="small"
>
{{ isPermissionSelected(row.permissionID) ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="权限名称" min-width="150" />
<el-table-column prop="permissionID" label="权限ID" min-width="120" />
<el-table-column prop="code" label="权限编码" min-width="150" />
<el-table-column prop="description" label="权限描述" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button
:type="isPermissionSelected(row.permissionID) ? 'danger' : 'primary'"
size="small"
@click="togglePermissionSelection(row)"
>
{{ isPermissionSelected(row.permissionID) ? '解绑' : '绑定' }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 统计信息 -->
<div class="binding-stats">
<el-alert
:title="`已绑定 ${selectedPermissions.length} 个权限,未绑定 ${permissionList.length - selectedPermissions.length} 个权限`"
type="info"
:closable="false"
show-icon
/>
</div>
</div>
<template #footer>
<el-button @click="bindPermissionDialogVisible = false">取消</el-button>
<el-button type="primary" @click="savePermissionBinding" :loading="submitting">
保存
</el-button>
</template>
</el-dialog>
<GenericSelector
v-model:visible="bindPermissionDialogVisible"
:title="`绑定权限 - ${currentMenu?.name || ''}`"
left-title="可选权限"
right-title="已选权限"
:fetch-available-api="fetchAllPermissions"
:fetch-selected-api="fetchMenuPermissions"
:item-config="{ id: 'permissionID', label: 'name', sublabel: 'code' }"
unit-name=""
search-placeholder="搜索权限名称或编码..."
@confirm="handlePermissionConfirm"
@cancel="resetBindList"
/>
</div>
</AdminLayout>
</template>
@@ -220,6 +181,8 @@ import { menuApi } from '@/apis/system/menu';
import { permissionApi } from '@/apis/system/permission';
import { SysMenu, SysPermission } from '@/types';
import { AdminLayout } from '@/views/admin';
import { PUBLIC_IMG_PATH } from '@/config';
import { GenericSelector } from '@/components/base';
defineOptions({
name: 'MenuManageView'
@@ -235,12 +198,7 @@ const submitting = ref(false);
const treeRef = ref();
// 权限绑定相关数据
const permissionList = ref<SysPermission[]>([]);
const selectedPermissions = ref<string[]>([]);
const currentMenu = ref<SysMenu | null>(null);
const bindList = ref<{ permissions: SysPermission[] }>({
permissions: []
});
// 对话框状态
const dialogVisible = ref(false);
@@ -533,65 +491,38 @@ function resetForm() {
});
}
// 获取所有可选权限的接口
async function fetchAllPermissions() {
const permission: SysPermission = {
permissionID: undefined,
name: undefined,
code: undefined,
description: undefined,
};
return await permissionApi.getPermissionList(permission);
}
// 获取菜单已绑定权限的接口
async function fetchMenuPermissions() {
if (!currentMenu.value?.menuID) {
return { success: true, dataList: [] };
}
return await menuApi.getMenuPermission(currentMenu.value.menuID);
}
// 查看绑定权限
async function handleBindPermission(row: SysMenu) {
currentMenu.value = row;
try {
// 获取所有权限
let permission:SysPermission = {
permissionID: undefined,
name: undefined,
code: undefined,
description: undefined,
};
const permissionResult = await permissionApi.getPermissionList(permission);
permissionList.value = permissionResult.dataList || [];
// 获取已绑定的权限
const bindingResult = await menuApi.getMenuPermission(row.menuID!);
bindList.value.permissions = bindingResult.dataList || [];
// 设置已选中的权限
selectedPermissions.value = bindList.value.permissions.map(permission => permission.permissionID).filter((id): id is string => !!id);
console.log('已绑定的权限:', bindList.value.permissions);
console.log('所有权限:', permissionList.value);
bindPermissionDialogVisible.value = true;
} catch (error) {
console.error('获取权限绑定信息失败:', error);
ElMessage.error('获取权限绑定信息失败');
}
bindPermissionDialogVisible.value = true;
}
// 重置绑定列表
function resetBindList() {
bindList.value = {
permissions: []
};
selectedPermissions.value = [];
currentMenu.value = null;
}
// 检查权限是否已选中
function isPermissionSelected(permissionID: string | undefined): boolean {
return permissionID ? selectedPermissions.value.includes(permissionID) : false;
}
// 切换权限选择状态
function togglePermissionSelection(permission: SysPermission) {
if (!permission.permissionID) return;
const index = selectedPermissions.value.indexOf(permission.permissionID);
if (index > -1) {
selectedPermissions.value.splice(index, 1);
} else {
selectedPermissions.value.push(permission.permissionID);
}
}
// 保存权限绑定
async function savePermissionBinding() {
// 权限选择确认 - 在confirm时提交请求
async function handlePermissionConfirm(items: SysPermission[]) {
if (!currentMenu.value || !currentMenu.value.menuID) {
ElMessage.error('菜单信息不完整');
return;
@@ -600,21 +531,22 @@ async function savePermissionBinding() {
try {
submitting.value = true;
// 获取当前已绑定的权限ID
const currentBoundPermissions = (bindList.value.permissions || []).map(permission => permission.permissionID).filter((id): id is string => !!id);
// 获取当前已绑定的权限
const currentBoundResult = await menuApi.getMenuPermission(currentMenu.value.menuID);
const currentBoundIds = (currentBoundResult.dataList || []).map(p => p.permissionID).filter((id): id is string => !!id);
// 新选择的权限ID
const newSelectedIds = items.map(p => p.permissionID).filter((id): id is string => !!id);
// 找出需要绑定的权限(新增的)
const permissionsToBind = selectedPermissions.value.filter(permissionID => !currentBoundPermissions.includes(permissionID));
const permissionsToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
// 找出需要解绑的权限(移除的)
const permissionsToUnbind = currentBoundPermissions.filter(permissionID => !selectedPermissions.value.includes(permissionID));
const permissionsToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
// 构建需要绑定的权限对象数组
if (permissionsToBind.length > 0) {
const permissionsToBindObjects = permissionsToBind.map(permissionID => {
const permission = permissionList.value.find(p => p.permissionID === permissionID);
return permission || { permissionID: permissionID };
});
const permissionsToBindObjects = items.filter(p => p.permissionID && permissionsToBind.includes(p.permissionID));
const bindMenu = {
...currentMenu.value,
@@ -626,10 +558,7 @@ async function savePermissionBinding() {
// 构建需要解绑的权限对象数组
if (permissionsToUnbind.length > 0) {
const permissionsToUnbindObjects = permissionsToUnbind.map(permissionID => {
const permission = permissionList.value.find(p => p.permissionID === permissionID);
return permission || { permissionID: permissionID };
});
const permissionsToUnbindObjects = (currentBoundResult.dataList || []).filter(p => p.permissionID && permissionsToUnbind.includes(p.permissionID));
const unbindMenu = {
...currentMenu.value,
@@ -640,7 +569,6 @@ async function savePermissionBinding() {
}
ElMessage.success('权限绑定保存成功');
bindPermissionDialogVisible.value = false;
// 刷新菜单列表
await loadMenuList();
@@ -742,8 +670,9 @@ async function handleNodeDrop(draggingNode: any, dropNode: any, dropType: string
.node-icon {
margin-right: 8px;
color: #409EFF;
font-size: 16px;
width: 16px;
height: 16px;
object-fit: contain;
}
.node-name {
@@ -890,30 +819,4 @@ async function handleNodeDrop(draggingNode: any, dropNode: any, dropType: string
}
}
}
// 权限绑定容器样式
.permission-binding-container {
.menu-info {
background: #f5f7fa;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
h4 {
margin: 0 0 8px 0;
color: #303133;
font-size: 16px;
}
p {
margin: 0;
color: #606266;
font-size: 14px;
}
}
.binding-stats {
margin-top: 20px;
}
}
</style>

View File

@@ -279,120 +279,34 @@
</el-dialog>
<!-- 绑定菜单对话框 -->
<el-dialog
v-model="bindMenuDialogVisible"
title="绑定菜单"
width="800px"
@close="resetBindList"
>
<div class="menu-binding-container">
<div class="permission-info" v-if="currentPermission">
<h4>权限信息{{ currentPermission.name }}</h4>
<p>权限编码{{ currentPermission.code }}</p>
</div>
<el-table :data="menuList" style="width: 100%" border stripe>
<el-table-column width="80" label="绑定状态">
<template #default="{ row }">
<el-tag
:type="isMenuSelected(row.menuID) ? 'success' : 'info'"
size="small"
>
{{ isMenuSelected(row.menuID) ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="菜单名称" min-width="150" />
<el-table-column prop="menuID" label="菜单ID" min-width="120" />
<el-table-column prop="url" label="菜单路径" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button
:type="isMenuSelected(row.menuID) ? 'danger' : 'primary'"
size="small"
@click="toggleMenuSelection(row)"
>
{{ isMenuSelected(row.menuID) ? '解绑' : '绑定' }}
</el-button>
</template>
</el-table-column>
</el-table>
<div class="binding-stats">
<el-alert
:title="`已绑定 ${selectedMenus.length} 个菜单`"
type="info"
:closable="false"
show-icon
/>
</div>
</div>
<template #footer>
<el-button @click="bindMenuDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveMenuBinding" :loading="submitting">
保存
</el-button>
</template>
</el-dialog>
<GenericSelector
v-model:visible="bindMenuDialogVisible"
:title="`绑定菜单 - ${currentPermission?.name || ''}`"
left-title="可选菜单"
right-title="已选菜单"
:fetch-available-api="fetchAllMenus"
:fetch-selected-api="fetchPermissionMenus"
:item-config="{ id: 'menuID', label: 'name', sublabel: 'url' }"
unit-name=""
search-placeholder="搜索菜单名称..."
@confirm="handleMenuConfirm"
@cancel="resetBindList"
/>
<!-- 绑定角色对话框 -->
<el-dialog
v-model="bindRoleDialogVisible"
title="绑定角色"
width="800px"
@close="resetBindList"
>
<div class="role-binding-container">
<div class="permission-info" v-if="currentPermission">
<h4>权限信息{{ currentPermission.name }}</h4>
<p>权限编码{{ currentPermission.code }}</p>
</div>
<el-table :data="roleList" style="width: 100%" border stripe>
<el-table-column width="80" label="绑定状态">
<template #default="{ row }">
<el-tag
:type="isRoleSelected(row.roleID) ? 'success' : 'info'"
size="small"
>
{{ isRoleSelected(row.roleID) ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="角色名称" min-width="150" />
<el-table-column prop="roleID" label="角色ID" min-width="120" />
<el-table-column prop="description" label="角色描述" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button
:type="isRoleSelected(row.roleID) ? 'danger' : 'primary'"
size="small"
@click="toggleRoleSelection(row)"
>
{{ isRoleSelected(row.roleID) ? '解绑' : '绑定' }}
</el-button>
</template>
</el-table-column>
</el-table>
<div class="binding-stats">
<el-alert
:title="`已绑定 ${selectedRoles.length} 个角色`"
type="info"
:closable="false"
show-icon
/>
</div>
</div>
<template #footer>
<el-button @click="bindRoleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveRoleBinding" :loading="submitting">
保存
</el-button>
</template>
</el-dialog>
<GenericSelector
v-model:visible="bindRoleDialogVisible"
:title="`绑定角色 - ${currentPermission?.name || ''}`"
left-title="可选角色"
right-title="已选角色"
:fetch-available-api="fetchAllRoles"
:fetch-selected-api="fetchPermissionRoles"
:item-config="{ id: 'roleID', label: 'name', sublabel: 'description' }"
unit-name=""
search-placeholder="搜索角色名称..."
@confirm="handleRoleConfirm"
@cancel="resetBindList"
/>
</div>
</AdminLayout>
</template>
@@ -407,6 +321,7 @@ import { menuApi } from '@/apis/system/menu';
import { roleApi } from '@/apis/system/role';
import type { SysModule, SysPermission, SysMenu, SysRole } from '@/types';
import { AdminLayout } from '@/views/admin';
import { GenericSelector } from '@/components/base';
defineOptions({
name: 'ModulePermissionManageView'
@@ -418,10 +333,6 @@ const permissionLoading = ref(false);
const submitting = ref(false);
const moduleList = ref<SysModule[]>([]);
const permissions = ref<SysPermission[]>([]);
const menuList = ref<SysMenu[]>([]);
const roleList = ref<SysRole[]>([]);
const selectedMenus = ref<string[]>([]);
const selectedRoles = ref<string[]>([]);
// 当前选中的模块和权限
const currentModule = ref<SysModule | null>(null);
@@ -686,80 +597,78 @@ async function handleSubmitPermission() {
}
}
// 获取所有菜单的接口
async function fetchAllMenus() {
return await menuApi.getAllMenuList();
}
// 获取权限已绑定菜单的接口
async function fetchPermissionMenus() {
if (!currentPermission.value) {
return {
success: true,
dataList: [],
code: 200,
message: '',
login: true,
auth: true
};
}
const permission = { ...currentPermission.value, bindType: 'menu' as const };
const result = await permissionApi.getPermissionBindingList(permission);
return {
code: result.code || 200,
message: result.message || '',
success: result.success,
login: result.login ?? true,
auth: result.auth ?? true,
dataList: result.data?.menus || []
};
}
// 获取所有角色的接口
async function fetchAllRoles() {
return await roleApi.getAllRoles();
}
// 获取权限已绑定角色的接口
async function fetchPermissionRoles() {
if (!currentPermission.value) {
return {
success: true,
dataList: [],
code: 200,
message: '',
login: true,
auth: true
};
}
const permission = { ...currentPermission.value, bindType: 'role' as const };
const result = await permissionApi.getPermissionBindingList(permission);
return {
code: result.code || 200,
message: result.message || '',
success: result.success,
login: result.login ?? true,
auth: result.auth ?? true,
dataList: result.data?.roles || []
};
}
// 绑定菜单
async function handleBindMenu(permission: SysPermission) {
currentPermission.value = permission;
permission.bindType = 'menu';
try {
const bindingResult = await permissionApi.getPermissionBindingList(permission);
const bindList = bindingResult.data?.menus || [];
const menuResult = await menuApi.getAllMenuList();
menuList.value = menuResult.dataList || [];
selectedMenus.value = bindList.map(menu => menu.menuID).filter((id): id is string => !!id);
bindMenuDialogVisible.value = true;
} catch (error) {
console.error('获取菜单绑定信息失败:', error);
ElMessage.error('获取菜单绑定信息失败');
}
bindMenuDialogVisible.value = true;
}
// 绑定角色
async function handleBindRole(permission: SysPermission) {
currentPermission.value = permission;
permission.bindType = 'role';
try {
const bindingResult = await permissionApi.getPermissionBindingList(permission);
const bindList = bindingResult.data?.roles || [];
const roleResult = await roleApi.getAllRoles();
roleList.value = roleResult.dataList || [];
selectedRoles.value = bindList.map(role => role.roleID).filter((id): id is string => !!id);
bindRoleDialogVisible.value = true;
} catch (error) {
console.error('获取角色绑定信息失败:', error);
ElMessage.error('获取角色绑定信息失败');
}
bindRoleDialogVisible.value = true;
}
// 菜单选择相关
function isMenuSelected(menuID: string | undefined): boolean {
return menuID ? selectedMenus.value.includes(menuID) : false;
}
function toggleMenuSelection(menu: SysMenu) {
if (!menu.menuID) return;
const index = selectedMenus.value.indexOf(menu.menuID);
if (index > -1) {
selectedMenus.value.splice(index, 1);
} else {
selectedMenus.value.push(menu.menuID);
}
}
// 角色选择相关
function isRoleSelected(roleID: string | undefined): boolean {
return roleID ? selectedRoles.value.includes(roleID) : false;
}
function toggleRoleSelection(role: SysRole) {
if (!role.roleID) return;
const index = selectedRoles.value.indexOf(role.roleID);
if (index > -1) {
selectedRoles.value.splice(index, 1);
} else {
selectedRoles.value.push(role.roleID);
}
}
// 保存菜单绑定
async function saveMenuBinding() {
// 菜单选择确认 - 在confirm时提交请求
async function handleMenuConfirm(items: SysMenu[]) {
if (!currentPermission.value?.permissionID) {
ElMessage.error('权限信息不完整');
return;
@@ -768,16 +677,20 @@ async function saveMenuBinding() {
try {
submitting.value = true;
// 获取原有绑定和新绑定的差异
// 获取当前已绑定的菜单
const permission: SysPermission = { ...currentPermission.value, bindType: 'menu' };
const bindingResult = await permissionApi.getPermissionBindingList(permission);
const currentBound = (bindingResult.data?.menus || []).map(m => m.menuID).filter((id): id is string => !!id);
const currentBoundIds = (bindingResult.data?.menus || []).map(m => m.menuID).filter((id): id is string => !!id);
const menusToBind = selectedMenus.value.filter(id => !currentBound.includes(id));
const menusToUnbind = currentBound.filter(id => !selectedMenus.value.includes(id));
// 新选择的菜单ID
const newSelectedIds = items.map(m => m.menuID).filter((id): id is string => !!id);
// 找出需要绑定和解绑的菜单
const menusToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
const menusToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
if (menusToBind.length > 0) {
const menusToBindObjects = menusToBind.map(id => ({ menuID: id }));
const menusToBindObjects = items.filter(m => m.menuID && menusToBind.includes(m.menuID));
await permissionApi.bindMenu({
...currentPermission.value,
menus: menusToBindObjects,
@@ -795,7 +708,6 @@ async function saveMenuBinding() {
}
ElMessage.success('菜单绑定保存成功');
bindMenuDialogVisible.value = false;
} catch (error) {
console.error('保存菜单绑定失败:', error);
ElMessage.error('保存菜单绑定失败');
@@ -804,8 +716,8 @@ async function saveMenuBinding() {
}
}
// 保存角色绑定
async function saveRoleBinding() {
// 角色选择确认 - 在confirm时提交请求
async function handleRoleConfirm(items: SysRole[]) {
if (!currentPermission.value?.permissionID) {
ElMessage.error('权限信息不完整');
return;
@@ -814,15 +726,20 @@ async function saveRoleBinding() {
try {
submitting.value = true;
// 获取当前已绑定的角色
const permission: SysPermission = { ...currentPermission.value, bindType: 'role' };
const bindingResult = await permissionApi.getPermissionBindingList(permission);
const currentBound = (bindingResult.data?.roles || []).map(r => r.roleID).filter((id): id is string => !!id);
const currentBoundIds = (bindingResult.data?.roles || []).map(r => r.roleID).filter((id): id is string => !!id);
const rolesToBind = selectedRoles.value.filter(id => !currentBound.includes(id));
const rolesToUnbind = currentBound.filter(id => !selectedRoles.value.includes(id));
// 新选择的角色ID
const newSelectedIds = items.map(r => r.roleID).filter((id): id is string => !!id);
// 找出需要绑定和解绑的角色
const rolesToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
const rolesToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
if (rolesToBind.length > 0) {
const rolesToBindObjects = rolesToBind.map(id => ({ roleID: id }));
const rolesToBindObjects = items.filter(r => r.roleID && rolesToBind.includes(r.roleID));
await permissionApi.bindRole({
...currentPermission.value,
roles: rolesToBindObjects,
@@ -840,7 +757,6 @@ async function saveRoleBinding() {
}
ElMessage.success('角色绑定保存成功');
bindRoleDialogVisible.value = false;
} catch (error) {
console.error('保存角色绑定失败:', error);
ElMessage.error('保存角色绑定失败');
@@ -870,8 +786,6 @@ function resetPermissionForm() {
}
function resetBindList() {
selectedMenus.value = [];
selectedRoles.value = [];
currentPermission.value = null;
}
@@ -1138,32 +1052,6 @@ onMounted(() => {
}
}
// 绑定对话框
.menu-binding-container,
.role-binding-container {
.permission-info {
background: #f5f7fa;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
h4 {
margin: 0 0 8px 0;
color: #303133;
font-size: 16px;
}
p {
margin: 0;
color: #606266;
font-size: 14px;
}
}
.binding-stats {
margin-top: 20px;
}
}
}
</style>

View File

@@ -97,61 +97,19 @@
</el-dialog>
<!-- 绑定权限对话框 -->
<el-dialog v-model="bindPermissionDialogVisible" title="绑定权限" width="800px" @close="resetBindList">
<div class="permission-binding-container">
<!-- 角色信息显示 -->
<div class="role-info" v-if="currentRole">
<h4>角色信息{{ currentRole.name }}</h4>
<p>角色ID{{ currentRole.roleID }}</p>
</div>
<!-- 权限绑定状态表格 -->
<el-table :data="permissionList" style="width: 100%" border stripe>
<el-table-column width="80" label="绑定状态">
<template #default="{ row }">
<el-tag
:type="isPermissionSelected(row.permissionID) ? 'success' : 'info'"
size="small"
>
{{ isPermissionSelected(row.permissionID) ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="权限名称" min-width="150" />
<el-table-column prop="permissionID" label="权限ID" min-width="120" />
<el-table-column prop="code" label="权限编码" min-width="150" />
<el-table-column prop="description" label="权限描述" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button
:type="isPermissionSelected(row.permissionID) ? 'danger' : 'primary'"
size="small"
@click="togglePermissionSelection(row)"
>
{{ isPermissionSelected(row.permissionID) ? '解绑' : '绑定' }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 统计信息 -->
<div class="binding-stats">
<el-alert
:title="`已绑定 ${selectedPermissions.length} 个权限,未绑定 ${permissionList.length - selectedPermissions.length} 个权限`"
type="info"
:closable="false"
show-icon
/>
</div>
</div>
<template #footer>
<el-button @click="bindPermissionDialogVisible = false">取消</el-button>
<el-button type="primary" @click="savePermissionBinding" :loading="submitting">
保存
</el-button>
</template>
</el-dialog>
<GenericSelector
v-model:visible="bindPermissionDialogVisible"
:title="`绑定权限 - ${currentRole?.name || ''}`"
left-title="可选权限"
right-title="已选权限"
:available-items="availablePermissions"
:initial-target-items="initialBoundPermissions"
:item-config="{ id: 'permissionID', label: 'name', sublabel: 'code' }"
unit-name=""
search-placeholder="搜索权限名称或编码..."
@confirm="handlePermissionConfirm"
@cancel="resetBindList"
/>
</div>
</AdminLayout>
</template>
@@ -161,11 +119,12 @@ import { roleApi } from '@/apis/system/role';
import { permissionApi } from '@/apis/system/permission';
import { SysRole, SysPermission } from '@/types';
import { AdminLayout } from '@/views/admin';
import { GenericSelector } from '@/components/base';
defineOptions({
name: 'RoleManageView'
});
import { ref, onMounted, reactive } from 'vue';
import { ref, onMounted, reactive, computed } from 'vue';
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
@@ -176,11 +135,8 @@ const submitting = ref(false);
// 权限绑定相关数据
const permissionList = ref<SysPermission[]>([]);
const selectedPermissions = ref<string[]>([]);
const currentRole = ref<SysRole | null>(null);
const bindList = ref<{ permissions: SysPermission[] }>({
permissions: []
});
const initialBoundPermissions = ref<SysPermission[]>([]);
// 对话框状态
const dialogVisible = ref(false);
@@ -295,6 +251,12 @@ function resetForm() {
});
}
// 计算可选权限(过滤掉已绑定的)
const availablePermissions = computed(() => {
const boundIds = new Set(initialBoundPermissions.value.map(p => p.permissionID));
return permissionList.value.filter(p => !boundIds.has(p.permissionID));
});
// 查看绑定权限
async function handleBindPermission(row: SysRole) {
currentRole.value = row;
@@ -314,14 +276,8 @@ async function handleBindPermission(row: SysRole) {
const bindingResult = await roleApi.getRolePermission({
roleID: row.roleID
});
console.log('已绑定的权限:', bindingResult);
bindList.value.permissions = bindingResult.dataList || [];
initialBoundPermissions.value = bindingResult.dataList || [];
// 设置已选中的权限
selectedPermissions.value = bindList.value.permissions.map(permission => permission.permissionID).filter((id): id is string => !!id);
console.log('已绑定的权限:', bindList.value.permissions);
console.log('所有权限:', permissionList.value);
bindPermissionDialogVisible.value = true;
} catch (error) {
console.error('获取权限绑定信息失败:', error);
@@ -331,32 +287,12 @@ async function handleBindPermission(row: SysRole) {
// 重置绑定列表
function resetBindList() {
bindList.value = {
permissions: []
};
selectedPermissions.value = [];
initialBoundPermissions.value = [];
currentRole.value = null;
}
// 检查权限是否已选中
function isPermissionSelected(permissionID: string | undefined): boolean {
return permissionID ? selectedPermissions.value.includes(permissionID) : false;
}
// 切换权限选择状态
function togglePermissionSelection(permission: SysPermission) {
if (!permission.permissionID) return;
const index = selectedPermissions.value.indexOf(permission.permissionID);
if (index > -1) {
selectedPermissions.value.splice(index, 1);
} else {
selectedPermissions.value.push(permission.permissionID);
}
}
// 保存权限绑定
async function savePermissionBinding() {
// 权限选择确认 - 在confirm时提交请求
async function handlePermissionConfirm(items: SysPermission[]) {
if (!currentRole.value || !currentRole.value.roleID) {
ElMessage.error('角色信息不完整');
return;
@@ -366,13 +302,16 @@ async function savePermissionBinding() {
submitting.value = true;
// 获取当前已绑定的权限ID
const currentBoundPermissions = (bindList.value.permissions || []).map(permission => permission.permissionID).filter((id): id is string => !!id);
const currentBoundIds = initialBoundPermissions.value.map(p => p.permissionID).filter((id): id is string => !!id);
// 新选择的权限ID
const newSelectedIds = items.map(p => p.permissionID).filter((id): id is string => !!id);
// 找出需要绑定的权限(新增的)
const permissionsToBind = selectedPermissions.value.filter(permissionID => !currentBoundPermissions.includes(permissionID));
const permissionsToBind = newSelectedIds.filter(id => !currentBoundIds.includes(id));
// 找出需要解绑的权限(移除的)
const permissionsToUnbind = currentBoundPermissions.filter(permissionID => !selectedPermissions.value.includes(permissionID));
const permissionsToUnbind = currentBoundIds.filter(id => !newSelectedIds.includes(id));
// 构建需要绑定的权限对象数组
if (permissionsToBind.length > 0) {
@@ -405,7 +344,6 @@ async function savePermissionBinding() {
}
ElMessage.success('权限绑定保存成功');
bindPermissionDialogVisible.value = false;
// 刷新角色列表
await loadRoleList();
@@ -515,30 +453,4 @@ onMounted(() => {
}
}
}
// 权限绑定容器样式
.permission-binding-container {
.role-info {
background: #f5f7fa;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
h4 {
margin: 0 0 8px 0;
color: #303133;
font-size: 16px;
}
p {
margin: 0;
color: #606266;
font-size: 14px;
}
}
.binding-stats {
margin-top: 20px;
}
}
</style>

View File

@@ -129,45 +129,25 @@
</template>
</el-dialog>
<!-- 绑定部门角色对话框 -->
<el-dialog
v-model="bindDialogVisible"
title="绑定部门角色"
width="600px"
@close="resetBindForm"
>
<el-form
ref="bindFormRef"
:model="bindForm"
label-width="100px"
>
<el-form-item label="选择部门">
<el-tree-select
v-model="bindForm.deptId"
:data="deptTree"
check-strictly
:render-after-expand="false"
placeholder="请选择部门"
/>
</el-form-item>
<el-form-item label="选择角色">
<el-select v-model="bindForm.roleIds" multiple placeholder="请选择角色" style="width: 100%">
<el-option
v-for="role in roles"
:key="role.roleID"
:label="role.name"
:value="role.roleID"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="bindDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitBindForm" :loading="binding">
确定
</el-button>
</template>
</el-dialog>
<!-- 部门角色选择器 -->
<GenericSelector
v-model:visible="showDeptRoleSelector"
:title="`绑定部门角色 - ${currentUser.username || ''}`"
left-title="可选的部门角色组合"
right-title="已选的部门角色"
:fetch-available-api="fetchAllDeptRoles"
:fetch-selected-api="fetchUserDeptRoles"
:filter-selected="filterDeptRoles"
:item-config="{ id: 'combinedId', label: 'displayName', sublabel: 'deptDescription' }"
:use-tree="true"
:tree-transform="transformDeptRolesToTree"
:tree-props="{ children: 'children', label: 'displayName', id: 'combinedId' }"
:only-leaf-selectable="true"
unit-name=""
search-placeholder="搜索部门或角色..."
@confirm="handleDeptRoleConfirm"
@cancel="closeDeptRoleSelector"
/>
</div>
</AdminLayout>
</template>
@@ -176,9 +156,10 @@
import { ref, onMounted } from 'vue';
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import { userApi, deptApi, roleApi } from '@/apis/system';
import type { SysUser, SysRole, PageParam, UserVO, UserDeptRoleVO } from '@/types';
import { userApi, deptApi } from '@/apis/system';
import type { SysUser, PageParam, UserVO, UserDeptRoleVO, SysUserDeptRole } from '@/types';
import { AdminLayout } from '@/views/admin';
import { GenericSelector } from '@/components/base';
defineOptions({
name: 'UserManageView'
@@ -189,8 +170,6 @@ const submitting = ref(false);
const binding = ref(false);
const userList = ref<UserVO[]>([]);
const deptTree = ref<any[]>([]);
const roles = ref<SysRole[]>([]);
// 分页参数
const pageParam = ref<PageParam>({
@@ -200,23 +179,147 @@ const pageParam = ref<PageParam>({
const total = ref(0);
const userDialogVisible = ref(false);
const bindDialogVisible = ref(false);
const isEdit = ref(false);
const userFormRef = ref<FormInstance>();
const bindFormRef = ref<FormInstance>();
const currentUser = ref<UserVO & { password?: string }>({
status: 0
});
const bindForm = ref<{
userId?: string;
deptId?: string;
roleIds: string[];
}>({
roleIds: []
});
// 保存原始用户数据,用于比较变更
const originalUser = ref<UserVO & { password?: string }>({});
// 选择器控制
const showDeptRoleSelector = ref(false);
const currentBindingUserId = ref<string>();
// 获取所有部门角色组合的接口
async function fetchAllDeptRoles() {
const result = await deptApi.getDeptRoleList({} as SysUserDeptRole);
if (result.success) {
const deptRoleList = result.dataList || [];
// 转换为带有combinedId和displayName的格式
const transformed = deptRoleList
.filter(item => item.deptID && item.roleID)
.map(item => ({
...item,
combinedId: `${item.deptID}-${item.roleID}`,
displayName: `${item.deptName || ''} - ${item.roleName || ''}`
}));
return { ...result, dataList: transformed };
}
return result;
}
// 获取用户已选的部门角色接口
async function fetchUserDeptRoles() {
if (!currentBindingUserId.value) {
return {
success: true,
dataList: [],
code: 200,
message: '',
login: true,
auth: true
};
}
const result = await userApi.getUserDeptRole({ userID: currentBindingUserId.value } as SysUserDeptRole);
if (result.success) {
const selectedList = result.dataList || [];
// 转换为带有combinedId和displayName的格式
const transformed = selectedList.map(item => ({
...item,
combinedId: `${item.deptID}-${item.roleID}`,
displayName: `${item.deptName || ''} - ${item.roleName || ''}`
}));
return { ...result, dataList: transformed };
}
return result;
}
// 过滤已选项的方法
function filterDeptRoles(available: any[], selected: any[]) {
const selectedIds = new Set(selected.map(item => item.combinedId));
return available.filter(item => !selectedIds.has(item.combinedId));
}
// 将部门角色扁平数据转换为树形结构
function transformDeptRolesToTree(flatData: any[]) {
if (!flatData || flatData.length === 0) {
return [];
}
// 按部门分组
const deptMap = new Map<string, any>();
const tree: any[] = [];
flatData.forEach(item => {
if (!item.deptID) return;
if (!deptMap.has(item.deptID)) {
// 创建部门节点
deptMap.set(item.deptID, {
combinedId: item.deptID,
displayName: item.deptName || '',
deptID: item.deptID,
deptName: item.deptName,
parentID: item.parentID,
deptPath: item.deptPath,
children: [],
isDept: true // 标记这是部门节点
});
}
// 添加角色到部门的children中
const deptNode = deptMap.get(item.deptID);
if (deptNode && item.roleID) {
deptNode.children.push({
...item,
isDept: false // 标记这是角色节点
});
}
});
// 构建树形结构
const allDepts = Array.from(deptMap.values());
const deptTreeMap = new Map<string, any>();
// 初始化所有部门节点
allDepts.forEach(dept => {
deptTreeMap.set(dept.deptID, { ...dept });
});
// 构建部门层级关系
allDepts.forEach(dept => {
const node = deptTreeMap.get(dept.deptID);
if (!node) return;
if (!dept.parentID || dept.parentID === '0' || dept.parentID === '') {
// 根部门
tree.push(node);
} else {
// 子部门
const parent = deptTreeMap.get(dept.parentID);
if (parent) {
if (!parent.children) {
parent.children = [];
}
// 将角色节点添加到部门节点之前
const roles = node.children || [];
node.children = [];
parent.children.push(node);
// 将角色添加到部门的children末尾
node.children = roles;
} else {
// 找不到父节点,作为根节点
tree.push(node);
}
}
});
return tree;
}
const userFormRules: FormRules = {
username: [
@@ -232,10 +335,9 @@ const userFormRules: FormRules = {
]
};
onMounted(() => {
loadUsers();
loadDepts();
loadRoles();
});
async function loadUsers() {
@@ -254,37 +356,20 @@ async function loadUsers() {
}
}
async function loadDepts() {
try {
const result = await deptApi.getAllDepts();
if (result.success) {
deptTree.value = result.dataList || [];
}
} catch (error) {
console.error('加载部门列表失败:', error);
}
}
async function loadRoles() {
try {
const result = await roleApi.getRoleList({});
if (result.success) {
roles.value = result.dataList || [];
}
} catch (error) {
console.error('加载角色列表失败:', error);
}
}
// 不再需要预加载由GenericSelector在打开时自动调用接口加载
function handleAdd() {
isEdit.value = false;
currentUser.value = { status: 0 };
originalUser.value = {};
userDialogVisible.value = true;
}
function handleEdit(row: UserVO) {
isEdit.value = true;
currentUser.value = { ...row };
// 保存原始数据用于比较
originalUser.value = { ...row };
userDialogVisible.value = true;
}
@@ -316,12 +401,76 @@ async function handleDelete(row: UserVO) {
}
function handleBindDeptRole(row: UserVO) {
bindForm.value = {
userId: row.id,
deptId: undefined,
roleIds: []
};
bindDialogVisible.value = true;
currentBindingUserId.value = row.id;
currentUser.value = { ...row };
showDeptRoleSelector.value = true;
}
// 部门角色选择确认 - 在confirm时提交请求
async function handleDeptRoleConfirm(items: any[]) {
if (!currentBindingUserId.value) {
ElMessage.error('用户信息不完整');
return;
}
if (items.length === 0) {
ElMessage.warning('请至少选择一个部门角色');
return;
}
try {
binding.value = true;
// 移除临时添加的字段,只保留原始字段
const userDeptRoles: SysUserDeptRole[] = items.map(item => ({
deptID: item.deptID,
roleID: item.roleID,
userID: currentBindingUserId.value
}));
// 构建 UserDeptRoleVO 对象(用于批量绑定)
const userDeptRoleVO = {
users: [{ id: currentBindingUserId.value } as SysUser],
userDeptRoles: userDeptRoles
} as UserDeptRoleVO;
const result = await userApi.bindUserDeptRole(userDeptRoleVO);
if (result.success) {
ElMessage.success(`成功绑定 ${items.length} 个部门角色`);
loadUsers();
} else {
ElMessage.error(result.message || '绑定失败');
}
} catch (error) {
console.error('绑定失败:', error);
ElMessage.error('绑定失败');
} finally {
binding.value = false;
}
}
// 关闭部门角色选择器
function closeDeptRoleSelector() {
currentBindingUserId.value = undefined;
}
// 获取修改过的字段
function getChangedFields(): Partial<SysUser> {
const changed: Partial<SysUser> = { id: currentUser.value.id };
// 比较每个字段,只包含修改过的字段
Object.keys(currentUser.value).forEach((key) => {
const currentValue = (currentUser.value as any)[key];
const originalValue = (originalUser.value as any)[key];
// 如果值发生变化,则添加到变更对象中
if (currentValue !== originalValue) {
(changed as any)[key] = currentValue;
}
});
return changed;
}
async function submitUserForm() {
@@ -333,7 +482,10 @@ async function submitUserForm() {
let result;
if (isEdit.value) {
result = await userApi.updateUser(currentUser.value as SysUser);
// 只传入修改过的字段
const changedData = getChangedFields();
console.log('更新用户 - 修改的字段:', changedData);
result = await userApi.updateUser(changedData as SysUser);
} else {
result = await userApi.createUser(currentUser.value as SysUser);
}
@@ -352,40 +504,11 @@ async function submitUserForm() {
}
}
async function submitBindForm() {
try {
binding.value = true;
// 构建 UserDeptRoleVO 对象
const userDeptRoleVO: UserDeptRoleVO = {
user: { id: bindForm.value.userId } as SysUser,
depts: bindForm.value.deptId ? [{ id: bindForm.value.deptId }] : [],
roles: bindForm.value.roleIds.map(roleId => ({ id: roleId }))
};
const result = await userApi.bindUserDeptRole(userDeptRoleVO);
if (result.success) {
ElMessage.success('绑定成功');
bindDialogVisible.value = false;
loadUsers();
} else {
ElMessage.error(result.message || '绑定失败');
}
} catch (error) {
console.error('绑定失败:', error);
ElMessage.error('绑定失败');
} finally {
binding.value = false;
}
}
// submitBindForm 已合并到 handleDeptRoleConfirm 中
function resetForm() {
userFormRef.value?.resetFields();
}
function resetBindForm() {
bindFormRef.value?.resetFields();
originalUser.value = {};
}
function handlePageChange(page: number) {

View File

@@ -76,14 +76,16 @@
<td>{{ formatDate(task.endTime) }}</td>
<td>
<span class="status-tag" :class="getStatusClass(task.status)">
{{ getStatusText(task.status) }}
{{ getStatusText(task.status, task.startTime, task.endTime) }}
</span>
</td>
<td>{{ formatDate(task.createTime) }}</td>
<td class="action-cell">
<button class="btn-link btn-primary" @click="handleView(task)">查看</button>
<button class="btn-link btn-warning" @click="handleEdit(task)" v-if="task.status === 0">编辑</button>
<button class="btn-link btn-success" @click="handlePublish(task)" v-if="task.status === 0">发布</button>
<button class="btn-link btn-success" @click="handleStateChange(task, 'publish')" v-if="task.status !== 1">发布</button>
<button class="btn-link btn-warning" @click="handleStateChange(task, 'unpublish')" v-if="task.status === 1">下架</button>
<button class="btn-link btn-primary" @click="handleStatistics(task)">统计</button>
<button class="btn-link btn-warning" @click="handleUpdateUser(task)" v-if="task.status !== 2">修改人员</button>
<button class="btn-link btn-danger" @click="handleDelete(task)" v-if="task.status === 0">删除</button>
</td>
@@ -184,7 +186,7 @@
<span class="detail-label">任务状态:</span>
<span class="detail-value">
<span class="status-badge" :class="getStatusClass(viewingTask.learningTask.status)">
{{ getStatusText(viewingTask.learningTask.status) }}
{{ getStatusText(viewingTask.learningTask.status, viewingTask.learningTask.startTime, viewingTask.learningTask.endTime) }}
</span>
</span>
</div>
@@ -311,15 +313,18 @@
</div>
<!-- 人员选择器组件 -->
<UserSelect
<GenericSelector
v-model:visible="showUserSelector"
:mode="selectorMode"
:title="selectorMode === 'add' ? '添加人员' : '删除人员'"
:left-title="selectorMode === 'add' ? '可添加人员' : '当前人员'"
:right-title="selectorMode === 'add' ? '待添加人员' : '待删除人员'"
:available-users="selectorMode === 'remove' ? availableUsers : []"
:initial-target-users="[]"
:available-items="selectorMode === 'remove' ? availableUsers : []"
:initial-target-items="[]"
:loading="saving"
:item-config="{ id: 'id', label: 'username', sublabel: 'deptName' }"
unit-name=""
search-placeholder="搜索人员..."
:use-pagination="selectorMode === 'add'"
:fetch-api="selectorMode === 'add' ? userApi.getUserPage : undefined"
:filter-params="userFilterParams"
@@ -335,7 +340,7 @@ import { ref, computed, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { learningTaskApi } from '@/apis/study';
import { userApi } from '@/apis/system';
import { UserSelect } from '@/components';
import { GenericSelector } from '@/components/base';
import type { LearningTask, TaskVO, PageParam, UserVO } from '@/types';
defineOptions({
@@ -518,6 +523,10 @@ function handleEdit(task: LearningTask) {
emit('edit', task);
}
function handleStatistics(task: LearningTask) {
return;
}
// 修改人员 - 显示当前人员列表
async function handleUpdateUser(task: LearningTask) {
managingTask.value = task;
@@ -583,7 +592,7 @@ function closeSelectorModal() {
}
// 处理用户选择器确认事件
async function handleUserSelectConfirm(selectedUsers: UserVO[]) {
async function handleUserSelectConfirm(selectedUsers: any[]) {
if (!managingTask.value || selectedUsers.length === 0) {
ElMessage.warning('请选择要操作的人员');
return;
@@ -602,7 +611,7 @@ async function handleUserSelectConfirm(selectedUsers: UserVO[]) {
ElMessage.success(`成功添加 ${userIds.length} 位人员`);
// 更新当前用户列表
currentUsers.value.push(...selectedUsers);
currentUsers.value.push(...(selectedUsers as UserVO[]));
} else {
// 执行删除操作
for (const userID of userIds) {
@@ -627,21 +636,21 @@ async function handleUserSelectConfirm(selectedUsers: UserVO[]) {
}
// 发布任务
async function handlePublish(task: LearningTask) {
async function handleStateChange(task: LearningTask, state: 'publish' | 'unpublish') {
try {
const res = await learningTaskApi.publishTask({
taskID: task.taskID!,
status: 1
const res = await learningTaskApi.changeTaskStatus({
...task,
status: state === 'publish' ? 1 : 2
});
if (res.success) {
ElMessage.success('任务发布成功');
ElMessage.success('任务状态更新成功');
loadTaskList();
} else {
ElMessage.error(res.message || '发布失败');
ElMessage.error(res.message || '状态更新失败');
}
} catch (error) {
console.error('发布任务失败:', error);
ElMessage.error('发布任务失败');
console.error('状态更新失败:', error);
ElMessage.error('状态更新失败');
}
}
@@ -689,18 +698,27 @@ function getStatusClass(status?: number) {
}
}
// 获取状态文本
function getStatusText(status?: number) {
switch (status) {
case 0:
return '草稿';
case 1:
return '进行中';
case 2:
return '已结束';
default:
return '未知';
function getStatusText(status?: number, startTime?: string, endTime?: string): string {
if (status === 0) {
return '草稿';
}
if (status === 1) {
let now = new Date();
let startTimeDate = new Date(startTime!);
let endTimeDate = new Date(endTime!);
if (now >= startTimeDate && now <= endTimeDate) {
return '进行中';
} else if (now < startTimeDate) {
return '未开始';
} else {
return '已结束';
}
}
if (status === 2) {
return '下架';
}
return '未知';
}
// 格式化日期
@@ -810,28 +828,6 @@ defineExpose({
margin-bottom: 20px;
}
.btn-primary {
padding: 10px 20px;
background: #409eff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 4px;
&:hover {
background: #66b1ff;
}
.icon {
font-size: 18px;
}
}
.task-table-wrapper {
background: #fff;
border-radius: 8px;
@@ -913,46 +909,45 @@ defineExpose({
.action-cell {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.btn-link {
border: none;
padding: 4px 8px;
padding: 6px 12px;
font-size: 13px;
cursor: pointer;
transition: all 0.3s;
border-radius: 4px;
min-width: 64px;
text-align: center;
white-space: nowrap;
&:hover {
opacity: 0.8;
}
&.btn-primary {
&:hover {
background: #ecf5ff;
}
background: #409eff;
color: #ffffff;
}
&.btn-warning {
color: #e6a23c;
&:hover {
background: #fdf6ec;
}
background: #e6a23c;
color: #ffffff;
}
&.btn-success {
color: #67c23a;
&:hover {
background: #f0f9ff;
}
background: #67c23a;
color: #ffffff;
}
&.btn-danger {
background: #f56c6c;
color: #ffffff;
&:hover {
background: #fef0f0;
}
}
}
@@ -1073,9 +1068,11 @@ defineExpose({
}
}
// 通用按钮样式
.btn-default,
.btn-danger {
// 通用按钮样式(排除表格中的 btn-link
.btn-primary:not(.btn-link),
.btn-success:not(.btn-link),
.btn-danger:not(.btn-link),
.btn-default:not(.btn-link) {
padding: 10px 24px;
border-radius: 4px;
font-size: 14px;
@@ -1083,13 +1080,44 @@ defineExpose({
transition: all 0.3s;
border: none;
.icon {
margin-right: 4px;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.btn-default {
.btn-primary:not(.btn-link) {
background: #409eff;
color: #fff;
&:hover:not(:disabled) {
background: #66b1ff;
}
}
.btn-success:not(.btn-link) {
background: #67c23a;
color: #fff;
&:hover:not(:disabled) {
background: #85ce61;
}
}
.btn-danger:not(.btn-link) {
background: #f56c6c;
color: #fff;
&:hover:not(:disabled) {
background: #f78989;
}
}
.btn-default:not(.btn-link) {
background: #fff;
color: #606266;
border: 1px solid #dcdfe6;
@@ -1100,15 +1128,6 @@ defineExpose({
}
}
.btn-danger {
background: #f56c6c;
color: #fff;
&:hover:not(:disabled) {
background: #f78989;
}
}
// 弹窗样式
.modal-overlay {
position: fixed;
@@ -1300,46 +1319,6 @@ defineExpose({
margin-bottom: 20px;
}
.btn-success,
.btn-danger {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 6px;
.icon {
font-size: 18px;
font-weight: bold;
}
}
.btn-success {
background: #67c23a;
color: #fff;
&:hover {
background: #85ce61;
}
}
.btn-danger {
background: #f56c6c;
color: #fff;
&:hover:not(:disabled) {
background: #f78989;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.current-user-list {
border: 1px solid #e0e0e0;