Files
abot/admin/dashboard/templates/contacts_management.html
T
2026-01-20 10:46:59 +08:00

1042 lines
45 KiB
HTML

{% 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-button
type="success"
size="small"
style="float: right; margin-left: 10px;"
{% raw %}@click="updateContacts" {% 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="220">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
{% raw %}@click="viewGroupDetails(scope.row)" {% endraw %}>
查看详情
</el-button>
<el-button
size="mini"
type="success"
{% raw %}@click="openChatDialog(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="220">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
{% raw %}@click="viewUserDetails(scope.row)" {% endraw %}>
查看详情
</el-button>
<el-button
size="mini"
type="success"
{% raw %}@click="openChatDialog(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="微信昵称" width="200"></el-table-column>
<el-table-column prop="name" label="群昵称" width="200"></el-table-column>
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<el-tag size="mini" :type="scope.row.status === 1 ? 'success' : 'info'">
{% raw %}{{ scope.row.status === 1 ? '在群' : '已退群' }}{% endraw %}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="latest_active_time" label="活跃时间" >
<template slot-scope="scope">
{% raw %}{{ scope.row.latest_active_time || '-' }}{% endraw %}
</template>
</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="currentPublic.small_head_img_url" {% 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>
<!-- 聊天对话框 -->
<el-dialog title="聊天" {% raw %}:visible.sync="chatDialogVisible" {% endraw %} width="60%" :close-on-click-modal="false">
<div class="chat-container">
<!-- 消息列表 -->
<div class="message-list" ref="messageList">
{% raw %}
<div v-for="(msg, index) in chatMessages" :key="index" class="message-item" :class="{'message-self': msg.isSelf}">
<div class="message-content">
<div v-if="msg.type === 'text'" v-text="msg.content"></div>
<div v-else-if="msg.type === 'image'">
<img :src="msg.content" style="max-width: 200px; max-height: 200px;">
</div>
<div v-else-if="msg.type === 'voice'">
<audio controls :src="msg.content"></audio>
</div>
<div v-else-if="msg.type === 'video'">
<video controls :src="msg.content" style="max-width: 200px;"></video>
</div>
<div v-else-if="msg.type === 'link'">
<a :href="msg.content.url" target="_blank" v-text="msg.content.title"></a>
<p v-text="msg.content.description"></p>
</div>
</div>
<div class="message-time" v-text="msg.time"></div>
</div>
{% endraw %}
</div>
<!-- 输入区域 -->
<div class="input-area">
<el-input
type="textarea"
:rows="3"
placeholder="请输入消息..."
{% raw %}v-model="messageInput" {% endraw %}
@keyup.enter.native="sendTextMessage">
</el-input>
<div class="toolbar">
<el-upload
class="upload-demo"
action="#"
{% raw %}:http-request="uploadImage" {% endraw %}
:show-file-list="false">
<el-button size="small" type="primary">图片</el-button>
</el-upload>
<el-upload
class="upload-demo"
action="#"
{% raw %}:http-request="uploadVoice" {% endraw %}
:show-file-list="false">
<el-button size="small" type="primary">语音</el-button>
</el-upload>
<el-upload
class="upload-demo"
action="#"
{% raw %}:http-request="uploadVideo" {% endraw %}
:show-file-list="false">
<el-button size="small" type="primary">视频</el-button>
</el-upload>
<el-button size="small" type="primary" {% raw %}@click="showLinkDialog" {% endraw %}>链接</el-button>
<el-button size="small" type="primary" {% raw %}@click="sendTextMessage" {% endraw %}>发送</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 链接消息对话框 -->
<el-dialog title="发送链接" {% raw %}:visible.sync="linkDialogVisible" {% endraw %} width="30%">
<el-form :model="linkForm" label-width="80px">
<el-form-item label="链接">
<el-input v-model="linkForm.url" placeholder="请输入链接"></el-input>
</el-form-item>
<el-form-item label="标题">
<el-input v-model="linkForm.title" placeholder="请输入标题"></el-input>
</el-form-item>
<el-form-item label="描述">
<el-input type="textarea" v-model="linkForm.description" placeholder="请输入描述"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="linkDialogVisible = false">取消</el-button>
<el-button type="primary" @click="sendLinkMessage">发送</el-button>
</span>
</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,
chatDialogVisible: false,
currentChatUser: null,
messageInput: '',
chatMessages: [],
linkDialogVisible: false,
linkForm: {
url: '',
title: '',
description: ''
}
};
},
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('加载联系人头像数据失败');
});
},
updateContacts() {
this.$message.info('正在更新通讯录...');
axios.post('/contacts/api/update')
.then(res => {
if (res.data.success) {
this.$message.success('通讯录更新成功!');
this.refreshContacts();
} else {
this.$message.error(res.data.message || '通讯录更新失败');
}
})
.catch(() => {
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 = members.map(item => ({
wxid: item.wxid,
name: item.nick_name,
display_name: item.display_name,
status: item.status,
latest_active_time: item.latest_active_time,
small_head_img_url:item.small_head_img_url
}));
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;
},
// 打开聊天对话框
openChatDialog(user) {
this.currentChatUser = user;
this.chatDialogVisible = true;
this.chatMessages = [];
},
// 发送文本消息
async sendTextMessage() {
if (!this.messageInput.trim()) return;
try {
const response = await axios.post('/contacts/api/send_message', {
wxid: this.currentChatUser.wxid,
type: 'text',
content: this.messageInput
});
if (response.data.success) {
this.chatMessages.push({
type: 'text',
content: this.messageInput,
isSelf: true,
time: new Date().toLocaleTimeString()
});
this.messageInput = '';
this.$nextTick(() => {
this.scrollToBottom();
});
}
} catch (error) {
this.$message.error('发送消息失败');
}
},
// 上传图片
async uploadImage(options) {
const file = options.file;
const formData = new FormData();
formData.append('file', file);
formData.append('wxid', this.currentChatUser.wxid);
formData.append('type', 'image');
try {
const response = await axios.post('/contacts/api/send_message', formData);
if (response.data.success) {
this.chatMessages.push({
type: 'image',
content: response.data.url,
isSelf: true,
time: new Date().toLocaleTimeString()
});
this.$nextTick(() => {
this.scrollToBottom();
});
}
} catch (error) {
this.$message.error('发送图片失败');
}
},
// 上传语音
async uploadVoice(options) {
const file = options.file;
const formData = new FormData();
formData.append('file', file);
formData.append('wxid', this.currentChatUser.wxid);
formData.append('type', 'voice');
try {
const response = await axios.post('/contacts/api/send_message', formData);
if (response.data.success) {
this.chatMessages.push({
type: 'voice',
content: response.data.url,
isSelf: true,
time: new Date().toLocaleTimeString()
});
this.$nextTick(() => {
this.scrollToBottom();
});
}
} catch (error) {
this.$message.error('发送语音失败');
}
},
// 上传视频
async uploadVideo(options) {
const file = options.file;
const formData = new FormData();
formData.append('file', file);
formData.append('wxid', this.currentChatUser.wxid);
formData.append('type', 'video');
try {
const response = await axios.post('/contacts/api/send_message', formData);
if (response.data.success) {
this.chatMessages.push({
type: 'video',
content: response.data.url,
isSelf: true,
time: new Date().toLocaleTimeString()
});
this.$nextTick(() => {
this.scrollToBottom();
});
}
} catch (error) {
this.$message.error('发送视频失败');
}
},
// 显示链接对话框
showLinkDialog() {
this.linkForm = {
url: '',
title: '',
description: ''
};
this.linkDialogVisible = true;
},
// 发送链接消息
async sendLinkMessage() {
if (!this.linkForm.url) {
this.$message.warning('请输入链接');
return;
}
try {
const response = await axios.post('/contacts/api/send_message', {
wxid: this.currentChatUser.wxid,
type: 'link',
content: this.linkForm
});
if (response.data.success) {
this.chatMessages.push({
type: 'link',
content: this.linkForm,
isSelf: true,
time: new Date().toLocaleTimeString()
});
this.linkDialogVisible = false;
this.$nextTick(() => {
this.scrollToBottom();
});
}
} catch (error) {
this.$message.error('发送链接失败');
}
},
// 滚动到底部
scrollToBottom() {
const messageList = this.$refs.messageList;
if (messageList) {
messageList.scrollTop = messageList.scrollHeight;
}
}
},
});
</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;
}
.chat-container {
display: flex;
flex-direction: column;
height: 500px;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #f5f5f5;
}
.message-item {
margin-bottom: 15px;
display: flex;
flex-direction: column;
}
.message-self {
align-items: flex-end;
}
.message-content {
max-width: 70%;
padding: 10px;
border-radius: 5px;
background: #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.message-self .message-content {
background: #95ec69;
}
.message-time {
font-size: 12px;
color: #999;
margin-top: 5px;
}
.input-area {
padding: 20px;
background: #fff;
border-top: 1px solid #eee;
}
.toolbar {
margin-top: 10px;
display: flex;
gap: 10px;
}
</style>
{% endblock %}