• {{ currentBot.charAt(0).toUpperCase() }}
  • {{ currentBot }}
{{ g.name.charAt(0) }}
{{ g.name }}
点击进入聊天
暂无群组
联系人
{{ f.friend_id.charAt(0).toUpperCase() }}
{{ f.friend_id }}
{{ statusText(f.status) }}
暂无联系人

{{ activeGroup.name }}

暂无消息

{{ m.content }}

{{ m.sender_id }}
{{ m.content }}
Enter Ctrl+Enter

选择一个群组开始聊天

{{ connected ? '已连接' : (connecting ? '连接中...' : '未连接') }}
const API = window.location.origin; let socket = null; const app = Vue.createApp({ data() { return { currentBot: 'gong3', chatTab: 'msgs', connected: false, connecting: false, bell: true, groups: [], activeGroup: null, messages: [], groupMembers: [], friends: [], inputText: '', keyword: '', enterMode: 1, showCreateGroup: false, showAddMember: false, showAddFriend: false, showGroupInfo: false, newGroupName: '', newMemberId: '', newMemberRole: 'member', newFriendId: '', }; }, computed: { filteredGroups() { const kw = this.keyword.trim().toLowerCase(); if (!kw) return this.groups; return this.groups.filter(g => g.name.toLowerCase().includes(kw)); } }, watch: { showGroupInfo(val) { if (val && this.activeGroup) this.loadGroupMembers(); }, messages() { this.$nextTick(() => this.scrollBottom()); } }, mounted() { this.loadGroups(); this.loadFriends(); this.connectWS(); }, methods: { async api(path, opts = {}) { try { const res = await fetch(API + path, { ...opts, headers: { 'Content-Type': 'application/json', ...opts.headers } }); return res.json(); } catch(e) { return null; } }, async loadGroups() { try { const d = await this.api('/api/groups'); if (Array.isArray(d)) this.groups = d; } catch(e){} }, async loadFriends() { try { const d = await this.api('/api/friends/'+this.currentBot); if (Array.isArray(d)) this.friends = d; } catch(e){} }, async switchGroup(g) { this.activeGroup = g; this.messages = []; try { const d = await this.api('/api/messages?group_id='+g.id+'&limit=50'); if (Array.isArray(d)) this.messages = d; } catch(e){} this.loadGroupMembers(); this.$nextTick(() => this.scrollBottom()); }, async loadGroupMembers() { if (!this.activeGroup) return; try { const d = await this.api('/api/groups/'+this.activeGroup.id+'/members'); if (Array.isArray(d)) this.groupMembers = d; } catch(e){} }, statusText(s) { return {pending:'待确认',approved:'已通过',blocked:'已屏蔽'}[s]||s; }, scrollBottom() { const el = this.$refs.msgContainer; if (el) setTimeout(() => { el.scrollTop = el.scrollHeight; }, 50); }, keywordSearch() { /* filtered by computed */ }, connectWS() { if (this.connecting) return; this.connecting = true; const wsUrl = API.replace('https://','wss://').replace('http://','ws://') + '/ws?bot_id=' + this.currentBot; socket = new WebSocket(wsUrl); socket.onopen = () => { this.connected = true; this.connecting = false; }; socket.onmessage = (e) => { try { const pkt = JSON.parse(e.data); if (pkt.type === 'hello' || pkt.type === 'event') return; if (pkt.type === 'message' && pkt.data && this.activeGroup && pkt.data.group_id === this.activeGroup.id) { this.messages.push({ id: pkt.data.id || Date.now(), group_id: pkt.data.group_id, sender_id: pkt.data.sender_id, content: pkt.data.content, msg_type: pkt.data.msg_type || 'text', created_at: pkt.data.send_at }); } } catch(err){} }; socket.onclose = () => { this.connected = false; this.connecting = false; setTimeout(() => this.connectWS(), 3000); }; }, handleKeydown(e) { if (this.enterMode === 1 && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } else if (this.enterMode === 2 && e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); this.sendMessage(); } }, async sendMessage() { const text = this.inputText.trim(); if (!text || !this.activeGroup || !this.connected) return; this.inputText = ''; try { const d = await this.api('/api/messages', { method:'POST', body: JSON.stringify({ group_id: this.activeGroup.id, sender_id: this.currentBot, content: text, msg_type: 'text' }) }); if (d && d.id) { this.messages.push({ id: d.id, group_id: this.activeGroup.id, sender_id: this.currentBot, content: text, msg_type: 'text', created_at: d.created_at }); } } catch(e){} this.$nextTick(() => this.scrollBottom()); }, async createGroup() { const name = this.newGroupName.trim(); if (!name) return; try { const d = await this.api('/api/groups', { method:'POST', body: JSON.stringify({ name, created_by: this.currentBot }) }); if (d && d.id) { await this.api('/api/groups/'+d.id+'/members', { method:'POST', body: JSON.stringify({ bot_id: this.currentBot, role: 'admin' }) }); this.groups.push(d); this.showCreateGroup = false; this.newGroupName = ''; this.switchGroup(d); } } catch(e){} }, async addFriend() { const id = this.newFriendId.trim(); if (!id) return; try { await this.api('/api/friends', { method:'POST', body: JSON.stringify({ user_id: this.currentBot, friend_id: id }) }); this.showAddFriend = false; this.newFriendId = ''; this.loadFriends(); } catch(e){} }, async addMember() { if (!this.newMemberId.trim() || !this.activeGroup) return; try { await this.api('/api/groups/'+this.activeGroup.id+'/members', { method:'POST', body: JSON.stringify({ bot_id: this.newMemberId, role: this.newMemberRole }) }); this.showAddMember = false; this.newMemberId = ''; this.loadGroupMembers(); } catch(e){} }, } }).mount('#app');