14 changed files with 2475 additions and 255 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,769 @@
@@ -0,0 +1,769 @@
|
||||
<template> |
||||
<div class="role-permission-assignment"> |
||||
<div v-if="visible" class="modal-overlay" @click="handleClose"> |
||||
<div class="modal large-modal" @click.stop> |
||||
<div class="modal-header"> |
||||
<h3>角色权限分配 - {{ currentRole?.name }}</h3> |
||||
<button class="close-btn" @click="handleClose"> |
||||
<i class="fas fa-times"></i> |
||||
</button> |
||||
</div> |
||||
|
||||
<div class="modal-body"> |
||||
<div v-if="loading" class="loading"> |
||||
<i class="fas fa-spinner fa-spin"></i> |
||||
<span>加载中...</span> |
||||
</div> |
||||
|
||||
<div v-else class="permission-content"> |
||||
<!-- 角色信息 --> |
||||
<div class="role-info"> |
||||
<div class="role-details"> |
||||
<h4>{{ currentRole?.name }}</h4> |
||||
<p class="role-description">{{ currentRole?.description }}</p> |
||||
<div class="role-stats"> |
||||
<span class="stat-item"> |
||||
<i class="fas fa-shield-alt"></i> |
||||
已分配权限: {{ assignedPermissions.length }} |
||||
</span> |
||||
<span class="stat-item"> |
||||
<i class="fas fa-list"></i> |
||||
总权限数: {{ allPermissions.length }} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- 权限分配区域 --> |
||||
<div class="permission-assignment"> |
||||
<div class="assignment-tabs"> |
||||
<button |
||||
class="tab-btn" |
||||
:class="{ active: activeTab === 'assigned' }" |
||||
@click="activeTab = 'assigned'" |
||||
> |
||||
<i class="fas fa-check-circle"></i> |
||||
已分配权限 ({{ assignedPermissions.length }}) |
||||
</button> |
||||
<button |
||||
class="tab-btn" |
||||
:class="{ active: activeTab === 'available' }" |
||||
@click="activeTab = 'available'" |
||||
> |
||||
<i class="fas fa-plus-circle"></i> |
||||
可分配权限 ({{ availablePermissions.length }}) |
||||
</button> |
||||
</div> |
||||
|
||||
<!-- 已分配权限列表 --> |
||||
<div v-if="activeTab === 'assigned'" class="permission-list assigned-permissions"> |
||||
<div class="list-header"> |
||||
<h5>已分配权限</h5> |
||||
<div class="list-actions"> |
||||
<button |
||||
class="btn btn-sm btn-danger" |
||||
@click="removeSelectedPermissions" |
||||
:disabled="selectedAssignedPermissions.length === 0" |
||||
> |
||||
<i class="fas fa-minus"></i> |
||||
移除选中 ({{ selectedAssignedPermissions.length }}) |
||||
</button> |
||||
<button |
||||
class="btn btn-sm btn-secondary" |
||||
@click="clearAssignedSelection" |
||||
> |
||||
清除选择 |
||||
</button> |
||||
</div> |
||||
</div> |
||||
|
||||
<div v-if="assignedPermissions.length === 0" class="empty-state"> |
||||
<i class="fas fa-inbox"></i> |
||||
<p>该角色暂无分配权限</p> |
||||
</div> |
||||
|
||||
<div v-else class="permission-items"> |
||||
<div |
||||
v-for="permission in assignedPermissions" |
||||
:key="permission.id" |
||||
class="permission-item" |
||||
:class="{ selected: selectedAssignedPermissions.includes(permission.id) }" |
||||
@click="toggleAssignedPermission(permission.id)" |
||||
> |
||||
<div class="permission-checkbox"> |
||||
<input |
||||
type="checkbox" |
||||
:checked="selectedAssignedPermissions.includes(permission.id)" |
||||
@change="toggleAssignedPermission(permission.id)" |
||||
/> |
||||
</div> |
||||
<div class="permission-info"> |
||||
<div class="permission-name">{{ permission.name }}</div> |
||||
<div class="permission-details"> |
||||
<span class="permission-code">{{ permission.code }}</span> |
||||
<span class="permission-resource">{{ permission.resource }}</span> |
||||
</div> |
||||
<div class="permission-description">{{ permission.description }}</div> |
||||
</div> |
||||
<div class="permission-actions"> |
||||
<button |
||||
class="btn btn-sm btn-danger" |
||||
@click.stop="removePermission(permission.id)" |
||||
title="移除权限" |
||||
> |
||||
<i class="fas fa-minus"></i> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- 可分配权限列表 --> |
||||
<div v-if="activeTab === 'available'" class="permission-list available-permissions"> |
||||
<div class="list-header"> |
||||
<h5>可分配权限</h5> |
||||
<div class="list-actions"> |
||||
<button |
||||
class="btn btn-sm btn-primary" |
||||
@click="assignSelectedPermissions" |
||||
:disabled="selectedAvailablePermissions.length === 0" |
||||
> |
||||
<i class="fas fa-plus"></i> |
||||
分配选中 ({{ selectedAvailablePermissions.length }}) |
||||
</button> |
||||
<button |
||||
class="btn btn-sm btn-secondary" |
||||
@click="clearAvailableSelection" |
||||
> |
||||
清除选择 |
||||
</button> |
||||
</div> |
||||
</div> |
||||
|
||||
<div v-if="availablePermissions.length === 0" class="empty-state"> |
||||
<i class="fas fa-check-circle"></i> |
||||
<p>所有权限已分配完毕</p> |
||||
</div> |
||||
|
||||
<div v-else class="permission-items"> |
||||
<div |
||||
v-for="permission in availablePermissions" |
||||
:key="permission.id" |
||||
class="permission-item" |
||||
:class="{ selected: selectedAvailablePermissions.includes(permission.id) }" |
||||
@click="toggleAvailablePermission(permission.id)" |
||||
> |
||||
<div class="permission-checkbox"> |
||||
<input |
||||
type="checkbox" |
||||
:checked="selectedAvailablePermissions.includes(permission.id)" |
||||
@change="toggleAvailablePermission(permission.id)" |
||||
/> |
||||
</div> |
||||
<div class="permission-info"> |
||||
<div class="permission-name">{{ permission.name }}</div> |
||||
<div class="permission-details"> |
||||
<span class="permission-code">{{ permission.code }}</span> |
||||
<span class="permission-resource">{{ permission.resource }}</span> |
||||
</div> |
||||
<div class="permission-description">{{ permission.description }}</div> |
||||
</div> |
||||
<div class="permission-actions"> |
||||
<button |
||||
class="btn btn-sm btn-primary" |
||||
@click.stop="assignPermission(permission.id)" |
||||
title="分配权限" |
||||
> |
||||
<i class="fas fa-plus"></i> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="modal-footer"> |
||||
<button class="btn btn-secondary" @click="handleClose">关闭</button> |
||||
<button class="btn btn-primary" @click="refreshPermissions" :disabled="loading"> |
||||
<i class="fas fa-sync-alt"></i> |
||||
刷新权限 |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { ref, watch, computed } from 'vue' |
||||
import roleService from '../services/roleService.js' |
||||
|
||||
export default { |
||||
name: 'RolePermissionAssignment', |
||||
props: { |
||||
modelValue: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
role: { |
||||
type: Object, |
||||
default: null |
||||
} |
||||
}, |
||||
emits: ['update:modelValue', 'permissions-updated'], |
||||
setup(props, { emit }) { |
||||
const visible = ref(false) |
||||
const loading = ref(false) |
||||
const currentRole = ref(null) |
||||
const allPermissions = ref([]) |
||||
const assignedPermissions = ref([]) |
||||
const selectedAssignedPermissions = ref([]) |
||||
const selectedAvailablePermissions = ref([]) |
||||
const activeTab = ref('assigned') |
||||
|
||||
// 计算可分配权限 |
||||
const availablePermissions = computed(() => { |
||||
const assignedIds = assignedPermissions.value.map(p => p.id) |
||||
return allPermissions.value.filter(p => !assignedIds.includes(p.id)) |
||||
}) |
||||
|
||||
// 监听visible变化 |
||||
watch(() => props.modelValue, (newVal) => { |
||||
visible.value = newVal |
||||
if (newVal && props.role) { |
||||
currentRole.value = props.role |
||||
loadData() |
||||
} |
||||
}) |
||||
|
||||
// 加载数据 |
||||
const loadData = async () => { |
||||
if (!currentRole.value) return |
||||
|
||||
loading.value = true |
||||
try { |
||||
await Promise.all([ |
||||
loadAllPermissions(), |
||||
loadRolePermissions() |
||||
]) |
||||
} catch (error) { |
||||
console.error('加载权限数据失败:', error) |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
// 加载所有权限 |
||||
const loadAllPermissions = async () => { |
||||
try { |
||||
const response = await roleService.getPermissions() |
||||
if (response.code === 200) { |
||||
allPermissions.value = response.data || [] |
||||
} |
||||
} catch (error) { |
||||
console.error('加载权限列表失败:', error) |
||||
} |
||||
} |
||||
|
||||
// 加载角色权限 |
||||
const loadRolePermissions = async () => { |
||||
try { |
||||
const response = await roleService.getRolePermissions(currentRole.value.id) |
||||
if (response.code === 200) { |
||||
assignedPermissions.value = response.data || [] |
||||
} |
||||
} catch (error) { |
||||
console.error('加载角色权限失败:', error) |
||||
} |
||||
} |
||||
|
||||
// 切换已分配权限选择 |
||||
const toggleAssignedPermission = (permissionId) => { |
||||
const index = selectedAssignedPermissions.value.indexOf(permissionId) |
||||
if (index > -1) { |
||||
selectedAssignedPermissions.value.splice(index, 1) |
||||
} else { |
||||
selectedAssignedPermissions.value.push(permissionId) |
||||
} |
||||
} |
||||
|
||||
// 切换可分配权限选择 |
||||
const toggleAvailablePermission = (permissionId) => { |
||||
const index = selectedAvailablePermissions.value.indexOf(permissionId) |
||||
if (index > -1) { |
||||
selectedAvailablePermissions.value.splice(index, 1) |
||||
} else { |
||||
selectedAvailablePermissions.value.push(permissionId) |
||||
} |
||||
} |
||||
|
||||
// 清除已分配权限选择 |
||||
const clearAssignedSelection = () => { |
||||
selectedAssignedPermissions.value = [] |
||||
} |
||||
|
||||
// 清除可分配权限选择 |
||||
const clearAvailableSelection = () => { |
||||
selectedAvailablePermissions.value = [] |
||||
} |
||||
|
||||
// 分配单个权限 |
||||
const assignPermission = async (permissionId) => { |
||||
try { |
||||
const response = await roleService.assignPermissionsToRole(currentRole.value.id, [permissionId]) |
||||
if (response.code === 200) { |
||||
await loadRolePermissions() |
||||
emit('permissions-updated') |
||||
} |
||||
} catch (error) { |
||||
console.error('分配权限失败:', error) |
||||
} |
||||
} |
||||
|
||||
// 移除单个权限 |
||||
const removePermission = async (permissionId) => { |
||||
try { |
||||
const response = await roleService.removePermissionsFromRole(currentRole.value.id, [permissionId]) |
||||
if (response.code === 200) { |
||||
await loadRolePermissions() |
||||
emit('permissions-updated') |
||||
} |
||||
} catch (error) { |
||||
console.error('移除权限失败:', error) |
||||
} |
||||
} |
||||
|
||||
// 批量分配权限 |
||||
const assignSelectedPermissions = async () => { |
||||
if (selectedAvailablePermissions.value.length === 0) return |
||||
|
||||
try { |
||||
const response = await roleService.assignPermissionsToRole( |
||||
currentRole.value.id, |
||||
selectedAvailablePermissions.value |
||||
) |
||||
if (response.code === 200) { |
||||
selectedAvailablePermissions.value = [] |
||||
await loadRolePermissions() |
||||
emit('permissions-updated') |
||||
} |
||||
} catch (error) { |
||||
console.error('批量分配权限失败:', error) |
||||
} |
||||
} |
||||
|
||||
// 批量移除权限 |
||||
const removeSelectedPermissions = async () => { |
||||
if (selectedAssignedPermissions.value.length === 0) return |
||||
|
||||
try { |
||||
const response = await roleService.removePermissionsFromRole( |
||||
currentRole.value.id, |
||||
selectedAssignedPermissions.value |
||||
) |
||||
if (response.code === 200) { |
||||
selectedAssignedPermissions.value = [] |
||||
await loadRolePermissions() |
||||
emit('permissions-updated') |
||||
} |
||||
} catch (error) { |
||||
console.error('批量移除权限失败:', error) |
||||
} |
||||
} |
||||
|
||||
// 刷新权限 |
||||
const refreshPermissions = async () => { |
||||
await loadData() |
||||
} |
||||
|
||||
// 关闭对话框 |
||||
const handleClose = () => { |
||||
visible.value = false |
||||
emit('update:modelValue', false) |
||||
// 重置状态 |
||||
selectedAssignedPermissions.value = [] |
||||
selectedAvailablePermissions.value = [] |
||||
activeTab.value = 'assigned' |
||||
} |
||||
|
||||
return { |
||||
visible, |
||||
loading, |
||||
currentRole, |
||||
allPermissions, |
||||
assignedPermissions, |
||||
availablePermissions, |
||||
selectedAssignedPermissions, |
||||
selectedAvailablePermissions, |
||||
activeTab, |
||||
toggleAssignedPermission, |
||||
toggleAvailablePermission, |
||||
clearAssignedSelection, |
||||
clearAvailableSelection, |
||||
assignPermission, |
||||
removePermission, |
||||
assignSelectedPermissions, |
||||
removeSelectedPermissions, |
||||
refreshPermissions, |
||||
handleClose |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.role-permission-assignment { |
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
||||
} |
||||
|
||||
.large-modal { |
||||
width: 90%; |
||||
max-width: 1200px; |
||||
height: 80vh; |
||||
max-height: 800px; |
||||
} |
||||
|
||||
.modal-overlay { |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
background: rgba(0, 0, 0, 0.5); |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
z-index: 1000; |
||||
} |
||||
|
||||
.modal { |
||||
background: white; |
||||
border-radius: 8px; |
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
||||
display: flex; |
||||
flex-direction: column; |
||||
max-height: 90vh; |
||||
} |
||||
|
||||
.modal-header { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
padding: 20px 24px; |
||||
border-bottom: 1px solid #e5e7eb; |
||||
background: #f9fafb; |
||||
border-radius: 8px 8px 0 0; |
||||
} |
||||
|
||||
.modal-header h3 { |
||||
margin: 0; |
||||
color: #1f2937; |
||||
font-size: 18px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.close-btn { |
||||
background: none; |
||||
border: none; |
||||
font-size: 18px; |
||||
cursor: pointer; |
||||
color: #6b7280; |
||||
padding: 4px; |
||||
border-radius: 4px; |
||||
transition: all 0.2s; |
||||
} |
||||
|
||||
.close-btn:hover { |
||||
background: #e5e7eb; |
||||
color: #374151; |
||||
} |
||||
|
||||
.modal-body { |
||||
flex: 1; |
||||
padding: 24px; |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
.modal-footer { |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
gap: 12px; |
||||
padding: 20px 24px; |
||||
border-top: 1px solid #e5e7eb; |
||||
background: #f9fafb; |
||||
border-radius: 0 0 8px 8px; |
||||
} |
||||
|
||||
.loading { |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 40px; |
||||
color: #6b7280; |
||||
} |
||||
|
||||
.loading i { |
||||
font-size: 24px; |
||||
margin-bottom: 12px; |
||||
} |
||||
|
||||
.role-info { |
||||
margin-bottom: 24px; |
||||
padding: 16px; |
||||
background: #f8fafc; |
||||
border-radius: 8px; |
||||
border: 1px solid #e2e8f0; |
||||
} |
||||
|
||||
.role-details h4 { |
||||
margin: 0 0 8px 0; |
||||
color: #1e293b; |
||||
font-size: 16px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.role-description { |
||||
margin: 0 0 12px 0; |
||||
color: #64748b; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
.role-stats { |
||||
display: flex; |
||||
gap: 16px; |
||||
} |
||||
|
||||
.stat-item { |
||||
display: flex; |
||||
align-items: center; |
||||
gap: 6px; |
||||
color: #475569; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
.stat-item i { |
||||
color: #3b82f6; |
||||
} |
||||
|
||||
.permission-assignment { |
||||
border: 1px solid #e2e8f0; |
||||
border-radius: 8px; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.assignment-tabs { |
||||
display: flex; |
||||
background: #f1f5f9; |
||||
border-bottom: 1px solid #e2e8f0; |
||||
} |
||||
|
||||
.tab-btn { |
||||
flex: 1; |
||||
padding: 12px 16px; |
||||
background: none; |
||||
border: none; |
||||
cursor: pointer; |
||||
font-size: 14px; |
||||
font-weight: 500; |
||||
color: #64748b; |
||||
transition: all 0.2s; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
gap: 8px; |
||||
} |
||||
|
||||
.tab-btn.active { |
||||
background: white; |
||||
color: #3b82f6; |
||||
border-bottom: 2px solid #3b82f6; |
||||
} |
||||
|
||||
.tab-btn:hover:not(.active) { |
||||
background: #e2e8f0; |
||||
color: #475569; |
||||
} |
||||
|
||||
.permission-list { |
||||
padding: 20px; |
||||
} |
||||
|
||||
.list-header { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
margin-bottom: 16px; |
||||
} |
||||
|
||||
.list-header h5 { |
||||
margin: 0; |
||||
color: #1e293b; |
||||
font-size: 16px; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.list-actions { |
||||
display: flex; |
||||
gap: 8px; |
||||
} |
||||
|
||||
.empty-state { |
||||
text-align: center; |
||||
padding: 40px 20px; |
||||
color: #64748b; |
||||
} |
||||
|
||||
.empty-state i { |
||||
font-size: 48px; |
||||
margin-bottom: 16px; |
||||
opacity: 0.5; |
||||
} |
||||
|
||||
.empty-state p { |
||||
margin: 0; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
.permission-items { |
||||
display: flex; |
||||
flex-direction: column; |
||||
gap: 8px; |
||||
} |
||||
|
||||
.permission-item { |
||||
display: flex; |
||||
align-items: center; |
||||
padding: 12px; |
||||
border: 1px solid #e2e8f0; |
||||
border-radius: 6px; |
||||
cursor: pointer; |
||||
transition: all 0.2s; |
||||
background: white; |
||||
} |
||||
|
||||
.permission-item:hover { |
||||
border-color: #3b82f6; |
||||
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1); |
||||
} |
||||
|
||||
.permission-item.selected { |
||||
border-color: #3b82f6; |
||||
background: #eff6ff; |
||||
} |
||||
|
||||
.permission-checkbox { |
||||
margin-right: 12px; |
||||
} |
||||
|
||||
.permission-checkbox input[type="checkbox"] { |
||||
width: 16px; |
||||
height: 16px; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.permission-info { |
||||
flex: 1; |
||||
} |
||||
|
||||
.permission-name { |
||||
font-weight: 500; |
||||
color: #1e293b; |
||||
margin-bottom: 4px; |
||||
} |
||||
|
||||
.permission-details { |
||||
display: flex; |
||||
gap: 12px; |
||||
margin-bottom: 4px; |
||||
} |
||||
|
||||
.permission-code { |
||||
font-family: 'Courier New', monospace; |
||||
font-size: 12px; |
||||
color: #64748b; |
||||
background: #f1f5f9; |
||||
padding: 2px 6px; |
||||
border-radius: 4px; |
||||
} |
||||
|
||||
.permission-resource { |
||||
font-size: 12px; |
||||
color: #8b5cf6; |
||||
background: #f3f4f6; |
||||
padding: 2px 6px; |
||||
border-radius: 4px; |
||||
} |
||||
|
||||
.permission-description { |
||||
font-size: 12px; |
||||
color: #64748b; |
||||
line-height: 1.4; |
||||
} |
||||
|
||||
.permission-actions { |
||||
margin-left: 12px; |
||||
} |
||||
|
||||
.btn { |
||||
padding: 6px 12px; |
||||
border: none; |
||||
border-radius: 4px; |
||||
cursor: pointer; |
||||
font-size: 12px; |
||||
font-weight: 500; |
||||
transition: all 0.2s; |
||||
display: inline-flex; |
||||
align-items: center; |
||||
gap: 4px; |
||||
} |
||||
|
||||
.btn-sm { |
||||
padding: 4px 8px; |
||||
font-size: 11px; |
||||
} |
||||
|
||||
.btn-primary { |
||||
background: #3b82f6; |
||||
color: white; |
||||
} |
||||
|
||||
.btn-primary:hover:not(:disabled) { |
||||
background: #2563eb; |
||||
} |
||||
|
||||
.btn-primary:disabled { |
||||
background: #9ca3af; |
||||
cursor: not-allowed; |
||||
} |
||||
|
||||
.btn-danger { |
||||
background: #ef4444; |
||||
color: white; |
||||
} |
||||
|
||||
.btn-danger:hover:not(:disabled) { |
||||
background: #dc2626; |
||||
} |
||||
|
||||
.btn-danger:disabled { |
||||
background: #9ca3af; |
||||
cursor: not-allowed; |
||||
} |
||||
|
||||
.btn-secondary { |
||||
background: #6b7280; |
||||
color: white; |
||||
} |
||||
|
||||
.btn-secondary:hover:not(:disabled) { |
||||
background: #4b5563; |
||||
} |
||||
|
||||
.btn-secondary:disabled { |
||||
background: #9ca3af; |
||||
cursor: not-allowed; |
||||
} |
||||
</style> |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
package migration |
||||
|
||||
import ( |
||||
"fmt" |
||||
"gofaster/internal/auth/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
// AddPermissionCodeField 为权限表添加code字段
|
||||
func AddPermissionCodeField(db *gorm.DB) error { |
||||
fmt.Println("🔧 开始为权限表添加code字段...") |
||||
|
||||
// 检查code字段是否已存在
|
||||
var count int64 |
||||
err := db.Raw("SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'permissions' AND column_name = 'code'").Count(&count).Error |
||||
if err != nil { |
||||
return fmt.Errorf("检查code字段失败: %v", err) |
||||
} |
||||
|
||||
if count > 0 { |
||||
fmt.Println("✅ code字段已存在,跳过添加") |
||||
return nil |
||||
} |
||||
|
||||
// 添加code字段
|
||||
err = db.Exec("ALTER TABLE permissions ADD COLUMN code VARCHAR(50)").Error |
||||
if err != nil { |
||||
return fmt.Errorf("添加code字段失败: %v", err) |
||||
} |
||||
|
||||
// 为现有权限记录生成code
|
||||
var permissions []model.Permission |
||||
err = db.Find(&permissions).Error |
||||
if err != nil { |
||||
return fmt.Errorf("查询现有权限失败: %v", err) |
||||
} |
||||
|
||||
fmt.Printf("🔧 为 %d 条现有权限记录生成code...\n", len(permissions)) |
||||
|
||||
for _, permission := range permissions { |
||||
code := fmt.Sprintf("%s:%s", permission.Resource, permission.Action) |
||||
err = db.Model(&permission).Update("code", code).Error |
||||
if err != nil { |
||||
return fmt.Errorf("更新权限 %d 的code失败: %v", permission.ID, err) |
||||
} |
||||
fmt.Printf("✅ 已为权限 '%s' 生成code: %s\n", permission.Name, code) |
||||
} |
||||
|
||||
// 添加唯一索引
|
||||
err = db.Exec("CREATE UNIQUE INDEX idx_permissions_code ON permissions(code)").Error |
||||
if err != nil { |
||||
return fmt.Errorf("创建code唯一索引失败: %v", err) |
||||
} |
||||
|
||||
fmt.Println("✅ 权限表code字段添加完成") |
||||
return nil |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
# 测试权限分配功能脚本 |
||||
Write-Host "🧪 开始测试权限分配功能..." -ForegroundColor Yellow |
||||
|
||||
# 启动后端服务 |
||||
Write-Host "🚀 启动后端服务..." -ForegroundColor Green |
||||
Start-Process -FilePath "powershell" -ArgumentList "-ExecutionPolicy", "Bypass", "-File", "start-backend-only.ps1" -WindowStyle Minimized |
||||
|
||||
# 等待后端启动 |
||||
Write-Host "⏳ 等待后端服务启动..." -ForegroundColor Cyan |
||||
Start-Sleep -Seconds 10 |
||||
|
||||
# 测试权限API |
||||
Write-Host "🔍 测试权限API..." -ForegroundColor Green |
||||
|
||||
# 测试获取权限列表 |
||||
Write-Host "📋 测试获取权限列表..." -ForegroundColor Cyan |
||||
try { |
||||
$permissionsResponse = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/permissions" -Method GET -Headers @{ |
||||
"Authorization" = "Bearer YOUR_TOKEN_HERE" |
||||
} -ErrorAction Stop |
||||
Write-Host "✅ 权限列表API正常" -ForegroundColor Green |
||||
Write-Host "权限数量: $($permissionsResponse.data.Count)" -ForegroundColor Cyan |
||||
} catch { |
||||
Write-Host "❌ 权限列表API测试失败: $($_.Exception.Message)" -ForegroundColor Red |
||||
} |
||||
|
||||
# 测试获取角色列表 |
||||
Write-Host "👥 测试获取角色列表..." -ForegroundColor Cyan |
||||
try { |
||||
$rolesResponse = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/roles" -Method GET -Headers @{ |
||||
"Authorization" = "Bearer YOUR_TOKEN_HERE" |
||||
} -ErrorAction Stop |
||||
Write-Host "✅ 角色列表API正常" -ForegroundColor Green |
||||
Write-Host "角色数量: $($rolesResponse.data.data.Count)" -ForegroundColor Cyan |
||||
} catch { |
||||
Write-Host "❌ 角色列表API测试失败: $($_.Exception.Message)" -ForegroundColor Red |
||||
} |
||||
|
||||
Write-Host "🎉 权限分配功能测试完成!" -ForegroundColor Green |
||||
Write-Host "现在可以启动前端应用进行界面测试:" -ForegroundColor Cyan |
||||
Write-Host "cd app && npm run dev:enhanced" -ForegroundColor Yellow |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
# 测试权限端点 |
||||
Write-Host "🔍 测试权限端点..." -ForegroundColor Yellow |
||||
|
||||
# 启动后端服务 |
||||
Write-Host "🚀 启动后端服务..." -ForegroundColor Green |
||||
Start-Process -FilePath "go" -ArgumentList "run", "main.go" -WorkingDirectory "backend" -WindowStyle Hidden |
||||
|
||||
# 等待服务启动 |
||||
Write-Host "⏳ 等待服务启动..." -ForegroundColor Yellow |
||||
Start-Sleep -Seconds 5 |
||||
|
||||
try { |
||||
# 测试权限端点 |
||||
Write-Host "🔍 测试 /api/auth/permissions 端点..." -ForegroundColor Cyan |
||||
$response = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/permissions" -Method GET -ErrorAction Stop |
||||
Write-Host "✅ 权限端点响应:" -ForegroundColor Green |
||||
$response | ConvertTo-Json -Depth 3 |
||||
} catch { |
||||
Write-Host "❌ 权限端点测试失败:" -ForegroundColor Red |
||||
Write-Host $_.Exception.Message -ForegroundColor Red |
||||
|
||||
# 测试其他端点 |
||||
Write-Host "🔍 测试 /api/auth/roles 端点..." -ForegroundColor Cyan |
||||
try { |
||||
$response = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/roles" -Method GET -ErrorAction Stop |
||||
Write-Host "✅ 角色端点响应:" -ForegroundColor Green |
||||
$response | ConvertTo-Json -Depth 3 |
||||
} catch { |
||||
Write-Host "❌ 角色端点也失败:" -ForegroundColor Red |
||||
Write-Host $_.Exception.Message -ForegroundColor Red |
||||
} |
||||
|
||||
# 测试健康检查端点 |
||||
Write-Host "🔍 测试 /health 端点..." -ForegroundColor Cyan |
||||
try { |
||||
$response = Invoke-RestMethod -Uri "http://localhost:8080/health" -Method GET -ErrorAction Stop |
||||
Write-Host "✅ 健康检查端点响应:" -ForegroundColor Green |
||||
$response | ConvertTo-Json -Depth 3 |
||||
} catch { |
||||
Write-Host "❌ 健康检查端点也失败:" -ForegroundColor Red |
||||
Write-Host $_.Exception.Message -ForegroundColor Red |
||||
} |
||||
} finally { |
||||
# 停止后端服务 |
||||
Write-Host "🛑 停止后端服务..." -ForegroundColor Yellow |
||||
Get-Process -Name "go" -ErrorAction SilentlyContinue | Stop-Process -Force |
||||
} |
||||
|
||||
Write-Host "✅ 测试完成" -ForegroundColor Green |
Loading…
Reference in new issue