2026-07-22 11:34:07 +08:00

620 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>