567 lines
24 KiB
HTML
567 lines
24 KiB
HTML
{% 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 %} |