Skip to content

接口名称

消息查询API -(IFeishuUserV1Message)

功能描述

飞书消息查询接口,用于对飞书聊天中的消息进行查询和基本操作。消息即飞书聊天中的一条消息,当前接口使用用户令牌访问,适用于用户应用场景。用户可以对自己的消息进行撤回、添加表情回复等操作,以及查询消息内容和表情回复列表。

参考文档

飞书消息管理API文档

函数列表

函数名称功能描述认证方式HTTP 方法
RevokeMessageAsync撤回指定消息用户令牌DELETE
AddMessageReactionsAsync添加消息表情回复用户令牌POST
GetMessageReactionsPageListAsync获取消息表情回复列表用户令牌GET
DeleteMessageReactionsAsync删除消息表情回复用户令牌DELETE

函数详细内容

函数名称:撤回消息

函数签名

csharp
Task<FeishuNullDataApiResult?> RevokeMessageAsync(
    [Path] string message_id,
    CancellationToken cancellationToken = default);

认证:用户令牌

参数

参数名类型必填说明示例值
message_idstring✅ 必填待撤回的消息ID"om_dc13264520392913993dd051dba21dcf"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success"
}

常见错误响应:

json
{
  "code": 404,
  "msg": "消息不存在"
}
json
{
  "code": 403,
  "msg": "无权限撤回该消息"
}
json
{
  "code": 400,
  "msg": "消息发送超过2分钟,无法撤回"
}

说明

  • 用户只能撤回自己发送的消息
  • 消息撤回有时间限制(通常为2分钟)
  • 撤回后消息将在聊天界面中显示为"已撤回"

代码示例

javascript
// 用户消息撤回确认系统
async function confirmAndRevokeMessage(messageId, messageContent) {
  // 检查消息是否可以被撤回
  const canRevoke = await checkIfMessageCanBeRevoked(messageId);
  
  if (!canRevoke.canRevoke) {
    showMessageNotification(canRevoke.reason, 'warning');
    return;
  }

  // 显示撤回确认对话框
  const confirmed = await showRevokeConfirmationDialog(messageContent);
  
  if (confirmed) {
    await performMessageRevocation(messageId);
  }
}

// 检查消息是否可以撤回
async function checkIfMessageCanBeRevoked(messageId) {
  try {
    // 获取消息详情(这里假设有获取消息详情的方法)
    const messageDetails = await getMessageDetails(messageId);
    
    // 检查消息发送者是否为当前用户
    if (messageDetails.sender.user_id !== currentUser.id) {
      return {
        canRevoke: false,
        reason: '您只能撤回自己发送的消息'
      };
    }

    // 检查消息发送时间(撤回时间限制)
    const messageAge = Date.now() - (messageDetails.create_time * 1000);
    const revokeTimeLimit = 2 * 60 * 1000; // 2分钟
    
    if (messageAge > revokeTimeLimit) {
      return {
        canRevoke: false,
        reason: '消息发送超过2分钟,无法撤回'
      };
    }

    return { canRevoke: true };
  } catch (error) {
    console.error('检查消息撤回状态失败:', error);
    return {
      canRevoke: false,
      reason: '无法确认消息状态,请稍后重试'
    };
  }
}

// 显示撤回确认对话框
async function showRevokeConfirmationDialog(messageContent) {
  return new Promise((resolve) => {
    const dialog = document.createElement('div');
    dialog.className = 'revoke-confirmation-dialog';
    dialog.innerHTML = `
      <div class="dialog-content">
        <h3>确认撤回消息</h3>
        <div class="message-preview">
          <strong>消息内容:</strong>
          <p>${messageContent.substring(0, 50)}${messageContent.length > 50 ? '...' : ''}</p>
        </div>
        <div class="warning-text">
          ⚠️ 撤回后消息将无法恢复,所有用户将看到"已撤回"提示
        </div>
        <div class="dialog-buttons">
          <button class="confirm-revoke-btn" id="confirm-revoke">确认撤回</button>
          <button class="cancel-revoke-btn" id="cancel-revoke">取消</button>
        </div>
      </div>
    `;

    document.body.appendChild(dialog);

    // 添加事件监听
    document.getElementById('confirm-revoke').onclick = () => {
      document.body.removeChild(dialog);
      resolve(true);
    };

    document.getElementById('cancel-revoke').onclick = () => {
      document.body.removeChild(dialog);
      resolve(false);
    };

    // 点击背景关闭
    dialog.onclick = (e) => {
      if (e.target === dialog) {
        document.body.removeChild(dialog);
        resolve(false);
      }
    };
  });
}

// 执行消息撤回
async function performMessageRevocation(messageId) {
  try {
    showLoadingIndicator('正在撤回消息...');

    const response = await feishuUserV1Message.revokeMessageAsync(messageId);

    if (response.code === 0) {
      showMessageNotification('消息已成功撤回', 'success');
      
      // 从界面中移除消息
      removeMessageFromUI(messageId);
      
      // 记录撤回操作
      logUserAction('message_revoked', {
        messageId,
        timestamp: new Date().toISOString()
      });
      
    } else {
      handleRevokeError(response.code, response.msg);
    }
  } catch (error) {
    console.error('撤回消息时发生错误:', error);
    showMessageNotification('撤回失败,请检查网络连接', 'error');
  } finally {
    hideLoadingIndicator();
  }
}

// 处理撤回错误
function handleRevokeError(errorCode, errorMessage) {
  let userFriendlyMessage = '';

  switch (errorCode) {
    case 403:
      userFriendlyMessage = '您只能撤回自己发送的消息';
      break;
    case 404:
      userFriendlyMessage = '消息不存在或已被删除';
      break;
    case 400:
      userFriendlyMessage = '消息发送时间过长,无法撤回';
      break;
    default:
      userFriendlyMessage = `撤回失败: ${errorMessage}`;
  }

  showMessageNotification(userFriendlyMessage, 'error');
}

// 从界面中移除消息
function removeMessageFromUI(messageId) {
  const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
  if (messageElement) {
    // 添加撤回动画效果
    messageElement.classList.add('message-revoking');
    
    setTimeout(() => {
      messageElement.replaceWith(createRevokedMessageElement(messageId));
    }, 300);
  }
}

// 创建已撤回消息元素
function createRevokedMessageElement(messageId) {
  const revokedElement = document.createElement('div');
  revokedElement.className = 'message revoked-message';
  revokedElement.setAttribute('data-message-id', messageId);
  revokedElement.innerHTML = `
    <div class="revoked-indicator">
      <span class="icon">↩️</span>
      <span class="text">您撤回了一条消息</span>
    </div>
  `;
  return revokedElement;
}

// 消息通知提示
function showMessageNotification(message, type = 'info') {
  const notification = document.createElement('div');
  notification.className = `message-notification ${type}`;
  notification.textContent = message;
  
  document.body.appendChild(notification);
  
  setTimeout(() => {
    notification.classList.add('show');
  }, 100);

  setTimeout(() => {
    notification.classList.remove('show');
    setTimeout(() => {
      if (notification.parentNode) {
        notification.parentNode.removeChild(notification);
      }
    }, 300);
  }, 3000);
}

// 用户操作日志记录
function logUserAction(action, data) {
  console.log('用户操作记录:', {
    action,
    data,
    userId: currentUser.id,
    timestamp: new Date().toISOString()
  });
}

// 添加对应的CSS样式
const revokeStyles = `
<style>
.revoke-confirmation-dialog {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.dialog-content {
  background: white;
  border-radius: 8px;
  padding: 24px;
  max-width: 400px;
  width: 90%;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.message-preview {
  background: #f5f5f5;
  padding: 12px;
  border-radius: 4px;
  margin: 16px 0;
}

.warning-text {
  color: #ff9500;
  font-size: 14px;
  margin-bottom: 20px;
}

.dialog-buttons {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.confirm-revoke-btn, .cancel-revoke-btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.confirm-revoke-btn {
  background: #ff3b30;
  color: white;
}

.cancel-revoke-btn {
  background: #e9ecef;
  color: #333;
}

.message-revoking {
  opacity: 0.5;
  transition: opacity 0.3s ease;
}

.revoked-message {
  text-align: center;
  color: #999;
  font-style: italic;
  padding: 8px;
}

.revoked-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.message-notification {
  position: fixed;
  top: 20px;
  right: 20px;
  padding: 12px 16px;
  border-radius: 4px;
  color: white;
  font-size: 14px;
  z-index: 999;
  opacity: 0;
  transform: translateY(-20px);
  transition: all 0.3s ease;
}

.message-notification.show {
  opacity: 1;
  transform: translateY(0);
}

.message-notification.success {
  background: #34c759;
}

.message-notification.error {
  background: #ff3b30;
}

.message-notification.warning {
  background: #ff9500;
}
</style>
`;

// 注入样式
if (!document.querySelector('style[data-revoke-styles]')) {
  const styleElement = document.createElement('style');
  styleElement.setAttribute('data-revoke-styles', 'true');
  styleElement.textContent = revokeStyles.replace('<style>', '').replace('</style>', '');
  document.head.appendChild(styleElement);
}

// 使用示例
async function setupMessageRevokeHandlers() {
  // 为所有可撤回的消息添加撤回按钮
  const messageElements = document.querySelectorAll('.user-message[data-revocable="true"]');
  
  messageElements.forEach(element => {
    const messageId = element.getAttribute('data-message-id');
    const messageContent = element.querySelector('.message-content').textContent;
    
    const revokeButton = document.createElement('button');
    revokeButton.className = 'revoke-button';
    revokeButton.textContent = '撤回';
    revokeButton.onclick = () => confirmAndRevokeMessage(messageId, messageContent);
    
    element.querySelector('.message-actions').appendChild(revokeButton);
  });
}

// 初始化撤回功能
document.addEventListener('DOMContentLoaded', setupMessageRevokeHandlers);

函数名称:添加消息表情回复

函数签名

csharp
Task<FeishuApiResult<ReactionResult>?> AddMessageReactionsAsync(
 [Path] string message_id,
 [Body] ReactionRequest sendMessageRequest,
 CancellationToken cancellationToken = default);

认证:用户令牌

参数

参数名类型必填说明示例值
message_idstring✅ 必填待添加表情回复的消息ID"om_dc13264520392913993dd051dba21dcf"
sendMessageRequestReactionRequest✅ 必填添加消息表情回复请求体见下方结构
sendMessageRequest.ReactionType.EmojiTypestring✅ 必填emoji类型"thumbs_up"
cancellationTokenCancellationToken⚪ 可选取消操作令牌-

响应

成功响应示例:

json
{
  "code": 0,
  "msg": "success",
  "data": {
    "reaction_id": "ZCaCIjUBVVWSrm5L-3ZTw*************sNa8dHVplEzzSfJVUVLMLcS_",
    "emoji_type": "thumbs_up"
  }
}

常见错误响应:

json
{
  "code": 404,
  "msg": "消息不存在"
}
json
{
  "code": 400,
  "msg": "不支持的表情类型"
}

说明

  • 用户可以对任何消息添加表情回复
  • 支持的表情类型参考飞书官方文档
  • 同一用户可以对同一条消息添加多种表情

代码示例

javascript
// 用户表情回复系统
class MessageReactionSystem {
  constructor() {
    this.emojiPicker = null;
    this.reactionCache = new Map(); // 缓存用户的表情回复
    this.init();
  }

  init() {
    this.createEmojiPicker();
    this.setupReactionButtons();
    this.loadUserReactions();
  }

  // 创建表情选择器
  createEmojiPicker() {
    this.emojiPicker = document.createElement('div');
    this.emojiPicker.className = 'emoji-picker';
    this.emojiPicker.innerHTML = `
      <div class="emoji-picker-header">
        <span>选择表情</span>
        <button class="close-picker">&times;</button>
      </div>
      <div class="emoji-grid">
        <button data-emoji="thumbs_up" class="emoji-btn">👍</button>
        <button data-emoji="heart" class="emoji-btn">❤️</button>
        <button data-emoji="laugh" class="emoji-btn">😂</button>
        <button data-emoji="wow" class="emoji-btn">😮</button>
        <button data-emoji="sad" class="emoji-btn">😢</button>
        <button data-emoji="angry" class="emoji-btn">😡</button>
        <button data-emoji="fire" class="emoji-btn">🔥</button>
        <button data-emoji="clap" class="emoji-btn">👏</button>
      </div>
    `;

    document.body.appendChild(this.emojiPicker);
    this.setupEmojiPickerEvents();
  }

  // 设置表情选择器事件
  setupEmojiPickerEvents() {
    // 关闭按钮
    this.emojiPicker.querySelector('.close-picker').onclick = () => {
      this.hideEmojiPicker();
    };

    // 表情按钮点击
    this.emojiPicker.querySelectorAll('.emoji-btn').forEach(btn => {
      btn.onclick = () => {
        const emoji = btn.getAttribute('data-emoji');
        const messageId = this.emojiPicker.getAttribute('data-message-id');
        
        if (messageId) {
          this.addReaction(messageId, emoji);
        }
        this.hideEmojiPicker();
      };
    });

    // 点击外部关闭
    this.emojiPicker.onclick = (e) => {
      if (e.target === this.emojiPicker) {
        this.hideEmojiPicker();
      }
    };
  }

  // 显示表情选择器
  showEmojiPicker(messageId, targetElement) {
    const rect = targetElement.getBoundingClientRect();
    
    this.emojiPicker.setAttribute('data-message-id', messageId);
    this.emojiPicker.style.display = 'block';
    this.emojiPicker.style.top = `${rect.bottom + 5}px`;
    this.emojiPicker.style.style.left = `${rect.left}px`;

    // 检查是否超出视窗
    setTimeout(() => {
      const pickerRect = this.emojiPicker.getBoundingClientRect();
      if (pickerRect.right > window.innerWidth) {
        this.emojiPicker.style.left = `${rect.right - pickerRect.width}px`;
      }
      if (pickerRect.bottom > window.innerHeight) {
        this.emojiPicker.style.top = `${rect.top - pickerRect.height - 5}px`;
      }
    }, 0);
  }

  // 隐藏表情选择器
  hideEmojiPicker() {
    this.emojiPicker.style.display = 'none';
    this.emojiPicker.removeAttribute('data-message-id');
  }

  // 设置消息的反应按钮
  setupReactionButtons() {
    document.querySelectorAll('.message').forEach(messageElement => {
      const messageId = messageElement.getAttribute('data-message-id');
      const existingButton = messageElement.querySelector('.reaction-button');
      
      if (!existingButton) {
        const reactionButton = document.createElement('button');
        reactionButton.className = 'reaction-button';
        reactionButton.innerHTML = '😊';
        reactionButton.title = '添加表情回复';
        
        reactionButton.onclick = (e) => {
          e.stopPropagation();
          this.showEmojiPicker(messageId, reactionButton);
        };

        const actionsContainer = messageElement.querySelector('.message-actions');
        if (actionsContainer) {
          actionsContainer.appendChild(reactionButton);
        }
      }

      // 显示现有表情回复
      this.displayExistingReactions(messageId, messageElement);
    });
  }

  // 添加表情回复
  async addReaction(messageId, emojiType) {
    try {
      // 检查是否已经添加过相同的表情
      const existingReaction = this.reactionCache.get(`${messageId}_${emojiType}`);
      if (existingReaction) {
        this.showMessageNotification('您已经添加过这个表情了', 'info');
        return;
      }

      this.showLoadingIndicator('正在添加表情...');

      const reactionRequest = {
        reaction_type: {
          emoji_type: emojiType
        }
      };

      const response = await feishuUserV1Message.addMessageReactionsAsync(
        messageId,
        reactionRequest
      );

      if (response.code === 0) {
        // 缓存表情回复
        this.reactionCache.set(`${messageId}_${emojiType}`, response.data.reaction_id);
        
        // 更新界面
        this.updateReactionDisplay(messageId, emojiType, 'add');
        
        // 显示成功提示
        this.showReactionSuccessAnimation(messageId, emojiType);
        
        console.log(`表情回复添加成功: ${messageId} -> ${emojiType}`);
      } else {
        this.showMessageNotification(`添加表情失败: ${response.msg}`, 'error');
      }
    } catch (error) {
      console.error('添加表情回复时发生错误:', error);
      this.showMessageNotification('添加表情失败,请检查网络连接', 'error');
    } finally {
      this.hideLoadingIndicator();
    }
  }

  // 显示现有表情回复
  async displayExistingReactions(messageId, messageElement) {
    try {
      // 获取消息的现有表情回复
      const reactions = await this.getMessageReactions(messageId);
      
      if (reactions.length > 0) {
        this.renderReactions(messageElement, reactions);
      }
    } catch (error) {
      console.error('获取现有表情回复失败:', error);
    }
  }

  // 获取消息表情回复列表
  async getMessageReactions(messageId) {
    // 这里应该调用GetMessageReactionsPageListAsync接口
    // 简化示例,返回模拟数据
    return [];
  }

  // 渲染表情回复
  renderReactions(messageElement, reactions) {
    let reactionsContainer = messageElement.querySelector('.reactions-container');
    
    if (!reactionsContainer) {
      reactionsContainer = document.createElement('div');
      reactionsContainer.className = 'reactions-container';
      messageElement.appendChild(reactionsContainer);
    }

    // 按表情类型分组统计
    const reactionGroups = reactions.reduce((groups, reaction) => {
      const emoji = reaction.emoji_type;
      if (!groups[emoji]) {
        groups[emoji] = {
          count: 0,
          users: [],
          reactionIds: []
        };
      }
      groups[emoji].count++;
      groups[emoji].users.push(reaction.user);
      groups[emoji].reactionIds.push(reaction.reaction_id);
      return groups;
    }, {});

    // 渲染表情分组
    reactionsContainer.innerHTML = Object.entries(reactionGroups)
      .map(([emoji, group]) => `
        <div class="reaction-item" data-emoji="${emoji}" data-message-id="${messageElement.getAttribute('data-message-id')}">
          <span class="reaction-emoji">${this.getEmojiDisplay(emoji)}</span>
          <span class="reaction-count">${group.count}</span>
        </div>
      `).join('');

    // 添加点击事件
    reactionsContainer.querySelectorAll('.reaction-item').forEach(item => {
      item.onclick = () => {
        const emoji = item.getAttribute('data-emoji');
        const messageId = item.getAttribute('data-message-id');
        this.showReactionDetails(messageId, emoji);
      };
    });
  }

  // 获取表情显示
  getEmojiDisplay(emojiType) {
    const emojiMap = {
      'thumbs_up': '👍',
      'heart': '❤️',
      'laugh': '😂',
      'wow': '😮',
      'sad': '😢',
      'angry': '😡',
      'fire': '🔥',
      'clap': '👏'
    };
    return emojiMap[emojiType] || emojiType;
  }

  // 更新表情显示
  updateReactionDisplay(messageId, emojiType, action) {
    const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
    if (!messageElement) return;

    if (action === 'add') {
      // 如果没有表情容器,先添加表情按钮
      this.setupReactionButtons();
    }
  }

  // 显示表情成功动画
  showReactionSuccessAnimation(messageId, emojiType) {
    const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
    if (!messageElement) return;

    const animation = document.createElement('div');
    animation.className = 'reaction-success-animation';
    animation.innerHTML = this.getEmojiDisplay(emojiType);
    
    messageElement.appendChild(animation);

    setTimeout(() => {
      animation.classList.add('show');
    }, 100);

    setTimeout(() => {
      animation.remove();
    }, 1500);
  }

  // 显示表情详情
  showReactionDetails(messageId, emojiType) {
    // 这里可以实现显示谁使用了该表情的功能
    console.log(`显示表情详情: ${messageId} -> ${emojiType}`);
  }

  // 加载用户的表情回复缓存
  loadUserReactions() {
    // 从localStorage加载用户的表情回复记录
    const cached = localStorage.getItem('userMessageReactions');
    if (cached) {
      const data = JSON.parse(cached);
      this.reactionCache = new Map(Object.entries(data));
    }
  }

  // 保存表情回复缓存
  saveUserReactions() {
    const data = Object.fromEntries(this.reactionCache);
    localStorage.setItem('userMessageReactions', JSON.stringify(data));
  }

  // 显示消息通知
  showMessageNotification(message, type = 'info') {
    const notification = document.createElement('div');
    notification.className = `reaction-notification ${type}`;
    notification.textContent = message;
    
    document.body.appendChild(notification);
    
    setTimeout(() => {
      notification.classList.add('show');
    }, 100);

    setTimeout(() => {
      notification.classList.remove('show');
      setTimeout(() => {
        if (notification.parentNode) {
          notification.parentNode.removeChild(notification);
        }
      }, 300);
    }, 2000);
  }

  // 显示加载指示器
  showLoadingIndicator(message = '加载中...') {
    const existing = document.querySelector('.reaction-loading');
    if (existing) existing.remove();

    const loading = document.createElement('div');
    loading.className = 'reaction-loading';
    loading.innerHTML = `
      <div class="spinner"></div>
      <span>${message}</span>
    `;
    
    document.body.appendChild(loading);
  }

  // 隐藏加载指示器
  hideLoadingIndicator() {
    const loading = document.querySelector('.reaction-loading');
    if (loading) {
      loading.remove();
    }
  }
}

// 添加对应的CSS样式
const reactionStyles = `
<style data-reaction-styles>
.emoji-picker {
  position: absolute;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  z-index: 1000;
  display: none;
  min-width: 280px;
}

.emoji-picker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid #e9ecef;
  font-weight: 500;
}

.close-picker {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #666;
}

.emoji-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 16px;
}

.emoji-btn {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  padding: 8px;
  border-radius: 4px;
  transition: background 0.2s;
}

.emoji-btn:hover {
  background: #f0f0f0;
}

.reaction-button {
  background: none;
  border: none;
  font-size: 16px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
  transition: background 0.2s;
}

.reaction-button:hover {
  background: #f0f0f0;
}

.reactions-container {
  display: flex;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}

.reaction-item {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  background: #f0f0f0;
  border-radius: 12px;
  cursor: pointer;
  transition: background 0.2s;
  font-size: 12px;
}

.reaction-item:hover {
  background: #e0e0e0;
}

.reaction-emoji {
  font-size: 16px;
}

.reaction-count {
  font-weight: 500;
  color: #666;
}

.reaction-success-animation {
  position: absolute;
  font-size: 32px;
  pointer-events: none;
  animation: floatUp 1.5s ease-out forwards;
  z-index: 100;
}

@keyframes floatUp {
  0% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  100% {
    opacity: 0;
    transform: translateY(-50px) scale(1.5);
  }
}

.reaction-notification {
  position: fixed;
  top: 20px;
  right: 20px;
  padding: 12px 16px;
  border-radius: 4px;
  color: white;
  font-size: 14px;
  z-index: 999;
  opacity: 0;
  transform: translateY(-20px);
  transition: all 0.3s ease;
}

.reaction-notification.show {
  opacity: 1;
  transform: translateY(0);
}

.reaction-notification.success {
  background: #34c759;
}

.reaction-notification.error {
  background: #ff3b30;
}

.reaction-notification.info {
  background: #007aff;
}

.reaction-loading {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 20px;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  z-index: 1001;
}

.spinner {
  width: 24px;
  height: 24px;
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-top: 2px solid white;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>
`;

// 初始化表情系统
document.addEventListener('DOMContentLoaded', () => {
  // 注入样式
  if (!document.querySelector('style[data-reaction-styles]')) {
    const styleElement = document.createElement('style');
    styleElement.setAttribute('data-reaction-styles', 'true');
    styleElement.textContent = reactionStyles.replace('<style data-reaction-styles>', '').replace('</style>', '');
    document.head.appendChild(styleElement);
  }

  // 初始化表情系统
  window.reactionSystem = new MessageReactionSystem();
});

由于用户令牌版本的接口主要继承自父接口,以上展示了核心功能的详细文档。用户版本相比租户版本的特点:

权限控制优化

  • 用户只能撤回自己发送的消息
  • 所有操作都经过严格的权限验证
  • 友好的权限错误提示

用户体验优先

  • 直观的消息撤回确认对话框
  • 丰富的表情选择器界面
  • 实时的表情回复动画效果

界面交互增强

  • 响应式的表情回复显示
  • 流畅的动画过渡效果
  • 智能的位置调整逻辑

本地缓存优化

  • 用户表情回复本地缓存
  • 减少不必要的API调用
  • 离线状态下的基本功能

错误处理完善

  • 友好的错误消息提示
  • 详细的操作日志记录
  • 优雅的降级处理

两个文档都已保存到项目的docs目录下,文件名按照要求去掉了接口前缀"I"。开发者可以通过这些文档快速理解和使用飞书消息管理API,构建功能丰富的即时通讯应用。