|
|
|
@ -1,14 +1,25 @@
@@ -1,14 +1,25 @@
|
|
|
|
|
<template> |
|
|
|
|
<div class="role-management"> |
|
|
|
|
<div class="page-header"> |
|
|
|
|
<h2>角色管理</h2> |
|
|
|
|
<button class="btn btn-primary" @click="createNewRole"> |
|
|
|
|
<i class="fas fa-plus"></i> 新建角色 |
|
|
|
|
</button> |
|
|
|
|
<!-- 搜索和操作 --> |
|
|
|
|
<div class="search-bar"> |
|
|
|
|
<div class="search-input"> |
|
|
|
|
<i class="fas fa-search"></i> |
|
|
|
|
<input |
|
|
|
|
v-model="searchQuery" |
|
|
|
|
type="text" |
|
|
|
|
placeholder="搜索角色名称或代码..." |
|
|
|
|
@input="handleSearch" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
<div class="filters"> |
|
|
|
|
<button class="btn btn-primary" @click="createNewRole"> |
|
|
|
|
<i class="fas fa-plus"></i> 新建角色 |
|
|
|
|
</button> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<!-- 角色列表 --> |
|
|
|
|
<div class="role-list"> |
|
|
|
|
<div class="role-table"> |
|
|
|
|
<div v-if="loading" class="loading"> |
|
|
|
|
<i class="fas fa-spinner fa-spin"></i> |
|
|
|
|
<span>加载中...</span> |
|
|
|
@ -33,7 +44,7 @@
@@ -33,7 +44,7 @@
|
|
|
|
|
</tr> |
|
|
|
|
</thead> |
|
|
|
|
<tbody> |
|
|
|
|
<tr v-for="role in roles" :key="role.id"> |
|
|
|
|
<tr v-for="role in filteredRoles" :key="role.id"> |
|
|
|
|
<td>{{ role.id }}</td> |
|
|
|
|
<td>{{ role.name }}</td> |
|
|
|
|
<td>{{ role.code }}</td> |
|
|
|
@ -44,7 +55,7 @@
@@ -44,7 +55,7 @@
|
|
|
|
|
<button class="btn btn-sm btn-info" @click="editRole(role)" title="编辑角色"> |
|
|
|
|
<i class="fas fa-edit"></i> |
|
|
|
|
</button> |
|
|
|
|
<button class="btn btn-sm btn-warning" @click="assignPermissions(role)" title="分配权限"> |
|
|
|
|
<button class="btn btn-sm btn-primary" @click="assignPermissions(role)" title="分配权限"> |
|
|
|
|
<i class="fas fa-shield-alt"></i> |
|
|
|
|
</button> |
|
|
|
|
<button class="btn btn-sm btn-danger" @click="deleteRole(role)" title="删除角色"> |
|
|
|
@ -138,7 +149,7 @@
@@ -138,7 +149,7 @@
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
<script> |
|
|
|
|
import { ref, reactive, onMounted } from 'vue' |
|
|
|
|
import { ref, reactive, computed, onMounted } from 'vue' |
|
|
|
|
import { roleService } from '../services/roleService.js' |
|
|
|
|
import RolePermissionAssignment from '../components/RolePermissionAssignment.vue' |
|
|
|
|
|
|
|
|
@ -159,6 +170,7 @@ export default {
@@ -159,6 +170,7 @@ export default {
|
|
|
|
|
const currentPage = ref(1) |
|
|
|
|
const pageSize = ref(10) |
|
|
|
|
const total = ref(0) |
|
|
|
|
const searchQuery = ref('') |
|
|
|
|
|
|
|
|
|
const roleForm = reactive({ |
|
|
|
|
name: '', |
|
|
|
@ -166,6 +178,24 @@ export default {
@@ -166,6 +178,24 @@ export default {
|
|
|
|
|
description: '' |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 过滤后的角色列表 |
|
|
|
|
const filteredRoles = computed(() => { |
|
|
|
|
if (!searchQuery.value) { |
|
|
|
|
return roles.value |
|
|
|
|
} |
|
|
|
|
const query = searchQuery.value.toLowerCase() |
|
|
|
|
return roles.value.filter(role => |
|
|
|
|
role.name.toLowerCase().includes(query) || |
|
|
|
|
role.code.toLowerCase().includes(query) || |
|
|
|
|
role.description?.toLowerCase().includes(query) |
|
|
|
|
) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 搜索处理 |
|
|
|
|
const handleSearch = () => { |
|
|
|
|
currentPage.value = 1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 获取角色列表 |
|
|
|
|
const loadRoles = async () => { |
|
|
|
|
loading.value = true |
|
|
|
@ -314,7 +344,9 @@ export default {
@@ -314,7 +344,9 @@ export default {
|
|
|
|
|
currentPage, |
|
|
|
|
pageSize, |
|
|
|
|
total, |
|
|
|
|
searchQuery, |
|
|
|
|
roleForm, |
|
|
|
|
filteredRoles, |
|
|
|
|
createNewRole, |
|
|
|
|
editRole, |
|
|
|
|
saveRole, |
|
|
|
@ -322,6 +354,7 @@ export default {
@@ -322,6 +354,7 @@ export default {
|
|
|
|
|
assignPermissions, |
|
|
|
|
handlePermissionsUpdated, |
|
|
|
|
handleCurrentChange, |
|
|
|
|
handleSearch, |
|
|
|
|
formatDate |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -331,25 +364,91 @@ export default {
@@ -331,25 +364,91 @@ export default {
|
|
|
|
|
<style scoped> |
|
|
|
|
.role-management { |
|
|
|
|
padding: 20px; |
|
|
|
|
padding-top: 10px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.page-header { |
|
|
|
|
.search-bar { |
|
|
|
|
display: flex; |
|
|
|
|
justify-content: space-between; |
|
|
|
|
gap: 20px; |
|
|
|
|
margin-bottom: 6px; |
|
|
|
|
margin-top: 0; |
|
|
|
|
align-items: center; |
|
|
|
|
margin-bottom: 20px; |
|
|
|
|
flex-wrap: wrap; |
|
|
|
|
justify-content: space-between; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.page-header h2 { |
|
|
|
|
margin: 0; |
|
|
|
|
color: var(--text-primary); |
|
|
|
|
.search-input { |
|
|
|
|
position: relative; |
|
|
|
|
flex: 0 0 280px; |
|
|
|
|
min-width: 200px; |
|
|
|
|
max-width: 350px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 响应式设计 */ |
|
|
|
|
@media (max-width: 768px) { |
|
|
|
|
.search-bar { |
|
|
|
|
flex-direction: column; |
|
|
|
|
align-items: stretch; |
|
|
|
|
gap: 15px; |
|
|
|
|
justify-content: flex-start; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.search-input { |
|
|
|
|
flex: 1; |
|
|
|
|
min-width: auto; |
|
|
|
|
max-width: none; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.filters { |
|
|
|
|
justify-content: space-between; |
|
|
|
|
flex: none; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 中等屏幕优化 */ |
|
|
|
|
@media (max-width: 1024px) and (min-width: 769px) { |
|
|
|
|
.search-input { |
|
|
|
|
flex: 0 0 250px; |
|
|
|
|
min-width: 200px; |
|
|
|
|
max-width: 300px; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.search-input i { |
|
|
|
|
position: absolute; |
|
|
|
|
left: 12px; |
|
|
|
|
top: 50%; |
|
|
|
|
transform: translateY(-50%); |
|
|
|
|
color: var(--text-muted); |
|
|
|
|
font-size: 12px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.search-input input { |
|
|
|
|
width: 100%; |
|
|
|
|
padding: 8px 10px 8px 35px; |
|
|
|
|
border: 1px solid var(--input-border); |
|
|
|
|
border-radius: 6px; |
|
|
|
|
font-size: 12px; |
|
|
|
|
background-color: var(--input-bg); |
|
|
|
|
color: var(--input-text); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.filters { |
|
|
|
|
display: flex; |
|
|
|
|
gap: 15px; |
|
|
|
|
flex-wrap: wrap; |
|
|
|
|
flex: 1; |
|
|
|
|
justify-content: flex-end; |
|
|
|
|
align-items: center; |
|
|
|
|
min-width: 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.role-list { |
|
|
|
|
.role-table { |
|
|
|
|
background: var(--card-bg); |
|
|
|
|
border-radius: 8px; |
|
|
|
|
padding: 20px; |
|
|
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|
|
|
|
box-shadow: 0 2px 8px var(--shadow-color); |
|
|
|
|
overflow: hidden; |
|
|
|
|
margin-bottom: 20px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.loading { |
|
|
|
@ -392,19 +491,20 @@ export default {
@@ -392,19 +491,20 @@ export default {
|
|
|
|
|
table { |
|
|
|
|
width: 100%; |
|
|
|
|
border-collapse: collapse; |
|
|
|
|
margin-bottom: 20px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
th, td { |
|
|
|
|
padding: 12px; |
|
|
|
|
padding: 8px; |
|
|
|
|
text-align: left; |
|
|
|
|
border-bottom: 1px solid var(--border-color); |
|
|
|
|
font-size: 12px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
th { |
|
|
|
|
background: var(--bg-secondary); |
|
|
|
|
font-weight: 600; |
|
|
|
|
font-weight: normal; |
|
|
|
|
color: var(--text-primary); |
|
|
|
|
font-size: 14px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.actions { |
|
|
|
@ -417,7 +517,7 @@ th {
@@ -417,7 +517,7 @@ th {
|
|
|
|
|
border: none; |
|
|
|
|
border-radius: 4px; |
|
|
|
|
cursor: pointer; |
|
|
|
|
font-size: 14px; |
|
|
|
|
font-size: 12px; |
|
|
|
|
display: inline-flex; |
|
|
|
|
align-items: center; |
|
|
|
|
gap: 6px; |
|
|
|
@ -425,12 +525,12 @@ th {
@@ -425,12 +525,12 @@ th {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.btn-primary { |
|
|
|
|
background: var(--accent-color); |
|
|
|
|
background: #1976d2; |
|
|
|
|
color: white; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.btn-primary:hover { |
|
|
|
|
background: var(--accent-hover); |
|
|
|
|
background: #1565c0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.btn-info { |
|
|
|
@ -450,19 +550,23 @@ th {
@@ -450,19 +550,23 @@ th {
|
|
|
|
|
|
|
|
|
|
.btn-sm { |
|
|
|
|
padding: 6px 12px; |
|
|
|
|
font-size: 12px; |
|
|
|
|
font-size: 10px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 表格操作按钮样式优化 */ |
|
|
|
|
.actions .btn-sm { |
|
|
|
|
background: none; |
|
|
|
|
border: none; |
|
|
|
|
padding: 4px 8px; |
|
|
|
|
margin: 0 2px; |
|
|
|
|
padding: 6px 10px; |
|
|
|
|
margin: 0 -2px; |
|
|
|
|
color: var(--text-primary); |
|
|
|
|
transition: all 0.2s; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.actions .btn-sm i { |
|
|
|
|
font-size: 14px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.actions .btn-sm:hover { |
|
|
|
|
background: var(--bg-secondary); |
|
|
|
|
color: var(--accent-color); |
|
|
|
@ -473,12 +577,12 @@ th {
@@ -473,12 +577,12 @@ th {
|
|
|
|
|
color: #2196f3; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.actions .btn-sm.btn-danger:hover { |
|
|
|
|
color: #d32f2f; |
|
|
|
|
.actions .btn-sm.btn-primary:hover { |
|
|
|
|
color: #1976d2; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.actions .btn-sm.btn-warning:hover { |
|
|
|
|
color: #ff9800; |
|
|
|
|
.actions .btn-sm.btn-danger:hover { |
|
|
|
|
color: #d32f2f; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.btn:hover { |
|
|
|
@ -498,7 +602,7 @@ th {
@@ -498,7 +602,7 @@ th {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.page-info { |
|
|
|
|
color: var(--text-secondary); |
|
|
|
|
color: #666; |
|
|
|
|
font-size: 14px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -535,27 +639,16 @@ th {
@@ -535,27 +639,16 @@ th {
|
|
|
|
|
|
|
|
|
|
.modal-header h3 { |
|
|
|
|
margin: 0; |
|
|
|
|
color: var(--text-primary); |
|
|
|
|
font-size: 14px; |
|
|
|
|
font-weight: normal; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.close-btn { |
|
|
|
|
background: none; |
|
|
|
|
border: none; |
|
|
|
|
font-size: 18px; |
|
|
|
|
font-size: 14px; |
|
|
|
|
cursor: pointer; |
|
|
|
|
color: var(--text-muted); |
|
|
|
|
display: flex; |
|
|
|
|
align-items: center; |
|
|
|
|
justify-content: center; |
|
|
|
|
width: 32px; |
|
|
|
|
height: 32px; |
|
|
|
|
border-radius: 4px; |
|
|
|
|
transition: all 0.2s; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.close-btn:hover { |
|
|
|
|
background: var(--bg-secondary); |
|
|
|
|
color: var(--text-primary); |
|
|
|
|
color: #999; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.modal-body { |
|
|
|
@ -570,20 +663,19 @@ th {
@@ -570,20 +663,19 @@ th {
|
|
|
|
|
.form-group label { |
|
|
|
|
display: block; |
|
|
|
|
margin-bottom: 8px; |
|
|
|
|
font-weight: 500; |
|
|
|
|
color: var(--text-primary); |
|
|
|
|
font-weight: normal; |
|
|
|
|
color: #333; |
|
|
|
|
font-size: 14px; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.form-group input, |
|
|
|
|
.form-group textarea { |
|
|
|
|
width: 100%; |
|
|
|
|
padding: 10px; |
|
|
|
|
border: 1px solid var(--border-color); |
|
|
|
|
border: 1px solid #ddd; |
|
|
|
|
border-radius: 4px; |
|
|
|
|
font-size: 14px; |
|
|
|
|
box-sizing: border-box; |
|
|
|
|
background: var(--input-bg); |
|
|
|
|
color: var(--input-text); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
.form-group textarea { |
|
|
|
|