Commit 32dc9cfc by 周海峰

用户组

parent 723e4652
...@@ -14,3 +14,55 @@ export function queryAll(query) { ...@@ -14,3 +14,55 @@ export function queryAll(query) {
params: query params: query
}) })
} }
export function query(data) {
return request({
url: '/console/usergroup/query',
method: 'post',
data: data
})
}
export function initEdit(data) {
return request({
url: '/console/usergroup/initEdit',
method: 'post',
data: data
})
}
export function add(data) {
return request({
url: '/console/usergroup/add',
method: 'post',
data: data
})
}
export function modify(data) {
return request({
url: '/console/usergroup/modify',
method: 'post',
data: data
})
}
export function del(data) {
return request({
url: '/console/usergroup/del',
method: 'post',
data: data
})
}
export function checkNameExit(query) {
return request({
url: '/console/usergroup/checkNameExit',
method: 'get',
params: query
})
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
title="编辑用户组" title="编辑用户组"
:model-value="visible" :model-value="visible"
@update:model-value="$emit('update:visible', $event)" @update:model-value="$emit('update:visible', $event)"
width="800px" width="600px"
:close-on-click-modal="false" :close-on-click-modal="false"
append-to-body append-to-body
destroy-on-close destroy-on-close
...@@ -20,103 +20,61 @@ ...@@ -20,103 +20,61 @@
label-width="100px" label-width="100px"
> >
<!-- 姓名和用户组名 --> <!-- 姓名和用户组名 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用户组名" prop="groupname"> <el-form-item label="用户组名" prop="groupname">
<el-input v-model="form.groupname" placeholder="请输入用户组名" /> <el-input v-model="form.groupname" placeholder="请输入用户组名" :disabled="form.id !== null" @blur="changeCheckNameExit" />
</el-form-item> </el-form-item>
</el-col>
</el-row>
<!-- 备注 --> <!-- 备注 -->
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input <el-input
v-model="form.remark" v-model="form.remark"
type="textarea"
placeholder="请输入备注" placeholder="请输入备注"
/> />
</el-form-item> </el-form-item>
</el-form>
<!-- 角色选择 --> <!-- 角色选择 -->
<el-form-item label="选择角色"> <div class="component-wrapper">
<div class="role-transfer"> <RoleTransfer
<div class="transfer-list"> :roles="roles"
<div class="transfer-header"> :selectedRoles="selectedRoles"
<span>角色列表</span> :searchRole="searchRole"
<el-checkbox v-model="allSelected" @change="handleSelectAll">全选</el-checkbox> @update:roles="val => roles = val"
</div> @update:selectedRoles="val => selectedRoles = val"
<el-input v-model="searchRole" placeholder="请输入搜索内容" /> @update:searchRole="val => searchRole = val"
<div class="role-list"> />
<div v-for="role in filteredRoles" :key="role.id" class="role-item"> <div v-if="isAdminUser" class="overlay-mask">
<el-checkbox <div class="mask-message">
v-model="role.selected" <el-alert
@change="(val) => handleRoleSelect(role, val)" title="超级管理员用户组的角色不可修改"
> type="info"
{{ role.name }} :closable="false"
</el-checkbox> show-icon
</div> />
</div>
</div>
<div class="transfer-operation">
<el-button type="primary" plain icon="ArrowRight" @click="addSelected" />
<el-button type="primary" plain icon="ArrowLeft" @click="removeSelected" />
</div>
<div class="transfer-list">
<div class="transfer-header">
<span>已选择角色列表</span>
<el-button link type="primary" @click="clearSelected">清空</el-button>
</div>
<div class="selected-list">
<div v-for="role in selectedRoles" :key="role.id" class="role-item">
{{ role.name }}
</div>
</div> </div>
</div> </div>
</div> </div>
</el-form-item>
<!-- 权限设置 --> <!-- 权限设置 -->
<el-form-item label="权限设置"> <div class="component-wrapper">
<div class="permission-area"> <PermissionArea
<div class="all-select"> :permissionList="permissionList"
<el-checkbox v-model="allPermissionsSelected" @change="handleAllPermissionsChange">全选</el-checkbox> :allPermissionsSelected="allPermissionsSelected"
</div> @update:allPermissionsSelected="val => allPermissionsSelected = val"
<div class="permission-list"> @permission-change="handlePermissionChange"
<div v-for="(item, index) in permissionList" :key="index" class="permission-item"> @sub-permission-change="handleSubPermissionChange"
<!-- 父级权限项 --> />
<div class="permission-header" @click="toggleExpand(item)"> <div v-if="isAdminUser" class="overlay-mask">
<div class="left"> <div class="mask-message">
<el-icon class="expand-icon" :class="{ expanded: item.expanded }"> <el-alert
<ArrowRight /> title="超级管理员用户组的权限不可修改"
</el-icon> type="info"
<el-checkbox :closable="false"
v-model="item.selected" show-icon
:indeterminate="item.indeterminate" />
@change="(val) => handlePermissionChange(item, val)"
>
{{ item.name }}
</el-checkbox>
</div>
<div class="right">
<el-tag size="small" v-if="item.children">{{ item.children.length }}</el-tag>
</div>
</div>
<!-- 子级权限列表 -->
<div v-if="item.children" class="sub-permissions" v-show="item.expanded">
<el-checkbox
v-for="child in item.children"
:key="child.id"
v-model="child.selected"
@change="(val) => handleSubPermissionChange(item, child, val)"
>
{{ child.name }}
</el-checkbox>
</div>
</div> </div>
</div> </div>
</div> </div>
</el-form-item>
</el-form>
</div> </div>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
...@@ -129,10 +87,21 @@ ...@@ -129,10 +87,21 @@
</el-dialog> </el-dialog>
</template> </template>
<script> <script setup>
export default { import { ref, reactive, computed, watch, getCurrentInstance } from 'vue'
name: 'GroupEdit', import { queryAll as queryAllRole } from '@/api/safetyManagement/roleConfig.js'
props: { import { initEdit, add, modify, checkNameExit } from '@/api/safetyManagement/groupConfig.js'
import { queryAll as queryAllMenu } from '@/api/safetyManagement/menuConfig.js'
import RoleTransfer from '@/components/RoleTransfer/index.vue'
import PermissionArea from '@/components/PermissionArea/index.vue'
// 定义组件名称
defineOptions({
name: 'GroupEdit'
})
// 定义props和emit
const props = defineProps({
visible: { visible: {
type: Boolean, type: Boolean,
default: false default: false
...@@ -141,186 +110,218 @@ export default { ...@@ -141,186 +110,218 @@ export default {
type: Object, type: Object,
default: () => null default: () => null
} }
}, })
emits: ['update:visible', 'success'],
watch: { const emit = defineEmits(['update:visible', 'success'])
visible(val) {
if (val && this.formData) { // 表单引用
this.form = { const formRef = ref(null)
...this.formData,
password: '', // 响应式数据
confirmPassword: '' const form = reactive({
} id: null,
} groupname: '',
} groupList: [],
}, rolesList: [],
data() { menuList: []
// 密码验证 })
const validatePassword = (rule, value, callback) => {
if (value === '') { const searchRole = ref('')
callback(new Error('请输入密码')) const roles = ref([])
const selectedRoles = ref([])
const allPermissionsSelected = ref(false)
const permissionList = ref([])
const instance = getCurrentInstance()
// 判断是否为超级管理员用户组(roleId = 1)
const isAdminUser = ref(false)
const rules = {
groupname: [
{ required: true, message: '请输入用户组名', trigger: 'blur' }
]
}
// 监听visible变化
watch(() => props.visible, (val) => {
if (val && props.formData && props.formData.id) {
// 弹窗打开且有id时,拉取用户组详情和相关数据
initEditData(props.formData.id)
} else { } else {
if (this.form.confirmPassword !== '') { // 新增,只需查询角色、用户组组、菜单权限
this.$refs.formRef.validateField('confirmPassword') Promise.all([
queryAllRole(),
queryAllMenu()
]).then(([roleRes, menuRes]) => {
// 角色
roles.value = (roleRes.data || []).map(r => ({
id: r.id,
name: r.rolename || r.name || '',
selected: false
}))
selectedRoles.value = []
// 权限
function mapMenu(menuArr) {
return (menuArr || []).filter(item => item.type !== '3' || item.type === 1).map(item => {
const hasChildren = Array.isArray(item.children) && item.children.length > 0
let children = hasChildren ? mapMenu(item.children) : null
return {
id: item.id,
name: item.type === '2'? '项目管理-'+item.name : item.name,
selected: false,
indeterminate: false,
expanded: false,
children
} }
callback() })
} }
permissionList.value = mapMenu(menuRes.data)
})
} }
// 确认密码验证 })
const validateConfirmPassword = (rule, value, callback) => {
if (value === '') { // 初始化编辑数据
callback(new Error('请再次输入密码')) const initEditData = async (id) => {
} else if (value !== this.form.password) { // 1. 查询用户组详情
callback(new Error('两次输入密码不一致!')) const groupRes = await initEdit({ groupid: id })
// 2. 查询角色、菜单权限
const [roleRes, menuRes] = await Promise.all([
queryAllRole(),
queryAllMenu()
])
// 3. 数据匹配
Object.assign(form, {
...groupRes.data.tBaseGroup
})
// 未选中角色
roles.value = roleRes.data
.filter(r => !(groupRes.data.tBaseGrouproleList.map(m => m.roleid) || []).includes(r.id))
.map(r => ({
id: r.id,
name: r.rolename || r.name || ''
}))
// 选中的角色
selectedRoles.value = roleRes.data
.filter(r => (groupRes.data.tBaseGrouproleList.map(m => m.roleid) || []).includes(r.id))
.map(r => ({
id: r.id,
name: r.rolename || r.name || ''
}))
// 权限
function mapMenu(menuArr, selectedIds = []) {
return (menuArr || []).filter(item => item.type !== '3' || item.type === 1).map(item => {
const hasChildren = Array.isArray(item.children) && item.children.length > 0
let children = hasChildren ? mapMenu(item.children, selectedIds) : null
let selected = selectedIds.includes(item.id)
let indeterminate = false
if (hasChildren) {
const selectedCount = children.filter(c => c.selected).length
if (selectedCount === 0) {
selected = false
indeterminate = false
} else if (selectedCount === children.length) {
selected = true
indeterminate = false
} else { } else {
callback() selected = false
indeterminate = true
} }
} }
return { return {
form: { id: item.id,
groupname: '', name: item.type === '2'? '项目管理-'+item.name : item.name,
remark: '', selected,
}, indeterminate,
rules: {
groupname: [
{ required: true, message: '请输入用户组名', trigger: 'blur' }
]
},
allSelected: false,
searchRole: '',
roles: [
{ id: 1, name: '系统管理员', selected: false },
{ id: 2, name: '2222', selected: false }
],
selectedRoles: [],
allPermissionsSelected: false,
permissionList: [
{
id: 1,
name: '管理首页',
selected: false,
expanded: false, expanded: false,
indeterminate: false children
},
{
id: 2,
name: '项目管理',
selected: false,
expanded: false
},
{
id: 3,
name: '规则管理',
selected: false,
children: [
{ id: '3-1', name: '规则管理子项1', selected: false },
{ id: '3-2', name: '规则管理子项2', selected: false }
]
},
{
id: 4,
name: '资产库',
selected: false
},
{
id: 5,
name: '系统设置',
selected: false,
children: [
{ id: '5-1', name: '设置子项1', selected: false },
{ id: '5-2', name: '设置子项2', selected: false }
]
},
{
id: 6,
name: '用户组管理',
selected: false,
children: [
{ id: '6-1', name: '用户组管理子项1', selected: false },
{ id: '6-2', name: '用户组管理子项2', selected: false }
]
}
]
} }
}, })
computed: {
filteredRoles() {
return this.roles.filter(role =>
role.name.toLowerCase().includes(this.searchRole.toLowerCase())
)
} }
}, permissionList.value = mapMenu(menuRes.data, groupRes.data.tBaseGroupmenuList ? groupRes.data.tBaseGroupmenuList.map(m => m.menuid) : [])
methods: { }
handleClose() {
this.$emit('update:visible', false) // 关闭弹窗
this.$refs.formRef?.resetFields() const handleClose = () => {
this.form = { emit('update:visible', false)
realname: '', formRef.value?.resetFields()
Object.assign(form, {
groupname: '', groupname: '',
password: '', remark: ''
confirmPassword: '', })
email: '', }
userGroup: '',
remark: '', // 提交表单
isDisabled: false const handleSubmit = () => {
} formRef.value.validate((valid) => {
},
handleSubmit() {
this.$refs.formRef.validate((valid) => {
if (valid) { if (valid) {
// 提交表单逻辑 // 提交表单逻辑
const params = { ...this.form } const params = { group: {
// 如果是编辑模式且没有修改密码,则不提交密码字段 id: form.id || null,
if (this.formData && !params.password) { remark: form.remark || '',
delete params.password groupname: form.groupname || ''
delete params.confirmPassword } }
} // 选中的角色和分组和权限
params.roleList = selectedRoles.value.map(r => r.id)
console.log('submit form', params) const selectedMenuIds = []
// 调用接口保存数据 function collectSelectedMenus(menus) {
this.$emit('success') menus.forEach(menu => {
this.handleClose() if (menu.selected) {
selectedMenuIds.push(menu.id)
}
if (menu.children && menu.children.length > 0) {
collectSelectedMenus(menu.children)
} }
}) })
}, }
handleSelectAll(val) { collectSelectedMenus(permissionList.value)
this.roles.forEach(role => { params.menuList = selectedMenuIds.map(id => id)
role.selected = val // console.log('submit form', params)
if(params.group.id){
modify(params).then(res => {
if (res.code === 'POP_00014') {
instance.appContext.config.globalProperties.$modal.msgSuccess('保存成功')
emit('success')
handleClose()
} else {
instance.appContext.config.globalProperties.$modal.msgError(res.msg || '保存失败')
}
}).catch(() => {
instance.appContext.config.globalProperties.$modal.msgError('保存异常')
}) })
}, }else{
handleRoleSelect(role, val) { add(params).then(res => {
role.selected = val if (res.code === 'POP_00014') {
this.allSelected = this.roles.every(role => role.selected) instance.appContext.config.globalProperties.$modal.msgSuccess('保存成功')
}, emit('success')
addSelected() { handleClose()
const selectedRoles = this.roles.filter(role => role.selected) } else {
this.selectedRoles = [...new Set([...this.selectedRoles, ...selectedRoles])] instance.appContext.config.globalProperties.$modal.msgError(res.msg || '保存失败')
this.roles = this.roles.filter(role => !role.selected) }
this.allSelected = false }).catch(() => {
}, instance.appContext.config.globalProperties.$modal.msgError('保存异常')
removeSelected() {
this.roles = [...this.roles, ...this.selectedRoles]
this.selectedRoles = []
},
clearSelected() {
this.roles = [...this.roles, ...this.selectedRoles]
this.selectedRoles = []
},
// 权限相关方法
handleAllPermissionsChange(val) {
// 处理全选
this.permissionList.forEach(item => {
item.selected = val
if (item.children) {
item.children.forEach(child => {
child.selected = val
}) })
} }
}
}) })
}, }
handlePermissionChange(item, val) {
// 处理父级权限选择 // 用户组名失去焦点时验证名字是否存在
const changeCheckNameExit = async () => {
if (form.groupname) {
try {
const res = await checkNameExit({ groupname: form.groupname });
if (res.code !== 'POP_00014') {
instance.appContext.config.globalProperties.$modal.msgError('用户组名已存在');
}
} catch (error) {
instance.appContext.config.globalProperties.$modal.msgError('验证用户组名异常');
}
}
}
// 处理权限选择
const handlePermissionChange = (item, val) => {
item.selected = val item.selected = val
item.indeterminate = false // 清除半选状态 item.indeterminate = false // 清除半选状态
if (item.children) { if (item.children) {
...@@ -328,10 +329,10 @@ export default { ...@@ -328,10 +329,10 @@ export default {
child.selected = val child.selected = val
}) })
} }
this.checkAllPermissionsStatus() checkAllPermissionsStatus()
}, }
handleSubPermissionChange(parent, child, val) {
// 处理子级权限选择 const handleSubPermissionChange = (parent, child, val) => {
child.selected = val child.selected = val
// 检查父级状态 // 检查父级状态
if (parent.children) { if (parent.children) {
...@@ -343,22 +344,16 @@ export default { ...@@ -343,22 +344,16 @@ export default {
parent.selected = allSelected parent.selected = allSelected
parent.indeterminate = !allSelected && someSelected parent.indeterminate = !allSelected && someSelected
} }
this.checkAllPermissionsStatus() checkAllPermissionsStatus()
}, }
checkAllPermissionsStatus() {
// 检查是否全部选中 const checkAllPermissionsStatus = () => {
this.allPermissionsSelected = this.permissionList.every(item => { allPermissionsSelected.value = permissionList.value.every(item => {
if (item.children) { if (item.children) {
return item.selected && item.children.every(child => child.selected) return item.selected && item.children.every(child => child.selected)
} }
return item.selected return item.selected
}) })
},
toggleExpand(item) {
// 展开/收起子菜单
item.expanded = !item.expanded
}
}
} }
</script> </script>
...@@ -375,6 +370,7 @@ export default { ...@@ -375,6 +370,7 @@ export default {
height: 300px; height: 300px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 310px;
.transfer-header { .transfer-header {
padding: 8px 12px; padding: 8px 12px;
...@@ -383,6 +379,18 @@ export default { ...@@ -383,6 +379,18 @@ export default {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
background-color: #f5f7fa; background-color: #f5f7fa;
.select-all-action {
color: #409EFF;
cursor: pointer;
font-size: 14px;
margin-left: 10px;
user-select: none;
transition: color 0.2s;
&:hover {
color: #66b1ff;
cursor: pointer;
}
}
} }
.role-list, .selected-list { .role-list, .selected-list {
...@@ -415,9 +423,11 @@ export default { ...@@ -415,9 +423,11 @@ export default {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 350px;
overflow-y: auto;
.all-select { .all-select {
padding: 8px 12px; padding: 2px 12px;
background-color: #f5f7fa; background-color: #f5f7fa;
border-bottom: 1px solid #dcdfe6; border-bottom: 1px solid #dcdfe6;
} }
...@@ -435,34 +445,26 @@ export default { ...@@ -435,34 +445,26 @@ export default {
.permission-header { .permission-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: flex-start;
padding: 12px 0; padding: 5px 0;
cursor: pointer; cursor: pointer;
gap: 12px;
.left { .left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.expand-icon {
transition: transform 0.3s;
font-size: 12px;
color: #909399;
&.expanded {
transform: rotate(90deg);
}
} }
} }
}
.sub-permissions { .sub-permissions {
padding: 8px 0 8px 32px; padding: 5px 0 5px 32px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
background-color: #f8f9fb; background-color: #f8f9fb;
border-top: 1px solid #ebeef5; border-top: 1px solid #ebeef5;
.el-checkbox {
margin-left: 0 !important;
}
} }
} }
} }
...@@ -512,4 +514,39 @@ export default { ...@@ -512,4 +514,39 @@ export default {
} }
} }
.permission-header .expand-icon-placeholder {
display: inline-block;
min-width: 22px;
height: 22px;
vertical-align: middle;
}
.permission-header .expand-icon {
min-width: 22px;
text-align: center;
display: inline-flex;
}
.component-wrapper {
position: relative;
}
.overlay-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
.mask-message {
padding: 10px;
border-radius: 4px;
width: 80%;
max-width: 400px;
}
</style> </style>
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
<el-form :inline="true" :model="searchForm" class="search-form"> <el-form :inline="true" :model="searchForm" class="search-form">
<div class="left-area"> <div class="left-area">
<el-form-item label="用户组名:"> <el-form-item label="用户组名:">
<el-input v-model="searchForm.groupname" placeholder="请输入用户组名"></el-input> <el-input v-model="searchForm.groupname" clearable placeholder="请输入用户组名"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="备注:"> <el-form-item label="备注:">
<el-input v-model="searchForm.remark" placeholder="请输入备注"></el-input> <el-input v-model="searchForm.remark" clearable placeholder="请输入备注"></el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button> <el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
...@@ -29,24 +29,36 @@ ...@@ -29,24 +29,36 @@
<!-- 用户列表 --> <!-- 用户列表 -->
<div class="user-list"> <div class="user-list">
<div class="user-grid"> <div class="user-grid">
<div v-for="(user, index) in userList" :key="index" class="user-card"> <div v-for="(user, index) in groupList" :key="index" class="user-card">
<div class="card-left-bar"></div>
<div class="user-info"> <div class="user-info">
<div class="avatar">
<el-avatar :size="50" icon="User"></el-avatar>
</div>
<div class="info"> <div class="info">
<div class="name">{{ user.groupname }}</div> <div class="name">{{ user.groupname }}</div>
<div class="remark">备注: {{ user.remark }}</div> <div class="remark">
<el-icon><Edit /></el-icon>
备注 {{ user.remark }}
</div>
</div>
<div class="avatar">
<el-avatar :size="80" icon="UserFilled" style="background: #f6f8fa; color: #d3d8e0;" />
</div>
</div>
<div class="card-divider"></div>
<div class="card-bottom">
<div>
<el-icon><Clock /></el-icon>
{{ user.createtime || '' }}
</div>
<el-icon class="lock"><Lock /></el-icon>
</div> </div>
<!-- 遮罩层和操作按钮 -->
<div class="hover-mask"> <div class="hover-mask">
<div class="operation-buttons"> <div class="operation-buttons">
<div class="operation-btn" @click="handleDelete(user)"> <div class="operation-btn" @click="handleDelete(user)">
<i class="el-icon-delete"></i> <el-icon><Delete /></el-icon>
<span>删除</span> <span>删除</span>
</div> </div>
<div class="operation-btn" @click="handleEdit(user)"> <div class="operation-btn" @click="handleEdit(user)">
<i class="el-icon-edit"></i> <el-icon><FolderOpened /></el-icon>
<span>编辑</span> <span>编辑</span>
</div> </div>
</div> </div>
...@@ -54,11 +66,12 @@ ...@@ -54,11 +66,12 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- 分页 --> <!-- 分页 -->
<div class="pagination"> <div class="pagination">
<div class="pagination-info">共有记录 1条,每页显示 8条,共 1页</div> <div class="pagination-info">
共有记录 {{ total }} 条,每页显示 {{ pageSize }} 条,共 {{ Math.ceil(total / pageSize) || 1 }}
</div>
<el-pagination <el-pagination
background background
layout="prev, pager, next, jumper" layout="prev, pager, next, jumper"
...@@ -78,72 +91,85 @@ ...@@ -78,72 +91,85 @@
</div> </div>
</template> </template>
<script> <script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessageBox } from 'element-plus'
import GroupEdit from './edit.vue' import GroupEdit from './edit.vue'
import { query, del } from '@/api/safetyManagement/groupConfig.js'
export default { const searchForm = reactive({
name: 'GroupConfig',
components: {
GroupEdit
},
data() {
return {
searchForm: {
name: '', name: '',
groupname: '', groupname: '',
remark: '' remark: ''
}, })
userList: [ const groupList = ref([
{ {
realname: 'admin', // realname: 'admin',
groupname: 'admin', // groupname: 'admin',
remark: '' // remark: ''
}
])
const total = ref(1)
const currentPage = ref(1)
const pageSize = ref(8)
const editVisible = ref(false)
const editData = ref(null)
const getList = async () => {
try {
const res = await query({
groupname: searchForm.groupname,
remark: searchForm.remark,
page: currentPage.value,
rows: pageSize.value
})
if (res.code === 'POP_00014') {
groupList.value = res.data.list || []
total.value = res.data.total || 0
}
} catch (error) {
console.error('获取用户组列表失败', error)
} }
], }
total: 1,
currentPage: 1, const handleSearch = () => {
pageSize: 8, currentPage.value = 1
editVisible: false, getList()
editData: null }
} const handleAdd = () => {
}, editData.value = null
methods: { editVisible.value = true
handleSearch() { }
// 实现搜索逻辑 const handlePageChange = (page) => {
}, currentPage.value = page
handleAdd() { getList()
// 打开新增用户弹窗 }
this.editData = null const handleDelete = async (row) => {
this.editVisible = true ElMessageBox.confirm('确认删除该用户组吗?', '提示', {
},
handlePageChange(page) {
// 实现分页逻辑
},
handleDelete(row) {
// 实现删除用户逻辑
this.$confirm('确认删除该用户组吗?', '提示', {
type: 'warning' type: 'warning'
}).then(() => { }).then(async () => {
// 调用删除接口 // 调用删除接口
console.log('删除用户组', row) try {
}).catch(() => {}) const res = await del({ groupid: row.id })
}, if (res.code === 'POP_00014') {
handleEdit(row) { getList()
// 打开编辑用户弹窗
this.editData = { ...row }
this.editVisible = true
},
handleEditSuccess() {
// 编辑成功后的回调
this.editVisible = false
// 刷新列表数据
this.getList()
},
getList() {
// 获取用户列表数据
} }
} catch (error) {
console.error('删除用户组失败', error)
} }
}).catch(() => {})
}
const handleEdit = (row) => {
editData.value = { ...row }
editVisible.value = true
}
const handleEditSuccess = () => {
editVisible.value = false
getList()
} }
onMounted(() => {
getList()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -202,37 +228,105 @@ export default { ...@@ -202,37 +228,105 @@ export default {
.user-card { .user-card {
background: #fff; background: #fff;
border-radius: 8px; border-radius: 12px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
overflow: hidden; overflow: hidden;
position: relative;
padding: 0;
border: none;
transition: box-shadow 0.2s;
display: flex;
flex-direction: column;
justify-content: space-between;
&:hover {
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
}
&:hover .hover-mask {
display: flex !important;
}
}
.card-left-bar {
position: absolute;
left: 0;
top: 24px;
bottom: 24px;
width: 8px;
background: #409EFF;
border-radius: 4px;
} }
.user-info { .user-info {
display: flex; display: flex;
align-items: center; align-items: flex-start;
padding: 20px; padding: 24px 24px 0 24px;
position: relative; position: relative;
height: auto;
cursor: pointer; cursor: pointer;
height: 100%;
.avatar { .avatar {
margin-right: 15px; margin-left: auto;
margin-right: 0;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
} }
.info { .info {
flex: 1; flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
.name { .name {
font-size: 16px; font-size: 15px;
font-weight: bold; font-weight: 600;
margin-bottom: 8px; color: #409EFF;
color: #333; margin-bottom: 12px;
} }
.groupname, .remark { .remark {
font-size: 12px;
color: #666; color: #666;
font-size: 14px; margin-bottom: 8px;
margin-bottom: 5px; display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
}
}
}
.card-divider {
height: 2px;
background: #f2f6fa;
margin: 12px 24px 0 24px;
border-radius: 1px;
}
.card-bottom {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 24px 12px 24px;
color: #909399;
font-size: 15px;
.el-icon {
margin-right: 6px;
}
.lock {
font-size: 18px;
color: #bdbdbd;
margin-left: 8px;
} }
} }
...@@ -243,13 +337,16 @@ export default { ...@@ -243,13 +337,16 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
display: none; display: none !important;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 2;
.operation-buttons { .operation-buttons {
display: flex; display: flex;
gap: 30px; gap: 80px;
justify-content: center;
align-items: center;
.operation-btn { .operation-btn {
display: flex; display: flex;
...@@ -257,14 +354,18 @@ export default { ...@@ -257,14 +354,18 @@ export default {
align-items: center; align-items: center;
color: white; color: white;
cursor: pointer; cursor: pointer;
background: none;
border-radius: 0;
padding: 0;
box-shadow: none;
transition: color 0.2s;
i { i {
font-size: 24px;
margin-bottom: 8px; margin-bottom: 8px;
} }
span { span {
font-size: 14px; font-weight: 500;
} }
&:hover { &:hover {
...@@ -274,13 +375,6 @@ export default { ...@@ -274,13 +375,6 @@ export default {
} }
} }
&:hover {
.hover-mask {
display: flex;
}
}
}
.operation-time { .operation-time {
color: #666; color: #666;
i { i {
......
<template>
<el-dialog
title="编辑角色"
:model-value="visible"
@update:model-value="$emit('update:visible', $event)"
width="800px"
:close-on-click-modal="false"
append-to-body
destroy-on-close
:align-center="true"
:fullscreen="false"
@close="handleClose"
class="user-edit-dialog"
>
<div class="dialog-content" style="padding: 20px; padding-bottom: 80px;height: 700px;overflow-y: auto;">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<!-- 姓名和角色名 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="角色名" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名" />
</el-form-item>
</el-col>
</el-row>
<!-- 备注 -->
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注"
/>
</el-form-item>
<!-- 权限设置 -->
<el-form-item label="权限设置">
<div class="permission-area">
<div class="all-select">
<el-checkbox v-model="allPermissionsSelected" @change="handleAllPermissionsChange">全选</el-checkbox>
</div>
<div class="permission-list">
<div v-for="(item, index) in permissionList" :key="index" class="permission-item">
<!-- 父级权限项 -->
<div class="permission-header" @click="toggleExpand(item)">
<div class="left">
<el-icon class="expand-icon" :class="{ expanded: item.expanded }">
<component :is="item.expanded ? 'ArrowDown' : 'ArrowRight'" />
</el-icon>
<el-checkbox
v-model="item.selected"
:indeterminate="item.indeterminate"
@change="(val) => handlePermissionChange(item, val)"
>
{{ item.name }}
</el-checkbox>
</div>
<div class="right">
<el-tag size="small" v-if="item.children">{{ item.children.length }}</el-tag>
</div>
</div>
<!-- 子级权限列表 -->
<div v-if="item.children" class="sub-permissions" v-show="item.expanded">
<el-checkbox
v-for="child in item.children"
:key="child.id"
v-model="child.selected"
@change="(val) => handleSubPermissionChange(item, child, val)"
>
{{ child.name }}
</el-checkbox>
</div>
</div>
</div>
</div>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<div class="footer-buttons">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" @click="handleSubmit">确 定</el-button>
</div>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, computed, watch, nextTick } from 'vue'
import { queryAll as queryAllMenu } from '@/api/safetyManagement/menuConfig.js'
import { ArrowRight, ArrowDown } from '@element-plus/icons-vue'
// 定义组件名称
defineOptions({
name: 'RoleEdit'
})
// 定义props
const props = defineProps({
visible: {
type: Boolean,
default: false
},
formData: {
type: Object,
default: () => null
}
})
// 定义emits
const emit = defineEmits(['update:visible', 'success'])
// 表单引用
const formRef = ref(null)
// 表单数据
const form = reactive({
realname: '',
roleName: '',
password: '',
confirmPassword: '',
email: '',
userGroup: '',
remark: '',
isDisabled: false
})
// 表单校验规则
const rules = {
roleName: [
{ required: true, message: '请输入角色名', trigger: 'blur' }
]
}
// 响应式数据
const allSelected = ref(false)
const searchRole = ref('')
const selectedRoles = ref([])
const allPermissionsSelected = ref(false)
const permissionList = ref([
{
id: 1,
name: '管理首页',
selected: false,
expanded: false,
indeterminate: false
},
{
id: 2,
name: '项目管理',
selected: false,
expanded: false
},
{
id: 3,
name: '规则管理',
selected: false,
expanded: false,
indeterminate: false,
children: [
{ id: '3-1', name: '规则管理子项1', selected: false },
{ id: '3-2', name: '规则管理子项2', selected: false }
]
},
{
id: 4,
name: '资产库',
selected: false
},
{
id: 5,
name: '系统设置',
selected: false,
expanded: false,
indeterminate: false,
children: [
{ id: '5-1', name: '设置子项1', selected: false },
{ id: '5-2', name: '设置子项2', selected: false }
]
},
{
id: 6,
name: '用户管理',
selected: false,
expanded: false,
indeterminate: false,
children: [
{ id: '6-1', name: '用户管理子项1', selected: false },
{ id: '6-2', name: '用户管理子项2', selected: false }
]
}
])
// 计算属性
const filteredRoles = computed(() => {
return props.roles?.filter(role =>
role.name.toLowerCase().includes(searchRole.value.toLowerCase())
) || []
})
// 监听props变化
watch(() => props.visible, (val) => {
if (val && props.formData) {
Object.assign(form, {
...props.formData,
password: '',
confirmPassword: ''
})
}
})
// 方法
const handleClose = () => {
emit('update:visible', false)
nextTick(() => {
formRef.value?.resetFields()
Object.assign(form, {
realname: '',
roleName: '',
password: '',
confirmPassword: '',
email: '',
userGroup: '',
remark: '',
isDisabled: false
})
})
}
const handleSubmit = () => {
formRef.value.validate((valid) => {
if (valid) {
// 提交表单逻辑
const params = { ...form }
// 如果是编辑模式且没有修改密码,则不提交密码字段
if (props.formData && !params.password) {
delete params.password
delete params.confirmPassword
}
console.log('submit form', params)
// 调用接口保存数据
emit('success')
handleClose()
}
})
}
const handleSelectAll = (val) => {
if (props.roles) {
props.roles.forEach(role => {
role.selected = val
})
}
}
const handleRoleSelect = (role, val) => {
role.selected = val
allSelected.value = props.roles?.every(role => role.selected) || false
}
const addSelected = () => {
const selected = props.roles?.filter(role => role.selected) || []
selectedRoles.value = [...new Set([...selectedRoles.value, ...selected])]
// 这里应该更新props.roles,但由于是props,需要通过emit事件通知父组件更新
allSelected.value = false
}
const removeSelected = () => {
// 这里应该将selectedRoles添加回props.roles,但由于是props,需要通过emit事件通知父组件更新
selectedRoles.value = []
}
const clearSelected = () => {
// 这里应该将selectedRoles添加回props.roles,但由于是props,需要通过emit事件通知父组件更新
selectedRoles.value = []
}
// 权限相关方法
const handleAllPermissionsChange = (val) => {
// 处理全选
permissionList.value.forEach(item => {
item.selected = val
if (item.children) {
item.children.forEach(child => {
child.selected = val
})
}
item.indeterminate = false
})
allPermissionsSelected.value = val
}
const handlePermissionChange = (item, val) => {
// 处理父级权限选择
item.selected = val
item.indeterminate = false // 清除半选状态
if (item.children) {
item.children.forEach(child => {
child.selected = val
})
}
checkAllPermissionsStatus()
}
const handleSubPermissionChange = (parent, child, val) => {
// 处理子级权限选择
child.selected = val
// 检查父级状态
if (parent.children) {
// 如果所有子项都选中,父级完全选中
const allSelected = parent.children.every(c => c.selected)
// 如果部分子项选中,父级半选
const someSelected = parent.children.some(c => c.selected)
parent.selected = allSelected
parent.indeterminate = !allSelected && someSelected
}
checkAllPermissionsStatus()
}
const checkAllPermissionsStatus = () => {
// 检查是否全部选中
allPermissionsSelected.value = permissionList.value.every(item => {
if (item.children) {
return item.selected && item.children.every(child => child.selected)
}
return item.selected
})
}
const toggleExpand = (item) => {
// 展开/收起子菜单
item.expanded = !item.expanded
}
</script>
<style lang="scss" scoped>
.role-transfer {
display: flex;
align-items: flex-start;
gap: 10px;
.transfer-list {
flex: 1;
border: 1px solid #dcdfe6;
border-radius: 4px;
height: 300px;
display: flex;
flex-direction: column;
.transfer-header {
padding: 8px 12px;
border-bottom: 1px solid #dcdfe6;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f7fa;
}
.role-list, .selected-list {
flex: 1;
overflow-y: auto;
padding: 6px 0;
}
.role-item {
padding: 6px 12px;
cursor: pointer;
&:hover {
background-color: #f5f7fa;
}
}
}
.transfer-operation {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px 0;
}
}
.permission-area {
border: 1px solid #dcdfe6;
border-radius: 4px;
width: 100%;
display: flex;
flex-direction: column;
max-height: 400px;
overflow-y: auto;
.all-select {
padding: 8px 12px;
background-color: #f5f7fa;
border-bottom: 1px solid #dcdfe6;
position: sticky;
top: 0;
z-index: 1;
}
.permission-list {
padding: 12px;
.permission-item {
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
}
.permission-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
cursor: pointer;
.left {
display: flex;
align-items: center;
gap: 8px;
.expand-icon {
transition: transform 0.3s;
font-size: 12px;
color: #909399;
&.expanded {
transform: rotate(90deg);
}
}
}
}
.sub-permissions {
padding: 8px 0 8px 32px;
display: flex;
flex-direction: column;
gap: 12px;
background-color: #f8f9fb;
border-top: 1px solid #ebeef5;
}
}
}
}
:deep(.el-transfer-panel) {
width: 100%;
}
.user-edit-dialog {
:deep(.el-dialog) {
display: flex;
flex-direction: column;
margin: 0 auto !important;
max-height: 90vh;
position: relative;
.el-dialog__body {
flex: 1;
overflow: hidden;
padding: 0;
}
.dialog-content {
height: 100%;
overflow-y: auto;
padding: 20px;
padding-bottom: 80px;
}
}
}
.dialog-footer {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 20px;
background-color: #fff;
border-top: 1px solid #dcdfe6;
z-index: 9999;
.footer-buttons {
display: flex;
gap: 10px;
justify-content: flex-end;
}
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论