M 模极社
首页 工业学院 造物商城 任务平台 人才空间 AI智库 智能体
登录 注册
🛒 购物车
{{item.name}}
¥{{item.price}} x {{item.quantity}}

购物车是空的

加载对话中…
* *
* TradeChat.mount('#trade-chat-root') */ (function (global) { 'use strict'; // ============================================================ // 样式 // ============================================================ const STYLE_ID = 'mj-trade-chat-styles'; if (!document.getElementById(STYLE_ID)) { const css = document.createElement('style'); css.id = STYLE_ID; css.textContent = ` /* ---- TradeChat Container ---- */ .trade-chat-wrap{background:#fff;border-radius:8px;border:1px solid #e4e7ed;overflow:hidden;margin-top:16px} .trade-chat-header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid #e4e7ed;background:#f8f9fa} .trade-chat-header h3{margin:0;font-size:14px;font-weight:600;color:#303133} .trade-chat-header .hint{font-size:12px;color:#909399} .trade-chat-header .toggle-btn{background:none;border:none;color:#409eff;font-size:13px;cursor:pointer;padding:4px 8px;border-radius:4px} .trade-chat-header .toggle-btn:hover{background:#ecf5ff} /* ---- Messages ---- */ .trade-chat-body{max-height:360px;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:8px;scroll-behavior:smooth} .trade-chat-body .loading{text-align:center;padding:12px;color:#c0c4cc;font-size:12px} .trade-chat-body .load-more{text-align:center;padding:6px;color:#909399;font-size:12px;cursor:pointer} .trade-chat-body .load-more:hover{color:#409eff} /* ---- Bubbles ---- */ .trade-msg{max-width:80%;padding:10px 14px;border-radius:14px;font-size:13px;line-height:1.6;word-break:break-word;position:relative;animation:fadeInChat .2s} @keyframes fadeInChat{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}} .trade-msg.buyer{align-self:flex-start;background:#f0f2f5;color:#303133;border-bottom-left-radius:4px} .trade-msg.seller{align-self:flex-end;background:#409eff;color:#fff;border-bottom-right-radius:4px} .trade-msg.system{align-self:center;font-size:12px;color:#909399;background:transparent;padding:4px 8px;max-width:95%;text-align:center} .trade-msg .meta{font-size:10px;margin-top:4px;opacity:.6} .trade-msg.seller .meta{color:rgba(255,255,255,.7)} .trade-msg.buyer .meta{color:#c0c4cc} .trade-msg .sender-name{font-weight:600;font-size:11px;margin-bottom:2px;display:block} .trade-msg.seller .sender-name{color:rgba(255,255,255,.8)} .trade-msg.buyer .sender-name{color:#606266} /* ---- Input ---- */ .trade-chat-input{display:flex;align-items:center;padding:10px 16px;border-top:1px solid #e4e7ed;gap:8px} .trade-chat-input textarea{flex:1;border:none;outline:none;resize:none;font-size:13px;font-family:inherit;padding:8px 0;max-height:60px;min-height:18px;line-height:1.5} .trade-chat-input .send-btn{width:34px;height:34px;border-radius:50%;background:#409eff;color:#fff;border:none;cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center;transition:all .15s;flex-shrink:0} .trade-chat-input .send-btn:hover{background:#66b1ff} .trade-chat-input .send-btn:disabled{background:#c0c4cc;cursor:not-allowed} /* ---- Error / Empty ---- */ .trade-chat-error{padding:10px 20px;background:#fef0f0;color:#f56c6c;font-size:12px;display:flex;align-items:center;gap:6px} .trade-chat-empty{padding:30px 20px;text-align:center;color:#c0c4cc;font-size:13px} .trade-chat-empty .icon{font-size:32px;display:block;margin-bottom:8px} /* ---- Unread Banner ---- */ .trade-chat-unread{padding:8px 20px;background:#fdf6ec;color:#e6a23c;font-size:12px;display:flex;align-items:center;gap:6px;cursor:pointer;border-bottom:1px solid #faecd8} .trade-chat-unread:hover{background:#fef0db} /* ---- Collapsed ---- */ .trade-chat-wrap.collapsed .trade-chat-body, .trade-chat-wrap.collapsed .trade-chat-input{display:none} .trade-chat-wrap.collapsed .trade-chat-unread{display:flex !important} `; document.head.appendChild(css); } // ============================================================ // TradeChat 组件 // ============================================================ class TradeChat { constructor(options = {}) { this.orderId = options.orderId; this.userId = options.userId; this.partnerId = options.partnerId; // 对方user_id(卖家或买家) this.isSeller = options.isSeller || false; // 当前用户是卖家? this.sdk = options.sdk; this.container = options.container || document.body; this.conversationId = null; this.messages = []; this._collapsed = false; this._build(); this._init(); } _build() { this._el = document.createElement('div'); this._el.className = 'trade-chat-wrap'; this._el.innerHTML = `

💬 订单沟通

咨询卖家
加载中…
`; this._body = this._el.querySelector('.trade-chat-body'); this._input = this._el.querySelector('[data-input]'); this._sendBtn = this._el.querySelector('[data-action="send"]'); this._headerToggle = this._el.querySelector('[data-action="toggle"]'); this._unreadBanner = this._el.querySelector('[data-action="expand"]'); this.container.appendChild(this._el); } async _init() { if (!this.sdk) { if (global.MessagingSDK && this.userId) { this.sdk = new MessagingSDK({ userId: this.userId }); try { await this.sdk.init(); } catch(e) {} } else { return; } } // 1. 先找有没有现存的订单对话 await this._findOrCreateConversation(); // 2. 绑定事件 this._bindEvents(); // 3. 加载消息 if (this.conversationId) { await this._loadMessages(); } } async _findOrCreateConversation() { try { // 查看现有对话 const convs = await this.sdk.getConversations(); const existing = convs.find(c => c.type === 'order' && String(c.order_id) === String(this.orderId) ); if (existing) { this.conversationId = existing.conversation_id; return; } // 如果没有则创建新对话 const result = await this.sdk.createConversation({ type: 'order', orderId: this.orderId, participantIds: this.partnerId ? [this.partnerId] : [], title: `订单 #${this.orderId} 咨询`, }); if (result.conversation_id) { this.conversationId = result.conversation_id; } } catch (e) { this._showError('对话创建失败'); } } _bindEvents() { // 发送 this._sendBtn.addEventListener('click', () => this._send()); this._input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this._send(); } }); this._input.addEventListener('input', () => { this._sendBtn.disabled = !this._input.value.trim(); // auto-resize this._input.style.height = 'auto'; this._input.style.height = Math.min(this._input.scrollHeight, 60) + 'px'; // 发送typing if (this.sdk && this.conversationId) { this.sdk.sendTyping(this.conversationId); } }); // 折叠/展开 this._headerToggle.addEventListener('click', () => this._toggle()); this._unreadBanner.addEventListener('click', () => this._expand()); // SDK 事件 if (this.sdk) { this.sdk.on('new_message', (msg) => { if (msg.conversation_id === this.conversationId) { this.messages.push(msg.message); this._renderMessages(); this._scrollBottom(); } }); this.sdk.on('unread_update', (data) => { if (this._collapsed && data.conversations) { const conv = data.conversations.find(c => c.conversation_id === this.conversationId); if (conv && conv.unread_count > 0) { this._unreadBanner.style.display = 'flex'; } } }); } } async _loadMessages() { if (!this.sdk || !this.conversationId) return; try { const msgs = await this.sdk.getMessages(this.conversationId, { limit: 30 }); this.messages = msgs || []; this._renderMessages(); this._scrollBottom(); } catch (e) { this._showError('消息加载失败'); } } _renderMessages() { if (!this.messages.length) { this._body.innerHTML = '
💬暂无对话消息
发送第一条消息开始咨询
'; return; } this._body.innerHTML = ''; if (this.messages.length > 20) { const loadMore = document.createElement('div'); loadMore.className = 'load-more'; loadMore.textContent = '加载更早消息…'; loadMore.addEventListener('click', async () => { const oldest = this.messages[0]; if (!oldest) return; try { const older = await this.sdk.getMessages(this.conversationId, { before: oldest.msg_id, limit: 20 }); if (older && older.length) { this.messages = [...older, ...this.messages]; this._renderMessages(); } else { loadMore.textContent = '没有更多消息'; } } catch(e) { loadMore.textContent = '加载失败'; } }); this._body.appendChild(loadMore); } this.messages.forEach(msg => { this._body.appendChild(this._createBubble(msg)); }); } _createBubble(msg) { const isSeller = msg.sender_id !== this.userId; const isSystem = msg.msg_type === 'system'; const bubble = document.createElement('div'); bubble.className = `trade-msg ${isSystem ? 'system' : isSeller ? 'seller' : 'buyer'}`; bubble.dataset.msgId = msg.msg_id; let html = ''; if (isSeller && !isSystem) { html += '卖家'; } else if (msg.sender_name) { html += `${this.escapeHtml(msg.sender_name)}`; } html += this.escapeHtml(msg.content || ''); if (msg.created_at) { const time = new Date(msg.created_at).toLocaleString('zh-CN', { hour: '2-digit', minute: '2-digit' }); html += `
${time}
`; } bubble.innerHTML = html; return bubble; } async _send() { const text = this._input.value.trim(); if (!text || !this.sdk || !this.conversationId) return; this._input.value = ''; this._input.style.height = 'auto'; this._sendBtn.disabled = true; // 乐观更新 const optimistic = { msg_id: `temp_${Date.now()}`, sender_id: this.userId, content: text, msg_type: 'text', created_at: new Date().toISOString(), }; this.messages.push(optimistic); this._renderMessages(); this._scrollBottom(); try { await this.sdk.sendWithAck(this.conversationId, text); } catch (e) { this._showError('发送失败'); } } _toggle() { this._collapsed = !this._collapsed; this._el.classList.toggle('collapsed', this._collapsed); this._headerToggle.textContent = this._collapsed ? '展开' : '收起'; } _expand() { this._collapsed = false; this._el.classList.remove('collapsed'); this._headerToggle.textContent = '收起'; this._unreadBanner.style.display = 'none'; // 标记已读 if (this.sdk && this.conversationId && this.messages.length) { const last = this.messages[this.messages.length - 1]; if (last.msg_id && typeof last.msg_id === 'number') { this.sdk.markReadWS(this.conversationId, last.msg_id); } } } _scrollBottom() { requestAnimationFrame(() => { this._body.scrollTop = this._body.scrollHeight; }); } _showError(msg) { const errEl = document.createElement('div'); errEl.className = 'trade-chat-error'; errEl.innerHTML = `⚠️ ${this.escapeHtml(msg)}`; this._el.insertBefore(errEl, this._body); setTimeout(() => errEl.remove(), 4000); } escapeHtml(text) { if (!text) return ''; const d = document.createElement('div'); d.textContent = text; return d.innerHTML; } } global.TradeChat = TradeChat; })(typeof window !== 'undefined' ? window : this);