You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
9.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// pages/chat/chat.js
Page({
data: {
peer: { name: '', avatar: '', openId: '', userId: '' },
myOpenId: '',
myAvatar: '',
myUserId: '',
productId: '',
sessionKey: '',
sessionKeyUser: '',
messages: [],
textInput: '',
sending: false,
uploading: false,
scrollInto: '',
watcher: null
},
async onLoad(options) {
// 参数toUserId(推荐), toOpenId(兼容), toName, productId
const toOpenId = options.toOpenId || '';
const toName = decodeURIComponent(options.toName || '');
const productId = options.productId || '';
const toUserId = options.toUserId || '';
const myOpenId = await this.ensureOpenId();
const myAvatar = (wx.getStorageSync('userInfo') || {}).avatar || '';
const myUserId = (wx.getStorageSync('userInfo') || {})._id || '';
const sessionKey = [myOpenId, toOpenId].sort().join('|');
const sessionKeyUser = (toUserId && myUserId) ? [myUserId, toUserId].sort().join('|') : '';
this.setData({
peer: { name: toName || '对话', avatar: '', openId: toOpenId, userId: toUserId },
myOpenId,
myAvatar,
myUserId,
productId,
sessionKey,
sessionKeyUser
});
await this.loadPeerInfo();
await this.loadMessages();
this.startWatch();
},
onUnload() {
try { if (this.data.watcher) this.data.watcher.close(); } catch(e){}
},
goBack() { wx.navigateBack({ delta: 1 }); },
async ensureOpenId() {
let openid = wx.getStorageSync('openid');
if (!openid) {
try {
const result = await wx.cloud.callFunction({ name: 'quickstartFunctions', data: { type: 'getOpenId' } });
if (result.result && result.result.openid) {
openid = result.result.openid;
wx.setStorageSync('openid', openid);
}
} catch (err) { console.error('获取openid失败:', err); }
}
return openid;
},
async loadPeerInfo() {
try {
const db = wx.cloud.database();
let u = null;
if (this.data.peer.userId) {
const byId = await db.collection('T_user').doc(this.data.peer.userId).get();
u = byId.data || null;
}
if (!u && this.data.peer.openId) {
const byOpen = await db.collection('T_user').where({ _openid: this.data.peer.openId }).limit(1).get();
u = (byOpen.data && byOpen.data[0]) || null;
}
if (u) {
const name = u.sname || u.nickName || this.data.peer.name || '对话';
const avatar = u.avatar || this.data.peer.avatar || '';
const openId = u._openid || this.data.peer.openId || '';
const userId = u._id || this.data.peer.userId || '';
this.setData({ peer: { ...this.data.peer, name, avatar, openId, userId } });
}
} catch(e){}
},
async loadMessages() {
try {
const db = wx.cloud.database();
const res = await db.collection('T_message').where({ toUserId: this.data.peer.userId, fromUserId: this.data.myUserId }).limit(1).get();
const doc = (res.data && res.data[0]) || null;
if (!doc) {
await db.collection('T_message').add({ data: { toUserId: this.data.peer.userId, fromUserId: this.data.myUserId, message: [] } });
this.setData({ messages: [] });
return;
}
const list = (doc.message || []).map(m => ({ ...m, _id: m._id || `${m.timestamp}`, timeText: this.formatTime(m.timestamp) }));
this.setData({ messages: list });
this.scrollToBottom();
} catch(err) { console.error('加载消息失败:', err); }
},
startWatch() {
try {
const db = wx.cloud.database();
const watcher = db.collection('T_message').where({ toUserId: this.data.peer.userId, fromUserId: this.data.myUserId })
.watch({
onChange: snapshot => {
const doc = (snapshot.docs && snapshot.docs[0]) || null;
const list = doc ? (doc.message || []).map(m => ({ ...m, _id: m._id || `${m.timestamp}`, timeText: this.formatTime(m.timestamp) })) : [];
this.setData({ messages: list });
this.scrollToBottom();
},
onError: err => { this.startPolling(); }
});
this.setData({ watcher });
} catch(e) {
this.startPolling();
}
},
startPolling() {
if (this._pollTimer) clearInterval(this._pollTimer);
this._pollTimer = setInterval(()=>this.loadMessages(), 3000);
},
onTextInput(e) { this.setData({ textInput: e.detail.value }); },
async sendText() {
const content = (this.data.textInput || '').trim();
if (!content) return;
this.setData({ sending: true });
try {
await this._sendMessage({ contentType: 'text', content });
this.setData({ textInput: '' });
} catch(err) {
wx.showToast({ title: '发送失败', icon: 'none' });
} finally {
this.setData({ sending: false });
}
},
async chooseImage() {
this.setData({ uploading: true });
try {
const pick = await wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album','camera'] });
const filePath = pick.tempFilePaths[0];
const cloudPath = `chat-images/${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`;
const up = await wx.cloud.uploadFile({ cloudPath, filePath });
// 可直接用 fileID也尝试获取临时URL用于展示
let imageUrl = up.fileID;
try {
const tmp = await wx.cloud.getTempFileURL({ fileList: [up.fileID] });
if (tmp.fileList && tmp.fileList[0] && tmp.fileList[0].tempFileURL) imageUrl = tmp.fileList[0].tempFileURL;
} catch(e){}
await this._sendMessage({ contentType: 'image', content: up.fileID, imageUrl });
} catch(err) {
wx.showToast({ title: '选择图片失败', icon: 'none' });
} finally { this.setData({ uploading: false }); }
},
async _sendMessage({ contentType, content, imageUrl }) {
try {
const db = wx.cloud.database();
const _ = db.command;
const msg = {
fromUserId: this.data.myUserId,
fromOpenId: this.data.myOpenId,
contentType,
content,
imageUrl: imageUrl || '',
timestamp: Date.now()
};
const a = { toUserId: this.data.peer.userId, fromUserId: this.data.myUserId };
const b = { toUserId: this.data.myUserId, fromUserId: this.data.peer.userId };
const chkA = await db.collection('T_message').where(a).limit(1).get();
if (!(chkA.data && chkA.data[0])) { await db.collection('T_message').add({ data: { toUserId: a.toUserId, fromUserId: a.fromUserId, message: [] } }); }
try { await db.collection('T_message').where(a).update({ data: { message: _.push(msg) } }); }
catch(e){
if (String(e.errMsg||'').includes('does not exist')||e.errCode===-502005){ await wx.cloud.callFunction({ name:'quickstartFunctions', data:{ type:'createMessageCollection' } }); await db.collection('T_message').where(a).update({ data: { message: _.push(msg) } }); }
else { throw e; }
}
const chkB = await db.collection('T_message').where(b).limit(1).get();
if (!(chkB.data && chkB.data[0])) { await db.collection('T_message').add({ data: { toUserId: b.toUserId, fromUserId: b.fromUserId, message: [] } }); }
try { await db.collection('T_message').where(b).update({ data: { message: _.push(msg) } }); }
catch(e){
if (String(e.errMsg||'').includes('does not exist')||e.errCode===-502005){ await wx.cloud.callFunction({ name:'quickstartFunctions', data:{ type:'createMessageCollection' } }); await db.collection('T_message').where(b).update({ data: { message: _.push(msg) } }); }
}
const messages = [...this.data.messages, { ...msg, _id: `local-${msg.timestamp}`, timeText: this.formatTime(msg.timestamp) }];
this.setData({ messages });
this.scrollToBottom();
} catch (err) {
console.error('发送消息失败:', err);
throw err;
}
},
async loadMore() {
try {
const oldest = this.data.messages[0]?.timestamp || 0;
if (!oldest) return;
const res = await wx.cloud.callFunction({
name: 'quickstartFunctions',
data: {
type: 'getChatMessages',
toUserId: this.data.peer.userId || '',
toOpenId: this.data.peer.openId || '',
before: oldest,
limit: 50
}
});
const ret = res.result || {};
const list = (ret.data || []).map(m => ({ ...m, timeText: this.formatTime(m.timestamp) }));
const merged = [...list, ...this.data.messages];
this.setData({ messages: merged });
} catch (e) {}
},
previewImage(e) {
const url = e.currentTarget.dataset.url;
wx.previewImage({ urls: [url], current: url });
},
async onMessageLongPress(e) {
const id = e.currentTarget.dataset.id;
const mine = !!e.currentTarget.dataset.mine;
if (!mine) return;
try {
wx.showActionSheet({ itemList: ['撤回'], success: async (res) => {
if (res.tapIndex === 0) {
try {
const ret = await wx.cloud.callFunction({ name: 'quickstartFunctions', data: { type: 'revokeChatMessage', id } });
if (ret.result && ret.result.success) {
await this.loadMessages();
} else {
wx.showToast({ title: '撤回失败', icon: 'none' });
}
} catch (err) { wx.showToast({ title: '撤回失败', icon: 'none' }); }
}
}});
} catch (e) {}
},
scrollToBottom() {
const last = this.data.messages[this.data.messages.length - 1];
if (last) this.setData({ scrollInto: `msg-${last._id || 'bottom-anchor'}` });
else this.setData({ scrollInto: 'bottom-anchor' });
},
formatTime(ts) {
const d = new Date(ts);
const pad = n => (n<10?'0'+n:n);
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
});