Files
abot/plugins/stats_dashboard/templates/robot_management.html

567 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% 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>
</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">
<template slot-scope="scope">
<el-tag
:type="scope.row.status === 'enabled' ? 'success' : 'danger'">
{% raw %}{{ scope.row.status === 'enabled' ? '已启用' : '已关闭' }}{% endraw %}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-switch
v-model="scope.row.statusBool"
active-color="#13ce66"
inactive-color="#ff4949"
@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>
<el-button @click="permissionDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
<!-- 添加群组对话框 -->
<el-dialog
title="添加群组"
:visible.sync="addGroupDialogVisible"
width="30%">
<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>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addGroupDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAddGroup">确定</el-button>
</span>
</el-dialog>
<!-- 群组消息趋势图对话框 -->
<el-dialog
:title="currentGroupName + ' 消息趋势'"
:visible.sync="trendDialogVisible"
width="70%"
@opened="onTrendDialogOpened"
destroy-on-close>
<div v-if="trendLoading" style="text-align: center; padding: 20px;">
<i class="el-icon-loading"></i>
<p>加载中...</p>
</div>
<div v-else>
<div class="chart-container">
<h3>消息数量趋势</h3>
<!-- 使用div作为容器动态创建canvas -->
<div ref="chartContainer" style="width: 100%; height: 300px;"></div>
</div>
<div style="margin-top: 20px; text-align: center;">
<el-radio-group v-model="trendDays" @change="loadMessageTrend">
<el-radio-button :label="7">最近7天</el-radio-button>
<el-radio-button :label="14">最近14天</el-radio-button>
<el-radio-button :label="30">最近30天</el-radio-button>
</el-radio-group>
</div>
</div>
</el-dialog>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
groups: [],
permissions: [],
currentGroupId: null,
currentGroupName: '',
searchQuery: '',
selectedGroups: [],
permissionDialogVisible: false,
// 添加群组相关数据
addGroupDialogVisible: false,
addGroupForm: {
groupId: ''
},
addGroupRules: {
groupId: [
{ required: true, message: '请输入群组ID', trigger: 'blur' },
{ pattern: /^\S+$/, message: '群组ID不能包含空格', trigger: 'blur' }
]
},
// 趋势图相关数据
trendDialogVisible: false,
trendLoading: false,
trendDays: 7,
charts: {} // 使用charts对象存储图表实例
}
},
computed: {
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)) ||
(group.group_name && group.group_name.toLowerCase().includes(query))
);
}
},
mounted() {
this.currentView = '6'; // 设置当前菜单项
this.loadGroups();
},
methods: {
loadGroups() {
axios.get('/api/robot/groups')
.then(response => {
if (response.data.success) {
this.groups = response.data.data || [];
} else {
this.$message.error('加载群组失败');
}
})
.catch(error => {
console.error('加载群组失败:', error);
this.$message.error('加载群组失败: ' + error.message);
});
},
handleSelectionChange(selection) {
this.selectedGroups = selection.map(item => item.group_id);
},
viewGroupPermissions(group) {
this.currentGroupId = group.group_id;
this.currentGroupName = group.group_name || group.group_id;
axios.get(`/api/robot/group/${group.group_id}/permissions`)
.then(response => {
if (response.data.success) {
// 添加布尔值属性用于switch组件
const permissionsData = response.data.data || [];
this.permissions = permissionsData.map(p => ({
...p,
statusBool: p.status === 'enabled'
}));
this.permissionDialogVisible = true;
} else {
this.$message.error('加载权限失败');
}
})
.catch(error => {
console.error('加载权限失败:', error);
this.$message.error('加载权限失败: ' + error.message);
});
},
togglePermission(permission) {
const newStatus = permission.statusBool ? 'enabled' : 'disabled';
axios.post(`/api/robot/group/${this.currentGroupId}/permission`, {
feature_id: permission.feature_id,
status: newStatus
})
.then(response => {
if (response.data.success) {
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);
});
},
enableAllPermissions() {
this.updateAllPermissions('enabled');
},
disableAllPermissions() {
this.updateAllPermissions('disabled');
},
updateAllPermissions(status) {
axios.post(`/api/robot/group/${this.currentGroupId}/permissions`, {
status: status
})
.then(response => {
if (response.data.success) {
// 更新本地数据
this.permissions.forEach(p => {
p.status = status;
p.statusBool = status === 'enabled';
});
this.$message.success('批量更新权限成功');
} else {
this.$message.error('批量更新权限失败');
}
})
.catch(error => {
console.error('批量更新权限失败:', error);
this.$message.error('批量更新权限失败: ' + error.message);
});
},
toggleRobotStatus(group) {
const newStatus = group.robot_status === 'enabled' ? 'disabled' : 'enabled';
axios.post(`/api/robot/group/${group.group_id}/status`, {
status: newStatus
})
.then(response => {
if (response.data.success) {
group.robot_status = newStatus;
this.$message.success('更新机器人状态成功');
} else {
this.$message.error('更新机器人状态失败');
}
})
.catch(error => {
console.error('更新机器人状态失败:', error);
this.$message.error('更新机器人状态失败: ' + error.message);
});
},
showAddGroupDialog() {
this.addGroupForm.groupId = '';
this.addGroupDialogVisible = true;
},
submitAddGroup() {
this.$refs.addGroupForm.validate(valid => {
if (valid) {
axios.post('/api/robot/group', {
group_id: this.addGroupForm.groupId
})
.then(response => {
if (response.data.success) {
this.addGroupDialogVisible = false;
this.$message.success('添加群组成功');
// 重新加载群组列表
this.loadGroups();
} else {
this.$message.error('添加群组失败');
}
})
.catch(error => {
console.error('添加群组失败:', error);
this.$message.error('添加群组失败: ' + error.message);
});
}
});
},
batchEnableRobot() {
this.batchUpdateRobotStatus('enabled');
},
batchDisableRobot() {
this.batchUpdateRobotStatus('disabled');
},
batchUpdateRobotStatus(status) {
if (this.selectedGroups.length === 0) {
this.$message.warning('请先选择群组');
return;
}
axios.post('/api/robot/batch_operation', {
operation: 'update_status',
group_ids: this.selectedGroups,
status: status
})
.then(response => {
if (response.data.success) {
// 更新本地数据
this.groups.forEach(g => {
if (this.selectedGroups.includes(g.group_id)) {
g.robot_status = status;
}
});
this.$message.success('批量更新状态成功');
} else {
this.$message.error('批量更新状态失败');
}
})
.catch(error => {
console.error('批量更新状态失败:', error);
this.$message.error('批量更新状态失败: ' + error.message);
});
},
batchRemoveGroups() {
if (this.selectedGroups.length === 0) {
this.$message.warning('请先选择群组');
return;
}
this.$confirm(`确定要批量移除 ${this.selectedGroups.length} 个群组吗? 此操作将清除这些群组的所有设置。`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'danger'
}).then(() => {
axios.post('/api/robot/batch_operation', {
operation: 'remove_groups',
group_ids: this.selectedGroups
})
.then(response => {
if (response.data.success) {
// 从列表中移除这些群组
this.groups = this.groups.filter(g => !this.selectedGroups.includes(g.group_id));
this.selectedGroups = [];
this.$message.success('批量移除成功');
} else {
this.$message.error('批量移除失败');
}
})
.catch(error => {
console.error('批量移除失败:', error);
this.$message.error('批量移除失败: ' + error.message);
});
}).catch(() => {});
},
// 查看消息趋势
viewMessageTrend(group) {
this.currentGroupId = group.group_id;
this.currentGroupName = group.group_name || group.group_id;
this.trendDialogVisible = true;
},
// 对话框打开后的回调
onTrendDialogOpened() {
this.loadMessageTrend();
},
loadMessageTrend() {
this.trendLoading = true;
axios.get(`/api/robot/group/${this.currentGroupId}/message_trend?days=${this.trendDays}`)
.then(response => {
if (response.data.success) {
// 使用setTimeout确保DOM已完全渲染
setTimeout(() => {
this.renderTrendChart(response.data.data);
this.trendLoading = false;
}, 300);
} else {
this.$message.error('加载消息趋势失败');
this.trendLoading = false;
}
})
.catch(error => {
console.error('加载消息趋势失败:', error);
this.$message.error('加载消息趋势失败: ' + error.message);
this.trendLoading = false;
});
},
renderTrendChart(data) {
try {
console.log('开始渲染图表');
// 获取容器元素
const container = this.$refs.chartContainer;
if (!container) {
console.error('找不到图表容器');
this.$message.error('无法找到图表容器,请尝试重新打开对话框');
return;
}
// 清空容器
container.innerHTML = '';
// 动态创建Canvas元素
const canvas = document.createElement('canvas');
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
container.appendChild(canvas);
console.log('已创建Canvas元素');
const ctx = canvas.getContext('2d');
// 销毁旧图表
if (this.charts.trendChart) {
this.charts.trendChart.destroy();
}
// 确保charts对象存在
if (!this.charts) {
this.charts = {};
}
// 准备数据
const labels = data.dates || [];
const messageData = (data.counts || []).map(count => parseInt(count) || 0);
// 创建新图表
this.charts.trendChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: '消息数量',
data: messageData,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 2,
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: false
},
tooltip: {
mode: 'index',
intersect: false
},
legend: {
position: 'top',
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '消息数量'
}
},
x: {
ticks: {
maxRotation: 45,
minRotation: 45
}
}
}
}
});
} catch (error) {
console.error('渲染图表出错:', error);
this.$message.error('渲染图表出错: ' + error.message);
this.trendLoading = false;
}
}
}
});
</script>
{% endblock %}
{% block styles %}
<style>
.chart-container {
margin-bottom: 20px;
padding: 10px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.chart-container h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
color: #606266;
}
</style>
{% endblock %}