commit:首页重构;应用管理优化

This commit is contained in:
gesilong
2026-02-10 09:30:20 +08:00
parent 9e210114b6
commit 34f7c68041
3 changed files with 124 additions and 58 deletions

View File

@@ -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 模块 ==========

View File

@@ -45,7 +45,7 @@ v-for="app in category.apps"
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 ClientApi from '@/api/system/oauth2/client'
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 ClientApi.getOAuth2ClientPage({ 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;

View File

@@ -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: '附加信息',