Compare commits
3 Commits
bcac3326a3
...
main_beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cdabba4da | ||
|
|
d7def83ee4 | ||
|
|
34f7c68041 |
@@ -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 })
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ export const getDictLabel = (dictType: string, value: any): string => {
|
|||||||
export enum DICT_TYPE {
|
export enum DICT_TYPE {
|
||||||
USER_TYPE = 'user_type',
|
USER_TYPE = 'user_type',
|
||||||
COMMON_STATUS = 'common_status',
|
COMMON_STATUS = 'common_status',
|
||||||
|
APP_CATEGORY = 'app_category',
|
||||||
|
APP_QX = 'app_qx',
|
||||||
TERMINAL = 'terminal', // 终端
|
TERMINAL = 'terminal', // 终端
|
||||||
|
|
||||||
// ========== SYSTEM 模块 ==========
|
// ========== SYSTEM 模块 ==========
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ 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)">
|
||||||
<div class="app-icon-wrapper">
|
<div class="app-icon-wrapper">
|
||||||
<i :class="app.iconClass" class="app-icon"></i>
|
<img :src="app.logo" class="app-icon-img" />
|
||||||
</div>
|
</div>
|
||||||
<div class="app-name">{{ app.name }}</div>
|
<div class="app-name">{{ app.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +60,7 @@ v-for="app in category.apps"
|
|||||||
custom-class="app-dialog">
|
custom-class="app-dialog">
|
||||||
<div v-if="selectedApp" class="dialog-content">
|
<div v-if="selectedApp" class="dialog-content">
|
||||||
<div class="dialog-icon-wrapper">
|
<div class="dialog-icon-wrapper">
|
||||||
<i :class="selectedApp.iconClass" class="dialog-app-icon"></i>
|
<img :src="selectedApp.logo" class="dialog-app-icon-img" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="dialog-app-name">{{ selectedApp.name }}</h3>
|
<h3 class="dialog-app-name">{{ selectedApp.name }}</h3>
|
||||||
<p class="dialog-app-id">ID: {{ selectedApp.id }}</p>
|
<p class="dialog-app-id">ID: {{ selectedApp.id }}</p>
|
||||||
@@ -77,6 +77,8 @@ v-for="app in category.apps"
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import * as RoleApi from '@/api/system/role'
|
||||||
|
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||||
export default {
|
export default {
|
||||||
name: 'AppManager',
|
name: 'AppManager',
|
||||||
data() {
|
data() {
|
||||||
@@ -84,42 +86,22 @@ export default {
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
selectedApp: null,
|
selectedApp: null,
|
||||||
appsData: {
|
appsData: []
|
||||||
统计报表: [
|
|
||||||
{id: 'office1', name: '文档编辑器', iconClass: 'el-icon-document'},
|
|
||||||
{id: 'office2', name: '电子表格', iconClass: 'el-icon-tickets'},
|
|
||||||
{id: 'office3', name: '演示文稿', iconClass: 'el-icon-present'},
|
|
||||||
{id: 'office4', name: 'PDF阅读器', iconClass: 'el-icon-notebook-2'}
|
|
||||||
],
|
|
||||||
工业互联网: [
|
|
||||||
{id: 'dev1', name: 'IDE Pro', iconClass: 'el-icon-setting'},
|
|
||||||
{id: 'dev2', name: 'Git 工具箱', iconClass: 'el-icon-share'},
|
|
||||||
{id: 'dev3', name: 'API测试工具', iconClass: 'el-icon-connection'},
|
|
||||||
{id: 'dev4', name: '数据库管理', iconClass: 'el-icon-data-line'}
|
|
||||||
],
|
|
||||||
数据中心: [
|
|
||||||
{id: 'fun1', name: '音乐播放器', iconClass: 'el-icon-headset'},
|
|
||||||
{id: 'fun2', name: '视频观看', iconClass: 'el-icon-video-play'},
|
|
||||||
{id: 'fun3', name: '游戏中心', iconClass: 'el-icon-game'},
|
|
||||||
{id: 'fun4', name: '阅读器', iconClass: 'el-icon-reading'}
|
|
||||||
],
|
|
||||||
系统工具: [
|
|
||||||
{id: 'sys1', name: '系统监控', iconClass: 'el-icon-monitor'},
|
|
||||||
{id: 'sys2', name: '磁盘清理', iconClass: 'el-icon-delete-solid'},
|
|
||||||
{id: 'sys3', name: '网络诊断', iconClass: 'el-icon-mobile-phone'},
|
|
||||||
{id: 'sys4', name: '安全防护', iconClass: 'el-icon-lock'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchApps()
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
categorizedApps() {
|
categorizedApps() {
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const [category, apps] of Object.entries(this.appsData)) {
|
this.appsData.forEach(app => {
|
||||||
result[category] = {
|
const categoryLabel = app.categoryLabel || '未分类'
|
||||||
apps: apps.map(app => ({...app, category}))
|
if (!result[categoryLabel]) {
|
||||||
};
|
result[categoryLabel] = { apps: [] }
|
||||||
}
|
}
|
||||||
|
result[categoryLabel].apps.push(app)
|
||||||
|
})
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
filteredCategories() {
|
filteredCategories() {
|
||||||
@@ -133,7 +115,7 @@ export default {
|
|||||||
for (const [categoryName, categoryData] of Object.entries(this.categorizedApps)) {
|
for (const [categoryName, categoryData] of Object.entries(this.categorizedApps)) {
|
||||||
const filteredApps = categoryData.apps.filter(app =>
|
const filteredApps = categoryData.apps.filter(app =>
|
||||||
app.name.toLowerCase().includes(query) ||
|
app.name.toLowerCase().includes(query) ||
|
||||||
app.category.toLowerCase().includes(query)
|
String(app.categoryLabel || '').toLowerCase().includes(query)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (filteredApps.length > 0) {
|
if (filteredApps.length > 0) {
|
||||||
@@ -144,19 +126,39 @@ export default {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
totalApps() {
|
totalApps() {
|
||||||
return Object.values(this.appsData).reduce((total, apps) => total + apps.length, 0);
|
return this.appsData.length;
|
||||||
},
|
},
|
||||||
categoriesCount() {
|
categoriesCount() {
|
||||||
return Object.keys(this.appsData).length;
|
return Object.keys(this.categorizedApps).length;
|
||||||
},
|
},
|
||||||
visibleCategories() {
|
visibleCategories() {
|
||||||
return Object.keys(this.filteredCategories).length;
|
return Object.keys(this.filteredCategories).length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async fetchApps() {
|
||||||
|
try {
|
||||||
|
const data = await RoleApi.getMyPage({ pageNo: 1, pageSize: 1000 })
|
||||||
|
const list = data?.list || []
|
||||||
|
this.appsData = list.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
logo: item.logo,
|
||||||
|
category: item.category,
|
||||||
|
categoryLabel: getDictLabel(DICT_TYPE.APP_CATEGORY, item.category),
|
||||||
|
redirectUris: item.redirectUris
|
||||||
|
}))
|
||||||
|
} catch (e) {
|
||||||
|
this.$message.error('应用数据加载失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
handleAppClick(app) {
|
handleAppClick(app) {
|
||||||
this.selectedApp = app;
|
const uri = Array.isArray(app.redirectUris) ? app.redirectUris[0] : app.redirectUris
|
||||||
this.dialogVisible = true;
|
if (uri) {
|
||||||
|
window.location.href = uri
|
||||||
|
} else {
|
||||||
|
this.$message.warning('未配置重定向地址')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
confirmLaunch() {
|
confirmLaunch() {
|
||||||
this.$message.success(`正在启动 ${this.selectedApp.name}...`);
|
this.$message.success(`正在启动 ${this.selectedApp.name}...`);
|
||||||
@@ -303,12 +305,18 @@ export default {
|
|||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
line-height: 60px;
|
line-height: 60px;
|
||||||
color: white;
|
color: white;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-icon-img {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.app-name {
|
.app-name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -334,12 +342,18 @@ export default {
|
|||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
line-height: 80px;
|
line-height: 80px;
|
||||||
color: white;
|
color: white;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-app-icon-img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-app-name {
|
.dialog-app-name {
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
|||||||
@@ -32,7 +32,46 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
<!-- 表单 -->
|
<!-- 表单 -->
|
||||||
<template #scopes-form="scope">
|
<template #category-form>
|
||||||
|
<el-select
|
||||||
|
v-model="tableForm.category"
|
||||||
|
filterable
|
||||||
|
placeholder="请输入应用分类"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in getIntDictOptions(DICT_TYPE.APP_CATEGORY)"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template #authorizedGrantTypes-form>
|
||||||
|
<el-select
|
||||||
|
v-model="tableForm.authorizedGrantTypes"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
|
allow-create
|
||||||
|
placeholder="请输入授权类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in getDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template #authorities-form>
|
||||||
|
<el-select
|
||||||
|
v-model="tableForm.authorities"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
|
allow-create
|
||||||
|
placeholder="请输入权限"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in getDictOptions(DICT_TYPE.APP_QX)" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template #scopes-form>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="tableForm.scopes"
|
v-model="tableForm.scopes"
|
||||||
filterable
|
filterable
|
||||||
@@ -41,20 +80,21 @@
|
|||||||
placeholder="请输入授权范围"
|
placeholder="请输入授权范围"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option v-for="item in scope.value" :key="item" :label="item" :value="item" />
|
<el-option v-for="item in [{
|
||||||
|
label: 'user.read',
|
||||||
|
value: 'user.read'
|
||||||
|
},{
|
||||||
|
label: 'user.write',
|
||||||
|
value: 'user.write'
|
||||||
|
}]" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
<template #redirectUris-form="scope">
|
<template #category="scope">
|
||||||
<el-select
|
<dict-tag
|
||||||
v-model="tableForm.redirectUris"
|
v-if="scope.row.category !== undefined"
|
||||||
filterable
|
:type="DICT_TYPE.APP_CATEGORY"
|
||||||
multiple
|
:value="scope.row.category"
|
||||||
allow-create
|
/>
|
||||||
placeholder="请输入可重定向的 URI 地址"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option v-for="item in scope.value" :key="item" :label="item" :value="item" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
</template>
|
||||||
<template #status="scope">
|
<template #status="scope">
|
||||||
<dict-tag
|
<dict-tag
|
||||||
@@ -105,9 +145,14 @@ const tableOption = reactive({
|
|||||||
name: {
|
name: {
|
||||||
label: '应用名',
|
label: '应用名',
|
||||||
search: true,
|
search: true,
|
||||||
minWidth: 100,
|
span: 12,
|
||||||
rules: [{ required: true, message: '应用名不能为空', trigger: 'blur' }]
|
rules: [{ required: true, message: '应用名不能为空', trigger: 'blur' }]
|
||||||
},
|
},
|
||||||
|
category: {
|
||||||
|
label: '应用分类',
|
||||||
|
span: 12,
|
||||||
|
rules: [{ required: true, message: '应用分类不能为空', trigger: 'blur' }]
|
||||||
|
},
|
||||||
logo: {
|
logo: {
|
||||||
label: '应用图标',
|
label: '应用图标',
|
||||||
span: 24,
|
span: 24,
|
||||||
@@ -174,7 +219,6 @@ const tableOption = reactive({
|
|||||||
hide: true,
|
hide: true,
|
||||||
control: (val) => {
|
control: (val) => {
|
||||||
let dicData = ["user.read","user.write"]
|
let dicData = ["user.read","user.write"]
|
||||||
debugger
|
|
||||||
if (val?.length) {
|
if (val?.length) {
|
||||||
dicData = val.map((item) => {
|
dicData = val.map((item) => {
|
||||||
return { label: item, value: item }
|
return { label: item, value: item }
|
||||||
@@ -195,8 +239,6 @@ const tableOption = reactive({
|
|||||||
},
|
},
|
||||||
redirectUris: {
|
redirectUris: {
|
||||||
label: '可重定向的 URI 地址',
|
label: '可重定向的 URI 地址',
|
||||||
// type: 'select',
|
|
||||||
multiple: true,
|
|
||||||
span: 12,
|
span: 12,
|
||||||
hide: true,
|
hide: true,
|
||||||
rules: [{ required: true, message: '可重定向的 URI 地址不能为空', trigger: 'blur' }]
|
rules: [{ required: true, message: '可重定向的 URI 地址不能为空', trigger: 'blur' }]
|
||||||
@@ -204,12 +246,20 @@ const tableOption = reactive({
|
|||||||
authorities: {
|
authorities: {
|
||||||
label: '权限',
|
label: '权限',
|
||||||
span: 12,
|
span: 12,
|
||||||
|
multiple: true,
|
||||||
|
hide: true,
|
||||||
|
type: 'select'
|
||||||
|
},
|
||||||
|
callbackUris: {
|
||||||
|
label: '回调地址',
|
||||||
|
span: 12,
|
||||||
hide: true
|
hide: true
|
||||||
},
|
},
|
||||||
resourceIds: {
|
resourceIds: {
|
||||||
label: '资源',
|
label: '资源',
|
||||||
span: 12,
|
span: 12,
|
||||||
hide: true
|
hide: true,
|
||||||
|
type: 'array'
|
||||||
},
|
},
|
||||||
additionalInformation: {
|
additionalInformation: {
|
||||||
label: '附加信息',
|
label: '附加信息',
|
||||||
|
|||||||
233
src/views/system/role/RoleAssignAppForm.vue
Normal file
233
src/views/system/role/RoleAssignAppForm.vue
Normal 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>
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
|
|||||||
Reference in New Issue
Block a user