Compare commits

...

3 Commits

Author SHA1 Message Date
5ae5353baa Merge pull request 'main_beta' (#4) from main_beta into main
Reviewed-on: #4
2026-02-11 10:04:32 +08:00
gesilong
1cdabba4da commit:更换首页获取应用接口 2026-02-10 16:44:58 +08:00
gesilong
d7def83ee4 commit:应用权限功能 2026-02-10 16:28:33 +08:00
4 changed files with 269 additions and 4 deletions

View File

@@ -17,6 +17,21 @@ export interface UpdateStatusReqVO {
status: number status: number
} }
// 获取登陆用户的应用
export const getMyPage = async (params: PageParam) => {
return await request.get({ url: '/system/oauth2-client/myPage', params })
}
// 获取角色下的应用id
export const getRoleAppIds = async (roleId: number) => {
return await request.get({ url: '/system/permission/list-role-clients?roleId=' + roleId })
}
// 新增角色
export const saveApp = async (data: any) => {
return await request.post({ url: '/system/permission/assign-role-client', data })
}
// 查询角色列表 // 查询角色列表
export const getRolePage = async (params: PageParam) => { export const getRolePage = async (params: PageParam) => {
return await request.get({ url: '/system/role/page', params }) return await request.get({ url: '/system/role/page', params })

View File

@@ -40,7 +40,7 @@ v-for="(category, categoryName) in filteredCategories"
<div class="apps-grid"> <div class="apps-grid">
<div <div
v-for="app in category.apps" v-for="app in category.apps"
:key="app.id" :key="app.id"
class="app-card" class="app-card"
@click="handleAppClick(app)"> @click="handleAppClick(app)">
@@ -77,7 +77,7 @@ v-for="app in category.apps"
</template> </template>
<script> <script>
import * as ClientApi from '@/api/system/oauth2/client' import * as RoleApi from '@/api/system/role'
import { DICT_TYPE, getDictLabel } from '@/utils/dict' import { DICT_TYPE, getDictLabel } from '@/utils/dict'
export default { export default {
name: 'AppManager', name: 'AppManager',
@@ -138,7 +138,7 @@ export default {
methods: { methods: {
async fetchApps() { async fetchApps() {
try { try {
const data = await ClientApi.getOAuth2ClientPage({ pageNo: 1, pageSize: 1000 }) const data = await RoleApi.getMyPage({ pageNo: 1, pageSize: 1000 })
const list = data?.list || [] const list = data?.list || []
this.appsData = list.map(item => ({ this.appsData = list.map(item => ({
id: item.id, id: item.id,

View File

@@ -0,0 +1,233 @@
<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>

View File

@@ -58,6 +58,15 @@
<span>菜单权限</span> <span>菜单权限</span>
</div> </div>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item
:command="{ type: 'app', row }"
v-if="checkPermi(['system:permission:assign-role-app'])"
>
<div class="flex items-center">
<Icon icon="ep:menu" />
<span>应用权限</span>
</div>
</el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:command="{ type: 'data', row }" :command="{ type: 'data', row }"
v-if="checkPermi(['system:permission:assign-role-data-scope'])" v-if="checkPermi(['system:permission:assign-role-data-scope'])"
@@ -86,6 +95,8 @@
<RoleAssignMenuForm ref="assignMenuFormRef" @success="getTableData" /> <RoleAssignMenuForm ref="assignMenuFormRef" @success="getTableData" />
<!-- 表单弹窗数据权限 --> <!-- 表单弹窗数据权限 -->
<RoleDataPermissionForm ref="dataPermissionFormRef" @success="getTableData" /> <RoleDataPermissionForm ref="dataPermissionFormRef" @success="getTableData" />
<!-- 表单弹窗应用权限 -->
<RoleAssignAppForm ref="assignAppFormRef" @success="getTableData" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
@@ -96,6 +107,7 @@ import * as RoleApi from '@/api/system/role'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import RoleAssignMenuForm from './RoleAssignMenuForm.vue' import RoleAssignMenuForm from './RoleAssignMenuForm.vue'
import RoleDataPermissionForm from './RoleDataPermissionForm.vue' import RoleDataPermissionForm from './RoleDataPermissionForm.vue'
import RoleAssignAppForm from './RoleAssignAppForm.vue'
defineOptions({ name: 'SystemRole' }) defineOptions({ name: 'SystemRole' })
@@ -186,7 +198,7 @@ const tablePage = ref({
total: 0 total: 0
}) })
const permission = getCurrPermi(['system:role']) const permission = getCurrPermi(['system:role'])
const assignAppFormRef = ref()
const crudRef = ref() const crudRef = ref()
useCrudHeight(crudRef) useCrudHeight(crudRef)
@@ -197,6 +209,11 @@ const menuHandle = ({ row, type }) => {
if (type == 'menu') openAssignMenuForm(row) if (type == 'menu') openAssignMenuForm(row)
else if (type == 'data') openDataPermissionForm(row) else if (type == 'data') openDataPermissionForm(row)
else if (type == 'del') rowDel(row) else if (type == 'del') rowDel(row)
else if (type == 'app') openAssignAppForm(row)
}
const openAssignAppForm = (row) => {
assignAppFormRef.value.open(row)
} }
/** 查询列表 */ /** 查询列表 */