Files
gr_report_web/src/views/system/role/RoleAssignAppForm.vue
2026-02-10 16:28:33 +08:00

233 lines
6.3 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>
<DesignPopup v-model="dialogVisible" title="应用权限" :is-footer="true" width="40%">
<div class="p-20px">
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
<el-form-item label="角色名称">
<el-tag>{{ formData.name }}</el-tag>
</el-form-item>
<el-form-item label="角色标识">
<el-tag>{{ formData.code }}</el-tag>
</el-form-item>
<el-form-item label="应用权限">
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
<template #header>
全选/全不选:
<el-switch
v-model="treeNodeAll"
active-text=""
inactive-text=""
inline-prompt
@change="handleCheckedTreeNodeAll"
/>
</template>
<!-- 修复移除未使用的node变量优化无子集Tree展示 -->
<el-tree
ref="treeRef"
:data="appOptions"
:props="{ label: 'name', children: 'children' }"
empty-text="暂无应用数据"
node-key="id"
show-checkbox
check-strictly
v-loading="tableLoading"
class="app-tree-list"
:indent="0"
>
<!-- 修复只解构使用到的data删除未使用的node -->
<template #default="{ data }">
<div class="app-tree-node">
<span class="node-name">{{ data.name }}</span>
<span class="node-clientId">客户端ID{{ data.clientId }}</span>
<span class="node-status">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="data.status" />
</span>
</div>
</template>
</el-tree>
</el-card>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</DesignPopup>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import * as RoleApi from '@/api/system/role'
defineOptions({ name: 'SystemRoleAssignAppForm' })
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const formLoading = ref(false)
const tableLoading = ref(false)
const formData = ref({
id: 0,
name: '',
code: '',
clientIds: [] as number[]
})
const formRef = ref()
const treeRef = ref()
const appOptions = ref<any[]>([])
const treeNodeAll = ref(false)
// 优化Tree配置明确指定无children纯列表展示
const treeProps = {
label: 'name',
children: () => [] // 强制返回空数组,彻底避免树形层级
}
/** 打开弹窗 */
const open = async (row: RoleApi.RoleVO) => {
dialogVisible.value = true
formLoading.value = true
resetForm()
// 设置角色信息
formData.value.id = row.id
formData.value.name = row.name
formData.value.code = row.code
try {
// 加载应用列表pageSize=99模拟不分页
await getAppList()
// 获取当前角色已有的应用ID
const roleAppIds = await RoleApi.getRoleAppIds(row.id)
formData.value.clientIds = roleAppIds
// 设置Tree选中状态
await nextTick()
treeRef.value?.setCheckedNodes([])
formData.value.clientIds.forEach(id => {
treeRef.value?.setChecked(id, true, false)
})
} finally {
formLoading.value = false
}
}
defineExpose({ open })
/** 获取应用列表固定pageSize=99不分页 */
const getAppList = async () => {
tableLoading.value = true
try {
const data = await RoleApi.getMyPage({
pageNo: 1,
pageSize: 99 // 设为99覆盖大部分场景的数量模拟不分页
})
// 优化确保返回数据绝对没有children字段避免树形展示
appOptions.value = data.list.map(item => {
const { children, ...rest } = item // 移除可能存在的children字段
return { ...rest, children: [] } // 强制设置空children
})
} finally {
tableLoading.value = false
}
}
/** 全选/全不选适配Tree组件 */
const handleCheckedTreeNodeAll = () => {
if (treeNodeAll.value) {
// 全选:选中所有节点
treeRef.value?.setCheckedNodes(appOptions.value)
formData.value.clientIds = appOptions.value.map(item => item.id)
} else {
// 全不选:清空选中
treeRef.value?.setCheckedNodes([])
formData.value.clientIds = []
}
}
/** 提交表单 */
const emit = defineEmits(['success'])
const submitForm = async () => {
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
formLoading.value = true
try {
// 获取Tree选中的节点ID
formData.value.clientIds = treeRef.value?.getCheckedKeys(false) as number[]
const data = {
roleId: formData.value.id,
clientIds: formData.value.clientIds
}
await RoleApi.saveApp(data)
message.success(t('common.updateSuccess'))
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
treeNodeAll.value = false
formData.value = {
id: 0,
name: '',
code: '',
clientIds: []
}
appOptions.value = []
treeRef.value?.setCheckedNodes([])
formRef.value?.resetFields()
}
</script>
<style lang="scss" scoped>
// 自定义Tree节点样式模拟表格列布局
.app-tree-list {
::v-deep(.el-tree-node) {
height: 40px;
line-height: 40px;
border-bottom: 1px solid #f5f7fa;
}
::v-deep(.el-tree-node__content) {
padding: 0 10px;
height: 40px !important;
// 移除树形节点的默认图标
.el-tree-node__expand-icon {
display: none;
}
}
::v-deep(.el-tree-node__children) {
padding-left: 0 !important; // 彻底去掉子节点缩进
}
}
// 节点内容布局
.app-tree-node {
display: flex;
align-items: center;
width: 100%;
.node-name {
flex: 0 0 120px;
font-weight: 500;
}
.node-clientId {
flex: 0 0 150px;
color: #666;
}
.node-status {
flex: 0 0 80px;
text-align: center;
}
}
// 卡片样式优化
::v-deep(.el-card__header) {
padding: 10px 20px;
}
::v-deep(.el-card__body) {
padding: 10px;
}
</style>