Files
abot/admin/dashboard/templates/contacts_management.html
2025-04-15 12:02:17 +08:00

671 lines
31 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 {% raw %}:gutter="20"{% endraw %}>
<el-col {% raw %}:span="24"{% endraw %}>
<el-card shadow="hover">
<div slot="header" class="clearfix">
<span>通讯录管理</span>
<el-button
type="primary"
size="small"
style="float: right; margin-left: 10px;"
{% raw %}@click="refreshContacts"{% endraw %}>
刷新数据
</el-button>
<el-input
placeholder="搜索联系人..."
{% raw %}v-model="searchQuery"{% endraw %}
style="width: 200px; float: right"
clearable>
</el-input>
</div>
<!-- 统计卡片 -->
<el-row {% raw %}:gutter="20"{% endraw %} style="margin-bottom: 20px;">
<el-col {% raw %}:span="6"{% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">总联系人数</div>
<div class="stat-value">{% raw %}{{ statistics.total }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="6"{% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">群组数</div>
<div class="stat-value">{% raw %}{{ statistics.groups }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="6"{% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">个人联系人数</div>
<div class="stat-value">{% raw %}{{ statistics.personal }}{% endraw %}</div>
</el-card>
</el-col>
<el-col {% raw %}:span="6"{% endraw %}>
<el-card shadow="hover" class="stat-card">
<div class="stat-title">公众号数</div>
<div class="stat-value">{% raw %}{{ statistics.official }}{% endraw %}</div>
</el-card>
</el-col>
</el-row>
<!-- 选项卡 -->
<el-tabs {% raw %}v-model="activeTab" @tab-click="handleTabClick"{% endraw %}>
<!-- 群组联系人 -->
<el-tab-pane label="群组" name="groups">
<el-table
{% raw %}:data="filteredGroups"{% endraw %}
style="width: 100%"
border>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column prop="wxid" label="群ID" width="220"></el-table-column>
<el-table-column prop="name" label="群名称"></el-table-column>
<el-table-column label="操作" width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
{% raw %}@click="viewGroupDetails(scope.row)"{% endraw %}>
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 个人联系人 -->
<el-tab-pane label="个人联系人" name="personal">
<el-table
{% raw %}:data="filteredPersonal"{% endraw %}
style="width: 100%"
border>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column label="头像" width="70">
<template slot-scope="scope">
<el-avatar
size="small"
{% raw %}:src="getHeadImage(scope.row.wxid)"{% endraw %}
{% raw %}@error="() => true"{% endraw %}>
<img src="/static/logo.png" />
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="wxid" label="微信ID" width="220"></el-table-column>
<el-table-column prop="name" label="昵称"></el-table-column>
<el-table-column label="操作" width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
{% raw %}@click="viewUserDetails(scope.row)"{% endraw %}>
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 公众号 -->
<el-tab-pane label="公众号" name="official">
<el-table
{% raw %}:data="filteredOfficial"{% endraw %}
style="width: 100%"
border>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column label="头像" width="70">
<template slot-scope="scope">
<el-avatar
size="small"
{% raw %}:src="getHeadImage(scope.row.wxid)"{% endraw %}
{% raw %}@error="() => true"{% endraw %}>
<img src="/static/logo.png" />
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="wxid" label="公众号ID" width="220"></el-table-column>
<el-table-column prop="name" label="公众号名称"></el-table-column>
<el-table-column label="操作" width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
{% raw %}@click="viewOfficialDetails(scope.row)"{% endraw %}>
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 公共好友 -->
<el-tab-pane label="公共好友" name="public">
<el-table
{% raw %}:data="filteredPublic"{% endraw %}
style="width: 100%"
border>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column label="头像" width="70">
<template slot-scope="scope">
<el-avatar
size="small"
{% raw %}:src="getHeadImage(scope.row.wxid)"{% endraw %}
{% raw %}@error="() => true"{% endraw %}>
<img src="/static/logo.png" />
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="wxid" label="ID" width="220"></el-table-column>
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column label="操作" width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
{% raw %}@click="viewPublicDetails(scope.row)"{% endraw %}>
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<!-- 分页 -->
<div class="pagination-container" {% raw %}v-if="activeTab === 'groups' && groupsList.length > 10"{% endraw %}>
<el-pagination
{% raw %}@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"{% endraw %}
layout="total, sizes, prev, pager, next, jumper"
{% raw %}:total="groupsList.length"{% endraw %}>
</el-pagination>
</div>
<div class="pagination-container" {% raw %}v-if="activeTab === 'personal' && personalList.length > 10"{% endraw %}>
<el-pagination
{% raw %}@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"{% endraw %}
layout="total, sizes, prev, pager, next, jumper"
{% raw %}:total="personalList.length"{% endraw %}>
</el-pagination>
</div>
<div class="pagination-container" {% raw %}v-if="activeTab === 'official' && officialList.length > 10"{% endraw %}>
<el-pagination
{% raw %}@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"{% endraw %}
layout="total, sizes, prev, pager, next, jumper"
{% raw %}:total="officialList.length"{% endraw %}>
</el-pagination>
</div>
<div class="pagination-container" {% raw %}v-if="activeTab === 'public' && publicList.length > 10"{% endraw %}>
<el-pagination
{% raw %}@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"{% endraw %}
layout="total, sizes, prev, pager, next, jumper"
{% raw %}:total="publicList.length"{% endraw %}>
</el-pagination>
</div>
</el-card>
</el-col>
</el-row>
<!-- 群组详情对话框 -->
<el-dialog title="群组详情" {% raw %}:visible.sync="groupDetailDialogVisible"{% endraw %} width="70%">
<el-descriptions {% raw %}:column="1"{% endraw %} border>
<el-descriptions-item label="群ID">{% raw %}{{ currentGroup.wxid }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="群名称">{% raw %}{{ currentGroup.name }}{% endraw %}</el-descriptions-item>
<!-- 可以添加更多群组相关信息 -->
</el-descriptions>
<!-- 群成员列表 -->
<div style="margin-top: 20px;">
<div class="section-title">
<h3>群成员列表</h3>
<el-input
placeholder="搜索群成员..."
{% raw %}v-model="groupMemberSearchQuery"{% endraw %}
style="width: 200px; float: right; margin-bottom: 10px;"
clearable>
</el-input>
</div>
<el-table
{% raw %}:data="filteredGroupMembers"{% endraw %}
style="width: 100%"
border
{% raw %}v-loading="groupMembersLoading"{% endraw %}>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column label="头像" width="70">
<template slot-scope="scope">
<el-avatar
size="small"
{% raw %}:src="getHeadImage(scope.row.wxid)"{% endraw %}
{% raw %}@error="() => true"{% endraw %}>
<img src="/static/logo.png" />
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="wxid" label="成员ID" width="220"></el-table-column>
<el-table-column prop="name" label="成员昵称"></el-table-column>
</el-table>
<!-- 群成员分页 -->
<div class="pagination-container" {% raw %}v-if="groupMembersList.length > 10"{% endraw %}>
<el-pagination
{% raw %}@size-change="handleGroupMembersSizeChange"
@current-change="handleGroupMembersCurrentChange"
:current-page="groupMembersCurrentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="groupMembersPageSize"{% endraw %}
layout="total, sizes, prev, pager, next, jumper"
{% raw %}:total="groupMembersList.length"{% endraw %}>
</el-pagination>
</div>
</div>
</el-dialog>
<!-- 用户详情对话框 -->
<el-dialog title="用户详情" {% raw %}:visible.sync="userDetailDialogVisible"{% endraw %} width="50%">
<div style="text-align: center; margin-bottom: 20px;">
<el-avatar
size="large"
{% raw %}:src="getHeadImage(currentUser.wxid)"{% endraw %}
{% raw %}@error="() => true"{% endraw %}
style="width: 100px; height: 100px;">
<img src="/static/logo.png" />
</el-avatar>
</div>
<el-descriptions {% raw %}:column="1"{% endraw %} border>
<el-descriptions-item label="微信ID">{% raw %}{{ currentUser.wxid }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="昵称">{% raw %}{{ currentUser.name }}{% endraw %}</el-descriptions-item>
<!-- 可以添加更多用户相关信息 -->
</el-descriptions>
</el-dialog>
<!-- 公众号详情对话框 -->
<el-dialog title="公众号详情" {% raw %}:visible.sync="officialDetailDialogVisible"{% endraw %} width="50%">
<div style="text-align: center; margin-bottom: 20px;">
<el-avatar
size="large"
{% raw %}:src="getHeadImage(currentOfficial.wxid)"{% endraw %}
{% raw %}@error="() => true"{% endraw %}
style="width: 100px; height: 100px;">
<img src="/static/logo.png" />
</el-avatar>
</div>
<el-descriptions {% raw %}:column="1"{% endraw %} border>
<el-descriptions-item label="公众号ID">{% raw %}{{ currentOfficial.wxid }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="公众号名称">{% raw %}{{ currentOfficial.name }}{% endraw %}</el-descriptions-item>
<!-- 可以添加更多公众号相关信息 -->
</el-descriptions>
</el-dialog>
<!-- 公共好友详情对话框 -->
<el-dialog title="公共好友详情" {% raw %}:visible.sync="publicDetailDialogVisible"{% endraw %} width="50%">
<div style="text-align: center; margin-bottom: 20px;">
<el-avatar
size="large"
{% raw %}:src="getHeadImage(currentPublic.wxid)"{% endraw %}
{% raw %}@error="() => true"{% endraw %}
style="width: 100px; height: 100px;">
<img src="/static/logo.png" />
</el-avatar>
</div>
<el-descriptions {% raw %}:column="1"{% endraw %} border>
<el-descriptions-item label="ID">{% raw %}{{ currentPublic.wxid }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="名称">{% raw %}{{ currentPublic.name }}{% endraw %}</el-descriptions-item>
<!-- 可以添加更多公共好友相关信息 -->
</el-descriptions>
</el-dialog>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
activeTab: 'groups',
searchQuery: '',
currentPage: 1,
pageSize: 10,
groupsList: [],
personalList: [],
officialList: [],
publicList: [],
headImages: {}, // 添加头像数据存储
statistics: {
total: 0,
groups: 0,
personal: 0,
official: 0,
public: 0
},
groupDetailDialogVisible: false,
userDetailDialogVisible: false,
officialDetailDialogVisible: false,
publicDetailDialogVisible: false,
currentGroup: {},
currentUser: {},
currentOfficial: {},
currentPublic: {},
// 新增群成员相关数据
groupMembersList: [],
groupMembersCurrentPage: 1,
groupMembersPageSize: 10,
groupMemberSearchQuery: '',
groupMembersLoading: false
};
},
computed: {
filteredGroups() {
const query = this.searchQuery.toLowerCase();
if (!query) {
return this.groupsList.slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
}
return this.groupsList.filter(group =>
group.wxid.toLowerCase().includes(query) ||
group.name.toLowerCase().includes(query)
).slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
},
filteredPersonal() {
const query = this.searchQuery.toLowerCase();
if (!query) {
return this.personalList.slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
}
return this.personalList.filter(user =>
user.wxid.toLowerCase().includes(query) ||
user.name.toLowerCase().includes(query)
).slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
},
filteredOfficial() {
const query = this.searchQuery.toLowerCase();
if (!query) {
return this.officialList.slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
}
return this.officialList.filter(official =>
official.wxid.toLowerCase().includes(query) ||
official.name.toLowerCase().includes(query)
).slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
},
filteredPublic() {
const query = this.searchQuery.toLowerCase();
if (!query) {
return this.publicList.slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
}
return this.publicList.filter(item =>
item.wxid.toLowerCase().includes(query) ||
item.name.toLowerCase().includes(query)
).slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
);
},
// 添加群成员过滤计算属性
filteredGroupMembers() {
const query = this.groupMemberSearchQuery.toLowerCase();
if (!query) {
return this.groupMembersList.slice(
(this.groupMembersCurrentPage - 1) * this.groupMembersPageSize,
this.groupMembersCurrentPage * this.groupMembersPageSize
);
}
return this.groupMembersList.filter(member =>
member.wxid.toLowerCase().includes(query) ||
(member.name && member.name.toLowerCase().includes(query))
).slice(
(this.groupMembersCurrentPage - 1) * this.groupMembersPageSize,
this.groupMembersCurrentPage * this.groupMembersPageSize
);
}
},
mounted() {
this.currentView = '10'; // 设置当前视图为通讯录管理
this.loadContactsData();
},
methods: {
loadContactsData() {
// 修改API请求路径从 /api/contacts/ 改为 /contacts/api/
// 加载统计数据
axios.get('/contacts/api/statistics')
.then(response => {
if (response.data.success) {
this.statistics = response.data.data;
}
})
.catch(error => {
console.error('加载联系人统计数据失败:', error);
this.$message.error('加载联系人统计数据失败');
});
// 加载群组数据
axios.get('/contacts/api/groups')
.then(response => {
if (response.data.success) {
const groups = response.data.data.groups;
this.groupsList = Object.entries(groups).map(([wxid, name]) => ({
wxid,
name
}));
}
})
.catch(error => {
console.error('加载群组数据失败:', error);
this.$message.error('加载群组数据失败');
});
// 加载个人联系人数据
axios.get('/contacts/api/personal')
.then(response => {
if (response.data.success) {
const personal = response.data.data.personal;
this.personalList = Object.entries(personal).map(([wxid, name]) => ({
wxid,
name
}));
}
})
.catch(error => {
console.error('加载个人联系人数据失败:', error);
this.$message.error('加载个人联系人数据失败');
});
// 加载公众号数据
axios.get('/contacts/api/official')
.then(response => {
if (response.data.success) {
const official = response.data.data.official;
this.officialList = Object.entries(official).map(([wxid, name]) => ({
wxid,
name
}));
}
})
.catch(error => {
console.error('加载公众号数据失败:', error);
this.$message.error('加载公众号数据失败');
});
// 加载公共好友数据
axios.get('/contacts/api/public')
.then(response => {
if (response.data.success) {
const publicFriends = response.data.data.public;
this.publicList = Object.entries(publicFriends).map(([wxid, name]) => ({
wxid,
name
}));
}
})
.catch(error => {
console.error('加载公共好友数据失败:', error);
this.$message.error('加载公共好友数据失败');
});
// 加载头像数据
axios.get('/contacts/api/head_images')
.then(response => {
if (response.data.success) {
this.headImages = response.data.data.head_images;
}
})
.catch(error => {
console.error('加载联系人头像数据失败:', error);
this.$message.error('加载联系人头像数据失败');
});
},
refreshContacts() {
this.loadContactsData();
this.$message.success('联系人数据已刷新');
},
handleTabClick(tab) {
this.currentPage = 1; // 切换选项卡时重置页码
},
// 添加获取头像的方法
getHeadImage(wxid) {
return this.headImages[wxid] || '';
},
handleSizeChange(size) {
this.pageSize = size;
},
handleCurrentChange(page) {
this.currentPage = page;
},
viewGroupDetails(group) {
this.currentGroup = group;
this.groupDetailDialogVisible = true;
this.loadGroupMembers(group.wxid);
},
viewUserDetails(user) {
this.currentUser = user;
this.userDetailDialogVisible = true;
},
viewOfficialDetails(official) {
this.currentOfficial = official;
this.officialDetailDialogVisible = true;
},
viewPublicDetails(publicFriend) {
this.currentPublic = publicFriend;
this.publicDetailDialogVisible = true;
},
// 添加加载群成员的方法
loadGroupMembers(roomid) {
this.groupMembersLoading = true;
this.groupMembersList = [];
this.groupMembersCurrentPage = 1;
axios.get(`/contacts/api/group_members/${roomid}`)
.then(response => {
if (response.data.success) {
const members = response.data.data.members;
console.log('获取到的群成员数据:', members); // 添加调试日志
this.groupMembersList = Object.entries(members).map(([wxid, name]) => ({
wxid,
name: name || wxid // 确保name不为空
}));
console.log('处理后的群成员列表:', this.groupMembersList); // 添加调试日志
} else {
console.error('获取群成员失败:', response.data.error);
this.$message.error('获取群成员失败');
}
})
.catch(error => {
console.error('加载群成员数据失败:', error);
this.$message.error('加载群成员数据失败');
})
.finally(() => {
this.groupMembersLoading = false;
});
},
// 群成员分页方法
handleGroupMembersSizeChange(size) {
this.groupMembersPageSize = size;
},
handleGroupMembersCurrentChange(page) {
this.groupMembersCurrentPage = page;
}
}
});
</script>
<style>
.stat-card {
text-align: center;
padding: 10px;
}
.stat-title {
font-size: 16px;
color: #606266;
margin-bottom: 10px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #409EFF;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.section-title {
margin: 20px 0 15px 0;
border-bottom: 1px solid #ebeef5;
padding-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title h3 {
margin: 0;
font-size: 18px;
color: #303133;
}
</style>
{% endblock %}