优化插件管理页小屏适配
- 为插件列表增加移动端卡片布局,避免手机端出现整页横向滚动 - 为插件详情与群状态弹窗增加响应式宽度和单列展示 - 为群状态列表增加移动端卡片视图,并优化小屏按钮与文案换行
This commit is contained in:
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user