Commit d44aa23a by zhangxingmin

租户管理、项目管理、用户管理

parent 884357e6
......@@ -117,3 +117,24 @@ export function deptTreeSelect(roleId) {
method: 'get'
})
}
//作用域切换搜索
export function searchScopeList(data) {
debugger
return request({
url: '/user/api/sysUser/scope/page',
method: 'post',
data: data
})
}
// 修改项目状态
export function changeProjectStatus(data) {
debugger
return request({
url: '/user/api/sysProject/edit/status',
method: 'patch',
params: data
})
}
import request from '@/utils/request'
// 查询项目用户关系列表
export function listProjectUser(data) {
return request({
url: '/user/api/relProjectUser/page',
method: 'post',
data: data
})
}
// 查询导入用户关系列表
export function listImportProjectUser(data) {
return request({
url: '/user/api/relProjectUser/select/user/page',
method: 'post',
data: data
})
}
// 导入用户列表数据提交
export function addImportProjectUserList(importProjectUserListIds, projectBizId) {
const data = {
userBizIdList: importProjectUserListIds,
projectBizId: projectBizId
}
return request({
url: '/user/api/relProjectUser/add/user/list',
method: 'post',
data: data
})
}
// 删除项目和用户关系
export function delRelProjectUser(id) {
return request({
url: '/user/api/relProjectUser/del?id=' + id,
method: 'delete'
})
}
// 查询项目角色关系列表
export function listProjectRole(data) {
return request({
url: '/user/api/relProjectRole/page',
method: 'post',
data: data
})
}
// 删除项目和角色关系
export function delRelProjectRole(id) {
return request({
url: '/user/api/relProjectRole/del?id=' + id,
method: 'delete'
})
}
// 查询导入角色关系列表
export function listImportProjectRole(data) {
return request({
url: '/user/api/relProjectRole/select/role/page',
method: 'post',
data: data
})
}
// 导入角色列表数据提交
export function addImportProjectRoleList(importProjectRoleListIds, projectBizId) {
const data = {
roleBizIdList: importProjectRoleListIds,
projectBizId: projectBizId
}
return request({
url: '/user/api/relProjectRole/add/role/list',
method: 'post',
data: data
})
}
// 查询项目和菜单关系列表
export function listMenu(data) {
return request({
url: '/user/api/relProjectMenu/page',
method: 'post',
data: data
})
}
// 获取项目菜单导入的菜单树
export function getMenuTree(projectBizId) {
const data = {
projectBizId: projectBizId
}
return request({
url: '/user/api/relProjectMenu/import/query/menu/tree',
method: 'post',
data: data
})
}
// 获取项目菜单导入选中的菜单列表
export function getImportSelectedMenuList(projectBizId) {
return request({
url: '/user/api/relProjectMenu/import/query/selected/menu/list?projectBizId=' + projectBizId,
method: 'get'
})
}
// 导入菜单列表数据提交
export function addImportProjectMenuList(data) {
return request({
url: '/user/api/relProjectMenu/add/menu/list',
method: 'post',
data: data
})
}
//分配角色-左侧待选列表
export function listLeftRole(data) {
debugger
return request({
url: '/user/api/relUserRole/candidate/project/userRolePage',
method: 'post',
data: data
})
}
//分配角色-右侧已选列表
export function listRightRole(data) {
return request({
url: '/user/api/relUserRole/selected/project/userRolePage',
method: 'post',
data: data
})
}
//分配角色-左侧待选列表-添加用户角色列表关系
export function addRightRoleList(data) {
return request({
url: '/user/api/relUserRole/add/project/userRoleList',
method: 'post',
data: data
})
}
//分配角色-右侧已选列表-移除用户角色列表关系
export function delRightRoleList(data) {
return request({
url: '/user/api/relUserRole/del/project/userRoleList',
method: 'post',
data: data
})
}
//项目和菜单关系树形列表查询
export function getFpMenuTree(data) {
return request({
url: '/user/api/relProjectMenu/tree',
method: 'post',
data: data
})
}
//项目和菜单关系树形列表查询
export function getSelectedFpMenuList(data) {
return request({
url: '/user/api/relRoleMenu/selected/project/roleMenuList',
method: 'post',
data: data
})
}
//添加菜单角色列表关系
export function addFpMenuList(data) {
return request({
url: '/user/api/relRoleMenu/add/project/roleMenuList',
method: 'post',
data: data
})
}
......@@ -134,3 +134,12 @@ export function deptTreeSelect() {
method: 'get'
})
}
// 修改租户状态
export function changeTenantStatus(data) {
return request({
url: '/user/api/sysTenant/edit/status',
method: 'patch',
params: data
})
}
......@@ -146,15 +146,65 @@ export function addImportTenantMenuList(data) {
})
}
export function getUserRole(data) {
//分配角色-左侧待选列表
export function listLeftRole(data) {
debugger
return request({
url: '/user/api/relUserRole/candidate/tenant/userRolePage',
method: 'post',
data: data
})
}
export function assignUserRole(data) {
//分配角色-右侧已选列表
export function listRightRole(data) {
return request({
url: '/user/api/relUserRole/selected/tenant/userRolePage',
method: 'post',
data: data
})
}
export function removeUserRoles(data) {
//分配角色-左侧待选列表-添加用户角色列表关系
export function addRightRoleList(data) {
return request({
url: '/user/api/relUserRole/add/tenant/userRoleList',
method: 'post',
data: data
})
}
//分配角色-右侧已选列表-移除用户角色列表关系
export function delRightRoleList(data) {
return request({
url: '/user/api/relUserRole/del/tenant/userRoleList',
method: 'post',
data: data
})
}
export function listRole(data) {
//租户和菜单关系树形列表查询
export function getFpMenuTree(data) {
return request({
url: '/user/api/relTenantMenu/tree',
method: 'post',
data: data
})
}
//租户和菜单关系树形列表查询
export function getSelectedFpMenuList(data) {
return request({
url: '/user/api/relRoleMenu/selected/tenant/roleMenuList',
method: 'post',
data: data
})
}
//添加菜单角色列表关系
export function addFpMenuList(data) {
return request({
url: '/user/api/relRoleMenu/add/tenant/roleMenuList',
method: 'post',
data: data
})
}
......@@ -11,9 +11,9 @@ export function listUser(data) {
}
// 查询用户详细
export function getUser(userId) {
export function getUser(userBizId) {
return request({
url: '/system/user/' + parseStrEmpty(userId),
url: '/user/api/sysUser/detail?userBizId=' + userBizId,
method: 'get'
})
}
......@@ -21,7 +21,7 @@ export function getUser(userId) {
// 新增用户
export function addUser(data) {
return request({
url: '/system/user',
url: '/user/api/sysUser/add',
method: 'post',
data: data
})
......@@ -30,17 +30,19 @@ export function addUser(data) {
// 修改用户
export function updateUser(data) {
return request({
url: '/system/user',
url: '/user/api/sysUser/edit',
method: 'put',
data: data
})
}
// 删除用户
export function delUser(userId) {
// 用户状态修改
export function changeUserStatus(data) {
debugger
return request({
url: '/system/user/' + userId,
method: 'delete'
url: '/user/api/sysUser/edit/status',
method: 'patch',
params: data
})
}
......@@ -57,49 +59,6 @@ export function resetUserPwd(userId, password) {
})
}
// 用户状态修改
export function changeUserStatus(userId, status) {
const data = {
userId,
status
}
return request({
url: '/system/user/changeStatus',
method: 'put',
data: data
})
}
// 查询用户个人信息
export function getUserProfile() {
return request({
url: '/system/user/profile',
method: 'get'
})
}
// 修改用户个人信息
export function updateUserProfile(data) {
return request({
url: '/system/user/profile',
method: 'put',
data: data
})
}
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
const data = {
oldPassword,
newPassword
}
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
data: data
})
}
// 用户头像上传
export function uploadAvatar(data) {
return request({
......@@ -110,27 +69,3 @@ export function uploadAvatar(data) {
})
}
// 查询授权角色
export function getAuthRole(userId) {
return request({
url: '/system/user/authRole/' + userId,
method: 'get'
})
}
// 保存授权角色
export function updateAuthRole(data) {
return request({
url: '/system/user/authRole',
method: 'put',
params: data
})
}
// 查询部门下拉树结构
export function deptTreeSelect() {
return request({
url: '/system/user/deptTree',
method: 'get'
})
}
......@@ -140,18 +140,65 @@
<el-form-item label="项目访问地址" prop="projectUrl">
<el-input v-model="form.projectUrl" placeholder="请输入项目访问地址" />
</el-form-item>
<!-- 作用域选择 -->
<el-form-item label="作用域">
<el-radio-group v-model="form.scope">
<el-radio-group v-model="form.scope" @change="handleScopeChange">
<el-radio
v-for="dict in sys_scope"
v-for="dict in filteredScopeOptions"
:key="Number(dict.value)"
:value="Number(dict.value)">
:value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="所属租户" prop="tenantBizId">
<el-input v-model="form.tenantBizId" placeholder="请输入所属租户" />
<!-- 所属租户 - 可搜索下拉框 -->
<el-form-item
v-if="[2].includes(form.scope)"
label="所属租户"
prop="tenantBizId"
>
<el-select
v-model="form.tenantBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索租户"
:remote-method="searchTenants"
:loading="tenantLoading"
>
<el-option
v-for="item in tenantOptions"
:key="item.tenantBizId"
:label="item.tenantName"
:value="item.tenantBizId"
/>
</el-select>
</el-form-item>
<!-- 所属项目 - 可搜索下拉框 -->
<el-form-item
v-if="[3].includes(form.scope)"
label="所属项目"
prop="projectBizId"
>
<el-select
v-model="form.projectBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索项目"
:remote-method="searchProjects"
:loading="projectLoading"
>
<el-option
v-for="item in projectOptions"
:key="item.projectBizId"
:label="item.projectName"
:value="item.projectBizId"
/>
</el-select>
</el-form-item>
<el-form-item label="项目描述">
<el-input v-model="form.description" type="textarea" placeholder="请输入项目描述"></el-input>
......@@ -225,7 +272,10 @@
</template>
<script setup name="Project">
import { addProject, changeRoleStatus, dataScope, delRole, getProject, listProject, updateProject, deptTreeSelect } from "@/api/system/project"
import { computed } from 'vue'
import { addProject, changeRoleStatus, dataScope, delRole, getProject, listProject, updateProject, deptTreeSelect,searchScopeList,changeProjectStatus } from "@/api/system/project"
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu"
import useUserStore from '@/store/modules/user'
import {formatIsoToDateTime} from '@/utils/date'
......@@ -265,7 +315,9 @@ const dataScopeOptions = ref([
])
const data = reactive({
form: {},
form: {
scope: 1
},
queryParams: {
pageNum: 1,
pageSize: 10,
......@@ -281,7 +333,17 @@ const data = reactive({
scope: [{ required: true, message: "作用域不能为空", trigger: "blur" }],
logoUrl: [{ required: true, message: "项目图标地址不能为空", trigger: "blur" }],
isIn: [{ required: true, message: "是否内置项目不能为空", trigger: "blur" }],
},
}
})
const filteredScopeOptions = computed(() => {
if (!sys_scope.value || !Array.isArray(sys_scope.value)) return []
return sys_scope.value.filter(option => {
// 添加安全判断
if (!option || typeof option.value === 'undefined') return false
return Number(option.value) !== 3
})
})
const { queryParams, form, rules } = toRefs(data)
......@@ -335,18 +397,14 @@ function handleSelectionChange(selection) {
/** 项目状态修改 */
function handleStatusChange(row, event) {
// 忽略初始化时的自动触发
if (!event || event.type !== 'change') return;
let text = row.status === "0" ? "启用" : "停用"
const projectName = row.projectName || "未命名项目"
let text = row.status === 0 ? "停用" : "启用"
const projectName = row.projectName
proxy.$modal.confirm(`确认要${text}"${projectName}"项目吗?`)
.then(() => changeProjectStatus(row.projectId, row.status))
.then(() => changeProjectStatus({projectBizId: row.projectBizId, status: row.status}))
.then(() => proxy.$modal.msgSuccess(`${text}成功`))
.catch(() => {
// 操作取消时恢复原状态
row.status = row.status === "0" ? "1" : "0"
row.status = row.status === 0 ? 1 : 0
})
}
......@@ -364,9 +422,9 @@ function handleCommand(command, row) {
}
}
/** 分配用户 */
function handleAuthUser(row) {
router.push("/system/role-auth/user/" + row.roleId)
/** 分配权限 */
function handlePermission(row) {
router.push(`/system/project/permission?projectBizId=` + row.projectBizId)
}
/** 所有部门节点数据 */
......@@ -415,6 +473,13 @@ function handleUpdate(row) {
startTime: formatIsoToDateTime(resData.startTime),
endTime: formatIsoToDateTime(resData.endTime)
};
// 根据作用域类型预加载关联数据
if (resData.scope === 2) {
tenantOptions.value.push({
tenantBizId: resData.tenantBizId,
tenantName: resData.tenantName
});
}
open.value = true
})
title.value = "修改项目"
......@@ -483,15 +548,13 @@ function getMenuAllCheckedKeys() {
function submitForm() {
proxy.$refs["projectRef"].validate(valid => {
if (valid) {
if (form.value.projectBizId != undefined) {
debugger
if (form.value.projectBizId) {
updateProject(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
debugger
addProject(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
open.value = false
......@@ -553,5 +616,69 @@ function cancelDataScope() {
reset()
}
//========作用域-切换逻辑开始=========
const tenantOptions = ref([])
const tenantLoading = ref(false)
const projectOptions = ref([])
const projectLoading = ref(false)
// 作用域变更处理
function handleScopeChange(val) {
console.log('作用域变更:', val);
// 清空不需要的字段值
if (val === 2) {
form.value.projectBizId = '';
// 自动加载租户列表(可选)
searchTenants('');
} else if (val === 3) {
form.value.tenantBizId = '';
// 自动加载项目列表(可选)
searchProjects('');
} else if (val === 1) {
//系统级
form.value.tenantBizId = '';
form.value.projectBizId = '';
}
}
// 搜索租户方法
function searchTenants(query) {
tenantLoading.value = true;
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 2,name: query, pageNo:1, pageSize: 10 };
searchScopeList(params).then(response => {
tenantOptions.value = response.data.records
})
} catch (error) {
console.error('租户搜索失败', error);
tenantOptions.value = [];
} finally {
tenantLoading.value = false;
}
}
// 搜索项目方法
function searchProjects(query) {
projectLoading.value = true;
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 3,name: query, pageNo:1, pageSize: 10 };
searchScopeList(params).then(response => {
projectOptions.value = response.data.records
})
} catch (error) {
console.error('项目搜索失败', error);
projectOptions.value = [];
} finally {
projectLoading.value = false;
}
}
//========作用域-切换逻辑结束=========
getList()
</script>
<template>
<div class="app-container">
<!-- 选项卡组件:增加 custom-tabs 类名 -->
<el-tabs
v-model="activeTab"
type="card"
@tab-change="handleTabChange"
class="custom-tabs"
>
<!-- 用户权限选项卡 -->
<el-tab-pane label="用户" name="user" class="tab-pane-content">
<el-form :model="userQueryParams" ref="userQueryRef" v-show="userShowSearch" :inline="true" label-width="68px">
<el-form-item label="账号" prop="userName">
<el-input
v-model="userQueryParams.userName"
placeholder="请输入账号"
clearable
style="width: 240px"
@keyup.enter="userHandleQuery"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="userQueryParams.realName"
placeholder="请输入姓名"
clearable
style="width: 240px"
@keyup.enter="userHandleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="userQueryParams.mobile"
placeholder="请输入手机号"
clearable
style="width: 240px"
@keyup.enter="userHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="userHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="userResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportProjectUserList">导入</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="userLoading" :data="projectUserList">
<el-table-column label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" />
<el-table-column label="昵称" prop="nickName" />
<el-table-column label="邮箱" prop="email" />
<el-table-column label="手机号" prop="mobile" />
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)" >分配角色</el-button>
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)" >删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="userTotal > 0"
:total="userTotal"
v-model:page="userQueryParams.pageNum"
v-model:limit="userQueryParams.pageSize"
@pagination="getUserList"
/>
<!-- 用户导入(根据权限从用户池导入进来) -->
<el-dialog :title="importUserTitle" v-model="importProjectUserListOpen" width="800px" append-to-body>
<el-form :model="importProjectUserListQueryParams" ref="importProjectUserListQueryRef" v-show="importProjectUserListShowSearch" :inline="true" label-width="55px">
<el-form-item label="账号" prop="userName">
<el-input
v-model="importProjectUserListQueryParams.userName"
placeholder="请输入账号"
clearable
style="width: 240px"
@keyup.enter="importProjectUserListHandleQuery"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="importProjectUserListQueryParams.realName"
placeholder="请输入姓名"
clearable
style="width: 240px"
@keyup.enter="importProjectUserListHandleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="importProjectUserListQueryParams.mobile"
placeholder="请输入手机号"
clearable
style="width: 240px"
@keyup.enter="importProjectUserListHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="importProjectUserListHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="importProjectUserListResetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="importProjectUserListLoading" :data="importProjectUserList" @selection-change="importProjectUserListHandleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" />
<el-table-column label="昵称" prop="nickName" />
<el-table-column label="邮箱" prop="email" />
<el-table-column label="手机号" prop="mobile" />
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="importProjectUserListTotal > 0"
:total="importProjectUserListTotal"
v-model:page="importProjectUserListQueryParams.pageNum"
v-model:limit="importProjectUserListQueryParams.pageSize"
@pagination="getImportProjectUserList"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="importProjectUserListSubmitForm">确 定</el-button>
<el-button @click="importProjectUserListCancel">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 用户分配角色弹窗 -->
<el-dialog
:title="assignRoleTitle"
v-model="assignRoleDialogVisible"
width="1100px"
append-to-body
>
<!-- 弹窗内容:左侧可选角色 + 中间箭头 + 右侧已选角色 -->
<div class="assign-role-container">
<!-- 左侧:可选角色列表(带复选框) -->
<div class="left-panel">
<el-form :model="leftQuery" ref="leftQueryRef" :inline="true" label-width="68px" class="search-form">
<el-form-item label="角色名称">
<el-input
v-model="leftQuery.roleName"
placeholder="请输入角色名称"
clearable
@keyup.enter="getLeftRoleList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="queryLeftRoleList">搜索</el-button>
<el-button icon="Refresh" @click="resetLeftRoleQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="leftLoading"
:data="leftRoleList"
@selection-change="handleLeftSelectionChange"
border
>
<el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="leftTotal > 0"
:total="leftTotal"
v-model:page="leftQuery.pageNo"
v-model:limit="leftQuery.pageSize"
@pagination="getLeftRoleList"
/>
</div>
<!-- 中间:箭头按钮(带提示和高亮) -->
<div class="center-panel">
<div class="arrow-buttons-container">
<!-- 向右箭头:添加到右侧 -->
<el-tooltip content="添加选中角色到已选列表" placement="top">
<el-button
icon="ArrowRight"
@click="moveToRight"
:disabled="leftSelectedRoles.length === 0"
class="arrow-btn"
/>
</el-tooltip>
<!-- 向左箭头:移除到左侧 -->
<el-tooltip content="移除选中角色到可选列表" placement="top">
<el-button
icon="ArrowLeft"
@click="moveToLeft"
:disabled="rightSelectedRoles.length === 0"
class="arrow-btn"
/>
</el-tooltip>
</div>
</div>
<!-- 右侧:已选角色列表(带复选框) -->
<div class="right-panel">
<div class="selected-title">已选角色({{ rightRoleList.length }}个)</div>
<el-form :model="rightQuery" ref="rightQueryRef" :inline="true" label-width="68px" class="search-form">
<el-form-item label="角色名称">
<el-input
v-model="rightQuery.roleName"
placeholder="请输入角色名称"
clearable
@keyup.enter="getRightRoleList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="queryRightRoleList">搜索</el-button>
<el-button icon="Refresh" @click="resetRightRoleQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="rightLoading"
:data="rightRoleList"
@selection-change="handleRightSelectionChange"
border
>
<el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="rightTotal > 0"
:total="rightTotal"
v-model:page="rightQuery.pageNo"
v-model:limit="rightQuery.pageSize"
@pagination="getRightRoleList"
/>
</div>
</div>
<!-- 底部统一保存按钮 -->
<template #footer>
<div class="dialog-footer">
<!-- <el-button type="primary" @click="saveAllRoles">确 定</el-button>-->
<el-button @click="cancelAssignRole">取 消</el-button>
</div>
</template>
</el-dialog>
</el-tab-pane>
<!-- 角色权限选项卡 -->
<el-tab-pane label="角色" name="role" class="tab-pane-content">
<el-form :model="roleQueryParams" ref="roleQueryRef" v-show="roleShowSearch" :inline="true" label-width="68px">
<el-form-item label="角色名称" prop="roleName">
<el-input
v-model="roleQueryParams.roleName"
placeholder="请输入角色名称"
clearable
style="width: 240px"
@keyup.enter="roleHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="roleResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportProjectRoleList">导入</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="roleLoading" :data="projectRoleList">
<el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)" >分配菜单</el-button>
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)" >删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="roleTotal > 0"
:total="roleTotal"
v-model:page="roleQueryParams.pageNum"
v-model:limit="roleQueryParams.pageSize"
@pagination="getRoleList"
/>
<!-- 角色导入(根据权限从角色池导入进来) -->
<el-dialog :title="importRoleTitle" v-model="importProjectRoleListOpen" width="800px" append-to-body>
<el-form :model="importProjectRoleListQueryParams" ref="importProjectRoleListQueryRef" v-show="importProjectRoleListShowSearch" :inline="true" label-width="70px">
<el-form-item label="角色名称" prop="roleName">
<el-input
v-model="importProjectRoleListQueryParams.roleName"
placeholder="请输入角色名称"
clearable
style="width: 240px"
@keyup.enter="importProjectRoleListHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="importProjectRoleListHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="importProjectRoleListResetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="importProjectRoleListLoading" :data="importProjectRoleList" @selection-change="importProjectRoleListHandleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="importProjectRoleListTotal > 0"
:total="importProjectRoleListTotal"
v-model:page="importProjectRoleListQueryParams.pageNum"
v-model:limit="importProjectRoleListQueryParams.pageSize"
@pagination="getImportProjectRoleList"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="importProjectRoleListSubmitForm">确 定</el-button>
<el-button @click="importProjectRoleListCancel">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 角色配置-分配菜单弹出框 -->
<el-dialog :title="fpMenuTitle" v-model="fpMenuOpen" width="500px" append-to-body>
<!-- 权限分配区域 -->
<div class="permission-section">
<el-card header="分配菜单" class="mt-4">
<el-tree
ref="fpMenuRef"
:data="fpMenuTree"
:props="fpMenuProps"
node-key="menuBizId"
show-checkbox
highlight-current
:check-strictly="fpMenuIsCheckStrictly"
:expand-on-click-node="false"
class="permission-tree"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ data.menuName }}</span>
</span>
</template>
</el-tree>
<div class="tree-actions mt-4">
<el-button type="primary" @click="saveFpMenuList">确定</el-button>
</div>
</el-card>
</div>
</el-dialog>
</el-tab-pane>
<!-- 菜单权限选项卡 -->
<el-tab-pane label="菜单" name="menu" class="tab-pane-content">
<el-form :model="menuQueryParams" ref="menuQueryRef" v-show="menuShowSearch" :inline="true" label-width="68px">
<el-form-item label="菜单名称" prop="menuName">
<el-input
v-model="menuQueryParams.menuName"
placeholder="请输入菜单名称"
clearable
style="width: 240px"
@keyup.enter="menuHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="menuHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="menuResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportProjectMenuList">导入</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="menuShowSearch" @queryTable="getMenuList"></right-toolbar>
</el-row>
<el-table
v-if="refreshTable"
v-loading="menuLoading"
:data="menuList"
row-key="menuBizId"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="icon" label="图标" align="center">
<template #default="scope">
<svg-icon :icon-class="scope.row.icon" />
</template>
</el-table-column>
<el-table-column prop="menuType" label="菜单类型">
<template #default="scope">
<dict-tag :options="sys_menu_type" :value="scope.row.menuType" />
</template>
</el-table-column>
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-if="scope.row.parentBizId === '0'">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 菜单导入(根据权限从菜单池导入进来) -->
<el-dialog :title="importMenuTitle" v-model="importProjectMenuListOpen" width="500px" append-to-body>
<!-- 权限分配区域 -->
<div class="permission-section">
<el-card header="导入菜单" class="mt-4">
<el-tree
ref="treeRef"
:data="menuTree"
:props="defaultProps"
node-key="menuBizId"
show-checkbox
highlight-current
:check-strictly="isCheckStrictly"
:expand-on-click-node="false"
class="permission-tree"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ data.menuName }}</span>
</span>
</template>
</el-tree>
<div class="tree-actions mt-4">
<el-button type="primary" @click="saveImportSelectedMenuList">确定</el-button>
</div>
</el-card>
</div>
</el-dialog>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup name="ProjectPermission">
import {listProjectUser,listImportProjectUser,
addImportProjectUserList,delRelProjectUser,listProjectRole,
delRelProjectRole,addImportProjectRoleList,listImportProjectRole,
listMenu,getMenuTree,getImportSelectedMenuList,addImportProjectMenuList,
listLeftRole, listRightRole, addRightRoleList,delRightRoleList,
getFpMenuTree,getSelectedFpMenuList,addFpMenuList} from "@/api/system/projectPermission"
import { ref } from 'vue'
const { proxy } = getCurrentInstance()
const { sys_status,sys_scope,sys_no_yes,sys_user_status,sys_gender,sys_role_type,sys_menu_type } = proxy.useDict("sys_status","sys_scope","sys_no_yes","sys_user_status","sys_gender","sys_role_type","sys_menu_type")
const route = useRoute()
const activeTab = ref('user')
const refreshTable = ref(true)
const projectProjectList = ref([])
const importProjectProjectList = ref([])
const projectUserList = ref([])
const importProjectUserList = ref([])
const projectRoleList = ref([])
const importProjectRoleList = ref([])
const menuList = ref([])
const isExpandAll = ref(false)
const showSearch = ref(true)
const userShowSearch = ref(true)
const importProjectUserListShowSearch = ref(true)
const roleShowSearch = ref(true)
const importProjectRoleListShowSearch = ref(true)
const menuShowSearch = ref(true)
const loading = ref(true)
const importProjectProjectListLoading = ref(true)
const userLoading = ref(true)
const importProjectUserListLoading = ref(true)
const roleLoading = ref(true)
const importProjectRoleListLoading = ref(true)
const menuLoading = ref(true)
const total = ref(0)
const importProjectProjectListTotal = ref(0)
const userTotal = ref(0)
const importProjectUserListTotal = ref(0)
const roleTotal = ref(0)
const importProjectRoleListTotal = ref(0)
const title = ref("")
const importUserTitle = ref("")
const importRoleTitle = ref("")
const importMenuTitle = ref("")
const importProjectProjectListOpen = ref(false)
const importProjectUserListOpen = ref(false)
const importProjectRoleListOpen = ref(false)
const importProjectMenuListOpen = ref(false)
const importProjectProjectListIds = ref([])
const importProjectProjectListNames = ref([])
const importProjectUserListIds = ref([])
const importProjectUserListNames = ref([])
const importProjectRoleListIds = ref([])
const importProjectRoleListNames = ref([])
// 菜单树数据(直接从接口获取树形结构)
const menuTree = ref([])
// 当前项目绑定的选中菜单ID
const checkedKeys = ref([])
// 树组件引用
const treeRef = ref(null)
// 添加响应式变量控制严格模式
const isCheckStrictly = ref(false); // 默认关闭严格模式(启用联动)
// 树形配置
const defaultProps = {
children: 'children',
label: 'menuName'
}
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
projectName: undefined
},
importProjectProjectListQueryParams: {
pageNum: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
projectName: undefined
},
userQueryParams: {
pageNum: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
userName: undefined,
realName: undefined,
mobile: undefined
},
importProjectUserListQueryParams: {
pageNum: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
userName: undefined,
realName: undefined,
mobile: undefined
},
roleQueryParams: {
pageNum: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
roleName: undefined
},
importProjectRoleListQueryParams: {
pageNum: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
roleName: undefined
},
menuQueryParams: {
pageNum: 1,
pageSize: 999999,
projectBizId: route.query.projectBizId,
menuName: undefined
}
})
const { queryParams,importProjectProjectListQueryParams, userQueryParams,
importProjectUserListQueryParams,roleQueryParams,importProjectRoleListQueryParams,
menuQueryParams,form, rules } = toRefs(data)
//========用户-列表逻辑开始=========
/** 查询项目用户关系列表 */
function getUserList() {
userLoading.value = true
listProjectUser(userQueryParams.value).then(response => {
projectUserList.value = response.data.records
userTotal.value = response.data.total
userLoading.value = false
})
}
/** 用户列表-搜索 */
function userHandleQuery() {
userQueryParams.value.pageNum = 1
getUserList()
}
/** 用户列表-重置按钮操作 */
function userResetQuery() {
proxy.resetForm("userQueryRef")
userHandleQuery()
}
/** 删除项目用户关系 */
function userHandleDelete(row) {
//项目和用户关系表主键id
const relProjectUserId = row.id
const userName = row.userName
proxy.$modal.confirm('是否确认删除用户账号为"' + userName + '"的项目和用户关系的数据项?').then(function () {
return delRelProjectUser(relProjectUserId)
}).then(() => {
getUserList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
//========用户-列表逻辑结束=========
//========用户-导入逻辑开始=========
/** 用户列表-导入-查询导入项目用户关系列表 */
function getImportProjectUserList() {
importProjectUserListLoading.value = true
listImportProjectUser(importProjectUserListQueryParams.value).then(response => {
importProjectUserList.value = response.data.records
importProjectUserListTotal.value = response.data.total
importProjectUserListLoading.value = false
})
}
/** 用户列表-导入用户 */
function handleImportProjectUserList() {
getImportProjectUserList()
importProjectUserListOpen.value = true
importUserTitle.value = "导入用户"
}
/** 用户列表-导入-搜索按钮操作 */
function importProjectUserListHandleQuery() {
importProjectUserListQueryParams.value.pageNum = 1
getImportProjectUserList()
}
/** 用户列表-导入-重置按钮操作 */
function importProjectUserListResetQuery() {
proxy.resetForm("importProjectUserListQueryRef")
importProjectUserListHandleQuery()
}
/** 用户列表-导入-多选框选中数据 */
function importProjectUserListHandleSelectionChange(selection) {
importProjectUserListIds.value = selection.map(item => item.userBizId)
importProjectUserListNames.value = selection.map(item => item.userName)
}
/** 用户列表-导入-导入用户提交 */
function importProjectUserListSubmitForm() {
const importProjectUserListIdList = importProjectUserListIds.value
const importProjectUserListNameList = importProjectUserListNames.value
const projectBizId = route.query.projectBizId
proxy.$modal.confirm('是否确认导入用户账号为"' + importProjectUserListNameList + '"的数据项?').then(function () {
return addImportProjectUserList(importProjectUserListIdList,projectBizId)
}).then(() => {
importProjectUserListOpen.value = false
getUserList()
proxy.$modal.msgSuccess("导入成功")
}).catch(() => {})
}
/** 用户列表-导入-取消 */
function importProjectUserListCancel() {
importProjectUserListOpen.value = false
proxy.resetForm("importProjectUserListQueryRef")
}
//========用户-导入逻辑结束=========
//========角色-列表逻辑开始=========
/** 查询项目角色关系列表 */
function getRoleList() {
roleLoading.value = true
listProjectRole(roleQueryParams.value).then(response => {
projectRoleList.value = response.data.records
roleTotal.value = response.data.total
roleLoading.value = false
})
}
/** 角色列表-搜索 */
function roleHandleQuery() {
roleQueryParams.value.pageNum = 1
getRoleList()
}
/** 角色列表-重置按钮操作 */
function roleResetQuery() {
proxy.resetForm("roleQueryRef")
roleHandleQuery()
}
/** 删除项目角色关系 */
function roleHandleDelete(row) {
//项目和角色关系表主键id
const relProjectRoleId = row.id
const roleName = row.roleName
proxy.$modal.confirm('是否确认删除角色名称为"' + roleName + '"的项目和角色关系的数据项?').then(function () {
return delRelProjectRole(relProjectRoleId)
}).then(() => {
getRoleList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
//========角色-列表逻辑结束=========
//========角色-导入逻辑开始=========
/** 角色列表-导入-查询导入项目角色关系列表 */
function getImportProjectRoleList() {
importProjectRoleListLoading.value = true
listImportProjectRole(importProjectRoleListQueryParams.value).then(response => {
importProjectRoleList.value = response.data.records
importProjectRoleListTotal.value = response.data.total
importProjectRoleListLoading.value = false
})
}
/** 角色列表-导入角色 */
function handleImportProjectRoleList() {
getImportProjectRoleList()
importProjectRoleListOpen.value = true
importRoleTitle.value = "导入角色"
}
/** 角色列表-导入-搜索按钮操作 */
function importProjectRoleListHandleQuery() {
importProjectRoleListQueryParams.value.pageNum = 1
getImportProjectRoleList()
}
/** 角色列表-导入-重置按钮操作 */
function importProjectRoleListResetQuery() {
proxy.resetForm("importProjectRoleListQueryRef")
importProjectRoleListHandleQuery()
}
/** 角色列表-导入-多选框选中数据 */
function importProjectRoleListHandleSelectionChange(selection) {
importProjectRoleListIds.value = selection.map(item => item.roleBizId)
importProjectRoleListNames.value = selection.map(item => item.roleName)
}
/** 角色列表-导入-导入角色提交 */
function importProjectRoleListSubmitForm() {
const importProjectRoleListIdList = importProjectRoleListIds.value
const importProjectRoleListNameList = importProjectRoleListNames.value
const projectBizId = route.query.projectBizId
proxy.$modal.confirm('是否确认导入角色名称为"' + importProjectRoleListNameList + '"的数据项?').then(function () {
return addImportProjectRoleList(importProjectRoleListIdList,projectBizId)
}).then(() => {
importProjectRoleListOpen.value = false
getRoleList()
proxy.$modal.msgSuccess("导入成功")
}).catch(() => {})
}
/** 角色列表-导入-取消 */
function importProjectRoleListCancel() {
importProjectRoleListOpen.value = false
proxy.resetForm("importProjectRoleListQueryRef")
}
//========角色-导入逻辑结束=========
//========菜单-列表逻辑结束=========
/** 查询菜单列表 */
function getMenuList() {
menuLoading.value = true
listMenu(menuQueryParams.value).then(response => {
menuList.value = proxy.handleTree(response.data.records, "menuBizId")
menuLoading.value = false
})
}
/** 菜单展开/折叠操作 */
function toggleExpandAll() {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
nextTick(() => {
refreshTable.value = true
})
}
/** 菜单列表-搜索 */
function menuHandleQuery() {
menuQueryParams.value.pageNum = 1
getMenuList()
}
/** 菜单列表-重置按钮操作 */
function menuResetQuery() {
proxy.resetForm("menuQueryRef")
menuHandleQuery()
}
//========菜单-列表逻辑结束=========
//========菜单-导入逻辑开始=========
/** 菜单列表-导入菜单 */
function handleImportProjectMenuList() {
loadMenuTree()
importProjectMenuListOpen.value = true
importMenuTitle.value = "导入菜单"
}
// 加载菜单树
const loadMenuTree = async () => {
try {
const res = await getMenuTree(route.query.projectBizId)
menuTree.value = res.data // 直接使用后端返回的树形结构
loadImportSelectedMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) {
console.error('加载菜单树失败:', error)
ElMessage.error('菜单加载失败')
}
}
// 修改加载选中菜单列表的逻辑
const loadImportSelectedMenuList = async () => {
try {
const res = await getImportSelectedMenuList(route.query.projectBizId);
const targetKeys = res.data || [];
// 开启严格模式(禁用联动)
isCheckStrictly.value = true;
// 等待DOM更新(确保严格模式生效)
await nextTick();
//清空并设置选中状态(此时不会触发联动)
if (treeRef.value) {
treeRef.value.setCheckedKeys([]);
treeRef.value.setCheckedKeys(targetKeys);
}
//等待选中状态渲染完成
await nextTick();
//关闭严格模式(恢复联动)
isCheckStrictly.value = false;
} catch (error) {
console.error('加载选中的菜单列表失败:', error);
ElMessage.error('加载选中的菜单列表失败');
// 异常时也要恢复严格模式状态
isCheckStrictly.value = false;
}
}
// 保存
const saveImportSelectedMenuList = async () => {
try {
// 获取当前选中的节点
const checkedKeys = treeRef.value.getCheckedKeys()
// 获取半选中的节点(下级勾选,上级没勾选,把上级没勾选带出来)
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys()
// 合并选中节点(根据业务需求选择是否包含半选节点)
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys]
await addImportProjectMenuList({
projectBizId: route.query.projectBizId,
menuBizIdList: allCheckedKeys
})
getMenuList()
importProjectMenuListOpen.value = false
ElMessage.success('更新成功')
} catch (error) {
console.error('更新失败:', error)
ElMessage.error('更新失败')
}
}
//========菜单-导入逻辑结束=========
//========分配角色-导入逻辑开始=========
// 弹窗显隐与标题
const assignRoleDialogVisible = ref(false)
const assignRoleTitle = ref("分配角色")
// 左侧可选角色(查询参数 + 列表数据)
const leftQuery = reactive({
pageNo: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
userBizId: '',
roleName: ''
})
const leftRoleList = ref([])
const leftLoading = ref(false)
const leftTotal = ref(0)
const leftSelectedRoles = ref([]) // 左侧选中的角色
// 右侧可选角色(查询参数 + 列表数据)
const rightQuery = reactive({
pageNo: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
userBizId: '',
roleName: ''
})
// 右侧已选角色(列表数据 + 选中项)
const rightRoleList = ref([])
const rightLoading = ref(false)
const rightTotal = ref(0)
const rightSelectedRoles = ref([]) // 右侧选中的角色
/** 打开分配角色弹窗 */
function openAssignRoleDialog(userRow) {
// 初始化状态
leftQuery.userBizId = userRow.userBizId
rightQuery.userBizId = userRow.userBizId
leftSelectedRoles.value = []
rightSelectedRoles.value = []
leftRoleList.value = []
rightRoleList.value = []
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
// 显示弹窗
assignRoleDialogVisible.value = true
}
/** 获取左侧待选角色分页列表(排除已选角色) */
function getLeftRoleList() {
leftLoading.value = true
listLeftRole({
...leftQuery
}).then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
}).catch(() => {
leftLoading.value = false
})
}
/** 获取右侧已选角色分页列表 */
function getRightRoleList() {
rightLoading.value = true
listRightRole({
...rightQuery
}).then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
}).catch(() => {
rightLoading.value = false
})
}
/** 左侧表格选中事件 */
function handleLeftSelectionChange(selection) {
leftSelectedRoles.value = selection
}
/** 右侧表格选中事件 */
function handleRightSelectionChange(selection) {
rightSelectedRoles.value = selection
}
/** 左侧搜索 */
function queryLeftRoleList() {
leftQuery.pageNo = 1
getLeftRoleList()
}
/** 重置左侧搜索 */
function resetLeftRoleQuery() {
proxy.resetForm("leftQueryRef")
leftQuery.pageNo = 1
leftQuery.roleName = ''
getLeftRoleList()
}
/** 右侧搜索 */
function queryRightRoleList() {
rightQuery.pageNo = 1
getRightRoleList()
}
/** 重置右侧搜索 */
function resetRightRoleQuery() {
proxy.resetForm("rightQueryRef")
rightQuery.pageNo = 1
rightQuery.roleName = ''
getRightRoleList()
}
/** 向右箭头:将左侧选中角色移到右侧-调接口添加关系 */
function moveToRight() {
const roleBizIdList = []
const leftRoleNameList = []
leftSelectedRoles.value.forEach(r => {
roleBizIdList.push(r.roleBizId)
leftRoleNameList.push(r.roleName)
})
const data = {
projectBizId: route.query.projectBizId,
userBizId: leftQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?').then(function () {
return addRightRoleList(data)
}).then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("添加成功")
}).catch(() => {})
}
/** 向左箭头:将右侧选中角色移除左侧,删除关系*/
function moveToLeft() {
const roleBizIdList = []
const rightRoleNameList = []
rightSelectedRoles.value.forEach(r => {
roleBizIdList.push(r.roleBizId)
rightRoleNameList.push(r.roleName)
})
const data = {
projectBizId: route.query.projectBizId,
userBizId: rightQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?').then(function () {
return delRightRoleList(data)
}).then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("移除成功")
}).catch(() => {})
}
/** 取消操作 */
function cancelAssignRole() {
assignRoleDialogVisible.value = false
}
//========分配角色-导入逻辑结束=========
//========分配菜单-导入逻辑开始=========
// 菜单树数据(直接从接口获取树形结构)
const fpMenuTree = ref([])
// 树组件引用
const fpMenuRef = ref(null)
// 添加响应式变量控制严格模式
const fpMenuIsCheckStrictly = ref(false); // 默认关闭严格模式(启用联动)
// 树形配置
const fpMenuProps = {
children: 'children',
label: 'menuName'
}
const getSelectedFpMenuListParams = reactive({
projectBizId: route.query.projectBizId,
roleBizId: ''
})
const addFpMenuParams = reactive({
roleBizId: ''
})
const getFpMenuTreeParams = reactive({
pageNo: 1,
pageSize: 10,
projectBizId: route.query.projectBizId,
menuName: undefined
})
const fpMenuTitle = ref("")
const fpMenuOpen = ref(false)
/** 分配菜单弹出 */
function handleFpMenu(row) {
addFpMenuParams.roleBizId = row.roleBizId
getSelectedFpMenuListParams.roleBizId = row.roleBizId
loadFpMenuTree()
fpMenuOpen.value = true
fpMenuTitle.value = "分配菜单"
}
// 加载菜单树
const loadFpMenuTree = async () => {
try {
const res = await getFpMenuTree({...getFpMenuTreeParams})
fpMenuTree.value = res.data // 直接使用后端返回的树形结构
loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) {
console.error('加载菜单树失败:', error)
proxy.$modal.msgSuccess("菜单加载失败")
}
}
// 修改加载选中菜单列表的逻辑
const loadSelectedFpMenuList = async () => {
try {
const res = await getSelectedFpMenuList({...getSelectedFpMenuListParams});
const targetKeys = res.data || [];
// 开启严格模式(禁用联动)
fpMenuIsCheckStrictly.value = true;
// 等待DOM更新(确保严格模式生效)
await nextTick();
//清空并设置选中状态(此时不会触发联动)
if (fpMenuRef.value) {
fpMenuRef.value.setCheckedKeys([]);
fpMenuRef.value.setCheckedKeys(targetKeys);
}
//等待选中状态渲染完成
await nextTick();
//关闭严格模式(恢复联动)
fpMenuIsCheckStrictly.value = false;
} catch (error) {
console.error('加载选中的菜单列表失败:', error);
proxy.$modal.msgSuccess("加载选中的菜单列表失败")
// 异常时也要恢复严格模式状态
fpMenuIsCheckStrictly.value = false;
}
}
// 保存
const saveFpMenuList = async () => {
try {
// 获取当前选中的节点
const checkedKeys = fpMenuRef.value.getCheckedKeys()
// 获取半选中的节点(下级勾选,上级没勾选,把上级没勾选带出来)
const halfCheckedKeys = fpMenuRef.value.getHalfCheckedKeys()
// 合并选中节点(根据业务需求选择是否包含半选节点)
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys]
await addFpMenuList({
projectBizId: route.query.projectBizId,
roleBizId: addFpMenuParams.roleBizId,
menuBizIdList: allCheckedKeys
})
getRoleList()
fpMenuOpen.value = false
proxy.$modal.msgSuccess("更新成功")
} catch (error) {
console.error('更新失败:', error)
proxy.$modal.msgSuccess("更新失败")
}
}
//========分配菜单-导入逻辑结束=========
const handleTabChange = (tabName) => {
console.log('切换到:', tabName)
if (tabName === "user"){
//用户tab获取用户列表数据
getUserList()
}else if (tabName === "role"){
//用户tab获取角色列表数据
getRoleList()
}else if (tabName === "menu"){
//用户tab获取菜单树数据
getMenuList()
}
}
getUserList()
</script>
<style scoped>
/* 全局容器 */
.app-container {
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 60px); /* 适配布局 */
}
/* 自定义选项卡:核心样式 */
.custom-tabs {
--active-tab-color: #fff; /* 激活选项卡文字颜色 */
--active-tab-bg: #409eff; /* 激活选项卡背景色 */
--inactive-tab-color: #606266; /* 未激活文字颜色 */
--inactive-tab-bg: #f5f7fa; /* 未激活背景色 */
--border-color: #dcdfe6; /* 边框颜色 */
--radius: 8px; /* 圆角 */
width: 100%;
}
/* 选项卡头部:消除默认样式,重构凹陷效果 */
::v-deep .el-tabs__header {
line-height: 1; /* 重置行高,避免影响高度计算 */
padding: 0; /* 清除默认内边距 */
}
/* 选项卡导航容器:核心凹陷逻辑 */
::v-deep .el-tabs__nav {
display: flex;
margin: 0; /* 清除默认 margin */
border-bottom: 1px solid var(--border-color); /* 底部边框 */
}
/* 选项卡 item 基础样式 */
::v-deep .el-tabs__item {
position: relative;
padding: 12px 24px;
margin-right: 0; /* 清除默认间距 */
font-size: 14px;
color: var(--inactive-tab-color);
background-color: var(--inactive-tab-bg);
border: 1px solid var(--border-color);
border-bottom: none; /* 隐藏底部边框,通过父容器 border 实现分割 */
cursor: pointer;
transition: all 0.3s ease;
}
/* 激活态:凹陷 + 高亮 */
::v-deep .el-tabs__item.is-active {
color: var(--active-tab-color);
background-color: var(--active-tab-bg);
/* 上、左、右边框 + 底部无边框,模拟“凹陷” */
border: 1px solid var(--border-color);
border-bottom-color: var(--active-tab-bg); /* 与背景色一致,视觉上“凹陷” */
/* 调整层级,避免底部边框遮挡 */
z-index: 1;
/* 细微阴影增强立体 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 未激活态:悬浮效果 */
::v-deep .el-tabs__item:not(.is-active):hover {
color: #409eff;
background-color: #eaf2ff;
}
/* 左侧边框处理(仅第一个 tab 左侧圆角) */
::v-deep .el-tabs__item:first-child {
border-top-left-radius: var(--radius);
}
/* 右侧边框处理(仅最后一个 tab 右侧圆角) */
::v-deep .el-tabs__item:last-child {
border-top-right-radius: var(--radius);
}
/* 激活态:覆盖父容器的底部边框,实现“凹陷” */
::v-deep .el-tabs__item.is-active::before {
content: "";
position: absolute;
bottom: -1px; /* 覆盖父容器的 border-bottom */
left: 0;
width: 100%;
height: 1px;
background-color: var(--active-tab-bg); /* 与激活态背景一致 */
}
/* 内容区域:和选项卡无缝衔接 */
::v-deep .el-tabs__content {
padding: 20px;
background-color: #fff;
border: 1px solid var(--border-color);
border-top: none; /* 消除顶部边框,与选项卡衔接 */
border-bottom-left-radius: var(--radius);
border-bottom-right-radius: var(--radius);
}
/* 空数据提示 */
.empty-tip {
text-align: center;
padding: 40px 0;
color: #909399;
}
.permission-section {
margin-top: 20px;
}
.permission-tree {
max-height: 500px;
overflow-y: auto;
padding: 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
.perm-tag {
margin-left: 10px;
padding: 0 5px;
background-color: #ecf5ff;
color: #409eff;
border-radius: 3px;
font-size: 12px;
}
.tree-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* 分配角色弹窗布局 */
.assign-role-container {
display: flex;
height: 450px;
padding: 15px 0;
box-sizing: border-box;
}
/* 左侧面板 */
.left-panel {
flex: 1;
height: 100%;
overflow: auto;
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 15px;
background-color: #fff;
}
/* 搜索表单样式 */
.search-form {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px dashed #f0f0f0;
}
/* 中间箭头区域 */
.center-panel {
width: 60px;
display: flex;
flex-direction: column;
justify-content: center; /* 垂直居中 */
align-items: center; /* 水平居中 */
height: 100%;
padding: 0 10px;
}
.arrow-buttons-container {
display: flex;
flex-direction: column;
gap: 20px;
align-items: center; /* 确保按钮水平居中 */
}
/* 箭头按钮样式(基础 + 高亮) */
.arrow-btn {
width: 36px;
height: 36px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: #409eff;
border: none;
color: white;
font-size: 16px;
border-radius: 50%;
transition: all 0.2s ease;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.arrow-btn {
display: inline-flex;
margin: 0 auto;
}
.arrow-btn:hover {
background-color: #66b1ff;
transform: scale(1.1);
}
.arrow-btn:disabled {
background-color: #c0c4cc;
cursor: not-allowed;
opacity: 0.6;
transform: none;
}
/* 右侧面板(带边框) */
.right-panel {
flex: 1;
height: 100%;
overflow: auto;
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 15px;
background-color: #fff;
}
/* 右侧标题 */
.selected-title {
font-size: 14px;
font-weight: 500;
color: #374151;
padding-bottom: 10px;
border-bottom: 1px dashed #f0f0f0;
}
</style>
......@@ -12,8 +12,18 @@
<el-input v-model="queryParams.contactPhone" placeholder="请输入联系电话" clearable style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="租户状态" clearable style="width: 240px">
<!-- <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />-->
<el-select
v-model="queryParams.status"
placeholder="租户状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in sys_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
......@@ -38,12 +48,13 @@
</el-table-column>
<el-table-column label="最大项目数" align="center" prop="maxProject" />
<el-table-column label="最大用户数" align="center" prop="maxUser" />
<el-table-column label="状态" align="center" key="status" >
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="1"
inactive-value="0"
:active-value="1"
:inactive-value="0"
@change="(val) => handleStatusChange(scope.row, $event)"
></el-switch>
</template>
</el-table-column>
......@@ -155,12 +166,12 @@
<script setup name="Tenant">
import { getToken } from "@/utils/auth"
import useAppStore from '@/store/modules/app'
import { changeUserStatus, listTenant, resetUserPwd, delUser, getTenant, updateTenant, addTenant } from "@/api/system/tenant"
import { changeUserStatus, listTenant, resetUserPwd, delUser, getTenant, updateTenant, addTenant,changeTenantStatus } from "@/api/system/tenant"
const router = useRouter()
const appStore = useAppStore()
const { proxy } = getCurrentInstance()
// const { sys_normal_disable, sys_user_sex } = proxy.useDict("sys_normal_disable", "sys_user_sex")
const { sys_status,sys_scope,sys_no_yes } = proxy.useDict("sys_status","sys_scope","sys_no_yes")
const tenantList = ref([])
const open = ref(false)
......@@ -265,15 +276,16 @@ function handleExport() {
},`user_${new Date().getTime()}.xlsx`)
}
/** 用户状态修改 */
function handleStatusChange(row) {
let text = row.status === "1" ? "启用" : "停用"
proxy.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?').then(function () {
return changeUserStatus(row.tenantBizId, row.status)
}).then(() => {
proxy.$modal.msgSuccess(text + "成功")
}).catch(function () {
row.status = row.status === "1" ? "0" : "1"
/** 租户状态修改 */
function handleStatusChange(row, event) {
let text = row.status === 0 ? "停用" : "启用"
const tenantName = row.tenantName
proxy.$modal.confirm(`确认要${text}"${tenantName}"租户吗?`)
.then(() => changeTenantStatus({tenantBizId: row.tenantBizId, status: row.status}))
.then(() => proxy.$modal.msgSuccess(`${text}成功`))
.catch(() => {
// 操作取消时恢复原状态
row.status = row.status === 0 ? 1 : 0
})
}
......
......@@ -49,7 +49,7 @@
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
......@@ -80,7 +80,7 @@
<pagination
v-show="importTenantProjectListTotal > 0"
:total="importTenantProjectListTotal"
v-model:page="importTenantProjectListQueryParams.pageNum"
v-model:page="importTenantProjectListQueryParams.pageNo"
v-model:limit="importTenantProjectListQueryParams.pageSize"
@pagination="getImportTenantProjectList"
/>
......@@ -137,7 +137,11 @@
<el-table-column label="昵称" prop="nickName" />
<el-table-column label="邮箱" prop="email" />
<el-table-column label="手机号" prop="mobile" />
<el-table-column label="性别" prop="gender" />
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)" >分配角色</el-button>
......@@ -149,7 +153,7 @@
<pagination
v-show="userTotal > 0"
:total="userTotal"
v-model:page="userQueryParams.pageNum"
v-model:page="userQueryParams.pageNo"
v-model:limit="userQueryParams.pageSize"
@pagination="getUserList"
/>
......@@ -197,12 +201,16 @@
<el-table-column label="昵称" prop="nickName" />
<el-table-column label="邮箱" prop="email" />
<el-table-column label="手机号" prop="mobile" />
<el-table-column label="性别" prop="gender" />
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="importTenantUserListTotal > 0"
:total="importTenantUserListTotal"
v-model:page="importTenantUserListQueryParams.pageNum"
v-model:page="importTenantUserListQueryParams.pageNo"
v-model:limit="importTenantUserListQueryParams.pageSize"
@pagination="getImportTenantUserList"
/>
......@@ -218,7 +226,7 @@
<el-dialog
:title="assignRoleTitle"
v-model="assignRoleDialogVisible"
width="1000px"
width="1100px"
append-to-body
>
<!-- 弹窗内容:左侧可选角色 + 中间箭头 + 右侧已选角色 -->
......@@ -235,8 +243,8 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getLeftRoleList">搜索</el-button>
<el-button icon="Refresh" @click="resetLeftQuery">重置</el-button>
<el-button type="primary" icon="Search" @click="queryLeftRoleList">搜索</el-button>
<el-button icon="Refresh" @click="resetLeftRoleQuery">重置</el-button>
</el-form-item>
</el-form>
......@@ -245,16 +253,17 @@
:data="leftRoleList"
@selection-change="handleLeftSelectionChange"
border
row-key="roleId"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="roleName" width="200" />
<el-table-column label="权限字符" prop="roleKey" width="200" />
<el-table-column label="状态" prop="status">
<el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
{{ scope.row.status === '0' ? '启用' : '停用' }}
</el-tag>
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
</el-table>
......@@ -262,10 +271,9 @@
<pagination
v-show="leftTotal > 0"
:total="leftTotal"
v-model:page="leftQuery.pageNum"
v-model:page="leftQuery.pageNo"
v-model:limit="leftQuery.pageSize"
@pagination="getLeftRoleList"
style="margin-top: 10px"
/>
</div>
......@@ -277,6 +285,7 @@
<el-button
icon="ArrowRight"
@click="moveToRight"
:disabled="leftSelectedRoles.length === 0"
class="arrow-btn"
/>
</el-tooltip>
......@@ -286,42 +295,66 @@
<el-button
icon="ArrowLeft"
@click="moveToLeft"
:disabled="rightSelectedRoles.length === 0"
class="arrow-btn"
/>
</el-tooltip>
</div>
</div>
<!-- 右侧:已选角色列表(带复选框) -->
<div class="right-panel">
<div class="selected-title">已选角色({{ rightRoleList.length }}个)</div>
<el-form :model="rightQuery" ref="rightQueryRef" :inline="true" label-width="68px" class="search-form">
<el-form-item label="角色名称">
<el-input
v-model="rightQuery.roleName"
placeholder="请输入角色名称"
clearable
@keyup.enter="getRightRoleList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="queryRightRoleList">搜索</el-button>
<el-button icon="Refresh" @click="resetRightRoleQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="rightLoading"
:data="rightRoleList"
@selection-change="handleRightSelectionChange"
border
row-key="roleId"
style="margin-top: 10px"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="roleName" width="200" />
<el-table-column label="权限字符" prop="roleKey" width="200" />
<el-table-column label="状态" prop="status">
<el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
{{ scope.row.status === '0' ? '启用' : '停用' }}
</el-tag>
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="rightTotal > 0"
:total="rightTotal"
v-model:page="rightQuery.pageNo"
v-model:limit="rightQuery.pageSize"
@pagination="getRightRoleList"
/>
</div>
</div>
<!-- 底部统一保存按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="saveAllRoles">确 定</el-button>
<!-- <el-button type="primary" @click="saveAllRoles">确 定</el-button>-->
<el-button @click="cancelAssignRole">取 消</el-button>
</div>
</template>
......@@ -349,8 +382,16 @@
<!-- 表格数据 -->
<el-table v-loading="roleLoading" :data="tenantRoleList">
<el-table-column label="角色名称" prop="roleName" />
<el-table-column label="作用域" prop="scope" />
<el-table-column label="角色类型" prop="roleType" />
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)" >分配菜单</el-button>
......@@ -362,11 +403,41 @@
<pagination
v-show="roleTotal > 0"
:total="roleTotal"
v-model:page="roleQueryParams.pageNum"
v-model:page="roleQueryParams.pageNo"
v-model:limit="roleQueryParams.pageSize"
@pagination="getRoleList"
/>
<!-- 角色配置-分配菜单弹出框 -->
<el-dialog :title="fpMenuTitle" v-model="fpMenuOpen" width="500px" append-to-body>
<!-- 权限分配区域 -->
<div class="permission-section">
<el-card header="分配菜单" class="mt-4">
<el-tree
ref="fpMenuRef"
:data="fpMenuTree"
:props="fpMenuProps"
node-key="menuBizId"
show-checkbox
highlight-current
:check-strictly="fpMenuIsCheckStrictly"
:expand-on-click-node="false"
class="permission-tree"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ data.menuName }}</span>
</span>
</template>
</el-tree>
<div class="tree-actions mt-4">
<el-button type="primary" @click="saveFpMenuList">确定</el-button>
</div>
</el-card>
</div>
</el-dialog>
<!-- 角色导入(根据权限从角色池导入进来) -->
<el-dialog :title="importRoleTitle" v-model="importTenantRoleListOpen" width="800px" append-to-body>
<el-form :model="importTenantRoleListQueryParams" ref="importTenantRoleListQueryRef" v-show="importTenantRoleListShowSearch" :inline="true" label-width="70px">
......@@ -388,13 +459,21 @@
<el-table v-loading="importTenantRoleListLoading" :data="importTenantRoleList" @selection-change="importTenantRoleListHandleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="roleName" />
<el-table-column label="作用域" prop="scope" />
<el-table-column label="角色类型" prop="roleType" />
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column prop="roleType" label="角色类型">
<template #default="scope">
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="importTenantRoleListTotal > 0"
:total="importTenantRoleListTotal"
v-model:page="importTenantRoleListQueryParams.pageNum"
v-model:page="importTenantRoleListQueryParams.pageNo"
v-model:limit="importTenantRoleListQueryParams.pageSize"
@pagination="getImportTenantRoleList"
/>
......@@ -452,14 +531,14 @@
</template>
</el-table-column>
<el-table-column prop="menuType" label="菜单类型">
<!-- <template #default="scope">-->
<!-- <dict-tag :options="sys_normal_disable" :value="scope.row.status" />-->
<!-- </template>-->
<template #default="scope">
<dict-tag :options="sys_menu_type" :value="scope.row.menuType" />
</template>
</el-table-column>
<el-table-column prop="scope" label="菜单类型">
<!-- <template #default="scope">-->
<!-- <dict-tag :options="sys_normal_disable" :value="scope.row.status" />-->
<!-- </template>-->
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
......@@ -504,15 +583,20 @@
</template>
<script setup name="TenantPermission">
import { listTenantProject,listImportTenantProject,addImportTenantProjectList,
delRelTenantProject,listTenantUser,listImportTenantUser,
addImportTenantUserList,delRelTenantUser,listTenantRole,
delRelTenantRole,addImportTenantRoleList,listImportTenantRole,
listMenu,getMenuTree,getImportSelectedMenuList,addImportTenantMenuList,
listRole, getUserRole, assignUserRole} from "@/api/system/tenantPermission"
listLeftRole, listRightRole,addRightRoleList,delRightRoleList,
getFpMenuTree,getSelectedFpMenuList,addFpMenuList} from "@/api/system/tenantPermission"
import { ref } from 'vue'
const { proxy } = getCurrentInstance()
const { sys_status,sys_scope,sys_no_yes,sys_user_status,sys_gender,sys_role_type,sys_menu_type } = proxy.useDict("sys_status","sys_scope","sys_no_yes","sys_user_status","sys_gender","sys_role_type","sys_menu_type")
const route = useRoute()
const activeTab = ref('project')
const refreshTable = ref(true)
......@@ -562,8 +646,6 @@ const importTenantRoleListNames = ref([])
// 菜单树数据(直接从接口获取树形结构)
const menuTree = ref([])
// 当前租户绑定的选中菜单ID
const checkedKeys = ref([])
// 树组件引用
const treeRef = ref(null)
// 添加响应式变量控制严格模式
......@@ -577,19 +659,19 @@ const defaultProps = {
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
projectName: undefined
},
importTenantProjectListQueryParams: {
pageNum: 1,
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
projectName: undefined
},
userQueryParams: {
pageNum: 1,
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
userName: undefined,
......@@ -597,7 +679,7 @@ const data = reactive({
mobile: undefined
},
importTenantUserListQueryParams: {
pageNum: 1,
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
userName: undefined,
......@@ -605,19 +687,19 @@ const data = reactive({
mobile: undefined
},
roleQueryParams: {
pageNum: 1,
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
roleName: undefined
},
importTenantRoleListQueryParams: {
pageNum: 1,
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
roleName: undefined
},
menuQueryParams: {
pageNum: 1,
pageNo: 1,
pageSize: 999999,
tenantBizId: route.query.tenantBizId,
menuName: undefined
......@@ -639,7 +721,7 @@ function getList() {
}
/** 搜索 */
function handleQuery() {
queryParams.value.pageNum = 1
queryParams.value.pageNo = 1
getList()
}
/** 项目列表-重置按钮操作 */
......@@ -679,7 +761,7 @@ function handleImportTenantProjectList() {
}
/** 搜索按钮操作 */
function importTenantProjectListHandleQuery() {
importTenantProjectListQueryParams.value.pageNum = 1
importTenantProjectListQueryParams.value.pageNo = 1
getImportTenantProjectList()
}
/** 重置按钮操作 */
......@@ -726,7 +808,7 @@ function getUserList() {
}
/** 用户列表-搜索 */
function userHandleQuery() {
userQueryParams.value.pageNum = 1
userQueryParams.value.pageNo = 1
getUserList()
}
/** 用户列表-重置按钮操作 */
......@@ -767,7 +849,7 @@ function handleImportTenantUserList() {
}
/** 用户列表-导入-搜索按钮操作 */
function importTenantUserListHandleQuery() {
importTenantUserListQueryParams.value.pageNum = 1
importTenantUserListQueryParams.value.pageNo = 1
getImportTenantUserList()
}
/** 用户列表-导入-重置按钮操作 */
......@@ -814,7 +896,7 @@ function getRoleList() {
}
/** 角色列表-搜索 */
function roleHandleQuery() {
roleQueryParams.value.pageNum = 1
roleQueryParams.value.pageNo = 1
getRoleList()
}
/** 角色列表-重置按钮操作 */
......@@ -855,7 +937,7 @@ function handleImportTenantRoleList() {
}
/** 角色列表-导入-搜索按钮操作 */
function importTenantRoleListHandleQuery() {
importTenantRoleListQueryParams.value.pageNum = 1
importTenantRoleListQueryParams.value.pageNo = 1
getImportTenantRoleList()
}
/** 角色列表-导入-重置按钮操作 */
......@@ -909,7 +991,7 @@ function toggleExpandAll() {
}
/** 菜单列表-搜索 */
function menuHandleQuery() {
menuQueryParams.value.pageNum = 1
menuQueryParams.value.pageNo = 1
getMenuList()
}
/** 菜单列表-重置按钮操作 */
......@@ -999,69 +1081,84 @@ const saveImportSelectedMenuList = async () => {
// 全局实例(用于弹窗、表单操作)
// ===== 核心数据 =====
//========分配角色-导入逻辑开始=========
// 弹窗显隐与标题
const assignRoleDialogVisible = ref(false)
const assignRoleTitle = ref("分配角色")
// 左侧可选角色(查询参数 + 列表数据)
const leftQuery = reactive({
pageNum: 1,
pageNo: 1,
pageSize: 10,
roleName: '',
userId: '' // 当前操作的用户ID(用于过滤已选角色)
tenantBizId: route.query.tenantBizId,
userBizId: '',
roleName: ''
})
const leftRoleList = ref([])
const leftLoading = ref(false)
const leftTotal = ref(0)
const leftSelectedRoles = ref([]) // 左侧选中的角色
// 右侧可选角色(查询参数 + 列表数据)
const rightQuery = reactive({
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
userBizId: '',
roleName: ''
})
// 右侧已选角色(列表数据 + 选中项)
const rightRoleList = ref([])
const rightLoading = ref(false)
const rightTotal = ref(0)
const rightSelectedRoles = ref([]) // 右侧选中的角色
const originRightRoles = ref([]) // 原始已选角色(用于计算变更)
// ===== 方法 =====
/** 打开分配角色弹窗 */
function openAssignRoleDialog(userRow) {
// 初始化状态
leftQuery.userId = userRow.userId
leftQuery.userBizId = userRow.userBizId
rightQuery.userBizId = userRow.userBizId
leftSelectedRoles.value = []
rightSelectedRoles.value = []
leftRoleList.value = []
rightRoleList.value = []
// 加载数据
getLeftRoleList() // 加载可选角色(排除已选)
getOriginRightRoles(userRow.userId) // 加载用户当前已分配的角色
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
// 显示弹窗
assignRoleDialogVisible.value = true
}
/** 获取左侧可选角色列表(排除已选角色) */
/** 获取左侧待选角色分页列表(排除已选角色) */
function getLeftRoleList() {
leftLoading.value = true
// listRole({
// ...leftQuery,
// excludeRoleIds: rightRoleList.value.map(role => role.roleId) // 排除右侧已选角色
// }).then(res => {
// leftRoleList.value = res.rows
// leftTotal.value = res.total
// leftLoading.value = false
// }).catch(() => {
// leftLoading.value = false
// })
}
/** 获取用户原始已选角色(用于后续计算变更) */
function getOriginRightRoles(userId) {
// getUserRole(userId).then(res => {
// rightRoleList.value = res.roles || []
// originRightRoles.value = [...rightRoleList.value] // 深拷贝原始数据
// })
listLeftRole({
...leftQuery
}).then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
}).catch(() => {
leftLoading.value = false
})
}
/** 获取右侧已选角色分页列表 */
function getRightRoleList() {
rightLoading.value = true
listRightRole({
...rightQuery
}).then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
}).catch(() => {
rightLoading.value = false
})
}
/** 左侧表格选中事件 */
......@@ -1074,36 +1171,80 @@ function handleRightSelectionChange(selection) {
rightSelectedRoles.value = selection
}
/** 左侧搜索 */
function queryLeftRoleList() {
leftQuery.pageNo = 1
getLeftRoleList()
}
/** 重置左侧搜索 */
function resetLeftQuery() {
function resetLeftRoleQuery() {
proxy.resetForm("leftQueryRef")
leftQuery.pageNum = 1
leftQuery.pageNo = 1
leftQuery.roleName = ''
getLeftRoleList()
}
/** 向右箭头:将左侧选中角色移到右侧 */
/** 右侧搜索 */
function queryRightRoleList() {
rightQuery.pageNo = 1
getRightRoleList()
}
/** 重置右侧搜索 */
function resetRightRoleQuery() {
proxy.resetForm("rightQueryRef")
rightQuery.pageNo = 1
rightQuery.roleName = ''
getRightRoleList()
}
/** 向右箭头:将左侧选中角色移到右侧-调接口添加关系 */
function moveToRight() {
leftSelectedRoles.value.forEach(role => {
// 去重:右侧不存在才添加
const isExist = rightRoleList.value.some(r => r.roleId === role.roleId)
if (!isExist) {
rightRoleList.value.push(role)
}
const roleBizIdList = []
const leftRoleNameList = []
leftSelectedRoles.value.forEach(r => {
roleBizIdList.push(r.roleBizId)
leftRoleNameList.push(r.roleName)
})
// 清空左侧选中 + 刷新左侧列表(排除新添加的角色)
leftSelectedRoles.value = []
const data = {
tenantBizId: route.query.tenantBizId,
userBizId: leftQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?').then(function () {
return addRightRoleList(data)
}).then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("添加成功")
}).catch(() => {})
}
/** 向左箭头:将右侧选中角色移到左侧 */
/** 向左箭头:将右侧选中角色移除左侧,删除关系*/
function moveToLeft() {
// 过滤掉右侧选中的角色
rightRoleList.value = rightRoleList.value.filter(
role => !rightSelectedRoles.value.some(r => r.roleId === role.roleId)
)
// 清空右侧选中 + 刷新左侧列表(重新包含移除的角色)
rightSelectedRoles.value = []
const roleBizIdList = []
const rightRoleNameList = []
rightSelectedRoles.value.forEach(r => {
roleBizIdList.push(r.roleBizId)
rightRoleNameList.push(r.roleName)
})
const data = {
tenantBizId: route.query.tenantBizId,
userBizId: rightQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?').then(function () {
return delRightRoleList(data)
}).then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("移除成功")
}).catch(() => {})
}
/** 底部保存按钮:统一提交所有变更 */
......@@ -1131,18 +1272,119 @@ function saveAllRoles() {
// emit('refreshUserList')
})
}
/** 取消操作 */
function cancelAssignRole() {
assignRoleDialogVisible.value = false
}
//========分配角色-导入逻辑结束=========
// 暴露打开弹窗的方法给外部调用
defineExpose({
openAssignRoleDialog
//========分配菜单-导入逻辑开始=========
// 菜单树数据(直接从接口获取树形结构)
const fpMenuTree = ref([])
// 树组件引用
const fpMenuRef = ref(null)
// 添加响应式变量控制严格模式
const fpMenuIsCheckStrictly = ref(false); // 默认关闭严格模式(启用联动)
// 树形配置
const fpMenuProps = {
children: 'children',
label: 'menuName'
}
const getSelectedFpMenuListParams = reactive({
tenantBizId: route.query.tenantBizId,
roleBizId: ''
})
const addFpMenuParams = reactive({
roleBizId: ''
})
const getFpMenuTreeParams = reactive({
pageNo: 1,
pageSize: 10,
tenantBizId: route.query.tenantBizId,
menuName: undefined
})
const fpMenuTitle = ref("")
const fpMenuOpen = ref(false)
/** 分配菜单弹出 */
function handleFpMenu(row) {
addFpMenuParams.roleBizId = row.roleBizId
getSelectedFpMenuListParams.roleBizId = row.roleBizId
loadFpMenuTree()
fpMenuOpen.value = true
fpMenuTitle.value = "分配菜单"
}
// 加载菜单树
const loadFpMenuTree = async () => {
try {
const res = await getFpMenuTree({...getFpMenuTreeParams})
fpMenuTree.value = res.data // 直接使用后端返回的树形结构
loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) {
console.error('加载菜单树失败:', error)
ElMessage.error('菜单加载失败')
}
}
// 修改加载选中菜单列表的逻辑
const loadSelectedFpMenuList = async () => {
try {
const res = await getSelectedFpMenuList({...getSelectedFpMenuListParams});
const targetKeys = res.data || [];
// 开启严格模式(禁用联动)
fpMenuIsCheckStrictly.value = true;
// 等待DOM更新(确保严格模式生效)
await nextTick();
//清空并设置选中状态(此时不会触发联动)
if (fpMenuRef.value) {
fpMenuRef.value.setCheckedKeys([]);
fpMenuRef.value.setCheckedKeys(targetKeys);
}
//等待选中状态渲染完成
await nextTick();
//关闭严格模式(恢复联动)
fpMenuIsCheckStrictly.value = false;
} catch (error) {
console.error('加载选中的菜单列表失败:', error);
ElMessage.error('加载选中的菜单列表失败');
// 异常时也要恢复严格模式状态
fpMenuIsCheckStrictly.value = false;
}
}
// 保存
const saveFpMenuList = async () => {
try {
// 获取当前选中的节点
const checkedKeys = fpMenuRef.value.getCheckedKeys()
// 获取半选中的节点(下级勾选,上级没勾选,把上级没勾选带出来)
const halfCheckedKeys = fpMenuRef.value.getHalfCheckedKeys()
// 合并选中节点(根据业务需求选择是否包含半选节点)
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys]
await addFpMenuList({
tenantBizId: route.query.tenantBizId,
roleBizId: addFpMenuParams.roleBizId,
menuBizIdList: allCheckedKeys
})
getRoleList()
fpMenuOpen.value = false
ElMessage.success('更新成功')
} catch (error) {
console.error('更新失败:', error)
ElMessage.error('更新失败')
}
}
//========分配菜单-导入逻辑结束=========
const handleTabChange = (tabName) => {
......
<template>
<div class="app-container">
<h4 class="form-header h4">基本信息</h4>
<el-form :model="form" label-width="80px">
<el-row>
<el-col :span="8" :offset="2">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" disabled />
</el-form-item>
</el-col>
<el-col :span="8" :offset="2">
<el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
<h4 class="form-header h4">角色信息</h4>
<el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
<el-table-column label="序号" width="55" type="index" align="center">
<template #default="scope">
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
<el-table-column label="角色编号" align="center" prop="roleId" />
<el-table-column label="角色名称" align="center" prop="roleName" />
<el-table-column label="权限字符" align="center" prop="roleKey" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
<el-form label-width="100px">
<div style="text-align: center;margin-left:-120px;margin-top:30px;">
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</div>
</el-form>
</div>
</template>
<script setup name="AuthRole">
import { getAuthRole, updateAuthRole } from "@/api/system/user"
const route = useRoute()
const { proxy } = getCurrentInstance()
const loading = ref(true)
const total = ref(0)
const pageNum = ref(1)
const pageSize = ref(10)
const roleIds = ref([])
const roles = ref([])
const form = ref({
nickName: undefined,
userName: undefined,
userId: undefined
})
/** 单击选中行数据 */
function clickRow(row) {
if (checkSelectable(row)) {
proxy.$refs["roleRef"].toggleRowSelection(row)
}
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
roleIds.value = selection.map(item => item.roleId)
}
/** 保存选中的数据编号 */
function getRowKey(row) {
return row.roleId
}
// 检查角色状态
function checkSelectable(row) {
return row.status === "0" ? true : false
}
/** 关闭按钮 */
function close() {
const obj = { path: "/system/user" }
proxy.$tab.closeOpenPage(obj)
}
/** 提交按钮 */
function submitForm() {
const userId = form.value.userId
const rIds = roleIds.value.join(",")
updateAuthRole({ userId: userId, roleIds: rIds }).then(response => {
proxy.$modal.msgSuccess("授权成功")
close()
})
}
(() => {
const userId = route.params && route.params.userId
if (userId) {
loading.value = true
getAuthRole(userId).then(response => {
form.value = response.user
roles.value = response.roles
total.value = roles.value.length
nextTick(() => {
roles.value.forEach(row => {
if (row.flag) {
proxy.$refs["roleRef"].toggleRowSelection(row)
}
})
})
loading.value = false
})
}
})()
</script>
<template>
<div class="app-container">
<el-row :gutter="20">
<splitpanes :horizontal="appStore.device === 'mobile'" class="default-theme">
<!--部门数据-->
<pane size="16">
<el-col>
<div class="head-container">
<el-input v-model="deptName" placeholder="请输入部门名称" clearable prefix-icon="Search" style="margin-bottom: 20px" />
</div>
<div class="head-container">
<el-tree :data="deptOptions" :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode" ref="deptTreeRef" node-key="id" highlight-current default-expand-all @node-click="handleNodeClick" />
</div>
</el-col>
</pane>
<!--用户数据-->
<pane size="84">
<el-col>
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 240px">
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:user:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:user:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:user:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:user:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:user:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns.userId.visible" />
<el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns.userName.visible" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns.nickName.visible" :show-overflow-tooltip="true" />
<el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns.deptName.visible" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns.phonenumber.visible" width="120" />
<el-table-column label="状态" align="center" key="status" v-if="columns.status.visible">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns.createTime.visible" width="160">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
</el-tooltip>
<el-tooltip content="重置密码" placement="top" v-if="scope.row.userId !== 1">
<el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button>
</el-tooltip>
<el-tooltip content="分配角色" placement="top" v-if="scope.row.userId !== 1">
<el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-col>
</pane>
</splitpanes>
</el-row>
<!-- 添加或修改用户配置对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form :model="form" :rules="rules" ref="userRef" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="归属部门" prop="deptId">
<el-tree-select v-model="form.deptId" :data="enabledDeptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="请选择归属部门" check-strictly />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
<el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="用户性别">
<el-select v-model="form.sex" placeholder="请选择">
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="岗位">
<el-select v-model="form.postIds" multiple placeholder="请选择">
<el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId" :disabled="item.status == 1"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="角色">
<el-select v-model="form.roleIds" multiple placeholder="请选择">
<el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" :value="item.roleId" :disabled="item.status == 1"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
<el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<div class="el-upload__tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm">确 定</el-button>
<el-button @click="upload.open = false">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User">
import { getToken } from "@/utils/auth"
import useAppStore from '@/store/modules/app'
import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser, deptTreeSelect } from "@/api/system/user"
import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css"
const router = useRouter()
const appStore = useAppStore()
const { proxy } = getCurrentInstance()
const { sys_normal_disable, sys_user_sex } = proxy.useDict("sys_normal_disable", "sys_user_sex")
const userList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const dateRange = ref([])
const deptName = ref("")
const deptOptions = ref(undefined)
const enabledDeptOptions = ref(undefined)
const initPassword = ref(undefined)
const postOptions = ref([])
const roleOptions = ref([])
/*** 用户导入参数 */
const upload = reactive({
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: "",
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
})
// 列显隐信息
const columns = ref({
userId: { label: '用户编号', visible: true },
userName: { label: '用户名称', visible: true },
nickName: { label: '用户昵称', visible: true },
deptName: { label: '部门', visible: true },
phonenumber: { label: '手机号码', visible: true },
status: { label: '状态', visible: true },
createTime: { label: '创建时间', visible: true }
})
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
phonenumber: undefined,
status: undefined,
deptId: undefined
},
rules: {
userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 和 20 之间", trigger: "blur" }],
nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
}
})
const { queryParams, form, rules } = toRefs(data)
/** 通过条件过滤节点 */
const filterNode = (value, data) => {
if (!value) return true
return data.label.indexOf(value) !== -1
}
/** 根据名称筛选部门树 */
watch(deptName, val => {
proxy.$refs["deptTreeRef"].filter(val)
})
/** 查询用户列表 */
function getList() {
loading.value = true
listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
loading.value = false
userList.value = res.rows
total.value = res.total
})
}
/** 查询部门下拉树结构 */
function getDeptTree() {
deptTreeSelect().then(response => {
deptOptions.value = response.data
enabledDeptOptions.value = filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
})
}
/** 过滤禁用的部门 */
function filterDisabledDept(deptList) {
return deptList.filter(dept => {
if (dept.disabled) {
return false
}
if (dept.children && dept.children.length) {
dept.children = filterDisabledDept(dept.children)
}
return true
})
}
/** 节点单击事件 */
function handleNodeClick(data) {
queryParams.value.deptId = data.id
handleQuery()
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
proxy.resetForm("queryRef")
queryParams.value.deptId = undefined
proxy.$refs.deptTreeRef.setCurrentKey(null)
handleQuery()
}
/** 删除按钮操作 */
function handleDelete(row) {
const userIds = row.userId || ids.value
proxy.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function () {
return delUser(userIds)
}).then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("system/user/export", {
...queryParams.value,
},`user_${new Date().getTime()}.xlsx`)
}
/** 用户状态修改 */
function handleStatusChange(row) {
let text = row.status === "0" ? "启用" : "停用"
proxy.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?').then(function () {
return changeUserStatus(row.userId, row.status)
}).then(() => {
proxy.$modal.msgSuccess(text + "成功")
}).catch(function () {
row.status = row.status === "0" ? "1" : "0"
})
}
/** 更多操作 */
function handleCommand(command, row) {
switch (command) {
case "handleResetPwd":
handleResetPwd(row)
break
case "handleAuthRole":
handleAuthRole(row)
break
default:
break
}
}
/** 跳转角色分配 */
function handleAuthRole(row) {
const userId = row.userId
router.push("/system/user-auth/role/" + userId)
}
/** 重置密码按钮操作 */
function handleResetPwd(row) {
proxy.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
inputErrorMessage: "用户密码长度必须介于 5 和 20 之间",
inputValidator: (value) => {
if (/<|>|"|'|\||\\/.test(value)) {
return "不能包含非法字符:< > \" ' \\\ |"
}
},
}).then(({ value }) => {
resetUserPwd(row.userId, value).then(response => {
proxy.$modal.msgSuccess("修改成功,新密码是:" + value)
})
}).catch(() => {})
}
/** 选择条数 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.userId)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 导入按钮操作 */
function handleImport() {
upload.title = "用户导入"
upload.open = true
}
/** 下载模板操作 */
function importTemplate() {
proxy.download("system/user/importTemplate", {
}, `user_template_${new Date().getTime()}.xlsx`)
}
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
upload.isUploading = true
}
/** 文件上传成功处理 */
const handleFileSuccess = (response, file, fileList) => {
upload.open = false
upload.isUploading = false
proxy.$refs["uploadRef"].handleRemove(file)
proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
getList()
}
/** 提交上传文件 */
function submitFileForm() {
proxy.$refs["uploadRef"].submit()
}
/** 重置操作表单 */
function reset() {
form.value = {
userId: undefined,
deptId: undefined,
userName: undefined,
nickName: undefined,
password: undefined,
phonenumber: undefined,
email: undefined,
sex: undefined,
status: "0",
remark: undefined,
postIds: [],
roleIds: []
}
proxy.resetForm("userRef")
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
/** 新增按钮操作 */
function handleAdd() {
reset()
getUser().then(response => {
postOptions.value = response.posts
roleOptions.value = response.roles
open.value = true
title.value = "添加用户"
form.value.password = initPassword.value
})
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset()
const userId = row.userId || ids.value
getUser(userId).then(response => {
form.value = response.data
postOptions.value = response.posts
roleOptions.value = response.roles
form.value.postIds = response.postIds
form.value.roleIds = response.roleIds
open.value = true
title.value = "修改用户"
form.password = ""
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["userRef"].validate(valid => {
if (valid) {
if (form.value.userId != undefined) {
updateUser(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
addUser(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
})
}
onMounted(() => {
getDeptTree()
getList()
proxy.getConfigKey("sys.user.initPassword").then(response => {
initPassword.value = response.msg
})
})
</script>
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :span="6" :xs="24">
<el-card class="box-card">
<template v-slot:header>
<div class="clearfix">
<span>个人信息</span>
</div>
</template>
<div>
<div class="text-center">
<userAvatar />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon-class="user" />用户名称
<div class="pull-right">{{ state.user.userName }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="phone" />手机号码
<div class="pull-right">{{ state.user.phonenumber }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="email" />用户邮箱
<div class="pull-right">{{ state.user.email }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属部门
<div class="pull-right" v-if="state.user.dept">{{ state.user.dept.deptName }} / {{ state.postGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色
<div class="pull-right">{{ state.roleGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ state.user.createTime }}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<template v-slot:header>
<div class="clearfix">
<span>基本资料</span>
</div>
</template>
<el-tabs v-model="selectedTab">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="state.user" />
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
<resetPwd />
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="Profile">
import userAvatar from "./userAvatar"
import userInfo from "./userInfo"
import resetPwd from "./resetPwd"
import { getUserProfile } from "@/api/system/user"
const route = useRoute()
const selectedTab = ref("userinfo")
const state = reactive({
user: {},
roleGroup: {},
postGroup: {}
})
function getUser() {
getUserProfile().then(response => {
state.user = response.data
state.roleGroup = response.roleGroup
state.postGroup = response.postGroup
})
}
onMounted(() => {
const activeTab = route.params && route.params.activeTab
if (activeTab) {
selectedTab.value = activeTab
}
getUser()
})
</script>
<template>
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { updateUserPwd } from "@/api/system/user"
const { proxy } = getCurrentInstance()
const user = reactive({
oldPassword: undefined,
newPassword: undefined,
confirmPassword: undefined
})
const equalToPassword = (rule, value, callback) => {
if (user.newPassword !== value) {
callback(new Error("两次输入的密码不一致"))
} else {
callback()
}
}
const rules = ref({
oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
})
/** 提交按钮 */
function submit() {
proxy.$refs.pwdRef.validate(valid => {
if (valid) {
updateUserPwd(user.oldPassword, user.newPassword).then(response => {
proxy.$modal.msgSuccess("修改成功")
})
}
})
}
/** 关闭按钮 */
function close() {
proxy.$tab.closePage()
}
</script>
<template>
<div class="user-info-head" @click="editCropper()">
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
<el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<vue-cropper
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
:outputType="options.outputType"
@realTime="realTime"
v-if="visible"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="avatar-upload-preview">
<img :src="options.previews.url" :style="options.previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<el-upload
action="#"
:http-request="requestUpload"
:show-file-list="false"
:before-upload="beforeUpload"
>
<el-button>
选择
<el-icon class="el-icon--right"><Upload /></el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="uploadImg()">提 交</el-button>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup>
import "vue-cropper/dist/index.css"
import { VueCropper } from "vue-cropper"
import { uploadAvatar } from "@/api/system/user"
import useUserStore from "@/store/modules/user"
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const open = ref(false)
const visible = ref(false)
const title = ref("修改头像")
//图片裁剪数据
const options = reactive({
img: userStore.avatar, // 裁剪图片的地址
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: true, // 固定截图框大小 不允许改变
outputType: "png", // 默认生成截图为PNG格式
filename: 'avatar', // 文件名称
previews: {} //预览数据
})
/** 编辑头像 */
function editCropper() {
open.value = true
}
/** 打开弹出层结束时的回调 */
function modalOpened() {
visible.value = true
}
/** 覆盖默认上传行为 */
function requestUpload() {}
/** 向左旋转 */
function rotateLeft() {
proxy.$refs.cropper.rotateLeft()
}
/** 向右旋转 */
function rotateRight() {
proxy.$refs.cropper.rotateRight()
}
/** 图片缩放 */
function changeScale(num) {
num = num || 1
proxy.$refs.cropper.changeScale(num)
}
/** 上传预处理 */
function beforeUpload(file) {
if (file.type.indexOf("image/") == -1) {
proxy.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。")
} else {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
options.img = reader.result
options.filename = file.name
}
}
}
/** 上传图片 */
function uploadImg() {
proxy.$refs.cropper.getCropBlob(data => {
let formData = new FormData()
formData.append("avatarfile", data, options.filename)
uploadAvatar(formData).then(response => {
open.value = false
options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl
userStore.avatar = options.img
proxy.$modal.msgSuccess("修改成功")
visible.value = false
})
})
}
/** 实时预览 */
function realTime(data) {
options.previews = data
}
/** 关闭窗口 */
function closeDialog() {
options.img = userStore.avatar
options.visible = false
}
</script>
<style lang='scss' scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: "+";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #eee;
background: rgba(0, 0, 0, 0.5);
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
line-height: 110px;
border-radius: 50%;
}
</style>
\ No newline at end of file
<template>
<el-form ref="userRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" maxlength="30" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="form.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex">
<el-radio value="0"></el-radio>
<el-radio value="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { updateUserProfile } from "@/api/system/user"
const props = defineProps({
user: {
type: Object
}
})
const { proxy } = getCurrentInstance()
const form = ref({})
const rules = ref({
nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, { type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phonenumber: [{ required: true, message: "手机号码不能为空", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
})
/** 提交按钮 */
function submit() {
proxy.$refs.userRef.validate(valid => {
if (valid) {
updateUserProfile(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
props.user.phonenumber = form.value.phonenumber
props.user.email = form.value.email
})
}
})
}
/** 关闭按钮 */
function close() {
proxy.$tab.closePage()
}
// 回显当前登录用户信息
watch(() => props.user, user => {
if (user) {
form.value = { nickName: user.nickName, phonenumber: user.phonenumber, email: user.email, sex: user.sex }
}
},{ immediate: true })
</script>
......@@ -111,8 +111,9 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200px">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" >修改</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdatePwd(scope.row)" >修改密码</el-button>
<!-- <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)" >详情</el-button>-->
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" >删除</el-button>
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" >删除</el-button>-->
</template>
</el-table-column>
</el-table>
......@@ -125,59 +126,37 @@
@pagination="getList"
/>
<!-- 添加或修改项目对话框 -->
<!-- 添加或修改用户对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="projectRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
<el-form ref="userRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="账号" prop="userName">
<el-input v-model="form.userName" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="项目开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="选择项目开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
<!-- <el-form-item label="密码" prop="password">-->
<!-- <el-input v-model="form.password" placeholder="请输入密码" />-->
<!-- </el-form-item>-->
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="项目结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="选择项目结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="是否内置项目">
<el-radio-group v-model="form.isIn">
<el-radio
v-for="dict in sys_no_yes"
:key="Number(dict.value)"
:value="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="项目访问地址" prop="projectUrl">
<el-input v-model="form.projectUrl" placeholder="请输入项目访问地址" />
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="作用域">
<el-radio-group v-model="form.scope">
<el-form-item label="性别">
<el-radio-group v-model="form.gender">
<el-radio
v-for="dict in sys_scope"
v-for="dict in sys_gender"
:key="Number(dict.value)"
:value="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="所属租户" prop="tenantBizId">
<el-input v-model="form.tenantBizId" placeholder="请输入所属租户" />
</el-form-item>
<el-form-item label="项目描述">
<el-input v-model="form.description" type="textarea" placeholder="请输入项目描述"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
......@@ -188,8 +167,8 @@
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="项目图标" prop="logoUrl">
<el-input v-model="form.logoUrl" placeholder="请输入项目图标" />
<el-form-item label="头像" prop="avatar">
<el-input v-model="form.avatar" placeholder="请输入头像" />
</el-form-item>
</el-form>
<template #footer>
......@@ -200,57 +179,13 @@
</template>
</el-dialog>
<!-- 分配角色数据权限对话框 -->
<el-dialog :title="title" v-model="openDataScope" width="500px" append-to-body>
<el-form :model="form" label-width="80px">
<el-form-item label="角色名称">
<el-input v-model="form.roleName" :disabled="true" />
</el-form-item>
<el-form-item label="权限字符">
<el-input v-model="form.roleKey" :disabled="true" />
</el-form-item>
<el-form-item label="权限范围">
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<el-option
v-for="item in dataScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="数据权限" v-show="form.dataScope == 2">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
<el-tree
class="tree-border"
:data="deptOptions"
show-checkbox
default-expand-all
ref="deptRef"
node-key="id"
:check-strictly="!form.deptCheckStrictly"
empty-text="加载中,请稍候"
:props="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDataScope">确 定</el-button>
<el-button @click="cancelDataScope">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User">
import { listUser } from "@/api/system/user"
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu"
import { listUser,getUser,updateUser,addUser,changeUserStatus } from "@/api/system/user"
import useUserStore from '@/store/modules/user'
import {formatIsoToDateTime} from '@/utils/date'
const userStore = useUserStore()
const router = useRouter()
......@@ -259,32 +194,13 @@ const { sys_status,sys_scope,sys_no_yes,sys_user_status,sys_gender } = proxy.use
const userList = ref([])
const open = ref(false)
const updatePwdOpen = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const dateRange = ref([])
const menuOptions = ref([])
const menuExpand = ref(false)
const menuNodeAll = ref(false)
const deptExpand = ref(true)
const deptNodeAll = ref(false)
const deptOptions = ref([])
const openDataScope = ref(false)
const menuRef = ref(null)
const deptRef = ref(null)
/** 数据范围选项*/
const dataScopeOptions = ref([
{ value: "1", label: "全部数据权限" },
{ value: "2", label: "自定数据权限" },
{ value: "3", label: "本部门数据权限" },
{ value: "4", label: "本部门及以下数据权限" },
{ value: "5", label: "仅本人数据权限" }
])
const updatePwdTitle = ref("")
const data = reactive({
form: {},
......@@ -299,16 +215,17 @@ const data = reactive({
isSuperAdmin: undefined
},
rules: {
projectName: [{ required: true, message: "项目名称不能为空", trigger: "blur" }],
userName: [{ required: true, message: "账号不能为空", trigger: "blur" }],
realName: [{ required: true, message: "姓名不能为空", trigger: "blur" }],
nickName: [{ required: true, message: "昵称不能为空", trigger: "blur" }],
email: [{ required: true, message: "邮箱不能为空", trigger: "blur" },{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
mobile: [{ required: true, message: "手机号不能为空", trigger: "blur" },{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
gender: [{ required: true, message: "性别不能为空", trigger: "blur" }],
status: [{ required: true, message: "状态不能为空", trigger: "blur" }],
startTime: [{ required: true, message: "项目开始时间不能为空", trigger: "blur" }],
endTime: [{ required: true, message: "项目结束时间不能为空", trigger: "blur" }],
scope: [{ required: true, message: "作用域不能为空", trigger: "blur" }],
logoUrl: [{ required: true, message: "项目图标地址不能为空", trigger: "blur" }],
isIn: [{ required: true, message: "是否内置项目不能为空", trigger: "blur" }],
avatar: [{ required: true, message: "头像URL不能为空", trigger: "blur" }],
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
},
})
const { queryParams, form, rules } = toRefs(data)
/** 查询用户列表 */
......@@ -344,180 +261,73 @@ function handleDelete(row) {
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("system/role/export", {
...queryParams.value,
}, `role_${new Date().getTime()}.xlsx`)
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.roleId)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 项目状态修改 */
/** 用户状态修改 */
function handleStatusChange(row, event) {
// 忽略初始化时的自动触发
if (!event || event.type !== 'change') return;
let text = row.status === "0" ? "启用" : "停用"
const projectName = row.projectName || "未命名项目"
proxy.$modal.confirm(`确认要${text}"${projectName}"项目吗?`)
.then(() => changeProjectStatus(row.projectId, row.status))
debugger
let text = row.status === 0 ? "停用" : "启用"
const userName = row.userName
proxy.$modal.confirm(`确认要${text}"${userName}"用户吗?`)
.then(() => changeUserStatus({userBizId: row.userBizId, status: row.status}))
.then(() => proxy.$modal.msgSuccess(`${text}成功`))
.catch(() => {
// 操作取消时恢复原状态
row.status = row.status === "0" ? "1" : "0"
row.status = row.status === 0 ? 1 : 0
})
}
/** 更多操作 */
function handleCommand(command, row) {
switch (command) {
case "handleDataScope":
handleDataScope(row)
break
case "handleAuthUser":
handleAuthUser(row)
break
default:
break
}
}
/** 分配用户 */
function handleAuthUser(row) {
router.push("/system/role-auth/user/" + row.roleId)
}
/** 所有部门节点数据 */
function getDeptAllCheckedKeys() {
// 目前被选中的部门节点
let checkedKeys = deptRef.value.getCheckedKeys()
// 半选中的部门节点
let halfCheckedKeys = deptRef.value.getHalfCheckedKeys()
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
return checkedKeys
}
/** 重置新增的表单以及其他数据 */
function reset() {
form.value = {
projectBizId: undefined,
projectName: undefined,
description: undefined,
status: 1,
startTime: undefined,
endTime: undefined,
scope: 1,
tenantBizId: undefined,
logoUrl: undefined,
isIn: 0,
projectUrl: undefined
userBizId: undefined,
userName: undefined,
password: undefined,
realName: undefined,
nickName: undefined,
avatar: undefined,
email: undefined,
mobile: undefined,
gender: 0,
status: 1
}
proxy.resetForm("projectRef")
proxy.resetForm("userRef")
}
/** 添加项目 */
/** 添加用户 */
function handleAdd() {
reset()
open.value = true
title.value = "添加项目"
title.value = "添加用户"
}
/** 修改项目 */
/** 修改用户弹框 */
function handleUpdate(row) {
reset()
const projectBizId = row.projectBizId
getProject(projectBizId).then(response => {
const resData = response.data;
form.value = {
...resData,
startTime: formatIsoToDateTime(resData.startTime),
endTime: formatIsoToDateTime(resData.endTime)
};
const userBizId = row.userBizId
getUser(userBizId).then(response => {
form.value = response.data
open.value = true
})
title.value = "修改项目"
}
/** 根据角色ID查询菜单树结构 */
function getProjectMenuTreeselect(roleId) {
return roleMenuTreeselect(roleId).then(response => {
menuOptions.value = response.menus
return response
})
}
/** 根据角色ID查询部门树结构 */
function getDeptTree(roleId) {
return deptTreeSelect(roleId).then(response => {
deptOptions.value = response.depts
return response
})
}
/** 树权限(展开/折叠)*/
function handleCheckedTreeExpand(value, type) {
if (type == "menu") {
let treeList = menuOptions.value
for (let i = 0; i < treeList.length; i++) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value
}
} else if (type == "dept") {
let treeList = deptOptions.value
for (let i = 0; i < treeList.length; i++) {
deptRef.value.store.nodesMap[treeList[i].id].expanded = value
}
}
}
/** 树权限(全选/全不选) */
function handleCheckedTreeNodeAll(value, type) {
if (type == "menu") {
menuRef.value.setCheckedNodes(value ? menuOptions.value : [])
} else if (type == "dept") {
deptRef.value.setCheckedNodes(value ? deptOptions.value : [])
}
/** 修改密码 */
function handleUpdatePwd(row) {
updatePwdOpen.value = true
updatePwdTitle.value = "修改密码"
}
/** 树权限(父子联动) */
function handleCheckedTreeConnect(value, type) {
if (type == "menu") {
form.value.menuCheckStrictly = value ? true : false
} else if (type == "dept") {
form.value.deptCheckStrictly = value ? true : false
}
}
/** 所有菜单节点数据 */
function getMenuAllCheckedKeys() {
// 目前被选中的菜单节点
let checkedKeys = menuRef.value.getCheckedKeys()
// 半选中的菜单节点
let halfCheckedKeys = menuRef.value.getHalfCheckedKeys()
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
return checkedKeys
}
/** 添加和修改项目提交按钮 */
/** 添加和修改用户提交按钮 */
function submitForm() {
proxy.$refs["projectRef"].validate(valid => {
proxy.$refs["userRef"].validate(valid => {
if (valid) {
if (form.value.projectBizId != undefined) {
debugger
updateProject(form.value).then(response => {
if (form.value.userBizId != undefined) {
updateUser(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
debugger
addProject(form.value).then(response => {
addUser(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
open.value = false
getList()
......@@ -533,50 +343,5 @@ function cancel() {
reset()
}
/** 选择角色权限范围触发 */
function dataScopeSelectChange(value) {
if (value !== "2") {
deptRef.value.setCheckedKeys([])
}
}
/** 分配数据权限操作 */
function handleDataScope(row) {
reset()
const deptTreeSelect = getDeptTree(row.roleId)
getProject(row.roleId).then(response => {
form.value = response.data
openDataScope.value = true
nextTick(() => {
deptTreeSelect.then(res => {
nextTick(() => {
if (deptRef.value) {
deptRef.value.setCheckedKeys(res.checkedKeys)
}
})
})
})
})
title.value = "分配数据权限"
}
/** 提交按钮(数据权限) */
function submitDataScope() {
if (form.value.roleId != undefined) {
form.value.deptIds = getDeptAllCheckedKeys()
dataScope(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
openDataScope.value = false
getList()
})
}
}
/** 取消按钮(数据权限)*/
function cancelDataScope() {
openDataScope.value = false
reset()
}
getList()
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment