Files
ApiServer-Web-admin_dashboa…/src/views/system/PermissionAdmin.vue
T
wlkjyy f7c3be1d30 refactor: extract image form to standalone page and implement tags view store
- Created ImageForm.vue as standalone page for add/edit image functionality
- Removed dialog-based image form from VmImages.vue
- Implemented tagsViewStore for global tab state management
- Added automatic tab closing on form cancel/back
- Fixed data persistence issue when switching between image edits
- Removed quick actions section from ImageForm
- Updated router configuration for new image form route
2025-11-28 14:15:29 +08:00

841 lines
26 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="permission-admin-container">
<!-- 主容器 -->
<el-card class="main-container" shadow="never">
<!-- 搜索和操作栏 -->
<div class="filter-section">
<div class="filter-content">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="类型">
<el-select v-model="queryParams.owner_type" placeholder="请选择类型" clearable style="width: 150px" @change="handleOwnerTypeChange">
<el-option label="用户" value="user" />
<el-option label="组" value="group" />
</el-select>
</el-form-item>
<el-form-item label="用户" v-if="queryParams.owner_type === 'user'">
<div class="user_selector-inline">
<el-tag v-if="queryParams.user_id" type="primary" closable @close="clearQueryUser" style="margin-right: 8px;">
{{ getQueryUserName() }}
</el-tag>
<el-button type="primary" plain @click="openQueryUserSelector" size="default">
<el-icon><User /></el-icon>
{{ queryParams.user_id ? '重新选择' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="管理员组" v-if="queryParams.owner_type === 'group'">
<el-select v-model="queryParams.admin_group_id" placeholder="请选择管理员组" clearable filterable style="width: 200px">
<el-option v-for="item in adminGroupOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>分配权限
</el-button>
<el-button type="success" @click="fetchAdminPermissionList">
<el-icon><Refresh/></el-icon>刷新
</el-button>
</div>
</div>
</div>
<!-- 管理员权限列表 -->
<div class="table-section">
<el-table
v-loading="loading"
:data="adminPermissionList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column label="拥有者类型" width="120">
<template #default="{ row }">
<el-tag :type="row.ownerType === 'user' ? 'primary' : 'success'">
{{ row.ownerType === 'user' ? '用户' : '组' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="拥有者" width="180">
<template #default="{ row }">
<span v-if="row.ownerType === 'user'">用户ID: {{ row.userId }}</span>
<span v-else>管理员组ID: {{ row.groupId }}</span>
</template>
</el-table-column>
<el-table-column prop="permissionId" label="路径权限ID" width="120" />
<el-table-column label="权限路径" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
{{ row.permission?.path || '-' }}
</template>
</el-table-column>
<el-table-column label="权限名称" width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.permission?.name || '-' }}
</template>
</el-table-column>
<el-table-column prop="weight" label="权重" width="100" />
<el-table-column label="权限类型" width="120">
<template #default="{ row }">
<el-tag :type="getPermissionTypeTag(row.permissionType)">
{{ getPermissionTypeText(row.permissionType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="过期时间" width="180" show-overflow-tooltip>
<template #default="{ row }">
{{ formatDate(row.expireAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</div>
</el-card>
<!-- 用户选择弹窗 -->
<el-dialog
v-model="userSelectorVisible"
title="选择用户"
width="800px"
class="user-selector-dialog"
>
<!-- 搜索栏 -->
<div class="selector-search">
<el-input
v-model="userSearchParams.key"
placeholder="搜索用户名或ID"
clearable
@keyup.enter="searchUsers"
style="width: 300px; margin-right: 12px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-button type="primary" @click="searchUsers">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="resetUserSearch">重置</el-button>
</div>
<!-- 用户表格 -->
<el-table
v-loading="userSelectorLoading"
:data="userSelectorList"
highlight-current-row
@current-change="handleUserSelectChange"
style="width: 100%; margin-top: 16px"
:height="400"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column prop="UserName" label="用户名" min-width="150" />
<el-table-column prop="Email" label="邮箱" min-width="180" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.Status === 1 ? 'success' : 'danger'" size="small">
{{ row.Status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="userSearchParams.page"
v-model:page-size="userSearchParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="userSelectorTotal"
@size-change="handleUserSelectorSizeChange"
@current-change="handleUserSelectorPageChange"
background
class="selector-pagination"
/>
<template #footer>
<el-button @click="userSelectorVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUserSelection" :disabled="!selectedUserTemp">
确定选择
</el-button>
</template>
</el-dialog>
<!-- 分配权限对话框 -->
<el-dialog
v-model="dialogVisible"
title="分配管理员权限"
width="700px"
>
<el-form
ref="permissionFormRef"
:model="permissionForm"
:rules="permissionRules"
label-width="140px"
>
<el-form-item label="权限绑定类型" prop="owner_type">
<el-select v-model="permissionForm.owner_type" placeholder="请选择权限绑定类型" style="width: 100%" @change="handleFormOwnerTypeChange" :disabled="permissionForm.id">
<el-option label="用户" value="user" />
<el-option label="组" value="group" />
</el-select>
<div class="form-tip">如果是 user 则填写 user_id如果是 group 则填写 admin_group_id</div>
</el-form-item>
<el-form-item label="用户" prop="user_id" v-if="permissionForm.owner_type === 'user'" >
<div class="user_selector-inline">
<el-tag v-if="permissionForm.user_id" type="primary" closable @close="clearFormUser" style="margin-right: 8px;">
{{ getFormUserName() }}
</el-tag>
<el-button type="primary" plain @click="openFormUserSelector" size="default" :disabled="permissionForm.user_id">
<el-icon><User /></el-icon>
{{ permissionForm.user_id ? '重新选择' : '选择用户' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="管理员组" prop="admin_group_id" v-if="permissionForm.owner_type === 'group'">
<el-select v-model="permissionForm.admin_group_id" placeholder="请选择管理员组" filterable style="width: 100%">
<el-option v-for="item in adminGroupOptions" :key="item.id" :label="`${item.name} (ID: ${item.id})`" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="路径权限" prop="permission_id">
<div style="display: flex; gap: 8px;">
<el-select
v-model="permissionForm.permission_id"
placeholder="请选择路径权限"
filterable
style="flex: 1"
:loading="permissionLoading"
>
<el-option
v-for="item in permissionOptions"
:key="item.id"
:value="item.id"
>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>
<el-tag v-if="item.method" :type="getMethodTag(item.method)" size="small" style="margin-right: 8px;">{{ item.method }}</el-tag>
{{ item.path }}
</span>
<span style="color: #999; font-size: 12px; margin-left: 12px;">{{ item.note || item.name || `ID: ${item.id}` }}</span>
</div>
</el-option>
</el-select>
<el-button @click="fetchPermissionList" :loading="permissionLoading" :icon="Refresh">刷新</el-button>
</div>
<div class="form-tip"> {{ permissionOptions.length }} 个路径权限可选</div>
</el-form-item>
<el-form-item label="权重" prop="weight">
<el-input-number v-model="permissionForm.weight" placeholder="请输入权重" :min="0" style="width: 100%" />
<div class="form-tip">权重默认 10</div>
</el-form-item>
<el-form-item label="权限类型" prop="permission_type">
<el-select v-model="permissionForm.permission_type" placeholder="请选择权限类型" style="width: 100%">
<el-option label="无权限 (none)" :value="0" />
<el-option label="禁止 (prohibit)" :value="1" />
<el-option label="读取 (read)" :value="2" />
<el-option label="写入 (write)" :value="3" />
<el-option label="全部 (all)" :value="4" />
</el-select>
<div class="form-tip">0 none / 1 prohibit / 2 read / 3 write / 4 all</div>
</el-form-item>
<el-form-item label="过期时间" prop="expire_at">
<el-date-picker
v-model="permissionForm.expire_at"
type="datetime"
placeholder="请选择过期时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
<div class="form-tip">权限过期时间 不填写则不会过期</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Refresh, User } from '@element-plus/icons-vue'
import {
getPermissionListByAdmin,
addPermissionAdmin,
updatePermissionAdmin,
deletePermissionAdmin,
getPermissionList
} from '@/api/admin/Permission'
import { getUserList } from '@/api/admin/user'
import { getAdminGroupList } from '@/api/admin/group'
import { formatDate ,timeToTimestamp} from '@/utils/tool'
const selectorType = ref('query')
const userSelectorVisible = ref(false)
const userSelectorList = ref([])
const userSelectorTotal = ref(0)
const userSearchParams = reactive({
key: '',
page: 1,
count: 10
})
const selectedUserTemp = ref(null)
const userSelectorLoading = ref(false)
const UserOptions = ref([])
// 查询参数
const queryParams = reactive({
owner_type: '',
user_id: undefined,
admin_group_id: undefined,
page: 1,
count: 10
})
// 清除查询用户
const clearQueryUser = () => {
queryParams.user_id = undefined
}
// 获取查询用户名称
const getQueryUserName = () => {
const user = UserOptions.value.find(u => u.UserId === queryParams.user_id)
return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${queryParams.user_id}`
}
// 表单:清除用户
const clearFormUser = () => {
permissionForm.user_id = undefined
}
// 表单:获取显示名称
const getFormUserName = () => {
const user = UserOptions.value.find(u => u.UserId === permissionForm.user_id)
return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${permissionForm.user_id}`
}
// 确认用户选择
const confirmUserSelection = () => {
if (!selectedUserTemp.value) {
ElMessage.warning('请选择一个用户')
return
}
if (selectorType.value === 'query') {
// 查询表单选择,仅影响查询条件
queryParams.user_id = selectedUserTemp.value.UserId
} else if (selectorType.value === 'form') {
// 表单选择,仅影响表单
permissionForm.user_id = selectedUserTemp.value.UserId
}
// 将选中的用户添加到 UserOptions 中(如果不存在)
if (!UserOptions.value.find(u => u.UserId === selectedUserTemp.value.UserId)) {
UserOptions.value.push(selectedUserTemp.value)
}
userSelectorVisible.value = false
ElMessage.success('用户选择成功')
}
// 打开查询用户选择器
const openQueryUserSelector = () => {
selectorType.value = 'query'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 打开表单用户选择器
const openFormUserSelector = () => {
selectorType.value = 'form'
userSelectorVisible.value = true
selectedUserTemp.value = null
userSearchParams.key = ''
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择变化
const handleUserSelectChange = (row) => {
selectedUserTemp.value = row
}
// 搜索用户
const searchUsers = () => {
userSearchParams.page = 1
fetchUserSelectorList()
}
// 用户选择器分页
const handleUserSelectorSizeChange = (size) => {
userSearchParams.count = size
fetchUserSelectorList()
}
const handleUserSelectorPageChange = (page) => {
userSearchParams.page = page
fetchUserSelectorList()
}
// 获取用户选择器列表
const fetchUserSelectorList = async () => {
userSelectorLoading.value = true
try {
const res = await getUserList(userSearchParams)
console.log('用户选择器列表:', res.data)
if (res.data.code === 200) {
userSelectorList.value = res.data.data?.data || []
userSelectorTotal.value = res.data.data?.all_count || 0
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
} finally {
userSelectorLoading.value = false
}
}
// 管理员权限表单
const permissionForm = reactive({
id: undefined,
user_id: undefined,
admin_group_id: undefined,
owner_type: '',
permission_id: undefined,
weight: 10,
permission_type: 4,
expire_at: ''
})
const permissionRules = {
owner_type: [
{ required: true, message: '请选择权限的绑定类型', trigger: 'change' }
],
permission_id: [
{ required: true, message: '请输入权限ID', trigger: 'blur' }
],
permission_type: [
{ required: true, message: '请选择权限类型', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const adminPermissionList = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const permissionFormRef = ref(null)
// const userOptions = ref([])
// const UserOptions = ref([])
const adminGroupOptions = ref([])
const permissionOptions = ref([])
const permissionLoading = ref(false)
// 获取方法标签颜色
const getMethodTag = (method) => {
const tagMap = {
'GET': 'success',
'POST': 'primary',
'PUT': 'warning',
'DELETE': 'danger',
'PATCH': 'info'
}
return tagMap[method?.toUpperCase()] || 'info'
}
// 获取权限类型标签颜色
const getPermissionTypeTag = (type) => {
const tagMap = {
0: 'info', // none
1: 'danger', // prohibit
2: 'warning', // read
3: 'primary', // write
4: 'success' // all
}
return tagMap[type] || 'info'
}
// 获取权限类型文本
const getPermissionTypeText = (type) => {
const textMap = {
0: '无权限',
1: '禁止',
2: '读取',
3: '写入',
4: '全部'
}
return textMap[type] || '未知'
}
// 获取管理员权限列表
const fetchAdminPermissionList = async () => {
if (!queryParams.owner_type) {
ElMessage.warning('请先选择类型')
return
}
loading.value = true
try {
const params = { ...queryParams }
// 清除空参数
Object.keys(params).forEach(key => {
if (params[key] === '' || params[key] === null || params[key] === undefined) {
delete params[key]
}
})
const res = await getPermissionListByAdmin(params)
console.log('管理员权限列表数据:', res.data)
if (res.data.code === 200) {
adminPermissionList.value = res.data.data || []
total.value = res.data.data.length || 0
} else {
ElMessage.error(res.data.message || '获取管理员权限列表失败')
}
} catch (error) {
console.error('获取管理员权限列表失败:', error)
ElMessage.error('获取管理员权限列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchAdminPermissionList()
}
// 重置查询
const resetQuery = () => {
queryParams.user_id = undefined
queryParams.admin_group_id = undefined
queryParams.owner_type = ''
queryParams.page = 1
adminPermissionList.value = []
total.value = 0
}
// 类型变化时清空关联字段
const handleOwnerTypeChange = () => {
if (queryParams.owner_type === 'user') {
queryParams.admin_group_id = undefined
} else if (queryParams.owner_type === 'group') {
queryParams.user_id = undefined
}
}
// 表单类型变化时清空关联字段
const handleFormOwnerTypeChange = () => {
if (permissionForm.owner_type === 'user') {
permissionForm.admin_group_id = undefined
} else if (permissionForm.owner_type === 'group') {
permissionForm.user_id = undefined
}
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchAdminPermissionList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchAdminPermissionList()
}
// 分配权限
const handleAdd = () => {
dialogVisible.value = true
Object.assign(permissionForm, {
id: undefined,
user_id: undefined,
admin_group_id: undefined,
owner_type: '',
permission_id: undefined,
weight: 10,
permission_type: 4,
expire_at: ''
})
permissionFormRef.value?.resetFields()
}
// 编辑权限
const handleEdit = (row) => {
// 处理过期时间
let expireTime = ''
if (row.expireAt && row.expireAt !== '0001-01-01T00:00:00Z' && row.expireAt !== null) {
const date = new Date(row.expireAt)
if (!isNaN(date.getTime())) {
expireTime = date.toISOString().slice(0, 19).replace('T', ' ')
}
}
Object.assign(permissionForm, {
id: row.id,
user_id: row.userId,
admin_group_id: row.groupId,
owner_type: row.ownerType,
permission_id: row.permissionId,
weight: row.weight,
permission_type: row.permissionType,
expire_at: expireTime
})
dialogVisible.value = true
}
// 删除权限
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除该权限吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deletePermissionAdmin({ id: row.id })
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchAdminPermissionList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
permissionFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
owner_type: permissionForm.owner_type,
permission_id: Number(permissionForm.permission_id),
weight: Number(permissionForm.weight),
permission_type: Number(permissionForm.permission_type)
}
// 根据 owner_type 添加对应的 ID
if (permissionForm.owner_type === 'user') {
submitData.user_id = Number(permissionForm.user_id)
} else if (permissionForm.owner_type === 'group') {
submitData.admin_group_id = Number(permissionForm.admin_group_id)
}
// 将过期时间转换为时间戳(秒)
if (permissionForm.expire_at) {
submitData.expire_at = timeToTimestamp(permissionForm.expire_at)
}
// 如果是编辑,添加ID
if (permissionForm.id) {
submitData.id = permissionForm.id
}
console.log('提交管理员权限数据:', submitData)
let res
if (permissionForm.id) {
res = await updatePermissionAdmin(submitData)
} else {
res = await addPermissionAdmin(submitData)
}
if (res.data.code === 200) {
ElMessage.success(permissionForm.id ? '修改成功' : '分配成功')
dialogVisible.value = false
fetchAdminPermissionList()
} else {
ElMessage.error(res.data.message || '操作失败')
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(error.response?.data?.message || '操作失败')
}
}
})
}
// 获取用户列表
const fetchUserList = async () => {
try {
const res = await getUserList({
page: 1,
count: 10000,
key: ''
})
if (res.data.code === 200) {
UserOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
// 获取管理员组列表
const fetchAdminGroupList = async () => {
try {
const res = await getAdminGroupList({
page: 1,
count: 1000
})
if (res.data.code === 200) {
adminGroupOptions.value = res.data.data?.data || []
}
} catch (error) {
console.error('获取管理员组列表失败:', error)
}
}
// 获取路径权限列表
const fetchPermissionList = async () => {
permissionLoading.value = true
try {
const res = await getPermissionList({
page: 1,
count: 10000
})
if (res.data.code === 200) {
permissionOptions.value = res.data.data?.list || []
console.log('路径权限列表加载成功,共', permissionOptions.value.length, '条')
if (dialogVisible.value) {
ElMessage.success(`已加载 ${permissionOptions.value.length} 个路径权限`)
}
} else {
ElMessage.error(res.data.message || '获取路径权限列表失败')
}
} catch (error) {
console.error('获取路径权限列表失败:', error)
ElMessage.error('获取路径权限列表失败')
} finally {
permissionLoading.value = false
}
}
// 初始化
onMounted(() => {
fetchUserList()
fetchAdminGroupList()
fetchPermissionList()
})
</script>
<style scoped>
.permission-admin-container {
padding: 0;
}
.main-container {
border: 1px solid #e1e8ed;
background: #ffffff;
}
.filter-section {
padding: 0;
border-bottom: 1px solid #e1e8ed;
background: #fafbfc;
}
.filter-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
gap: 20px;
flex-wrap: wrap;
}
.search-form {
margin: 0;
flex: 1;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.search-form :deep(.el-form-item) {
margin-bottom: 0;
margin-right: 12px;
}
.action-bar {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.table-section {
padding: 0;
}
.pagination {
margin-top: 20px;
padding: 16px 20px;
border-top: 1px solid #e1e8ed;
background: #fafbfc;
justify-content: flex-end;
}
.form-tip {
font-size: 12px;
color: #999;
margin-top: 4px;
line-height: 1.5;
}
/* 表格样式优化 */
:deep(.el-table) {
border: none;
color: #2c3e50;
}
:deep(.el-table__header) {
background: #f8f9fa;
}
:deep(.el-table th) {
background: #f8f9fa !important;
border-bottom: 2px solid #e1e8ed;
color: #2c3e50;
font-weight: 600;
font-size: 13px;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
color: #34495e;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 0;
}
</style>