UI改版测试V0

This commit is contained in:
liuwei
2026-03-09 11:32:08 +08:00
parent 8b95fbc2a9
commit 14720f48a4
17 changed files with 3054 additions and 4559 deletions

View File

@@ -3,129 +3,153 @@
{% block title %}群权限管理 - 机器人管理后台{% endblock %}
{% block content %}
<!-- 群机器人管理 -->
<div>
<el-row :gutter="20">
<el-col :span="24">
<el-card shadow="hover">
<div slot="header" class="clearfix">
<span>群机器人管理</span>
<el-button
type="primary"
size="small"
style="float: right; margin-left: 10px;"
@click="showAddGroupDialog">
添加群组
</el-button>
<el-input
placeholder="搜索群组..."
v-model="searchQuery"
style="width: 200px; float: right"
clearable>
</el-input>
</div>
<!-- 群组列表 -->
<el-table
:data="filteredGroups"
style="width: 100%"
border
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="群组信息">
<template slot-scope="scope">
{% raw %}{{ scope.row.group_name || scope.row.group_id }} ({{ scope.row.group_id }}){% endraw %}
</template>
</el-table-column>
<el-table-column label="机器人状态" width="120">
<template slot-scope="scope">
<el-tag
:type="scope.row.robot_status === 'enabled' ? 'success' : 'danger'">
{% raw %}{{ scope.row.robot_status === 'enabled' ? '已启用' : '已关闭' }}{% endraw %}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="280">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="viewGroupPermissions(scope.row)">
查看权限
</el-button>
<el-button
size="mini"
:type="scope.row.robot_status === 'enabled' ? 'danger' : 'success'"
@click="toggleRobotStatus(scope.row)">
{% raw %}{{ scope.row.robot_status === 'enabled' ? '关闭' : '启用' }}{% endraw %}
</el-button>
<el-button
size="mini"
type="info"
@click="viewMessageTrend(scope.row)">
消息趋势
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 批量操作按钮 -->
<div style="margin-top: 20px" v-if="selectedGroups.length > 0">
<el-alert
title="批量操作"
type="info"
:closable="false">
<span>已选择 {% raw %}{{ selectedGroups.length }}{% endraw %} 个群组</span>
</el-alert>
<div style="margin-top: 10px">
<el-button type="success" size="small" @click="batchEnableRobot">批量启用</el-button>
<el-button type="danger" size="small" @click="batchDisableRobot">批量关闭</el-button>
<el-button type="warning" size="small" @click="batchRemoveGroups">批量移除</el-button>
</div>
</div>
<div class="page-shell robot-page">
<div class="page-hero">
<div class="page-hero-copy">
<div class="page-eyebrow">Groups Workspace</div>
<h1>群权限与机器人管理</h1>
<p>集中管理群组启停、权限开关与消息趋势,减少传统后台式分散操作。</p>
</div>
<div class="page-hero-actions">
<el-input
placeholder="搜索群组名称 / ID"
v-model="searchQuery"
class="hero-search"
clearable>
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
<el-button type="primary" @click="showAddGroupDialog">
<i class="el-icon-plus"></i> 添加群组
</el-button>
</div>
</div>
<el-row :gutter="16" class="overview-grid">
<el-col :span="6">
<el-card class="overview-card overview-card--primary" shadow="hover">
<div class="overview-label">群组总数</div>
<div class="overview-value">{% raw %}{{ groups.length }}{% endraw %}</div>
<div class="overview-note">已纳入机器人管理的群组</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="overview-card" shadow="hover">
<div class="overview-label">已启用</div>
<div class="overview-value">{% raw %}{{ enabledGroupsCount }}{% endraw %}</div>
<div class="overview-note">机器人功能已开启</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="overview-card" shadow="hover">
<div class="overview-label">已关闭</div>
<div class="overview-value">{% raw %}{{ disabledGroupsCount }}{% endraw %}</div>
<div class="overview-note">等待重新启用或清理</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="overview-card overview-card--soft" shadow="hover">
<div class="overview-label">当前筛选结果</div>
<div class="overview-value">{% raw %}{{ filteredGroups.length }}{% endraw %}</div>
<div class="overview-note">按搜索条件即时过滤</div>
</el-card>
</el-col>
</el-row>
<!-- 群组权限管理对话框 -->
<el-dialog
:title="currentGroupName + ' 功能权限管理'"
:visible.sync="permissionDialogVisible"
width="70%">
<el-table :data="permissions" style="width: 100%" border>
<el-table-column prop="feature_id" label="功能ID" width="80"></el-table-column>
<el-table-column prop="feature_description" label="功能描述"></el-table-column>
<el-table-column label="状态" width="100">
<el-card class="workspace-card" shadow="hover">
<div slot="header" class="workspace-header">
<div>
<h3>群组列表</h3>
<p>先处理状态,再深入查看权限与趋势。</p>
</div>
<div class="workspace-actions" v-if="selectedGroups.length > 0">
<span class="selection-summary">已选择 {% raw %}{{ selectedGroups.length }}{% endraw %} 个群组</span>
<el-button type="success" size="small" @click="batchEnableRobot">批量启用</el-button>
<el-button type="danger" size="small" @click="batchDisableRobot">批量关闭</el-button>
<el-button type="warning" size="small" @click="batchRemoveGroups">批量移除</el-button>
</div>
</div>
<el-table
:data="filteredGroups"
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="52"></el-table-column>
<el-table-column label="群组信息" min-width="300">
<template slot-scope="scope">
<el-tag
:type="scope.row.status === 'enabled' ? 'success' : 'danger'">
<div class="entity-cell">
<div class="entity-badge">{% raw %}{{ scope.$index + 1 }}{% endraw %}</div>
<div class="entity-copy">
<div class="entity-title">{% raw %}{{ scope.row.group_name || scope.row.group_id }}{% endraw %}</div>
<div class="entity-subtitle">Group ID: {% raw %}{{ scope.row.group_id }}{% endraw %}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="机器人状态" width="140" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.robot_status === 'enabled' ? 'success' : 'info'">
{% raw %}{{ scope.row.robot_status === 'enabled' ? '已启用' : '已关闭' }}{% endraw %}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="360">
<template slot-scope="scope">
<div class="action-row">
<el-button size="mini" type="primary" plain @click="viewGroupPermissions(scope.row)">
查看权限
</el-button>
<el-button
size="mini"
:type="scope.row.robot_status === 'enabled' ? 'danger' : 'success'"
@click="toggleRobotStatus(scope.row)">
{% raw %}{{ scope.row.robot_status === 'enabled' ? '关闭机器人' : '启用机器人' }}{% endraw %}
</el-button>
<el-button size="mini" type="info" plain @click="viewMessageTrend(scope.row)">
消息趋势
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
:title="currentGroupName + ' 功能权限管理'"
:visible.sync="permissionDialogVisible"
width="72%">
<div class="dialog-intro">按功能维度快速启停,适合对群组做差异化权限控制。</div>
<el-table :data="permissions" style="width: 100%">
<el-table-column prop="feature_id" label="功能 ID" width="90"></el-table-column>
<el-table-column prop="feature_description" label="功能描述"></el-table-column>
<el-table-column label="当前状态" width="120" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 'enabled' ? 'success' : 'info'">
{% raw %}{{ scope.row.status === 'enabled' ? '已启用' : '已关闭' }}{% endraw %}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<el-table-column label="切换" width="140" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.statusBool"
active-color="#13ce66"
inactive-color="#ff4949"
active-color="#10b981"
inactive-color="#cbd5e1"
@change="togglePermission(scope.row)">
</el-switch>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="enableAllPermissions" type="success">一键启用全部</el-button>
<el-button @click="disableAllPermissions" type="danger">一键关闭全部</el-button>
<div slot="footer" class="dialog-footer dialog-footer-split">
<div>
<el-button @click="enableAllPermissions" type="success">一键启用全部</el-button>
<el-button @click="disableAllPermissions" type="danger">一键关闭全部</el-button>
</div>
<el-button @click="permissionDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
<!-- 添加群组对话框 -->
<el-dialog
title="添加群组"
:visible.sync="addGroupDialogVisible"
width="30%">
<el-dialog title="添加群组" :visible.sync="addGroupDialogVisible" width="34%">
<div class="dialog-intro">输入需要纳入机器人管理的群组 ID。</div>
<el-form :model="addGroupForm" :rules="addGroupRules" ref="addGroupForm">
<el-form-item label="群组ID" prop="groupId">
<el-input v-model="addGroupForm.groupId" placeholder="请输入群组ID"></el-input>
@@ -136,15 +160,12 @@
<el-button type="primary" @click="submitAddGroup">确定</el-button>
</span>
</el-dialog>
<!-- 群组消息趋势对话框 -->
<el-dialog
title="群组消息趋势"
:visible.sync="messageTrendDialogVisible"
width="70%">
<div class="chart-container">
<h3>{% raw %}{{ currentGroupName }}{% endraw %} 消息趋势</h3>
<canvas id="messageTrendChart" width="800" height="400"></canvas>
<el-dialog title="群组消息趋势" :visible.sync="messageTrendDialogVisible" width="72%">
<div class="dialog-intro">观察该群组在当前统计区间内的消息节奏与活跃度变化。</div>
<div class="chart-shell">
<div class="chart-heading">{% raw %}{{ currentGroupName }}{% endraw %} · 消息趋势</div>
<canvas id="messageTrendChart" width="800" height="360"></canvas>
</div>
</el-dialog>
</div>
@@ -164,7 +185,6 @@
searchQuery: '',
selectedGroups: [],
permissionDialogVisible: false,
// 添加群组相关数据
addGroupDialogVisible: false,
addGroupForm: {
groupId: ''
@@ -175,7 +195,6 @@
{ pattern: /^\S+$/, message: '群组ID不能包含空格', trigger: 'blur' }
]
},
// 添加消息趋势相关数据
messageTrendDialogVisible: false,
messageTrendData: {
dates: [],
@@ -187,14 +206,20 @@
filteredGroups() {
if (!this.searchQuery) return this.groups;
const query = this.searchQuery.toLowerCase();
return this.groups.filter(group =>
(group.group_id && group.group_id.toLowerCase().includes(query)) ||
return this.groups.filter(group =>
(group.group_id && group.group_id.toLowerCase().includes(query)) ||
(group.group_name && group.group_name.toLowerCase().includes(query))
);
},
enabledGroupsCount() {
return this.groups.filter(group => group.robot_status === 'enabled').length;
},
disabledGroupsCount() {
return this.groups.filter(group => group.robot_status !== 'enabled').length;
}
},
mounted() {
this.currentView = '6'; // 设置当前菜单项
this.currentView = '6';
this.loadGroups();
},
methods: {
@@ -218,11 +243,10 @@
viewGroupPermissions(group) {
this.currentGroupId = group.group_id;
this.currentGroupName = group.group_name || group.group_id;
axios.get(`/robot/api/group/${group.group_id}/permissions`)
.then(response => {
if (response.data.success) {
// 添加布尔值属性用于switch组件
const permissionsData = response.data.data || [];
this.permissions = permissionsData.map(p => ({
...p,
@@ -240,7 +264,7 @@
},
togglePermission(permission) {
const newStatus = permission.statusBool ? 'enabled' : 'disabled';
axios.post(`/robot/api/group/${this.currentGroupId}/permissions`, {
feature_id: permission.feature_id,
status: newStatus
@@ -250,13 +274,11 @@
permission.status = newStatus;
this.$message.success('更新权限成功');
} else {
// 恢复原状态
permission.statusBool = !permission.statusBool;
this.$message.error('更新权限失败');
}
})
.catch(error => {
// 恢复原状态
permission.statusBool = !permission.statusBool;
console.error('更新权限失败:', error);
this.$message.error('更新权限失败: ' + error.message);
@@ -274,7 +296,6 @@
})
.then(response => {
if (response.data.success) {
// 更新本地数据
this.permissions.forEach(p => {
p.status = status;
p.statusBool = status === 'enabled';
@@ -291,7 +312,7 @@
},
toggleRobotStatus(group) {
const newStatus = group.robot_status === 'enabled' ? 'disabled' : 'enabled';
axios.post(`/robot/api/group/${group.group_id}/status`, {
status: newStatus
})
@@ -322,7 +343,6 @@
if (response.data.success) {
this.addGroupDialogVisible = false;
this.$message.success('添加群组成功');
// 重新加载群组列表
this.loadGroups();
} else {
this.$message.error('添加群组失败');
@@ -346,7 +366,7 @@
this.$message.warning('请先选择群组');
return;
}
axios.post('/robot/api/batch_operation', {
operation: 'update_status',
group_ids: this.selectedGroups,
@@ -354,7 +374,6 @@
})
.then(response => {
if (response.data.success) {
// 更新本地数据
this.groups.forEach(g => {
if (this.selectedGroups.includes(g.group_id)) {
g.robot_status = status;
@@ -375,7 +394,7 @@
this.$message.warning('请先选择群组');
return;
}
this.$confirm(`确定要批量移除 ${this.selectedGroups.length} 个群组吗? 此操作将清除这些群组的所有设置。`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -387,7 +406,6 @@
})
.then(response => {
if (response.data.success) {
// 从列表中移除这些群组
this.groups = this.groups.filter(g => !this.selectedGroups.includes(g.group_id));
this.selectedGroups = [];
this.$message.success('批量移除成功');
@@ -401,20 +419,17 @@
});
}).catch(() => {});
},
// 添加查看消息趋势的方法
viewMessageTrend(group) {
this.currentGroupId = group.group_id;
this.currentGroupName = group.group_name || group.group_id;
// 获取消息趋势数据
const days = parseInt(this.timeRange || 7);
axios.get(`/robot/api/group/${group.group_id}/message_trend?days=${days}`)
.then(response => {
if (response.data.success) {
this.messageTrendData = response.data.data || { dates: [], counts: [] };
this.messageTrendDialogVisible = true;
// 在下一个DOM更新周期渲染图表
this.$nextTick(() => {
this.renderMessageTrendChart();
});
@@ -427,21 +442,17 @@
this.$message.error('加载消息趋势失败: ' + error.message);
});
},
// 渲染消息趋势图表
renderMessageTrendChart() {
const ctx = document.getElementById('messageTrendChart').getContext('2d');
// 销毁旧图表
if (this.charts && this.charts.messageTrendChart) {
this.charts.messageTrendChart.destroy();
}
// 确保charts对象存在
if (!this.charts) {
this.charts = {};
}
// 创建新图表
this.charts.messageTrendChart = new Chart(ctx, {
type: 'line',
data: {
@@ -450,18 +461,34 @@
{
label: '消息数量',
data: this.messageTrendData.counts,
fill: false,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
tension: 0.1
fill: true,
backgroundColor: 'rgba(79, 70, 229, 0.10)',
borderColor: 'rgba(79, 70, 229, 1)',
tension: 0.28,
borderWidth: 3,
pointRadius: 2
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
y: {
beginAtZero: true
beginAtZero: true,
grid: {
color: 'rgba(148,163,184,0.12)'
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
}
}
}
@@ -470,4 +497,187 @@
}
});
</script>
{% endblock %}
{% endblock %}
{% block styles %}
<style>
.page-shell {
display: flex;
flex-direction: column;
gap: 16px;
}
.page-hero {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 18px;
padding: 24px 26px;
border-radius: 24px;
background: linear-gradient(135deg, rgba(79,70,229,0.10), rgba(59,130,246,0.08), rgba(255,255,255,0.9));
border: 1px solid rgba(148, 163, 184, 0.16);
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.06);
}
.page-eyebrow {
font-size: 12px;
text-transform: uppercase;
letter-spacing: .08em;
color: #6366f1;
font-weight: 700;
margin-bottom: 8px;
}
.page-hero-copy h1 {
font-size: 30px;
line-height: 1.1;
margin-bottom: 10px;
color: #0f172a;
}
.page-hero-copy p {
color: #64748b;
font-size: 14px;
}
.page-hero-actions {
display: flex;
align-items: center;
gap: 12px;
}
.hero-search {
width: 260px;
}
.overview-grid .el-col {
margin-bottom: 16px;
}
.overview-card {
min-height: 112px;
}
.overview-card--primary {
background: linear-gradient(180deg, rgba(79,70,229,0.10), rgba(255,255,255,0.94)) !important;
}
.overview-card--soft {
background: linear-gradient(180deg, rgba(59,130,246,0.08), rgba(255,255,255,0.94)) !important;
}
.overview-label {
font-size: 13px;
color: #64748b;
margin-bottom: 14px;
}
.overview-value {
font-size: 30px;
font-weight: 700;
color: #0f172a;
margin-bottom: 10px;
}
.overview-note {
font-size: 12px;
color: #94a3b8;
}
.workspace-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.workspace-header h3 {
font-size: 18px;
margin-bottom: 4px;
}
.workspace-header p {
font-size: 13px;
color: #64748b;
}
.workspace-actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.selection-summary {
font-size: 13px;
color: #475569;
margin-right: 4px;
}
.entity-cell {
display: flex;
align-items: center;
gap: 12px;
}
.entity-badge {
width: 30px;
height: 30px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(79,70,229,0.10);
color: #4f46e5;
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
}
.entity-title {
font-size: 14px;
font-weight: 600;
color: #0f172a;
}
.entity-subtitle {
margin-top: 4px;
font-size: 12px;
color: #94a3b8;
}
.action-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.dialog-intro {
margin-bottom: 14px;
color: #64748b;
font-size: 13px;
}
.dialog-footer-split {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.chart-shell {
padding: 16px;
border-radius: 18px;
background: linear-gradient(180deg, rgba(248,250,252,0.78), rgba(255,255,255,0.96));
border: 1px solid rgba(148,163,184,0.12);
}
.chart-heading {
margin-bottom: 12px;
font-size: 14px;
font-weight: 600;
color: #334155;
}
</style>
{% endblock %}