<!--
  Uniapp WebSocket 测试页面
  用于在 Uniapp 项目中测试微信小程序的 WebSocket 功能
  
  使用方法：
  1. 在 Uniapp 项目的 pages 目录下创建 ws-test 目录
  2. 将此文件保存为 ws-test.vue
  3. 在 pages.json 中注册该页面
  4. 在微信开发者工具中运行项目并访问该页面
-->

<template>
  <view class="container">
    <view class="header">
      <text class="title">WebSocket 测试</text>
      <view class="status" :class="statusClass">
        {{ connectionStatus }}
      </view>
    </view>

    <view class="settings">
      <view class="form-item">
        <text class="label">服务器地址：</text>
        <input 
          class="input" 
          v-model="wsUrl" 
          placeholder="wss://your-domain.com/ws/aikefu"
        />
      </view>
      <view class="btn-group">
        <button 
          class="btn btn-primary" 
          @click="connectWebSocket"
          :disabled="connecting"
        >
          {{ connecting ? '连接中...' : '连接' }}
        </button>
        <button 
          class="btn btn-default" 
          @click="disconnectWebSocket"
          :disabled="!connected"
        >
          断开连接
        </button>
        <button 
          class="btn btn-warn" 
          @click="reconnectWebSocket"
          :disabled="connecting"
        >
          重连
        </button>
      </view>
    </view>

    <view class="message-area">
      <scroll-view 
        class="message-list" 
        scroll-y="true" 
        :scroll-top="scrollTop"
        @scroll="onScroll"
      >
        <view 
          v-for="(message, index) in messages" 
          :key="message.id"
          class="message-item"
          :class="message.type"
        >
          <view class="message-header">
            <text class="sender">{{ message.sender }}</text>
            <text class="time">{{ message.time }}</text>
          </view>
          <view class="message-content">
            {{ formatMessage(message.content) }}
          </view>
        </view>
      </scroll-view>
    </view>

    <view class="input-area">
      <textarea 
        class="message-input" 
        v-model="inputMessage" 
        placeholder="输入要发送的消息..."
        @confirm="sendCustomMessage"
      ></textarea>
      <view class="btn-group">
        <button class="btn btn-sm" @click="sendPing">Ping</button>
        <button class="btn btn-sm" @click="sendTestMessage">测试消息</button>
        <button class="btn btn-sm btn-primary" @click="sendCustomMessage">发送</button>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      wsUrl: 'wss://your-domain.com/ws/aikefu', // 替换为你的 WebSocket 服务器地址
      connectionStatus: '未连接',
      connected: false,
      connecting: false,
      socketTask: null,
      messages: [],
      inputMessage: '',
      scrollTop: 0,
      autoScroll: true
    };
  },

  computed: {
    statusClass() {
      if (this.connecting) return 'status-connecting';
      if (this.connected) return 'status-connected';
      return 'status-disconnected';
    }
  },

  onLoad() {
    // 页面加载时自动连接
    this.connectWebSocket();
  },

  onUnload() {
    // 页面卸载时断开连接
    this.closeWebSocket();
  },

  onHide() {
    // 页面隐藏时断开连接
    this.closeWebSocket();
  },

  methods: {
    // 连接 WebSocket
    connectWebSocket() {
      if (this.connected || this.connecting) return;

      this.connecting = true;
      this.updateStatus('连接中...');

      try {
        // 创建 WebSocket 连接 (Uniapp API)
        this.socketTask = uni.connectSocket({
          url: this.wsUrl,
          header: {
            'content-type': 'application/json'
          },
          method: 'GET',
          success: (res) => {
            console.log('WebSocket 连接请求发送成功', res);
          },
          fail: (err) => {
            console.error('WebSocket 连接请求发送失败', err);
            this.connecting = false;
            this.updateStatus('连接失败');
            this.addMessage('系统', 'WebSocket 连接请求发送失败: ' + JSON.stringify(err), 'error');
          }
        });

        // 监听 WebSocket 连接打开
        this.socketTask.onOpen((res) => {
          console.log('WebSocket 连接已打开', res);
          this.connected = true;
          this.connecting = false;
          this.updateStatus('已连接');
          this.addMessage('系统', 'WebSocket 连接已打开', 'system');
        });

        // 监听 WebSocket 接收到服务器的消息
        this.socketTask.onMessage((res) => {
          console.log('收到服务器消息', res.data);
          this.addMessage('服务器', res.data, 'received');
        });

        // 监听 WebSocket 连接关闭
        this.socketTask.onClose((res) => {
          console.log('WebSocket 连接已关闭', res);
          this.connected = false;
          this.connecting = false;
          this.updateStatus('已断开');
          this.addMessage('系统', 'WebSocket 连接已关闭', 'system');
        });

        // 监听 WebSocket 错误
        this.socketTask.onError((res) => {
          console.error('WebSocket 连接发生错误', res);
          this.connected = false;
          this.connecting = false;
          this.updateStatus('连接错误');
          this.addMessage('系统', 'WebSocket 连接发生错误: ' + JSON.stringify(res), 'error');
        });

      } catch (error) {
        console.error('WebSocket 连接异常', error);
        this.connecting = false;
        this.updateStatus('连接异常');
        this.addMessage('系统', 'WebSocket 连接异常: ' + error.message, 'error');
      }
    },

    // 断开 WebSocket 连接
    disconnectWebSocket() {
      if (!this.connected && !this.connecting) return;

      this.closeWebSocket();
      this.connected = false;
      this.connecting = false;
      this.updateStatus('已断开');
      this.addMessage('系统', 'WebSocket 连接已手动断开', 'system');
    },

    // 关闭 WebSocket 连接（内部使用）
    closeWebSocket() {
      if (this.socketTask) {
        try {
          this.socketTask.close({
            code: 1000,
            reason: '用户主动断开连接'
          });
        } catch (error) {
          console.error('关闭 WebSocket 连接失败', error);
        }
        this.socketTask = null;
      }
    },

    // 重连 WebSocket
    reconnectWebSocket() {
      this.closeWebSocket();
      this.connected = false;
      this.connecting = false;
      
      // 延迟 1 秒后重连
      setTimeout(() => {
        this.connectWebSocket();
      }, 1000);
    },

    // 发送 Ping 消息
    sendPing() {
      this.sendMessage(JSON.stringify({ action: 'ping' }));
    },

    // 发送测试消息
    sendTestMessage() {
      this.sendMessage(JSON.stringify({
        message: '你好，这是 Uniapp 测试消息！',
        action: 'test',
        timestamp: Date.now()
      }));
    },

    // 发送自定义消息
    sendCustomMessage() {
      if (!this.inputMessage.trim()) {
        uni.showToast({
          title: '请输入消息内容',
          icon: 'none'
        });
        return;
      }

      try {
        // 尝试解析为 JSON
        JSON.parse(this.inputMessage);
        this.sendMessage(this.inputMessage);
      } catch (error) {
        // 不是 JSON，包装为普通消息
        this.sendMessage(JSON.stringify({
          message: this.inputMessage,
          action: 'chat'
        }));
      }

      // 清空输入框
      this.inputMessage = '';
    },

    // 发送消息（通用方法）
    sendMessage(message) {
      if (!this.connected || !this.socketTask) {
        uni.showToast({
          title: 'WebSocket 未连接',
          icon: 'none'
        });
        this.updateStatus('已断开');
        return;
      }

      try {
        // 使用 Uniapp API 发送消息
        this.socketTask.send({
          data: message,
          success: () => {
            this.addMessage('我', message, 'sent');
          },
          fail: (err) => {
            console.error('发送消息失败', err);
            this.addMessage('系统', '发送消息失败: ' + JSON.stringify(err), 'error');
          }
        });
      } catch (error) {
        console.error('发送消息异常', error);
        this.addMessage('系统', '发送消息异常: ' + error.message, 'error');
      }
    },

    // 更新连接状态
    updateStatus(status) {
      this.connectionStatus = status;
    },

    // 添加消息到消息列表
    addMessage(sender, content, type) {
      const message = {
        id: Date.now(),
        sender,
        content,
        type,
        time: this.formatTime(new Date())
      };

      this.messages.push(message);

      // 自动滚动到底部
      this.scrollToBottom();
    },

    // 滚动到底部
    scrollToBottom() {
      if (this.autoScroll) {
        // 延迟执行以确保 DOM 已更新
        this.$nextTick(() => {
          uni.createSelectorQuery().in(this)
            .select('.message-list')
            .boundingClientRect((rect) => {
              if (rect) {
                this.scrollTop = rect.height;
              }
            })
            .exec();
        });
      }
    },

    // 格式化时间
    formatTime(date) {
      const hours = date.getHours().toString().padStart(2, '0');
      const minutes = date.getMinutes().toString().padStart(2, '0');
      const seconds = date.getSeconds().toString().padStart(2, '0');
      return `${hours}:${minutes}:${seconds}`;
    },

    // 格式化消息内容（美化 JSON）
    formatMessage(content) {
      try {
        const parsed = JSON.parse(content);
        return JSON.stringify(parsed, null, 2);
      } catch (error) {
        return content;
      }
    },

    // 处理滚动事件
    onScroll(e) {
      const { scrollTop, scrollHeight, clientHeight } = e.detail;
      // 判断是否滚动到底部附近
      this.autoScroll = scrollTop + clientHeight >= scrollHeight - 20;
    }
  }
};
</script>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  padding: 20rpx;
  background-color: #f5f5f5;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
  padding-bottom: 20rpx;
  border-bottom: 1rpx solid #eee;
}

.title {
  font-size: 36rpx;
  font-weight: bold;
  color: #333;
}

.status {
  padding: 8rpx 16rpx;
  border-radius: 8rpx;
  font-size: 24rpx;
  font-weight: bold;
}

.status-connecting {
  background-color: #fff3cd;
  color: #856404;
}

.status-connected {
  background-color: #d4edda;
  color: #155724;
}

.status-disconnected {
  background-color: #f8d7da;
  color: #721c24;
}

.settings {
  background-color: #fff;
  border-radius: 10rpx;
  padding: 20rpx;
  margin-bottom: 20rpx;
}

.form-item {
  display: flex;
  flex-direction: column;
  margin-bottom: 20rpx;
}

.label {
  font-size: 28rpx;
  color: #666;
  margin-bottom: 10rpx;
}

.input {
  height: 80rpx;
  padding: 0 20rpx;
  border: 1rpx solid #ddd;
  border-radius: 10rpx;
  font-size: 28rpx;
  background-color: #f9f9f9;
}

.btn-group {
  display: flex;
  justify-content: flex-start;
  gap: 15rpx;
  flex-wrap: wrap;
}

.btn {
  flex: 1;
  min-width: 150rpx;
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  border-radius: 10rpx;
  font-size: 28rpx;
  border: none;
}

.btn-primary {
  background-color: #007aff;
  color: #fff;
}

.btn-default {
  background-color: #f5f5f5;
  color: #333;
  border: 1rpx solid #ddd;
}

.btn-warn {
  background-color: #ff3b30;
  color: #fff;
}

.btn-sm {
  height: 60rpx;
  line-height: 60rpx;
  font-size: 24rpx;
  min-width: 100rpx;
}

.message-area {
  flex: 1;
  background-color: #fff;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
  overflow: hidden;
}

.message-list {
  height: 100%;
  padding: 20rpx;
}

.message-item {
  margin-bottom: 20rpx;
  padding: 15rpx;
  border-radius: 10rpx;
  max-width: 80%;
}

.message-item.sent {
  align-self: flex-end;
  margin-left: auto;
  background-color: #d4edda;
  border-right: 4rpx solid #28a745;
}

.message-item.received {
  align-self: flex-start;
  background-color: #e9ecef;
  border-left: 4rpx solid #007bff;
}

.message-item.system {
  align-self: center;
  background-color: #fff3cd;
  border-left: 4rpx solid #856404;
  max-width: 90%;
}

.message-item.error {
  align-self: center;
  background-color: #f8d7da;
  border-left: 4rpx solid #dc3545;
  max-width: 90%;
}

.message-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10rpx;
  font-size: 24rpx;
  color: #666;
}

.message-content {
  font-size: 28rpx;
  line-height: 1.5;
  word-break: break-all;
  white-space: pre-wrap;
}

.input-area {
  background-color: #fff;
  border-radius: 10rpx;
  padding: 20rpx;
}

.message-input {
  width: 100%;
  min-height: 120rpx;
  max-height: 200rpx;
  padding: 20rpx;
  margin-bottom: 20rpx;
  border: 1rpx solid #ddd;
  border-radius: 10rpx;
  font-size: 28rpx;
  background-color: #f9f9f9;
}
</style>

<script module="pages.json">
// 该模块仅用于示例，实际应在项目根目录的 pages.json 中配置

/*
{
  "pages": [
    {
      "path": "pages/ws-test/ws-test",
      "style": {
        "navigationBarTitleText": "WebSocket 测试"
      }
    }
  ]
}
*/
</script>

<script module="README">
/*
# Uniapp WebSocket 测试页面使用说明

## 功能说明
用于在 Uniapp 项目中测试微信小程序的 WebSocket 功能，支持连接管理、消息发送和接收。

## 使用方法

### 1. 创建页面
将本文件保存为 `pages/ws-test/ws-test.vue`

### 2. 配置页面路由
在 `pages.json` 中添加以下配置：

```json
{
  "pages": [
    {
      "path": "pages/ws-test/ws-test",
      "style": {
        "navigationBarTitleText": "WebSocket 测试"
      }
    }
  ]
}
```

### 3. 配置服务器地址
在 `ws-test.vue` 文件中修改 `wsUrl` 变量：

```javascript
data() {
  return {
    wsUrl: 'wss://your-domain.com/ws/aikefu', // 替换为你的 WebSocket 服务器地址
    // ...
  };
}
```

### 4. 运行测试
- 使用 HBuilderX 开发工具
- 选择 "运行" -> "运行到小程序模拟器" -> "微信开发者工具"
- 在微信开发者工具中访问该页面

## 注意事项

### 微信小程序限制
1. **域名配置**：需要在微信小程序管理后台配置合法域名
2. **协议支持**：仅支持 HTTPS/WSS 协议
3. **连接数限制**：最多同时存在 5 个 WebSocket 连接

### Uniapp API 特点
1. Uniapp 使用 `uni.connectSocket()` 替代微信小程序的 `wx.connectSocket()`
2. Uniapp 使用 `uni.createSelectorQuery()` 替代微信小程序的 `wx.createSelectorQuery()`
3. 页面生命周期钩子函数使用 Vue 组件的 `onLoad()`、`onUnload()`、`onHide()`

## 扩展功能

### 添加消息类型支持
可以根据业务需求扩展支持的消息类型：

```javascript
// 发送聊天消息
sendChatMessage() {
  this.sendMessage(JSON.stringify({
    action: 'chat',
    message: this.inputMessage,
    userId: 'user123',
    timestamp: Date.now()
  }));
}

// 发送指令消息
sendCommand(command, params) {
  this.sendMessage(JSON.stringify({
    action: 'command',
    command,
    params,
    timestamp: Date.now()
  }));
}
```

### 添加自动重连机制
可以实现更智能的自动重连机制：

```javascript
data() {
  return {
    // ...
    reconnectAttempts: 0,
    maxReconnectAttempts: 5,
    reconnectDelay: 1000
  };
},

methods: {
  // 自动重连
  autoReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      this.addMessage('系统', '自动重连失败，已达到最大尝试次数', 'error');
      return;
    }

    this.reconnectAttempts++;
    this.addMessage('系统', `尝试自动重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`, 'system');

    setTimeout(() => {
      this.connectWebSocket();
    }, this.reconnectDelay);
  },

  // 连接成功后重置重连计数
  onConnectSuccess() {
    this.reconnectAttempts = 0;
    // ...
  }
}
```

## 常见问题

### 1. 连接失败
**可能原因**：
- 服务器地址错误
- 未配置合法域名
- 服务器未启动或网络不可达
- 使用了 HTTP 协议而不是 HTTPS

**解决方案**：
- 检查服务器地址和端口
- 在微信小程序管理后台配置合法域名
- 确保服务器正常运行
- 切换到 WSS 协议

### 2. 消息发送失败
**可能原因**：
- WebSocket 连接已关闭
- 消息格式不正确
- 网络中断

**解决方案**：
- 检查连接状态，必要时重新连接
- 确保消息格式为 JSON 字符串
- 检查网络连接

### 3. 接收消息异常
**可能原因**：
- 服务器返回格式错误
- 客户端解析错误

**解决方案**：
- 检查服务器返回的消息格式
- 调试客户端解析逻辑

## 联系和支持

如果在使用过程中遇到问题，请联系开发人员或查看相关文档。

---

**更新时间**：2025-12-19  
**版本**：1.0.0  
**维护人员**：WebSocket 开发团队
*/
</script>
