优化插件管理页小屏适配

- 为插件列表增加移动端卡片布局,避免手机端出现整页横向滚动
- 为插件详情与群状态弹窗增加响应式宽度和单列展示
- 为群状态列表增加移动端卡片视图,并优化小屏按钮与文案换行
This commit is contained in:
liuwei
2026-04-27 16:52:56 +08:00
parent 9a84b6b313
commit 0fc5398b9b

View File

@@ -18,28 +18,28 @@
</div>
<el-row :gutter="16" class="overview-grid">
<el-col :span="6">
<el-col :xs="24" :sm="12" :md="6">
<el-card class="overview-card overview-card--primary" shadow="hover">
<div class="overview-label">插件总数</div>
<div class="overview-value">{% raw %}{{ plugins.length }}{% endraw %}</div>
<div class="overview-note">当前已注册插件模块</div>
</el-card>
</el-col>
<el-col :span="6">
<el-col :xs="24" :sm="12" :md="6">
<el-card class="overview-card" shadow="hover">
<div class="overview-label">运行中</div>
<div class="overview-value">{% raw %}{{ runningPluginsCount }}{% endraw %}</div>
<div class="overview-note">可正常提供能力的插件</div>
</el-card>
</el-col>
<el-col :span="6">
<el-col :xs="24" :sm="12" :md="6">
<el-card class="overview-card" shadow="hover">
<div class="overview-label">已停用</div>
<div class="overview-value">{% raw %}{{ stoppedPluginsCount }}{% endraw %}</div>
<div class="overview-note">待启用或排查状态</div>
</el-card>
</el-col>
<el-col :span="6">
<el-col :xs="24" :sm="12" :md="6">
<el-card class="overview-card overview-card--soft" shadow="hover">
<div class="overview-label">作者数量</div>
<div class="overview-value">{% raw %}{{ authorsCount }}{% endraw %}</div>
@@ -56,7 +56,8 @@
</div>
</div>
<el-table :data="plugins" style="width: 100%" v-loading="loading">
<!-- 桌面端继续保留表格视图,避免影响高密度信息浏览效率。 -->
<el-table v-if="!isMobileViewport" :data="plugins" style="width: 100%" v-loading="loading">
<el-table-column label="插件信息" min-width="260">
<template slot-scope="scope">
<div class="entity-cell">
@@ -100,12 +101,59 @@
</template>
</el-table-column>
</el-table>
<div v-else class="mobile-plugin-list" v-loading="loading">
<!-- 手机端改成卡片流式布局,避免表格固定列宽把页面撑出横向滚动。 -->
<div v-if="plugins.length === 0" class="mobile-empty-state">暂无插件数据</div>
<el-card
v-for="(plugin, index) in plugins"
:key="plugin.module_name || plugin.name || index"
class="mobile-plugin-card"
shadow="hover">
<div class="mobile-plugin-card__header">
<div class="entity-cell">
<div class="entity-badge">{% raw %}{{ index + 1 }}{% endraw %}</div>
<div class="entity-copy">
<div class="entity-title">{% raw %}{{ plugin.name }}{% endraw %}</div>
<div class="entity-subtitle">模块:{% raw %}{{ plugin.module_name }}{% endraw %}</div>
</div>
</div>
<el-tag :type="plugin.status === 'RUNNING' ? 'success' : 'info'" size="small">
{% raw %}{{ plugin.status === 'RUNNING' ? '已启用' : '已禁用' }}{% endraw %}
</el-tag>
</div>
<div class="mobile-plugin-card__meta">
<span>版本:{% raw %}{{ plugin.version || '未知' }}{% endraw %}</span>
<span>作者:{% raw %}{{ plugin.author || '未知' }}{% endraw %}</span>
</div>
<div class="mobile-plugin-card__desc">
{% raw %}{{ plugin.description || '暂无描述' }}{% endraw %}
</div>
<div class="mobile-plugin-card__actions">
<el-button
size="mini"
:type="plugin.status === 'RUNNING' ? 'danger' : 'success'"
@click="togglePluginStatus(plugin)">
{% raw %}{{ plugin.status === 'RUNNING' ? '禁用' : '启用' }}{% endraw %}
</el-button>
<el-button size="mini" type="primary" plain @click="reloadPlugin(plugin)">
重载
</el-button>
<el-button size="mini" type="info" plain @click="showPluginInfo(plugin)">
详情
</el-button>
<el-button size="mini" type="warning" plain @click="showPluginGroupStatus(plugin)">
群状态
</el-button>
</div>
</el-card>
</div>
</el-card>
<el-dialog title="插件详情" :visible.sync="pluginInfoVisible" width="64%" top="5vh">
<el-dialog title="插件详情" :visible.sync="pluginInfoVisible" :width="pluginInfoDialogWidth" top="5vh">
<div v-if="selectedPlugin" class="plugin-detail-container">
<div class="dialog-intro">查看插件基础信息、命令列表与配置内容,需要时可直接在这里编辑配置。</div>
<el-descriptions border direction="vertical" :column="2" size="small" class="plugin-descriptions">
<el-descriptions border direction="vertical" :column="pluginDescriptionsColumn" size="small" class="plugin-descriptions">
<el-descriptions-item label="插件名称" :span="1">{% raw %}{{ selectedPlugin.name }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="模块名称" :span="1">{% raw %}{{ selectedPlugin.module_name }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="版本" :span="1">{% raw %}{{ selectedPlugin.version }}{% endraw %}</el-descriptions-item>
@@ -152,7 +200,7 @@
</div>
</el-dialog>
<el-dialog title="插件群状态" :visible.sync="pluginGroupStatusVisible" width="72%" top="5vh">
<el-dialog title="插件群状态" :visible.sync="pluginGroupStatusVisible" :width="groupStatusDialogWidth" top="5vh">
<div class="plugin-group-status-dialog" v-loading="groupStatusLoading">
<div v-if="pluginGroupStatusData" class="group-status-header">
<div class="group-status-title">
@@ -177,13 +225,15 @@
</el-alert>
<el-row :gutter="16" v-if="pluginGroupStatusData">
<el-col :span="12">
<el-col :xs="24" :sm="24" :md="12">
<el-card shadow="never" class="group-status-card">
<div slot="header" class="group-status-card-header">
<span>已开启群</span>
<el-tag size="mini" type="success">{% raw %}{{ pluginGroupStatusData.enabled_count || 0 }}{% endraw %}</el-tag>
</div>
<!-- 桌面端保留双列表格,移动端单独渲染群卡片,避免群 ID 列挤出屏幕。 -->
<el-table
v-if="!isMobileViewport"
:data="pluginGroupStatusData.enabled_groups || []"
size="mini"
max-height="420"
@@ -207,15 +257,37 @@
</template>
</el-table-column>
</el-table>
<div v-else class="mobile-group-list">
<div v-if="!(pluginGroupStatusData.enabled_groups || []).length" class="mobile-empty-state">暂无已开启群</div>
<el-card
v-for="group in (pluginGroupStatusData.enabled_groups || [])"
:key="`enabled-${group.group_id}`"
class="mobile-group-card"
shadow="never">
<div class="mobile-group-card__title">{% raw %}{{ group.group_name || group.group_id }}{% endraw %}</div>
<div class="mobile-group-card__id">群ID{% raw %}{{ group.group_id }}{% endraw %}</div>
<div class="mobile-group-card__actions">
<el-button
size="mini"
type="danger"
plain
:disabled="groupStatusLoading || !pluginGroupStatusData.supports_group_switch"
@click="togglePluginGroupSwitch(group, false)">
关闭
</el-button>
</div>
</el-card>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-col :xs="24" :sm="24" :md="12">
<el-card shadow="never" class="group-status-card">
<div slot="header" class="group-status-card-header">
<span>未开启群</span>
<el-tag size="mini" type="info">{% raw %}{{ pluginGroupStatusData.disabled_count || 0 }}{% endraw %}</el-tag>
</div>
<el-table
v-if="!isMobileViewport"
:data="pluginGroupStatusData.disabled_groups || []"
size="mini"
max-height="420"
@@ -239,6 +311,27 @@
</template>
</el-table-column>
</el-table>
<div v-else class="mobile-group-list">
<div v-if="!(pluginGroupStatusData.disabled_groups || []).length" class="mobile-empty-state">暂无未开启群</div>
<el-card
v-for="group in (pluginGroupStatusData.disabled_groups || [])"
:key="`disabled-${group.group_id}`"
class="mobile-group-card"
shadow="never">
<div class="mobile-group-card__title">{% raw %}{{ group.group_name || group.group_id }}{% endraw %}</div>
<div class="mobile-group-card__id">群ID{% raw %}{{ group.group_id }}{% endraw %}</div>
<div class="mobile-group-card__actions">
<el-button
size="mini"
type="success"
plain
:disabled="groupStatusLoading || !pluginGroupStatusData.supports_group_switch"
@click="togglePluginGroupSwitch(group, true)">
开启
</el-button>
</div>
</el-card>
</div>
</el-card>
</el-col>
</el-row>
@@ -265,7 +358,9 @@
pluginGroupStatusVisible: false,
groupStatusLoading: false,
pluginGroupStatusData: null,
currentGroupStatusPlugin: null
currentGroupStatusPlugin: null,
// 使用响应式视口状态切换移动端卡片布局,避免仅靠 CSS 隐藏后仍渲染宽表格。
isMobileViewport: false
}
},
computed: {
@@ -277,13 +372,34 @@
},
authorsCount() {
return new Set((this.plugins || []).map(plugin => plugin.author).filter(Boolean)).size;
},
// 弹窗宽度按视口分级收缩,保证手机上弹窗内容不会贴边或继续触发横向溢出。
pluginInfoDialogWidth() {
return this.isMobileViewport ? '94%' : '64%';
},
groupStatusDialogWidth() {
return this.isMobileViewport ? '94%' : '72%';
},
pluginDescriptionsColumn() {
return this.isMobileViewport ? 1 : 2;
}
},
mounted() {
this.currentView = '11';
// 首次进入页面就同步一次屏幕宽度,确保移动端不会先闪出桌面表格。
this.updateViewportState();
window.addEventListener('resize', this.updateViewportState);
this.loadPlugins();
},
beforeDestroy() {
// 页面销毁时移除监听,避免重复绑定造成状态更新和内存占用问题。
window.removeEventListener('resize', this.updateViewportState);
},
methods: {
updateViewportState() {
// 这里统一以 768px 作为移动端断点,和常见后台管理布局断点保持一致。
this.isMobileViewport = window.innerWidth <= 768;
},
loadPlugins() {
this.loading = true;
axios.get('/api/plugins')
@@ -603,6 +719,67 @@
.config-editor { font-family: monospace; font-size: 12px; }
.config-error { color: #ef4444; font-size: 12px; margin-top: 5px; }
.plugin-group-status-dialog { min-height: 240px; }
.mobile-plugin-list,
.mobile-group-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.mobile-plugin-card,
.mobile-group-card {
border-radius: 16px;
}
.mobile-plugin-card__header,
.mobile-group-card__actions {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
}
.mobile-plugin-card__header {
margin-bottom: 12px;
}
.mobile-plugin-card__meta {
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
margin-bottom: 10px;
font-size: 12px;
color: #64748b;
}
.mobile-plugin-card__desc,
.mobile-group-card__id {
font-size: 13px;
line-height: 1.7;
color: #475569;
word-break: break-word;
}
.mobile-plugin-card__actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
}
.mobile-group-card__title {
font-size: 14px;
font-weight: 600;
color: #0f172a;
margin-bottom: 6px;
word-break: break-word;
}
.mobile-group-card__actions {
margin-top: 12px;
justify-content: flex-end;
}
.mobile-empty-state {
padding: 16px 12px;
text-align: center;
color: #94a3b8;
font-size: 13px;
background: rgba(248, 250, 252, 0.9);
border: 1px dashed rgba(148, 163, 184, 0.35);
border-radius: 14px;
}
.group-status-header {
display: flex;
align-items: center;
@@ -623,5 +800,65 @@
font-weight: 600;
color: #334155;
}
/* 小屏重点做三件事:
1. 让头部和操作区纵向排布,避免按钮把容器撑宽;
2. 让弹窗内容与卡片内边距缩小,提升可视面积;
3. 让配置编辑区和标签内容可换行,不再出现页面级横向滚动。 */
@media (max-width: 768px) {
.page-hero {
flex-direction: column;
align-items: stretch;
padding: 18px 16px;
border-radius: 20px;
}
.page-hero-copy h1 {
font-size: 24px;
}
.page-hero-actions .el-button {
width: 100%;
}
.workspace-header {
flex-direction: column;
align-items: flex-start;
}
.workspace-card .el-card__body,
.group-status-card .el-card__body {
padding: 14px;
}
.entity-cell {
align-items: flex-start;
}
.entity-copy,
.group-status-title {
min-width: 0;
}
.entity-title,
.entity-subtitle,
.group-status-title,
.group-status-subtitle {
word-break: break-word;
}
.plugin-detail-container {
max-height: 72vh;
}
.config-container {
padding: 10px;
}
.config-actions {
flex-wrap: wrap;
}
.mobile-plugin-card__header {
flex-direction: column;
}
.mobile-plugin-card__actions .el-button,
.mobile-group-card__actions .el-button {
flex: 1 1 calc(50% - 8px);
min-width: 0;
margin-left: 0 !important;
}
.group-status-summary {
flex-wrap: wrap;
}
}
</style>
{% endblock %}