620 lines
16 KiB
Vue
620 lines
16 KiB
Vue
<template>
|
||
<div class="role-management-page">
|
||
<div class="page-head">
|
||
<div>
|
||
<p class="eyebrow">ROLE MANAGEMENT</p>
|
||
<h1>角色管理</h1>
|
||
<span>统一管理系统用户与角色权限。</span>
|
||
</div>
|
||
<el-button type="primary" icon="el-icon-plus" class="add-role-btn" @click="openAddDialog">
|
||
添加角色
|
||
</el-button>
|
||
</div>
|
||
|
||
<div class="filter-card">
|
||
<el-form :model="queryForm" inline class="filter-form">
|
||
<el-form-item label="用户名">
|
||
<el-input
|
||
v-model.trim="queryForm.username"
|
||
size="small"
|
||
clearable
|
||
placeholder="请输入用户名"
|
||
@keyup.enter.native="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="手机号">
|
||
<el-input
|
||
v-model.trim="queryForm.mobile"
|
||
size="small"
|
||
clearable
|
||
placeholder="请输入手机号"
|
||
@keyup.enter.native="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button size="small" icon="el-icon-search" type="primary" @click="handleSearch">搜索</el-button>
|
||
<el-button size="small" icon="el-icon-refresh-left" @click="resetSearch">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<div class="table-card">
|
||
<el-table v-loading="loading" :data="roles" class="role-table" empty-text="暂无角色数据">
|
||
<el-table-column label="序号" width="80" align="center">
|
||
<template slot-scope="scope">{{ scope.$index + 1 }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="username" label="用户名" min-width="180">
|
||
<template slot-scope="scope">
|
||
<div class="role-name-cell">
|
||
<span class="role-avatar">{{ getAvatarText(scope.row.username) }}</span>
|
||
<div>
|
||
<strong>{{ scope.row.username }}</strong>
|
||
<small>{{ getRoleText(scope.row.roles) }}</small>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="email" label="邮箱" min-width="220" show-overflow-tooltip />
|
||
<el-table-column prop="mobile" label="手机号" width="150" align="center" />
|
||
<el-table-column label="角色权限" width="180" align="center">
|
||
<template slot-scope="scope">
|
||
{{ getRoleText(scope.row.roles) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="create_at" label="创建时间" width="180" align="center" />
|
||
<el-table-column label="操作" width="140" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button type="text" size="mini" @click="openAssignDialog(scope.row)">分配角色</el-button>
|
||
<!-- <el-button type="text" size="mini" class="danger-text">删除</el-button> -->
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="table-pagination">
|
||
<span class="pagination-info">共 {{ total }} 条</span>
|
||
<el-pagination
|
||
background
|
||
layout="prev, pager, next, jumper"
|
||
:current-page.sync="currentPage"
|
||
:page-size="pageSize"
|
||
:total="total"
|
||
@current-change="handlePageChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<el-dialog
|
||
:visible.sync="addDialogVisible"
|
||
append-to-body
|
||
width="520px"
|
||
custom-class="role-add-dialog"
|
||
:close-on-click-modal="false"
|
||
@close="resetAddForm"
|
||
>
|
||
<div slot="title" class="dialog-title">
|
||
<div class="dialog-icon">
|
||
<i class="el-icon-user-solid"></i>
|
||
</div>
|
||
<div>
|
||
<h3>添加角色用户</h3>
|
||
<p>创建角色账号,并配置对应角色权限。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<el-form ref="addForm" :model="addForm" :rules="addRules" label-position="top" class="dialog-form">
|
||
<el-form-item label="用户名" prop="username">
|
||
<el-input v-model.trim="addForm.username" maxlength="30" placeholder="请输入用户名" />
|
||
</el-form-item>
|
||
<el-form-item label="密码" prop="password">
|
||
<el-input v-model.trim="addForm.password" type="password" show-password maxlength="30" placeholder="请输入密码" />
|
||
</el-form-item>
|
||
<el-form-item label="邮箱" prop="email">
|
||
<el-input v-model.trim="addForm.email" maxlength="60" placeholder="请输入邮箱" />
|
||
</el-form-item>
|
||
<el-form-item label="手机号" prop="mobile">
|
||
<el-input v-model.trim="addForm.mobile" maxlength="11" placeholder="请输入手机号" />
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="addDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" class="submit-btn" @click="submitAddRole()">确认添加</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:visible.sync="assignDialogVisible"
|
||
append-to-body
|
||
width="500px"
|
||
custom-class="role-add-dialog"
|
||
:close-on-click-modal="false"
|
||
@close="resetAssignForm"
|
||
>
|
||
<div slot="title" class="dialog-title">
|
||
<div class="dialog-icon">
|
||
<i class="el-icon-s-check"></i>
|
||
</div>
|
||
<div>
|
||
<h3>分配角色</h3>
|
||
<p>为 {{ currentAssignUser.username || '当前用户' }} 配置可用角色权限。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<el-form ref="assignForm" :model="assignForm" :rules="assignRules" label-position="top" class="dialog-form">
|
||
<el-form-item label="角色权限" prop="roleid">
|
||
<el-select
|
||
v-model="assignForm.roleid"
|
||
multiple
|
||
collapse-tags
|
||
placeholder="请选择角色权限"
|
||
class="full-select"
|
||
filterable
|
||
>
|
||
<el-option
|
||
v-for="item in roleOptions"
|
||
:key="item.id"
|
||
:label="item.role"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="assignDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" class="submit-btn" @click="submitAssignRole">确认分配</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { addRoleAPI, assignRoleAPI, getListAPI, getRoleAPI } from '@/api/roleManagement';
|
||
|
||
export default {
|
||
name: 'RoleManagement',
|
||
data() {
|
||
return {
|
||
queryForm: {
|
||
username: '',
|
||
mobile: ''
|
||
},
|
||
loading: false,
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
addDialogVisible: false,
|
||
assignDialogVisible: false,
|
||
currentAssignUser: {},
|
||
addForm: this.createEmptyRole(),
|
||
assignForm: {
|
||
roleid: []
|
||
},
|
||
roleOptions: [],
|
||
addRules: {
|
||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||
email: [
|
||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||
{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }
|
||
],
|
||
mobile: [
|
||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||
]
|
||
},
|
||
assignRules: {
|
||
roleid: [{ required: true, type: 'array', message: '请选择角色权限', trigger: 'change' }]
|
||
},
|
||
roles: []
|
||
}
|
||
},
|
||
created() {
|
||
this.getRoleList()
|
||
this.getUserList()
|
||
},
|
||
methods: {
|
||
createEmptyRole() {
|
||
return {
|
||
username: '',
|
||
password: '',
|
||
email: '',
|
||
mobile: ''
|
||
}
|
||
},
|
||
openAddDialog() {
|
||
if (!this.roleOptions.length) this.getRoleList()
|
||
this.addDialogVisible = true
|
||
},
|
||
// 获取角色
|
||
async getRoleList() {
|
||
const orgType = sessionStorage.getItem('org_type') || sessionStorage.getItem('orgType') || ''
|
||
try {
|
||
const res = await getRoleAPI({ org_type: orgType })
|
||
if (res && res.status === true) {
|
||
this.roleOptions = this.normalizeRoleOptions(res.data)
|
||
return
|
||
}
|
||
this.roleOptions = []
|
||
} catch (error) {
|
||
this.roleOptions = []
|
||
this.$message.error('角色权限获取失败')
|
||
}
|
||
},
|
||
handleSearch() {
|
||
this.currentPage = 1
|
||
this.getUserList()
|
||
},
|
||
resetSearch() {
|
||
this.queryForm = {
|
||
username: '',
|
||
mobile: ''
|
||
}
|
||
this.currentPage = 1
|
||
this.getUserList()
|
||
},
|
||
handlePageChange(page) {
|
||
this.currentPage = page
|
||
this.getUserList()
|
||
},
|
||
getAvatarText(username) {
|
||
return String(username || '-').slice(0, 1).toUpperCase()
|
||
},
|
||
getRoleText(roles) {
|
||
return Array.isArray(roles) && roles.length
|
||
? roles.map(item => item.name).join('、')
|
||
: '-'
|
||
},
|
||
normalizeRoleOptions(list) {
|
||
return Array.isArray(list)
|
||
? list.map(item => ({
|
||
...item,
|
||
role: item.role || item.name || ''
|
||
}))
|
||
: []
|
||
},
|
||
mergeRoleOptions(roles = []) {
|
||
const nextOptions = [...this.roleOptions]
|
||
roles.forEach(role => {
|
||
if (role && role.id && !nextOptions.some(item => item.id === role.id)) {
|
||
nextOptions.push({
|
||
id: role.id,
|
||
role: role.name || role.role || role.id
|
||
})
|
||
}
|
||
})
|
||
this.roleOptions = nextOptions
|
||
},
|
||
getOrgId() {
|
||
return sessionStorage.getItem('orgid') || ''
|
||
},
|
||
async getUserList() {
|
||
this.loading = true
|
||
try {
|
||
const res = await getListAPI({
|
||
orgid: this.getOrgId(),
|
||
current_page: this.currentPage,
|
||
page_size: this.pageSize,
|
||
username: this.queryForm.username,
|
||
mobile: this.queryForm.mobile
|
||
})
|
||
if (res && res.status === true && res.data) {
|
||
this.roles = Array.isArray(res.data.rows) ? res.data.rows : []
|
||
this.total = Number(res.data.total_count || 0)
|
||
this.currentPage = Number(res.data.current_page || this.currentPage)
|
||
this.pageSize = Number(res.data.page_size || this.pageSize)
|
||
return
|
||
}
|
||
this.roles = []
|
||
this.total = 0
|
||
} catch (error) {
|
||
this.roles = []
|
||
this.total = 0
|
||
this.$message.error('用户角色列表获取失败')
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
resetAddForm() {
|
||
this.addForm = this.createEmptyRole()
|
||
this.$nextTick(() => {
|
||
this.$refs.addForm && this.$refs.addForm.clearValidate()
|
||
})
|
||
},
|
||
resetAssignForm() {
|
||
this.currentAssignUser = {}
|
||
this.assignForm = {
|
||
roleid: []
|
||
}
|
||
this.$nextTick(() => {
|
||
this.$refs.assignForm && this.$refs.assignForm.clearValidate()
|
||
})
|
||
},
|
||
submitAddRole() {
|
||
this.$refs.addForm.validate(async valid => {
|
||
if (!valid) return
|
||
await this.addUser()
|
||
})
|
||
},
|
||
// 添加角色用户
|
||
async addUser() {
|
||
const orgType = sessionStorage.getItem('org_type') || sessionStorage.getItem('orgType') || ''
|
||
const data = {
|
||
username: this.addForm.username,
|
||
password: this.addForm.password,
|
||
email: this.addForm.email,
|
||
mobile: this.addForm.mobile,
|
||
orgid: this.getOrgId(),
|
||
org_type: orgType
|
||
}
|
||
try {
|
||
const res = await addRoleAPI(data)
|
||
if (res && res.status === true) {
|
||
const now = new Date()
|
||
const createdAt = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
|
||
this.roles.unshift({
|
||
username: this.addForm.username,
|
||
email: this.addForm.email,
|
||
mobile: this.addForm.mobile,
|
||
roles: [],
|
||
create_at: createdAt
|
||
})
|
||
this.total += 1
|
||
this.$message.success(res.msg || '角色用户添加成功')
|
||
this.addDialogVisible = false
|
||
this.getUserList()
|
||
return
|
||
}
|
||
this.$message.error((res && res.msg) || '角色用户添加失败')
|
||
} catch (error) {
|
||
this.$message.error('角色用户添加失败')
|
||
}
|
||
},
|
||
async openAssignDialog(row) {
|
||
this.currentAssignUser = row || {}
|
||
if (!this.roleOptions.length) await this.getRoleList()
|
||
this.mergeRoleOptions(row.roles || [])
|
||
this.assignForm.roleid = Array.isArray(row.roles) ? row.roles.map(item => item.id) : []
|
||
this.assignDialogVisible = true
|
||
},
|
||
submitAssignRole() {
|
||
this.$refs.assignForm.validate(async valid => {
|
||
if (!valid) return
|
||
try {
|
||
const res = await assignRoleAPI({
|
||
userid: this.currentAssignUser.id,
|
||
roleid: this.assignForm.roleid
|
||
})
|
||
if (res && res.status === true) {
|
||
this.$message.success(res.msg || '角色分配成功')
|
||
this.assignDialogVisible = false
|
||
this.getUserList()
|
||
return
|
||
}
|
||
this.$message.error((res && res.msg) || '角色分配失败')
|
||
} catch (error) {
|
||
this.$message.error('角色分配失败')
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.role-management-page {
|
||
min-height: calc(100vh - 84px);
|
||
padding: 24px;
|
||
background: #f3f7ff;
|
||
}
|
||
|
||
.page-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.eyebrow {
|
||
margin: 0 0 6px;
|
||
color: #3b82f6;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.page-head h1 {
|
||
margin: 0;
|
||
color: #111827;
|
||
font-size: 28px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.page-head span {
|
||
display: inline-block;
|
||
margin-top: 8px;
|
||
color: #64748b;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.add-role-btn {
|
||
height: 38px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.filter-card,
|
||
.table-card {
|
||
background: #fff;
|
||
border: 1px solid #edf0f5;
|
||
border-radius: 16px;
|
||
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.04);
|
||
}
|
||
|
||
.filter-card {
|
||
padding: 16px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.filter-form {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-form ::v-deep .el-form-item {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.filter-form ::v-deep .el-input__inner {
|
||
border-radius: 9px;
|
||
}
|
||
|
||
.filter-form ::v-deep .el-button--small {
|
||
height: 32px;
|
||
border-radius: 9px;
|
||
}
|
||
|
||
.table-card {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.table-pagination {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 18px;
|
||
border-top: 1px solid #edf0f5;
|
||
}
|
||
|
||
.pagination-info {
|
||
color: #64748b;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.role-table ::v-deep th {
|
||
background: #f8fafc;
|
||
color: #64748b;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.role-name-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.role-avatar {
|
||
width: 36px;
|
||
height: 36px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #2563eb;
|
||
font-size: 16px;
|
||
font-weight: 800;
|
||
background: #eff6ff;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.role-name-cell strong {
|
||
display: block;
|
||
color: #1f2937;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.role-name-cell small {
|
||
display: block;
|
||
margin-top: 3px;
|
||
color: #94a3b8;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.danger-text {
|
||
color: #ef4444;
|
||
}
|
||
|
||
::v-deep .role-add-dialog {
|
||
border-radius: 24px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
::v-deep .role-add-dialog .el-dialog__header {
|
||
padding: 26px 30px 18px;
|
||
border-bottom: 1px solid #edf0f5;
|
||
}
|
||
|
||
::v-deep .role-add-dialog .el-dialog__body {
|
||
padding: 24px 30px 6px;
|
||
}
|
||
|
||
::v-deep .role-add-dialog .el-dialog__footer {
|
||
padding: 16px 30px 28px;
|
||
}
|
||
|
||
.dialog-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
}
|
||
|
||
.dialog-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #2563eb;
|
||
font-size: 20px;
|
||
background: linear-gradient(135deg, #eff6ff, #dbeafe);
|
||
border-radius: 14px;
|
||
}
|
||
|
||
.dialog-title h3 {
|
||
margin: 0;
|
||
color: #111827;
|
||
font-size: 22px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.dialog-title p {
|
||
margin: 5px 0 0;
|
||
color: #64748b;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.dialog-form ::v-deep .el-form-item {
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.dialog-form ::v-deep .el-form-item__label {
|
||
padding-bottom: 7px;
|
||
color: #334155;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.dialog-form ::v-deep .el-input__inner,
|
||
.dialog-form ::v-deep .el-textarea__inner {
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.full-select {
|
||
width: 100%;
|
||
}
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
}
|
||
|
||
.dialog-footer .el-button {
|
||
min-width: 96px;
|
||
height: 38px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.submit-btn {
|
||
background: #2563eb;
|
||
border-color: #2563eb;
|
||
}
|
||
</style>
|