审计日志修改完善
This commit is contained in:
@ -46,4 +46,14 @@ def init_extensions(app):
|
|||||||
redis_client.ping()
|
redis_client.ping()
|
||||||
app.logger.info("✅ Redis connected successfully")
|
app.logger.info("✅ Redis connected successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.warning(f"⚠️ Redis connection failed: {e}, single-device login will be disabled")
|
app.logger.warning(f"⚠️ Redis connection failed: {e}, single-device login will be disabled")
|
||||||
|
|
||||||
|
# ★ 注册 SQLAlchemy 审计监听器
|
||||||
|
# 必须在 db.init_app 之后调用,确保所有模型已映射
|
||||||
|
try:
|
||||||
|
from app.core.audit_listener import register_audit_listeners
|
||||||
|
with app.app_context():
|
||||||
|
count = register_audit_listeners(db)
|
||||||
|
app.logger.info(f"✅ 审计监听器注册成功,共绑定 {count} 个模型")
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error(f"⚠️ 审计监听器注册失败: {e}")
|
||||||
@ -62,6 +62,28 @@
|
|||||||
<el-tag type="warning" effect="plain" round>当前配置: {{ getRoleLabel(currentRole) }}</el-tag>
|
<el-tag type="warning" effect="plain" round>当前配置: {{ getRoleLabel(currentRole) }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增:拉取其他角色权限的操作区 -->
|
||||||
|
<div class="pull-permission-bar">
|
||||||
|
<span class="pull-label">从其他角色复制权限:</span>
|
||||||
|
<el-select
|
||||||
|
v-model="sourceRoleForClone"
|
||||||
|
placeholder="选择源角色..."
|
||||||
|
clearable
|
||||||
|
size="default"
|
||||||
|
@change="handleClonePermissions"
|
||||||
|
class="clone-select"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="role in availableSourceRoles"
|
||||||
|
:key="role.value"
|
||||||
|
:label="role.label"
|
||||||
|
:value="role.value"
|
||||||
|
:disabled="role.value === currentRole"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<span class="pull-hint" v-if="!sourceRoleForClone">选择后将覆盖当前未保存的勾选状态</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
@ -78,20 +100,25 @@
|
|||||||
|
|
||||||
<el-table-column label="访问权限" width="150" align="center">
|
<el-table-column label="访问权限" width="150" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<!-- 父级目录隐藏复选框,仅叶子节点可操作 -->
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
|
v-if="row.type !== 'menu' || !row.children?.length"
|
||||||
v-model="row.hasRead"
|
v-model="row.hasRead"
|
||||||
@change="(val) => handleReadChange(val, row)"
|
@change="(val) => handleReadChange(val, row)"
|
||||||
class="custom-checkbox"
|
class="custom-checkbox"
|
||||||
>
|
>
|
||||||
<span :class="{ 'text-active': row.hasRead }">可见 (Read)</span>
|
<span :class="{ 'text-active': row.hasRead }">可见 (Read)</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
|
<span v-else class="text-gray">-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="操作权限" width="180" align="center">
|
<el-table-column label="操作权限" width="180" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div v-if="row.operationCode">
|
<!-- 父级目录隐藏操作权限列 -->
|
||||||
|
<div v-if="row.type !== 'menu' || !row.children?.length">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
|
v-if="row.operationCode"
|
||||||
v-model="row.hasWrite"
|
v-model="row.hasWrite"
|
||||||
:disabled="!row.hasRead"
|
:disabled="!row.hasRead"
|
||||||
@change="(val) => handleWriteChange(val, row)"
|
@change="(val) => handleWriteChange(val, row)"
|
||||||
@ -99,6 +126,7 @@
|
|||||||
>
|
>
|
||||||
<span :class="{ 'text-active': row.hasWrite, 'text-disabled': !row.hasRead }">可编辑 (Write)</span>
|
<span :class="{ 'text-active': row.hasWrite, 'text-disabled': !row.hasRead }">可编辑 (Write)</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
|
<span v-else class="text-gray">-</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-else class="text-gray">-</span>
|
<span v-else class="text-gray">-</span>
|
||||||
</template>
|
</template>
|
||||||
@ -153,8 +181,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { User, UserFilled, ArrowRight, Setting, Check, Avatar } from '@element-plus/icons-vue'
|
import { User, UserFilled, ArrowRight, Setting, Check, Avatar } from '@element-plus/icons-vue'
|
||||||
import { getAllPermissionTree, getRolePermissions, saveRolePermissions } from '@/api/system/permission'
|
import { getAllPermissionTree, getRolePermissions, saveRolePermissions } from '@/api/system/permission'
|
||||||
|
|
||||||
@ -180,6 +208,7 @@ interface PermissionNode {
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const currentRole = ref('')
|
const currentRole = ref('')
|
||||||
|
const sourceRoleForClone = ref('') // 用于克隆权限的源角色
|
||||||
const roleList = [
|
const roleList = [
|
||||||
{ label: '超级管理员', value: 'SUPER_ADMIN' },
|
{ label: '超级管理员', value: 'SUPER_ADMIN' },
|
||||||
{ label: '主管', value: 'SUPERVISOR' },
|
{ label: '主管', value: 'SUPERVISOR' },
|
||||||
@ -269,6 +298,8 @@ const transformData = (nodes: any[]): PermissionNode[] => {
|
|||||||
// 2. 切换角色:回显权限
|
// 2. 切换角色:回显权限
|
||||||
const handleRoleSelect = async (roleCode: string) => {
|
const handleRoleSelect = async (roleCode: string) => {
|
||||||
currentRole.value = roleCode
|
currentRole.value = roleCode
|
||||||
|
// 切换角色时清空克隆选择
|
||||||
|
sourceRoleForClone.value = ''
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -286,6 +317,53 @@ const handleRoleSelect = async (roleCode: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 可用的源角色列表(排除当前已选角色)
|
||||||
|
const availableSourceRoles = computed(() => {
|
||||||
|
return roleList.filter(r => r.value !== currentRole.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ★ 新增:从其他角色拉取权限
|
||||||
|
const handleClonePermissions = async (sourceRole: string) => {
|
||||||
|
if (!sourceRole) {
|
||||||
|
sourceRoleForClone.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认提示
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`将从角色【${getRoleLabel(sourceRole)}】复制权限到【${getRoleLabel(currentRole.value)}】,覆盖当前未保存的勾选状态,是否继续?`,
|
||||||
|
'权限拉取确认',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确认拉取',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// 用户取消
|
||||||
|
sourceRoleForClone.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 调用后端 API 获取源角色的权限
|
||||||
|
const res: any = await getRolePermissions(sourceRole)
|
||||||
|
if (res.code === 200) {
|
||||||
|
const perms = new Set([...(res.data.menus || []), ...(res.data.elements || [])])
|
||||||
|
// 递归设置表格每一行的状态
|
||||||
|
setRowStatus(tableData.value, perms)
|
||||||
|
ElMessage.success(`已从【${getRoleLabel(sourceRole)}】拉取权限,请修改后点击保存`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('拉取权限失败')
|
||||||
|
} finally {
|
||||||
|
sourceRoleForClone.value = '' // 清空选择器
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 递归回显状态
|
// 递归回显状态
|
||||||
const setRowStatus = (rows: PermissionNode[], perms: Set<any>) => {
|
const setRowStatus = (rows: PermissionNode[], perms: Set<any>) => {
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
@ -599,6 +677,33 @@ onMounted(() => {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 新增:拉取权限操作条 */
|
||||||
|
.pull-permission-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
background: #f0f7ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #d9ecff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #303133;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clone-select {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
/* 表格内样式 */
|
/* 表格内样式 */
|
||||||
.custom-checkbox {
|
.custom-checkbox {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user