Files
YwxAppThink/addon/wxchat/view/index/index copy.html
T

1130 lines
39 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>企业微信 · Layui版</title>
<link rel="stylesheet" href="/assets/layui/css/layui.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
background: #f5f5f5;
}
.chat-container {
display: flex;
height: 100vh;
max-width: 1400px;
margin: 0 auto;
background: #fff;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
/* ========== 左侧联系人栏 ========== */
.chat-sidebar {
width: 260px;
border-right: 1px solid #e7e7e7;
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 15px;
border-bottom: 1px solid #e7e7e7;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-title {
font-size: 18px;
font-weight: bold;
color: #1aad19;
}
.sidebar-search {
padding: 10px 15px;
border-bottom: 1px solid #e7e7e7;
}
.sidebar-list {
flex: 1;
overflow-y: auto;
}
.contact-item {
display: flex;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
transition: background 0.2s;
}
.contact-item:hover,
.contact-item.active {
background: #f5f9fd;
}
.contact-item.active {
border-left: 3px solid #1aad19;
padding-left: 12px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #1aad19;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 10px;
font-size: 16px;
flex-shrink: 0;
}
.avatar.group {
background: #5fb878;
}
.contact-info {
flex: 1;
min-width: 0;
}
.contact-name {
font-weight: bold;
font-size: 14px;
color: #333;
margin-bottom: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.contact-msg {
font-size: 12px;
color: #999;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.contact-time {
font-size: 11px;
color: #999;
}
.badge {
display: inline-block;
min-width: 18px;
height: 18px;
border-radius: 9px;
background: #ff4d4f;
color: white;
font-size: 12px;
text-align: center;
line-height: 18px;
padding: 0 4px;
margin-left: 5px;
}
.online-dot {
width: 8px;
height: 8px;
background: #52c41a;
border-radius: 50%;
display: inline-block;
margin-left: 5px;
vertical-align: middle;
}
/* ========== 右侧聊天区 ========== */
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
}
.chat-header {
padding: 15px 20px;
border-bottom: 1px solid #e7e7e7;
display: flex;
align-items: center;
}
.chat-avatar {
width: 45px;
height: 45px;
border-radius: 50%;
background: #1aad19;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 18px;
margin-right: 15px;
flex-shrink: 0;
}
.chat-info {
flex: 1;
}
.chat-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.chat-status {
font-size: 12px;
color: #999;
margin-top: 3px;
}
.chat-status.online::after {
content: "";
display: inline-block;
width: 6px;
height: 6px;
background: #52c41a;
border-radius: 50%;
margin-left: 5px;
vertical-align: middle;
}
.chat-body {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #f5f5f5;
}
.message-list {
max-width: 720px;
margin: 0 auto;
}
/* ========== 消息气泡 ========== */
.message-item {
margin-bottom: 15px;
display: flex;
}
.message-item.self {
justify-content: flex-end;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #d9d9d9;
margin: 0 10px 0 0;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
}
.message-item.self .message-avatar {
margin: 0 0 0 10px;
}
.message-content {
max-width: 65%;
position: relative;
}
.message-item.self .message-content {
align-items: flex-end;
}
.msg-bubble {
padding: 10px 15px;
border-radius: 18px;
line-height: 1.5;
font-size: 14px;
word-break: break-word;
position: relative;
display: inline-block;
max-width: 100%;
}
.msg-bubble.text {
background: #9eea6a;
color: #000;
border-bottom-right-radius: 4px;
}
.message-item.self .msg-bubble.text {
background: #1aad19;
color: white;
border-bottom-right-radius: 18px;
border-bottom-left-radius: 4px;
}
.msg-bubble.image {
padding: 0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.msg-bubble.image img {
display: block;
max-width: 300px;
max-height: 400px;
}
.msg-bubble.file {
background: #f7f7f7;
border: 1px solid #e5e5e5;
border-radius: 8px;
padding: 12px;
width: 280px;
}
.file-icon {
font-size: 24px;
color: #1aad19;
margin-bottom: 5px;
}
.file-name {
font-weight: bold;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-meta {
font-size: 12px;
color: #999;
margin-top: 3px;
}
.file-size::before {
content: "· ";
}
/* 气泡三角 */
.msg-bubble::after {
content: "";
position: absolute;
bottom: -6px;
width: 0;
height: 0;
border: 6px solid transparent;
}
.message-item:not(.self) .msg-bubble.text::after {
left: -6px;
border-right-color: #9eea6a;
border-bottom-color: transparent;
}
.message-item.self .msg-bubble.text::after {
right: -6px;
border-left-color: #1aad19;
border-bottom-color: transparent;
}
.msg-time {
font-size: 11px;
color: #999;
text-align: center;
margin: 8px 0;
}
.msg-status {
font-size: 11px;
color: #999;
margin-top: 5px;
text-align: right;
}
.msg-status.sending::after {
content: "·";
animation: blink 1s infinite;
}
.msg-status.failed {
color: #ff4d4f;
}
@keyframes blink {
50% {
opacity: 0;
}
}
/* ========== 输入区域 ========== */
.chat-footer {
border-top: 1px solid #e7e7e7;
padding: 10px 15px;
background: white;
}
.chat-tools {
display: flex;
padding-bottom: 10px;
border-bottom: 1px dashed #e7e7e7;
}
.tool-btn {
width: 36px;
height: 36px;
border-radius: 50%;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
cursor: pointer;
color: #666;
font-size: 18px;
transition: all 0.2s;
}
.tool-btn:hover {
background: #e6e6e6;
color: #1aad19;
}
.tool-btn.active {
background: #e6f7ff;
color: #1890ff;
}
.tool-btn i {
font-size: 16px;
}
.chat-input-wrap {
display: flex;
margin-top: 10px;
}
.chat-input {
flex: 1;
border: 1px solid #d9d9d9;
border-radius: 20px;
padding: 0 20px;
height: 40px;
outline: none;
resize: none;
font-size: 14px;
line-height: 40px;
}
.chat-input:focus {
border-color: #1aad19;
box-shadow: 0 0 0 2px rgba(26, 173, 25, 0.15);
}
.chat-send {
width: 80px;
height: 40px;
background: #1aad19;
color: white;
border: none;
border-radius: 20px;
margin-left: 10px;
font-size: 15px;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
.chat-send:hover {
background: #179716;
}
.chat-send:active {
transform: scale(0.98);
}
/* ========== 表情面板 ========== */
.emoji-panel {
display: none;
position: absolute;
bottom: 100px;
left: 15px;
background: white;
border: 1px solid #d9d9d9;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
padding: 10px;
z-index: 1000;
width: 360px;
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 8px;
}
.emoji-item {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
cursor: pointer;
font-size: 20px;
transition: background 0.2s;
}
.emoji-item:hover {
background: #f5f5f5;
}
/* ========== 系统消息 ========== */
.system-msg {
text-align: center;
color: #999;
font-size: 13px;
padding: 8px 15px;
background: #f9f9f9;
border-radius: 15px;
display: inline-block;
margin: 10px auto;
max-width: 80%;
}
/* ========== 响应式 ========== */
@media (max-width: 992px) {
.chat-container {
flex-direction: column;
}
.chat-sidebar {
width: 100%;
height: 40%;
border-right: none;
border-bottom: 1px solid #e7e7e7;
}
.contact-item {
padding: 10px;
}
.message-content {
max-width: 85%;
}
}
/* ========== 连接状态 ========== */
.conn-status {
position: fixed;
top: 15px;
right: 15px;
padding: 8px 15px;
border-radius: 20px;
color: white;
font-size: 13px;
z-index: 9999;
display: none;
}
.status-connected {
background: #52c41a;
}
.status-disconnected {
background: #ff4d4f;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(255, 77, 79, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(255, 77, 79, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 77, 79, 0);
}
}
/* ========== 滚动条优化 ========== */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>
</head>
<body>
<div class="chat-container">
<!-- 左侧联系人栏 -->
<div class="chat-sidebar">
<div class="sidebar-header">
<div class="sidebar-title">💬 企业微信</div>
<div class="layui-badge-dot layui-bg-green" title="在线"></div>
</div>
<div class="sidebar-search">
<input type="text" id="contact-search" placeholder="搜索联系人/群聊" class="layui-input" style="padding-left: 30px;">
<i class="layui-icon layui-icon-search" style="position: absolute; left: 10px; top: 9px; color: #999;"></i>
</div>
<div class="sidebar-list" id="contact-list">
<!-- 联系人列表由JS动态生成 -->
</div>
</div>
<!-- 右侧聊天区 -->
<div class="chat-main">
<div class="chat-header">
<div class="chat-avatar" id="current-avatar">T</div>
<div class="chat-info">
<div class="chat-title" id="current-title">选择联系人开始聊天</div>
<div class="chat-status" id="current-status">未连接</div>
</div>
<div>
<button class="layui-btn layui-btn-sm layui-btn-primary" id="conn-status-btn">
<i class="layui-icon layui-icon-refresh"></i> 重连
</button>
</div>
</div>
<div class="chat-body">
<div class="message-list" id="message-list">
<div class="system-msg">👈 请从左侧选择联系人或群聊</div>
</div>
</div>
<div class="chat-footer">
<div class="chat-tools">
<div class="tool-btn" id="emoji-btn" title="表情">
<i class="layui-icon layui-icon-face-smile"></i>
</div>
<div class="tool-btn" id="image-btn" title="图片">
<i class="layui-icon layui-icon-picture"></i>
<input type="file" id="image-upload" accept="image/*" style="display:none;">
</div>
<div class="tool-btn" id="file-btn" title="文件">
<i class="layui-icon layui-icon-file"></i>
<input type="file" id="file-upload" style="display:none;">
</div>
<div class="tool-btn" title="语音"><i class="layui-icon layui-icon-voice"></i></div>
<div class="tool-btn" title="视频通话"><i class="layui-icon layui-icon-video"></i></div>
</div>
<div class="chat-input-wrap">
<textarea class="chat-input" id="chat-input" placeholder="输入消息... (Ctrl+Enter发送)" rows="1"></textarea>
<button class="chat-send" id="send-btn">发送</button>
</div>
</div>
</div>
</div>
<!-- 表情面板 -->
<div class="emoji-panel" id="emoji-panel">
<div class="emoji-grid" id="emoji-grid">
<!-- 表情由JS动态生成 -->
</div>
</div>
<!-- 连接状态提示 -->
<div class="conn-status" id="conn-status">● 已连接</div>
<!-- 用户凭证(实际项目应通过AJAX获取) -->
<input type="hidden" id="user-token" value="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx">
<input type="hidden" id="user-uid" value="u_10086">
<input type="hidden" id="user-nickname" value="唐">
<input type="hidden" id="user-avatar" value="T">
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="member"></script>
<script>
layui.use(['layer', 'element', 'util', 'upload', 'setter', 'chatClient'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var util = layui.util;
var upload = layui.upload;
var ChatClient = layui.chatClient;
const { access_token } = layui.setter.getTokens();
// ========== 初始化变量 ==========
var currentSession = null; // { id: 'u_10086', type: 'single', name: '张三', avatar: 'Z' }
var messageQueue = []; // 未发送消息队列
var chatClient = null;
var emojiPanelVisible = false;
// ========== 模拟联系人数据(实际项目从服务端获取) ==========
var contacts = [
{ id: '88888891', type: 'single', name: '张三', avatar: 'Z', online: true, lastMsg: '好的,明天见', time: '10:23', unread: 2 },
{ id: 'u_10087', type: 'single', name: '李四', avatar: 'L', online: true, lastMsg: '文件已收到', time: '昨天', unread: 0 },
{ id: 'g_2024', type: 'group', name: '项目组', avatar: 'P', online: true, lastMsg: '[图片]', time: '14:15', unread: 9 },
{ id: 'u_10088', type: 'single', name: '王五', avatar: 'W', online: true, lastMsg: '在吗?', time: '周一', unread: 0 },
{ id: 'g_2025', type: 'group', name: '技术部', avatar: 'J', online: true, lastMsg: '会议推迟到下午', time: '09:30', unread: 1 }
];
// ========== 渲染联系人列表 ==========
function renderContacts(filter = '') {
var html = '';
contacts.forEach(contact => {
if (filter && !contact.name.includes(filter)) return;
var badge = contact.unread > 0 ? `<span class="badge">${contact.unread > 99 ? '99+' : contact.unread}</span>` : '';
var onlineDot = contact.online ? '<span class="online-dot"></span>' : '';
var activeClass = currentSession && currentSession.id === contact.id ? 'active' : '';
html += `
<div class="contact-item ${activeClass}" data-id="${contact.id}" data-type="${contact.type}" data-name="${contact.name}" data-avatar="${contact.avatar}">
<div class="avatar ${contact.type === 'group' ? 'group' : ''}">${contact.avatar}</div>
<div class="contact-info">
<div class="contact-name">${contact.name}${onlineDot}</div>
<div class="contact-msg">${contact.lastMsg}</div>
</div>
<div class="contact-time">${contact.time}${badge}</div>
</div>
`;
});
$('#contact-list').html(html || '<div style="text-align:center;padding:20px;color:#999">无匹配联系人</div>');
}
// ========== 渲染消息 ==========
function renderMessage(packet, isSelf = false) {
var header = packet.header || {};
var payload = packet.payload || {};
var ext = packet.ext || {};
var msgType = header.type || 'text';
var timeStr = util.toDateString(header.server_time || Date.now(), 'yyyy-MM-dd HH:mm');
// 消息容器
var msgClass = isSelf ? 'message-item self' : 'message-item';
var avatarText = isSelf ? $('#user-avatar').val() : (ext.from_avatar || header.from.substring(0, 1).toUpperCase());
var avatarClass = isSelf ? 'message-avatar' : 'message-avatar';
// 消息内容
var contentHtml = '';
switch (msgType) {
case 'text':
// XSS防护 + 表情转换
var text = util.escape(payload.content || '');
text = text.replace(/\[([^\]]+)\]/g, (match, p1) => {
var emojiMap = { '微笑': '😄', '大笑': '😂', '点赞': '👍', '爱心': '❤️', '哭': '😭', '生气': '😠' };
return emojiMap[p1] || match;
});
contentHtml = `<div class="msg-bubble text">${text}</div>`;
break;
case 'image':
var imgData = payload.content || {};
contentHtml = `
<div class="msg-bubble image">
<img src="${imgData.thumb || imgData.url}"
data-original="${imgData.url}"
onclick="previewImage(this)"
style="cursor: zoom-in;"
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNHB4IiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5JbWFnZSBMb2FkaW5nPC90ZXh0Pjwvc3ZnPg=='">
${imgData.size ? `<div style="padding:5px 10px;font-size:12px;color:#999;text-align:center">${formatFileSize(imgData.size)}</div>` : ''}
</div>
`;
break;
case 'file':
var fileData = payload.content || {};
contentHtml = `
<div class="msg-bubble file" onclick="downloadFile('${fileData.url}', '${fileData.name}')">
<div class="file-icon">📄</div>
<div class="file-name" title="${fileData.name}">${fileData.name}</div>
<div class="file-meta">
<span>${fileData.type || '未知类型'}</span>
<span class="file-size">${formatFileSize(fileData.size)}</span>
</div>
</div>
`;
break;
case 'system':
$('#message-list').append(`<div class="system-msg">${util.escape(payload.content)}</div>`);
return;
}
// 消息状态(仅自己发送的消息)
var statusHtml = isSelf ? `<div class="msg-status ${packet.status || 'sent'}">${getStatusText(packet.status)}</div>` : '';
var msgHtml = `
<div class="${msgClass}" data-packet-id="${header.id || ''}">
${!isSelf ? `<div class="${avatarClass}">${avatarText}</div>` : ''}
<div class="message-content">
${contentHtml}
${statusHtml}
</div>
${isSelf ? `<div class="${avatarClass}">${avatarText}</div>` : ''}
</div>
`;
// 添加时间分隔(每5分钟一条)
var lastMsgTime = $('#message-list .msg-time:last').data('timestamp') || 0;
var currentTimestamp = header.server_time || Date.now();
if (currentTimestamp - lastMsgTime > 5 * 60 * 1000) {
$('#message-list').append(`<div class="msg-time" data-timestamp="${currentTimestamp}">${timeStr}</div>`);
}
$('#message-list').append(msgHtml);
scrollToBottom();
}
// ========== 工具函数 ==========
function scrollToBottom() {
var container = $('.chat-body');
container.scrollTop(container[0].scrollHeight);
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
else return (bytes / 1048576).toFixed(1) + ' MB';
}
function getStatusText(status) {
const map = { sending: '发送中', sent: '已发送', failed: '发送失败', received: '已读' };
return map[status] || '';
}
function showConnStatus(msg, type = 'connected') {
var $status = $('#conn-status');
$status.text(msg).removeClass().addClass('conn-status status-' + type).fadeIn();
setTimeout(() => $status.fadeOut(), type === 'connected' ? 2000 : 5000);
}
// ========== 初始化ChatClient ==========
function initChat() {
if (chatClient) return;
chatClient = new ChatClient({
token: access_token,
wsUrl: 'wss://dev.xixingwl.cn:2346',
uid: $('#user-uid').val(),
onMessage: function (packet) {
// 处理收到的消息
if (currentSession && packet.header &&
(packet.header.to === currentSession.id || packet.header.from === currentSession.id)) {
renderMessage(packet, false);
// 更新联系人最后消息(简化版)
if (packet.header.type === 'text') {
contacts.find(c => c.id === packet.header.from).lastMsg = packet.payload.content;
renderContacts();
}
}
},
onError: function (msg) {
layer.msg('❌ ' + msg, { icon: 2, time: 3000 });
showConnStatus('● 连接断开: ' + msg, 'disconnected');
$('#current-status').text('离线').removeClass('online');
}
});
// 连接状态监听
chatClient.onConnect = function () {
showConnStatus('● 已连接到服务器', 'connected');
$('#current-status').text('在线').addClass('online');
$('#conn-status-btn').html('<i class="layui-icon layui-icon-refresh"></i> 重连').prop('disabled', false);
// 重发队列中消息
messageQueue.forEach(msg => {
chatClient.send(msg.payload, msg.type);
});
messageQueue = [];
};
chatClient.onDisconnect = function () {
showConnStatus('● 连接已断开,正在重连...', 'disconnected');
$('#current-status').text('离线').removeClass('online');
$('#conn-status-btn').html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i> 重连中').prop('disabled', true);
};
// 尝试连接
chatClient.connect();
}
// ========== 事件绑定 ==========
$(function () {
// 渲染初始联系人
renderContacts();
// 联系人点击
$(document).on('click', '.contact-item', function () {
$('.contact-item').removeClass('active');
$(this).addClass('active');
currentSession = {
id: $(this).data('id'),
type: $(this).data('type'),
name: $(this).data('name'),
avatar: $(this).data('avatar')
};
console.log('切换会话:', currentSession);
// 更新顶部信息
$('#current-title').text(currentSession.name);
$('#current-avatar').text(currentSession.avatar).css('background', currentSession.type === 'group' ? '#5fb878' : '#1aad19');
$('#current-status').text(currentSession.type === 'group' ? '群聊' : (contacts.find(c => c.id === currentSession.id)?.online ? '在线' : '离线'));
$('#message-list').html(`<div class="system-msg">与 ${currentSession.name} 的聊天记录</div>`);
if (!chatClient) initChat();
var contact = contacts.find(c => c.id === currentSession.id);
if (contact && contact.unread > 0) {
contact.unread = 0;
renderContacts();
}
chatClient.setCurrentSession(currentSession.id, currentSession.type);
});
// 搜索联系人
$('#contact-search').on('input', function () {
renderContacts($(this).val().trim());
});
// 发送消息
function sendMessage() {
var content = $.trim($('#chat-input').val());
if (!content || !currentSession) return;
if (!chatClient || !chatClient.isConnected) {
layer.msg('未连接服务器,请稍候...', { icon: 5 });
return;
}
console.log('发送消息:', currentSession);
// 本地预渲染(发送中状态)
var fakePacket = {
header: {
id: 'temp_' + Date.now(),
server_time: Date.now(),
from: $('#user-uid').val(),
to: currentSession.id,
type: 'text'
},
payload: { content: content },
status: 'sending'
};
renderMessage(fakePacket, true);
// 发送真实消息
var packetId = chatClient.send({ content: content }, 'text');
// 更新本地消息状态(简化版,实际用ACK回调更新)
setTimeout(() => {
$(`.message-item.self[data-packet-id="${fakePacket.header.id}"] .msg-status`)
.removeClass('sending')
.addClass('sent')
.text('已发送');
}, 300);
$('#chat-input').val('').focus();
}
$('#send-btn').click(sendMessage);
$('#chat-input').keypress(function (e) {
if (e.ctrlKey && e.which === 13) sendMessage(); // Ctrl+Enter
});
// 重连按钮
$('#conn-status-btn').click(function () {
if (chatClient) {
chatClient.disconnect();
setTimeout(() => chatClient.connect(), 500);
} else {
initChat();
}
});
// ========== 表情面板 ==========
var emojis = ['😄', '😂', '😊', '😍', '😭', '😡', '👍', '👏', '💪', '🎉', '❤️', '💯', '🙏', '🔥', '🚀', '💡'];
var emojiHtml = emojis.map(e => `<div class="emoji-item">${e}</div>`).join('');
$('#emoji-grid').html(emojiHtml);
$('#emoji-btn').click(function () {
emojiPanelVisible = !emojiPanelVisible;
$('#emoji-panel').toggle(emojiPanelVisible);
});
$(document).on('click', '.emoji-item', function () {
var currentVal = $('#chat-input').val();
$('#chat-input').val(currentVal + $(this).text()).focus();
});
// 点击外部关闭表情面板
$(document).click(function (e) {
if (emojiPanelVisible && !$(e.target).closest('#emoji-panel, #emoji-btn').length) {
$('#emoji-panel').hide();
emojiPanelVisible = false;
}
});
// ========== 图片上传 ==========
$('#image-btn').click(function () {
$('#image-upload').click();
});
$('#image-upload').change(function (e) {
var file = e.target.files[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
layer.msg('图片不能超过 5MB', { icon: 2 });
return;
}
// 本地预览 + 压缩(简化版,实际用browser-image-compression
var reader = new FileReader();
reader.onload = function (ev) {
// 生成缩略图(Base64
var img = new Image();
img.onload = function () {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var maxWidth = 300;
var ratio = Math.min(maxWidth / img.width, 1);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
var thumbBase64 = canvas.toDataURL('image/jpeg', 0.7);
// 模拟上传到CDN(实际调用上传接口)
layer.msg('正在上传图片...', { time: 0, shade: 0.3 });
setTimeout(() => {
layer.closeAll('msg');
// 模拟CDN URL
var cdnUrl = 'https://cdn.example.com/images/' + Date.now() + '.jpg';
// 发送消息(含缩略图+CDN URL)
if (chatClient && chatClient.isConnected && currentSession) {
chatClient.send({
content: {
type: file.type,
name: file.name,
size: file.size,
width: canvas.width,
height: canvas.height,
thumb: thumbBase64,
url: cdnUrl
}
}, 'image');
// 本地预渲染
renderMessage({
header: { server_time: Date.now(), type: 'image' },
payload: { content: { thumb: thumbBase64, url: cdnUrl, size: file.size } }
}, true);
}
}, 800);
};
img.src = ev.target.result;
};
reader.readAsDataURL(file);
this.value = null; // 重置input
});
// ========== 文件上传 ==========
$('#file-btn').click(function () {
$('#file-upload').click();
});
$('#file-upload').change(function (e) {
var file = e.target.files[0];
if (!file) return;
if (file.size > 50 * 1024 * 1024) {
layer.msg('文件不能超过 50MB', { icon: 2 });
return;
}
// 模拟上传
layer.msg('正在上传文件...', { time: 0, shade: 0.3 });
setTimeout(() => {
layer.closeAll('msg');
var cdnUrl = 'https://cdn.example.com/files/' + encodeURIComponent(file.name);
if (chatClient && chatClient.isConnected && currentSession) {
chatClient.send({
content: {
type: file.type || 'application/octet-stream',
name: file.name,
size: file.size,
url: cdnUrl
}
}, 'file');
// 本地预渲染
renderMessage({
header: { server_time: Date.now(), type: 'file' },
payload: {
content: {
type: file.type.split('/')[0],
name: file.name,
size: file.size,
url: cdnUrl
}
}
}, true);
}
}, 1200);
this.value = null;
});
// 初始化连接(可选:自动连接)
initChat();
// 页面卸载前清理
$(window).on('beforeunload', function () {
if (chatClient) chatClient.disconnect();
});
});
});
// ========== 全局函数(图片预览/文件下载) ==========
function previewImage(el) {
var src = el.dataset.original || el.src;
layer.photos({
photos: { "title": "", "id": Date.now(), "start": 0, "data": [{ "alt": "预览", "pid": 1, "src": src, "thumb": src }] },
anim: 5
});
}
function downloadFile(url, name) {
layer.confirm(`确定下载文件 "${name}"`, { icon: 3, title: '文件下载' }, function (index) {
// 实际项目:跳转到下载链接或调用下载接口
layer.msg('模拟下载: ' + url, { icon: 1 });
layer.close(index);
});
}
</script>
</body>
</html>